Vulkan编程指南翻译 第三章 队列和命令 第1节 设备队列

99 篇文章 54 订阅
17 篇文章 0 订阅

第三章 队列和命令 第3节 设备队列

 

将在本章中学到

队列是什么,如何使用它

如何创建命令并把它们发送给Vulkan

如何保证设备已完成任务

Vulkan设备对外暴露多个队列来完成任务。在本章我们讨论多种队列类型并详解如何以命令buffer的形式向他们提交任务。我们也演示如何指示一个队列完成你发送给它任务。

 

设备队列

Vulkan中每一个队列都有一个或多个队列。队列设备中真正执行工作的。它可以被认为是对外暴露了设备功能的子设备。在一些实现中,每一个独立饿甚至是系统内物理性分离

多个设备被分组到一个或者多个队列集中,一个队列集包含一个或者多个队列在一个集中的多个队列基本上是相同的。他们能力性能级别对资源的访问也是相同的,除了同步的时候,相互之间转移工作也是没有损耗的。如果设备哟多个具有相同能力性能不同的核心内存的访问其他导致表现不同的因素他们不同的集明显的表现各异。

如在第一章讨论过可以调用vkGetPhysicalDeviceQueueFamilyProperties()查询每一个物理设备队列集的属性这个函数一个你给定的VkQueueFamilyProperties类型的数据写入队列集的属性。

当你创建设备的时候队列数量和类题型能够必须要确定。如你在第一章所见,你传入vkCreateDevice()VkDeviceCreateInfo结构,包含queueCreateInfoCountpQueueCreateInfos这两个成员。第一章简介了他们,现在我们来做详细的讲解queueCreateInfoCount成员包含了一个由pQueueCreateInfos指针指向的数组中VkDeviceQueueCreateInfo类型对象的数量VkDeviceQueueCreateInfo结构如下:

typedef struct VkDeviceQueueCreateInfo {

VkStructureType  sType;

const void*  pNext;

VkDeviceQueueCreateFlags  flags;

uint32_t  queueFamilyIndex;

uint32_t  queueCount;

const float*  pQueuePriorities;

} VkDeviceQueueCreateInfo;

多数Vulkan数据结构一样,sType是数据的类型,在此种情况下是VK_STRUCTURE_TYPE_QUEUE_CREATE_INFOpNext被拓展使用,当没有拓展时应当设置为nullptrflags 包含对队列的控制标记信息,但是,当前的Vulkan并没有定义任何标志所以此成员应当被设置为0

我们感兴趣的域是queueFamilyIndex  queueCountqueueFamilyIndex指定了哪个集分配队queueCount指定了需分配的队列的个数。多个集合中分配队列时,只需向VkDeviceCreateInfo类型对象的pQueueCreateInfos成员,传入多于一个VkDeviceQueueCreateInfo类型对象的数组即可

设备被创建的时候队列就被构造了。所以我们无需创建队列,但是,为了获取他们,我们需要调用vkGetDeviceQueue():

void vkGetDeviceQueue (

VkDevice device,

uint32_t queueFamilyIndex,

uint32_t queueIndex,

VkQueue* pQueue);

这个函数以你需要从哪个设备获取队列device,集的索引queueFamilyIndex,集合中队列的索引queueIndex 作为参数。pQueue参数指向了一个VkQueue类型handle就是你想要的队列的handle。queueFamilyIndexqueueIndex必须参考device被创建时的参数。如果正确的这么做了,一个正确的队列 handle将会被赋值到pQueue否则,pQueue值就会是VK_NULL_HANDLE

 

创建命令buffer

队列的意义就是应用程序内处理任务。任务通过一串的命令表示的,命令被记录到命令缓冲区(command buffer)中。你的应用将会创建包含任务的命令缓冲区进而提交到队列来执行记录任何命令之前,你需要创建命令缓冲区。命令缓冲区并不被直接创建,需要从pool中分配。你可以调用vkCreateCommandPool() 函数来创建pool,其原型如下:

VkResult vkCreateCommandPool (

VkDevice  device,

const VkCommandPoolCreateInfo*  pCreateInfo,

const VkAllocationCallbacks*  pAllocator,

VkCommandPool*  pCommandPool);

Vulkan中绝大多数对象创建一样,它的第一个参数device,就是拥有该pool的设备对该pool的描述信息是通过一个指向某个数据结构的指针pCreateInfo传递的。这个数据结构就是VkCommandPoolCreateInfo,定义如下:

typedef struct VkCommandPoolCreateInfo {

VkStructureType  sType;

const void*  pNext;

VkCommandPoolCreateFlags  flags;

uint32_t  queueFamilyIndex;

} VkCommandPoolCreateInfo;

大多数Vulkan数据结构类似,前两个是sType  pNext前一包含了数据结构的类型,后一个是一指向包含更多关于pool创建信息的数据。这里我们设置sTypeK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO我们不需要传递任何其他信息,pNext需被设置为nullptr。

flags域包含标志,它控制了pool和从pool分配得倒的命令缓冲区的行为其值为VkCommandPoolCreateFlagBits枚举类型。这个枚举现在有两个值被定义了:

