Vulkan【16】绘制立方体

绘制立方体

本节的代码是 15-draw_cube.cpp

你快完成了!

下面是让你的Vulkan图像出现在屏幕上的最后步骤:

等待交换链缓冲区

在开始绘制任何东西之前,样例程序需要一个目标交换链图像来呈现。vkAcquireNextImageKHR()函数用于将索引放入交换链图像列表中,因此它知道哪个framebuffer会被用作渲染目标。即下一个可供渲染的图像。

res = vkCreateSemaphore(info.device, &imageAcquiredSemaphoreCreateInfo,
                        NULL, &imageAcquiredSemaphore);

// Get the index of the next available swapchain image:
res = vkAcquireNextImageKHR(info.device, info.swap_chain, UINT64_MAX,
                            imageAcquiredSemaphore, VK_NULL_HANDLE,
                            &info.current_buffer);

对于第一帧,可能不需要使用信号量,因为交换链中的所有图像都是可用的。但是,在继续向GPU提交命令之前,确保图像已经准备好仍然是一种很好的做法。如果这个样例被更改为渲染多个帧,比如动画,那么就有必要等到硬件完成后再使用它。

请注意,您现在没有等待任何东西。你只是在创建这个信号量,并将它与图像关联起来,这样就可以使用信号量来延迟命令缓冲区的提交,直到图像准备好为止。

开始渲染过程

您已经在前一节中定义了渲染过程,因此在命令缓冲区中放置一个“开始渲染过程”命令就可以开始渲染过程,这很简单:

VkRenderPassBeginInfo rp_begin;
rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
rp_begin.pNext = NULL;
rp_begin.renderPass = info.render_pass;
rp_begin.framebuffer = info.framebuffers[info.current_buffer];
rp_begin.renderArea.offset.x = 0;
rp_begin.renderArea.offset.y = 0;
rp_begin.renderArea.extent.width = info.width;
rp_begin.renderArea.extent.height = info.height;
rp_begin.clearValueCount = 2;
rp_begin.pClearValues = clear_values;
vkCmdBeginRenderPass(info.cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);

请注意,您已经创建了一个命令缓冲区,并通过调用init_command_buffer()execute_begin_command()将其转换为记录模式。

您提供了先前定义的渲染过程,以及由vkAcquireNextImageKHR()返回的索引所选择的framebuffer。

清空值被初始化,并将背景颜色设置为黑灰色,且深度缓冲区被设置成“远”值(clear_values)。

其余的信息都在info.render_pass中,正如您在前面设置的那样,然后您继续插入这个命令,以启动渲染过程到命令缓冲区。

绑定管线

下一步将管线绑定到命令缓冲区:

vkCmdBindPipeline(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline);

您在前一节中定义了管道,并在这将其绑定,然后告诉GPU如何渲染稍后将要出现的图形。

VK_PIPELINE_BIND_POINT_GRAPHICS告诉GPU这是一个图形管线而不是计算管线。

注意,由于这个命令是一个命令缓冲区命令,所以一个程序可以定义多个图形管道,并在单个命令缓冲区中切换它们。

绑定描述符集合

回想一下,我们前面定义的描述符集描述了着色程序期望如何找到它的输入数据,比如MVP转换。把这些信息告诉GPU:

vkCmdBindDescriptorSets(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
                        info.pipeline_layout, 0, 1,
                        info.desc_set.data(), 0, NULL);

请再次注意,如果您想要更改shader程序如何找到其数据,您可以在命令缓冲区的中间绑定不同的描述符。例如,如果您想要改变命令缓冲区中间的转换,您可以使用不同的描述符来指向不同的MVP转换。

绑定顶点缓冲区

您创建了一个顶点缓冲,并在“顶点缓冲区”示例中填充了顶点数据。在这里,告诉GPU如何找到它:

const VkDeviceSize offsets[1] = {0};
vkCmdBindVertexBuffers(info.cmd, 0, 1, &info.vertex_buffer.buf, offsets);

这个命令将一个或多个顶点缓冲绑定到命令缓冲区。在这种情况下,您只绑定一个缓冲区。

设置Viewport 和Scissors 矩形

之前指出,viewport和scissors 是动态的状态,这意味着它们可以用一个命令缓冲命令来设置。所以,你需要把它们设置在这里。下面是用来设置viewport的 init_viewports()中的代码:

