Vulkan设置缓冲区内存屏障

设置缓冲区内存屏障
缓冲区拥有各种用途:我们可以将数据写入缓冲区,也可以从缓冲区复制数据;通过描述符集合可以将缓冲区与通道绑定,并在着色器中将缓冲区作为数据源,还可以将数据存储到着色器中的缓冲区。

我们必须向驱动程序通报这些缓冲区用法,不仅要在创建缓冲区的过程中通报,而且在改变缓冲区使用方式时也要通报。如果想要换一种方式使用缓冲区,就必须将更换缓冲区用法的信息通报给驱动程序,通过缓冲区内存屏障可以做到这一点。在执行命令缓冲区记录操作的过程中,缓冲区内存屏障会作为通道屏障(pipeline barrier)的组成部分被设置。

准备工作

为了完成本节的编程任务,我们将使用BufferTransition自定义结构类型,下面是该数据类型的定义。

Vulkan设置缓冲区内存屏障
使用该结构可以定义应用于缓冲区内存屏障的参数。在CurrentAccess和NewAccess成员中,可以分别存储缓冲区被使用过的方式和当前缓冲区正在被使用的方式(在这种情况中,缓冲区用法被定义为会包含到指定缓冲区中的内存操作类型)。CurrentQueueFamily和NewQueueFamily成员用于将缓冲区的所有权,从一个队列家族转给另一个队列家族。如果在创建缓冲区的过程中设定了独占共享模式,就需要这样做。

具体处理过程

(1)为每个想要设置内存屏障的缓冲区准备参数。将这些参数存储在一个std::vector<BufferTransition>类型的vector容器变量中,将该变量命名为buffer_transitions。为每个缓冲区元素存储下列参数。
① 将缓冲区的句柄存储在Buffer成员中。
② 将包含在该缓冲区中的内存操作类型存储在CurrentAccess成员中。
③ 将当前缓冲区将要执行的内存操作类型(在内存屏障之后)存储在NewAccess成员中。
④ 如果不想在队列家族之间转接缓冲区的所有权,就将VK_QUEUE_FAMILY_IGNORED赋予CurrentQueueFamily成员;否则,就将引用过该缓冲区的队列家族的索引存储到CurrentQueueFamily成员中。
⑤ 如果不想在队列家族之间转接缓冲区的所有权,就将VK_QUEUE_FAMILY_IGNORED赋予NewQueueFamily成员;否则,就将当前引用该缓冲区的队列家族的索引存储到NewQueueFamily成员中。
(2)创建一个std::vector<VkBufferMemoryBarrier>类型的vector容器变量,将其命名为buffer_memory_barriers。
(3)与buffer_transitions变量中的每个元素对应,为vector容器buffer_memory_barriers变量添加元素。使用下列值为每个元素中的各个成员赋值。
● 将VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER赋予sType成员。
● 将nullptr赋予pNext成员。
● 将当前元素(当前的缓冲区)CurrentAccess成员的值赋予srcAccessMask成员。
● 将当前元素NewAccess成员的值赋予dstAccessMask成员。
● 将当前元素CurrentQueueFamily成员的值赋予srcQueueFamilyIndex成员。
● 将当前元素NewQueueFamily成员的值赋予dstQueueFamilyIndex成员。
● 将当前缓冲区的句柄赋予buffer成员。
● 将0赋予offset成员。
● 将VK_WHOLE_SIZE赋予size成员。
(4)获取命令缓冲区的句柄,将该句柄存储在一个VkCommandBuffer类型的变量中,将该变量命名为command_buffer。
(5)确保command_buffer变量代表的命令缓冲区处于记录状态(已经为该命令缓冲区启动了记录操作)。
(6)创建一个VkPipelineStageFlags类型的位域变量,将其命名为generating_stages。将已经使用了该缓冲区的管线阶段的值存储到generating_stages变量中。
(7)创建一个VkPipelineStageFlags类型的位域变量,将其命名为consuming_stages。将在内存屏障后面使用缓冲区的管线阶段的值赋予consuming_stages变量。
(8)调用vkCmdPipelineBarrier(command_buffer,generating_stages,consuming_stages,0,0,nullptr,static_cast<uint32_t>(buffer_memory_barriers.size()),&buffer_memory_barriers[0],0,nullptr)函数。将第一个参数设置为命令缓冲区的句柄;将第二个参数设置为generating_stages变量;将第三个参数设置为consuming_stages变量;将第四个和第五个参数设置为0;将第六个参数设置为nullptr;将第七个参数设置为vector容器buffer_memory_barriers变量中含有元素的数量;将第八个参数设置为指向vector容器buffer_memory_barriers变量中第一个元素的指针;将第九个参数设置为0;将第十个参数设置为nullptr。

具体运行情况

