Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[Impeller] Implement framebuffer-fetch via subpasses in Vulkan withou…
…t 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.
  • Loading branch information
chinmaygarde committed Jan 16, 2024
commit 10e844e0ee8f109b6159500d73e680831f80d54b
4 changes: 0 additions & 4 deletions impeller/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
}
Expand Down
45 changes: 15 additions & 30 deletions impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -223,64 +223,49 @@ ContentContext::ContentContext(
if (context_->GetCapabilities()->SupportsFramebufferFetch()) {
framebuffer_blend_color_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kColor), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kColor), supports_decal});
framebuffer_blend_colorburn_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kColorBurn), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kColorBurn), supports_decal});
framebuffer_blend_colordodge_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kColorDodge), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kColorDodge), supports_decal});
framebuffer_blend_darken_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kDarken), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kDarken), supports_decal});
framebuffer_blend_difference_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kDifference), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kDifference), supports_decal});
framebuffer_blend_exclusion_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kExclusion), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kExclusion), supports_decal});
framebuffer_blend_hardlight_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kHardLight), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kHardLight), supports_decal});
framebuffer_blend_hue_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kHue), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kHue), supports_decal});
framebuffer_blend_lighten_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kLighten), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kLighten), supports_decal});
framebuffer_blend_luminosity_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kLuminosity), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kLuminosity), supports_decal});
framebuffer_blend_multiply_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kMultiply), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kMultiply), supports_decal});
framebuffer_blend_overlay_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kOverlay), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kOverlay), supports_decal});
framebuffer_blend_saturation_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kSaturation), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kSaturation), supports_decal});
framebuffer_blend_screen_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kScreen), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kScreen), supports_decal});
framebuffer_blend_softlight_pipelines_.CreateDefault(
*context_, options_trianglestrip,
{static_cast<Scalar>(BlendSelectValues::kSoftLight), supports_decal},
UseSubpassInput::kYes);
{static_cast<Scalar>(BlendSelectValues::kSoftLight), supports_decal});
}

blend_color_pipelines_.CreateDefault(
Expand Down
4 changes: 1 addition & 3 deletions impeller/entity/contents/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -799,15 +799,13 @@ class ContentContext {

void CreateDefault(const Context& context,
const ContentContextOptions& options,
const std::initializer_list<Scalar>& constants = {},
UseSubpassInput subpass_input = UseSubpassInput::kNo) {
const std::initializer_list<Scalar>& 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<PipelineT>(context, desc));
}
Expand Down
33 changes: 0 additions & 33 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<PipelineDescriptor>();
auto desc_2 = std::make_shared<PipelineDescriptor>();

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.
Expand Down
14 changes: 11 additions & 3 deletions impeller/fixtures/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions impeller/fixtures/sepia.frag
Original file line number Diff line number Diff line change
@@ -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);
}
13 changes: 13 additions & 0 deletions impeller/fixtures/sepia.vert
Original file line number Diff line number Diff line change
@@ -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);
}
11 changes: 11 additions & 0 deletions impeller/fixtures/swizzle.frag
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 13 additions & 0 deletions impeller/fixtures/texture.frag
Original file line number Diff line number Diff line change
@@ -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);
}
17 changes: 17 additions & 0 deletions impeller/fixtures/texture.vert
Original file line number Diff line number Diff line change
@@ -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;
}
34 changes: 31 additions & 3 deletions impeller/playground/backend/vulkan/playground_impl_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char*> enabled_layers_c;
std::vector<const char*> 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);
}

Expand Down
2 changes: 1 addition & 1 deletion impeller/playground/playground.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions impeller/renderer/backend/vulkan/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 7 additions & 13 deletions impeller/renderer/backend/vulkan/allocator_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ AllocatorVK::AllocatorVK(std::weak_ptr<Context> context,
allocator_.reset(allocator);
supports_memoryless_textures_ =
capabilities.SupportsDeviceTransientTextures();
supports_framebuffer_fetch_ = capabilities.SupportsFramebufferFetch();
is_valid_ = true;
}

Expand All @@ -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) {
Expand All @@ -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<TextureUsageMask>(TextureUsage::kShaderRead)) {
Expand Down Expand Up @@ -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;
Expand All @@ -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 = {};
Expand Down Expand Up @@ -415,8 +410,7 @@ std::shared_ptr<Texture> AllocatorVK::OnCreateTexture(
desc, //
allocator_.get(), //
device_holder->GetDevice(), //
supports_memoryless_textures_, //
supports_framebuffer_fetch_ //
supports_memoryless_textures_ //
);
if (!source->IsValid()) {
return nullptr;
Expand Down
1 change: 0 additions & 1 deletion impeller/renderer/backend/vulkan/allocator_vk.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading