blob: 8e45b70d7bd019b73d0c8fce04be0a08774f07e3 [file] [log] [blame]
// 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/render_pass_vk.h"
#include <array>
#include <cstdint>
#include <vector>
#include "fml/status.h"
#include "impeller/base/validation.h"
#include "impeller/core/device_buffer.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture.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/context_vk.h"
#include "impeller/renderer/backend/vulkan/device_buffer_vk.h"
#include "impeller/renderer/backend/vulkan/formats_vk.h"
#include "impeller/renderer/backend/vulkan/pipeline_vk.h"
#include "impeller/renderer/backend/vulkan/sampler_vk.h"
#include "impeller/renderer/backend/vulkan/shared_object_vk.h"
#include "impeller/renderer/backend/vulkan/texture_vk.h"
#include "vulkan/vulkan_enums.hpp"
#include "vulkan/vulkan_handles.hpp"
#include "vulkan/vulkan_to_string.hpp"
namespace impeller {
// Warning: if any of the constant values or layouts are changed in the
// framebuffer fetch shader, then this input binding may need to be
// manually changed.
//
// See: impeller/entity/shaders/blending/framebuffer_blend.frag
static constexpr size_t kMagicSubpassInputBinding = 64;
static vk::ClearColorValue VKClearValueFromColor(Color color) {
vk::ClearColorValue value;
value.setFloat32(
std::array<float, 4>{color.red, color.green, color.blue, color.alpha});
return value;
}
static vk::ClearDepthStencilValue VKClearValueFromDepthStencil(uint32_t stencil,
Scalar depth) {
vk::ClearDepthStencilValue value;
value.depth = depth;
value.stencil = stencil;
return value;
}
static std::vector<vk::ClearValue> GetVKClearValues(
const RenderTarget& target) {
std::vector<vk::ClearValue> clears;
for (const auto& [_, color] : target.GetColorAttachments()) {
clears.emplace_back(VKClearValueFromColor(color.clear_color));
if (color.resolve_texture) {
clears.emplace_back(VKClearValueFromColor(color.clear_color));
}
}
const auto& depth = target.GetDepthAttachment();
const auto& stencil = target.GetStencilAttachment();
if (depth.has_value()) {
clears.emplace_back(VKClearValueFromDepthStencil(
stencil ? stencil->clear_stencil : 0u, depth->clear_depth));
}
if (stencil.has_value()) {
clears.emplace_back(VKClearValueFromDepthStencil(
stencil->clear_stencil, depth ? depth->clear_depth : 0.0f));
}
return clears;
}
static vk::AttachmentDescription CreateAttachmentDescription(
const Attachment& attachment,
const std::shared_ptr<Texture> Attachment::*texture_ptr,
bool supports_framebuffer_fetch) {
const auto& texture = attachment.*texture_ptr;
if (!texture) {
return {};
}
const auto& texture_vk = TextureVK::Cast(*texture);
const auto& desc = texture->GetTextureDescriptor();
auto current_layout = texture_vk.GetLayout();
auto load_action = attachment.load_action;
auto store_action = attachment.store_action;
if (current_layout == vk::ImageLayout::eUndefined) {
load_action = LoadAction::kClear;
}
if (desc.storage_mode == StorageMode::kDeviceTransient) {
store_action = StoreAction::kDontCare;
} else if (texture_ptr == &Attachment::resolve_texture) {
store_action = StoreAction::kStore;
}
// Always insert a barrier to transition to color attachment optimal.
if (current_layout != vk::ImageLayout::ePresentSrcKHR) {
// Note: This should incur a barrier.
current_layout = vk::ImageLayout::eGeneral;
}
return CreateAttachmentDescription(desc.format, //
desc.sample_count, //
load_action, //
store_action, //
current_layout,
supports_framebuffer_fetch //
);
}
static void SetTextureLayout(
const Attachment& attachment,
const vk::AttachmentDescription& attachment_desc,
const std::shared_ptr<CommandBufferVK>& command_buffer,
const std::shared_ptr<Texture> Attachment::*texture_ptr) {
const auto& texture = attachment.*texture_ptr;
if (!texture) {
return;
}
const auto& texture_vk = TextureVK::Cast(*texture);
if (attachment_desc.initialLayout == vk::ImageLayout::eGeneral) {
BarrierVK barrier;
barrier.new_layout = vk::ImageLayout::eGeneral;
barrier.cmd_buffer = command_buffer->GetEncoder()->GetCommandBuffer();
barrier.src_access = vk::AccessFlagBits::eShaderRead;
barrier.src_stage = vk::PipelineStageFlagBits::eFragmentShader;
barrier.dst_access = vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eTransferWrite;
barrier.dst_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eTransfer;
texture_vk.SetLayout(barrier);
}
// Instead of transitioning layouts manually using barriers, we are going to
// make the subpass perform our transitions.
texture_vk.SetLayoutWithoutEncoding(attachment_desc.finalLayout);
}
SharedHandleVK<vk::RenderPass> RenderPassVK::CreateVKRenderPass(
const ContextVK& context,
const std::shared_ptr<CommandBufferVK>& command_buffer,
bool supports_framebuffer_fetch) const {
std::vector<vk::AttachmentDescription> attachments;
std::vector<vk::AttachmentReference> color_refs;
std::vector<vk::AttachmentReference> resolve_refs;
vk::AttachmentReference depth_stencil_ref = kUnusedAttachmentReference;
// Spec says: "Each element of the pColorAttachments array corresponds to an
// output location in the shader, i.e. if the shader declares an output
// variable decorated with a Location value of X, then it uses the attachment
// provided in pColorAttachments[X]. If the attachment member of any element
// of pColorAttachments is VK_ATTACHMENT_UNUSED."
//
// Just initialize all the elements as unused and fill in the valid bind
// points in the loop below.
color_refs.resize(render_target_.GetMaxColorAttacmentBindIndex() + 1u,
kUnusedAttachmentReference);
resolve_refs.resize(render_target_.GetMaxColorAttacmentBindIndex() + 1u,
kUnusedAttachmentReference);
for (const auto& [bind_point, color] : render_target_.GetColorAttachments()) {
color_refs[bind_point] = vk::AttachmentReference{
static_cast<uint32_t>(attachments.size()),
supports_framebuffer_fetch ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal};
attachments.emplace_back(CreateAttachmentDescription(
color, &Attachment::texture, supports_framebuffer_fetch));
SetTextureLayout(color, attachments.back(), command_buffer,
&Attachment::texture);
if (color.resolve_texture) {
resolve_refs[bind_point] = vk::AttachmentReference{
static_cast<uint32_t>(attachments.size()),
supports_framebuffer_fetch
? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal};
attachments.emplace_back(CreateAttachmentDescription(
color, &Attachment::resolve_texture, supports_framebuffer_fetch));
SetTextureLayout(color, attachments.back(), command_buffer,
&Attachment::resolve_texture);
}
}
auto depth = render_target_.GetDepthAttachment();
if (depth.has_value()) {
depth_stencil_ref = vk::AttachmentReference{
static_cast<uint32_t>(attachments.size()),
vk::ImageLayout::eDepthStencilAttachmentOptimal};
attachments.emplace_back(CreateAttachmentDescription(
depth.value(), &Attachment::texture, supports_framebuffer_fetch));
SetTextureLayout(depth.value(), attachments.back(), command_buffer,
&Attachment::texture);
}
if (auto stencil = render_target_.GetStencilAttachment();
stencil.has_value()) {
depth_stencil_ref = vk::AttachmentReference{
static_cast<uint32_t>(attachments.size()),
vk::ImageLayout::eDepthStencilAttachmentOptimal};
attachments.emplace_back(CreateAttachmentDescription(
stencil.value(), &Attachment::texture, supports_framebuffer_fetch));
// If the depth and stencil are stored in the same texture, then we've
// already inserted a memory barrier to transition this texture as part of
// the depth branch above.
if (depth.has_value() && depth->texture != stencil->texture) {
SetTextureLayout(stencil.value(), attachments.back(), command_buffer,
&Attachment::texture);
}
}
vk::SubpassDescription subpass_desc;
subpass_desc.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass_desc.setColorAttachments(color_refs);
subpass_desc.setResolveAttachments(resolve_refs);
subpass_desc.setPDepthStencilAttachment(&depth_stencil_ref);
std::vector<vk::SubpassDependency> subpass_dependencies;
std::vector<vk::AttachmentReference> subpass_color_ref;
subpass_color_ref.push_back(vk::AttachmentReference{
static_cast<uint32_t>(0), vk::ImageLayout::eColorAttachmentOptimal});
if (supports_framebuffer_fetch) {
subpass_desc.setFlags(vk::SubpassDescriptionFlagBits::
eRasterizationOrderAttachmentColorAccessARM);
subpass_desc.setInputAttachments(subpass_color_ref);
}
vk::RenderPassCreateInfo render_pass_desc;
render_pass_desc.setAttachments(attachments);
render_pass_desc.setPSubpasses(&subpass_desc);
render_pass_desc.setSubpassCount(1u);
auto [result, pass] =
context.GetDevice().createRenderPassUnique(render_pass_desc);
if (result != vk::Result::eSuccess) {
VALIDATION_LOG << "Failed to create render pass: " << vk::to_string(result);
return {};
}
return MakeSharedVK(std::move(pass));
}
RenderPassVK::RenderPassVK(const std::shared_ptr<const Context>& context,
const RenderTarget& target,
std::shared_ptr<CommandBufferVK> command_buffer)
: RenderPass(context, target), command_buffer_(std::move(command_buffer)) {
const auto& vk_context = ContextVK::Cast(*context);
const std::shared_ptr<CommandEncoderVK>& encoder =
command_buffer_->GetEncoder();
command_buffer_vk_ = encoder->GetCommandBuffer();
render_target_.IterateAllAttachments(
[&encoder](const auto& attachment) -> bool {
encoder->Track(attachment.texture);
encoder->Track(attachment.resolve_texture);
return true;
});
const auto& target_size = render_target_.GetRenderTargetSize();
render_pass_ = CreateVKRenderPass(
vk_context, command_buffer_,
vk_context.GetCapabilities()->SupportsFramebufferFetch());
if (!render_pass_) {
VALIDATION_LOG << "Could not create renderpass.";
is_valid_ = false;
return;
}
auto framebuffer = CreateVKFramebuffer(vk_context, *render_pass_);
if (!framebuffer) {
VALIDATION_LOG << "Could not create framebuffer.";
is_valid_ = false;
return;
}
if (!encoder->Track(framebuffer) || !encoder->Track(render_pass_)) {
is_valid_ = false;
return;
}
auto clear_values = GetVKClearValues(render_target_);
vk::RenderPassBeginInfo pass_info;
pass_info.renderPass = *render_pass_;
pass_info.framebuffer = *framebuffer;
pass_info.renderArea.extent.width = static_cast<uint32_t>(target_size.width);
pass_info.renderArea.extent.height =
static_cast<uint32_t>(target_size.height);
pass_info.setClearValues(clear_values);
command_buffer_vk_.beginRenderPass(pass_info, vk::SubpassContents::eInline);
// Set the initial viewport and scissors.
const auto vp = Viewport{.rect = Rect::MakeSize(target_size)};
vk::Viewport viewport = vk::Viewport()
.setWidth(vp.rect.GetWidth())
.setHeight(-vp.rect.GetHeight())
.setY(vp.rect.GetHeight())
.setMinDepth(0.0f)
.setMaxDepth(1.0f);
command_buffer_vk_.setViewport(0, 1, &viewport);
// Set the initial scissor rect.
const auto sc = IRect::MakeSize(target_size);
vk::Rect2D scissor =
vk::Rect2D()
.setOffset(vk::Offset2D(sc.GetX(), sc.GetY()))
.setExtent(vk::Extent2D(sc.GetWidth(), sc.GetHeight()));
command_buffer_vk_.setScissor(0, 1, &scissor);
color_image_vk_ =
render_target_.GetColorAttachments().find(0u)->second.texture;
resolve_image_vk_ =
render_target_.GetColorAttachments().find(0u)->second.resolve_texture;
is_valid_ = true;
}
RenderPassVK::~RenderPassVK() = default;
bool RenderPassVK::IsValid() const {
return is_valid_;
}
void RenderPassVK::OnSetLabel(std::string label) {
#ifdef IMPELLER_DEBUG
ContextVK::Cast(*context_).SetDebugName(render_pass_->Get(),
std::string(label).c_str());
#endif // IMPELLER_DEBUG
}
SharedHandleVK<vk::Framebuffer> RenderPassVK::CreateVKFramebuffer(
const ContextVK& context,
const vk::RenderPass& pass) const {
vk::FramebufferCreateInfo fb_info;
fb_info.renderPass = pass;
const auto target_size = render_target_.GetRenderTargetSize();
fb_info.width = target_size.width;
fb_info.height = target_size.height;
fb_info.layers = 1u;
std::vector<vk::ImageView> attachments;
// This bit must be consistent to ensure compatibility with the pass created
// earlier. Follow this order: Color attachments, then depth, then stencil.
for (const auto& [_, color] : render_target_.GetColorAttachments()) {
// The bind point doesn't matter here since that information is present in
// the render pass.
attachments.emplace_back(
TextureVK::Cast(*color.texture).GetRenderTargetView());
if (color.resolve_texture) {
attachments.emplace_back(
TextureVK::Cast(*color.resolve_texture).GetRenderTargetView());
}
}
if (auto depth = render_target_.GetDepthAttachment(); depth.has_value()) {
attachments.emplace_back(
TextureVK::Cast(*depth->texture).GetRenderTargetView());
}
if (auto stencil = render_target_.GetStencilAttachment();
stencil.has_value()) {
attachments.emplace_back(
TextureVK::Cast(*stencil->texture).GetRenderTargetView());
}
fb_info.setAttachments(attachments);
auto [result, framebuffer] =
context.GetDevice().createFramebufferUnique(fb_info);
if (result != vk::Result::eSuccess) {
VALIDATION_LOG << "Could not create framebuffer: " << vk::to_string(result);
return {};
}
return MakeSharedVK(std::move(framebuffer));
}
// |RenderPass|
void RenderPassVK::SetPipeline(
const std::shared_ptr<Pipeline<PipelineDescriptor>>& pipeline) {
PipelineVK& pipeline_vk = PipelineVK::Cast(*pipeline);
auto descriptor_result =
command_buffer_->GetEncoder()->AllocateDescriptorSets(
pipeline_vk.GetDescriptorSetLayout(), ContextVK::Cast(*context_));
if (!descriptor_result.ok()) {
return;
}
pipeline_valid_ = true;
descriptor_set_ = descriptor_result.value();
pipeline_layout_ = pipeline_vk.GetPipelineLayout();
command_buffer_vk_.bindPipeline(vk::PipelineBindPoint::eGraphics,
pipeline_vk.GetPipeline());
if (pipeline->GetDescriptor().UsesSubpassInput()) {
if (bound_image_offset_ >= kMaxBindings) {
pipeline_valid_ = false;
return;
}
vk::DescriptorImageInfo image_info;
image_info.imageLayout = vk::ImageLayout::eGeneral;
image_info.sampler = VK_NULL_HANDLE;
image_info.imageView = TextureVK::Cast(*color_image_vk_).GetImageView();
image_workspace_[bound_image_offset_++] = image_info;
vk::WriteDescriptorSet write_set;
write_set.dstBinding = kMagicSubpassInputBinding;
write_set.descriptorCount = 1u;
write_set.descriptorType = vk::DescriptorType::eInputAttachment;
write_set.pImageInfo = &image_workspace_[bound_image_offset_ - 1];
write_workspace_[descriptor_write_offset_++] = write_set;
}
}
// |RenderPass|
void RenderPassVK::SetCommandLabel(std::string_view label) {
#ifdef IMPELLER_DEBUG
command_buffer_->GetEncoder()->PushDebugGroup(label);
has_label_ = true;
#endif // IMPELLER_DEBUG
}
// |RenderPass|
void RenderPassVK::SetStencilReference(uint32_t value) {
command_buffer_vk_.setStencilReference(
vk::StencilFaceFlagBits::eVkStencilFrontAndBack, value);
}
// |RenderPass|
void RenderPassVK::SetBaseVertex(uint64_t value) {
base_vertex_ = value;
}
// |RenderPass|
void RenderPassVK::SetViewport(Viewport viewport) {
vk::Viewport viewport_vk = vk::Viewport()
.setWidth(viewport.rect.GetWidth())
.setHeight(-viewport.rect.GetHeight())
.setY(viewport.rect.GetHeight())
.setMinDepth(0.0f)
.setMaxDepth(1.0f);
command_buffer_vk_.setViewport(0, 1, &viewport_vk);
}
// |RenderPass|
void RenderPassVK::SetScissor(IRect scissor) {
vk::Rect2D scissor_vk =
vk::Rect2D()
.setOffset(vk::Offset2D(scissor.GetX(), scissor.GetY()))
.setExtent(vk::Extent2D(scissor.GetWidth(), scissor.GetHeight()));
command_buffer_vk_.setScissor(0, 1, &scissor_vk);
}
// |RenderPass|
void RenderPassVK::SetInstanceCount(size_t count) {
instance_count_ = count;
}
// |RenderPass|
bool RenderPassVK::SetVertexBuffer(VertexBuffer buffer) {
vertex_count_ = buffer.vertex_count;
if (buffer.index_type == IndexType::kUnknown || !buffer.vertex_buffer) {
return false;
}
if (!command_buffer_->GetEncoder()->Track(buffer.vertex_buffer.buffer)) {
return false;
}
// Bind the vertex buffer.
vk::Buffer vertex_buffer_handle =
DeviceBufferVK::Cast(*buffer.vertex_buffer.buffer).GetBuffer();
vk::Buffer vertex_buffers[] = {vertex_buffer_handle};
vk::DeviceSize vertex_buffer_offsets[] = {buffer.vertex_buffer.range.offset};
command_buffer_vk_.bindVertexBuffers(0u, 1u, vertex_buffers,
vertex_buffer_offsets);
// Bind the index buffer.
if (buffer.index_type != IndexType::kNone) {
has_index_buffer_ = true;
const BufferView& index_buffer_view = buffer.index_buffer;
if (!index_buffer_view) {
return false;
}
const std::shared_ptr<const DeviceBuffer>& index_buffer =
index_buffer_view.buffer;
if (!index_buffer) {
VALIDATION_LOG << "Failed to acquire device buffer"
<< " for index buffer view";
return false;
}
if (!command_buffer_->GetEncoder()->Track(index_buffer)) {
return false;
}
vk::Buffer index_buffer_handle =
DeviceBufferVK::Cast(*index_buffer).GetBuffer();
command_buffer_vk_.bindIndexBuffer(index_buffer_handle,
index_buffer_view.range.offset,
ToVKIndexType(buffer.index_type));
} else {
has_index_buffer_ = false;
}
return true;
}
// |RenderPass|
fml::Status RenderPassVK::Draw() {
if (!pipeline_valid_) {
return fml::Status(fml::StatusCode::kCancelled,
"No valid pipeline is bound to the RenderPass.");
}
const ContextVK& context_vk = ContextVK::Cast(*context_);
for (auto i = 0u; i < descriptor_write_offset_; i++) {
write_workspace_[i].dstSet = descriptor_set_;
}
context_vk.GetDevice().updateDescriptorSets(descriptor_write_offset_,
write_workspace_.data(), 0u, {});
command_buffer_vk_.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics, // bind point
pipeline_layout_, // layout
0, // first set
1, // set count
&descriptor_set_, // sets
0, // offset count
nullptr // offsets
);
if (has_index_buffer_) {
command_buffer_vk_.drawIndexed(vertex_count_, // index count
instance_count_, // instance count
0u, // first index
base_vertex_, // vertex offset
0u // first instance
);
} else {
command_buffer_vk_.draw(vertex_count_, // vertex count
instance_count_, // instance count
base_vertex_, // vertex offset
0u // first instance
);
}
#ifdef IMPELLER_DEBUG
if (has_label_) {
command_buffer_->GetEncoder()->PopDebugGroup();
}
#endif // IMPELLER_DEBUG
has_label_ = false;
has_index_buffer_ = false;
bound_image_offset_ = 0u;
bound_buffer_offset_ = 0u;
descriptor_write_offset_ = 0u;
instance_count_ = 1u;
base_vertex_ = 0u;
vertex_count_ = 0u;
pipeline_valid_ = false;
return fml::Status();
}
// The RenderPassVK binding methods only need the binding, set, and buffer type
// information.
bool RenderPassVK::BindResource(ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const ShaderMetadata& metadata,
BufferView view) {
return BindResource(slot.binding, type, view);
}
bool RenderPassVK::BindResource(
ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const std::shared_ptr<const ShaderMetadata>& metadata,
BufferView view) {
return BindResource(slot.binding, type, view);
}
bool RenderPassVK::BindResource(size_t binding,
DescriptorType type,
const BufferView& view) {
if (bound_buffer_offset_ >= kMaxBindings) {
return false;
}
const std::shared_ptr<const DeviceBuffer>& device_buffer = view.buffer;
auto buffer = DeviceBufferVK::Cast(*device_buffer).GetBuffer();
if (!buffer) {
return false;
}
if (!command_buffer_->GetEncoder()->Track(device_buffer)) {
return false;
}
uint32_t offset = view.range.offset;
vk::DescriptorBufferInfo buffer_info;
buffer_info.buffer = buffer;
buffer_info.offset = offset;
buffer_info.range = view.range.length;
buffer_workspace_[bound_buffer_offset_++] = buffer_info;
vk::WriteDescriptorSet write_set;
write_set.dstBinding = binding;
write_set.descriptorCount = 1u;
write_set.descriptorType = ToVKDescriptorType(type);
write_set.pBufferInfo = &buffer_workspace_[bound_buffer_offset_ - 1];
write_workspace_[descriptor_write_offset_++] = write_set;
return true;
}
bool RenderPassVK::BindResource(ShaderStage stage,
DescriptorType type,
const SampledImageSlot& slot,
const ShaderMetadata& metadata,
std::shared_ptr<const Texture> texture,
const std::unique_ptr<const Sampler>& sampler) {
if (bound_buffer_offset_ >= kMaxBindings) {
return false;
}
if (!texture->IsValid() || !sampler) {
return false;
}
const TextureVK& texture_vk = TextureVK::Cast(*texture);
const SamplerVK& sampler_vk = SamplerVK::Cast(*sampler);
if (!command_buffer_->GetEncoder()->Track(texture)) {
return false;
}
vk::DescriptorImageInfo image_info;
image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
image_info.sampler = sampler_vk.GetSampler();
image_info.imageView = texture_vk.GetImageView();
image_workspace_[bound_image_offset_++] = image_info;
vk::WriteDescriptorSet write_set;
write_set.dstBinding = slot.binding;
write_set.descriptorCount = 1u;
write_set.descriptorType = vk::DescriptorType::eCombinedImageSampler;
write_set.pImageInfo = &image_workspace_[bound_image_offset_ - 1];
write_workspace_[descriptor_write_offset_++] = write_set;
return true;
}
bool RenderPassVK::OnEncodeCommands(const Context& context) const {
command_buffer_->GetEncoder()->GetCommandBuffer().endRenderPass();
// If this render target will be consumed by a subsequent render pass,
// perform a layout transition to a shader read state.
const std::shared_ptr<Texture>& result_texture =
resolve_image_vk_ ? resolve_image_vk_ : color_image_vk_;
if (result_texture->GetTextureDescriptor().usage &
static_cast<TextureUsageMask>(TextureUsage::kShaderRead)) {
BarrierVK barrier;
barrier.cmd_buffer = command_buffer_vk_;
barrier.src_access = vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eTransferWrite;
barrier.src_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eTransfer;
barrier.dst_access = vk::AccessFlagBits::eShaderRead;
barrier.dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
barrier.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
if (!TextureVK::Cast(*result_texture).SetLayout(barrier)) {
return false;
}
}
return true;
}
} // namespace impeller