Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
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
176 changes: 97 additions & 79 deletions impeller/renderer/backend/vulkan/blit_command_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,46 @@

#include <cstdint>

#include "impeller/renderer/backend/vulkan/barrier_vk.h"
#include "impeller/renderer/backend/vulkan/command_encoder_vk.h"
#include "impeller/renderer/backend/vulkan/texture_vk.h"
#include "vulkan/vulkan_core.h"
#include "vulkan/vulkan_enums.hpp"
#include "vulkan/vulkan_structs.hpp"

namespace impeller {

static void InsertImageMemoryBarrier(const vk::CommandBuffer& cmd,
const vk::Image& image,
vk::AccessFlags src_access_mask,
vk::AccessFlags dst_access_mask,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::PipelineStageFlags src_stage,
vk::PipelineStageFlags dst_stage,
uint32_t base_mip_level,
uint32_t mip_level_count = 1u) {
if (old_layout == new_layout) {
return;
}

vk::ImageMemoryBarrier barrier;
barrier.srcAccessMask = src_access_mask;
barrier.dstAccessMask = dst_access_mask;
barrier.oldLayout = old_layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
barrier.subresourceRange.baseMipLevel = base_mip_level;
barrier.subresourceRange.levelCount = mip_level_count;
barrier.subresourceRange.baseArrayLayer = 0u;
barrier.subresourceRange.layerCount = 1u;

cmd.pipelineBarrier(src_stage, dst_stage, {}, nullptr, nullptr, barrier);
}

BlitEncodeVK::~BlitEncodeVK() = default;

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -240,37 +275,6 @@ std::string BlitGenerateMipmapCommandVK::GetLabel() const {
return label;
}

static void InsertImageMemoryBarrier(const vk::CommandBuffer& cmd,
const vk::Image& image,
vk::AccessFlags src_access_mask,
vk::AccessFlags dst_access_mask,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::PipelineStageFlags src_stage,
vk::PipelineStageFlags dst_stage,
uint32_t base_mip_level,
uint32_t mip_level_count = 1u) {
if (old_layout == new_layout) {
return;
}

vk::ImageMemoryBarrier barrier;
barrier.srcAccessMask = src_access_mask;
barrier.dstAccessMask = dst_access_mask;
barrier.oldLayout = old_layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
barrier.subresourceRange.baseMipLevel = base_mip_level;
barrier.subresourceRange.levelCount = mip_level_count;
barrier.subresourceRange.baseArrayLayer = 0u;
barrier.subresourceRange.layerCount = 1u;

cmd.pipelineBarrier(src_stage, dst_stage, {}, nullptr, nullptr, barrier);
}

bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const {
auto& src = TextureVK::Cast(*texture);

Expand All @@ -288,55 +292,73 @@ bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const {
return false;
}

// Transition the base mip level to transfer-src layout so we can read from
// it and transition the rest to dst-optimal since they are going to be
// written to.
// Initialize all mip levels to be in TransferDst mode. Later, in a loop,
// after writing to that mip level, we'll first switch its layout to
// TransferSrc to prepare the mip level after it, use the image as the source
// of the blit, before finally switching it to ShaderReadOnly so its available
// for sampling in a shader.
InsertImageMemoryBarrier(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Suggestion:

Initialize all mip levels to be in TransferDst mode. Later, in a loop, after writing to that mip level, we'll first switch its layout to TransferSrc to prepare the mip level after it, use the image as the source of the blit, before finally switching it to ShaderReadOnly so its available for sampling in a shader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

cmd, // command buffer
image, // image
vk::AccessFlagBits::eTransferWrite, // src access mask
vk::AccessFlagBits::eTransferRead, // dst access mask
src.GetLayout(), // old layout
vk::ImageLayout::eTransferSrcOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eTransfer, // dst stage
0u // mip level
);
InsertImageMemoryBarrier(
cmd, // command buffer
image, // image
{}, // src access mask
vk::AccessFlagBits::eTransferWrite, // dst access mask
vk::ImageLayout::eUndefined, // old layout
vk::ImageLayout::eTransferDstOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eTransfer, // dst stage
1u, // mip level
mip_count - 1 // mip level count
0u, // mip level
mip_count // mip level count
);

// Blit from the base mip level to all other levels.
vk::ImageMemoryBarrier barrier;
barrier.image = image;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.subresourceRange.levelCount = 1;

// Blit from the mip level N - 1 to mip level N.
size_t width = size.width;
size_t height = size.height;
for (size_t mip_level = 1u; mip_level < mip_count; mip_level++) {
vk::ImageBlit blit;
barrier.subresourceRange.baseMipLevel = mip_level - 1;
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;

// We just finished writing to the previous (N-1) mip level or it was the
// base mip level. These were initialized to TransferDst earler. We are now
// going to read from it to write to the current level (N) . So it must be
// converted to TransferSrc.
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make this easier to read, add a comment about why the barrier is necessary. I'll attempt to jot down how I read it but please proofread.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Suggestion:

We just finished writing to the previous (N-1) mip level. These were initialized to TransferDst earler. We are now going to read from it to write to the current level (N) . So it must be converted to TransferSrc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{barrier});

vk::ImageBlit blit;
blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
blit.srcSubresource.baseArrayLayer = 0u;
blit.srcSubresource.layerCount = 1u;
blit.srcSubresource.mipLevel = 0u;
blit.srcSubresource.mipLevel = mip_level - 1;

blit.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
blit.dstSubresource.baseArrayLayer = 0u;
blit.dstSubresource.layerCount = 1u;
blit.dstSubresource.mipLevel = mip_level;

// offsets[0] is origin.
blit.srcOffsets[1].x = size.width;
blit.srcOffsets[1].y = size.height;
blit.srcOffsets[1].x = std::max<int32_t>(width, 1u);
blit.srcOffsets[1].y = std::max<int32_t>(height, 1u);
blit.srcOffsets[1].z = 1u;

width = width / 2;
height = height / 2;

// offsets[0] is origin.
blit.dstOffsets[1].x = std::max<int32_t>(size.width >> mip_level, 1u);
blit.dstOffsets[1].y = std::max<int32_t>(size.height >> mip_level, 1u);
blit.dstOffsets[1].x = std::max<int32_t>(width, 1u);
blit.dstOffsets[1].y = std::max<int32_t>(height, 1u);
blit.dstOffsets[1].z = 1u;

cmd.blitImage(image, // src image
Expand All @@ -347,33 +369,29 @@ bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const {
&blit, // regions
vk::Filter::eLinear // filter
);

barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;

// Now that the blit is done, the image at the previous level (N-1)
// is done reading from (TransferSrc)/ Now we must prepare it to be read
// from a shader (ShaderReadOnly).
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Suggestion:

Now that the blit is done, the image at the previous level (N-1) is done reading from (TransferSrc). Now we must prepare it to be read from a shader (ShaderReadOnly).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{barrier});
}

// Transition all mip levels to shader read. The base mip level has a
// different "old" layout than the rest now.
InsertImageMemoryBarrier(
cmd, // command buffer
image, // image
vk::AccessFlagBits::eTransferWrite, // src access mask
vk::AccessFlagBits::eShaderRead, // dst access mask
vk::ImageLayout::eTransferSrcOptimal, // old layout
vk::ImageLayout::eShaderReadOnlyOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eFragmentShader, // dst stage
0u // mip level
);
InsertImageMemoryBarrier(
cmd, // command buffer
image, // image
vk::AccessFlagBits::eTransferWrite, // src access mask
vk::AccessFlagBits::eShaderRead, // dst access mask
vk::ImageLayout::eTransferDstOptimal, // old layout
vk::ImageLayout::eShaderReadOnlyOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eFragmentShader, // dst stage
1u, // mip level
mip_count - 1 // mip level count
);
barrier.subresourceRange.baseMipLevel = mip_count - 1;
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;

cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment SUggestion:

The very last mip level is still in TransferDst and must be converted to ShaderReadOnly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{barrier});

// We modified the layouts of this image from underneath it. Tell it its new
// state so it doesn't try to perform redundant transitions under the hood.
Expand Down