info.viewport.height = (float)info.height;
info.viewport.width = (float)info.width;
info.viewport.minDepth = (float)0.0f;
info.viewport.maxDepth = (float)1.0f;
info.viewport.x = 0;
info.viewport.y = 0;
vkCmdSetViewport(info.cmd, 0, NUM_VIEWPORTS, &info.viewport);

scissors 矩形的代码和上述类似

最好将这些值设为动态的,因为执行过程中窗口的大小发生变化,那么许多应用程序需要更改这些值。这就避免了在窗口大小发生变化时重新构建管线。

绘制顶点

最后,发出一个绘图命令,告诉GPU将顶点发送到管线中,并完成渲染过程。

vkCmdDraw(info.cmd, 12 * 3, 1, 0, 0);
vkCmdEndRenderPass(info.cmd);

vkCmdDraw命令告诉GPU一次性画36个顶点。你已经在管线章节中配置了几何装配,去渲染独立的三角形,所以意味着最终有12个三角形被绘制。

vkCmdEndRenderPass 命令标志着渲染过程的结束,但是命令缓冲区仍然是“开放的”,并且示例还没有停止记录命令。

转换交换链图像以供呈现

当GPU在渲染时,目标交换链的图像布局是VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,这是GPU渲染的最佳布局。在本教程之前的章节定义渲染过程时,你在定义子过程里设置了这个布局。但是这个布局可能不是将图像扫描到显示设备的最佳布局。例如,用于渲染的最优GPU内存布局可能是“平铺的”,正如本教程的“渲染过程”部分所讨论的那样。但是显示硬件可能更喜欢线性内存布局。您可以使用VK_IMAGE_LAYOUT_PRESENT_SRC_KHR布局来指定图像将要呈现给显示器。

您已经在渲染过程章节中处理了这个布局转换,在颜色图像附件的描述中指定finalLayoutVK_IMAGE_LAYOUT_PRESENT_SRC_KHR

VkAttachmentDescription attachments[2];
attachments[0].format = info.format;
attachments[0].samples = NUM_SAMPLES;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
attachments[0].flags = 0;

内存屏障方式(Memory barrier approach)

注意,还有另一种方法可以通过在命令缓冲器中记录另一个内存屏障命令来完成这种布局转换。这种替代方法在某些情况下可能很有用,例如在不使用渲染过程的队列提交的情况下。这种情况的一个例子可以在copy_blit_image示例中找到,它不是本教程的一部分,但是可以在与这些教程示例相同的文件夹中找到。

本例中,你是用一个渲染过程,但你仍然可以使用这个可选的方式。通过设置initialLayoutfinalLayout为相同的颜色附件:

attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

这就要求您使用另一个管线内存屏障执行这个转换,就像set_image_layout()执行布局转换一样:

VkImageMemoryBarrier prePresentBarrier = {};
prePresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
prePresentBarrier.pNext = NULL;
prePresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
prePresentBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
prePresentBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
prePresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
prePresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
prePresentBarrier.subresourceRange.baseMipLevel = 0;
prePresentBarrier.subresourceRange.levelCount = 1;
prePresentBarrier.subresourceRange.baseArrayLayer = 0;
prePresentBarrier.subresourceRange.layerCount = 1;
prePresentBarrier.image = info.buffers[info.current_buffer].image;
vkCmdPipelineBarrier(info.cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
                     VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0,
                     NULL, 1, &prePresentBarrier);

上面的代码不在本例中,但是可以在 copy_blit_image示例中找到。

一旦该命令在渲染过程结束后执行,图像缓冲区就可以显示了。当然,您不需要转换深度缓冲图像布局。

提交命令缓冲区

请记住,您还没有向GPU发送任何命令。您刚刚将它们记录到命令缓冲区中。但是现在你已经完成了记录:

res = vkEndCommandBuffer(info.cmd);

你需要创建一个围栏,你可以用它来判断GPU何时完成。你需要知道GPU什么时候完成,这样你就不会很快地把呈现展示给显示器了。

VkFenceCreateInfo fenceInfo;
VkFence drawFence;
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.pNext = NULL;
fenceInfo.flags = 0;
vkCreateFence(info.device, &fenceInfo, NULL, &drawFence);

现在我们可以提交命令缓冲区:

