blob: 86fadc770cdd0309e24c8cdafe8b3f66c5857777 [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/render_pass_builder_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_handles.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 = 64u;
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));
} else if (stencil.has_value()) {
clears.emplace_back(VKClearValueFromDepthStencil(
stencil->clear_stencil, depth ? depth->clear_depth : 0.0f));
}
return clears;
}
SharedHandleVK<vk::RenderPass> RenderPassVK::CreateVKRenderPass(
const ContextVK& context,
const SharedHandleVK<vk::RenderPass>& recycled_renderpass,
const std::shared_ptr<CommandBufferVK>& command_buffer) const {
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;
RenderPassBuilderVK builder;
for (const auto& [bind_point, color] : render_target_.GetColorAttachments()) {
builder.SetColorAttachment(
bind_point, //
color.texture->GetTextureDescriptor().format, //
color.texture->GetTextureDescriptor().sample_count, //
color.load_action, //
color.store_action //
);
TextureVK::Cast(*color.texture).SetLayout(barrier);
if (color.resolve_texture) {
TextureVK::Cast(*color.resolve_texture).SetLayout(barrier);
}
}
if (auto depth = render_target_.GetDepthAttachment(); depth.has_value()) {
builder.SetDepthStencilAttachment(
depth->texture->GetTextureDescriptor().format, //
depth->texture->GetTextureDescriptor().sample_count, //
depth->load_action, //
depth->store_action //
);
} else if (auto stencil = render_target_.GetStencilAttachment();
stencil.has_value()) {
builder.SetStencilAttachment(
stencil->texture->GetTextureDescriptor().format, //
stencil->texture->GetTextureDescriptor().sample_count, //
stencil->load_action, //
stencil->store_action //
);
}
if (recycled_renderpass != nullptr) {
return recycled_renderpass;
}
auto pass = builder.Build(context.GetDevice());
if (!pass) {
VALIDATION_LOG << "Failed to create render pass for framebuffer.";
return {};
}
context.SetDebugName(pass.get(), debug_label_.c_str());
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)) {
color_image_vk_ =
render_target_.GetColorAttachments().find(0u)->second.texture;
resolve_image_vk_ =
render_target_.GetColorAttachments().find(0u)->second.resolve_texture;
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;
});
SharedHandleVK<vk::RenderPass> recycled_render_pass;
SharedHandleVK<vk::Framebuffer> recycled_framebuffer;
if (resolve_image_vk_) {
recycled_render_pass =
TextureVK::Cast(*resolve_image_vk_).GetCachedRenderPass();
recycled_framebuffer =
TextureVK::Cast(*resolve_image_vk_).GetCachedFramebuffer();
}
const auto& target_size = render_target_.GetRenderTargetSize();
render_pass_ =
CreateVKRenderPass(vk_context, recycled_render_pass, command_buffer_);
if (!render_pass_) {
VALIDATION_LOG << "Could not create renderpass.";
is_valid_ = false;
return;
}
auto framebuffer = (recycled_framebuffer == nullptr)
? CreateVKFramebuffer(vk_context, *render_pass_)
: recycled_framebuffer;
if (!framebuffer) {
VALIDATION_LOG << "Could not create framebuffer.";
is_valid_ = false;
return;
}
if (!encoder->Track(framebuffer) || !encoder->Track(render_pass_)) {
is_valid_ = false;
return;
}
if (resolve_image_vk_) {
TextureVK::Cast(*resolve_image_vk_).SetCachedFramebuffer(framebuffer);
TextureVK::Cast(*resolve_image_vk_).SetCachedRenderPass(render_pass_);
}
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.
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.
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);
// Set the initial stencil reference.
command_buffer_vk_.setStencilReference(
vk::StencilFaceFlagBits::eVkStencilFrontAndBack, 0u);
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-stencil, 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());
} else 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) {
pipeline_ = pipeline.get();
if (!pipeline_) {
return;
}
pipeline_uses_input_attachments_ =
pipeline_->GetDescriptor().GetVertexDescriptor()->UsesInputAttacments();
if (pipeline_uses_input_attachments_) {
if (bound_image_offset_ >= kMaxBindings) {
pipeline_ = nullptr;
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_) {
return fml::Status(fml::StatusCode::kCancelled,
"No valid pipeline is bound to the RenderPass.");
}
//----------------------------------------------------------------------------
/// If there are immutable samplers referenced in the render pass, the base
/// pipeline variant is no longer valid and needs to be re-constructed to
/// reference the samplers.
///
/// This is an instance of JIT creation of PSOs that can cause jank. It is
/// unavoidable because it isn't possible to know all possible combinations of
/// target YUV conversions. Fortunately, this will only ever happen when
/// rendering to external textures. Like Android Hardware Buffers on Android.
///
/// Even when JIT creation is unavoidable, pipelines will cache their variants
/// when able and all pipeline creation will happen via a base pipeline cache
/// anyway. So the jank can be mostly entirely ameliorated and it should only
/// ever happen when the first unknown YUV conversion is encountered.
///
/// Jank can be completely eliminated by pre-populating known YUV conversion
/// pipelines.
if (immutable_sampler_) {
std::shared_ptr<PipelineVK> pipeline_variant =
PipelineVK::Cast(*pipeline_)
.CreateVariantForImmutableSamplers(immutable_sampler_);
if (!pipeline_variant) {
return fml::Status(
fml::StatusCode::kAborted,
"Could not create pipeline variant with immutable sampler.");
}
pipeline_ = pipeline_variant.get();
}
const auto& context_vk = ContextVK::Cast(*context_);
const auto& pipeline_vk = PipelineVK::Cast(*pipeline_);
auto descriptor_result =
command_buffer_->GetEncoder()->AllocateDescriptorSets(
pipeline_vk.GetDescriptorSetLayout(), context_vk);
if (!descriptor_result.ok()) {
return fml::Status(fml::StatusCode::kAborted,
"Could not allocate descriptor sets.");
}
const auto descriptor_set = descriptor_result.value();
const auto pipeline_layout = pipeline_vk.GetPipelineLayout();
command_buffer_vk_.bindPipeline(vk::PipelineBindPoint::eGraphics,
pipeline_vk.GetPipeline());
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 (pipeline_uses_input_attachments_) {
InsertBarrierForInputAttachmentRead(
command_buffer_vk_, TextureVK::Cast(*color_image_vk_).GetImage());
}
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_ = nullptr;
pipeline_uses_input_attachments_ = false;
immutable_sampler_ = nullptr;
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;
}
if (!immutable_sampler_) {
immutable_sampler_ = texture_vk.GetImmutableSamplerVariant(sampler_vk);
}
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 &
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