Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ In the next chapter, we'll create command buffers and record rendering commands.

[,c++]
----
void recordCommandBuffer(uint32_t imageIndex) {
void recordCommandBuffer(uint32_t imageIndex)
{
commandBuffer.begin({});

// Transition the image layout for rendering
Expand All @@ -38,20 +39,18 @@ void recordCommandBuffer(uint32_t imageIndex) {
// Set up the color attachment
vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f);
vk::RenderingAttachmentInfo attachmentInfo = {
.imageView = swapChainImageViews[imageIndex],
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = clearColor
};
.imageView = swapChainImageViews[imageIndex],
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = clearColor};

// Set up the rendering info
vk::RenderingInfo renderingInfo = {
.renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent },
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo
};
.renderArea = {.offset = {0, 0}, .extent = swapChainExtent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo};

// Begin rendering
commandBuffer.beginRendering(renderingInfo);
Expand Down
165 changes: 82 additions & 83 deletions en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ In addition, this allows command recording to happen in multiple threads if so d

We have to create a command pool before we can create command buffers.
Command pools manage the memory that is used to store the buffers and command buffers are allocated from them.
Add a new class member to store a `VkCommandPool`:
Add a new class member to store a `vk::raii::CommandPool`:

[,c++]
----
Expand All @@ -24,7 +24,8 @@ Then create a new function `createCommandPool` and call it from `initVulkan` aft

