[Impeller] immediately encode blit commands for Vulkan. (flutter/engine#52794)

No reason to defer encoding. Just like cmd buffers, we can immediately encode this. Reduce heap allocation of command objects, which were additionally stored in unique ptrs for extra memory inefficiency.
This commit is contained in:
Jonah Williams 2024-05-14 16:50:09 -07:00 committed by GitHub
parent b6bb39ad2d
commit b322ce3a58
10 changed files with 352 additions and 635 deletions

View File

@ -169,7 +169,6 @@
../../../flutter/impeller/renderer/backend/gles/test
../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm
../../../flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc
../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc
../../../flutter/impeller/renderer/backend/vulkan/command_encoder_vk_unittests.cc
../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc
../../../flutter/impeller/renderer/backend/vulkan/context_vk_unittests.cc

View File

@ -42706,8 +42706,6 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/android/ahb_texture_so
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/barrier_vk.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/barrier_vk.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc + ../../../flutter/LICENSE
@ -45579,8 +45577,6 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/android/ahb_texture_sour
FILE: ../../../flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h
FILE: ../../../flutter/impeller/renderer/backend/vulkan/barrier_vk.cc
FILE: ../../../flutter/impeller/renderer/backend/vulkan/barrier_vk.h
FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk.cc
FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk.h
FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.cc
FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.h
FILE: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc

View File

@ -9,7 +9,6 @@ impeller_component("vulkan_unittests") {
testonly = true
sources = [
"allocator_vk_unittests.cc",
"blit_command_vk_unittests.cc",
"command_encoder_vk_unittests.cc",
"command_pool_vk_unittests.cc",
"context_vk_unittests.cc",
@ -38,8 +37,6 @@ impeller_component("vulkan") {
"allocator_vk.h",
"barrier_vk.cc",
"barrier_vk.h",
"blit_command_vk.cc",
"blit_command_vk.h",
"blit_pass_vk.cc",
"blit_pass_vk.h",
"capabilities_vk.cc",

View File

@ -1,422 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/renderer/backend/vulkan/blit_command_vk.h"
#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;
//------------------------------------------------------------------------------
/// BlitCopyTextureToTextureCommandVK
///
BlitCopyTextureToTextureCommandVK::~BlitCopyTextureToTextureCommandVK() =
default;
std::string BlitCopyTextureToTextureCommandVK::GetLabel() const {
return label;
}
bool BlitCopyTextureToTextureCommandVK::Encode(
CommandEncoderVK& encoder) const {
const auto& cmd_buffer = encoder.GetCommandBuffer();
const auto& src = TextureVK::Cast(*source);
const auto& dst = TextureVK::Cast(*destination);
if (!encoder.Track(source) || !encoder.Track(destination)) {
return false;
}
BarrierVK src_barrier;
src_barrier.cmd_buffer = cmd_buffer;
src_barrier.new_layout = vk::ImageLayout::eTransferSrcOptimal;
src_barrier.src_access = vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eColorAttachmentWrite;
src_barrier.src_stage = vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eColorAttachmentOutput;
src_barrier.dst_access = vk::AccessFlagBits::eTransferRead;
src_barrier.dst_stage = vk::PipelineStageFlagBits::eTransfer;
BarrierVK dst_barrier;
dst_barrier.cmd_buffer = cmd_buffer;
dst_barrier.new_layout = vk::ImageLayout::eTransferDstOptimal;
dst_barrier.src_access = {};
dst_barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_barrier.dst_access =
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferWrite;
dst_barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer;
if (!src.SetLayout(src_barrier) || !dst.SetLayout(dst_barrier)) {
VALIDATION_LOG << "Could not complete layout transitions.";
return false;
}
vk::ImageCopy image_copy;
image_copy.setSrcSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.setDstSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.srcOffset =
vk::Offset3D(source_region.GetX(), source_region.GetY(), 0);
image_copy.dstOffset =
vk::Offset3D(destination_origin.x, destination_origin.y, 0);
image_copy.extent =
vk::Extent3D(source_region.GetWidth(), source_region.GetHeight(), 1);
// Issue the copy command now that the images are already in the right
// layouts.
cmd_buffer.copyImage(src.GetImage(), //
src_barrier.new_layout, //
dst.GetImage(), //
dst_barrier.new_layout, //
image_copy //
);
// If this is an onscreen texture, do not transition the layout
// back to shader read.
if (dst.IsSwapchainImage()) {
return true;
}
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier.src_access = {};
barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
return dst.SetLayout(barrier);
}
//------------------------------------------------------------------------------
/// BlitCopyTextureToBufferCommandVK
///
BlitCopyTextureToBufferCommandVK::~BlitCopyTextureToBufferCommandVK() = default;
std::string BlitCopyTextureToBufferCommandVK::GetLabel() const {
return label;
}
bool BlitCopyTextureToBufferCommandVK::Encode(CommandEncoderVK& encoder) const {
const auto& cmd_buffer = encoder.GetCommandBuffer();
// cast source and destination to TextureVK
const auto& src = TextureVK::Cast(*source);
if (!encoder.Track(source) || !encoder.Track(destination)) {
return false;
}
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.new_layout = vk::ImageLayout::eTransferSrcOptimal;
barrier.src_access = vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eColorAttachmentWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eColorAttachmentOutput;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eVertexShader |
vk::PipelineStageFlagBits::eFragmentShader;
const auto& dst = DeviceBufferVK::Cast(*destination);
vk::BufferImageCopy image_copy;
image_copy.setBufferOffset(destination_offset);
image_copy.setBufferRowLength(0);
image_copy.setBufferImageHeight(0);
image_copy.setImageSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.setImageOffset(
vk::Offset3D(source_region.GetX(), source_region.GetY(), 0));
image_copy.setImageExtent(
vk::Extent3D(source_region.GetWidth(), source_region.GetHeight(), 1));
if (!src.SetLayout(barrier)) {
VALIDATION_LOG << "Could not encode layout transition.";
return false;
}
cmd_buffer.copyImageToBuffer(src.GetImage(), //
barrier.new_layout, //
dst.GetBuffer(), //
image_copy //
);
// If the buffer is used for readback, then apply a transfer -> host memory
// barrier.
if (destination->GetDeviceBufferDescriptor().readback) {
vk::MemoryBarrier barrier;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
cmd_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eHost, {}, 1,
&barrier, 0, {}, 0, {});
}
return true;
}
//------------------------------------------------------------------------------
/// BlitCopyBufferToTextureCommandVK
///
BlitCopyBufferToTextureCommandVK::~BlitCopyBufferToTextureCommandVK() = default;
std::string BlitCopyBufferToTextureCommandVK::GetLabel() const {
return label;
}
bool BlitCopyBufferToTextureCommandVK::Encode(CommandEncoderVK& encoder) const {
const auto& cmd_buffer = encoder.GetCommandBuffer();
// cast destination to TextureVK
const auto& dst = TextureVK::Cast(*destination);
const auto& src = DeviceBufferVK::Cast(*source.buffer);
if (!encoder.Track(source.buffer) || !encoder.Track(destination)) {
return false;
}
BarrierVK dst_barrier;
dst_barrier.cmd_buffer = cmd_buffer;
dst_barrier.new_layout = vk::ImageLayout::eTransferDstOptimal;
dst_barrier.src_access = {};
dst_barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_barrier.dst_access =
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferWrite;
dst_barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer;
vk::BufferImageCopy image_copy;
image_copy.setBufferOffset(source.range.offset);
image_copy.setBufferRowLength(0);
image_copy.setBufferImageHeight(0);
image_copy.setImageSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.imageOffset.x = destination_region.GetX();
image_copy.imageOffset.y = destination_region.GetY();
image_copy.imageOffset.z = 0u;
image_copy.imageExtent.width = destination_region.GetWidth();
image_copy.imageExtent.height = destination_region.GetHeight();
image_copy.imageExtent.depth = 1u;
if (!dst.SetLayout(dst_barrier)) {
VALIDATION_LOG << "Could not encode layout transition.";
return false;
}
cmd_buffer.copyBufferToImage(src.GetBuffer(), //
dst.GetImage(), //
dst_barrier.new_layout, //
image_copy //
);
// Transition to shader-read.
{
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.src_access = vk::AccessFlagBits::eTransferWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eTransfer;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
barrier.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
if (!dst.SetLayout(barrier)) {
return false;
}
}
return true;
}
//------------------------------------------------------------------------------
/// BlitGenerateMipmapCommandVK
///
BlitGenerateMipmapCommandVK::~BlitGenerateMipmapCommandVK() = default;
std::string BlitGenerateMipmapCommandVK::GetLabel() const {
return label;
}
bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const {
auto& src = TextureVK::Cast(*texture);
const auto size = src.GetTextureDescriptor().size;
uint32_t mip_count = src.GetTextureDescriptor().mip_count;
if (mip_count < 2u) {
return true;
}
const auto& image = src.GetImage();
const auto& cmd = encoder.GetCommandBuffer();
if (!encoder.Track(texture)) {
return false;
}
// 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(
cmd, // command buffer
image, // image
vk::AccessFlagBits::eTransferWrite, // src access mask
vk::AccessFlagBits::eTransferRead, // dst access mask
src.GetLayout(), // old layout
vk::ImageLayout::eTransferDstOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eTransfer, // dst stage
0u, // mip level
mip_count // mip level count
);
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++) {
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,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{barrier});
vk::ImageBlit blit;
blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
blit.srcSubresource.baseArrayLayer = 0u;
blit.srcSubresource.layerCount = 1u;
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 = 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>(width, 1u);
blit.dstOffsets[1].y = std::max<int32_t>(height, 1u);
blit.dstOffsets[1].z = 1u;
cmd.blitImage(image, // src image
vk::ImageLayout::eTransferSrcOptimal, // src layout
image, // dst image
vk::ImageLayout::eTransferDstOptimal, // dst layout
1u, // region count
&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,
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{barrier});
}
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,
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.
src.SetLayoutWithoutEncoding(vk::ImageLayout::eShaderReadOnlyOptimal);
src.SetMipMapGenerated();
return true;
}
} // namespace impeller