VK_COMMAND_POOL_CREATE_TRANSIENT_BIT 表示命令缓冲区使用周期短,使用完后马上退回给pool。设置这个枚举值,就意味着告诉Vulkan,你将长时间的持有这个命令缓冲区。

VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT表示允许单独的命令缓冲区可通过重置重启而被重用如果没有这个bit标志那么只有pool本身能够被重置,这隐式的回收所有由它分配出的命令缓冲区。

每一个bit标志位都会增加一些开销,因为Vulkan实现需要跟踪资源或者改变其自身分配策略。例如设置VK_COMMAND_POOL_CREATE_TRANSIENT_BIT这个标志位导致Vulkan实现跟踪每一个命令缓冲区的重置状态而不是简单的只是在pool内跟踪。在这种情形下我们实际上需要两个标志位都设置上。这将给我们极大的灵活性,可能在一些性能损失条件下,我们可以批量的管理命令缓冲区。

最后,VkCommandPoolCreateInfo queueFamilyIndex域指定这个pool分配的命令缓冲区需要提交的队列的所属的集合这是必需的,因为一个设备上拥有相同性能并具有相同命令的两个队列,发送相同命令到他们,表现也会各异。

pAllocator参数应用程序用来管理主机内存的,这部分在第二章有讲解。假设一个命令池被成功的创建,它的handle被赋值给pCommandPoolvkCreateCommandPool()就会返回VK_SUCCESS

一旦我们有了一个可以分配命令缓冲区的pool,我们可以通过调用vkAllocateCommandBuffers()得到新的命令缓冲区其原型如下:

VkResult vkAllocateCommandBuffers (

VkDevice  device,

const VkCommandBufferAllocateInfo*  pAllocateInfo,

VkCommandBuffer*  pCommandBuffers);

分配额命令缓冲区的设备通过device参数传递,剩余的参数描述了命令缓冲区,是通过一个VkCommandBufferAllocateInfo类型数据传递的缓冲区的地址是通过pCommandBuffers参数传递的VkCommandBufferAllocateInfo原型如下:

typedef struct VkCommandBufferAllocateInfo {

VkStructureType  sType;

const void*  pNext;

VkCommandPool  commandPool;

VkCommandBufferLevel  level;

uint32_t  commandBufferCount;

} VkCommandBufferAllocateInfo; sType应当赋值为VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO当我们仅仅使用核心功能集的时候,需要设置pNextnullptr。我在之前创建的命令池被放入了commandPool参数
level参数表示我们需要分配的命令缓冲区的级别。它可以被赋值为VK_COMMAND_BUFFER_LEVEL_PRIMARY或者VK_COMMAND_BUFFER_LEVEL_SECONDARY.Vulkan允许主命令缓冲区来调用次命令缓冲区。在我们最初的几个例子中,我们将使用主级别的命令缓冲区。将在本书的后面讲到次级命令缓冲区。

最后,commandBufferCount指定我们想要从pool中分配的命令缓冲区的数量注意,我们并没有告诉Vulkan任何关于我们需要的命令缓冲区的大小和个数。表示设备命令的内部内部数据将自由的变动以适应任意尺寸大小command。Vulkan替你管理好命令缓冲区的内存。

如果vkAllocateCommandBuffers()运行成功,会返回VK_SUCCESS并且把分配好的多个命令缓冲区handle放到pCommandBuffers这个数组。这个数组应当足够的大以容纳这些handle当然,如果你想仅仅分配一个命令缓冲区,可以只声明一个普通的VkCommandBuffer类型的变量即可。

需要使用vkFreeCommandBuffers()释放命令缓冲区,其声明如下:

 

void vkFreeCommandBuffers (

VkDevice  device,

VkCommandPool  commandPool,

uint32_t  commandBufferCount,

const VkCommandBuffer*  pCommandBuffers);

device参数是进行命令缓冲区分配所在的设备commandPoolpool的handle,commandBufferCount需要释放的命令缓冲区的个数,pCommandBuffers需要被释放的命令缓冲区的handle组成的数组。注意,释放一个命令缓冲区并不意味着需要释放与它相关的资源,只是把它们放回了他们被创建所在的pool。

为了释放一个命令池所用所有资源它创建的所有命令缓冲区,需要调用vkDestroyCommandPool(),原型如下:

void vkDestroyCommandPool (

VkDevice  device,

VkCommandPool  commandPool,

const VkAllocationCallbacks* pAllocator);

拥有命令池设备是通过device参数传入的,需要销毁的命令池通过commandPool参数传递pAllocator参数应当和pool创建时所用的该参数保持一致。如果vkCreateCommandPool()pAllocator参数是nullptr,这个函数中的pAllocator参数也应该为nullptr。

pool销毁之前,没有必要显式的释放所有从它分配出来的命令缓冲区pool被销毁时,分配出来命令缓冲区,作为pool的组成部分,会自动的被释放,每个缓冲区相关的资源同样会被释放。然而这里需要注意需要保证当vkDestroyCommandPool()调用时,从pool分配出来的命令缓冲区正在执行。

 

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值