Series of articles developed by Vulkan:
- Invader Vulkan mobile Development (I) of this life prelife
- Vulkan Mobile Development (II) : Understanding the rendering process
- Instance & Device & Queue for Vulkan mobile development
This article continues learning about the component in Vulkan: the Command Buffer.
In the previous article, we have created three components, Instance, Device and Queue, and learned that Queue is used as a bridge to communicate with physical devices. The specific communication process requires command-buffer, which is a collection of several commands. We submit the command-buffer to the Queue before it is processed by the physical device GPU.
The Command – the Pool components
Before creating a Command-Buffer, create a Command-Pool component to allocate the Command-Buffer from the Command-Pool.
Create a VkXXXXCreateInfo structure, and see the official documentation for the definition of each parameter.
// Create a command-pool component
VkCommandPool command_pool;
VkCommandPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
// We can see that command-pool is associated with Queue
poolCreateInfo.queueFamilyIndex = info.graphics_queue_family_index;
// Identifies some behavior of the command buffer
poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
// The call to create the function
vkCreateCommandPool(info.device, &poolCreateInfo, nullptr, &command_pool);
Copy the code
There are a few parameters to note:
queueFamilyIndex
The parameter is createQueue
The one I chosequeueFlags
为VK_QUEUE_GRAPHICS_BIT
Index fromCommand-Pool
Is assigned toCommand-Buffer
Must be submitted to the sameQueue
In the.flags
There are the following options, specified separatelyCommand-Buffer
Different characteristics of:
typedef enum VkCommandPoolCreateFlagBits {
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT = 0x00000001,
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT = 0x00000002,
VK_COMMAND_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkCommandPoolCreateFlagBits;
Copy the code
-
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
- According to the
Command-Buffer
Is short-lived and can be reset or released at short notice
- According to the
-
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
- Says from the
Command-Pool
In the distribution ofCommand-Buffer
Can be achieved byvkResetCommandBuffer
orvkBeginCommandBuffer
Method, cannot be called if the identity bit is not setvkResetCommandBuffer
Method to reset.
- Says from the
The Command – Buffer components
Next comes from the Command – Command – Buffer Pool in distribution, through VkCommandBufferAllocateInfo function.
You first need a VkCommandBufferAllocateInfo structure said distribution of the information you need.
typedef struct VkCommandBufferAllocateInfo {
VkStructureType sType;
const void* pNext;
VkCommandPool commandPool; // Corresponds to the command-pool created above
VkCommandBufferLevel level;
uint32_t commandBufferCount; // Number of created items
} VkCommandBufferAllocateInfo;
Copy the code
Here is another parameter to note:
VkCommandBufferLevel
The specifiedCommand-Buffer
The level of.
The following levels are available:
typedef enum VkCommandBufferLevel {
VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0,
VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1,
VK_COMMAND_BUFFER_LEVEL_BEGIN_RANGE = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
VK_COMMAND_BUFFER_LEVEL_END_RANGE = VK_COMMAND_BUFFER_LEVEL_SECONDARY,
VK_COMMAND_BUFFER_LEVEL_RANGE_SIZE = (VK_COMMAND_BUFFER_LEVEL_SECONDARY - VK_COMMAND_BUFFER_LEVEL_PRIMARY + 1),
VK_COMMAND_BUFFER_LEVEL_MAX_ENUM = 0x7FFFFFFF
} VkCommandBufferLevel;
Copy the code
In general, VK_COMMAND_BUFFER_LEVEL_PRIMARY is fine.
The specific creation code is as follows:
VkCommandBuffer commandBuffer[2];
VkCommandBufferAllocateInfo command_buffer_allocate_info{};
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.commandPool = command_pool;
command_buffer_allocate_info.commandBufferCount = 2;
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
vkAllocateCommandBuffers(info.device, &command_buffer_allocate_info, commandBuffer);
Copy the code
The life cycle of the command-buffer
After creating the command-buffer, take a look at its life cycle as shown below:
- The Initial state
When the command-buffer is created, it is initialized. From this state, you can achieve a Recording state, in addition, if reset, will return to the state.
- Recording state
Call the vkBeginCommandBuffer method from the Initial state to this state. Once in that state, a series of method logging commands such as vkCmd* can be invoked.
- The Executable state
The vkEndCommandBuffer method is called from a Recording state to a Recording state in which a CommandBuffer can be committed or reset.
- Pending state
This state occurs when the Command-Buffer is committed to the Queue. The physical device may be processing recorded commands in this state, so do not change the Command Buffer at this time. When the processing is complete, the Command Buffer may return to the Executable state or Invalid state.
- Invalid state
Some operations put the Command-Buffer into this state, where it can only be reset or released.
The recording and submission of command-buffer
Now you can try logging some commands to the Queue. The command logging process looks like this:
Between the vkBeginCommandBuffer and vkEndCommandBuffer methods, you can record the commands related to rendering. Here, you can create the commit directly without thinking about the intermediate process.
The begin stage
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer[0], &beginInfo);
Copy the code
First, you need to create a VkCommandBufferBeginInfo structure to represent the information starting from the CommandBuffer.
The argument to note here is flags, which indicates the purpose of the command-buffer,
typedef enum VkCommandBufferUsageFlagBits {
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001,
VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002,
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004,
VK_COMMAND_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkCommandBufferUsageFlagBits;
Copy the code
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
- Indicates that the command-buffer is committed only once, then reset and re-logged each time it is committed
End stage
A direct call to the vkEndCommandBuffer method terminates the recording, at which point it can be committed.
vkEndCommandBuffer(commandBuffer[0]);
Copy the code
Buffer to submit
The Command-Buffer is submitted to the Queue using the vkQueueSubmit method.
Again, we need to create a VkSubmitInfo structure:
typedef struct VkSubmitInfo {
VkStructureType sType;
const void* pNext;
uint32_t waitSemaphoreCount; // The number of Semaphore waiting
const VkSemaphore* pWaitSemaphores; // Wait for pointer to Semaphore array
const VkPipelineStageFlags* pWaitDstStageMask; // At which stage to wait
uint32_t commandBufferCount; // The number of Command buffers submitted
const VkCommandBuffer* pCommandBuffers; // Specifies the Command-Buffer array pointer
uint32_t signalSemaphoreCount; // Number of Semaphore notifications after execution
const VkSemaphore* pSignalSemaphores; // Semaphore array pointer notified after execution
} VkSubmitInfo;
Copy the code
It has a large number of parameters and involves the synchronization relationship between Command-Buffer. This section is briefly described here and discussed in detail later.
As shown below, There are four mechanisms in Vulkan to ensure synchronization: Semaphore, Fences, Event and Barrier.
A few words about Semaphore and Fence.
-
Semaphore
Semaphore
The role of is mainly used toQueue
In the submissionCommand-Buffer
To achieve synchronization. For example, someoneCommand-Buffer-B
One phase of execution requires waiting for anotherCommand-Buffer-A
Results after successful execution, andCommand-Buffer-C
At some stage you have to waitCommand-Buffer-B
Is executed as a result, then it should be usedSemaphore
Mechanism to achieve synchronization;- At this time
Command-Buffer-B
Submitted to theQueue
You need twoVkSemaphor
, one that means it needs to waitSemaphore
And specify at which stage to wait; One is when it’s doneSemaphore
.
-
Fence
Fence
Is used to ensure synchronization between physical devices and applications, for example, toQueue
In the filedCommand-Buffer
After that, the actual execution is handed over to the physical device, which is an asynchronous process that the application uses if it wants to wait for the execution to finishFence
Mechanism.
Semaphore and Fence have something in common, but are used differently, as shown in the figure below.
Semaphore and Fence are created as follows, not much different from the usual Vulkan object creation call:
/ / create a Semaphore
VkSemaphore imageAcquiredSemaphore;
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
vkCreateSemaphore(info.device, &semaphoreCreateInfo, nullptr, &imageAcquiredSemaphore);
/ / create a Fence
VkFence drawFence;
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
Signaled state indicates the state of the Fence, and if it is not set or 0 indicates unsignaled state
fence_info.flags = 0;
vkCreateFence(info.device, &fenceCreateInfo, nullptr, &drawFence);
Copy the code
Nullptr = nullPTR = nullPTR = nullPTR = nullPTR = nullPTR = nullPTR = nullPTR
// Simple submission process
// Start recording
VkCommandBufferBeginInfo beginInfo1 = {};
beginInfo1.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo1.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer[0], &beginInfo1);
// omit the intermediate vkCmdXXXX series methods
// End the record
vkEndCommandBuffer(commandBuffer[0]);
VkSubmitInfo submitInfo1 = {};
submitInfo1.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
// pWaitSemaphores and pSignalSemaphores are not set, just commit
submitInfo1.commandBufferCount = 1;
submitInfo1.pCommandBuffers = &commandBuffer[0];
// Note that the last parameter is temporarily set to VK_NULL_HANDLE, or it can be set to Fence for synchronization
vkQueueSubmit(info.queue.1, &submitInfo1, VK_NULL_HANDLE);
Copy the code
This completes the command-buffer commit to Queue, leaving out Semaphores and Fences synchronization. You can also add them.
The last argument in vkQueueSubmit is set to VK_NULL_HANDLE, which is a method set to NULL in Vulkan (which is actually an integer 0), as well as the Fence, Semaphore: indicates that we wait for the command-buffer to terminate in a Queue. Although a command-buffer can also terminate in a Semaphore, the two methods are used in different scenarios.
Back to the creation of a Fence, there is a flags parameter that indicates the state of the Fence, which has the following two states:
- signaled state
- If the flags parameter is VK_FENCE_CREATE_SIGNALED_BIT, it indicates that the flags is in this state after creation.
- unsignaled state
- Default status.
When the last argument to vkQueueSubmit is passed to the Fence, the Fence waits for the command-buffer execution to end.
// wait fence to enter the signaled state on the host
// Wrong use of waitForFences because it is not a blocking method
// VkResult res = vkWaitForFences(info.device, 1, &fence, VK_TRUE, UINT64_MAX);
VkResult res;
do {
res = vkWaitForFences(info.device, 1, &fence, VK_TRUE, UINT64_MAX);
} while (res == VK_TIMEOUT);
Copy the code
Waiting on the Fence to go into signaled State, the call is placed on a while loop. Waiting on the Fence are released from the while loop and wait for the result to become invalid. VK_SUCCESS is returned only when the result meets the requirements.
Signaled from Unsignaled State to Wait State after the execution of the Command Buffer ends, the Fence parameter passed in triggers the vkWaitForFences call ending the loop.
This is how a Fence is used, and for an example of synchronization between command-buffers via Semaphore, see a subsequent article.
conclusion
This article focuses on the use and submission of command-buffer and covers some synchronization mechanisms for Vulkan.
Specific operations related to rendering should be recorded between Command and Buffer, and then submitted to Queue after recording, so that GPU can perform specific operations. Of course, the specific execution is an asynchronous process, requiring the synchronization mechanism.
Both Semaphore and Fence can be synchronized, but in different scenarios.
Welcome to pay attention to wechat public number: [paper talk], get the latest article push ~~~