[,c++]
----
void initVulkan() {
void initVulkan()
{
createInstance();
setupDebugMessenger();
createSurface();
Expand All @@ -38,25 +39,26 @@ void initVulkan() {

...

void createCommandPool() {

void createCommandPool()
{
}
----

Command pool creation only takes two parameters:

[,c++]
----
vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .queueFamilyIndex = graphicsIndex };
vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = queueIndex};
----

There are two possible flags for command pools:

* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: Hint that command buffers are rerecorded with new commands very often (may change memory allocation behavior)
* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: Allow command buffers to be rerecorded individually, without this flag they all have to be reset together
* `vk::CommandPoolCreateFlagBits::eTransient`: Hint that command buffers are rerecorded with new commands very often (may change memory allocation behavior)
* `vk::CommandPoolCreateFlagBits::eResetCommandBuffer`: Allow command buffers to be rerecorded individually, without this flag they all have to be reset together

We will be recording a command buffer every frame, so we want to be able to reset and rerecord over it.
Thus, we need to set the `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag bit for our command pool.
Thus, we need to set the `vk::CommandPoolCreateFlagBits::eResetCommandBuffer` flag bit for our command pool.

Command buffers are executed by submitting them on one of the device queues, like the graphics and presentation queues we retrieved.
Each command pool can only allocate command buffers that are submitted on a single type of queue.
Expand All @@ -67,15 +69,15 @@ We're going to record commands for drawing, which is why we've chosen the graphi
commandPool = vk::raii::CommandPool(device, poolInfo);
----

Finish creating the command pool using the `vkCreateCommandPool` function.
Finish creating the command pool using the `vk::raii::CommandPool` constructor.
It doesn't have any special parameters.
Commands will be used throughout the program to draw things on the screen.

== Command buffer allocation

We can now start allocating command buffers.

Create a `VkCommandBuffer` object as a class member.
Create a `vk::raii::CommandBuffer` object as a class member.
Command buffers will be automatically freed when their command pool is destroyed, so we don't need explicit cleanup.

[,c++]
Expand All @@ -87,7 +89,8 @@ We'll now start working on a `createCommandBuffer` function to allocate a single

[,c++]
----
void initVulkan() {
void initVulkan()
{
createInstance();
setupDebugMessenger();
createSurface();
Expand All @@ -102,12 +105,12 @@ void initVulkan() {

...

void createCommandBuffer() {

void createCommandBuffer()
{
}
----

Command buffers are allocated with the `vkAllocateCommandBuffers` function, which takes a `VkCommandBufferAllocateInfo` struct as parameter that specifies the command pool and number of buffers to allocate:
Command buffers are allocated with the `vk::raii::CommandBuffers` constructor, which takes a `vk::CommandBufferAllocateInfo` struct as parameter that specifies the command pool and number of buffers to allocate:

[,c++]
----
Expand All @@ -118,87 +121,85 @@ commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front());

The `level` parameter specifies if the allocated command buffers are primary or secondary command buffers.

* `VK_COMMAND_BUFFER_LEVEL_PRIMARY`: Can be submitted to a queue for execution, but cannot be called from other command buffers.
* `VK_COMMAND_BUFFER_LEVEL_SECONDARY`: Cannot be submitted directly, but can be called from primary command buffers.
* `vk::CommandBufferLevel::ePrimary`: Can be submitted to a queue for execution, but cannot be called from other command buffers.
* `vk::CommandBufferLevel::eSecondary`: Cannot be submitted directly, but can be called from primary command buffers.

We won't make use of the secondary command buffer functionality here, but you can imagine that it's helpful to reuse common operations from primary command buffers.

Since we are only allocating one command buffer, the `commandBufferCount` parameter is just one.
The `vk::raii::CommandBuffers` constructor generates a `std::vector<vk::raii::CommandBuffer>`, but we just want a single one here, so we move it out of that vector into a singular variable.

== Command buffer recording

We'll now start working on the `recordCommandBuffer` function that writes the commands we want to execute into a command buffer.
The `VkCommandBuffer` used will be passed in as a parameter, as well as the index of the current swapchain image we want to write to.
The `vk::raii::CommandBuffer` used will be passed in as a parameter, as well as the index of the current swapchain image we want to write to.

[,c++]
----
void recordCommandBuffer(uint32_t imageIndex) {

void recordCommandBuffer(uint32_t imageIndex)
{
}
----

We always begin recording a command buffer by calling `vkBeginCommandBuffer` with a small `VkCommandBufferBeginInfo` structure as argument that specifies some details about the usage of this specific command buffer.
We always begin recording a command buffer by calling `vk::raii::CommandBuffer::begin` with a small `vk::CommandBufferBeginInfo` structure as argument that specifies some details about the usage of this specific command buffer.

[,c++]
----
commandBuffer->begin( {} );
commandBuffer->begin({});
----

The `flags` parameter specifies how we're going to use the command buffer.
The `flags` member of the `vk::CommandBufferBeginInfo` specifies how we're going to use the command buffer.
The following values are available:

* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`: The command buffer will be rerecorded right after executing it once.
* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT`: This is a secondary command buffer that will be entirely within a single render pass.
* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: The command buffer can be resubmitted while it is also already pending execution.
* `vk::CommandBufferUsageFlagBits::eOneTimeSubmit`: The command buffer will be rerecorded right after executing it once.
* `vk::CommandBufferUsageFlagBits::eRenderPassContinue`: This is a secondary command buffer that will be entirely within a single render pass.
* `vk::CommandBufferUsageFlagBits::eSimultaneousUse`: The command buffer can be resubmitted while it is also already pending execution.

None of these flags are applicable for us right now.

The `pInheritanceInfo` parameter is only relevant for secondary command buffers.
The `pInheritanceInfo` member of the `vk::CommandBufferBeginInfo` is only relevant for secondary command buffers.
It specifies which state to inherit from the calling primary command buffers.

If the command buffer was already recorded once, then a call to `vkBeginCommandBuffer` will implicitly reset it.
If the command buffer was already recorded once, then a call to `vk::raii::CommandBuffer::begin` will implicitly reset it.
It's not possible to append commands to a buffer at a later time.

== Image layout transitions

Before we can start rendering to an image, we need to transition its layout to one that is suitable for rendering. In Vulkan, images can be in different layouts that are optimized for different operations. For example, an image can be in a layout that is optimal for presenting to the screen, or in a layout that is optimal for being used as a color attachment.

We'll use a pipeline barrier to transition the image layout from `VK_IMAGE_LAYOUT_UNDEFINED` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`:
We'll use a pipeline barrier to transition the image layout from `vk::ImageLayout::eUndefined` to `vk::ImageLayout::eColorAttachmentOptimal`:

[,c++]
----
void transition_image_layout(
uint32_t imageIndex,
vk::ImageLayout oldLayout,
vk::ImageLayout newLayout,
vk::AccessFlags2 srcAccessMask,
vk::AccessFlags2 dstAccessMask,
vk::PipelineStageFlags2 srcStageMask,
vk::PipelineStageFlags2 dstStageMask
) {
vk::ImageMemoryBarrier2 barrier = {
.srcStageMask = srcStageMask,
.srcAccessMask = srcAccessMask,
.dstStageMask = dstStageMask,
.dstAccessMask = dstAccessMask,
.oldLayout = oldLayout,
.newLayout = newLayout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapChainImages[imageIndex],
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
};
vk::DependencyInfo dependencyInfo = {
.dependencyFlags = {},
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &barrier
};
uint32_t imageIndex,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::AccessFlags2 src_access_mask,
vk::AccessFlags2 dst_access_mask,
vk::PipelineStageFlags2 src_stage_mask,
vk::PipelineStageFlags2 dst_stage_mask)
{
vk::ImageMemoryBarrier2 barrier = {
.srcStageMask = src_stage_mask,
.srcAccessMask = src_access_mask,
.dstStageMask = dst_stage_mask,
.dstAccessMask = dst_access_mask,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapChainImages[imageIndex],
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1}};
vk::DependencyInfo dependency_info = {
.dependencyFlags = {},
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &barrier};
commandBuffer.pipelineBarrier2(dependencyInfo);
}
----
Expand All @@ -211,44 +212,42 @@ With dynamic rendering, we don't need to create a render pass or framebuffers. I

[,c++]
----
// Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL
// Before starting rendering, transition the swapchain image to vk::ImageLayout::eColorAttachmentOptimal
transition_image_layout(
imageIndex,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal,
{}, // srcAccessMask (no need to wait for previous operations)
vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask
vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage
vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage
{}, // srcAccessMask (no need to wait for previous operations)
vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask
vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage
vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage
);
----

First, we transition the image layout to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. Then, we set up the color attachment:
First, we transition the image layout to `vk::ImageLayout::eColorAttachmentOptimal`. Then, we set up the color attachment:

[,c++]
----
vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f);
vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f);
vk::RenderingAttachmentInfo attachmentInfo = {
.imageView = swapChainImageViews[imageIndex],
.imageView = swapChainImageViews[imageIndex],
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = clearColor
};
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = clearColor};
----

