9.Command Recording and Drawing
[TOC]
介绍
内容
- Clearing a color image
- Clearing a depth-stencil image
- Clearing render pass attachments
- Binding vertex buffers
- Binding an index buffer
- Providing data to shaders through push constants
- Setting viewport state dynamically
- Setting scissor state dynamically
- Setting line width state dynamically
- Setting depth bias state dynamically
- Setting blend constants state dynamically
- Drawing a geometry
- Drawing an indexed geometry
- Dispatching compute work
- Executing a secondary command buffer inside a primary command buffer
- Recording a command buffer that draws a geometry with a dynamic viewport
and scissor states- Recording command buffers on multiple threads
- Preparing a single frame of animation
- Increasing performance through increasing the number of separately rendered
frames
简述
Vulkan设计为图形和计算API.它的主要目的是允许我们用多个厂商生产的grahpics 硬件生成dynamic images.
已经了解了如何创建和管理资源以及在shaders中使用.了解了不同的shader stages和pipeline objects控制rendering state或分发computational work.最后一件事是需要知道如何绘制images的知识.
本文讨论commands.受线学习drawing commands和在我们的source code里管理它们以达到最高性能.最后vulkan API里最强力的能力–在多线程进行record command buffers.
准备
Clearing a color image
vulkan里,给render pass的attachment description设置loadOp为VK_ATTACHMENT_LOAD_OP_CLEAR以clear.
有时,我们不想这么做,需要隐式实现
1 | vkCmdClearColorImage( command_buffer, image, image_layout, &clear_color, |
提供image的handle,layout,sub-resources的数组(mipmap level and/or array layers).
只能清理color image.以及transfer dst usage images.
Clearing a depth-stencil image
1 | vkCmdClearDepthStencilImage( command_buffer, image, image_layout, |
VkClearDepthStencilValue
- depth when a depth aspect should be cleared
- stencil for a value used to clear the stencil aspect
Clearing render pass attachments
vkCmdClearAttachments
有时清理attachements of sub-passes.
1 | vkCmdClearAttachments( command_buffer, |
VkClearAttachment
- aspectMask attachment的aspect(color,depth,stencil)
- aspectMask 置为VK_IMAGE_ASPECT_COLOR_BIT,指明colorAttachment为当前sub-pass里的color attachemnt,否则忽略
- clearValue
VkClearRect
- top-left,width,height
Binding vertex buffers
当进行几何绘制需要指明vertiices数据.至少需要vertex positions(其实也不是必须的,可以shader里生成…).其他数据还有normal,tangent/bitangent,colors,teexture coordinates.这些数据来源于usage为vertex buffer的buffers.需要在dc前绑定这些buffers.
VertexBufferParameters
1 | struct VertexBufferParameters { |
1 | std::vector<VkBuffer> buffers; |
Binding an index buffer
index buffer的usage为index buffer,type为,比如VK_INDEX_TYPE_UINT16,VK_INDEX_TYPE_UINT32
1 | vkCmdBindIndexBuffer( command_buffer, buffer, memory_offset, index_type ); |
Providing data to shaders through push constants
大多数时间使用descriptor set通过buffers或images提供大量数据.为了快速方便提供数据给shader,可以使用push constants.
1 | vkCmdPushConstants( command_buffer, |
硬件最少支持128bytes.
一个例子
1 | std::array<float, 4> color = { 0.0f, 0.7f, 0.4f, 0.1f }; |
1 | ProvideDataToShadersThroughPushConstants(...) |
settings
Setting viewport state dynamically
VkViewport
- left :up left for x
- top: up left for y
- width
- height
1
2
3 > vkCmdSetViewport( command_buffer, first_viewport,
> static_cast<uint32_t>(viewports.size()), viewports.data() );
>
stages dynamic指明是动态的,但数量是创建时就固定了的
Setting scissor state dynamically
scissor额外再viewport dimentsion内添加了一个渲染rectangle区域.总开启.可以静态设置,也可以cb动态设置
VkRect2D
- x:horizontal offset (in pixels) from up left corner of viewport for x number of offset
- y:upper left corner
- width
- height
vkCmdSetScissor
1
2
3 > vkCmdSetScissor( command_buffer, first_scissor,
> static_cast<uint32_t>(scissors.size()), scissors.data() );
>
Setting line width states dynamically
1 | vkCmdSetLineWidth( command_buffer, line_width ) |
Setting depth bias state dynamically
depth bias可以修正fragment的depth value计算.
depth bias可以对fragment的depth进行offset.通常绘制非常近的objects用到.比如墙上的pictures or posters.这类objects绘制会有z-fighting.
depth bias修正value计算–存储在depth attachment里的depth value.但不会影响渲染的image.也就是不会影响距离感.修正是基于constant factor和fragment的slope.也指明depth bias(clamp)能加的最大或最小值.
1 | vkCmdSetDepthBias( command_buffer, constant_factor, clamp, slope_factor ); |
Setting blend constants states dynamically
blend用于透明物体模拟.通过控制混合英子和操作,得到最终结果.也可以使用constant color进行计算.constant color可以动态设置.
1 | vkCmdSetBlendConstants( command_buffer, blend_constants.data() ); |
drawing
Drawing a geometry
1 | vkCmdDraw( command_buffer, |
instance在不改变vertex进行通mesh绘制很有用(ref specifying pipeline vertex binding description,attribute description,and input state,chapter 8,graphics and compute piipeline).
vulkan里没有Default state.
比如descriptor sets或dynamic pipeline states.每次record cb,所有需求的descriptor sets需要绑定给成本,类似作为dynamic的pipeline state必须用对于函数提供值,render pass必须在合适的command buffer里开始.
drawing cam be performed only inside the render pass.
Drawing an indexed geometry
最常用的.
vkCmdDrawIndexed()
去重复顶点,需要额外的index buffer.但在vertex有很多额外数据时很有必要(normal,tangent,bitangent,two texture coordinates).
$\color {red}{新的概念(reuse vertex)}$:Indexed drawing允许硬件重用vertex caching里已经计算的vertices.根据indices,如果已经计算过,reuse.
1 | vkCmdDrawIndexed( command_buffer, index_count, instance_count, first_index, |
Dispatching compute work
compute pipeline
resource通过且只能通过descriptor sets
可用来进行image post-processing,color correction or blur.physical 计算.
compute shaderdispatched in groups.
vkCmdDispatch( command_buffer, x_size, y_size, z_size );
workgroups
maxComputeWorkGroupCount[3]
硬件最少支持65,535
不能再render passes里进行
Executing a secondary command buffer inside a primary command buffer
vulkan里可以record2中command buffers-primary and secondary.primary command buffers能直接submit到queues.secondary command buffers只能在primary command buffer里执行.
vkCmdExecuteCommands
一般primary command buffers已经足够用来rendering或computing work.但是有时需要把工作分到两种command buffer 里.当想图形硬件执行secondary command buffers时我们能在primary command buffer里这样做:
1 | vkCmdExecuteCommands( command_buffer, |
example
Recording a command buffer that draws a geometry with a dynamic viewport and scissor states
1 | struct Mesh { |
1 | if( !BeginCommandBufferRecordingOperation( command_buffer, |
image memory barrier
1 | if( present_queue_family_index != graphics_queue_family_index ) { |
start render pass,bind pipeline object
1 | BeginRenderPass( command_buffer, render_pass, framebuffer, { { 0, 0 }, |
设置dynamic states.viewport ,scissor..bind a buffer for vertex data
1 | VkViewport viewport = { |
descriptor sets,shaders访问
1 | BindDescriptorSets( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, |
现在可以绘制几何体了.当然还可以设置index buffer,提供push constants值.
1 | for( size_t i = 0; i < geometry.Parts.size(); ++i ) { |
在停止record command buffer前,需要end render pass.之后需要另一个transition on a swapchain image.当完成在single frame of animation上进行绘制,想要在swapchain image上显示.为此需要改变它的layout为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,因为这是presentation engine正确显示image要求的.这个不走需要显示进行.
$\color{red}{注意}$,如果用于graphics operations和presentations的queues不同,需要一个queue ownership transfer.这通过另一个image memory barrier完成.之后,我们能停止record a command buffer.
1 | EndRenderPass( command_buffer ); |
我们能用这个cb并submit it to a (graphic) queue.只能submit一次,因为flag 为VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT.
submit这个cb之后,能显示到swapchain image上.需要注意submission和presentation operations需要进行同步
advanced
*Recording command buffers on multiple threads
自定义结构
1 | struct CommandBufferRecordingThreadParameters { |
每个线程一个,记录cbs.RecordingFunction定义了一个在独立thread里record command buffer的function.
为了多线程使用vulkan,需要记住一些规则.
第一不能再多线程修改同一个object.比如不能再多线程从同一个pool allocate command buffers或不能从多线程更新descriptor set.
只有再资源时只读的或者时访问分开的资源吗,我们能从多线程访问.但很难追踪哪个资源时哪个线程创建的.通常,资源创建和修改再主线程(rendering thread).
在Vulkan中使用多线程最常见的场景是并发地记录命令缓冲区.这个操作花费大量时间,分开到多线程进行时很有道理的.
当多线程进行record command buffers时需要多线程和每个线程对应一个独立的command pool
command buffer recording不影响其他资源(除了pool).只准备给一个queue submit commands,所以能record任何操作使用任何资源.比如记录多个操作访问同样的图片或descriptor sets.同样的pipelines能同时绑定不同的command buffers.我们也能record operations绘制到同样的attachments里.
1 | std::vector<std::thread> threads( threads_parameters.size() ); |
所有thread完成record cbs后需要收集到一起然后submit它们到queue.
真实app里会避免这样创建和销毁threads的方式.相反,使用已有的job/task system并使用它们record需要的cbs.如图.
submission只能再单线程进行(queus,similarly to other resources,cannot be accessed concurrently),需要等待所有线程完成.
1 | std::vector<VkCommandBuffer> command_buffers( threads_parameters.size() ); |
submitting cbs一次只能再一个线程进行.
swapchain object也会发生同样的情况.同时只能在一个线程acquire和present swapchain images.
需要留意将layout 从VK_IMAGE_LAYOUT_PRESENT_SRC_KHR (or VK_IMAGE_LAYOUT_UNDEFINED)转换为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR.
Preparing a single frame of animation
Preparing a single frame of animation can be divided into five steps:
- Acquiring a swapchain image.
- Creating a framebuffer.
- Recording a command buffer.
- Submitting the command buffer to the queue.
- Presenting an image.
1 | uint32_t image_index; |
1 | std::vector<WaitSemaphoreInfo> wait_semaphore_infos = wait_infos; |
fence用于GPU确定cb结束
*Increasing performance through increasing the number of separately rendered frames
在等待cb 运行结束这段时间是浪费了的.所以需要独立render multiple frames of animation .
自定义结构体
1 | struct FrameResources { |
用于管理单帧生命周期内管理的资源.
rendering animation是要给循环.一帧绘制,一帧显示.
需要准备多份set
Tests have shown that increasing the number of frame resources from one to two may increase the performance by 50%.
Adding a third set increases the performance further, but the growth isn’t as big this time.
So, the performance gain is smaller with each additional set of frame resources. Three sets of rendering resources seems like a good choice, but we should perform our own tests and see what is best for our specific needs.
check
1 | static uint32_t frame_index = 0; |
1 | InitVkDestroyer( logical_device, current_frame.Framebuffer ); |