View File

@ -1,67 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_BLIT_COMMAND_VK_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_BLIT_COMMAND_VK_H_
#include <memory>
#include "impeller/base/backend_cast.h"
#include "impeller/renderer/backend/vulkan/context_vk.h"
#include "impeller/renderer/blit_command.h"
#include "impeller/renderer/context.h"
namespace impeller {
class CommandEncoderVK;
// TODO(csg): Should these be backend castable to blit command?
/// Mixin for dispatching Vulkan commands.
struct BlitEncodeVK : BackendCast<BlitEncodeVK, BlitCommand> {
virtual ~BlitEncodeVK();
virtual std::string GetLabel() const = 0;
[[nodiscard]] virtual bool Encode(CommandEncoderVK& encoder) const = 0;
};
struct BlitCopyTextureToTextureCommandVK
: public BlitCopyTextureToTextureCommand,
public BlitEncodeVK {
~BlitCopyTextureToTextureCommandVK() override;
std::string GetLabel() const override;
[[nodiscard]] bool Encode(CommandEncoderVK& encoder) const override;
};
struct BlitCopyTextureToBufferCommandVK : public BlitCopyTextureToBufferCommand,
public BlitEncodeVK {
~BlitCopyTextureToBufferCommandVK() override;
std::string GetLabel() const override;
[[nodiscard]] bool Encode(CommandEncoderVK& encoder) const override;
};
struct BlitCopyBufferToTextureCommandVK : public BlitCopyBufferToTextureCommand,
public BlitEncodeVK {
~BlitCopyBufferToTextureCommandVK() override;
std::string GetLabel() const override;
[[nodiscard]] bool Encode(CommandEncoderVK& encoder) const override;
};
struct BlitGenerateMipmapCommandVK : public BlitGenerateMipmapCommand,
public BlitEncodeVK {
~BlitGenerateMipmapCommandVK() override;
std::string GetLabel() const override;
[[nodiscard]] bool Encode(CommandEncoderVK& encoder) const override;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_BLIT_COMMAND_VK_H_

View File

@ -1,82 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/testing/testing.h" // IWYU pragma: keep
#include "impeller/renderer/backend/vulkan/blit_command_vk.h"
#include "impeller/renderer/backend/vulkan/command_encoder_vk.h"
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
namespace impeller {
namespace testing {
TEST(BlitCommandVkTest, BlitCopyTextureToTextureCommandVK) {
auto context = MockVulkanContextBuilder().Build();
auto pool = context->GetCommandPoolRecycler()->Get();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyTextureToTextureCommandVK cmd;
cmd.source = context->GetResourceAllocator()->CreateTexture({
.format = PixelFormat::kR8G8B8A8UNormInt,
.size = ISize(100, 100),
});
cmd.destination = context->GetResourceAllocator()->CreateTexture({
.format = PixelFormat::kR8G8B8A8UNormInt,
.size = ISize(100, 100),
});
bool result = cmd.Encode(*encoder.get());
EXPECT_TRUE(result);
EXPECT_TRUE(encoder->IsTracking(cmd.source));
EXPECT_TRUE(encoder->IsTracking(cmd.destination));
}
TEST(BlitCommandVkTest, BlitCopyTextureToBufferCommandVK) {
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyTextureToBufferCommandVK cmd;
cmd.source = context->GetResourceAllocator()->CreateTexture({
.format = PixelFormat::kR8G8B8A8UNormInt,
.size = ISize(100, 100),
});
cmd.destination = context->GetResourceAllocator()->CreateBuffer({
.size = 1,
});
bool result = cmd.Encode(*encoder.get());
EXPECT_TRUE(result);
EXPECT_TRUE(encoder->IsTracking(cmd.source));
EXPECT_TRUE(encoder->IsTracking(cmd.destination));
}
TEST(BlitCommandVkTest, BlitCopyBufferToTextureCommandVK) {
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyBufferToTextureCommandVK cmd;
cmd.destination = context->GetResourceAllocator()->CreateTexture({
.format = PixelFormat::kR8G8B8A8UNormInt,
.size = ISize(100, 100),
});
cmd.source =
DeviceBuffer::AsBufferView(context->GetResourceAllocator()->CreateBuffer({
.size = 1,
}));
bool result = cmd.Encode(*encoder.get());
EXPECT_TRUE(result);
EXPECT_TRUE(encoder->IsTracking(cmd.source.buffer));
EXPECT_TRUE(encoder->IsTracking(cmd.destination));
}
TEST(BlitCommandVkTest, BlitGenerateMipmapCommandVK) {
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitGenerateMipmapCommandVK cmd;
cmd.texture = context->GetResourceAllocator()->CreateTexture({
.format = PixelFormat::kR8G8B8A8UNormInt,
.size = ISize(100, 100),
.mip_count = 2,
});
bool result = cmd.Encode(*encoder.get());
EXPECT_TRUE(result);
EXPECT_TRUE(encoder->IsTracking(cmd.texture));
}
} // namespace testing
} // namespace impeller

View File

@ -4,13 +4,48 @@
#include "impeller/renderer/backend/vulkan/blit_pass_vk.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/renderer/backend/vulkan/barrier_vk.h"
#include "impeller/renderer/backend/vulkan/command_buffer_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 {
BlitPassVK::BlitPassVK(std::weak_ptr<CommandBufferVK> command_buffer)
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);
}
BlitPassVK::BlitPassVK(std::shared_ptr<CommandBufferVK> command_buffer)
: command_buffer_(std::move(command_buffer)) {}
BlitPassVK::~BlitPassVK() = default;
@ -30,27 +65,6 @@ bool BlitPassVK::IsValid() const {
// |BlitPass|
bool BlitPassVK::EncodeCommands(
const std::shared_ptr<Allocator>& transients_allocator) const {
TRACE_EVENT0("impeller", "BlitPassVK::EncodeCommands");
if (!IsValid()) {
return false;
}
auto command_buffer = command_buffer_.lock();
if (!command_buffer) {
return false;
}
auto encoder = command_buffer->GetEncoder();
if (!encoder) {
return false;
}
for (auto& command : commands_) {
if (!command->Encode(*encoder)) {
return false;
}
}
return true;
}
@ -61,16 +75,81 @@ bool BlitPassVK::OnCopyTextureToTextureCommand(
IRect source_region,
IPoint destination_origin,
std::string label) {
auto command = std::make_unique<BlitCopyTextureToTextureCommandVK>();
auto& encoder = *command_buffer_->GetEncoder();
const auto& cmd_buffer = encoder.GetCommandBuffer();
command->source = std::move(source);
command->destination = std::move(destination);
command->source_region = source_region;
command->destination_origin = destination_origin;
command->label = std::move(label);
const auto& src = TextureVK::Cast(*source);
const auto& dst = TextureVK::Cast(*destination);
commands_.push_back(std::move(command));
return true;
if (!encoder.Track(source) || !encoder.Track(destination)) {
return false;
}
BarrierVK src_barrier;
src_barrier.cmd_buffer = cmd_buffer;
src_barrier.new_layout = vk::ImageLayout::eTransferSrcOptimal;
src_barrier.src_access = vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eColorAttachmentWrite;
src_barrier.src_stage = vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eColorAttachmentOutput;
src_barrier.dst_access = vk::AccessFlagBits::eTransferRead;
src_barrier.dst_stage = vk::PipelineStageFlagBits::eTransfer;
BarrierVK dst_barrier;
dst_barrier.cmd_buffer = cmd_buffer;
dst_barrier.new_layout = vk::ImageLayout::eTransferDstOptimal;
dst_barrier.src_access = {};
dst_barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_barrier.dst_access =
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferWrite;
dst_barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer;
if (!src.SetLayout(src_barrier) || !dst.SetLayout(dst_barrier)) {
VALIDATION_LOG << "Could not complete layout transitions.";
return false;
}
vk::ImageCopy image_copy;
image_copy.setSrcSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.setDstSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.srcOffset =
vk::Offset3D(source_region.GetX(), source_region.GetY(), 0);
image_copy.dstOffset =
vk::Offset3D(destination_origin.x, destination_origin.y, 0);
image_copy.extent =
vk::Extent3D(source_region.GetWidth(), source_region.GetHeight(), 1);
// Issue the copy command now that the images are already in the right
// layouts.
cmd_buffer.copyImage(src.GetImage(), //
src_barrier.new_layout, //
dst.GetImage(), //
dst_barrier.new_layout, //
image_copy //
);
// If this is an onscreen texture, do not transition the layout
// back to shader read.
if (dst.IsSwapchainImage()) {
return true;
}
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier.src_access = {};
barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
return dst.SetLayout(barrier);
}
// |BlitPass|
@ -80,15 +159,65 @@ bool BlitPassVK::OnCopyTextureToBufferCommand(
IRect source_region,
size_t destination_offset,
std::string label) {
auto command = std::make_unique<BlitCopyTextureToBufferCommandVK>();
auto& encoder = *command_buffer_->GetEncoder();
const auto& cmd_buffer = encoder.GetCommandBuffer();
command->source = std::move(source);
command->destination = std::move(destination);
command->source_region = source_region;
command->destination_offset = destination_offset;
command->label = std::move(label);
// cast source and destination to TextureVK
const auto& src = TextureVK::Cast(*source);
if (!encoder.Track(source) || !encoder.Track(destination)) {
return false;
}
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.new_layout = vk::ImageLayout::eTransferSrcOptimal;
barrier.src_access = vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eColorAttachmentWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eColorAttachmentOutput;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eVertexShader |
vk::PipelineStageFlagBits::eFragmentShader;
const auto& dst = DeviceBufferVK::Cast(*destination);
vk::BufferImageCopy image_copy;
image_copy.setBufferOffset(destination_offset);
image_copy.setBufferRowLength(0);
image_copy.setBufferImageHeight(0);
image_copy.setImageSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.setImageOffset(
vk::Offset3D(source_region.GetX(), source_region.GetY(), 0));
image_copy.setImageExtent(
vk::Extent3D(source_region.GetWidth(), source_region.GetHeight(), 1));
if (!src.SetLayout(barrier)) {
VALIDATION_LOG << "Could not encode layout transition.";
return false;
}
cmd_buffer.copyImageToBuffer(src.GetImage(), //
barrier.new_layout, //
dst.GetBuffer(), //
image_copy //
);
// If the buffer is used for readback, then apply a transfer -> host memory
// barrier.
if (destination->GetDeviceBufferDescriptor().readback) {
vk::MemoryBarrier barrier;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
cmd_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eHost, {}, 1,
&barrier, 0, {}, 0, {});
}
commands_.push_back(std::move(command));
return true;
}
@ -99,27 +228,196 @@ bool BlitPassVK::OnCopyBufferToTextureCommand(
IRect destination_region,
std::string label,
uint32_t slice) {
auto command = std::make_unique<BlitCopyBufferToTextureCommandVK>();
auto& encoder = *command_buffer_->GetEncoder();
const auto& cmd_buffer = encoder.GetCommandBuffer();
command->source = std::move(source);
command->destination = std::move(destination);
command->destination_region = destination_region;
command->label = std::move(label);
command->slice = slice;
// cast destination to TextureVK
const auto& dst = TextureVK::Cast(*destination);
const auto& src = DeviceBufferVK::Cast(*source.buffer);
if (!encoder.Track(source.buffer) || !encoder.Track(destination)) {
return false;
}
BarrierVK dst_barrier;
dst_barrier.cmd_buffer = cmd_buffer;
dst_barrier.new_layout = vk::ImageLayout::eTransferDstOptimal;
dst_barrier.src_access = {};
dst_barrier.src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_barrier.dst_access =
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferWrite;
dst_barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer;
vk::BufferImageCopy image_copy;
image_copy.setBufferOffset(source.range.offset);
image_copy.setBufferRowLength(0);
image_copy.setBufferImageHeight(0);
image_copy.setImageSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1));
image_copy.imageOffset.x = destination_region.GetX();
image_copy.imageOffset.y = destination_region.GetY();
image_copy.imageOffset.z = 0u;
image_copy.imageExtent.width = destination_region.GetWidth();
image_copy.imageExtent.height = destination_region.GetHeight();
image_copy.imageExtent.depth = 1u;
if (!dst.SetLayout(dst_barrier)) {
VALIDATION_LOG << "Could not encode layout transition.";
return false;
}
cmd_buffer.copyBufferToImage(src.GetBuffer(), //
dst.GetImage(), //
dst_barrier.new_layout, //
image_copy //
);
// Transition to shader-read.
{
BarrierVK barrier;
barrier.cmd_buffer = cmd_buffer;
barrier.src_access = vk::AccessFlagBits::eTransferWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eTransfer;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
barrier.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
if (!dst.SetLayout(barrier)) {
return false;
}
}
commands_.push_back(std::move(command));
return true;
}
// |BlitPass|
bool BlitPassVK::OnGenerateMipmapCommand(std::shared_ptr<Texture> texture,
std::string label) {
auto command = std::make_unique<BlitGenerateMipmapCommandVK>();
auto& encoder = *command_buffer_->GetEncoder();
auto& src = TextureVK::Cast(*texture);
command->texture = std::move(texture);
command->label = std::move(label);
const auto size = src.GetTextureDescriptor().size;
uint32_t mip_count = src.GetTextureDescriptor().mip_count;
if (mip_count < 2u) {
return true;
}
const auto& image = src.GetImage();
const auto& cmd = encoder.GetCommandBuffer();
if (!encoder.Track(texture)) {
return false;
}
// 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(
cmd, // command buffer
image, // image
vk::AccessFlagBits::eTransferWrite, // src access mask
vk::AccessFlagBits::eTransferRead, // dst access mask
src.GetLayout(), // old layout
vk::ImageLayout::eTransferDstOptimal, // new layout
vk::PipelineStageFlagBits::eTransfer, // src stage
vk::PipelineStageFlagBits::eTransfer, // dst stage
0u, // mip level
mip_count // mip level count
);
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++) {
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,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{barrier});
vk::ImageBlit blit;
blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
blit.srcSubresource.baseArrayLayer = 0u;
blit.srcSubresource.layerCount = 1u;
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 = 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>(width, 1u);
blit.dstOffsets[1].y = std::max<int32_t>(height, 1u);
blit.dstOffsets[1].z = 1u;
cmd.blitImage(image, // src image
vk::ImageLayout::eTransferSrcOptimal, // src layout
image, // dst image
vk::ImageLayout::eTransferDstOptimal, // dst layout
1u, // region count
&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,
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{barrier});
}
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,
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.
src.SetLayoutWithoutEncoding(vk::ImageLayout::eShaderReadOnlyOptimal);
src.SetMipMapGenerated();
commands_.push_back(std::move(command));
return true;
}

