上一节聊到了instance、physical device、logical device、queue的概念,这一节我们继续往上聊,下一个就是command buffer


Command buffer用于录制command,然后submit到queue中,进行执行。Command buffer有两种类型,primary command buffer,可以调用secondary command buffer,可以直接submit给queue。Secondary command buffer,可以被primary command buffer调用,不能直接submit给queue。

被录制的command 包含 bind pipelines 和 descriptor set to command buffer 的 command,改变 dynamic state的command,绘制 command(for graphics rendering),dispatch command(for compute),调用 secondary command buffers的command(for primary command buffers only),copy buffer和images的command等等

每个command buffer的状态都独立于其它command buffer。 primary 和 secondary command buffer,以及secondary comnad buffer之间的状态也没有继承关系。当开始录制一个command buffer的时候,该command buffer中的所有状态都是未定义。当primary command buffer 录制“执行一个secondary command buffer"的时候,,secondary command buffer不从 primary command buffer 继承任何状态,并且在记录完毕后,primary command buffer的所有状态都未定义。此规则有一个例外————如果primary command buffer在一个render pass instance中,则执行secondary command buffer不会干扰render pas和subpass的状态。针对一些依赖状态的命令(比如draw和dispatch),这些command 关联的任何状态都不能是未定义。

除非另有规定,并且在没有显示同步的情况下,通过command buffers提交到queue的各种commands可以以相对于彼此的任意顺序,并发执行。此外,如果没有显示的内存依赖关系,这些command的内存side-effect对其他command可能不直接可见。不管是command buffer内部的commands或者是同一个queue的command buffers之间,都是如此。有关commands之间的隐式和显式同步的相关信息,请参加下一章:同步(synchronization)

下面聊一下command buffer的生命周期,每个command buffer都一定存在于其中一个状态中:

  • Initial:当创建一个command buffer的时候,它的初始状态就是initial state。一些命令可以将一个command buffer(或一组command buffers)从executable、recording、invalid状态置回该状态。initial 状态的command buffers只能被moved to recording状态,或者释放(Patrick:还可以变成invalid状态吧?)
  • Recording:vkBeginCommandBuffer将command buffer的状态从initial 状态切换到 recording状态。一旦command buffer处于recording状态,则可以通过vkCmd*命令来对该command buffer进行录制。
  • Executable:vkEndCommandBuffer用于结束一个command buffer的录制,并将其从recording state 切换到 executable state。executable commdn buffers可以被 submitted、reset或者 recorded to another command buffer。
  • Pending:Queue submission of a command buffer将command buffer的状态从executable state切换到pending state。当在pending state的时候,应用程序不得以任何方式修改command buffer————因为device可能正在处理command buffer中录制的command。一旦command buffer执行完毕,该状态将会被revert到executable state(如果该command buffer录制的时候打了 VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT flag,则会被move to invalid state)。a synchronization command should被用于监测这个事情的发生。
  • Invalid:一些操作,比如:修改或者删除command buffer中某个command所使用的资源,都会导致comna的buffer的状态切换到invalid state。invalid state的command buffer只能被reset或者释放。
Vulkan

在command bfufer上操作任何command,都有其自身的要求,它要求command buffer必须处于什么状态,这在该command 的有效使用限制中有详细说明。

reset 一个command buffer 将丢弃先前录制的command,并将command buffer置于initial state。 reset是调用 vkResetCommandBuffer、vkResetCommandPool、或者vkBeginCommandBuffer的一部分(该命令还会使得command buffer处于录制状态)

Secondary command buffers也通过vkCmdExecuteCommands被记录到primary command buffer中。这将两个command buffer的生命周期联系在一起————如果primary command buffer被submit到queue,那么primary command buffer以及其关联的所有secondary command buffer都将move to pending state。一旦primary command buffer执行完毕,其关联的secondary command buffer也就都执行完毕了。当每个command buffer都执行完毕后,它们将分别进入它们相应的完成状态(如上所述,executable state或者invalid state)

当一个secondary command buffer move to invalid state或者initial state,那么它关联的所有primary command buffer都将move to invalid state。而primary command buffer切换状态,不会影响secondary command buffer的状态。(需要注意的是:如果reset或者释放一个primary command buffer,会删除所有关联的secondary command buffer的lifecycle linkage)

Command buffer是从command pool中获取内存创建的,这样的话,可以将多个command buffer的创建成本平摊。command pool是externally synchronized,意味着一个command pool不能被多个线程同时使用。这包括:pool中创建的command buffer录制命令,以及分配、释放和重置command buffer或者pool本身。

VkResult vkCreateCommandPool( VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);

该API用于创建一个Command Pool

第一个输入参数,为用于创建command pool的logical device

第二个输入参数,为一个VkCommandPoolCreateInfo的结构体,用于表明该command pool的属性

第三个输入参数,用于内存分配

第四个输入参数,用于获取生成的command pool

typedef struct VkCommandPoolCreateInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体,必须是NULL VkCommandPoolCreateFlags flags; //a bitmask of VkCommandPoolCreateFlagBits ,用于指出该pool和关联command buffer的usage。如果没有启用protected memory,那么不能将 flags 设置为 VK_COMMAND_POOL_CREATE_PROTECTED_BIT。必须是 VkCommandPoolCreateFlagBits 组合出来的,或者0 uint32_t queueFamilyIndex; //对应上一节中提到的 queueFamilyIndex ,该pool中创建的所有command buffer都必须submit到同一个queue family的queues中。必须是logical device对应的queue family } VkCommandPoolCreateInfo;

