From 941474a2f670a53b5e3e82745991165688cb92ec Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 2 Jan 2024 15:25:01 -0800 Subject: [PATCH] [Impeller] Implement framebuffer-fetch via subpasses in Vulkan without extensions. * Subpasses are not exposed in the HAL and the need for subpasses in Vulkan can be determined based on the presence and use of input-attachments in the shaders. This information is already reflected by the compiler. Because of this, all references to subpasses have been removed from APIs above the HAL. * `RenderPassBuilderVK` is a lightweight object used to generate render passes to use either with the pipelines (compat, base, or per-subpass) or during rendering along with the framebuffer. Using the builder also sets up the right subpass dependencies. As long as the builder contains compatible attachments and subpass counts, different subpasses stamped by the builder (via the `Build` method) are guaranteed to be compatible per the rules in the spec. * Pass attachments are now in the `eGeneral` layout. There was no observable difference in performance when manually inserting the right transitions. Except, a lot of transitions needed to be inserted. If we need it, we can add it back in short order. I wouldn't be averse to adding it if reviewers insist. * Additional pipeline state objects need not be created as the sub-pass self-dependencies are sufficient to setup the render-pass. * Speaking of the `rasterization_order_attachment_access` extension, its use has been removed in this patch. I am prototyping adding it back to measure the overhead introduced by manual subpass management. If the overhead is measurable, we can use the extension on devices that have it as an added optimization. * The complexity of command encoding remains linear (to the number of commands) per pass. * This patch only works on a single color attachment being used as an input attachment. While this is sufficient for current use cases, the Metal implementation is significantly more capable since the multiple attachments and attachment types (depth) are already supported. Rounding out support for this is in progress. --- ci/licenses_golden/licenses_flutter | 4 + impeller/entity/contents/content_context.cc | 45 +- impeller/entity/contents/content_context.h | 4 +- impeller/entity/entity_unittests.cc | 33 -- impeller/fixtures/BUILD.gn | 14 +- impeller/fixtures/sepia.frag | 17 + impeller/fixtures/sepia.vert | 14 + impeller/fixtures/swizzle.frag | 11 + impeller/fixtures/texture.frag | 13 + impeller/fixtures/texture.vert | 18 + impeller/renderer/backend/vulkan/BUILD.gn | 2 + .../renderer/backend/vulkan/allocator_vk.cc | 20 +- .../renderer/backend/vulkan/allocator_vk.h | 1 - impeller/renderer/backend/vulkan/barrier_vk.h | 7 + .../backend/vulkan/capabilities_vk.cc | 18 +- .../renderer/backend/vulkan/capabilities_vk.h | 3 - .../renderer/backend/vulkan/context_vk.cc | 5 + impeller/renderer/backend/vulkan/context_vk.h | 4 +- .../backend/vulkan/debug_report_vk.cc | 13 +- .../renderer/backend/vulkan/device_holder.h | 2 + impeller/renderer/backend/vulkan/formats_vk.h | 98 +--- .../backend/vulkan/pipeline_library_vk.cc | 489 +----------------- .../backend/vulkan/pipeline_library_vk.h | 11 +- .../renderer/backend/vulkan/pipeline_vk.cc | 453 +++++++++++++++- .../renderer/backend/vulkan/pipeline_vk.h | 28 +- .../backend/vulkan/render_pass_builder_vk.cc | 187 +++++++ .../backend/vulkan/render_pass_builder_vk.h | 71 +++ .../renderer/backend/vulkan/render_pass_vk.cc | 227 +++----- .../renderer/backend/vulkan/render_pass_vk.h | 5 +- .../backend/vulkan/swapchain_impl_vk.cc | 34 +- impeller/renderer/pipeline.h | 7 +- impeller/renderer/pipeline_descriptor.cc | 4 +- impeller/renderer/pipeline_descriptor.h | 17 - impeller/renderer/renderer_unittests.cc | 221 ++++++++ impeller/renderer/vertex_descriptor.cc | 6 + impeller/renderer/vertex_descriptor.h | 3 + 36 files changed, 1195 insertions(+), 914 deletions(-) create mode 100644 impeller/fixtures/sepia.frag create mode 100644 impeller/fixtures/sepia.vert create mode 100644 impeller/fixtures/swizzle.frag create mode 100644 impeller/fixtures/texture.frag create mode 100644 impeller/fixtures/texture.vert create mode 100644 impeller/renderer/backend/vulkan/render_pass_builder_vk.cc create mode 100644 impeller/renderer/backend/vulkan/render_pass_builder_vk.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 30d8aad50fc43..2746f905be0f6 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5478,6 +5478,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc + ../.. ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/queue_vk.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/queue_vk.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_vk.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/resource_manager_vk.cc + ../../../flutter/LICENSE @@ -8334,6 +8336,8 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/queue_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/queue_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_builder_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/render_pass_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/resource_manager_vk.cc diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 77c915f955d03..4aaa978353759 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -240,64 +240,49 @@ ContentContext::ContentContext( if (context_->GetCapabilities()->SupportsFramebufferFetch()) { framebuffer_blend_color_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kColor), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kColor), supports_decal}); framebuffer_blend_colorburn_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kColorBurn), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kColorBurn), supports_decal}); framebuffer_blend_colordodge_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kColorDodge), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kColorDodge), supports_decal}); framebuffer_blend_darken_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kDarken), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kDarken), supports_decal}); framebuffer_blend_difference_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kDifference), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kDifference), supports_decal}); framebuffer_blend_exclusion_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kExclusion), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kExclusion), supports_decal}); framebuffer_blend_hardlight_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kHardLight), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kHardLight), supports_decal}); framebuffer_blend_hue_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kHue), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kHue), supports_decal}); framebuffer_blend_lighten_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kLighten), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kLighten), supports_decal}); framebuffer_blend_luminosity_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kLuminosity), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kLuminosity), supports_decal}); framebuffer_blend_multiply_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kMultiply), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kMultiply), supports_decal}); framebuffer_blend_overlay_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kOverlay), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kOverlay), supports_decal}); framebuffer_blend_saturation_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kSaturation), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kSaturation), supports_decal}); framebuffer_blend_screen_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kScreen), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kScreen), supports_decal}); framebuffer_blend_softlight_pipelines_.CreateDefault( *context_, options_trianglestrip, - {static_cast(BlendSelectValues::kSoftLight), supports_decal}, - UseSubpassInput::kYes); + {static_cast(BlendSelectValues::kSoftLight), supports_decal}); } blend_color_pipelines_.CreateDefault( diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index ca3a9ccccf20c..370d7b3d1cddf 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -826,15 +826,13 @@ class ContentContext { void CreateDefault(const Context& context, const ContentContextOptions& options, - const std::initializer_list& constants = {}, - UseSubpassInput subpass_input = UseSubpassInput::kNo) { + const std::initializer_list& constants = {}) { auto desc = PipelineT::Builder::MakeDefaultPipelineDescriptor(context, constants); if (!desc.has_value()) { VALIDATION_LOG << "Failed to create default pipeline."; return; } - desc->SetUseSubpassInput(subpass_input); options.ApplyToPipelineDescriptor(*desc); SetDefault(options, std::make_unique(context, desc)); } diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 17d83085de404..6841439095d06 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2689,39 +2689,6 @@ TEST_P(EntityTest, DecalSpecializationAppliedToMorphologyFilter) { expected_constants); } -TEST_P(EntityTest, FramebufferFetchPipelinesDeclareUsage) { - auto content_context = - ContentContext(GetContext(), TypographerContextSkia::Make()); - if (!content_context.GetDeviceCapabilities().SupportsFramebufferFetch()) { - GTEST_SKIP() << "Framebuffer fetch not supported."; - } - - ContentContextOptions options; - options.color_attachment_pixel_format = PixelFormat::kR8G8B8A8UNormInt; - auto color_burn = - content_context.GetFramebufferBlendColorBurnPipeline(options); - - EXPECT_TRUE(color_burn->GetDescriptor().UsesSubpassInput()); -} - -TEST_P(EntityTest, PipelineDescriptorEqAndHash) { - auto desc_1 = std::make_shared(); - auto desc_2 = std::make_shared(); - - EXPECT_TRUE(desc_1->IsEqual(*desc_2)); - EXPECT_EQ(desc_1->GetHash(), desc_2->GetHash()); - - desc_1->SetUseSubpassInput(UseSubpassInput::kYes); - - EXPECT_FALSE(desc_1->IsEqual(*desc_2)); - EXPECT_NE(desc_1->GetHash(), desc_2->GetHash()); - - desc_2->SetUseSubpassInput(UseSubpassInput::kYes); - - EXPECT_TRUE(desc_1->IsEqual(*desc_2)); - EXPECT_EQ(desc_1->GetHash(), desc_2->GetHash()); -} - // This doesn't really tell you if the hashes will have frequent // collisions, but since this type is only used to hash a bounded // set of options, we can just compare benchmarks. diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 16081a9ba4470..ee299ee2660eb 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -8,6 +8,9 @@ import("//flutter/testing/testing.gni") impeller_shaders("shader_fixtures") { name = "fixtures" + # 2.3 adds support for framebuffer fetch in Metal. + metal_version = "2.3" + # Not analyzing because they are not performance critical, and mipmap uses # textureLod, which uses an extension that malioc does not support. analyze = false @@ -16,8 +19,9 @@ impeller_shaders("shader_fixtures") { "array.vert", "box_fade.frag", "box_fade.vert", - "colors.vert", "colors.frag", + "colors.vert", + "half.frag", "impeller.frag", "impeller.vert", "inactive_uniforms.frag", @@ -27,12 +31,16 @@ impeller_shaders("shader_fixtures") { "mipmaps.frag", "mipmaps.vert", "sample.comp", + "sepia.frag", + "sepia.vert", + "simple.vert", "stage1.comp", "stage2.comp", - "simple.vert", + "swizzle.frag", "test_texture.frag", "test_texture.vert", - "half.frag", + "texture.frag", + "texture.vert", ] if (impeller_enable_opengles) { diff --git a/impeller/fixtures/sepia.frag b/impeller/fixtures/sepia.frag new file mode 100644 index 0000000000000..1379a5f4f533b --- /dev/null +++ b/impeller/fixtures/sepia.frag @@ -0,0 +1,17 @@ +// 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. + +out vec4 frag_color; + +layout(input_attachment_index = 0) uniform subpassInputMS subpass_input; + +void main() { + // https://github.com/chinmaygarde/merle/blob/3eecb311ac8862c41f0c53a5d9b360be923142bb/src/texture.cc#L195 + const mat4 sepia_matrix = mat4(0.3588, 0.2990, 0.2392, 0.0000, // + 0.7044, 0.5870, 0.4696, 0.0000, // + 0.1368, 0.1140, 0.0912, 0.0000, // + 0.0000, 0.0000, 0.0000, 1.0000 // + ); + frag_color = sepia_matrix * subpassLoad(subpass_input, 0); +} diff --git a/impeller/fixtures/sepia.vert b/impeller/fixtures/sepia.vert new file mode 100644 index 0000000000000..b8b09eeaae0e8 --- /dev/null +++ b/impeller/fixtures/sepia.vert @@ -0,0 +1,14 @@ +// 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. + +uniform UniformBuffer { + mat4 mvp; +} +uniform_buffer; + +in vec3 vertex_position; + +void main() { + gl_Position = uniform_buffer.mvp * vec4(vertex_position, 1.0); +} diff --git a/impeller/fixtures/swizzle.frag b/impeller/fixtures/swizzle.frag new file mode 100644 index 0000000000000..63d793e3ead99 --- /dev/null +++ b/impeller/fixtures/swizzle.frag @@ -0,0 +1,11 @@ +// 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. + +out vec4 frag_color; + +layout(input_attachment_index = 0) uniform subpassInputMS subpass_input; + +void main() { + frag_color = subpassLoad(subpass_input, 0).gbra; +} diff --git a/impeller/fixtures/texture.frag b/impeller/fixtures/texture.frag new file mode 100644 index 0000000000000..0b7091261bc6a --- /dev/null +++ b/impeller/fixtures/texture.frag @@ -0,0 +1,13 @@ +// 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. + +in vec2 interporlated_texture_coordinates; + +out vec4 frag_color; + +uniform sampler2D texture_contents; + +void main() { + frag_color = texture(texture_contents, interporlated_texture_coordinates); +} diff --git a/impeller/fixtures/texture.vert b/impeller/fixtures/texture.vert new file mode 100644 index 0000000000000..fd65238d83de7 --- /dev/null +++ b/impeller/fixtures/texture.vert @@ -0,0 +1,18 @@ +// 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. + +uniform UniformBuffer { + mat4 mvp; +} +uniform_buffer; + +in vec3 vertex_position; +in vec2 texture_coordinates; + +out vec2 interpolated_texture_coordinates; + +void main() { + gl_Position = uniform_buffer.mvp * vec4(vertex_position, 1.0); + interpolated_texture_coordinates = texture_coordinates; +} diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index 78a356cb49e82..f759ea269b24e 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -75,6 +75,8 @@ impeller_component("vulkan") { "pipeline_vk.h", "queue_vk.cc", "queue_vk.h", + "render_pass_builder_vk.cc", + "render_pass_builder_vk.h", "render_pass_vk.cc", "render_pass_vk.h", "resource_manager_vk.cc", diff --git a/impeller/renderer/backend/vulkan/allocator_vk.cc b/impeller/renderer/backend/vulkan/allocator_vk.cc index 3f5a046c6a39b..2f90cd768dc4e 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk.cc +++ b/impeller/renderer/backend/vulkan/allocator_vk.cc @@ -147,7 +147,6 @@ AllocatorVK::AllocatorVK(std::weak_ptr context, allocator_.reset(allocator); supports_memoryless_textures_ = capabilities.SupportsDeviceTransientTextures(); - supports_framebuffer_fetch_ = capabilities.SupportsFramebufferFetch(); is_valid_ = true; } @@ -167,8 +166,7 @@ static constexpr vk::ImageUsageFlags ToVKImageUsageFlags( PixelFormat format, TextureUsageMask usage, StorageMode mode, - bool supports_memoryless_textures, - bool supports_framebuffer_fetch) { + bool supports_memoryless_textures) { vk::ImageUsageFlags vk_usage; switch (mode) { @@ -188,9 +186,7 @@ static constexpr vk::ImageUsageFlags ToVKImageUsageFlags( } else { vk_usage |= vk::ImageUsageFlagBits::eColorAttachment; } - if (supports_framebuffer_fetch) { - vk_usage |= vk::ImageUsageFlagBits::eInputAttachment; - } + vk_usage |= vk::ImageUsageFlagBits::eInputAttachment; } if (usage & static_cast(TextureUsage::kShaderRead)) { @@ -267,8 +263,7 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { const TextureDescriptor& desc, VmaAllocator allocator, vk::Device device, - bool supports_memoryless_textures, - bool supports_framebuffer_fetch) + bool supports_memoryless_textures) : TextureSourceVK(desc), resource_(std::move(resource_manager)) { FML_DCHECK(desc.format != PixelFormat::kUnknown); vk::ImageCreateInfo image_info; @@ -285,9 +280,9 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { image_info.arrayLayers = ToArrayLayerCount(desc.type); image_info.tiling = vk::ImageTiling::eOptimal; image_info.initialLayout = vk::ImageLayout::eUndefined; - image_info.usage = ToVKImageUsageFlags( - desc.format, desc.usage, desc.storage_mode, - supports_memoryless_textures, supports_framebuffer_fetch); + image_info.usage = + ToVKImageUsageFlags(desc.format, desc.usage, desc.storage_mode, + supports_memoryless_textures); image_info.sharingMode = vk::SharingMode::eExclusive; VmaAllocationCreateInfo alloc_nfo = {}; @@ -433,8 +428,7 @@ std::shared_ptr AllocatorVK::OnCreateTexture( desc, // allocator_.get(), // device_holder->GetDevice(), // - supports_memoryless_textures_, // - supports_framebuffer_fetch_ // + supports_memoryless_textures_ // ); if (!source->IsValid()) { return nullptr; diff --git a/impeller/renderer/backend/vulkan/allocator_vk.h b/impeller/renderer/backend/vulkan/allocator_vk.h index c1489abe7c8a6..caaedfc520966 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk.h +++ b/impeller/renderer/backend/vulkan/allocator_vk.h @@ -34,7 +34,6 @@ class AllocatorVK final : public Allocator { ISize max_texture_size_; bool is_valid_ = false; bool supports_memoryless_textures_ = false; - bool supports_framebuffer_fetch_ = false; // TODO(jonahwilliams): figure out why CI can't create these buffer pools. bool created_buffer_pool_ = true; diff --git a/impeller/renderer/backend/vulkan/barrier_vk.h b/impeller/renderer/backend/vulkan/barrier_vk.h index b4bc2257899b1..bded9c3d5dc5d 100644 --- a/impeller/renderer/backend/vulkan/barrier_vk.h +++ b/impeller/renderer/backend/vulkan/barrier_vk.h @@ -18,6 +18,13 @@ namespace impeller { /// and the Vulkan spec. The docs for the various member of this /// class are based on verbiage in the spec. /// +/// A useful mnemonic for building a mental model of how to add +/// these barriers is to build a sentence like so; "All commands +/// before this barrier may continue till they encounter a in the . And, all commands after +/// this barrier may proceed till in the ." +/// struct BarrierVK { vk::CommandBuffer cmd_buffer = {}; vk::ImageLayout new_layout = vk::ImageLayout::eUndefined; diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc index 4d83152795af7..08d07918c42ac 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.cc +++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc @@ -157,10 +157,6 @@ static const char* GetDeviceExtensionName(OptionalDeviceExtensionVK ext) { switch (ext) { case OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback: return VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME; - case OptionalDeviceExtensionVK::kARMRasterizationOrderAttachmentAccess: - return VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME; - case OptionalDeviceExtensionVK::kEXTRasterizationOrderAttachmentAccess: - return VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME; case OptionalDeviceExtensionVK::kLast: return "Unknown"; } @@ -406,18 +402,6 @@ bool CapabilitiesVK::SetPhysicalDevice(const vk::PhysicalDevice& device) { }); } - { - supports_framebuffer_fetch_ = - (optional_device_extensions_.find( - OptionalDeviceExtensionVK:: - kARMRasterizationOrderAttachmentAccess) != - optional_device_extensions_.end() || - optional_device_extensions_.find( - OptionalDeviceExtensionVK:: - kEXTRasterizationOrderAttachmentAccess) != - optional_device_extensions_.end()); - } - return true; } @@ -448,7 +432,7 @@ bool CapabilitiesVK::SupportsTextureToTextureBlits() const { // |Capabilities| bool CapabilitiesVK::SupportsFramebufferFetch() const { - return supports_framebuffer_fetch_; + return true; } // |Capabilities| diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h index 4cb7ce8996600..b2dad43260b99 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.h +++ b/impeller/renderer/backend/vulkan/capabilities_vk.h @@ -22,8 +22,6 @@ class ContextVK; enum class OptionalDeviceExtensionVK : uint32_t { // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_pipeline_creation_feedback.html kEXTPipelineCreationFeedback, - kARMRasterizationOrderAttachmentAccess, - kEXTRasterizationOrderAttachmentAccess, kLast, }; @@ -112,7 +110,6 @@ class CapabilitiesVK final : public Capabilities, vk::PhysicalDeviceProperties device_properties_; bool supports_compute_subgroups_ = false; bool supports_device_transient_textures_ = false; - bool supports_framebuffer_fetch_ = false; bool is_valid_ = false; bool HasExtension(const std::string& ext) const; diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index 1a625f357a609..b6cf44dc66bfa 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -566,6 +566,11 @@ std::shared_ptr ContextVK::GetGPUTracer() const { return gpu_tracer_; } +std::shared_ptr ContextVK::GetDescriptorPoolRecycler() + const { + return descriptor_pool_recycler_; +} + std::shared_ptr ContextVK::GetCommandQueue() const { return command_queue_vk_; } diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h index 07884f1f1ef35..a0e058f2d8eee 100644 --- a/impeller/renderer/backend/vulkan/context_vk.h +++ b/impeller/renderer/backend/vulkan/context_vk.h @@ -161,9 +161,7 @@ class ContextVK final : public Context, std::shared_ptr GetCommandPoolRecycler() const; - std::shared_ptr GetDescriptorPoolRecycler() const { - return descriptor_pool_recycler_; - } + std::shared_ptr GetDescriptorPoolRecycler() const; std::shared_ptr GetCommandQueue() const override; diff --git a/impeller/renderer/backend/vulkan/debug_report_vk.cc b/impeller/renderer/backend/vulkan/debug_report_vk.cc index 39068e056e0ec..c18c97087d622 100644 --- a/impeller/renderer/backend/vulkan/debug_report_vk.cc +++ b/impeller/renderer/backend/vulkan/debug_report_vk.cc @@ -103,10 +103,19 @@ DebugReportVK::Result DebugReportVK::OnDebugCallback( vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* data) { + // This is a real issue caused by INPUT_ATTACHMENT_BIT not being a supported + // `VkSurfaceCapabilitiesKHR::supportedUsageFlags` on any platform other than + // Android. This is necessary for all the framebuffer fetch related tests. We + // can get away with suppressing this on macOS but this must be fixed. + if (data->messageIdNumber == 0x2c36905d) { + return Result::kContinue; + } + // Issue in older versions of the SDK. // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/3554 - if (strstr(data->pMessageIdName, "CoreValidation-Shader-OutputNotConsumed") != - nullptr) { + if (data->pMessageIdName != nullptr && + strstr(data->pMessageIdName, "CoreValidation-Shader-OutputNotConsumed") != + nullptr) { return Result::kContinue; } diff --git a/impeller/renderer/backend/vulkan/device_holder.h b/impeller/renderer/backend/vulkan/device_holder.h index bd26e09e2029b..09bd14185c544 100644 --- a/impeller/renderer/backend/vulkan/device_holder.h +++ b/impeller/renderer/backend/vulkan/device_holder.h @@ -12,7 +12,9 @@ namespace impeller { class DeviceHolder { public: virtual ~DeviceHolder() = default; + virtual const vk::Device& GetDevice() const = 0; + virtual const vk::PhysicalDevice& GetPhysicalDevice() const = 0; }; diff --git a/impeller/renderer/backend/vulkan/formats_vk.h b/impeller/renderer/backend/vulkan/formats_vk.h index 9e39a5906f38f..d73705f90f0b2 100644 --- a/impeller/renderer/backend/vulkan/formats_vk.h +++ b/impeller/renderer/backend/vulkan/formats_vk.h @@ -6,7 +6,9 @@ #define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_FORMATS_VK_H_ #include +#include +#include "flutter/fml/hash_combine.h" #include "flutter/fml/macros.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" @@ -393,102 +395,6 @@ constexpr bool PixelFormatIsDepthStencil(PixelFormat format) { return false; } -enum class AttachmentKind { - kColor, - kDepth, - kStencil, - kDepthStencil, -}; - -constexpr AttachmentKind AttachmentKindFromFormat(PixelFormat format) { - switch (format) { - case PixelFormat::kUnknown: - case PixelFormat::kA8UNormInt: - case PixelFormat::kR8UNormInt: - case PixelFormat::kR8G8UNormInt: - case PixelFormat::kR8G8B8A8UNormInt: - case PixelFormat::kR8G8B8A8UNormIntSRGB: - case PixelFormat::kB8G8R8A8UNormInt: - case PixelFormat::kB8G8R8A8UNormIntSRGB: - case PixelFormat::kR32G32B32A32Float: - case PixelFormat::kR16G16B16A16Float: - case PixelFormat::kB10G10R10XR: - case PixelFormat::kB10G10R10XRSRGB: - case PixelFormat::kB10G10R10A10XR: - return AttachmentKind::kColor; - case PixelFormat::kS8UInt: - return AttachmentKind::kStencil; - case PixelFormat::kD24UnormS8Uint: - case PixelFormat::kD32FloatS8UInt: - return AttachmentKind::kDepthStencil; - } - FML_UNREACHABLE(); -} - -constexpr vk::AttachmentDescription CreateAttachmentDescription( - PixelFormat format, - SampleCount sample_count, - LoadAction load_action, - StoreAction store_action, - vk::ImageLayout current_layout, - bool supports_framebuffer_fetch) { - vk::AttachmentDescription vk_attachment; - - vk_attachment.format = ToVKImageFormat(format); - vk_attachment.samples = ToVKSampleCount(sample_count); - - // The Vulkan spec has somewhat complicated rules for when these ops are used - // and ignored. Just set safe defaults. - vk_attachment.loadOp = vk::AttachmentLoadOp::eDontCare; - vk_attachment.storeOp = vk::AttachmentStoreOp::eDontCare; - vk_attachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - vk_attachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - - const auto kind = AttachmentKindFromFormat(format); - - switch (kind) { - case AttachmentKind::kColor: - // If the attachment uses a color format, then loadOp and storeOp are - // used, and stencilLoadOp and stencilStoreOp are ignored. - vk_attachment.loadOp = ToVKAttachmentLoadOp(load_action); - vk_attachment.storeOp = ToVKAttachmentStoreOp(store_action); - break; - case AttachmentKind::kDepth: - case AttachmentKind::kDepthStencil: - // If the format has depth and/or stencil components, loadOp and storeOp - // apply only to the depth data, while stencilLoadOp and stencilStoreOp - // define how the stencil data is handled. - vk_attachment.loadOp = ToVKAttachmentLoadOp(load_action); - vk_attachment.storeOp = ToVKAttachmentStoreOp(store_action); - [[fallthrough]]; - case AttachmentKind::kStencil: - vk_attachment.stencilLoadOp = ToVKAttachmentLoadOp(load_action); - vk_attachment.stencilStoreOp = ToVKAttachmentStoreOp(store_action); - break; - } - - switch (kind) { - case AttachmentKind::kColor: - vk_attachment.initialLayout = current_layout; - if (supports_framebuffer_fetch) { - vk_attachment.finalLayout = vk::ImageLayout::eGeneral; - } else { - vk_attachment.finalLayout = vk::ImageLayout::eColorAttachmentOptimal; - } - break; - case AttachmentKind::kDepth: - case AttachmentKind::kStencil: - case AttachmentKind::kDepthStencil: - // Separate depth stencil layouts feature is only available in Vulkan 1.2. - vk_attachment.initialLayout = current_layout; - vk_attachment.finalLayout = - vk::ImageLayout::eDepthStencilAttachmentOptimal; - break; - } - - return vk_attachment; -} - static constexpr vk::AttachmentReference kUnusedAttachmentReference = { VK_ATTACHMENT_UNUSED, vk::ImageLayout::eUndefined}; diff --git a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index 5149158a6904a..600ef830450d5 100644 --- a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc @@ -30,7 +30,6 @@ PipelineLibraryVK::PipelineLibraryVK( fml::UniqueFD cache_directory, std::shared_ptr worker_task_runner) : device_holder_(device_holder), - supports_framebuffer_fetch_(caps->SupportsFramebufferFetch()), pso_cache_(std::make_shared(std::move(caps), device_holder, std::move(cache_directory))), @@ -50,472 +49,6 @@ bool PipelineLibraryVK::IsValid() const { return is_valid_; } -//------------------------------------------------------------------------------ -/// @brief Creates an attachment description that does just enough to -/// ensure render pass compatibility with the pass associated later -/// with the framebuffer. -/// -/// See -/// https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility -/// -static vk::AttachmentDescription CreatePlaceholderAttachmentDescription( - PixelFormat format, - SampleCount sample_count) { - // Load store ops are immaterial for pass compatibility. The right ops will be - // picked up when the pass associated with framebuffer. - return CreateAttachmentDescription(format, // - sample_count, // - LoadAction::kDontCare, // - StoreAction::kDontCare, // - vk::ImageLayout::eUndefined, // - false // - ); -} - -//---------------------------------------------------------------------------- -/// Render Pass -/// We are NOT going to use the same render pass with the framebuffer (later) -/// and the graphics pipeline (here). Instead, we are going to ensure that the -/// sub-passes are compatible. To see the compatibility rules, see the Vulkan -/// spec: -/// https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility -/// -static vk::UniqueRenderPass CreateCompatRenderPassForPipeline( - const vk::Device& device, - const PipelineDescriptor& desc, - bool supports_framebuffer_fetch) { - std::vector attachments; - - std::vector color_refs; - std::vector subpass_color_ref; - vk::AttachmentReference depth_stencil_ref = kUnusedAttachmentReference; - - color_refs.resize(desc.GetMaxColorAttacmentBindIndex() + 1, - kUnusedAttachmentReference); - - const auto sample_count = desc.GetSampleCount(); - - for (const auto& [bind_point, color] : desc.GetColorAttachmentDescriptors()) { - color_refs[bind_point] = - vk::AttachmentReference{static_cast(attachments.size()), - vk::ImageLayout::eColorAttachmentOptimal}; - attachments.emplace_back( - CreatePlaceholderAttachmentDescription(color.format, sample_count)); - } - subpass_color_ref.push_back(vk::AttachmentReference{ - static_cast(0), vk::ImageLayout::eColorAttachmentOptimal}); - - if (auto depth = desc.GetDepthStencilAttachmentDescriptor(); - depth.has_value()) { - depth_stencil_ref = vk::AttachmentReference{ - static_cast(attachments.size()), - vk::ImageLayout::eDepthStencilAttachmentOptimal}; - attachments.emplace_back(CreatePlaceholderAttachmentDescription( - desc.GetDepthPixelFormat(), sample_count)); - } - if (desc.HasStencilAttachmentDescriptors()) { - depth_stencil_ref = vk::AttachmentReference{ - static_cast(attachments.size()), - vk::ImageLayout::eDepthStencilAttachmentOptimal}; - attachments.emplace_back(CreatePlaceholderAttachmentDescription( - desc.GetStencilPixelFormat(), sample_count)); - } - - vk::SubpassDescription subpass_desc; - subpass_desc.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; - - // If the device supports framebuffer fetch, compatibility pipelines are - // always created with the self reference and rasterization order flag. This - // ensures that all compiled pipelines are compatible with a render pass that - // contains a framebuffer fetch shader (advanced blends). - std::vector subpass_dependencies; - if (supports_framebuffer_fetch) { - subpass_desc.setFlags(vk::SubpassDescriptionFlagBits:: - eRasterizationOrderAttachmentColorAccessARM); - subpass_desc.setInputAttachments(subpass_color_ref); - } - subpass_desc.setColorAttachments(color_refs); - subpass_desc.setPDepthStencilAttachment(&depth_stencil_ref); - - vk::RenderPassCreateInfo render_pass_desc; - render_pass_desc.setAttachments(attachments); - render_pass_desc.setPSubpasses(&subpass_desc); - render_pass_desc.setSubpassCount(1u); - render_pass_desc.setDependencies(subpass_dependencies); - - auto [result, pass] = device.createRenderPassUnique(render_pass_desc); - if (result != vk::Result::eSuccess) { - VALIDATION_LOG << "Failed to create render pass for pipeline '" - << desc.GetLabel() << "'. Error: " << vk::to_string(result); - return {}; - } - - // This pass is not used with the render pass. It is only necessary to tell - // Vulkan the expected render pass layout. The actual pass will be created - // later during render pass setup and will need to be compatible with this - // one. - ContextVK::SetDebugName(device, pass.get(), - "Compat Render Pass: " + desc.GetLabel()); - - return std::move(pass); -} - -constexpr vk::FrontFace ToVKFrontFace(WindingOrder order) { - switch (order) { - case WindingOrder::kClockwise: - return vk::FrontFace::eClockwise; - case WindingOrder::kCounterClockwise: - return vk::FrontFace::eCounterClockwise; - } - FML_UNREACHABLE(); -} - -static vk::PipelineCreationFeedbackEXT EmptyFeedback() { - vk::PipelineCreationFeedbackEXT feedback; - // If the VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT is not set in flags, an - // implementation must not set any other bits in flags, and the values of all - // other VkPipelineCreationFeedback data members are undefined. - feedback.flags = vk::PipelineCreationFeedbackFlagBits::eValid; - return feedback; -} - -static void ReportPipelineCreationFeedbackToLog( - std::stringstream& stream, - const vk::PipelineCreationFeedbackEXT& feedback) { - const auto pipeline_cache_hit = - feedback.flags & - vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit; - const auto base_pipeline_accl = - feedback.flags & - vk::PipelineCreationFeedbackFlagBits::eBasePipelineAcceleration; - auto duration = std::chrono::duration_cast( - std::chrono::nanoseconds{feedback.duration}); - stream << "Time: " << duration.count() << "ms" - << " Cache Hit: " << static_cast(pipeline_cache_hit) - << " Base Accel: " << static_cast(base_pipeline_accl) - << " Thread: " << std::this_thread::get_id(); -} - -static void ReportPipelineCreationFeedbackToLog( - const PipelineDescriptor& desc, - const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { - std::stringstream stream; - stream << std::fixed << std::showpoint << std::setprecision(2); - stream << std::endl << ">>>>>>" << std::endl; - stream << "Pipeline '" << desc.GetLabel() << "' "; - ReportPipelineCreationFeedbackToLog(stream, - *feedback.pPipelineCreationFeedback); - if (feedback.pipelineStageCreationFeedbackCount != 0) { - stream << std::endl; - } - for (size_t i = 0, count = feedback.pipelineStageCreationFeedbackCount; - i < count; i++) { - stream << "\tStage " << i + 1 << ": "; - ReportPipelineCreationFeedbackToLog( - stream, feedback.pPipelineStageCreationFeedbacks[i]); - if (i != count - 1) { - stream << std::endl; - } - } - stream << std::endl << "<<<<<<" << std::endl; - FML_LOG(ERROR) << stream.str(); -} - -static void ReportPipelineCreationFeedbackToTrace( - const PipelineDescriptor& desc, - const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { - static int64_t gPipelineCacheHits = 0; - static int64_t gPipelineCacheMisses = 0; - static int64_t gPipelines = 0; - if (feedback.pPipelineCreationFeedback->flags & - vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit) { - gPipelineCacheHits++; - } else { - gPipelineCacheMisses++; - } - gPipelines++; - static constexpr int64_t kImpellerPipelineTraceID = 1988; - FML_TRACE_COUNTER("impeller", // - "PipelineCache", // series name - kImpellerPipelineTraceID, // series ID - "PipelineCacheHits", gPipelineCacheHits, // - "PipelineCacheMisses", gPipelineCacheMisses, // - "TotalPipelines", gPipelines // - ); -} - -static void ReportPipelineCreationFeedback( - const PipelineDescriptor& desc, - const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { - constexpr bool kReportPipelineCreationFeedbackToLogs = false; - constexpr bool kReportPipelineCreationFeedbackToTraces = true; - if (kReportPipelineCreationFeedbackToLogs) { - ReportPipelineCreationFeedbackToLog(desc, feedback); - } - if (kReportPipelineCreationFeedbackToTraces) { - ReportPipelineCreationFeedbackToTrace(desc, feedback); - } -} - -std::unique_ptr PipelineLibraryVK::CreatePipeline( - const PipelineDescriptor& desc) { - TRACE_EVENT0("flutter", __FUNCTION__); - vk::StructureChain - chain; - - const auto& supports_pipeline_creation_feedback = - pso_cache_->GetCapabilities()->HasOptionalDeviceExtension( - OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback); - if (!supports_pipeline_creation_feedback) { - chain.unlink(); - } - - auto& pipeline_info = chain.get(); - - //---------------------------------------------------------------------------- - /// Dynamic States - /// - vk::PipelineDynamicStateCreateInfo dynamic_create_state_info; - std::vector dynamic_states = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor, - vk::DynamicState::eStencilReference, - }; - dynamic_create_state_info.setDynamicStates(dynamic_states); - pipeline_info.setPDynamicState(&dynamic_create_state_info); - - //---------------------------------------------------------------------------- - /// Viewport State - /// - vk::PipelineViewportStateCreateInfo viewport_state; - viewport_state.setViewportCount(1u); - viewport_state.setScissorCount(1u); - // The actual viewport and scissor rects are not set here since they are - // dynamic as mentioned above in the dynamic state info. - pipeline_info.setPViewportState(&viewport_state); - - //---------------------------------------------------------------------------- - /// Shader Stages - /// - const auto& constants = desc.GetSpecializationConstants(); - - std::vector> map_entries( - desc.GetStageEntrypoints().size()); - std::vector specialization_infos( - desc.GetStageEntrypoints().size()); - std::vector shader_stages; - - size_t entrypoint_count = 0; - for (const auto& entrypoint : desc.GetStageEntrypoints()) { - auto stage = ToVKShaderStageFlagBits(entrypoint.first); - if (!stage.has_value()) { - VALIDATION_LOG << "Unsupported shader type in pipeline: " - << desc.GetLabel(); - return nullptr; - } - - std::vector& entries = - map_entries[entrypoint_count]; - for (auto i = 0u; i < constants.size(); i++) { - vk::SpecializationMapEntry entry; - entry.offset = (i * sizeof(Scalar)); - entry.size = sizeof(Scalar); - entry.constantID = i; - entries.emplace_back(entry); - } - - vk::SpecializationInfo& specialization_info = - specialization_infos[entrypoint_count]; - specialization_info.setMapEntries(map_entries[entrypoint_count]); - specialization_info.setPData(constants.data()); - specialization_info.setDataSize(sizeof(Scalar) * constants.size()); - - vk::PipelineShaderStageCreateInfo info; - info.setStage(stage.value()); - info.setPName("main"); - info.setModule( - ShaderFunctionVK::Cast(entrypoint.second.get())->GetModule()); - info.setPSpecializationInfo(&specialization_info); - shader_stages.push_back(info); - entrypoint_count++; - } - pipeline_info.setStages(shader_stages); - - //---------------------------------------------------------------------------- - /// Rasterization State - /// - vk::PipelineRasterizationStateCreateInfo rasterization_state; - rasterization_state.setFrontFace(ToVKFrontFace(desc.GetWindingOrder())); - rasterization_state.setCullMode(ToVKCullModeFlags(desc.GetCullMode())); - rasterization_state.setPolygonMode(ToVKPolygonMode(desc.GetPolygonMode())); - rasterization_state.setLineWidth(1.0f); - rasterization_state.setDepthClampEnable(false); - rasterization_state.setRasterizerDiscardEnable(false); - pipeline_info.setPRasterizationState(&rasterization_state); - - //---------------------------------------------------------------------------- - /// Multi-sample State - /// - vk::PipelineMultisampleStateCreateInfo multisample_state; - multisample_state.setRasterizationSamples( - ToVKSampleCountFlagBits(desc.GetSampleCount())); - pipeline_info.setPMultisampleState(&multisample_state); - - //---------------------------------------------------------------------------- - /// Primitive Input Assembly State - vk::PipelineInputAssemblyStateCreateInfo input_assembly; - const auto topology = ToVKPrimitiveTopology(desc.GetPrimitiveType()); - input_assembly.setTopology(topology); - pipeline_info.setPInputAssemblyState(&input_assembly); - - //---------------------------------------------------------------------------- - /// Color Blend State - std::vector attachment_blend_state; - for (const auto& color_desc : desc.GetColorAttachmentDescriptors()) { - // TODO(csg): The blend states are per color attachment. But it isn't clear - // how the color attachment indices are specified in the pipeline create - // info. But, this should always work for one color attachment. - attachment_blend_state.push_back( - ToVKPipelineColorBlendAttachmentState(color_desc.second)); - } - vk::PipelineColorBlendStateCreateInfo blend_state; - blend_state.setAttachments(attachment_blend_state); - pipeline_info.setPColorBlendState(&blend_state); - - std::shared_ptr strong_device = device_holder_.lock(); - if (!strong_device) { - return nullptr; - } - - auto render_pass = CreateCompatRenderPassForPipeline( - strong_device->GetDevice(), desc, supports_framebuffer_fetch_); - if (render_pass) { - pipeline_info.setBasePipelineHandle(VK_NULL_HANDLE); - pipeline_info.setSubpass(0); - pipeline_info.setRenderPass(render_pass.get()); - } else { - return nullptr; - } - - //---------------------------------------------------------------------------- - /// Vertex Input Setup - /// - std::vector attr_descs; - std::vector buffer_descs; - - const auto& stage_inputs = desc.GetVertexDescriptor()->GetStageInputs(); - const auto& stage_buffer_layouts = - desc.GetVertexDescriptor()->GetStageLayouts(); - for (const ShaderStageIOSlot& stage_in : stage_inputs) { - vk::VertexInputAttributeDescription attr_desc; - attr_desc.setBinding(stage_in.binding); - attr_desc.setLocation(stage_in.location); - attr_desc.setFormat(ToVertexDescriptorFormat(stage_in)); - attr_desc.setOffset(stage_in.offset); - attr_descs.push_back(attr_desc); - } - for (const ShaderStageBufferLayout& layout : stage_buffer_layouts) { - vk::VertexInputBindingDescription binding_description; - binding_description.setBinding(layout.binding); - binding_description.setInputRate(vk::VertexInputRate::eVertex); - binding_description.setStride(layout.stride); - buffer_descs.push_back(binding_description); - } - - vk::PipelineVertexInputStateCreateInfo vertex_input_state; - vertex_input_state.setVertexAttributeDescriptions(attr_descs); - vertex_input_state.setVertexBindingDescriptions(buffer_descs); - - pipeline_info.setPVertexInputState(&vertex_input_state); - - //---------------------------------------------------------------------------- - /// Pipeline Layout a.k.a the descriptor sets and uniforms. - /// - std::vector desc_bindings; - - for (auto layout : desc.GetVertexDescriptor()->GetDescriptorSetLayouts()) { - auto vk_desc_layout = ToVKDescriptorSetLayoutBinding(layout); - desc_bindings.push_back(vk_desc_layout); - } - - vk::DescriptorSetLayoutCreateInfo descs_layout_info; - descs_layout_info.setBindings(desc_bindings); - - auto [descs_result, descs_layout] = - strong_device->GetDevice().createDescriptorSetLayoutUnique( - descs_layout_info); - if (descs_result != vk::Result::eSuccess) { - VALIDATION_LOG << "unable to create uniform descriptors"; - return nullptr; - } - - ContextVK::SetDebugName(strong_device->GetDevice(), descs_layout.get(), - "Descriptor Set Layout " + desc.GetLabel()); - - //---------------------------------------------------------------------------- - /// Create the pipeline layout. - /// - vk::PipelineLayoutCreateInfo pipeline_layout_info; - pipeline_layout_info.setSetLayouts(descs_layout.get()); - auto pipeline_layout = strong_device->GetDevice().createPipelineLayoutUnique( - pipeline_layout_info); - if (pipeline_layout.result != vk::Result::eSuccess) { - VALIDATION_LOG << "Could not create pipeline layout for pipeline " - << desc.GetLabel() << ": " - << vk::to_string(pipeline_layout.result); - return nullptr; - } - pipeline_info.setLayout(pipeline_layout.value.get()); - - //---------------------------------------------------------------------------- - /// Create the depth stencil state. - /// - auto depth_stencil_state = ToVKPipelineDepthStencilStateCreateInfo( - desc.GetDepthStencilAttachmentDescriptor(), - desc.GetFrontStencilAttachmentDescriptor(), - desc.GetBackStencilAttachmentDescriptor()); - pipeline_info.setPDepthStencilState(&depth_stencil_state); - - //---------------------------------------------------------------------------- - /// Setup the optional pipeline creation feedback struct so we can understand - /// how Vulkan created the PSO. - /// - auto& feedback = chain.get(); - auto pipeline_feedback = EmptyFeedback(); - std::vector stage_feedbacks( - pipeline_info.stageCount, EmptyFeedback()); - feedback.setPPipelineCreationFeedback(&pipeline_feedback); - feedback.setPipelineStageCreationFeedbacks(stage_feedbacks); - - //---------------------------------------------------------------------------- - /// Finally, all done with the setup info. Create the pipeline itself. - /// - auto pipeline = pso_cache_->CreatePipeline(pipeline_info); - if (!pipeline) { - VALIDATION_LOG << "Could not create graphics pipeline: " << desc.GetLabel(); - return nullptr; - } - - if (supports_pipeline_creation_feedback) { - ReportPipelineCreationFeedback(desc, feedback); - } - - ContextVK::SetDebugName(strong_device->GetDevice(), *pipeline_layout.value, - "Pipeline Layout " + desc.GetLabel()); - ContextVK::SetDebugName(strong_device->GetDevice(), *pipeline, - "Pipeline " + desc.GetLabel()); - - return std::make_unique(device_holder_, - weak_from_this(), // - desc, // - std::move(pipeline), // - std::move(render_pass), // - std::move(pipeline_layout.value), // - std::move(descs_layout) // - ); -} - std::unique_ptr PipelineLibraryVK::CreateComputePipeline( const ComputePipelineDescriptor& desc) { TRACE_EVENT0("flutter", __FUNCTION__); @@ -653,14 +186,11 @@ PipelineFuture PipelineLibraryVK::GetPipeline( return; } - auto pipeline = PipelineLibraryVK::Cast(*thiz).CreatePipeline(descriptor); - if (!pipeline) { - promise->set_value(nullptr); - VALIDATION_LOG << "Could not create pipeline: " << descriptor.GetLabel(); - return; - } - - promise->set_value(std::move(pipeline)); + promise->set_value(PipelineVK::Create( + descriptor, // + PipelineLibraryVK::Cast(*thiz).device_holder_.lock(), // + weak_this // + )); }); return pipeline_future; @@ -741,4 +271,13 @@ void PipelineLibraryVK::PersistPipelineCacheToDisk() { }); } +const std::shared_ptr& PipelineLibraryVK::GetPSOCache() const { + return pso_cache_; +} + +const std::shared_ptr& +PipelineLibraryVK::GetWorkerTaskRunner() const { + return worker_task_runner_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/pipeline_library_vk.h b/impeller/renderer/backend/vulkan/pipeline_library_vk.h index 7866b4909d6a0..d683a9c9863c2 100644 --- a/impeller/renderer/backend/vulkan/pipeline_library_vk.h +++ b/impeller/renderer/backend/vulkan/pipeline_library_vk.h @@ -32,18 +32,21 @@ class PipelineLibraryVK final void DidAcquireSurfaceFrame(); + const std::shared_ptr& GetPSOCache() const; + + const std::shared_ptr& GetWorkerTaskRunner() const; + private: friend ContextVK; std::weak_ptr device_holder_; - bool supports_framebuffer_fetch_ = false; std::shared_ptr pso_cache_; std::shared_ptr worker_task_runner_; Mutex pipelines_mutex_; PipelineMap pipelines_ IPLR_GUARDED_BY(pipelines_mutex_); Mutex compute_pipelines_mutex_; - ComputePipelineMap compute_pipelines_ - IPLR_GUARDED_BY(compute_pipelines_mutex_); + ComputePipelineMap compute_pipelines_ IPLR_GUARDED_BY( + compute_pipelines_mutex_); std::atomic_size_t frames_acquired_ = 0u; bool is_valid_ = false; @@ -68,8 +71,6 @@ class PipelineLibraryVK final void RemovePipelinesWithEntryPoint( std::shared_ptr function) override; - std::unique_ptr CreatePipeline(const PipelineDescriptor& desc); - std::unique_ptr CreateComputePipeline( const ComputePipelineDescriptor& desc); diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.cc b/impeller/renderer/backend/vulkan/pipeline_vk.cc index c1ca1e3084765..497fa2c3032cc 100644 --- a/impeller/renderer/backend/vulkan/pipeline_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_vk.cc @@ -4,8 +4,447 @@ #include "impeller/renderer/backend/vulkan/pipeline_vk.h" +#include "flutter/fml/make_copyable.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/timing.h" +#include "impeller/renderer/backend/vulkan/capabilities_vk.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/render_pass_builder_vk.h" +#include "impeller/renderer/backend/vulkan/shader_function_vk.h" +#include "impeller/renderer/backend/vulkan/vertex_descriptor_vk.h" + namespace impeller { +static vk::PipelineCreationFeedbackEXT EmptyFeedback() { + vk::PipelineCreationFeedbackEXT feedback; + // If the VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT is not set in flags, an + // implementation must not set any other bits in flags, and the values of all + // other VkPipelineCreationFeedback data members are undefined. + feedback.flags = vk::PipelineCreationFeedbackFlagBits::eValid; + return feedback; +} + +constexpr vk::FrontFace ToVKFrontFace(WindingOrder order) { + switch (order) { + case WindingOrder::kClockwise: + return vk::FrontFace::eClockwise; + case WindingOrder::kCounterClockwise: + return vk::FrontFace::eCounterClockwise; + } + FML_UNREACHABLE(); +} + +static void ReportPipelineCreationFeedbackToLog( + std::stringstream& stream, + const vk::PipelineCreationFeedbackEXT& feedback) { + const auto pipeline_cache_hit = + feedback.flags & + vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit; + const auto base_pipeline_accl = + feedback.flags & + vk::PipelineCreationFeedbackFlagBits::eBasePipelineAcceleration; + auto duration = std::chrono::duration_cast( + std::chrono::nanoseconds{feedback.duration}); + stream << "Time: " << duration.count() << "ms" + << " Cache Hit: " << static_cast(pipeline_cache_hit) + << " Base Accel: " << static_cast(base_pipeline_accl) + << " Thread: " << std::this_thread::get_id(); +} + +static void ReportPipelineCreationFeedbackToLog( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + std::stringstream stream; + stream << std::fixed << std::showpoint << std::setprecision(2); + stream << std::endl << ">>>>>>" << std::endl; + stream << "Pipeline '" << desc.GetLabel() << "' "; + ReportPipelineCreationFeedbackToLog(stream, + *feedback.pPipelineCreationFeedback); + if (feedback.pipelineStageCreationFeedbackCount != 0) { + stream << std::endl; + } + for (size_t i = 0, count = feedback.pipelineStageCreationFeedbackCount; + i < count; i++) { + stream << "\tStage " << i + 1 << ": "; + ReportPipelineCreationFeedbackToLog( + stream, feedback.pPipelineStageCreationFeedbacks[i]); + if (i != count - 1) { + stream << std::endl; + } + } + stream << std::endl << "<<<<<<" << std::endl; + FML_LOG(ERROR) << stream.str(); +} + +static void ReportPipelineCreationFeedbackToTrace( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + static int64_t gPipelineCacheHits = 0; + static int64_t gPipelineCacheMisses = 0; + static int64_t gPipelines = 0; + if (feedback.pPipelineCreationFeedback->flags & + vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit) { + gPipelineCacheHits++; + } else { + gPipelineCacheMisses++; + } + gPipelines++; + static constexpr int64_t kImpellerPipelineTraceID = 1988; + FML_TRACE_COUNTER("impeller", // + "PipelineCache", // series name + kImpellerPipelineTraceID, // series ID + "PipelineCacheHits", gPipelineCacheHits, // + "PipelineCacheMisses", gPipelineCacheMisses, // + "TotalPipelines", gPipelines // + ); +} + +static void ReportPipelineCreationFeedback( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + constexpr bool kReportPipelineCreationFeedbackToLogs = false; + constexpr bool kReportPipelineCreationFeedbackToTraces = true; + if (kReportPipelineCreationFeedbackToLogs) { + ReportPipelineCreationFeedbackToLog(desc, feedback); + } + if (kReportPipelineCreationFeedbackToTraces) { + ReportPipelineCreationFeedbackToTrace(desc, feedback); + } +} + +//---------------------------------------------------------------------------- +/// Render Pass +/// We are NOT going to use the same render pass with the framebuffer (later) +/// and the graphics pipeline (here). Instead, we are going to ensure that the +/// sub-passes are compatible. To see the compatibility rules, see the Vulkan +/// spec: +/// https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility +/// +static vk::UniqueRenderPass CreateCompatRenderPassForPipeline( + const vk::Device& device, + const PipelineDescriptor& desc) { + RenderPassBuilderVK builder; + + for (const auto& [bind_point, color] : desc.GetColorAttachmentDescriptors()) { + builder.SetColorAttachment(bind_point, // + color.format, // + desc.GetSampleCount(), // + LoadAction::kDontCare, // + StoreAction::kDontCare // + ); + } + + if (auto depth = desc.GetDepthStencilAttachmentDescriptor(); + depth.has_value()) { + builder.SetDepthStencilAttachment(desc.GetDepthPixelFormat(), // + desc.GetSampleCount(), // + LoadAction::kDontCare, // + StoreAction::kDontCare // + ); + } + + if (desc.HasStencilAttachmentDescriptors()) { + builder.SetStencilAttachment(desc.GetStencilPixelFormat(), // + desc.GetSampleCount(), // + LoadAction::kDontCare, // + StoreAction::kDontCare // + ); + } + + auto pass = builder.Build(device); + if (!pass) { + VALIDATION_LOG << "Failed to create render pass for pipeline: " + << desc.GetLabel(); + return {}; + } + + ContextVK::SetDebugName(device, pass.get(), + "Compat Render Pass: " + desc.GetLabel()); + + return pass; +} + +std::unique_ptr PipelineVK::Create( + const PipelineDescriptor& desc, + const std::shared_ptr& device_holder, + const std::weak_ptr& weak_library) { + TRACE_EVENT0("flutter", "PipelineVK::Create"); + + auto library = weak_library.lock(); + + if (!device_holder || !library) { + return nullptr; + } + + const auto& pso_cache = PipelineLibraryVK::Cast(*library).GetPSOCache(); + + vk::StructureChain + chain; + + const auto* caps = pso_cache->GetCapabilities(); + + const auto supports_pipeline_creation_feedback = + caps->HasOptionalDeviceExtension( + OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback); + if (!supports_pipeline_creation_feedback) { + chain.unlink(); + } + + auto& pipeline_info = chain.get(); + + //---------------------------------------------------------------------------- + /// Dynamic States + /// + vk::PipelineDynamicStateCreateInfo dynamic_create_state_info; + std::vector dynamic_states = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eStencilReference, + }; + dynamic_create_state_info.setDynamicStates(dynamic_states); + pipeline_info.setPDynamicState(&dynamic_create_state_info); + + //---------------------------------------------------------------------------- + /// Viewport State + /// + vk::PipelineViewportStateCreateInfo viewport_state; + viewport_state.setViewportCount(1u); + viewport_state.setScissorCount(1u); + // The actual viewport and scissor rects are not set here since they are + // dynamic as mentioned above in the dynamic state info. + pipeline_info.setPViewportState(&viewport_state); + + //---------------------------------------------------------------------------- + /// Shader Stages + /// + const auto& constants = desc.GetSpecializationConstants(); + + std::vector> map_entries( + desc.GetStageEntrypoints().size()); + std::vector specialization_infos( + desc.GetStageEntrypoints().size()); + std::vector shader_stages; + + size_t entrypoint_count = 0; + for (const auto& entrypoint : desc.GetStageEntrypoints()) { + auto stage = ToVKShaderStageFlagBits(entrypoint.first); + if (!stage.has_value()) { + VALIDATION_LOG << "Unsupported shader type in pipeline: " + << desc.GetLabel(); + return nullptr; + } + + std::vector& entries = + map_entries[entrypoint_count]; + for (auto i = 0u; i < constants.size(); i++) { + vk::SpecializationMapEntry entry; + entry.offset = (i * sizeof(Scalar)); + entry.size = sizeof(Scalar); + entry.constantID = i; + entries.emplace_back(entry); + } + + vk::SpecializationInfo& specialization_info = + specialization_infos[entrypoint_count]; + specialization_info.setMapEntries(map_entries[entrypoint_count]); + specialization_info.setPData(constants.data()); + specialization_info.setDataSize(sizeof(Scalar) * constants.size()); + + vk::PipelineShaderStageCreateInfo info; + info.setStage(stage.value()); + info.setPName("main"); + info.setModule( + ShaderFunctionVK::Cast(entrypoint.second.get())->GetModule()); + info.setPSpecializationInfo(&specialization_info); + shader_stages.push_back(info); + entrypoint_count++; + } + pipeline_info.setStages(shader_stages); + + //---------------------------------------------------------------------------- + /// Rasterization State + /// + vk::PipelineRasterizationStateCreateInfo rasterization_state; + rasterization_state.setFrontFace(ToVKFrontFace(desc.GetWindingOrder())); + rasterization_state.setCullMode(ToVKCullModeFlags(desc.GetCullMode())); + rasterization_state.setPolygonMode(ToVKPolygonMode(desc.GetPolygonMode())); + rasterization_state.setLineWidth(1.0f); + rasterization_state.setDepthClampEnable(false); + rasterization_state.setRasterizerDiscardEnable(false); + pipeline_info.setPRasterizationState(&rasterization_state); + + //---------------------------------------------------------------------------- + /// Multi-sample State + /// + vk::PipelineMultisampleStateCreateInfo multisample_state; + multisample_state.setRasterizationSamples( + ToVKSampleCountFlagBits(desc.GetSampleCount())); + pipeline_info.setPMultisampleState(&multisample_state); + + //---------------------------------------------------------------------------- + /// Primitive Input Assembly State + vk::PipelineInputAssemblyStateCreateInfo input_assembly; + const auto topology = ToVKPrimitiveTopology(desc.GetPrimitiveType()); + input_assembly.setTopology(topology); + pipeline_info.setPInputAssemblyState(&input_assembly); + + //---------------------------------------------------------------------------- + /// Color Blend State + std::vector attachment_blend_state; + for (const auto& color_desc : desc.GetColorAttachmentDescriptors()) { + // TODO(csg): The blend states are per color attachment. But it isn't clear + // how the color attachment indices are specified in the pipeline create + // info. But, this should always work for one color attachment. + attachment_blend_state.push_back( + ToVKPipelineColorBlendAttachmentState(color_desc.second)); + } + vk::PipelineColorBlendStateCreateInfo blend_state; + blend_state.setAttachments(attachment_blend_state); + pipeline_info.setPColorBlendState(&blend_state); + + auto render_pass = + CreateCompatRenderPassForPipeline(device_holder->GetDevice(), // + desc // + ); + + if (!render_pass) { + VALIDATION_LOG << "Could not create render pass for pipeline."; + return nullptr; + } + + // Convention wisdom says that the base acceleration pipelines are never used + // by drivers for cache hits. Instead, the PSO cache is the preferred + // mechanism. + pipeline_info.setBasePipelineHandle(VK_NULL_HANDLE); + pipeline_info.setSubpass(0u); + pipeline_info.setRenderPass(render_pass.get()); + + //---------------------------------------------------------------------------- + /// Vertex Input Setup + /// + std::vector attr_descs; + std::vector buffer_descs; + + const auto& stage_inputs = desc.GetVertexDescriptor()->GetStageInputs(); + const auto& stage_buffer_layouts = + desc.GetVertexDescriptor()->GetStageLayouts(); + for (const ShaderStageIOSlot& stage_in : stage_inputs) { + vk::VertexInputAttributeDescription attr_desc; + attr_desc.setBinding(stage_in.binding); + attr_desc.setLocation(stage_in.location); + attr_desc.setFormat(ToVertexDescriptorFormat(stage_in)); + attr_desc.setOffset(stage_in.offset); + attr_descs.push_back(attr_desc); + } + for (const ShaderStageBufferLayout& layout : stage_buffer_layouts) { + vk::VertexInputBindingDescription binding_description; + binding_description.setBinding(layout.binding); + binding_description.setInputRate(vk::VertexInputRate::eVertex); + binding_description.setStride(layout.stride); + buffer_descs.push_back(binding_description); + } + + vk::PipelineVertexInputStateCreateInfo vertex_input_state; + vertex_input_state.setVertexAttributeDescriptions(attr_descs); + vertex_input_state.setVertexBindingDescriptions(buffer_descs); + + pipeline_info.setPVertexInputState(&vertex_input_state); + + //---------------------------------------------------------------------------- + /// Pipeline Layout a.k.a the descriptor sets and uniforms. + /// + std::vector desc_bindings; + + for (auto layout : desc.GetVertexDescriptor()->GetDescriptorSetLayouts()) { + auto vk_desc_layout = ToVKDescriptorSetLayoutBinding(layout); + desc_bindings.push_back(vk_desc_layout); + } + + vk::DescriptorSetLayoutCreateInfo descs_layout_info; + descs_layout_info.setBindings(desc_bindings); + + auto [descs_result, descs_layout] = + device_holder->GetDevice().createDescriptorSetLayoutUnique( + descs_layout_info); + if (descs_result != vk::Result::eSuccess) { + VALIDATION_LOG << "unable to create uniform descriptors"; + return nullptr; + } + + ContextVK::SetDebugName(device_holder->GetDevice(), descs_layout.get(), + "Descriptor Set Layout " + desc.GetLabel()); + + //---------------------------------------------------------------------------- + /// Create the pipeline layout. + /// + vk::PipelineLayoutCreateInfo pipeline_layout_info; + pipeline_layout_info.setSetLayouts(descs_layout.get()); + auto pipeline_layout = device_holder->GetDevice().createPipelineLayoutUnique( + pipeline_layout_info); + if (pipeline_layout.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Could not create pipeline layout for pipeline " + << desc.GetLabel() << ": " + << vk::to_string(pipeline_layout.result); + return nullptr; + } + pipeline_info.setLayout(pipeline_layout.value.get()); + + //---------------------------------------------------------------------------- + /// Create the depth stencil state. + /// + auto depth_stencil_state = ToVKPipelineDepthStencilStateCreateInfo( + desc.GetDepthStencilAttachmentDescriptor(), + desc.GetFrontStencilAttachmentDescriptor(), + desc.GetBackStencilAttachmentDescriptor()); + pipeline_info.setPDepthStencilState(&depth_stencil_state); + + //---------------------------------------------------------------------------- + /// Setup the optional pipeline creation feedback struct so we can understand + /// how Vulkan created the PSO. + /// + auto& feedback = chain.get(); + auto pipeline_feedback = EmptyFeedback(); + std::vector stage_feedbacks( + pipeline_info.stageCount, EmptyFeedback()); + feedback.setPPipelineCreationFeedback(&pipeline_feedback); + feedback.setPipelineStageCreationFeedbacks(stage_feedbacks); + + //---------------------------------------------------------------------------- + /// Finally, all done with the setup info. Create the pipeline itself. + /// + auto pipeline = pso_cache->CreatePipeline(pipeline_info); + if (!pipeline) { + VALIDATION_LOG << "Could not create graphics pipeline: " << desc.GetLabel(); + return nullptr; + } + + if (supports_pipeline_creation_feedback) { + ReportPipelineCreationFeedback(desc, feedback); + } + + ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline_layout.value, + "Pipeline Layout " + desc.GetLabel()); + ContextVK::SetDebugName(device_holder->GetDevice(), *pipeline, + "Pipeline " + desc.GetLabel()); + + auto pipeline_vk = std::unique_ptr(new PipelineVK( + device_holder, // + library, // + desc, // + std::move(pipeline), // + std::move(render_pass), // + std::move(pipeline_layout.value), // + std::move(descs_layout) // + )); + if (!pipeline_vk->IsValid()) { + VALIDATION_LOG << "Could not create a valid pipeline."; + return nullptr; + } + return pipeline_vk; +} + PipelineVK::PipelineVK(std::weak_ptr device_holder, std::weak_ptr library, const PipelineDescriptor& desc, @@ -23,13 +462,7 @@ PipelineVK::PipelineVK(std::weak_ptr device_holder, } PipelineVK::~PipelineVK() { - std::shared_ptr device_holder = device_holder_.lock(); - if (device_holder) { - descriptor_set_layout_.reset(); - layout_.reset(); - render_pass_.reset(); - pipeline_.reset(); - } else { + if (auto device = device_holder_.lock(); !device) { descriptor_set_layout_.release(); layout_.release(); render_pass_.release(); @@ -41,14 +474,10 @@ bool PipelineVK::IsValid() const { return is_valid_; } -const vk::Pipeline& PipelineVK::GetPipeline() const { +vk::Pipeline PipelineVK::GetPipeline() const { return *pipeline_; } -const vk::RenderPass& PipelineVK::GetRenderPass() const { - return *render_pass_; -} - const vk::PipelineLayout& PipelineVK::GetPipelineLayout() const { return *layout_; } diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.h b/impeller/renderer/backend/vulkan/pipeline_vk.h index c27d18643347f..dfe75cfdafcc1 100644 --- a/impeller/renderer/backend/vulkan/pipeline_vk.h +++ b/impeller/renderer/backend/vulkan/pipeline_vk.h @@ -5,10 +5,14 @@ #ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_VK_H_ #define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_VK_H_ +#include #include #include "impeller/base/backend_cast.h" +#include "impeller/base/thread.h" #include "impeller/renderer/backend/vulkan/device_holder.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/pipeline_cache_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/pipeline.h" @@ -22,20 +26,15 @@ class PipelineVK final : public Pipeline, public BackendCast> { public: - PipelineVK(std::weak_ptr device_holder, - std::weak_ptr library, - const PipelineDescriptor& desc, - vk::UniquePipeline pipeline, - vk::UniqueRenderPass render_pass, - vk::UniquePipelineLayout layout, - vk::UniqueDescriptorSetLayout descriptor_set_layout); + static std::unique_ptr Create( + const PipelineDescriptor& desc, + const std::shared_ptr& device_holder, + const std::weak_ptr& weak_library); // |Pipeline| ~PipelineVK() override; - const vk::Pipeline& GetPipeline() const; - - const vk::RenderPass& GetRenderPass() const; + vk::Pipeline GetPipeline() const; const vk::PipelineLayout& GetPipelineLayout() const; @@ -49,8 +48,17 @@ class PipelineVK final vk::UniqueRenderPass render_pass_; vk::UniquePipelineLayout layout_; vk::UniqueDescriptorSetLayout descriptor_set_layout_; + bool is_valid_ = false; + PipelineVK(std::weak_ptr device_holder, + std::weak_ptr library, + const PipelineDescriptor& desc, + vk::UniquePipeline pipeline, + vk::UniqueRenderPass render_pass, + vk::UniquePipelineLayout layout, + vk::UniqueDescriptorSetLayout descriptor_set_layout); + // |Pipeline| bool IsValid() const override; diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc new file mode 100644 index 0000000000000..5edd04441553f --- /dev/null +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc @@ -0,0 +1,187 @@ +// 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_builder_vk.h" + +#include +#include + +#include "impeller/renderer/backend/vulkan/formats_vk.h" + +namespace impeller { + +constexpr auto kSelfDependencySrcStageMask = + vk::PipelineStageFlagBits::eColorAttachmentOutput; +constexpr auto kSelfDependencySrcAccessMask = + vk::AccessFlagBits::eColorAttachmentWrite; + +constexpr auto kSelfDependencyDstStageMask = + vk::PipelineStageFlagBits::eFragmentShader; +constexpr auto kSelfDependencyDstAccessMask = + vk::AccessFlagBits::eInputAttachmentRead; + +constexpr auto kSelfDependencyFlags = vk::DependencyFlagBits::eByRegion; + +RenderPassBuilderVK::RenderPassBuilderVK() = default; + +RenderPassBuilderVK::~RenderPassBuilderVK() = default; + +RenderPassBuilderVK& RenderPassBuilderVK::SetColorAttachment( + size_t index, + PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action) { + vk::AttachmentDescription desc; + desc.format = ToVKImageFormat(format); + desc.samples = ToVKSampleCount(sample_count); + desc.loadOp = ToVKAttachmentLoadOp(load_action); + desc.storeOp = ToVKAttachmentStoreOp(store_action); + desc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; + desc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + desc.initialLayout = vk::ImageLayout::eGeneral; + desc.finalLayout = vk::ImageLayout::eGeneral; + colors_[index] = desc; + + desc.samples = vk::SampleCountFlagBits::e1; + resolves_[index] = desc; + + return *this; +} + +RenderPassBuilderVK& RenderPassBuilderVK::SetDepthStencilAttachment( + PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action) { + vk::AttachmentDescription desc; + desc.format = ToVKImageFormat(format); + desc.samples = ToVKSampleCount(sample_count); + desc.loadOp = ToVKAttachmentLoadOp(load_action); + desc.storeOp = ToVKAttachmentStoreOp(store_action); + desc.stencilLoadOp = desc.loadOp; // Not separable in Impeller. + desc.stencilStoreOp = desc.storeOp; // Not separable in Impeller. + desc.initialLayout = vk::ImageLayout::eGeneral; + desc.finalLayout = vk::ImageLayout::eGeneral; + depth_stencil_ = desc; + return *this; +} + +RenderPassBuilderVK& RenderPassBuilderVK::SetStencilAttachment( + PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action) { + vk::AttachmentDescription desc; + desc.format = ToVKImageFormat(format); + desc.samples = ToVKSampleCount(sample_count); + desc.loadOp = vk::AttachmentLoadOp::eDontCare; + desc.storeOp = vk::AttachmentStoreOp::eDontCare; + desc.stencilLoadOp = ToVKAttachmentLoadOp(load_action); + desc.stencilStoreOp = ToVKAttachmentStoreOp(store_action); + desc.initialLayout = vk::ImageLayout::eGeneral; + desc.finalLayout = vk::ImageLayout::eGeneral; + depth_stencil_ = desc; + return *this; +} + +vk::UniqueRenderPass RenderPassBuilderVK::Build( + const vk::Device& device) const { + FML_DCHECK(colors_.size() == resolves_.size()); + + // This must be less than `VkPhysicalDeviceLimits::maxColorAttachments` but we + // are not checking. + const auto color_attachments_count = + colors_.empty() ? 0u : colors_.rbegin()->first + 1u; + + std::vector attachments; + + std::vector color_refs(color_attachments_count, + kUnusedAttachmentReference); + std::vector resolve_refs(color_attachments_count, + kUnusedAttachmentReference); + vk::AttachmentReference depth_stencil_ref = kUnusedAttachmentReference; + + for (const auto& color : colors_) { + vk::AttachmentReference color_ref; + color_ref.attachment = attachments.size(); + color_ref.layout = vk::ImageLayout::eGeneral; + color_refs[color.first] = color_ref; + attachments.push_back(color.second); + + if (color.second.samples != vk::SampleCountFlagBits::e1) { + vk::AttachmentReference resolve_ref; + resolve_ref.attachment = attachments.size(); + resolve_ref.layout = vk::ImageLayout::eGeneral; + resolve_refs[color.first] = resolve_ref; + attachments.push_back(resolves_.at(color.first)); + } + } + + if (depth_stencil_.has_value()) { + depth_stencil_ref.attachment = attachments.size(); + depth_stencil_ref.layout = vk::ImageLayout::eGeneral; + attachments.push_back(depth_stencil_.value()); + } + + vk::SubpassDescription subpass0; + subpass0.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass0.setInputAttachments(color_refs); + subpass0.setColorAttachments(color_refs); + subpass0.setResolveAttachments(resolve_refs); + subpass0.setPDepthStencilAttachment(&depth_stencil_ref); + + vk::SubpassDependency self_dep; + self_dep.srcSubpass = 0u; // first subpass + self_dep.dstSubpass = 0u; // to itself + self_dep.srcStageMask = kSelfDependencySrcStageMask; + self_dep.srcAccessMask = kSelfDependencySrcAccessMask; + self_dep.dstStageMask = kSelfDependencyDstStageMask; + self_dep.dstAccessMask = kSelfDependencyDstAccessMask; + self_dep.dependencyFlags = kSelfDependencyFlags; + + vk::RenderPassCreateInfo render_pass_desc; + render_pass_desc.setAttachments(attachments); + render_pass_desc.setSubpasses(subpass0); + render_pass_desc.setDependencies(self_dep); + + auto [result, pass] = device.createRenderPassUnique(render_pass_desc); + if (result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create render pass: " << vk::to_string(result); + return {}; + } + return std::move(pass); +} + +void InsertBarrierForInputAttachmentRead(const vk::CommandBuffer& buffer, + const vk::Image& image) { + // This barrier must be a subset of the masks specified in the subpass + // dependency setup. + vk::ImageMemoryBarrier barrier; + barrier.srcAccessMask = kSelfDependencySrcAccessMask; + barrier.dstAccessMask = kSelfDependencyDstAccessMask; + barrier.oldLayout = vk::ImageLayout::eGeneral; + barrier.newLayout = vk::ImageLayout::eGeneral; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + vk::ImageSubresourceRange image_levels; + image_levels.aspectMask = vk::ImageAspectFlagBits::eColor; + image_levels.baseArrayLayer = 0u; + image_levels.baseMipLevel = 0u; + image_levels.layerCount = VK_REMAINING_ARRAY_LAYERS; + image_levels.levelCount = VK_REMAINING_MIP_LEVELS; + barrier.subresourceRange = image_levels; + + buffer.pipelineBarrier(kSelfDependencySrcStageMask, // + kSelfDependencyDstStageMask, // + kSelfDependencyFlags, // + {}, // + {}, // + barrier // + ); +} + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk.h b/impeller/renderer/backend/vulkan/render_pass_builder_vk.h new file mode 100644 index 0000000000000..4ed9f1c9c5a39 --- /dev/null +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.h @@ -0,0 +1,71 @@ +// 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_RENDER_PASS_BUILDER_VK_H_ +#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_RENDER_PASS_BUILDER_VK_H_ + +#include +#include + +#include "impeller/core/formats.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" + +namespace impeller { + +class RenderPassBuilderVK { + public: + RenderPassBuilderVK(); + + ~RenderPassBuilderVK(); + + RenderPassBuilderVK(const RenderPassBuilderVK&) = delete; + + RenderPassBuilderVK& operator=(const RenderPassBuilderVK&) = delete; + + RenderPassBuilderVK& SetColorAttachment(size_t index, + PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action); + + RenderPassBuilderVK& SetDepthStencilAttachment(PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action); + + RenderPassBuilderVK& SetStencilAttachment(PixelFormat format, + SampleCount sample_count, + LoadAction load_action, + StoreAction store_action); + + vk::UniqueRenderPass Build(const vk::Device& device) const; + + private: + std::map colors_; + std::map resolves_; + std::optional depth_stencil_; +}; + +//------------------------------------------------------------------------------ +/// @brief Inserts the appropriate barriers to ensure that subsequent +/// commands can read from the specified image (itself a framebuffer +/// attachment) as an input attachment. +/// +/// Unlike most barriers, this barrier may only be inserted within a +/// Vulkan render-pass. +/// +/// The type of barrier inserted depends on the subpass setup and +/// self-dependencies. Only use this utility method for inserting +/// barriers in render passes created by `RenderPassBuilderVK`. +/// +/// @param[in] buffer The buffer +/// @param[in] image The image +/// +void InsertBarrierForInputAttachmentRead(const vk::CommandBuffer& buffer, + const vk::Image& image); + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_RENDER_PASS_BUILDER_VK_H_ diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.cc b/impeller/renderer/backend/vulkan/render_pass_vk.cc index 8e45b70d7bd01..a8ce872f6db47 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc @@ -20,13 +20,11 @@ #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_enums.hpp" -#include "vulkan/vulkan_handles.hpp" -#include "vulkan/vulkan_to_string.hpp" +#include "impeller/renderer/backend/vulkan/vk.h" namespace impeller { @@ -35,7 +33,7 @@ namespace impeller { // manually changed. // // See: impeller/entity/shaders/blending/framebuffer_blend.frag -static constexpr size_t kMagicSubpassInputBinding = 64; +static constexpr size_t kMagicSubpassInputBinding = 64u; static vk::ClearColorValue VKClearValueFromColor(Color color) { vk::ClearColorValue value; @@ -79,176 +77,65 @@ static std::vector GetVKClearValues( return clears; } -static vk::AttachmentDescription CreateAttachmentDescription( - const Attachment& attachment, - const std::shared_ptr 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& command_buffer, - const std::shared_ptr 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 RenderPassVK::CreateVKRenderPass( const ContextVK& context, - const std::shared_ptr& command_buffer, - bool supports_framebuffer_fetch) const { - std::vector attachments; - - std::vector color_refs; - std::vector 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); + const std::shared_ptr& 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()) { - color_refs[bind_point] = vk::AttachmentReference{ - static_cast(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); + 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) { - resolve_refs[bind_point] = vk::AttachmentReference{ - static_cast(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); + TextureVK::Cast(*color.resolve_texture).SetLayout(barrier); } } - auto depth = render_target_.GetDepthAttachment(); - if (depth.has_value()) { - depth_stencil_ref = vk::AttachmentReference{ - static_cast(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 depth = render_target_.GetDepthAttachment(); depth.has_value()) { + builder.SetDepthStencilAttachment( + depth->texture->GetTextureDescriptor().format, // + depth->texture->GetTextureDescriptor().sample_count, // + depth->load_action, // + depth->store_action // + ); + TextureVK::Cast(*depth->texture).SetLayout(barrier); } if (auto stencil = render_target_.GetStencilAttachment(); stencil.has_value()) { - depth_stencil_ref = vk::AttachmentReference{ - static_cast(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 subpass_dependencies; - std::vector subpass_color_ref; - subpass_color_ref.push_back(vk::AttachmentReference{ - static_cast(0), vk::ImageLayout::eColorAttachmentOptimal}); - if (supports_framebuffer_fetch) { - subpass_desc.setFlags(vk::SubpassDescriptionFlagBits:: - eRasterizationOrderAttachmentColorAccessARM); - subpass_desc.setInputAttachments(subpass_color_ref); + builder.SetStencilAttachment( + stencil->texture->GetTextureDescriptor().format, // + stencil->texture->GetTextureDescriptor().sample_count, // + stencil->load_action, // + stencil->store_action // + ); + TextureVK::Cast(*stencil->texture).SetLayout(barrier); } - vk::RenderPassCreateInfo render_pass_desc; - render_pass_desc.setAttachments(attachments); - render_pass_desc.setPSubpasses(&subpass_desc); - render_pass_desc.setSubpassCount(1u); + auto pass = builder.Build(context.GetDevice()); - 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); + 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)); } @@ -269,9 +156,7 @@ RenderPassVK::RenderPassVK(const std::shared_ptr& context, const auto& target_size = render_target_.GetRenderTargetSize(); - render_pass_ = CreateVKRenderPass( - vk_context, command_buffer_, - vk_context.GetCapabilities()->SupportsFramebufferFetch()); + render_pass_ = CreateVKRenderPass(vk_context, command_buffer_); if (!render_pass_) { VALIDATION_LOG << "Could not create renderpass."; is_valid_ = false; @@ -355,7 +240,8 @@ SharedHandleVK RenderPassVK::CreateVKFramebuffer( std::vector attachments; // This bit must be consistent to ensure compatibility with the pass created - // earlier. Follow this order: Color attachments, then depth, then stencil. + // 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. @@ -369,9 +255,8 @@ SharedHandleVK RenderPassVK::CreateVKFramebuffer( 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()) { + } else if (auto stencil = render_target_.GetStencilAttachment(); + stencil.has_value()) { attachments.emplace_back( TextureVK::Cast(*stencil->texture).GetRenderTargetView()); } @@ -406,12 +291,14 @@ void RenderPassVK::SetPipeline( command_buffer_vk_.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline_vk.GetPipeline()); - if (pipeline->GetDescriptor().UsesSubpassInput()) { + pipeline_uses_input_attachments_ = + pipeline->GetDescriptor().GetVertexDescriptor()->UsesInputAttacments(); + + if (pipeline_uses_input_attachments_) { 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; @@ -548,6 +435,11 @@ fml::Status RenderPassVK::Draw() { 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 @@ -577,6 +469,7 @@ fml::Status RenderPassVK::Draw() { base_vertex_ = 0u; vertex_count_ = 0u; pipeline_valid_ = false; + pipeline_uses_input_attachments_ = false; return fml::Status(); } diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.h b/impeller/renderer/backend/vulkan/render_pass_vk.h index 04739363d31a9..4836cf4c86c81 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.h +++ b/impeller/renderer/backend/vulkan/render_pass_vk.h @@ -47,7 +47,7 @@ class RenderPassVK final : public RenderPass { bool has_index_buffer_ = false; bool has_label_ = false; bool pipeline_valid_ = false; - vk::Pipeline last_pipeline_; + bool pipeline_uses_input_attachments_ = false; vk::DescriptorSet descriptor_set_ = {}; vk::PipelineLayout pipeline_layout_ = {}; @@ -123,8 +123,7 @@ class RenderPassVK final : public RenderPass { SharedHandleVK CreateVKRenderPass( const ContextVK& context, - const std::shared_ptr& command_buffer, - bool has_subpass_dependency) const; + const std::shared_ptr& command_buffer) const; SharedHandleVK CreateVKFramebuffer( const ContextVK& context, diff --git a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc index 860fcf0e225f5..a35881438aa04 100644 --- a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc +++ b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc @@ -163,7 +163,7 @@ SwapchainImplVK::SwapchainImplVK( auto& vk_context = ContextVK::Cast(*context); - auto [caps_result, caps] = + const auto [caps_result, surface_caps] = vk_context.GetPhysicalDevice().getSurfaceCapabilitiesKHR(*surface); if (caps_result != vk::Result::eSuccess) { VALIDATION_LOG << "Could not get surface capabilities: " @@ -188,7 +188,7 @@ SwapchainImplVK::SwapchainImplVK( vk_context.SetOffscreenFormat(ToPixelFormat(format.value().format)); const auto composite = - ChooseAlphaCompositionMode(caps.supportedCompositeAlpha); + ChooseAlphaCompositionMode(surface_caps.supportedCompositeAlpha); if (!composite.has_value()) { VALIDATION_LOG << "No composition mode supported."; return; @@ -209,22 +209,26 @@ SwapchainImplVK::SwapchainImplVK( swapchain_info.imageColorSpace = format.value().colorSpace; swapchain_info.presentMode = vk::PresentModeKHR::eFifo; swapchain_info.imageExtent = vk::Extent2D{ - std::clamp(caps.currentExtent.width, caps.minImageExtent.width, - caps.maxImageExtent.width), - std::clamp(caps.currentExtent.height, caps.minImageExtent.height, - caps.maxImageExtent.height), + std::clamp(surface_caps.currentExtent.width, + surface_caps.minImageExtent.width, + surface_caps.maxImageExtent.width), + std::clamp(surface_caps.currentExtent.height, + surface_caps.minImageExtent.height, + surface_caps.maxImageExtent.height), }; - swapchain_info.minImageCount = std::clamp( - caps.minImageCount + 1u, // preferred image count - caps.minImageCount, // min count cannot be zero - caps.maxImageCount == 0u ? caps.minImageCount + 1u - : caps.maxImageCount // max zero means no limit - ); + swapchain_info.minImageCount = + std::clamp(surface_caps.minImageCount + 1u, // preferred image count + surface_caps.minImageCount, // min count cannot be zero + surface_caps.maxImageCount == 0u + ? surface_caps.minImageCount + 1u + : surface_caps.maxImageCount // max zero means no limit + ); swapchain_info.imageArrayLayers = 1u; - // Swapchain images are primarily used as color attachments (via resolve) or - // blit targets. + // Swapchain images are primarily used as color attachments (via resolve), + // blit targets, or input attachments. swapchain_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eTransferDst; + vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eInputAttachment; swapchain_info.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; swapchain_info.compositeAlpha = composite.value(); // If we set the clipped value to true, Vulkan expects we will never read back diff --git a/impeller/renderer/pipeline.h b/impeller/renderer/pipeline.h index 63d0918b78a7b..eda23adebc5ff 100644 --- a/impeller/renderer/pipeline.h +++ b/impeller/renderer/pipeline.h @@ -64,12 +64,13 @@ class Pipeline { std::function descriptor_callback) const; protected: - Pipeline(std::weak_ptr library, T desc); - - private: const std::weak_ptr library_; + const T desc_; + Pipeline(std::weak_ptr library, T desc); + + private: Pipeline(const Pipeline&) = delete; Pipeline& operator=(const Pipeline&) = delete; diff --git a/impeller/renderer/pipeline_descriptor.cc b/impeller/renderer/pipeline_descriptor.cc index 8b9dcf8fef7ae..9ae6710426f63 100644 --- a/impeller/renderer/pipeline_descriptor.cc +++ b/impeller/renderer/pipeline_descriptor.cc @@ -45,7 +45,6 @@ std::size_t PipelineDescriptor::GetHash() const { fml::HashCombineSeed(seed, cull_mode_); fml::HashCombineSeed(seed, primitive_type_); fml::HashCombineSeed(seed, polygon_mode_); - fml::HashCombineSeed(seed, use_subpass_input_); return seed; } @@ -66,8 +65,7 @@ bool PipelineDescriptor::IsEqual(const PipelineDescriptor& other) const { cull_mode_ == other.cull_mode_ && primitive_type_ == other.primitive_type_ && polygon_mode_ == other.polygon_mode_ && - specialization_constants_ == other.specialization_constants_ && - use_subpass_input_ == other.use_subpass_input_; + specialization_constants_ == other.specialization_constants_; } PipelineDescriptor& PipelineDescriptor::SetLabel(std::string label) { diff --git a/impeller/renderer/pipeline_descriptor.h b/impeller/renderer/pipeline_descriptor.h index 460d7e1ee8d4c..3ea2ae6080c60 100644 --- a/impeller/renderer/pipeline_descriptor.h +++ b/impeller/renderer/pipeline_descriptor.h @@ -21,11 +21,6 @@ class VertexDescriptor; template class Pipeline; -enum class UseSubpassInput { - kYes, - kNo, -}; - class PipelineDescriptor final : public Comparable { public: PipelineDescriptor(); @@ -134,17 +129,6 @@ class PipelineDescriptor final : public Comparable { const std::vector& GetSpecializationConstants() const; - void SetUseSubpassInput(UseSubpassInput value) { use_subpass_input_ = value; } - - bool UsesSubpassInput() const { - switch (use_subpass_input_) { - case UseSubpassInput::kYes: - return true; - case UseSubpassInput::kNo: - return false; - } - } - private: std::string label_; SampleCount sample_count_ = SampleCount::kCount1; @@ -163,7 +147,6 @@ class PipelineDescriptor final : public Comparable { back_stencil_attachment_descriptor_; PrimitiveType primitive_type_ = PrimitiveType::kTriangle; PolygonMode polygon_mode_ = PolygonMode::kFill; - UseSubpassInput use_subpass_input_ = UseSubpassInput::kNo; std::vector specialization_constants_; }; diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index c5336fb7433e3..1e5c38344a662 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -22,6 +22,13 @@ #include "impeller/fixtures/instanced_draw.vert.h" #include "impeller/fixtures/mipmaps.frag.h" #include "impeller/fixtures/mipmaps.vert.h" +#include "impeller/fixtures/sepia.frag.h" +#include "impeller/fixtures/sepia.vert.h" +#include "impeller/fixtures/swizzle.frag.h" +#include "impeller/fixtures/test_texture.frag.h" +#include "impeller/fixtures/test_texture.vert.h" +#include "impeller/fixtures/texture.frag.h" +#include "impeller/fixtures/texture.vert.h" #include "impeller/geometry/path_builder.h" #include "impeller/playground/playground_test.h" #include "impeller/renderer/command.h" @@ -1299,6 +1306,220 @@ TEST_P(RendererTest, GetContext()->GetCapabilities()->GetDefaultDepthStencilFormat()); } +template +std::shared_ptr> CreateDefaultPipeline( + const std::shared_ptr& context) { + using TexturePipelineBuilder = PipelineBuilder; + auto pipeline_desc = + TexturePipelineBuilder::MakeDefaultPipelineDescriptor(*context); + if (!pipeline_desc.has_value()) { + return nullptr; + } + pipeline_desc->SetSampleCount(SampleCount::kCount4); + pipeline_desc->SetStencilAttachmentDescriptors(std::nullopt); + auto pipeline = + context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); + if (!pipeline || !pipeline->IsValid()) { + return nullptr; + } + return pipeline; +} + +TEST_P(RendererTest, CanSepiaToneWithSubpasses) { + // Define shader types + using TextureVS = TextureVertexShader; + using TextureFS = TextureFragmentShader; + + using SepiaVS = SepiaVertexShader; + using SepiaFS = SepiaFragmentShader; + + auto context = GetContext(); + ASSERT_TRUE(context); + + if (!context->GetCapabilities()->SupportsFramebufferFetch()) { + GTEST_SKIP_( + "This test uses framebuffer fetch and the backend doesn't support it."); + return; + } + + // Create pipelines. + auto texture_pipeline = CreateDefaultPipeline(context); + auto sepia_pipeline = CreateDefaultPipeline(context); + + ASSERT_TRUE(texture_pipeline); + ASSERT_TRUE(sepia_pipeline); + + // Vertex buffer builders. + VertexBufferBuilder texture_vtx_builder; + texture_vtx_builder.AddVertices({ + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 100, 0.0}, {1.0, 0.0}}, // 2 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 800, 0.0}, {0.0, 1.0}}, // 4 + }); + + VertexBufferBuilder sepia_vtx_builder; + sepia_vtx_builder.AddVertices({ + {{100, 100, 0.0}}, // 1 + {{800, 100, 0.0}}, // 2 + {{800, 800, 0.0}}, // 3 + {{100, 100, 0.0}}, // 1 + {{800, 800, 0.0}}, // 3 + {{100, 800, 0.0}}, // 4 + }); + + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(boston); + + const auto& sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + SinglePassCallback callback = [&](RenderPass& pass) { + auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + + // Draw the texture. + { + pass.SetPipeline(texture_pipeline); + pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + TextureVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + TextureFS::BindTextureContents(pass, boston, sampler); + if (!pass.Draw().ok()) { + return false; + } + } + + // Draw the sepia toner. + { + pass.SetPipeline(sepia_pipeline); + pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SepiaVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SepiaVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + if (!pass.Draw().ok()) { + return false; + } + } + + return true; + }; + OpenPlaygroundHere(callback); +} + +TEST_P(RendererTest, CanSepiaToneThenSwizzleWithSubpasses) { + // Define shader types + using TextureVS = TextureVertexShader; + using TextureFS = TextureFragmentShader; + + using SwizzleVS = SepiaVertexShader; + using SwizzleFS = SwizzleFragmentShader; + + using SepiaVS = SepiaVertexShader; + using SepiaFS = SepiaFragmentShader; + + auto context = GetContext(); + ASSERT_TRUE(context); + + if (!context->GetCapabilities()->SupportsFramebufferFetch()) { + GTEST_SKIP_( + "This test uses framebuffer fetch and the backend doesn't support it."); + return; + } + + // Create pipelines. + auto texture_pipeline = CreateDefaultPipeline(context); + auto swizzle_pipeline = CreateDefaultPipeline(context); + auto sepia_pipeline = CreateDefaultPipeline(context); + + ASSERT_TRUE(texture_pipeline); + ASSERT_TRUE(swizzle_pipeline); + ASSERT_TRUE(sepia_pipeline); + + // Vertex buffer builders. + VertexBufferBuilder texture_vtx_builder; + texture_vtx_builder.AddVertices({ + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 100, 0.0}, {1.0, 0.0}}, // 2 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 100, 0.0}, {0.0, 0.0}}, // 1 + {{800, 800, 0.0}, {1.0, 1.0}}, // 3 + {{100, 800, 0.0}, {0.0, 1.0}}, // 4 + }); + + VertexBufferBuilder sepia_vtx_builder; + sepia_vtx_builder.AddVertices({ + {{100, 100, 0.0}}, // 1 + {{800, 100, 0.0}}, // 2 + {{800, 800, 0.0}}, // 3 + {{100, 100, 0.0}}, // 1 + {{800, 800, 0.0}}, // 3 + {{100, 800, 0.0}}, // 4 + }); + + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(boston); + + const auto& sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + SinglePassCallback callback = [&](RenderPass& pass) { + auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + + // Draw the texture. + { + pass.SetPipeline(texture_pipeline); + pass.SetVertexBuffer(texture_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + TextureVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + TextureVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + TextureFS::BindTextureContents(pass, boston, sampler); + if (!pass.Draw().ok()) { + return false; + } + } + + // Draw the sepia toner. + { + pass.SetPipeline(sepia_pipeline); + pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SepiaVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SepiaVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + if (!pass.Draw().ok()) { + return false; + } + } + + // Draw the swizzle. + { + pass.SetPipeline(swizzle_pipeline); + pass.SetVertexBuffer(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SwizzleVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SwizzleVS::BindUniformBuffer(pass, buffer->EmplaceUniform(uniforms)); + if (!pass.Draw().ok()) { + return false; + } + } + + return true; + }; + OpenPlaygroundHere(callback); +} + } // namespace testing } // namespace impeller diff --git a/impeller/renderer/vertex_descriptor.cc b/impeller/renderer/vertex_descriptor.cc index 1c912308859b4..a8e4ee3422de1 100644 --- a/impeller/renderer/vertex_descriptor.cc +++ b/impeller/renderer/vertex_descriptor.cc @@ -37,6 +37,8 @@ void VertexDescriptor::RegisterDescriptorSetLayouts( size_t count) { desc_set_layouts_.reserve(desc_set_layouts_.size() + count); for (size_t i = 0; i < count; i++) { + uses_input_attachments_ |= + desc_set_layout[i].descriptor_type == DescriptorType::kInputAttachment; desc_set_layouts_.emplace_back(desc_set_layout[i]); } } @@ -72,4 +74,8 @@ VertexDescriptor::GetDescriptorSetLayouts() const { return desc_set_layouts_; } +bool VertexDescriptor::UsesInputAttacments() const { + return uses_input_attachments_; +} + } // namespace impeller diff --git a/impeller/renderer/vertex_descriptor.h b/impeller/renderer/vertex_descriptor.h index 58addd4a82eb1..55c487e0cdf6a 100644 --- a/impeller/renderer/vertex_descriptor.h +++ b/impeller/renderer/vertex_descriptor.h @@ -67,10 +67,13 @@ class VertexDescriptor final : public Comparable { // |Comparable| bool IsEqual(const VertexDescriptor& other) const override; + bool UsesInputAttacments() const; + private: std::vector inputs_; std::vector layouts_; std::vector desc_set_layouts_; + bool uses_input_attachments_ = false; VertexDescriptor(const VertexDescriptor&) = delete;