在Vulkan中,提交给队列的操作会按顺序执行,但它们是独立的。有时某些操作可以在它们前面的操作完成前就开始执行,这种并行执行方式是当前图形硬件最重要的性能要素之一。但有时候,一些操作必须等待较早前操作的结果,通过内存屏障可以轻松做到这一点。
TIP 内存屏障用于定义命令缓冲区执行过程中的转折点,在这些时刻较后面的命令必须等待较早前的命令完成任务。这也会使这些较早前操作的结果对其他操作可见。
在使用内存屏障处理缓冲区时,我们可以设置使用缓冲区的方式,并在管线阶段使用缓冲区时放置内存屏障。定义在内存屏障后面哪些管线阶段会使用缓冲区和使用该缓冲区的方式,驱动程序通过这些信息可以暂停需要等待较早前操作结果的操作,仅执行完全没有引用该缓冲区的操作。
只能通过在创建缓冲区的过程中定义的用途使用缓冲区,这些用法与用于访问缓冲区的内存操作的类型对应。下面是得到支持的内存访问类型。
● 当缓冲区的内容为用于间接绘制操作的数据源时,可使用VK_ACCESS_INDIRECT_COMMAND_READ_BIT标志值。
● VK_ACCESS_INDEX_READ_BIT标志值可以指明缓冲区的内容为在绘制操作过程中使用的索引。
● VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT标志值表明缓冲区的内容为在绘制过程中读取的顶点属性。
● 当能够通过着色器将缓冲区作为统一缓冲区访问时,可使用VK_ACCESS_UNIFORM_READ_BIT标志值。
● VK_ACCESS_SHADER_READ_BIT标志值指明缓冲区可以在着色器内部被读取(但不作为统一缓冲区)。
● VK_ACCESS_SHADER_WRITE_BIT标志值指明着色器会向缓冲区中写入数据。
● 当需要从缓冲区读取数据时,可使用VK_ACCESS_TRANSFER_READ_BIT标志值。
● 当需要向缓冲区中写入数据时,可使用VK_ACCESS_TRANSFER_WRITE_BIT标志值。
● VK_ACCESS_HOST_READ_BIT标志值指明应用程序会读取缓冲区的内容(通过内存映射操作)。
● 如果需要使应用程序向缓冲区中写入数据(通过内存映射操作),那么可使用VK_ACCESS_HOST_WRITE_BIT标志值。
● 如果要通过除上述方式外的其他方式读取缓冲区的内容,那么可使用VK_ACCESS_MEMORY_READ_BIT标志值。
● 如果要通过除上述方式外的其他方式向缓冲区中写入数据,那么可使用VK_ACCESS_MEMORY_WRITE_BIT标志值。
要使内存操作变得对后续命令可见,就需要使用内存屏障。如果不使用内存屏障,那么读取缓冲区内容的命令,会在向缓冲区中写入这些内容的操作还没有被执行前就开始执行。但是这些命令缓冲区在执行过程中的暂停,会拖慢图形硬件处理管线的速度。但是,这会影响编写的应用程序的性能。

Vulkan设置缓冲区内存屏障
TIP 应该通过尽可能少的内存屏障为尽可能多的缓冲区设置使用方式和所有权转接方式。
要为缓冲区设置内存屏障,需要创建一个VkBufferMemoryBarrier类型的变量。如果条件允许,那么应将多个缓冲区的数据集中在一个内存屏障后面,这就是元素类型为VkBuffer MemoryBarrier的vector容器非常有用的原因,可使用下列代码为该vector容器赋值。

Vulkan设置缓冲区内存屏障
在命令缓冲区中设置内存屏障,这可以在命令缓冲区执行记录操作的过程中做到这一点。

Vulkan设置缓冲区内存屏障
在内存屏障后面设定的管线阶段中的命令,应等待内存屏障前面的管线阶段中命令的执行结果。
注意,只有当缓冲区的使用方式发生改变时才需要设置内存屏障。如果通过同一种方式多次使用一个缓冲区,就不需要设置内存屏障。当需要从两个不同的数据源向缓冲区中复制两次数据时,首先需要设置一个内存屏障,以便通知驱动程序执行的操作中含有VK_ACCESS_TRANSFER_WRITE_BIT类型的内存访问操作;然后就可以执行任意次数的向缓冲区中复制数据的操作。如果想要将该缓冲区作为顶点缓冲区(在渲染过程中使用的顶点属性数据源),就需要设置另一个内存屏障,以便指明将要从该缓冲区读取顶点属性数据(这些操作由VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT类型的内存访问操作代表)。当完成绘制作业后需要将该缓冲区作为其他用途,甚至可能需要再次向该缓冲区中复制数据,并通过合适的参数设置内存屏障。

补充说明

不需要为整个缓冲区设置内存屏障,需要设置内存屏障的是缓冲区中的某个(些)部分。要为缓冲区设置内存屏障,只需要将合适的值赋予为指定缓冲区定义的VkBufferMemory Barrier类型变量中offset和size成员。通过这些成员可以定义通过屏障划分的缓冲区各个部分的起始位置和尺寸,并且这些成员都是以机器单元(字节)的形式定义的。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!

 

Vulkan实战