The `imageView` parameter specifies which image view to render to. The `imageLayout` parameter specifies the layout the image will be in during rendering. The `loadOp` parameter specifies what to do with the image before rendering, and the `storeOp` parameter specifies what to do with the image after rendering. We're using `VK_ATTACHMENT_LOAD_OP_CLEAR` to clear the image to black before rendering, and `VK_ATTACHMENT_STORE_OP_STORE` to store the rendered image for later use.
The `imageView` parameter specifies which image view to render to. The `imageLayout` parameter specifies the layout the image will be in during rendering. The `loadOp` parameter specifies what to do with the image before rendering, and the `storeOp` parameter specifies what to do with the image after rendering. We're using `VK_ATTACHMENT_LOAD_OP_CLEAR` to clear the image to black before rendering, and `vk::AttachmentStoreOp::eStore` to store the rendered image for later use.

Next, we set up the rendering info:

[,c++]
----
vk::RenderingInfo renderingInfo = {
.renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent },
.layerCount = 1,
.renderArea = {.offset = {0, 0}, .extent = swapChainExtent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentInfo
};
.pColorAttachments = &attachmentInfo};
----

The `renderArea` parameter defines the size of the render area, similar to the render area in a render pass. The `layerCount` parameter specifies the number of layers to render to, which is 1 for a non-layered image. The `colorAttachmentCount` and `pColorAttachments` parameters specify the color attachments to render to.
Expand All @@ -260,7 +259,7 @@ Now we can begin rendering:
commandBuffer.beginRendering(renderingInfo);
----

All the functions that record commands can be recognized by their `vkCmd` prefix. They all return `void`, so there will be no error handling until we've finished recording.
All the functions that record commands return `void`, so there will be no error handling until we've finished recording.

The parameter for the `beginRendering` command is the rendering info we just set up, which specifies the attachments to render to and the render area.

Expand All @@ -270,13 +269,13 @@ We can now bind the graphics pipeline:

[,c++]
----
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline);
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline);
----

The first parameter specifies if the pipeline object is a graphics or compute pipeline.
We've now told Vulkan which operations to execute in the graphics pipeline and which attachment to use in the fragment shader.

As noted in the link:../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state[fixed functions chapter], we did specify viewport and scissor state for this pipeline to be dynamic.
As noted in the link:../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state[fixed functions chapter], we did specify viewport and scissor state for this pipeline to be dynamic.
So we need to set them in the command buffer before issuing our draw command:

[,c++]
Expand All @@ -292,8 +291,8 @@ Now we are ready to issue the draw command for the triangle:
commandBuffer.draw(3, 1, 0, 0);
----

The actual `vkCmdDraw` function is a bit anticlimactic, but it's so simple because of all the information we specified in advance.
It has the following parameters, aside from the command buffer:
The actual `vk::raii::CommandBuffer::draw` function is a bit anticlimactic, but it's so simple because of all the information we specified in advance.
It has the following parameters:

* `vertexCount`: Even though we don't have a vertex buffer, we technically still have 3 vertices to draw.
* `instanceCount`: Used for instanced rendering, use `1` if you're not doing that.
Expand All @@ -309,11 +308,11 @@ The rendering can now be ended:
commandBuffer.endRendering();
----

After rendering, we need to transition the image layout back to `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` so it can be presented to the screen:
After rendering, we need to transition the image layout back to `vk::ImageLayout::ePresentSrcKHR` so it can be presented to the screen:

[,c++]
----
// After rendering, transition the swapchain image to PRESENT_SRC
// After rendering, transition the swapchain image to vk::ImageLayout::ePresentSrcKHR
transition_image_layout(
imageIndex,
vk::ImageLayout::eColorAttachmentOptimal,
Expand Down
Loading