const VkCommandBuffer cmd_bufs[] = {info.cmd};
VkPipelineStageFlags pipe_stage_flags =
    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
VkSubmitInfo submit_info[1] = {};
submit_info[0].pNext = NULL;
submit_info[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info[0].waitSemaphoreCount = 1;
submit_info[0].pWaitSemaphores = &imageAcquiredSemaphore;
submit_info[0].pWaitDstStageMask = &pipe_stage_flags;
submit_info[0].commandBufferCount = 1;
submit_info[0].pCommandBuffers = cmd_bufs;
submit_info[0].signalSemaphoreCount = 0;
submit_info[0].pSignalSemaphores = NULL;
res = vkQueueSubmit(info.queue, 1, submit_info, drawFence);

VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT管线中完成命令执行的最后一步。

imageAcquiredSemaphore用于等待图像在绘图前准备好,正如本节的顶部所解释的那样。这使得驱动程序在imageAcquiredSemaphore上等待,以知道交换链图像是可用的。然后,它将命令提交给GPU。当GPU执行这些命令时,它会通知栅drawFence,表明绘制已经完成了。

等待命令缓冲区完成

vkWaitForFences() 等待命令缓冲区完成执行。
它在循环里被调用,以防命令比预期花费更长的时间完成。

do {
    res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT);
} while (res == VK_TIMEOUT);

在这一点上,您知道交换链的图像缓冲区已经准备好呈现给显示器了。

呈现交换链缓冲给显示器

将交换链的图像呈现给显示器非常简单:

VkPresentInfoKHR present;
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext = NULL;
present.swapchainCount = 1;
present.pSwapchains = &info.swap_chain;
present.pImageIndices = &info.current_buffer;
present.pWaitSemaphores = NULL;
present.waitSemaphoreCount = 0;
present.pResults = NULL;
res = vkQueuePresentKHR(info.queue, &present);

您现在应该在屏幕上看到一个立方体!

Draw Cube

© Copyright 2016-2017 LunarG, Inc

要使用Vulkan绘制一个三角形,需要经过以下步骤: 1. 创建一个Vulkan实例 2. 创建一个逻辑设备 3. 创建一个窗口表面 4. 创建一个交换链 5. 创建渲染通道和帧缓冲区 6. 创建着色器模块 7. 创建管线布局和管线 8. 分配顶点缓冲区 9. 开始绘制 下面是一个简单的示例代码,用于绘制一个三角形: ``` // 顶点数据 std::vector<Vertex> vertices = { { { 0.0f, -0.5f },{ 1.0f, 0.0f, 0.0f } }, { { 0.5f, 0.5f },{ 0.0f, 1.0f, 0.0f } }, { { -0.5f, 0.5f },{ 0.0f, 0.0f, 1.0f } } }; // 创建顶点缓冲区 VkBuffer vertexBuffer; VkDeviceMemory vertexBufferMemory; createBuffer(sizeof(vertices[0]) * vertices.size(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory); // 在CPU上填充顶点缓冲区 void* data; vkMapMemory(device, vertexBufferMemory, 0, sizeof(vertices[0]) * vertices.size(), 0, &data); memcpy(data, vertices.data(), sizeof(vertices[0]) * vertices.size()); vkUnmapMemory(device, vertexBufferMemory); // 创建渲染通道和帧缓冲区 VkRenderPass renderPass; createRenderPass(renderPass); VkFramebuffer framebuffer; createFramebuffer(framebuffer); // 创建着色器模块 VkShaderModule vertShaderModule; VkShaderModule fragShaderModule; createShaderModule("vert.spv", vertShaderModule); createShaderModule("frag.spv", fragShaderModule); // 创建管线布局和管线 VkPipelineLayout pipelineLayout; VkPipeline pipeline; createPipelineLayout(pipelineLayout); createPipeline(pipeline, pipelineLayout, renderPass, vertShaderModule, fragShaderModule); // 开始绘制 VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f }; VkRenderPassBeginInfo renderPassInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = framebuffer; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = swapChainExtent; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkBuffer vertexBuffers[] = { vertexBuffer }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); endSingleTimeCommands(commandBuffer); ``` 这段代码中,我们首先创建一个包含三个顶点的顶点缓冲区,然后使用着色器模块、管线布局和管线来定义如何渲染这些顶点。最后,我们通过调用`vkCmdDraw`函数来绘制三角形。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值