typedef enum VkCommandPoolCreateFlagBits { VK_COMMAND_POOL_CREATE_TRANSIENT_BIT = 0x00000001, //意味着pool中分配的command buffer的生命周期很短,很快就会被reset或者free。这个flag可以被设计者用来控制pool中的内存分配机制。 VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT = 0x00000002, //将允许pool中分配的command buffer可以分别被reset到initial state(通过vkResetCommandBuffer或者vkBeginCommandBuffer),如果pool的该flag没有被设置,则其关联的command buffer不能被执行vkResetCommandBuffer。 // Provided by VK_VERSION_1_1 VK_COMMAND_POOL_CREATE_PROTECTED_BIT = 0x00000004, //意味着pool中分配的command buffer是protected command buffer。 } VkCommandPoolCreateFlagBits;

可能会出现的错误:VK_ERROR_OUT_OF_HOST_MEMORY、VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:vkCreateCommandPool(device->get_handle(), &command_pool_info, nullptr, &cmd_pool)

void vkTrimCommandPool( VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);

该API用于 trim一个Command Pool

第一个输入参数,为command pool关联的logical device

第二个输入参数,为一个由device创建的command pool

第三个输入参数,必须是0

trim a command pool会将未使用的内存从command pool回收到系统。该pool分配的command buffers不会被该API所影响。

该命令为应用程序提供了对command pool相关的内部内存分配的一些控制。

未使用的内存通常来自record,然后又reset的command buffer。reset的时候,command buffer会将内存返还给command pool,但是唯一一个将内存从command pool返还给系统的办法是调用vkResetCommandPool,当该pool中还有command buffer正在被使用的时候,无法使用该API。command buffer之后的record操作会重用这块内存,但是由于总内存需求随时间波动,所以未使用的内存可能会累积。

在这种情况下,trim a command pool,将未使用的内存返还给系统就很有用了,将pool分配的内存重置到更平均的值。

然而由于实现使用了不同的分配策略,使得不可能保证所有未使用的内存都被释放回系统。比如pool的实现可能是从系统allocating memory in bulk。这样的话,任何正在使用的command buffer都可能hold住reference to a bulk,即使只有一小部分在使用,而导致无法释放。

所以,trim会减少已分配但未使用的内存,但不能保证理想状况。

trim是一个expensive的操作,不影响经常调用。应该在应用程序判断已经有足够多的内存被释放的时候,值得调用trim的时候,去调用trim。

在CTS中的使用:无

		Host access to commandPool must be externally synchronized
	
VkResult vkResetCommandPool( VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);

该API用于 reset一个Command Pool

第一个输入参数,为command pool关联的logical device

第二个输入参数,为一个由device创建的command pool

第三个输入参数,为a bitmask of VkCommandPoolResetFlagBits。必须是 VkCommandPoolResetFlagBits 组合出来的,或者0

typedef enum VkCommandPoolResetFlagBits { VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT = 0x00000001, //reset a Command pool将其所有资源回收到system } VkCommandPoolResetFlagBits;

reset a command pool会将所有与其关联的资源(关联的command buffers分配的所有资源)返回给command pool。所有与其关联的command buffers都将被重置到initial state。

如果有一个primary command buffer是通过其他VkCommandPool 分配的,状态为recording或者executable state,拥有一个secondary command buffer是由被reset的pool分配出来的,会变成 invalid state。

注意:所有当前pool创建的command buffers,在调用这个命令的时候,必须不能处于pending state。

可能会出现的错误:VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:vkResetCommandPool(context.device, context.per_frame[*image].primary_command_pool, 0);

void vkDestroyCommandPool( VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);

该API用于 destroy 一个Command Pool

第一个输入参数,为command pool关联的logical device

第二个输入参数,为一个由device创建的command pool,或者是 VK_NULL_HANDLE

第三个输入参数,用于内存分配

当pool被 destroy的时候,所有与其关联的 command buffers都被释放

如果有一个primary command buffer是通过其他VkCommandPool 分配的,状态为recording或者executable state,拥有一个secondary command buffer是由被destroy的pool分配出来的,会变成 invalid state。

注意:所有当前pool创建的command buffers,在调用这个命令的时候,必须不能处于pending state。

如果在创建pool的时候使用了 VkAllocationCallbacks , 那么在destroy 的时候也要提供一个相应的callback。反之,这里的第三个参数必须是NULL。

在CTS中的使用:vkDestroyCommandPool(device->get_handle(), cmd_pool, nullptr);

VkResult vkAllocateCommandBuffers( VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);

该API用于通过一个已经存在的command pool创建一个Command Buffer

第一个输入参数,为用于创建command buffer 的logical device

第二个输入参数,为一个 VkCommandBufferAllocateInfo 的结构体,用于表明该command buffer 的属性

第三个输入参数,用于获取生成的command buffer。必须是一个指向 pAllocateInfo->commandBufferCount 个 VkCommandBuffer 元素的数组指针。该数组的尺寸必须大于等于 pAllocateInfo 的成员 commandBufferCount。每个被创建的 command buffer的状态为 initial state

typedef struct VkCommandBufferAllocateInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体,必须是NULL VkCommandPool commandPool; //用于分配command buffer的command pool VkCommandBufferLevel level; //是一个 VkCommandBufferLevel 数值,用于指定 command buffer的level uint32_t commandBufferCount; //从command pool中分配出来command buffers的数量,必须大于0 } VkCommandBufferAllocateInfo;

typedef enum VkCommandBufferLevel { VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0, //指定一个primary command buffer VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1, //指定一个secondary command buffer } VkCommandBufferLevel;

该API可以创建多个command buffers。如果任何一个command buffer创建失败,则必须destroy通过该api已经成功创建的其它command buffers,并将pCommandBuffers数组设置为NULL,并返回错误信息。

可能会出现的错误:VK_ERROR_OUT_OF_HOST_MEMORY、VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:vkAllocateCommandBuffers(context.device, &cmd_buf_info, &per_frame.primary_command_buffer);

		Host access to pAllocateInfo->commandPool must be externally synchronized
	
VkResult vkResetCommandBuffer( VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);

该API用于 reset 一个Command Buffer

第一个输入参数,为将要被reset的command buffer。该command buffer可以是除了pending之外的任意state,执行后将被转变成 initial state。command buffer关联的pool必须有 VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT flag。

第二个输入参数,为a bitmask of VkCommandBufferResetFlagBits

typedef enum VkCommandBufferResetFlagBits { VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001, //指出大部分或者所有该command buffer关联的内存都要返还给command pool,如果没有设置该flag,则该command buffer可以保留这些内存,在之后record的时候继续使用。command buffer随后被切换到 initial state。 } VkCommandBufferResetFlagBits;

如果该commandbuffer在reset的时候,归属于另外一个primary command buffer,且该command buffer处于recording或者executable state,则该primary command buffer会被置于invalid state

可能会出现的错误: VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:result = vkResetCommandBuffer(handle, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);

		Host access tocommandBuffer must be externally synchronized
	
void vkFreeCommandBuffers( VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);

该API用于释放Command Buffers

第一个输入参数,为创建command pool 的logical device

第二个输入参数,为创建command buffers的command pool

第三个输入参数,为要释放的command buffer的数量,也是第四个输入参数数组的长度,必须大于0

第四个输入参数,为要释放的command buffer的数组,尺寸必须是 commandBufferCount。而且每个元素都必须合法,不能是NULL。

如果有一个处于recording 或者 executable state 的primary command buffer,它包含的一个command buffer被这个api释放后,该primary command buffer会变成invalid。

被释放的command buffer不能处于pending state

在CTS中的使用:vkFreeCommandBuffers(device->get_handle(), cmd_pool, static_cast(draw_cmd_buffers.size()), draw_cmd_buffers.data());

		Host access to commandPool must be externally synchronized
		Host access to each member of pCommandBuffers must be externally synchronized
	
VkResult vkBeginCommandBuffer( VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);

该API用于录制一个command buffer

第一个输入参数,为进入recording state的command buffer,在执行该API之前,不能处于 recording or pending state

第二个输入参数,为一个 VkCommandBufferBeginInfo 的结构体,用于说明录制command buffer的信息。

如果该command buffer是由一个没有 VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT flag 的pool创建的,那么在执行这个API之前,该command buffer必须处于 initial state。

如果该command buffer是由一个有 VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT flag 的pool创建的,且该 command buffer处于 invalid 或者 executable state,vkBeginCommandBuffer 可以隐式的 reset这个 command buffer,看上去类似执行了 vkResetCommandBuffer 附带参数 VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT 。然后该command buffer就被move to recording state。

进入recording state后,应用程序可以录制一系列的command(vkCmd*)来设置command buffer,比如draw、dispatch等command。

typedef struct VkCommandBufferBeginInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体,必须是NULL或者指向 VkDeviceGroupCommandBufferBeginInfo 的指针 VkCommandBufferUsageFlags flags; //a bitmask of VkCommandBufferUsageFlagBits 。必须是 VkCommandBufferUsageFlagBits 组合出来的,或者0 const VkCommandBufferInheritanceInfo* pInheritanceInfo; //如果command buffer为secondary command buffer,则该值为指向 VkCommandBufferInheritanceInfo 的指针,表明会从primary command buffer继承的状态。如果是primary command buffer,则该值被忽略。 } VkCommandBufferBeginInfo;

每个command buffer都有一个状态块,用于存储command buffer的device mask。该device mask用于控制logical device中的那些physical devices将会执行随后的command,包括状态设置、操作和同步命令。

不同的physical device 可以使用不同的Scissor 和 viewport state(当被设置为dynamic state的时候),每个physical device会使用自己的local copy of the state进行渲染。其它的state在physical devices之间被共享,这样的话,保证physical devices使用最新的数值。然而,在记录使用某个状态的操作命令时,设置该状态的最新命令,必须包含在其当前device mask中执行该操作命令的所有physical devices中

Command buffer的device mask与 VkDeviceGroupSubmitInfo 的 pCommandBufferDeviceMasks 相关联。Command 只会执行在这两个devices mask都设置的physical device上。

在当前API中,如果 pNext 包含了 VkDeviceGroupCommandBufferBeginInfo ,这里也就是为了当前command buffer设置了device mask的初始值。如果 pNext没有包含该结构体,则command buffer的device mask的初始值为logical device对应的所有 physical devices

typedef struct VkDeviceGroupCommandBufferBeginInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体 uint32_t deviceMask; //该command buffer的device mask的初始值,该值还充当了当前command buffer device mask的上限。不能为0。 } VkDeviceGroupCommandBufferBeginInfo;

typedef enum VkCommandBufferUsageFlagBits { VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001, //表明该command buffer的每次录制只能submit一次,submit后,就需要被reset,然后再次record,才能再次被submit。不能被用于primary command buffer。 VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002, //表明该command buffer如果是一个second command buffer的话,其会被认为完全处于一个render pass中,这样的话, pInheritanceInfo 的 renderPass 和 subpass 成员必须合法有效,相应的 framebuffer 成员必须是 VK_NULL_HANDLE 或者是一个合法的 VKFramebuffer ,并且与 renderpass相兼容。如果是一个primary command buffer,则忽略该值。 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004, //表明该command buffer当处于pending stat的时候,可以被resubmitted到一个queue,并且还可以被录制到多个primary command buffer中。不能被用于primary command buffer。在一些implementation中,不适用这个bit可以使得command buffer可以根据需要打补丁,而非创建a copy of the command buffer。 } VkCommandBufferUsageFlagBits;

typedef struct VkCommandBufferInheritanceInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体,必须是NULL VkRenderPass renderPass; //是一个render pass对象,表明了该 command buffer 与哪个render pass兼容,且可以在其中执行。如果 VKCommand Buffer不在render pass中执行,则该值可以被忽略。如果使用该值的话,该值必须与相同的logical device关联 uint32_t subpass; //表明该command buffer在render pass中的哪个subpass中执行。如果VKCommandBuffer不在render pass中执行,则该值可以被忽略。 VkFramebuffer framebuffer; //可选。如果VKCommandBuffer执行在一个renderpass中,该值用于表明将会渲染到哪个framebuffer上。如果还不确定,或者 command buffer不执行在renderpass中,这里可以使用 VK_NULL_HANDLE 。(为secondary commmand buffer设定一个明确的framebuffer,可能会提升command buffer执行时期的性能)。如果使用该值的话,该值必须与相同的logical device关联 VkBool32 occlusionQueryEnable; //表明该command buffer是否可以在primary command buffer的occlusion query被开启的时候被执行。如果VK_TRUE,则无论primary command buffer是否开启occlusion query,该command buffer都可以被执行,如果是VK_FLASE,则primary command buffer必须不能开启occlusion query。如果没有开启 inherited queries feature,则这里必须是 VK_FALSE VkQueryControlFlags queryFlags; //表明当secondary command buffer在execute的时候,primary command buffer开启occlusion query的时候可以用哪些flag。如果这个 flag包含 VK_QUERY_CONTROL_PRECISE_BIT ,则query可以返回bool或者实际数值。如果这个bit没有设置,则query不能使用 VK_QUERY_CONTROL_PRECISE_BIT bit。如果 occlusionQueryEnable 为VK_FALSE,或者没有开启 precise occlusion queries,则该值不能包含 VK_QUERY_CONTROL_PRECISE_BIT。如果开启了 inherited queries feature,则该值必须是 VkQueryControlFlagBits 的合法组合,否则必须为0 VkQueryPipelineStatisticFlags pipelineStatistics; //a bitmask of VkQueryPipelinesStatisticFlagBits ,用于当secondary command buffer在execute的时候,primary command buffer开启occlusion query的时候可以统计的pipeline statistics。如果这个flag包含某个bit,则在primary command buffer开启一个附带或者不附带该 bit 的pipeline statistics query的时候,该second command buffer可以执行。如果该flag没有包含某个bit,则pipeline statistics query必须不能开启一个query pool count这个statistic。如果 pipeline statistics queries feature开启的话,则该值必须是 VkQueryPipelinesStatisticFlagBits 的组合,否则,该值比如为0。 } VkCommandBufferInheritanceInfo;

typedef enum VkQueryControlFlagBits { VK_QUERY_CONTROL_PRECISE_BIT = 0x00000001, //表明occlusion queries的精度 } VkQueryControlFlagBits;

typedef enum VkQueryPipelineStatisticFlagBits { VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT = 0x00000001, //表明pool控制的这个query用于统计Input Assembly阶段的顶点数。未完成的图元对应的顶点也参与了这个统计。 VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT = 0x00000002, //表明pool控制的这个query用于统计Input Assembly阶段的图元数。如果开启primitive restart,则该技术对技术没有任何影响。未完成的图元也参与了这个统计。 VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT = 0x00000004, //表明pool控制的这个query用于统计vs被调用的次数,每次vs被调用,该统计值将被加一。 VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT = 0x00000008, //表明pool控制的这个query用于统计gs被调用的次数,每次gs被调用,该统计值将被加一。如果使用的是instanced geometry shader,那么每次单独的instance调用,该统计值都会加一。 VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT = 0x00000010, //表明pool控制的这个query用于统计通过gs生成的图元数,每当gs生成一个图元,该统计值将被加一。SPIR-V的指令OpEndPrimitive或OpEndStreamPrimitive 提供的Restarting primitive技术对该统计没有影响。 VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT = 0x00000020, //表明pool控制的这个query用于统计渲染管线的 primitive clipping 阶段处理的图元数。每当一个图元到达primitive clipping 阶段的时候,该统计都会加一。 VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT = 0x00000040, //表明pool控制的这个query用于统计渲染管线的 primitive clipping 阶段通过的图元数。每当一个图元通过primitive clipping 阶段的时候,该统计都会加一。针对一组特定的图元,通过primitive clipping阶段的数量根据不同implementation结果不同,但是都必须满足以下条件:1.如果有一个或者一个以上的图元在clipping volume中,则该计数要加一或者加更多,否则,应该加0(Patrick:spec这里有个编写错误,写的是否则加zero or more。) VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT = 0x00000080, //表明pool控制的这个query用于统计ps被调用的次数,每次ps被调用,该统计值将被加一。 VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT = 0x00000100, //表明pool控制的这个query用于统计tesselation control shader处理的patches的数量。每次tesselation control shader处理一个patch的时候,该统计值将被加一。 VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT = 0x00000200,//表明pool控制的这个query用于统计tesselation evaluation shader被调用的次数,每次tessellation evaluation shader被调用,该统计值将被加一。 VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT = 0x00000400, //表明pool控制的这个query用于统计cs被调用的次数,每次cs被调用,该统计值将被加一。Implementation可以跳过一些由于implementation的原因导致的,不会影响渲染结果的,特定cs或者额外cs的执行。 } VkQueryPipelineStatisticFlagBits;

可能会出现的错误:VK_ERROR_OUT_OF_HOST_MEMORY、VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:vkBeginCommandBuffer(command_buffer, &command_buffer_info)

		Host access to commandBuffer must be externally synchronized
		Host access to the VkCommandPool that commandBuffer was allocated from must be externally synchronized
	
VkResult vkEndCommandBuffer( VkCommandBuffer commandBuffer);

该API用于结束一个command buffer的录制

第一个输入参数,为结束recording state的command buffer

如果在录制阶段出现了错误,那么 vkEndCommandBuffer 会返回 unsuccessful 。如果之后应用程序还想用这个command buffer,则该command buffer需要被reset。

该command 在被调用这个API的时候,必须处于 recording state,调用完毕后,会被move to executable state。

如果command buffer是一个 primary command buffer,则必须不是一个active render pass instance。

在command buffer录制过程中 active的 queues,必须先被inactive

可能会出现的错误:VK_ERROR_OUT_OF_HOST_MEMORY、VK_ERROR_OUT_OF_DEVICE_MEMORY

在CTS中的使用:vkEndCommandBuffer(command_buffer)

		Host access to commandBuffer must be externally synchronized
		Host access to the VkCommandPool that commandBuffer was allocated from must be externally synchronized
	
VkResult vkQueueSubmit( VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);

当一个command buffer处于executable state的时候,可以被submit到一个queue去执行。

submission是一个很耗的操作,所以应用程序必须将work batch在一起,形成few calls to vkQueueSubmit

该API用于将 command buffer submit to a queue

第一个输入参数,为command buffer即将submitted to的queue。必须属于相同的VkDevice

第二个输入参数,为第三个输入参数数组的元素数量

第三个输入参数是一个数组,包含的元素为 VkSubmitInfo 结构体,每个都代表着一组command buffers的submission batch

第四个输入参数,为一个可选的 fence,用来标记记录所有submitted 的command buffers执行完毕。如果该值不是 VK_NULL_HANDLE ,则它必须是unsignaled,定义了一个fence signal operation,且在当前queue执行的时候,必须不能是关联了其它未完成的queue。必须属于相同的VkDevice

typedef struct VkSubmitInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_SUBMIT_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体,必须是NULL 或者 VkDeviceGroupSubmitInfo 、VkProtectedSubmitInfo, or VkTimelineSemaphoreSubmitInfo uint32_t waitSemaphoreCount; //在执行该batch对应的command buffers之前,需要等待的 semaphores的数量 const VkSemaphore* pWaitSemaphores; //在执行该batch对应的command buffers之前,需要等待的 semaphores数组。如果这里填写了需要等待的 semaphores,将产生一个semaphore wait operation。如果是一个 binary semaphore,该semaphore必须不能被其它queue 等待。当queue开始执行的时候,该semaphore必须不能被其它queue等待。必须是signaled状态,或者有一个semaphore signal operations被submit for execution。如果是VkSemaphoreType 为 VK_SEMAPHORE_TYPE_BINARY,则必须关联一个semaphore signal operation,并将其以及其依赖的所有semaphore singal operation 都必须submit for execution const VkPipelineStageFlags* pWaitDstStageMask; //指向每个相应semaphore wait会发生的pipeline stages的数组,数量也是 waitSemaphoreCount,每个元素都必须不能是0。表示此次提交的所有命令在执行到 pWaitDstStageMask 时,要停下,必须要等待 pWaitSemaphores 所指向的所有Semaphore的状态变成signaled时才可以继续执行 uint32_t commandBufferCount; //该batch中执行的command buffers的数量 const VkCommandBuffer* pCommandBuffers; //该batch中执行的command buffers数组。所有的command buffer,包括它们关联的secondary command buffer都必须是 pending 或者 executable state(如果 command buffer(包含它们关联的secondary command buffer) 在录制的时候没有打开 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT ,则它在此时,不能pending state)。这里所有的command buffer都必须来自同一个pool,拥有相同的queue family。这里的command buffer不能是 VK_COMMAND_BUFFER_LEVEL_SECONDARY. uint32_t signalSemaphoreCount; //当batch中的command buffers执行完毕后,会标记的semaphores数量 const VkSemaphore* pSignalSemaphores; //当batch中的command buffers执行完毕后,会标记的semaphores。如果这里填写了semaphores,将产生一个semaphore wait operation。执行的时候必须是unsignaled。 } VkSubmitInfo;

pCommandBuffers、pSignalSemaphores、pWaitSemaphores必须创建自同一个VkDevice

vkQueueSubmit是一个queue submission command,pSubmits中的一个元素对应一个batch。Batches按照pSubmits中的顺序进行执行,但是可能不按照顺序结束。

与其它submission commands相比,vkQueueSubmit中包含的fence和semaphore具有额外的排序约束,依赖关系涉及以前和以后的queue 操作。关于这些附加约束的信息,可以在下一章看到。

关于 pWaitDstStageMask 的 synchronization interaction也在下一章介绍。

pSubmits中出现的顺序为batches的提交顺序,因此所有隐式排序都保证遵守该顺序。除了这些隐式排序和任何显式同步之外,这些batches可能会重叠或者以其它方式无序执行。

如果该queue中的任何command buffer处于executable state,将会move to pending state。一旦一个command buffer中的所有东西都执行完毕,将会将其从 pending state转成executable state(如果一个command buffer录制的时候被打上了 VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT 的flag,则其将会转成invalid state)

如果vkQueueSubmit失败,则会返回 VK_ERROR_OUT_OF_HOST_MEMORY or VK_ERROR_OUT_OF_DEVICE_MEMORY 的错误。如果发生了错误,implementation必须确保资源对应的state和内容,或者submitted command buffer对应的synchronizatioon primitives 以及pSubmits对应的semaphores不会受到该调用或者失败的影响。如果vkQueueSubmit失败导致implementation无法保证,则必须返回 VK_ERROR_DEVICE_LOST 。

pWaitDstStageMask的值必须是当前queue支持的,不能是 VK_PIPELINE_STAGE_HOST_BIT

Vulkan Vulkan

如果gs feature不支持,这里不能包含 VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT

如果ts feature不支持,这里不能包含 VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT or VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT

如果 某个 pWaitSemaphores or pSignalSemaphores 在创建的时候 VkSemaphoreType 为 VK_SEMAPHORE_TYPE_TIMELINE,则 pNext中必须包含 VkTimelineSemaphoreSubmitInfo 结构体。反之,则忽略这些。

typedef struct VkTimelineSemaphoreSubmitInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体 uint32_t waitSemaphoreValueCount; //pWaitSemaphoreValues 数组的元素数量, 与 waitSemaphoreCount 对应 const uint64_t* pWaitSemaphoreValues; //为一个 pWaitSemaphoreValues 个元素的数组,包含了 VkSubmitInfo::pWaitSemaphores 中相应 semaphores 等待的value。该值与 semaphore当前的值或者任意等待或者signal operation时候的值的差距一定不能超过 maxTimelineSemaphoreValueDifference uint32_t signalSemaphoreValueCount; //pSignalSemaphoreValues 数组的元素数量,与 signalSemaphoreCount 对应 const uint64_t* pSignalSemaphoreValues; //为一个 pSignalSemaphoreValues 个元素的数组,包含了 VkSubmitInfo::pSignalSemaphores 中相应 semaphores zai signaled的时候被设置的值。该值必须比 semaphore signal operation 执行的时候 semaphore的值大,且该值与 semaphore当前的值或者任意等待或者signal operation时候的值的差距一定不能超过 maxTimelineSemaphoreValueDifference } VkTimelineSemaphoreSubmitInfo;

VkTimelineSemaphoreSubmitInfo 还被用于 VkSubmitInfo 为 vkQueueBindSparse 的 VkBindSparseInfo 参数 中的一个参数时。

如果pNext中不包含 参数 protectedSubmit 设置为 VK_TRUE 的 VkProtectedSubmitInfo 结构体,则所有的command buffer都必须是unprotected command buffer。反之,如果pNext中包含 参数 protectedSubmit 设置为 VK_TRUE 的 VkProtectedSubmitInfo 结构体,则所有的command buffer都必须是protected command buffer。

(Patrick:什么是 unprotected command buffer。)

typedef struct VkProtectedSubmitInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO const void* pNext; VkBool32 protectedSubmit; //如果protected memory feature 没有被开启,则该值必须是 VK_TRUE } VkProtectedSubmitInfo;

如果pNext中包含 VkDeviceGroupSubmitInfo 结构体,该结构体中包含的device indices和masks 规定了哪些physical devices 执行 semaphore operation 和 command buffer。如果没有使用该结构体,则semaphore operation 和command bffers执行在index为0的device上。

typedef struct VkDeviceGroupSubmitInfo { VkStructureType sType; //当前结构体的类型,必须是 VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO const void* pNext; //NULL,或者扩展该结构体的另外一个结构体 uint32_t waitSemaphoreCount; //pWaitSemaphoreDeviceIndices 数组的元素数量,对应VkSubmitInfo::waitSemaphoreCount const uint32_t* pWaitSemaphoreDeviceIndices; //为一个 waitSemaphoreCount 个元素的数组,每个元素为一个device indices用于表明 VkSubmitInfo::pWaitSemaphores 中对应的 semaphore wait operation执行在哪个 physical device上。device indices必须合法 uint32_t commandBufferCount; //pCommandBufferDeviceMasks 数组的元素数量,对应VkSubmitInfo::commandBufferCount const uint32_t* pCommandBufferDeviceMasks; //为一个 commandBufferCount 个元素的数组,每个元素为一个device mask 用于表明 VkSubmitInfo::pCommandBuffers 中对应的 command buffer 执行在哪些 physical devices上(执行在mask上对应的physical device上)。device mask必须合法 uint32_t signalSemaphoreCount; //pSignalSemaphoreDeviceIndices 数组的元素数量,对应VkSubmitInfo::signalSemaphoreCount const uint32_t* pSignalSemaphoreDeviceIndices; //为一个 signalSemaphoreCount 个元素的数组,每个元素为一个device indices用于表明 VkSubmitInfo::pSignalSemaphores. 中对应的 semaphore signal operation执行在哪个 physical device上。device indices必须合法。 } VkDeviceGroupSubmitInfo;

如果command buffer中包含a Queue Family Transfer Acquire Operation,则在该queue family的queue中必须先有一个被submitted的 Queue Family Transfer Release Operation,其参数与acquire operation匹配,并且在acquire operation之前执行。

如果command buffer中录制了vkCmdSetEvent,vkCmdResetEvent或vkCmdWaitEvents,对应的VkEvent必须不能关联其它queue中处于pending state的command buffer。

pCommandBuffers中出现的顺序为 command buffer的提交顺序,因此所有隐式排序都保证遵守该顺序。除了这些隐式排序和任何explicit synchronization primitives之外,这些 command buffer可能会重叠或者以其它方式无序执行。

(Patrick:什么是 explicit synchronization primitives。)

在创建的时候使用 VK_SHARING_MODE_EXCLUSIVE 属性的资源,如果在pSubmits中使用了,那么在executed期间,必须不能属于其它queue family。

在创建的时候使用 VK_SHARING_MODE_CONCURRENT 属性的资源,如果在pSubmits中使用了,那么在创建的时候,必须属于该queue family

可能会出现的错误:VK_ERROR_OUT_OF_HOST_MEMORY、VK_ERROR_OUT_OF_DEVICE_MEMORY、VK_ERROR_DEVICE_LOST

如果command buffer中有一个录制命令是 vkCmdBeginQuery,其中的queryPool参数是通过 queryType 为 VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR 创建的。那么在这些command buffer 录制期间,profiling lock必须时刻保持在queue归属的vkDevice上。

在CTS中的使用:vkQueueSubmit(handle/*Queue::get_handle()*/, to_u32(submit_infos.size()), submit_infos.data(), fence);

		Host access to queue must be externally synchronized
		Host access to fence must be externally synchronized
	

在使用binary semaphores的时候,应用程序必须保证command buffer的提交无需等待应用程序在其它queue的操作。在调用vkQueueSubmit(或者其它queue operation)之后,对于需要等待 VkSemaphoreType 为 VK_SEMAPHORE_TYPE_BINARY 的semaphore的queue,必须有一个前置signal of semaphore,而该前置semaphore不会需要等待其它东西。

当使用timeline semaphores,wait-before-signal行为被没问题的,应用程序可以在提交一个相应的semaphore signal operation之前,通过 vkQueueSubmit submit 定义一个timeline semaphore wait operation。对于vkQueueSubmit调用的每个timeline semaphore wait operation,应用程序必须确保相应的semaphore signal operation在forward progress之前被执行。

(Patrick:什么是 forward progress。)

submit的command buffer可以包含 vkCmdWaitEvents 命令,等待一个当前queue的之前command 没有signaled的event。该event需要通过 vkSetEvent signaled, 对应的 vkCmdWaitEvents command 必须不能inside a render pass instance。该event一定要在 vkCmdWaitEvets 执行之前 set。(Implementation可以对waiting on events to be set有一定tolerance)

void vkCmdExecuteCommands( VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);

secondary command buffer必须不能直接 submit 到一个queue。相应的,secondary command buffer需要被录制到primary command buffer中,作为其中一部分来执行。该 API 就是被用于将secondary command buffer 录制到primary command buffer中的

第一个输入参数,为一个primary command buffer,secondary command buffer即将在其上执行。必须正在处于recording state。其对应的pool必须支持 transfer、graphics或者compute operation

第二个输入参数,为第三个输入参数 pCommandBuffers 的尺寸,必须大于0

第三个输入参数,为一个 commandBufferCount 个元素的数组,每个元素都是一个secondary command buffer,且处于pending或者executable state(如果录制的时候没有包含 flag VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,则不能处于pending state)。它们在primary command buffer上的执行顺序为它们在array上的顺序。

如果 pCommandBuffers 中的任何一个元素在录制的时候没有包含 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT ,且它已经被录制到其它正在处于 executable or recording state 的primary command buffer,该primay command buffer会变成invalid state(Patrick:哪个primary command buffer会变成invalid?)

如果 pCommandBuffers 中的任何一个元素在录制的时候没有包含 VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT ,则它一定不能已经被录制到command buffer中,在command buffer中也不能被录制超过一次。

pCommandBuffers对应的pool必须和 commandBuffer对应的pool使用相同的queue family

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,则该render pass instance 必须 begun with vkCmdBeginRenderPass的contents参数为 VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,则 pCommandBuffers中的每个command buffer在录制的时候都必须拥有 flag VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,则 pCommandBuffers中的每个command buffer在录制的时候都必须将 VkCommandBufferInheritanceInfo::subpass 设置成 command buffer执行时候对应的subpass 的index。

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,则 pCommandBuffers中的每个command buffer在通过 vkBeginCommandBuffer 录制的时候,对应的参数 pBeginInfo->pInheritanceInfo->renderPass 都必须与当前render pass相匹配。

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,则 pCommandBuffers中的每个command buffer在录制的时候都必须将 VkCommandBufferInheritanceInfo::framebuffer 设置为当前render pass的 VKFramebuffer

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,且该render pass instance在 VkRenderPassBeginInfo 的pNext中包含 VkRenderPassTransformBeginInfoQCOM , 那么pCommandBuffers的每个元素在录制的时候 VkCommandBufferBeginInfo 的pNext 都必须包含 VkCommandBufferInheritanceRenderPassTransformInfoQCOM

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,且该render pass instance在 VkRenderPassBeginInfo 的pNext中包含 VkRenderPassTransformBeginInfoQCOM , 那么pCommandBuffers的每个元素在录制的时候 VkCommandBufferInheritanceRenderPassTransformInfoQCOM::transform 都必须设置为 VkRenderPassTransformBeginInfoQCOM::transform

如果 vkCmdExecuteCommands 在一个render pass instance中被调用,且该render pass instance在 VkRenderPassBeginInfo 的pNext中包含 VkRenderPassTransformBeginInfoQCOM , 那么pCommandBuffers的每个元素在录制的时候 VkCommandBufferInheritanceRenderPassTransformInfoQCOM::renderArea 都必须设置为 VkRenderPassBeginInfo::renderArea

如果 vkCmdExecuteCommands 不在一个render pass instance中被调用,则 pCommandBuffers中的每个command buffer在录制的时候都必须不能拥有 flag VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT

如果 inherited queries feature 没有开启,则commandBuffer必须不能拥有任何开启的queries

如果 commandBuffer 有一个 VK_QUERY_TYPE_OCCLUSION 开启,则pCommandBuffers 中的每一个元素在录制的时候,都必须将 VkCommandBufferInheritanceInfo::occlusionQueryEnable 设置为 VK_TRUE

如果 commandBuffer 有一个 VK_QUERY_TYPE_OCCLUSION 开启,则pCommandBuffers 中的每一个元素在录制的时候,都必须将 VkCommandBufferInheritanceInfo::queryFlags 设置成query设置的bits

如果 commandBuffer 有一个 VK_QUERY_TYPE_PIPELINE_STATISTICS 开启,则pCommandBuffers 中的每一个元素在录制的时候,都必须将 VkCommandBufferInheritanceInfo::pipelineStatistics 设置成query对应的pool设置的bits。

pCommandBuffers中的每一个元素都不能开启任何commandBuffer中已经开启的query type

如果commandBuffer是protected command buffer,则pCommandBuffers必须也是一个protected command buffer

如果commandBuffer是unprotected command buffer,则pCommandBuffers必须也是一个unprotected command buffer

当transform feedback开启的时候,不能录制该command

commandBuffer和pCommandBuffers必须创建自同一个VkDevice

在CTS中的使用:vkCmdExecuteCommands(get_handle(), to_u32(sec_cmd_buf_handles.size()), sec_cmd_buf_handles.data());

		Host access to commandBuffer must be externally synchronized
		Host access to the VkCommandPool that commandBuffer was allocated from must be externally synchronized
	
void vkCmdSetDeviceMask( VkCommandBuffer commandBuffer, uint32_t deviceMask);

该API用于修改该command buffer的device mask

第一个输入参数,为将要被修改device mask的command buffer,必须处于recording state。且其对应的pool,必须 allocated from graphics、compute or transfer operation

第二个输入参数,为修改后的devicemask,必须不能为0。且不能包含录制的时候 VkDeviceGroupCommandBufferBeginInfo::deviceMask 设置的值。如果 vkCmdSetDeviceMask inside a render pass instance,该值不能包含 render pass instance 开始录制的时候,VkDeviceGroupRenderPassBeginInfo::deviceMask 中没有包含的值。

devicemask用于指定后面的命令执行在哪些physical device上。除非是开启一个render pass instance的命令、在render pass instance transition to next subpass 的command、结束一个render pass instance 的command,这些命令永远都执行在开启render pass instance的时候,VkDeviceGroupRenderPassBeginInfo 的devicemask。

Vulkan

在CTS中的使用:无

		Host access to commandBuffer must be externally synchronized
		Host access to the VkCommandPool that commandBuffer was allocated from must be externally synchronized
	
Vulkan

本节教程就到此结束,希望大家继续阅读我之后的教程。

谢谢大家,再见!


原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2021 年 4 月 29 日发表,原文链接(http://geekfaner.com/shineengine/blog15_Vulkanv1.2_2.html)