[Impeller] optimize drawImageRect with blend and matrix color filter. (#165998)
Fixes https://github.com/flutter/flutter/issues/165959
Flame uses a lot of individual calls to drawImageRect w/ blend color
filters to color the sprites. This is much slower with Impeller today,
because we convert this into:
```
saveLayer(color filter)
drawImageRect
restore
```
Instead, we can detect this case and convert to a call to drawAtlas.
drawAtlas will use the porter-duff shader which removes the saveLayer
entirely. We could do this optimization while recording, but for now its
fine to do in dispatch.
We do have a snapshot optimization that catches "normal" image usage,
but it doesn't apply in the flame case as the source rects are sampling
from a larger texture and we don't have a way to pass those through the
snapshot system.
## Before
<img
src="https://github.com/user-attachments/assets/faada042-843f-4f15-af20-9baf862f66fd"
width="200"/>
## After
<img
src="https://github.com/user-attachments/assets/c83e061c-190f-4613-a725-88e16b776c04"
width="200"/>
---------
Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_atlas_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_atlas_unittests.cc
index 3206db5..6d4d4fe 100644
--- a/engine/src/flutter/impeller/display_list/aiks_dl_atlas_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/aiks_dl_atlas_unittests.cc
@@ -3,6 +3,10 @@
// found in the LICENSE file.
#include "display_list/dl_sampling_options.h"
+#include "display_list/dl_types.h"
+#include "display_list/effects/dl_color_filter.h"
+#include "display_list/effects/image_filters/dl_matrix_image_filter.h"
+#include "display_list/geometry/dl_geometry_types.h"
#include "flutter/impeller/display_list/aiks_unittests.h"
#include "flutter/display_list/dl_blend_mode.h"
@@ -248,5 +252,70 @@
EXPECT_TRUE(geom.ShouldSkip());
}
+TEST_P(AiksTest, DrawImageRectWithBlendColorFilter) {
+ sk_sp<DlImageImpeller> texture =
+ DlImageImpeller::Make(CreateTextureForFixture("bay_bridge.jpg"));
+
+ DisplayListBuilder builder;
+ DlPaint paint = DlPaint().setColorFilter(DlColorFilter::MakeBlend(
+ DlColor::kRed().withAlphaF(0.4), DlBlendMode::kSrcOver));
+
+ DlMatrix filter_matrix = DlMatrix();
+ auto filter = flutter::DlMatrixImageFilter(filter_matrix,
+ flutter::DlImageSampling::kLinear);
+ DlPaint paint_with_filter = paint;
+ paint_with_filter.setImageFilter(&filter);
+
+ // Compare porter-duff blend modes.
+ builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
+ // Uses image filter to disable atlas conversion.
+ builder.DrawImageRect(texture, DlRect::MakeSize(texture->GetSize()),
+ DlRect::MakeLTRB(0, 0, 500, 500), {},
+ &paint_with_filter);
+
+ // Uses atlas conversion.
+ builder.Translate(600, 0);
+ builder.DrawImageRect(texture, DlRect::MakeSize(texture->GetSize()),
+ DlRect::MakeLTRB(0, 0, 500, 500), {}, &paint);
+
+ ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
+TEST_P(AiksTest, DrawImageRectWithMatrixColorFilter) {
+ sk_sp<DlImageImpeller> texture =
+ DlImageImpeller::Make(CreateTextureForFixture("bay_bridge.jpg"));
+
+ DisplayListBuilder builder;
+ static const constexpr ColorMatrix kColorInversion = {
+ .array = {
+ -1.0, 0, 0, 1.0, 0, //
+ 0, -1.0, 0, 1.0, 0, //
+ 0, 0, -1.0, 1.0, 0, //
+ 1.0, 1.0, 1.0, 1.0, 0 //
+ }};
+ DlPaint paint = DlPaint().setColorFilter(
+ DlColorFilter::MakeMatrix(kColorInversion.array));
+
+ DlMatrix filter_matrix = DlMatrix();
+ auto filter = flutter::DlMatrixImageFilter(filter_matrix,
+ flutter::DlImageSampling::kLinear);
+ DlPaint paint_with_filter = paint;
+ paint_with_filter.setImageFilter(&filter);
+
+ // Compare inverting color matrix filter.
+ builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
+ // Uses image filter to disable atlas conversion.
+ builder.DrawImageRect(texture, DlRect::MakeSize(texture->GetSize()),
+ DlRect::MakeLTRB(0, 0, 500, 500), {},
+ &paint_with_filter);
+
+ // Uses atlas conversion.
+ builder.Translate(600, 0);
+ builder.DrawImageRect(texture, DlRect::MakeSize(texture->GetSize()),
+ DlRect::MakeLTRB(0, 0, 500, 500), {}, &paint);
+
+ ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
} // namespace testing
} // namespace impeller
diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc
index 17436c1..442c54d 100644
--- a/engine/src/flutter/impeller/display_list/canvas.cc
+++ b/engine/src/flutter/impeller/display_list/canvas.cc
@@ -9,13 +9,18 @@
#include <unordered_map>
#include <utility>
+#include "display_list/effects/color_filters/dl_blend_color_filter.h"
+#include "display_list/effects/color_filters/dl_matrix_color_filter.h"
+#include "display_list/effects/dl_color_filter.h"
#include "display_list/effects/dl_color_source.h"
#include "display_list/effects/dl_image_filter.h"
+#include "display_list/image/dl_image.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/display_list/color_filter.h"
+#include "impeller/display_list/dl_atlas_geometry.h"
#include "impeller/display_list/image_filter.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/entity/contents/atlas_contents.h"
@@ -44,12 +49,19 @@
#include "impeller/geometry/color.h"
#include "impeller/geometry/constants.h"
#include "impeller/geometry/path_builder.h"
+#include "impeller/geometry/rstransform.h"
#include "impeller/renderer/command_buffer.h"
namespace impeller {
namespace {
+bool IsPipelineBlendOrMatrixFilter(const flutter::DlColorFilter* filter) {
+ return filter->type() == flutter::DlColorFilterType::kMatrix ||
+ (filter->type() == flutter::DlColorFilterType::kBlend &&
+ filter->asBlend()->mode() <= Entity::kLastPipelineBlendMode);
+}
+
static bool UseColorSourceContents(
const std::shared_ptr<VerticesGeometry>& vertices,
const Paint& paint) {
@@ -156,8 +168,10 @@
}
return std::make_unique<EntityPassTarget>(
- target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
- renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
+ target, //
+ renderer.GetDeviceCapabilities().SupportsReadFromResolve(), //
+ renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() //
+ );
}
} // namespace
@@ -315,6 +329,78 @@
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
}
+// Optimization: if the texture has a color filter that is a simple
+// porter-duff blend or matrix filter, then instead of performing a save layer
+// we should swap out the shader for the porter duff blend shader and avoid a
+// saveLayer. This can only be done for imageRects without a strict source
+// rect, as the porter duff shader does not support this feature. This
+// optimization is important for Flame.
+bool Canvas::AttemptColorFilterOptimization(
+ const std::shared_ptr<Texture>& image,
+ Rect source,
+ Rect dest,
+ const Paint& paint,
+ const SamplerDescriptor& sampler,
+ SourceRectConstraint src_rect_constraint) {
+ if (src_rect_constraint == SourceRectConstraint::kStrict || //
+ !paint.color_filter || //
+ paint.image_filter != nullptr || //
+ paint.invert_colors || //
+ paint.mask_blur_descriptor.has_value() || //
+ !IsPipelineBlendOrMatrixFilter(paint.color_filter)) {
+ return false;
+ }
+
+ if (paint.color_filter->type() == flutter::DlColorFilterType::kBlend) {
+ const flutter::DlBlendColorFilter* blend_filter =
+ paint.color_filter->asBlend();
+ DrawImageRectAtlasGeometry geometry = DrawImageRectAtlasGeometry(
+ /*texture=*/image,
+ /*source=*/source,
+ /*destination=*/dest,
+ /*color=*/skia_conversions::ToColor(blend_filter->color()),
+ /*blend_mode=*/blend_filter->mode(),
+ /*desc=*/sampler);
+
+ auto atlas_contents = std::make_shared<AtlasContents>();
+ atlas_contents->SetGeometry(&geometry);
+ atlas_contents->SetAlpha(paint.color.alpha);
+
+ Entity entity;
+ entity.SetTransform(GetCurrentTransform());
+ entity.SetBlendMode(paint.blend_mode);
+ entity.SetContents(atlas_contents);
+
+ AddRenderEntityToCurrentPass(entity);
+ } else {
+ const flutter::DlMatrixColorFilter* matrix_filter =
+ paint.color_filter->asMatrix();
+
+ DrawImageRectAtlasGeometry geometry = DrawImageRectAtlasGeometry(
+ /*texture=*/image,
+ /*source=*/source,
+ /*destination=*/dest,
+ /*color=*/Color::Khaki(), // ignored
+ /*blend_mode=*/BlendMode::kSrcOver, // ignored
+ /*desc=*/sampler);
+
+ auto atlas_contents = std::make_shared<ColorFilterAtlasContents>();
+ atlas_contents->SetGeometry(&geometry);
+ atlas_contents->SetAlpha(paint.color.alpha);
+ impeller::ColorMatrix color_matrix;
+ matrix_filter->get_matrix(color_matrix.array);
+ atlas_contents->SetMatrix(color_matrix);
+
+ Entity entity;
+ entity.SetTransform(GetCurrentTransform());
+ entity.SetBlendMode(paint.blend_mode);
+ entity.SetContents(atlas_contents);
+
+ AddRenderEntityToCurrentPass(entity);
+ }
+ return true;
+}
+
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
Size corner_radii,
const Paint& paint) {
@@ -703,8 +789,8 @@
return;
}
- const auto source = Rect::MakeSize(image->GetSize());
- const auto dest = source.Shift(offset);
+ const Rect source = Rect::MakeSize(image->GetSize());
+ const Rect dest = source.Shift(offset);
DrawImageRect(image, source, dest, paint, sampler);
}
@@ -719,8 +805,7 @@
return;
}
- auto size = image->GetSize();
-
+ ISize size = image->GetSize();
if (size.IsEmpty()) {
return;
}
@@ -730,6 +815,12 @@
if (!clipped_source) {
return;
}
+
+ if (AttemptColorFilterOptimization(image, source, dest, paint, sampler,
+ src_rect_constraint)) {
+ return;
+ }
+
if (*clipped_source != source) {
Scalar sx = dest.GetWidth() / source.GetWidth();
Scalar sy = dest.GetHeight() / source.GetHeight();
diff --git a/engine/src/flutter/impeller/display_list/canvas.h b/engine/src/flutter/impeller/display_list/canvas.h
index 1962d48..746167b 100644
--- a/engine/src/flutter/impeller/display_list/canvas.h
+++ b/engine/src/flutter/impeller/display_list/canvas.h
@@ -357,6 +357,17 @@
Size corner_radii,
const Paint& paint);
+ /// For simple DrawImageRect calls, optimize any draws with a color filter
+ /// into the corresponding atlas draw.
+ ///
+ /// Returns whether not the optimization was applied.
+ bool AttemptColorFilterOptimization(const std::shared_ptr<Texture>& image,
+ Rect source,
+ Rect dest,
+ const Paint& paint,
+ const SamplerDescriptor& sampler,
+ SourceRectConstraint src_rect_constraint);
+
RenderPass& GetCurrentRenderPass() const;
Canvas(const Canvas&) = delete;
diff --git a/engine/src/flutter/impeller/display_list/dl_atlas_geometry.cc b/engine/src/flutter/impeller/display_list/dl_atlas_geometry.cc
index 2188770..bcd473a 100644
--- a/engine/src/flutter/impeller/display_list/dl_atlas_geometry.cc
+++ b/engine/src/flutter/impeller/display_list/dl_atlas_geometry.cc
@@ -54,7 +54,7 @@
return bounding_box;
}
-std::shared_ptr<Texture> DlAtlasGeometry::GetAtlas() const {
+const std::shared_ptr<Texture>& DlAtlasGeometry::GetAtlas() const {
return atlas_;
}
@@ -69,16 +69,15 @@
VertexBuffer DlAtlasGeometry::CreateSimpleVertexBuffer(
HostBuffer& host_buffer) const {
using VS = TextureFillVertexShader;
-
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
- auto buffer_view = host_buffer.Emplace(
+ BufferView buffer_view = host_buffer.Emplace(
sizeof(VS::PerVertexData) * count_ * 6, alignof(VS::PerVertexData),
[&](uint8_t* raw_data) {
VS::PerVertexData* data =
reinterpret_cast<VS::PerVertexData*>(raw_data);
int offset = 0;
- auto texture_size = atlas_->GetSize();
+ ISize texture_size = atlas_->GetSize();
for (auto i = 0u; i < count_; i++) {
flutter::DlRect sample_rect = tex_[i];
auto points = sample_rect.GetPoints();
@@ -102,16 +101,15 @@
VertexBuffer DlAtlasGeometry::CreateBlendVertexBuffer(
HostBuffer& host_buffer) const {
using VS = PorterDuffBlendVertexShader;
-
constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
- auto buffer_view = host_buffer.Emplace(
+ BufferView buffer_view = host_buffer.Emplace(
sizeof(VS::PerVertexData) * count_ * 6, alignof(VS::PerVertexData),
[&](uint8_t* raw_data) {
VS::PerVertexData* data =
reinterpret_cast<VS::PerVertexData*>(raw_data);
int offset = 0;
- auto texture_size = atlas_->GetSize();
+ ISize texture_size = atlas_->GetSize();
for (auto i = 0u; i < count_; i++) {
flutter::DlRect sample_rect = tex_[i];
auto points = sample_rect.GetPoints();
diff --git a/engine/src/flutter/impeller/display_list/dl_atlas_geometry.h b/engine/src/flutter/impeller/display_list/dl_atlas_geometry.h
index 3c29451..ed20379 100644
--- a/engine/src/flutter/impeller/display_list/dl_atlas_geometry.h
+++ b/engine/src/flutter/impeller/display_list/dl_atlas_geometry.h
@@ -38,7 +38,7 @@
Rect ComputeBoundingBox() const override;
- std::shared_ptr<Texture> GetAtlas() const override;
+ const std::shared_ptr<Texture>& GetAtlas() const override;
const SamplerDescriptor& GetSamplerDescriptor() const override;
diff --git a/engine/src/flutter/impeller/entity/contents/atlas_contents.cc b/engine/src/flutter/impeller/entity/contents/atlas_contents.cc
index 4f1d1d4..6bd795a 100644
--- a/engine/src/flutter/impeller/entity/contents/atlas_contents.cc
+++ b/engine/src/flutter/impeller/entity/contents/atlas_contents.cc
@@ -8,6 +8,7 @@
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/content_context.h"
#include "impeller/entity/contents/filters/blend_filter_contents.h"
+#include "impeller/entity/contents/pipelines.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/texture_fill.frag.h"
#include "impeller/entity/texture_fill.vert.h"
@@ -16,6 +17,114 @@
namespace impeller {
+DrawImageRectAtlasGeometry::DrawImageRectAtlasGeometry(
+ std::shared_ptr<Texture> texture,
+ const Rect& source,
+ const Rect& destination,
+ const Color& color,
+ BlendMode blend_mode,
+ const SamplerDescriptor& desc)
+ : texture_(std::move(texture)),
+ source_(source),
+ destination_(destination),
+ color_(color),
+ blend_mode_(blend_mode),
+ desc_(desc) {}
+
+DrawImageRectAtlasGeometry::~DrawImageRectAtlasGeometry() = default;
+
+bool DrawImageRectAtlasGeometry::ShouldUseBlend() const {
+ return true;
+}
+
+bool DrawImageRectAtlasGeometry::ShouldSkip() const {
+ return false;
+}
+
+VertexBuffer DrawImageRectAtlasGeometry::CreateSimpleVertexBuffer(
+ HostBuffer& host_buffer) const {
+ using VS = TextureFillVertexShader;
+ constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
+
+ BufferView buffer_view = host_buffer.Emplace(
+ sizeof(VS::PerVertexData) * 6, alignof(VS::PerVertexData),
+ [&](uint8_t* raw_data) {
+ VS::PerVertexData* data =
+ reinterpret_cast<VS::PerVertexData*>(raw_data);
+ int offset = 0;
+ std::array<TPoint<float>, 4> destination_points =
+ destination_.GetPoints();
+ std::array<TPoint<float>, 4> texture_coords =
+ Rect::MakeSize(texture_->GetSize()).Project(source_).GetPoints();
+ for (size_t j = 0; j < 6; j++) {
+ data[offset].position = destination_points[indices[j]];
+ data[offset].texture_coords = texture_coords[indices[j]];
+ offset++;
+ }
+ });
+
+ return VertexBuffer{
+ .vertex_buffer = buffer_view,
+ .index_buffer = {},
+ .vertex_count = 6,
+ .index_type = IndexType::kNone,
+ };
+}
+
+VertexBuffer DrawImageRectAtlasGeometry::CreateBlendVertexBuffer(
+ HostBuffer& host_buffer) const {
+ using VS = PorterDuffBlendVertexShader;
+ constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3};
+
+ BufferView buffer_view = host_buffer.Emplace(
+ sizeof(VS::PerVertexData) * 6, alignof(VS::PerVertexData),
+ [&](uint8_t* raw_data) {
+ VS::PerVertexData* data =
+ reinterpret_cast<VS::PerVertexData*>(raw_data);
+ int offset = 0;
+ std::array<TPoint<float>, 4> texture_coords =
+ Rect::MakeSize(texture_->GetSize()).Project(source_).GetPoints();
+ std::array<TPoint<float>, 4> destination_points =
+ destination_.GetPoints();
+ for (size_t j = 0; j < 6; j++) {
+ data[offset].vertices = destination_points[indices[j]];
+ data[offset].texture_coords = texture_coords[indices[j]];
+ data[offset].color = color_.Premultiply();
+ offset++;
+ }
+ });
+
+ return VertexBuffer{
+ .vertex_buffer = buffer_view,
+ .index_buffer = {},
+ .vertex_count = 6,
+ .index_type = IndexType::kNone,
+ };
+}
+
+Rect DrawImageRectAtlasGeometry::ComputeBoundingBox() const {
+ return destination_;
+}
+
+const std::shared_ptr<Texture>& DrawImageRectAtlasGeometry::GetAtlas() const {
+ return texture_;
+}
+
+const SamplerDescriptor& DrawImageRectAtlasGeometry::GetSamplerDescriptor()
+ const {
+ return desc_;
+}
+
+BlendMode DrawImageRectAtlasGeometry::GetBlendMode() const {
+ return blend_mode_;
+}
+
+bool DrawImageRectAtlasGeometry::ShouldInvertBlendMode() const {
+ return false;
+}
+
+////
+
AtlasContents::AtlasContents() = default;
AtlasContents::~AtlasContents() = default;
@@ -42,11 +151,8 @@
return true;
}
- auto dst_sampler_descriptor = geometry_->GetSamplerDescriptor();
- if (renderer.GetDeviceCapabilities().SupportsDecalSamplerAddressMode()) {
- dst_sampler_descriptor.width_address_mode = SamplerAddressMode::kDecal;
- dst_sampler_descriptor.height_address_mode = SamplerAddressMode::kDecal;
- }
+ const SamplerDescriptor& dst_sampler_descriptor =
+ geometry_->GetSamplerDescriptor();
raw_ptr<const Sampler> dst_sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
@@ -56,8 +162,6 @@
using VS = TextureFillVertexShader;
using FS = TextureFillFragmentShader;
- auto dst_sampler_descriptor = geometry_->GetSamplerDescriptor();
-
raw_ptr<const Sampler> dst_sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
dst_sampler_descriptor);
@@ -97,10 +201,12 @@
pass.SetCommandLabel("DrawAtlas Blend");
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(geometry_->CreateBlendVertexBuffer(host_buffer));
- auto inverted_blend_mode =
- InvertPorterDuffBlend(blend_mode).value_or(BlendMode::kSrc);
- pass.SetPipeline(renderer.GetPorterDuffPipeline(inverted_blend_mode,
- OptionsFromPass(pass)));
+ BlendMode inverted_blend_mode =
+ geometry_->ShouldInvertBlendMode()
+ ? (InvertPorterDuffBlend(blend_mode).value_or(BlendMode::kSrc))
+ : blend_mode;
+ pass.SetPipeline(renderer.GetPorterDuffPipeline(
+ inverted_blend_mode, OptionsFromPassAndEntity(pass, entity)));
FS::FragInfo frag_info;
VS::FrameInfo frame_info;
@@ -134,7 +240,8 @@
#endif // IMPELLER_DEBUG
pass.SetVertexBuffer(geometry_->CreateBlendVertexBuffer(host_buffer));
- renderer.GetDrawVerticesUberPipeline(blend_mode, OptionsFromPass(pass));
+ renderer.GetDrawVerticesUberPipeline(blend_mode,
+ OptionsFromPassAndEntity(pass, entity));
FS::BindTextureSampler(pass, geometry_->GetAtlas(), dst_sampler);
VUS::FrameInfo frame_info;
@@ -157,4 +264,83 @@
return pass.Draw().ok();
}
+///////////////
+
+ColorFilterAtlasContents::ColorFilterAtlasContents() = default;
+
+ColorFilterAtlasContents::~ColorFilterAtlasContents() = default;
+
+std::optional<Rect> ColorFilterAtlasContents::GetCoverage(
+ const Entity& entity) const {
+ if (!geometry_) {
+ return std::nullopt;
+ }
+ return geometry_->ComputeBoundingBox().TransformBounds(entity.GetTransform());
+}
+
+void ColorFilterAtlasContents::SetGeometry(AtlasGeometry* geometry) {
+ geometry_ = geometry;
+}
+
+void ColorFilterAtlasContents::SetAlpha(Scalar alpha) {
+ alpha_ = alpha;
+}
+
+void ColorFilterAtlasContents::SetMatrix(ColorMatrix matrix) {
+ matrix_ = matrix;
+}
+
+bool ColorFilterAtlasContents::Render(const ContentContext& renderer,
+ const Entity& entity,
+ RenderPass& pass) const {
+ if (geometry_->ShouldSkip() || alpha_ <= 0.0) {
+ return true;
+ }
+
+ const SamplerDescriptor& dst_sampler_descriptor =
+ geometry_->GetSamplerDescriptor();
+
+ raw_ptr<const Sampler> dst_sampler =
+ renderer.GetContext()->GetSamplerLibrary()->GetSampler(
+ dst_sampler_descriptor);
+
+ auto& host_buffer = renderer.GetTransientsBuffer();
+
+ using VS = ColorMatrixColorFilterPipeline::VertexShader;
+ using FS = ColorMatrixColorFilterPipeline::FragmentShader;
+
+#ifdef IMPELLER_DEBUG
+ pass.SetCommandLabel("Atlas ColorFilter");
+#endif // IMPELLER_DEBUG
+ pass.SetVertexBuffer(geometry_->CreateSimpleVertexBuffer(host_buffer));
+ pass.SetPipeline(
+ renderer.GetColorMatrixColorFilterPipeline(OptionsFromPass(pass)));
+
+ FS::FragInfo frag_info;
+ VS::FrameInfo frame_info;
+
+ FS::BindInputTexture(pass, geometry_->GetAtlas(), dst_sampler);
+ frame_info.texture_sampler_y_coord_scale =
+ geometry_->GetAtlas()->GetYCoordScale();
+
+ frag_info.input_alpha = 1;
+ frag_info.output_alpha = alpha_;
+ const float* matrix = matrix_.array;
+ frag_info.color_v = Vector4(matrix[4], matrix[9], matrix[14], matrix[19]);
+ frag_info.color_m = Matrix(matrix[0], matrix[5], matrix[10], matrix[15], //
+ matrix[1], matrix[6], matrix[11], matrix[16], //
+ matrix[2], matrix[7], matrix[12], matrix[17], //
+ matrix[3], matrix[8], matrix[13], matrix[18] //
+ );
+
+ FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
+
+ frame_info.mvp = entity.GetShaderTransform(pass);
+
+ auto uniform_view = host_buffer.EmplaceUniform(frame_info);
+ VS::BindFrameInfo(pass, uniform_view);
+
+ return pass.Draw().ok();
+}
+
} // namespace impeller
diff --git a/engine/src/flutter/impeller/entity/contents/atlas_contents.h b/engine/src/flutter/impeller/entity/contents/atlas_contents.h
index a0c0046..f34c85f 100644
--- a/engine/src/flutter/impeller/entity/contents/atlas_contents.h
+++ b/engine/src/flutter/impeller/entity/contents/atlas_contents.h
@@ -30,11 +30,52 @@
virtual Rect ComputeBoundingBox() const = 0;
- virtual std::shared_ptr<Texture> GetAtlas() const = 0;
+ virtual const std::shared_ptr<Texture>& GetAtlas() const = 0;
virtual const SamplerDescriptor& GetSamplerDescriptor() const = 0;
virtual BlendMode GetBlendMode() const = 0;
+
+ virtual bool ShouldInvertBlendMode() const { return true; }
+};
+
+/// @brief An atlas geometry that adapts for drawImageRect.
+class DrawImageRectAtlasGeometry : public AtlasGeometry {
+ public:
+ DrawImageRectAtlasGeometry(std::shared_ptr<Texture> texture,
+ const Rect& source,
+ const Rect& destination,
+ const Color& color,
+ BlendMode blend_mode,
+ const SamplerDescriptor& desc);
+
+ ~DrawImageRectAtlasGeometry();
+
+ bool ShouldUseBlend() const override;
+
+ bool ShouldSkip() const override;
+
+ VertexBuffer CreateSimpleVertexBuffer(HostBuffer& host_buffer) const override;
+
+ VertexBuffer CreateBlendVertexBuffer(HostBuffer& host_buffer) const override;
+
+ Rect ComputeBoundingBox() const override;
+
+ const std::shared_ptr<Texture>& GetAtlas() const override;
+
+ const SamplerDescriptor& GetSamplerDescriptor() const override;
+
+ BlendMode GetBlendMode() const override;
+
+ bool ShouldInvertBlendMode() const override;
+
+ private:
+ const std::shared_ptr<Texture> texture_;
+ const Rect source_;
+ const Rect destination_;
+ const Color color_;
+ const BlendMode blend_mode_;
+ const SamplerDescriptor desc_;
};
class AtlasContents final : public Contents {
@@ -64,6 +105,41 @@
AtlasContents& operator=(const AtlasContents&) = delete;
};
+/// A specialized atlas class for applying a color matrix filter to a
+/// drawImageRect call.
+class ColorFilterAtlasContents final : public Contents {
+ public:
+ explicit ColorFilterAtlasContents();
+
+ ~ColorFilterAtlasContents() override;
+
+ void SetGeometry(AtlasGeometry* geometry);
+
+ void SetAlpha(Scalar alpha);
+
+ void SetMatrix(ColorMatrix matrix);
+
+ // |Contents|
+ std::optional<Rect> GetCoverage(const Entity& entity) const override;
+
+ // |Contents|
+ bool Render(const ContentContext& renderer,
+ const Entity& entity,
+ RenderPass& pass) const override;
+
+ private:
+ // These contents are created temporarily on the stack and never stored.
+ // The referenced geometry is also stack allocated and will be de-allocated
+ // after the contents are.
+ AtlasGeometry* geometry_ = nullptr;
+ ColorMatrix matrix_;
+ Scalar alpha_ = 1.0;
+
+ ColorFilterAtlasContents(const ColorFilterAtlasContents&) = delete;
+
+ ColorFilterAtlasContents& operator=(const ColorFilterAtlasContents&) = delete;
+};
+
} // namespace impeller
#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_ATLAS_CONTENTS_H_
diff --git a/engine/src/flutter/impeller/entity/contents/filters/color_matrix_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/color_matrix_filter_contents.cc
index 0bcf9d8..337512f 100644
--- a/engine/src/flutter/impeller/entity/contents/filters/color_matrix_filter_contents.cc
+++ b/engine/src/flutter/impeller/entity/contents/filters/color_matrix_filter_contents.cc
@@ -64,10 +64,10 @@
auto size = input_snapshot->texture->GetSize();
std::array<VS::PerVertexData, 4> vertices = {
- VS::PerVertexData{Point(0, 0)},
- VS::PerVertexData{Point(1, 0)},
- VS::PerVertexData{Point(0, 1)},
- VS::PerVertexData{Point(1, 1)},
+ VS::PerVertexData{Point(0, 0), Point(0, 0)},
+ VS::PerVertexData{Point(1, 0), Point(1, 0)},
+ VS::PerVertexData{Point(0, 1), Point(0, 1)},
+ VS::PerVertexData{Point(1, 1), Point(1, 1)},
};
auto& host_buffer = renderer.GetTransientsBuffer();
pass.SetVertexBuffer(
@@ -84,23 +84,21 @@
FS::FragInfo frag_info;
const float* matrix = color_matrix.array;
frag_info.color_v = Vector4(matrix[4], matrix[9], matrix[14], matrix[19]);
- // clang-format off
- frag_info.color_m = Matrix(
- matrix[0], matrix[5], matrix[10], matrix[15],
- matrix[1], matrix[6], matrix[11], matrix[16],
- matrix[2], matrix[7], matrix[12], matrix[17],
- matrix[3], matrix[8], matrix[13], matrix[18]
+ frag_info.color_m = Matrix(matrix[0], matrix[5], matrix[10], matrix[15], //
+ matrix[1], matrix[6], matrix[11], matrix[16], //
+ matrix[2], matrix[7], matrix[12], matrix[17], //
+ matrix[3], matrix[8], matrix[13], matrix[18] //
);
- // clang-format on
frag_info.input_alpha =
absorb_opacity == ColorFilterContents::AbsorbOpacity::kYes
? input_snapshot->opacity
: 1.0f;
+ frag_info.output_alpha = 1;
+
raw_ptr<const Sampler> sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler({});
FS::BindInputTexture(pass, input_snapshot->texture, sampler);
FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info));
-
VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info));
return pass.Draw().ok();
diff --git a/engine/src/flutter/impeller/entity/contents/pipelines.h b/engine/src/flutter/impeller/entity/contents/pipelines.h
index 290e90c..f3998a7 100644
--- a/engine/src/flutter/impeller/entity/contents/pipelines.h
+++ b/engine/src/flutter/impeller/entity/contents/pipelines.h
@@ -99,7 +99,7 @@
using BlendSoftLightPipeline = AdvancedBlendPipelineHandle;
using BorderMaskBlurPipeline = RenderPipelineHandle<FilterPositionUvVertexShader, BorderMaskBlurFragmentShader>;
using ClipPipeline = RenderPipelineHandle<ClipVertexShader, ClipFragmentShader>;
-using ColorMatrixColorFilterPipeline = RenderPipelineHandle<FilterPositionVertexShader, ColorMatrixColorFilterFragmentShader>;
+using ColorMatrixColorFilterPipeline = RenderPipelineHandle<FilterPositionUvVertexShader, ColorMatrixColorFilterFragmentShader>;
using ConicalGradientFillConicalPipeline = GradientPipelineHandle<ConicalGradientFillConicalFragmentShader>;
using ConicalGradientFillRadialPipeline = GradientPipelineHandle<ConicalGradientFillRadialFragmentShader>;
using ConicalGradientFillStripPipeline = GradientPipelineHandle<ConicalGradientFillStripFragmentShader>;
diff --git a/engine/src/flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag b/engine/src/flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag
index b8f2da6..c9e74b1 100644
--- a/engine/src/flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag
+++ b/engine/src/flutter/impeller/entity/shaders/filters/color_matrix_color_filter.frag
@@ -35,6 +35,7 @@
mat4 color_m;
f16vec4 color_v;
float16_t input_alpha;
+ float16_t output_alpha;
}
frag_info;
@@ -54,5 +55,5 @@
float16_t(0), float16_t(1.0));
// premultiply the outputs
- frag_color = f16vec4(color.rgb * color.a, color.a);
+ frag_color = IPHalfPremultiply(color) * frag_info.output_alpha;
}
diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json
index ed4cb61..83636d9 100644
--- a/engine/src/flutter/impeller/tools/malioc.json
+++ b/engine/src/flutter/impeller/tools/malioc.json
@@ -409,13 +409,11 @@
"performance": {
"longest_path_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"longest_path_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.0625,
0.0625,
0.0,
@@ -433,13 +431,11 @@
],
"shortest_path_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"shortest_path_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.0625,
0.0625,
0.0,
@@ -448,13 +444,11 @@
],
"total_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"total_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.0625,
0.0625,
0.0,
@@ -464,8 +458,8 @@
},
"stack_spill_bytes": 0,
"thread_occupancy": 100,
- "uniform_registers_used": 14,
- "work_registers_used": 10
+ "uniform_registers_used": 16,
+ "work_registers_used": 9
}
}
}
@@ -2232,13 +2226,11 @@
"performance": {
"longest_path_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"longest_path_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.09375,
0.0625,
0.0,
@@ -2256,13 +2248,11 @@
],
"shortest_path_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"shortest_path_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.0625,
0.0625,
0.0,
@@ -2271,13 +2261,11 @@
],
"total_bound_pipelines": [
"arith_total",
- "arith_fma",
- "varying",
- "texture"
+ "arith_fma"
],
"total_cycles": [
- 0.25,
- 0.25,
+ 0.28125,
+ 0.28125,
0.09375,
0.0625,
0.0,
@@ -2305,7 +2293,7 @@
"arithmetic"
],
"longest_path_cycles": [
- 2.640000104904175,
+ 2.9700000286102295,
1.0,
1.0
],
@@ -2318,7 +2306,7 @@
"arithmetic"
],
"shortest_path_cycles": [
- 2.640000104904175,
+ 2.9700000286102295,
1.0,
1.0
],
@@ -2326,7 +2314,7 @@
"arithmetic"
],
"total_cycles": [
- 3.0,
+ 3.3333332538604736,
1.0,
1.0
]