From 10e844e0ee8f109b6159500d73e680831f80d54b Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 2 Jan 2024 15:25:01 -0800 Subject: [PATCH 1/4] [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. * Depending on where the subpass cursor is for each command, a different pipeline variant is necessary. For instance, if a command uses a pipeline at subpass 1 of 10 and that same pipeline is reused later in say subpass 6, the variant for subpass 1 is not suitable for subpass 6 (`VkGraphicsPipelineCreateInfo::subpass(uint32_t)` is part of the compat rules). Creation of these subpass variants from the lone-pass (subpass index 0 of count 1) prototype is done via a preload operation with jobs submitted to a concurrent worker. The preload can only happen once the number of passes needed can be determined. On mobile and desktop devices at hand, the observation was that the variants obtained from the prototype already in a `PipelineCacheVK` was extremely fast. Even so, once the variant is obtained, it is cached in `PipelineVK`. Notably, this is not present in `PipelineCacheVK`. That top-level cache only contains prototypes for the lone-pass pipeline configuration. The allows for purging of subpass variants of which there can theoretically be an unbounded number, and also a single point where subpass prototype creation can be elided completely if the `rasterization_order_attachment_access` extension is present. * 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. * This patch contains some test harness updates for MoltenVK that will be backed out and submitted separately. --- impeller/BUILD.gn | 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 | 18 + impeller/fixtures/sepia.vert | 13 + impeller/fixtures/swizzle.frag | 11 + impeller/fixtures/texture.frag | 13 + impeller/fixtures/texture.vert | 17 + .../backend/vulkan/playground_impl_vk.cc | 34 +- impeller/playground/playground.cc | 2 +- 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/binding_helpers_vk.cc | 95 ++-- .../backend/vulkan/binding_helpers_vk.h | 2 +- .../backend/vulkan/capabilities_vk.cc | 18 +- .../renderer/backend/vulkan/capabilities_vk.h | 3 - .../backend/vulkan/command_encoder_vk.cc | 4 +- .../backend/vulkan/command_encoder_vk.h | 2 +- .../renderer/backend/vulkan/context_vk.cc | 5 + impeller/renderer/backend/vulkan/context_vk.h | 4 +- .../backend/vulkan/debug_report_vk.cc | 23 +- .../backend/vulkan/descriptor_pool_vk.cc | 6 +- .../backend/vulkan/descriptor_pool_vk.h | 2 +- .../vulkan/descriptor_pool_vk_unittests.cc | 2 +- .../renderer/backend/vulkan/device_holder.h | 2 + impeller/renderer/backend/vulkan/formats_vk.h | 47 ++ .../backend/vulkan/pipeline_library_vk.cc | 490 +--------------- .../backend/vulkan/pipeline_library_vk.h | 11 +- .../renderer/backend/vulkan/pipeline_vk.cc | 530 +++++++++++++++++- .../renderer/backend/vulkan/pipeline_vk.h | 45 +- .../backend/vulkan/render_pass_builder_vk.cc | 206 +++++++ .../backend/vulkan/render_pass_builder_vk.h | 56 ++ .../renderer/backend/vulkan/render_pass_vk.cc | 309 +++++----- .../renderer/backend/vulkan/render_pass_vk.h | 2 +- .../backend/vulkan/swapchain_impl_vk.cc | 3 +- impeller/renderer/pipeline.h | 7 +- impeller/renderer/pipeline_descriptor.cc | 4 +- impeller/renderer/pipeline_descriptor.h | 17 - impeller/renderer/renderer_unittests.cc | 217 +++++++ impeller/renderer/vertex_descriptor.cc | 6 + impeller/renderer/vertex_descriptor.h | 3 + impeller/tools/impeller.gni | 7 - 46 files changed, 1505 insertions(+), 861 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/impeller/BUILD.gn b/impeller/BUILD.gn index 3bfe85a0fb072..3a69d7db48a8b 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -34,10 +34,6 @@ config("impeller_public_config") { defines += [ "IMPELLER_ENABLE_VULKAN=1" ] } - if (impeller_enable_vulkan_playgrounds) { - defines += [ "IMPELLER_ENABLE_VULKAN_PLAYGROUNDS=1" ] - } - if (impeller_trace_all_gl_calls) { defines += [ "IMPELLER_TRACE_ALL_GL_CALLS" ] } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 634ae0b8e6fa7..24940aa98fa3d 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -223,64 +223,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 94d4af747750c..883b04f3674eb 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -799,15 +799,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 520d2ee9ee3db..67bce9cc6a1fb 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2577,39 +2577,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 9165f06542a2a..db4dde8fbfee7 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..e171047ac4f2a --- /dev/null +++ b/impeller/fixtures/sepia.frag @@ -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. + +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..47f3773aa3911 --- /dev/null +++ b/impeller/fixtures/sepia.vert @@ -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. + +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..df927b9e9bfcb --- /dev/null +++ b/impeller/fixtures/texture.vert @@ -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. + +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/playground/backend/vulkan/playground_impl_vk.cc b/impeller/playground/backend/vulkan/playground_impl_vk.cc index d05004f4eb43a..b42c11011724d 100644 --- a/impeller/playground/backend/vulkan/playground_impl_vk.cc +++ b/impeller/playground/backend/vulkan/playground_impl_vk.cc @@ -175,10 +175,38 @@ void PlaygroundImplVK::InitGlobalVulkanInstance() { application_info.setPEngineName("PlaygroundImplVK"); application_info.setPApplicationName("PlaygroundImplVK"); - auto instance_result = - vk::createInstanceUnique(vk::InstanceCreateInfo({}, &application_info)); + CapabilitiesVK caps(false); + auto enabled_layers = caps.GetEnabledLayers(); + auto enabled_extensions = caps.GetEnabledInstanceExtensions(); + + FML_CHECK(enabled_layers.has_value() && enabled_extensions.has_value()); + + std::vector enabled_layers_c; + std::vector enabled_extensions_c; + + for (const auto& layer : enabled_layers.value()) { + enabled_layers_c.push_back(layer.c_str()); + } + + for (const auto& ext : enabled_extensions.value()) { + enabled_extensions_c.push_back(ext.c_str()); + } + + vk::InstanceCreateInfo instance_info; + instance_info.setPEnabledLayerNames(enabled_layers_c); + instance_info.setPEnabledExtensionNames(enabled_extensions_c); + instance_info.setPApplicationInfo(&application_info); + + if (std::find(enabled_extensions->begin(), enabled_extensions->end(), + "VK_KHR_portability_enumeration") != + enabled_extensions->end()) { + instance_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; + } + + auto instance_result = vk::createInstanceUnique(instance_info); FML_CHECK(instance_result.result == vk::Result::eSuccess) - << "Unable to initialize global Vulkan instance"; + << "Unable to initialize global Vulkan instance: " + << vk::to_string(instance_result.result); global_instance_ = std::move(instance_result.value); } diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 1c6dbfa694b49..f2a51ba4d7715 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -102,7 +102,7 @@ bool Playground::SupportsBackend(PlaygroundBackend backend) { return false; #endif // IMPELLER_ENABLE_OPENGLES case PlaygroundBackend::kVulkan: -#if IMPELLER_ENABLE_VULKAN && IMPELLER_ENABLE_VULKAN_PLAYGROUNDS +#if IMPELLER_ENABLE_VULKAN return true; #else // IMPELLER_ENABLE_VULKAN return false; diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index be72d061fb56c..2567b243b8d57 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -78,6 +78,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 f6e2bf67e4858..bc7b8ff36dfd1 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 = {}; @@ -415,8 +410,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 51be4d8eb9521..58a57dca1e003 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; uint32_t frame_count_ = 0; 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/binding_helpers_vk.cc b/impeller/renderer/backend/vulkan/binding_helpers_vk.cc index 19fe5350d602b..57c1d973ce0a9 100644 --- a/impeller/renderer/backend/vulkan/binding_helpers_vk.cc +++ b/impeller/renderer/backend/vulkan/binding_helpers_vk.cc @@ -18,13 +18,40 @@ namespace impeller { -// Warning: if any of the constant values or layouts are changed in the -// framebuffer fetch shader, then this input binding may need to be -// manually changed. -static constexpr size_t kMagicSubpassInputBinding = 64; +static bool BindInputAttachments( + const PipelineDescriptor& desc, + vk::DescriptorSet& vk_desc_set, + const std::shared_ptr& color_attachment0, + std::vector& images, + std::vector& writes) { + if (!desc.GetVertexDescriptor()->UsesInputAttacments()) { + return true; + } + + // This routine is a bit of a lark at the moment since there is an assumption + // that there can only be one input attachment at a pre-determined binding. No + // matter the actual index specification in the shader. + + vk::DescriptorImageInfo image_info; + image_info.imageLayout = vk::ImageLayout::eGeneral; + // Loads from input attachments in a subpass are direct pixel loads of values + // from previous subpasses. As such, there can be no sampling. + image_info.sampler = VK_NULL_HANDLE; + image_info.imageView = TextureVK::Cast(*color_attachment0).GetImageView(); + images.push_back(image_info); + + vk::WriteDescriptorSet write_set; + write_set.dstSet = vk_desc_set; + write_set.dstBinding = 64u; + write_set.descriptorCount = 1u; + write_set.descriptorType = vk::DescriptorType::eInputAttachment; + write_set.pImageInfo = &images.back(); + writes.push_back(write_set); + + return true; +} static bool BindImages(const Bindings& bindings, - Allocator& allocator, const std::shared_ptr& encoder, vk::DescriptorSet& vk_desc_set, std::vector& images, @@ -53,7 +80,6 @@ static bool BindImages(const Bindings& bindings, write_set.descriptorCount = 1u; write_set.descriptorType = vk::DescriptorType::eCombinedImageSampler; write_set.pImageInfo = &images.back(); - writes.push_back(write_set); } @@ -124,7 +150,7 @@ fml::StatusOr> AllocateAndBindDescriptorSets( const ContextVK& context, const std::shared_ptr& encoder, const std::vector& commands, - const TextureVK& input_attachment) { + const std::shared_ptr& color_attachment0) { if (commands.empty()) { return std::vector{}; } @@ -132,9 +158,10 @@ fml::StatusOr> AllocateAndBindDescriptorSets( // Step 1: Determine the total number of buffer and sampler descriptor // sets required. Collect this information along with the layout information // to allocate a correctly sized descriptor pool. - size_t buffer_count = 0; - size_t samplers_count = 0; - size_t subpass_count = 0; + size_t buffer_count = 0u; + size_t samplers_count = 0u; + size_t input_attachements_count = 0u; + std::vector layouts; layouts.reserve(commands.size()); @@ -142,14 +169,17 @@ fml::StatusOr> AllocateAndBindDescriptorSets( buffer_count += command.vertex_bindings.buffers.size(); buffer_count += command.fragment_bindings.buffers.size(); samplers_count += command.fragment_bindings.sampled_images.size(); - subpass_count += - command.pipeline->GetDescriptor().UsesSubpassInput() ? 1 : 0; + input_attachements_count += command.pipeline->GetDescriptor() + .GetVertexDescriptor() + ->UsesInputAttacments() + ? 1u + : 0u; layouts.emplace_back( PipelineVK::Cast(*command.pipeline).GetDescriptorSetLayout()); } auto descriptor_result = encoder->AllocateDescriptorSets( - buffer_count, samplers_count, subpass_count, layouts); + buffer_count, samplers_count, input_attachements_count, layouts); if (!descriptor_result.ok()) { return descriptor_result.status(); } @@ -158,14 +188,14 @@ fml::StatusOr> AllocateAndBindDescriptorSets( return fml::Status(); } - // Step 2: Update the descriptors for all image and buffer descriptors used - // in the render pass. + // Step 2: Update the descriptors for all image, buffer, and input-attachment + // descriptors used in the render pass. std::vector images; std::vector buffers; std::vector writes; - images.reserve(samplers_count + subpass_count); + images.reserve(samplers_count + input_attachements_count); buffers.reserve(buffer_count); - writes.reserve(samplers_count + buffer_count + subpass_count); + writes.reserve(samplers_count + buffer_count + input_attachements_count); auto& allocator = *context.GetResourceAllocator(); auto desc_index = 0u; @@ -177,28 +207,15 @@ fml::StatusOr> AllocateAndBindDescriptorSets( descriptor_sets[desc_index], desc_set, buffers, writes) || !BindBuffers(command.fragment_bindings, allocator, encoder, descriptor_sets[desc_index], desc_set, buffers, writes) || - !BindImages(command.fragment_bindings, allocator, encoder, - descriptor_sets[desc_index], images, writes)) { + !BindImages(command.fragment_bindings, encoder, + descriptor_sets[desc_index], images, writes) || + !BindInputAttachments(command.pipeline->GetDescriptor(), + descriptor_sets[desc_index], color_attachment0, + images, writes)) { return fml::Status(fml::StatusCode::kUnknown, "Failed to bind texture or buffer."); } - if (command.pipeline->GetDescriptor().UsesSubpassInput()) { - vk::DescriptorImageInfo image_info; - image_info.imageLayout = vk::ImageLayout::eGeneral; - image_info.sampler = VK_NULL_HANDLE; - image_info.imageView = input_attachment.GetImageView(); - images.push_back(image_info); - - vk::WriteDescriptorSet write_set; - write_set.dstSet = descriptor_sets[desc_index]; - write_set.dstBinding = kMagicSubpassInputBinding; - write_set.descriptorCount = 1u; - write_set.descriptorType = vk::DescriptorType::eInputAttachment; - write_set.pImageInfo = &images.back(); - - writes.push_back(write_set); - } desc_index += 1; } @@ -228,8 +245,8 @@ fml::StatusOr> AllocateAndBindDescriptorSets( layouts.emplace_back( ComputePipelineVK::Cast(*command.pipeline).GetDescriptorSetLayout()); } - auto descriptor_result = - encoder->AllocateDescriptorSets(buffer_count, samplers_count, 0, layouts); + auto descriptor_result = encoder->AllocateDescriptorSets( + buffer_count, samplers_count, 0u, layouts); if (!descriptor_result.ok()) { return descriptor_result.status(); } @@ -253,8 +270,8 @@ fml::StatusOr> AllocateAndBindDescriptorSets( if (!BindBuffers(command.bindings, allocator, encoder, descriptor_sets[desc_index], desc_set, buffers, writes) || - !BindImages(command.bindings, allocator, encoder, - descriptor_sets[desc_index], images, writes)) { + !BindImages(command.bindings, encoder, descriptor_sets[desc_index], + images, writes)) { return fml::Status(fml::StatusCode::kUnknown, "Failed to bind texture or buffer."); } diff --git a/impeller/renderer/backend/vulkan/binding_helpers_vk.h b/impeller/renderer/backend/vulkan/binding_helpers_vk.h index b232d360cf4d4..6279507128087 100644 --- a/impeller/renderer/backend/vulkan/binding_helpers_vk.h +++ b/impeller/renderer/backend/vulkan/binding_helpers_vk.h @@ -19,7 +19,7 @@ fml::StatusOr> AllocateAndBindDescriptorSets( const ContextVK& context, const std::shared_ptr& encoder, const std::vector& commands, - const TextureVK& input_attachment); + const std::shared_ptr& color_attachment0); fml::StatusOr> AllocateAndBindDescriptorSets( const ContextVK& context, 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/command_encoder_vk.cc b/impeller/renderer/backend/vulkan/command_encoder_vk.cc index 31ab804027da9..094a56cf6455d 100644 --- a/impeller/renderer/backend/vulkan/command_encoder_vk.cc +++ b/impeller/renderer/backend/vulkan/command_encoder_vk.cc @@ -298,14 +298,14 @@ fml::StatusOr> CommandEncoderVK::AllocateDescriptorSets( uint32_t buffer_count, uint32_t sampler_count, - uint32_t subpass_count, + uint32_t input_attachments_count, const std::vector& layouts) { if (!IsValid()) { return fml::Status(fml::StatusCode::kUnknown, "command encoder invalid"); } return tracked_objects_->GetDescriptorPool().AllocateDescriptorSets( - buffer_count, sampler_count, subpass_count, layouts); + buffer_count, sampler_count, input_attachments_count, layouts); } void CommandEncoderVK::PushDebugGroup(const char* label) const { diff --git a/impeller/renderer/backend/vulkan/command_encoder_vk.h b/impeller/renderer/backend/vulkan/command_encoder_vk.h index 4c682e8284884..30850fccbbe17 100644 --- a/impeller/renderer/backend/vulkan/command_encoder_vk.h +++ b/impeller/renderer/backend/vulkan/command_encoder_vk.h @@ -85,7 +85,7 @@ class CommandEncoderVK { fml::StatusOr> AllocateDescriptorSets( uint32_t buffer_count, uint32_t sampler_count, - uint32_t subpass_count, + uint32_t input_attachments_count, const std::vector& layouts); private: diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index 6c1804dd74022..b8829016bb214 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -562,4 +562,9 @@ std::shared_ptr ContextVK::GetGPUTracer() const { return gpu_tracer_; } +std::shared_ptr ContextVK::GetDescriptorPoolRecycler() + const { + return descriptor_pool_recycler_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h index ac059ebed237c..a78413dff8a82 100644 --- a/impeller/renderer/backend/vulkan/context_vk.h +++ b/impeller/renderer/backend/vulkan/context_vk.h @@ -159,9 +159,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 GetGPUTracer() const; diff --git a/impeller/renderer/backend/vulkan/debug_report_vk.cc b/impeller/renderer/backend/vulkan/debug_report_vk.cc index 39068e056e0ec..e90aa8d9d0ccd 100644 --- a/impeller/renderer/backend/vulkan/debug_report_vk.cc +++ b/impeller/renderer/backend/vulkan/debug_report_vk.cc @@ -103,10 +103,29 @@ DebugReportVK::Result DebugReportVK::OnDebugCallback( vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* data) { + // TODO(csg): 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; + } + + // Issue in MoltenVK where the ID number is zero and the message warns of + // emulation. + if (data->messageIdNumber == 0 && // + data->pMessage != nullptr && // + strstr(data->pMessage, "MTLCounterSampleBuffer") != nullptr // + ) { return Result::kContinue; } diff --git a/impeller/renderer/backend/vulkan/descriptor_pool_vk.cc b/impeller/renderer/backend/vulkan/descriptor_pool_vk.cc index 9d9461453c11f..b791547f12309 100644 --- a/impeller/renderer/backend/vulkan/descriptor_pool_vk.cc +++ b/impeller/renderer/backend/vulkan/descriptor_pool_vk.cc @@ -14,7 +14,7 @@ namespace impeller { -// Holds the command pool in a background thread, recyling it when not in use. +// Holds the command pool in a background thread, recycling it when not in use. class BackgroundDescriptorPoolVK final { public: BackgroundDescriptorPoolVK(BackgroundDescriptorPoolVK&&) = default; @@ -83,14 +83,14 @@ fml::StatusOr> DescriptorPoolVK::AllocateDescriptorSets( uint32_t buffer_count, uint32_t sampler_count, - uint32_t subpass_count, + uint32_t input_attachments_count, const std::vector& layouts) { std::shared_ptr strong_context = context_.lock(); if (!strong_context) { return fml::Status(fml::StatusCode::kUnknown, "No device"); } auto minimum_capacity = - std::max(std::max(sampler_count, buffer_count), subpass_count); + std::max({sampler_count, buffer_count, input_attachments_count}); auto [new_pool, capacity] = strong_context->GetDescriptorPoolRecycler()->Get(minimum_capacity); if (!new_pool) { diff --git a/impeller/renderer/backend/vulkan/descriptor_pool_vk.h b/impeller/renderer/backend/vulkan/descriptor_pool_vk.h index f4965ec224f6a..a717aa53da2ee 100644 --- a/impeller/renderer/backend/vulkan/descriptor_pool_vk.h +++ b/impeller/renderer/backend/vulkan/descriptor_pool_vk.h @@ -33,7 +33,7 @@ class DescriptorPoolVK { fml::StatusOr> AllocateDescriptorSets( uint32_t buffer_count, uint32_t sampler_count, - uint32_t subpass_count, + uint32_t input_attachments_count, const std::vector& layouts); private: diff --git a/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc b/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc index 8749f52af4daa..830192d5d5b3f 100644 --- a/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc +++ b/impeller/renderer/backend/vulkan/descriptor_pool_vk_unittests.cc @@ -66,7 +66,7 @@ TEST(DescriptorPoolRecyclerVKTest, ReclaimMakesDescriptorPoolAvailable) { { // Fetch a pool (which will be created). auto pool = DescriptorPoolVK(context); - pool.AllocateDescriptorSets(1024, 1024, 1024, {}); + pool.AllocateDescriptorSets(1024, 1024, 1024u, {}); } // There is a chance that the first death rattle item below is destroyed in 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 bf0ce5fb6a760..4c0c20890b6aa 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" @@ -665,6 +667,51 @@ constexpr vk::ImageAspectFlags ToImageAspectFlags(PixelFormat format) { FML_UNREACHABLE(); } +struct SubpassCursorVK { + size_t index = 0u; + size_t count = 1u; + + constexpr SubpassCursorVK() = default; + + constexpr SubpassCursorVK(size_t p_index, size_t p_count) + : index(p_index), count(p_count) {} + + constexpr bool IsValid() const { return index < count; } + + constexpr bool IsLoneSubpass() const { return index == 0u && count == 1u; } + + constexpr bool IsFinalSubpass() const { return index == count - 1u; } + + constexpr bool operator==(const SubpassCursorVK& o) const { + return index == o.index && count == o.count; + } + + struct Hash { + constexpr std::size_t operator()(const SubpassCursorVK& o) const { + return fml::HashCombine(o.index, o.count); + } + }; + + struct Equal { + constexpr bool operator()(const SubpassCursorVK& lhs, + const SubpassCursorVK& rhs) const { + return lhs.index == rhs.index && lhs.count == rhs.count; + } + }; +}; + +static constexpr auto kLoneSupassCursor = SubpassCursorVK{}; + } // namespace impeller +namespace std { + +inline std::ostream& operator<<(std::ostream& out, + const impeller::SubpassCursorVK& cursor) { + out << "{" << cursor.index << ", " << cursor.count << "}"; + return out; +} + +} // namespace std + #endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_FORMATS_VK_H_ diff --git a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index 5149158a6904a..d4f080b0d02d3 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,12 @@ 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, // + kLoneSupassCursor // + )); }); return pipeline_future; @@ -741,4 +272,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..7978bb282edb0 100644 --- a/impeller/renderer/backend/vulkan/pipeline_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_vk.cc @@ -4,32 +4,481 @@ #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, + size_t subpass_count) { + RenderPassBuilderVK builder; + + builder.SetSubpassCount(subpass_count); + + 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, + std::weak_ptr weak_library, + SubpassCursorVK subpass_cursor) { + TRACE_EVENT0("flutter", "PipelineVK::Create"); + + auto library = weak_library.lock(); + + if (!device_holder || !library) { + return nullptr; + } + + // There seems to be no validation error for this simple error. And this + // condition is easy to get into if the right subpass steps are not added. + if (!subpass_cursor.IsValid()) { + VALIDATION_LOG << "Subpass cursor for pipeline creation was invalid: " + << subpass_cursor; + 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, // + subpass_cursor.count // + ); + + 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(subpass_cursor.index); + 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), // + subpass_cursor // + )); + 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, vk::UniquePipeline pipeline, vk::UniqueRenderPass render_pass, vk::UniquePipelineLayout layout, - vk::UniqueDescriptorSetLayout descriptor_set_layout) + vk::UniqueDescriptorSetLayout descriptor_set_layout, + SubpassCursorVK subpass_cursor) : Pipeline(std::move(library), desc), device_holder_(std::move(device_holder)), pipeline_(std::move(pipeline)), render_pass_(std::move(render_pass)), layout_(std::move(layout)), - descriptor_set_layout_(std::move(descriptor_set_layout)) { + descriptor_set_layout_(std::move(descriptor_set_layout)), + subpass_cursor_(subpass_cursor) { is_valid_ = pipeline_ && render_pass_ && layout_ && descriptor_set_layout_; } 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,12 +490,11 @@ bool PipelineVK::IsValid() const { return is_valid_; } -const vk::Pipeline& PipelineVK::GetPipeline() const { - return *pipeline_; -} - -const vk::RenderPass& PipelineVK::GetRenderPass() const { - return *render_pass_; +vk::Pipeline PipelineVK::GetPipeline(SubpassCursorVK cursor) const { + if (subpass_cursor_ == cursor) { + return *pipeline_; + } + return CreateOrGetVariantForSubpass(cursor).get()->GetPipeline(cursor); } const vk::PipelineLayout& PipelineVK::GetPipelineLayout() const { @@ -57,4 +505,56 @@ const vk::DescriptorSetLayout& PipelineVK::GetDescriptorSetLayout() const { return *descriptor_set_layout_; } +std::shared_future> +PipelineVK::CreateOrGetVariantForSubpass(SubpassCursorVK cursor) const { + Lock lock(subpass_pipelines_mutex_); + auto found = subpass_pipelines_.find(cursor); + if (found != subpass_pipelines_.end()) { + return found->second; + } + std::promise> promise; + auto future = + std::shared_future>{promise.get_future()}; + subpass_pipelines_[cursor] = future; + + auto task = [promise = std::move(promise), // + desc = desc_, // + device = device_holder_, // + library = library_, // + cursor // + ]() mutable { + TRACE_EVENT0("impeller", "CreateSubpassPipelineVariantAsync"); + auto pipeline = PipelineVK::Create(desc, // + device.lock(), // + library, // + cursor // + ); + promise.set_value(std::shared_ptr{std::move(pipeline)}); + }; + + auto library = library_.lock(); + if (!library) { + promise.set_value(nullptr); + return future; + } + + PipelineLibraryVK::Cast(*library).GetWorkerTaskRunner()->PostTask( + fml::MakeCopyable(std::move(task))); + + return future; +} + +void PipelineVK::PreloadPipeline(SubpassCursorVK cursor) const { + if (subpass_cursor_ == cursor) { + return; + } + // Create a future but don't await on it. The task has been enqueued. + CreateOrGetVariantForSubpass(cursor); +} + +bool PipelineVK::HasPreloadedPipeline(SubpassCursorVK cursor) const { + Lock lock(subpass_pipelines_mutex_); + return subpass_pipelines_.find(cursor) != subpass_pipelines_.end(); +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.h b/impeller/renderer/backend/vulkan/pipeline_vk.h index ffe245152c4da..fceb2a2ff2dbd 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" @@ -18,20 +22,20 @@ 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, + std::weak_ptr weak_library, + SubpassCursorVK subpass_cursor); // |Pipeline| ~PipelineVK() override; - const vk::Pipeline& GetPipeline() const; + void PreloadPipeline(SubpassCursorVK cursor) const; + + bool HasPreloadedPipeline(SubpassCursorVK cursor) const; - const vk::RenderPass& GetRenderPass() const; + vk::Pipeline GetPipeline(SubpassCursorVK cursor) const; const vk::PipelineLayout& GetPipelineLayout() const; @@ -40,19 +44,42 @@ class PipelineVK final private: friend class PipelineLibraryVK; + using SubpassPipelines = + std::unordered_map>, + SubpassCursorVK::Hash, + SubpassCursorVK::Equal>; + std::weak_ptr device_holder_; vk::UniquePipeline pipeline_; vk::UniqueRenderPass render_pass_; vk::UniquePipelineLayout layout_; vk::UniqueDescriptorSetLayout descriptor_set_layout_; + SubpassCursorVK subpass_cursor_; + mutable Mutex subpass_pipelines_mutex_; + mutable SubpassPipelines subpass_pipelines_ IPLR_GUARDED_BY( + subpass_pipelines_mutex_); + 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, + SubpassCursorVK subpass_cursor); + // |Pipeline| bool IsValid() const override; PipelineVK(const PipelineVK&) = delete; PipelineVK& operator=(const PipelineVK&) = delete; + + std::shared_future> CreateOrGetVariantForSubpass( + SubpassCursorVK cursor) const; }; } // namespace impeller 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..4eea0e4c72f31 --- /dev/null +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc @@ -0,0 +1,206 @@ +// 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 { + +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 { + if (colors_.empty()) { + return {}; + } + + FML_DCHECK(colors_.size() == resolves_.size()); + + // This must be less than `VkPhysicalDeviceLimits::maxColorAttachments` but we + // are not checking. + const auto color_attachments_count = 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()) { + vk::AttachmentReference depth_stencil_ref; + depth_stencil_ref.attachment = attachments.size(); + depth_stencil_ref.layout = vk::ImageLayout::eGeneral; + attachments.push_back(depth_stencil_.value()); + } + + std::vector subpasses; + subpasses.reserve(subpass_count_); + + std::vector subpass_dependencies; + subpass_dependencies.reserve(subpass_count_); + + for (size_t i = 0; i < subpass_count_; i++) { + // Setup the subpass. + { + vk::SubpassDescription subpass; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.setInputAttachments(color_refs); + subpass.setColorAttachments(color_refs); + subpass.setResolveAttachments(resolve_refs); + subpass.setPDepthStencilAttachment(&depth_stencil_ref); + subpasses.emplace_back(std::move(subpass)); + } + + // Wire up the dependencies from the subpass before and after this subpass. + { + const bool is_first_subpass = i == 0u; + const bool is_last_subpass = i == subpass_count_ - 1u; + const bool is_sole_subpass = is_first_subpass && is_last_subpass; + + if (is_sole_subpass) { + // Don't bother adding dependencies from the external passes if this is + // the only subpass. The defaults are fine. + break; + } + + { + vk::SubpassDependency dep; + + dep.srcSubpass = is_first_subpass ? VK_SUBPASS_EXTERNAL : i - 1; + dep.dstSubpass = i; + + // All dependencies are framebuffer local. + dep.dependencyFlags = vk::DependencyFlagBits::eByRegion; + + if (!is_last_subpass) { + // All commands before this barrier must proceed till at least the + // color-attachment writes in the color-attachment output pipeline + // stage are executed. And, all commands after this barrier may + // continue till they encounter an input-attachment read in the + // fragment shader pipeline stage. + dep.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dep.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + dep.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader; + dep.dstAccessMask = vk::AccessFlagBits::eInputAttachmentRead; + } else { + // This is the final subpass! + // + // All commands before this barrier must proceed till they finish + // color-attachment writes in the color-attachment output pipeline + // stage. And, there are no commands after this barrier since the + // subpass ends. Just say these non-existent commands can continue + // till the bottom of pipe. + dep.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dep.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + dep.dstStageMask = vk::PipelineStageFlagBits::eBottomOfPipe; + dep.dstAccessMask = {}; + } + + subpass_dependencies.emplace_back(std::move(dep)); + } + } + } + + vk::RenderPassCreateInfo render_pass_desc; + render_pass_desc.setAttachments(attachments); + render_pass_desc.setSubpasses(subpasses); + 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: " << vk::to_string(result); + return {}; + } + return std::move(pass); +} + +RenderPassBuilderVK& RenderPassBuilderVK::SetSubpassCount(size_t subpasses) { + subpass_count_ = std::max(1u, subpasses); + return *this; +} + +} // 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..1308b4d62b4bc --- /dev/null +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.h @@ -0,0 +1,56 @@ +// 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& SetSubpassCount(size_t subpasses); + + 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_; + size_t subpass_count_ = 1u; +}; + +} // 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 67066c0878d0c..c1971cf7cfe31 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc @@ -4,8 +4,10 @@ #include "impeller/renderer/backend/vulkan/render_pass_vk.h" +#include #include #include +#include #include #include "flutter/fml/trace_event.h" @@ -19,6 +21,7 @@ #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/shared_object_vk.h" #include "impeller/renderer/backend/vulkan/texture_vk.h" #include "impeller/renderer/command.h" @@ -28,171 +31,68 @@ namespace impeller { -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 && - current_layout != vk::ImageLayout::eUndefined) { - // 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); + size_t subpass_count) 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; + + builder.SetSubpassCount(subpass_count); 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); } } if (auto depth = render_target_.GetDepthAttachment(); 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); + 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)); - 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); - } - - vk::RenderPassCreateInfo render_pass_desc; - render_pass_desc.setAttachments(attachments); - render_pass_desc.setPSubpasses(&subpass_desc); - render_pass_desc.setSubpassCount(1u); - - auto [result, pass] = - context.GetDevice().createRenderPassUnique(render_pass_desc); - if (result != vk::Result::eSuccess) { - VALIDATION_LOG << "Failed to create render pass: " << vk::to_string(result); + builder.SetStencilAttachment( + stencil->texture->GetTextureDescriptor().format, // + stencil->texture->GetTextureDescriptor().sample_count, // + stencil->load_action, // + stencil->store_action // + ); + TextureVK::Cast(*stencil->texture).SetLayout(barrier); + } + + auto pass = builder.Build(context.GetDevice()); + + if (!pass) { + VALIDATION_LOG << "Failed to create render pass for framebuffer."; return {}; } + context.SetDebugName(pass.get(), debug_label_.c_str()); + return MakeSharedVK(std::move(pass)); } @@ -369,7 +269,8 @@ static bool EncodeCommand(const Context& context, CommandEncoderVK& encoder, PassBindingsCache& command_buffer_cache, const ISize& target_size, - const vk::DescriptorSet vk_desc_set) { + const vk::DescriptorSet vk_desc_set, + SubpassCursorVK subpass_cursor) { #ifdef IMPELLER_DEBUG fml::ScopedCleanupClosure pop_marker( [&encoder]() { encoder.PopDebugGroup(); }); @@ -391,8 +292,9 @@ static bool EncodeCommand(const Context& context, nullptr // offsets ); - command_buffer_cache.BindPipeline( - cmd_buffer, vk::PipelineBindPoint::eGraphics, pipeline_vk.GetPipeline()); + command_buffer_cache.BindPipeline(cmd_buffer, + vk::PipelineBindPoint::eGraphics, + pipeline_vk.GetPipeline(subpass_cursor)); // Set the viewport and scissors. SetViewportAndScissor(command, cmd_buffer, command_buffer_cache, target_size); @@ -468,6 +370,69 @@ static bool EncodeCommand(const Context& context, return true; } +static size_t CountSubpassesForCommandStream( + const std::vector& commands) { + if (commands.empty()) { + return 1u; + } + auto commands_with_input_attachments = + std::accumulate(commands.cbegin(), commands.cend(), 0u, + [](size_t count, const auto& command) { + return count + (command.pipeline->GetDescriptor() + .GetVertexDescriptor() + ->UsesInputAttacments() + ? 1u + : 0u); + }); + // If there are no commands with input attachments use, then we only need one + // lone subpass. This should be the most common case. + if (commands_with_input_attachments == 0) { + return 1u; + } + const bool first_command_uses_input_attachments = + commands.cbegin() + ->pipeline->GetDescriptor() + .GetVertexDescriptor() + ->UsesInputAttacments(); + return first_command_uses_input_attachments + ? commands_with_input_attachments + : commands_with_input_attachments + 1u; +} + +static bool ShouldAdvanceSubpass(const Command& command, size_t command_index) { + const bool command_uses_input_attachments = command.pipeline->GetDescriptor() + .GetVertexDescriptor() + ->UsesInputAttacments(); + + if (!command_uses_input_attachments) { + return false; + } + + if (command_index == 0 && command_uses_input_attachments) { + return false; + } + + return true; +} + +static void PreloadSubpassPipelineForCommandStream( + const std::vector& commands, + size_t subpass_count) { + if (subpass_count <= 1u) { + return; + } + size_t subpass_index = 0u; + for (size_t command_index = 0; command_index < commands.size(); + command_index++) { + const auto& command = commands[command_index]; + if (ShouldAdvanceSubpass(command, command_index)) { + subpass_index++; + } + PipelineVK::Cast(*command.pipeline) + .PreloadPipeline(SubpassCursorVK{subpass_index, subpass_count}); + } +} + bool RenderPassVK::OnEncodeCommands(const Context& context) const { TRACE_EVENT0("impeller", "RenderPassVK::OnEncodeCommands"); if (!IsValid()) { @@ -509,9 +474,13 @@ bool RenderPassVK::OnEncodeCommands(const Context& context) const { const auto& target_size = render_target_.GetRenderTargetSize(); - auto render_pass = CreateVKRenderPass( - vk_context, command_buffer, - vk_context.GetCapabilities()->SupportsFramebufferFetch()); + SubpassCursorVK subpass_cursor; + subpass_cursor.count = CountSubpassesForCommandStream(commands_); + + PreloadSubpassPipelineForCommandStream(commands_, subpass_cursor.count); + + auto render_pass = + CreateVKRenderPass(vk_context, command_buffer, subpass_cursor.count); if (!render_pass) { VALIDATION_LOG << "Could not create renderpass."; return false; @@ -537,10 +506,11 @@ bool RenderPassVK::OnEncodeCommands(const Context& context) const { static_cast(target_size.height); pass_info.setClearValues(clear_values); - const auto& color_image_vk = TextureVK::Cast( - *render_target_.GetColorAttachments().find(0u)->second.texture); + // Only attachment at index 0 may be used as a subpass input at this time. + const auto& color_attachment0 = + render_target_.GetColorAttachments().at(0u).texture; auto desc_sets_result = AllocateAndBindDescriptorSets( - vk_context, encoder, commands_, color_image_vk); + vk_context, encoder, commands_, color_attachment0); if (!desc_sets_result.ok()) { return false; } @@ -553,14 +523,31 @@ bool RenderPassVK::OnEncodeCommands(const Context& context) const { fml::ScopedCleanupClosure end_render_pass( [cmd_buffer]() { cmd_buffer.endRenderPass(); }); - auto desc_index = 0u; + size_t command_index = 0u; for (const auto& command : commands_) { - if (!EncodeCommand(context, command, *encoder, pass_bindings_cache_, - target_size, desc_sets[desc_index])) { + if (ShouldAdvanceSubpass(command, command_index)) { + subpass_cursor.index++; + encoder->GetCommandBuffer().nextSubpass(vk::SubpassContents::eInline); + } + FML_DCHECK(subpass_cursor.IsValid()); + FML_DCHECK(PipelineVK::Cast(*command.pipeline) + .HasPreloadedPipeline(subpass_cursor)) + << "Insufficient subpass pipeline preloading. Functionally correct " + "but misses using available concurrency."; + if (!EncodeCommand(context, // + command, // + *encoder, // + pass_bindings_cache_, // + target_size, // + desc_sets[command_index], // + subpass_cursor // + )) { return false; } - desc_index += 1; + command_index += 1; } + FML_DCHECK(subpass_cursor.IsValid()); + FML_DCHECK(subpass_cursor.IsFinalSubpass()); } return true; diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.h b/impeller/renderer/backend/vulkan/render_pass_vk.h index a173aafe35736..cb9f58a88e4ca 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.h +++ b/impeller/renderer/backend/vulkan/render_pass_vk.h @@ -45,7 +45,7 @@ class RenderPassVK final : public RenderPass { SharedHandleVK CreateVKRenderPass( const ContextVK& context, const std::shared_ptr& command_buffer, - bool has_subpass_dependency) const; + size_t subpass_count) 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 ac31b6eea5be8..4ee44971dd2ec 100644 --- a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc +++ b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc @@ -219,7 +219,8 @@ SwapchainImplVK::SwapchainImplVK( // Swapchain images are primarily used as color attachments (via resolve) or // blit targets. 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 dcc40359f9d27..a061b859db542 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" @@ -1314,6 +1321,216 @@ TEST_P(RendererTest, CanLookupRenderTargetProperties) { render_target.GetRenderTargetSize()); } +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); + + // 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); + + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + SinglePassCallback callback = [&](RenderPass& pass) { + auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + + // Draw the texture. + { + Command texture_cmd; + texture_cmd.pipeline = texture_pipeline; + texture_cmd.BindVertices(texture_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + TextureVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + TextureVS::BindUniformBuffer(texture_cmd, + buffer->EmplaceUniform(uniforms)); + TextureFS::BindTextureContents(texture_cmd, boston, sampler); + if (!pass.AddCommand(std::move(texture_cmd))) { + return false; + } + } + + // Draw the sepia toner. + { + Command sepia_cmd; + sepia_cmd.pipeline = sepia_pipeline; + sepia_cmd.BindVertices(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SepiaVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SepiaVS::BindUniformBuffer(sepia_cmd, buffer->EmplaceUniform(uniforms)); + if (!pass.AddCommand(std::move(sepia_cmd))) { + 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); + + // 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); + + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + SinglePassCallback callback = [&](RenderPass& pass) { + auto buffer = HostBuffer::Create(context->GetResourceAllocator()); + + // Draw the texture. + { + Command texture_cmd; + texture_cmd.pipeline = texture_pipeline; + texture_cmd.BindVertices(texture_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + TextureVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + TextureVS::BindUniformBuffer(texture_cmd, + buffer->EmplaceUniform(uniforms)); + TextureFS::BindTextureContents(texture_cmd, boston, sampler); + if (!pass.AddCommand(std::move(texture_cmd))) { + return false; + } + } + + // Draw the sepia toner. + { + Command sepia_cmd; + sepia_cmd.pipeline = sepia_pipeline; + sepia_cmd.BindVertices(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SepiaVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SepiaVS::BindUniformBuffer(sepia_cmd, buffer->EmplaceUniform(uniforms)); + if (!pass.AddCommand(std::move(sepia_cmd))) { + return false; + } + } + + // Draw the swizzle. + { + Command swizzle_cmd; + swizzle_cmd.pipeline = swizzle_pipeline; + swizzle_cmd.BindVertices(sepia_vtx_builder.CreateVertexBuffer( + *context->GetResourceAllocator())); + SwizzleVS::UniformBuffer uniforms; + uniforms.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + SwizzleVS::BindUniformBuffer(swizzle_cmd, + buffer->EmplaceUniform(uniforms)); + if (!pass.AddCommand(std::move(swizzle_cmd))) { + 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; diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni index 6541c3b12173b..ffc2c40667cdc 100644 --- a/impeller/tools/impeller.gni +++ b/impeller/tools/impeller.gni @@ -25,13 +25,6 @@ declare_args() { impeller_enable_vulkan = (is_linux || is_win || is_android || enable_unittests) && target_os != "fuchsia" - # Whether playgrounds should run with Vulkan. - # - # impeller_enable_vulkan may be true in build environments that run tests but - # do not have a Vulkan ICD present. - impeller_enable_vulkan_playgrounds = - (is_linux || is_win || is_android) && target_os != "fuchsia" - # Whether to use a prebuilt impellerc. # If this is the empty string, impellerc will be built. # If it is non-empty, it should be the absolute path to impellerc. From f4a85c11e5183a70d5adac5d1d0dd1ca3b5667c5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 16 Jan 2024 11:40:22 -0800 Subject: [PATCH 2/4] Format SL. --- impeller/fixtures/sepia.frag | 9 ++++----- impeller/fixtures/sepia.vert | 3 ++- impeller/fixtures/texture.vert | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/impeller/fixtures/sepia.frag b/impeller/fixtures/sepia.frag index e171047ac4f2a..1379a5f4f533b 100644 --- a/impeller/fixtures/sepia.frag +++ b/impeller/fixtures/sepia.frag @@ -8,11 +8,10 @@ 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 + 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 index 47f3773aa3911..b8b09eeaae0e8 100644 --- a/impeller/fixtures/sepia.vert +++ b/impeller/fixtures/sepia.vert @@ -4,7 +4,8 @@ uniform UniformBuffer { mat4 mvp; -} uniform_buffer; +} +uniform_buffer; in vec3 vertex_position; diff --git a/impeller/fixtures/texture.vert b/impeller/fixtures/texture.vert index df927b9e9bfcb..fd65238d83de7 100644 --- a/impeller/fixtures/texture.vert +++ b/impeller/fixtures/texture.vert @@ -4,7 +4,8 @@ uniform UniformBuffer { mat4 mvp; -} uniform_buffer; +} +uniform_buffer; in vec3 vertex_position; in vec2 texture_coordinates; From 1521ac999319adc2464050b1e8a862560eb8dc27 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 16 Jan 2024 11:46:03 -0800 Subject: [PATCH 3/4] Don't overspecify the final dependency. The last subpass dependency doesn't specify the transition out of it. --- .../backend/vulkan/render_pass_builder_vk.cc | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc index 4eea0e4c72f31..4f1d2b73586f5 100644 --- a/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_builder_vk.cc @@ -156,29 +156,15 @@ vk::UniqueRenderPass RenderPassBuilderVK::Build( // All dependencies are framebuffer local. dep.dependencyFlags = vk::DependencyFlagBits::eByRegion; - if (!is_last_subpass) { - // All commands before this barrier must proceed till at least the - // color-attachment writes in the color-attachment output pipeline - // stage are executed. And, all commands after this barrier may - // continue till they encounter an input-attachment read in the - // fragment shader pipeline stage. - dep.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dep.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - dep.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader; - dep.dstAccessMask = vk::AccessFlagBits::eInputAttachmentRead; - } else { - // This is the final subpass! - // - // All commands before this barrier must proceed till they finish - // color-attachment writes in the color-attachment output pipeline - // stage. And, there are no commands after this barrier since the - // subpass ends. Just say these non-existent commands can continue - // till the bottom of pipe. - dep.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dep.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - dep.dstStageMask = vk::PipelineStageFlagBits::eBottomOfPipe; - dep.dstAccessMask = {}; - } + // All commands before this barrier must proceed till at least the + // color-attachment writes in the color-attachment output pipeline + // stage are executed. And, all commands after this barrier may + // continue till they encounter an input-attachment read in the + // fragment shader pipeline stage. + dep.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dep.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + dep.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader; + dep.dstAccessMask = vk::AccessFlagBits::eInputAttachmentRead; subpass_dependencies.emplace_back(std::move(dep)); } From badb9a7ab0badaea18621f489b84b061eff77cb9 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 16 Jan 2024 12:06:05 -0800 Subject: [PATCH 4/4] Fix DCHECK making tests trip. --- impeller/renderer/backend/vulkan/pipeline_vk.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/impeller/renderer/backend/vulkan/pipeline_vk.cc b/impeller/renderer/backend/vulkan/pipeline_vk.cc index 7978bb282edb0..007802472f740 100644 --- a/impeller/renderer/backend/vulkan/pipeline_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_vk.cc @@ -553,6 +553,9 @@ void PipelineVK::PreloadPipeline(SubpassCursorVK cursor) const { } bool PipelineVK::HasPreloadedPipeline(SubpassCursorVK cursor) const { + if (subpass_cursor_ == cursor) { + return true; + } Lock lock(subpass_pipelines_mutex_); return subpass_pipelines_.find(cursor) != subpass_pipelines_.end(); }