View File

@ -7,7 +7,6 @@
#include "flutter/impeller/base/config.h"
#include "impeller/geometry/rect.h"
#include "impeller/renderer/backend/vulkan/blit_command_vk.h"
#include "impeller/renderer/blit_pass.h"
namespace impeller {
@ -23,11 +22,10 @@ class BlitPassVK final : public BlitPass {
private:
friend class CommandBufferVK;
std::weak_ptr<CommandBufferVK> command_buffer_;
std::vector<std::unique_ptr<BlitEncodeVK>> commands_;
std::shared_ptr<CommandBufferVK> command_buffer_;
std::string label_;
explicit BlitPassVK(std::weak_ptr<CommandBufferVK> command_buffer);
explicit BlitPassVK(std::shared_ptr<CommandBufferVK> command_buffer);
// |BlitPass|
bool IsValid() const override;

View File

@ -76,7 +76,7 @@ std::shared_ptr<BlitPass> CommandBufferVK::OnCreateBlitPass() {
if (!IsValid()) {
return nullptr;
}
auto pass = std::shared_ptr<BlitPassVK>(new BlitPassVK(weak_from_this()));
auto pass = std::shared_ptr<BlitPassVK>(new BlitPassVK(shared_from_this()));
if (!pass->IsValid()) {
return nullptr;
}

View File

@ -130,7 +130,6 @@ TEST(GPUTracerVK, TracesWithPartialFrameOverlap) {
auto cmd_buffer = context->CreateCommandBuffer();
auto blit_pass = cmd_buffer->CreateBlitPass();
blit_pass->EncodeCommands(context->GetResourceAllocator());
tracer->MarkFrameEnd();
auto latch = std::make_shared<fml::CountDownLatch>(1u);
if (!context->GetCommandQueue()
@ -140,6 +139,7 @@ TEST(GPUTracerVK, TracesWithPartialFrameOverlap) {
.ok()) {
GTEST_FAIL() << "Failed to submit cmd buffer";
}
tracer->MarkFrameEnd();
latch->Wait();