第三章 队列和命令 第3节 创建命令缓冲区
创建命令缓冲区
队列的意义就是在应用程序内处理任务。任务是通过一串的命令表示的,命令被记录到命令缓冲区(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创建信息的数据。这里,我们设置sType为K_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO。我们不需要传递任何其他信息,pNext需被设置为nullptr。
flags域包含标志,它控制了pool和从pool分配得倒的命令缓冲区的行为。其值为VkCommandPoolCreateFlagBits枚举类型。这个枚举现在有两个值被定义了:
l VK_COMMAND_POOL_CREATE_TRANSIENT_BIT 表示命令缓冲区使用周期短,使用完后马上退回给pool。不设置这个枚举值,就意味着告诉Vulkan,你将长时间的持有这个命令缓冲区。
l 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被赋值给pCommandPool,vkCreateCommandPool()就会返回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,当我们仅仅使用核心功能集的时候,需要设置pNext为nullptr。我们在之前创建的命令池被放入了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参数是进行命令缓冲区分配所在的设备。commandPool是pool的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分配出来的命令缓冲区正在执行。