From fd9fdbf6b8aaa0e4e67d224488d9cd6b989a830e Mon Sep 17 00:00:00 2001 From: Dan Field Date: Thu, 18 Aug 2022 16:12:11 -0700 Subject: [PATCH] Support deferred GPU images in ImageShaders (flutter/engine#35323) --- .../src/flutter/display_list/display_list.h | 1 + .../display_list/display_list_builder.cc | 7 ++ .../display_list/display_list_color_source.cc | 9 ++ .../display_list/display_list_color_source.h | 106 +++++++++++++++++- .../display_list_color_source_unittests.cc | 49 ++++++++ .../flutter/display_list/display_list_image.h | 11 ++ .../display_list/display_list_image_skia.cc | 5 + .../display_list/display_list_image_skia.h | 3 + .../flutter/display_list/display_list_ops.h | 23 ++++ .../flutter/flow/layers/shader_mask_layer.cc | 12 +- .../flutter/flow/layers/shader_mask_layer.h | 10 +- .../layers/shader_mask_layer_unittests.cc | 38 ++++--- .../display_list/display_list_dispatcher.cc | 1 + .../display_list_image_impeller.cc | 6 + .../display_list_image_impeller.h | 3 + .../lib/ui/compositing/scene_builder.cc | 3 +- .../display_list_deferred_image_gpu.cc | 5 + .../display_list_deferred_image_gpu.h | 3 + .../lib/ui/painting/display_list_image_gpu.cc | 8 ++ .../lib/ui/painting/display_list_image_gpu.h | 3 + .../lib/ui/painting/fragment_program.cc | 12 +- .../lib/ui/painting/fragment_shader.cc | 10 +- .../flutter/lib/ui/painting/fragment_shader.h | 9 +- .../flutter/lib/ui/painting/image_shader.cc | 6 - engine/src/flutter/lib/ui/painting/paint.cc | 9 +- .../testing/dart/fragment_shader_test.dart | 32 ++++++ .../testing/dart/image_shader_test.dart | 10 +- 27 files changed, 334 insertions(+), 60 deletions(-) diff --git a/engine/src/flutter/display_list/display_list.h b/engine/src/flutter/display_list/display_list.h index 3731a575b8c..8eab4494ef9 100644 --- a/engine/src/flutter/display_list/display_list.h +++ b/engine/src/flutter/display_list/display_list.h @@ -90,6 +90,7 @@ namespace flutter { V(SetPodColorSource) \ V(SetSkColorSource) \ V(SetImageColorSource) \ + V(SetRuntimeEffectColorSource) \ \ V(ClearImageFilter) \ V(SetPodImageFilter) \ diff --git a/engine/src/flutter/display_list/display_list_builder.cc b/engine/src/flutter/display_list/display_list_builder.cc index 4b7c1c0e81c..788938e4b05 100644 --- a/engine/src/flutter/display_list/display_list_builder.cc +++ b/engine/src/flutter/display_list/display_list_builder.cc @@ -6,6 +6,7 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_color_source.h" #include "flutter/display_list/display_list_ops.h" namespace flutter { @@ -184,6 +185,12 @@ void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { new (pod) DlSweepGradientColorSource(sweep); break; } + case DlColorSourceType::kRuntimeEffect: { + const DlRuntimeEffectColorSource* effect = source->asRuntimeEffect(); + FML_DCHECK(effect); + Push(0, 0, effect); + break; + } case DlColorSourceType::kUnknown: Push(0, 0, source->skia_object()); break; diff --git a/engine/src/flutter/display_list/display_list_color_source.cc b/engine/src/flutter/display_list/display_list_color_source.cc index c8ff65b2aca..4348abbe8b5 100644 --- a/engine/src/flutter/display_list/display_list_color_source.cc +++ b/engine/src/flutter/display_list/display_list_color_source.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/display_list/display_list_color_source.h" +#include "display_list_color_source.h" #include "flutter/display_list/display_list_sampling_options.h" namespace flutter { @@ -128,4 +129,12 @@ std::shared_ptr DlColorSource::MakeSweep( return std::move(ret); } +std::shared_ptr DlColorSource::MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data) { + return std::make_shared( + std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); +} + } // namespace flutter diff --git a/engine/src/flutter/display_list/display_list_color_source.h b/engine/src/flutter/display_list/display_list_color_source.h index 97c30371491..d38217bd25f 100644 --- a/engine/src/flutter/display_list/display_list_color_source.h +++ b/engine/src/flutter/display_list/display_list_color_source.h @@ -15,6 +15,7 @@ #include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkShader.h" #include "third_party/skia/include/effects/SkGradientShader.h" +#include "third_party/skia/include/effects/SkRuntimeEffect.h" namespace flutter { @@ -24,6 +25,7 @@ class DlLinearGradientColorSource; class DlRadialGradientColorSource; class DlConicalGradientColorSource; class DlSweepGradientColorSource; +class DlRuntimeEffectColorSource; class DlUnknownColorSource; // The DisplayList ColorSource class. This class implements all of the @@ -43,6 +45,7 @@ enum class DlColorSourceType { kRadialGradient, kConicalGradient, kSweepGradient, + kRuntimeEffect, kUnknown }; @@ -104,6 +107,11 @@ class DlColorSource DlTileMode tile_mode, const SkMatrix* matrix = nullptr); + static std::shared_ptr MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data); + virtual bool is_opaque() const = 0; virtual std::shared_ptr with_sampling( @@ -143,6 +151,19 @@ class DlColorSource return nullptr; } + virtual const DlRuntimeEffectColorSource* asRuntimeEffect() const { + return nullptr; + } + + // If this filter contains images, specifies the owning context for those + // images. + // Images with a DlImage::OwningContext::kRaster must only call skia_object + // on the raster task runner. + // A nullopt return means there is no image. + virtual std::optional owning_context() const { + return std::nullopt; + } + protected: DlColorSource() = default; @@ -229,12 +250,10 @@ class DlImageColorSource final : public SkRefCnt, DlColorSourceType type() const override { return DlColorSourceType::kImage; } size_t size() const override { return sizeof(*this); } - bool is_opaque() const override { - // TODO(109286): Consider implementing 'isOpaque' on 'DlImage'. - if (!image_->skia_image()) { - return false; - } - return image_->skia_image()->isOpaque(); + bool is_opaque() const override { return image_->isOpaque(); } + + std::optional owning_context() const override { + return image_->owning_context(); } sk_sp image() const { return image_; } @@ -636,6 +655,81 @@ class DlSweepGradientColorSource final : public DlGradientColorSourceBase { FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlSweepGradientColorSource); }; +class DlRuntimeEffectColorSource final : public DlColorSource { + public: + DlRuntimeEffectColorSource( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data) + : runtime_effect_(std::move(runtime_effect)), + samplers_(std::move(samplers)), + uniform_data_(std::move(uniform_data)) {} + + const DlRuntimeEffectColorSource* asRuntimeEffect() const override { + return this; + } + + std::shared_ptr shared() const override { + return std::make_shared( + runtime_effect_, samplers_, uniform_data_); + } + + DlColorSourceType type() const override { + return DlColorSourceType::kRuntimeEffect; + } + size_t size() const override { return sizeof(*this); } + + bool is_opaque() const override { return false; } + + const sk_sp runtime_effect() const { + return runtime_effect_; + } + const std::vector> samplers() const { + return samplers_; + } + const sk_sp uniform_data() const { return uniform_data_; } + + sk_sp skia_object() const override { + if (!runtime_effect_) { + return nullptr; + } + std::vector> sk_samplers(samplers_.size()); + for (size_t i = 0; i < samplers_.size(); i++) { + sk_samplers[i] = samplers_[i]->skia_object(); + } + return runtime_effect_->makeShader(uniform_data_, sk_samplers.data(), + sk_samplers.size()); + } + + protected: + bool equals_(DlColorSource const& other) const override { + FML_DCHECK(other.type() == DlColorSourceType::kRuntimeEffect); + auto that = static_cast(&other); + if (runtime_effect_ != that->runtime_effect_) { + return false; + } + if (uniform_data_ != that->uniform_data_) { + return false; + } + if (samplers_.size() != that->samplers_.size()) { + return false; + } + for (size_t i = 0; i < samplers_.size(); i++) { + if (samplers_[i] != that->samplers_[i]) { + return false; + } + } + return true; + } + + private: + sk_sp runtime_effect_; + std::vector> samplers_; + sk_sp uniform_data_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRuntimeEffectColorSource); +}; + class DlUnknownColorSource final : public DlColorSource { public: DlUnknownColorSource(sk_sp shader) : sk_shader_(shader) {} diff --git a/engine/src/flutter/display_list/display_list_color_source_unittests.cc b/engine/src/flutter/display_list/display_list_color_source_unittests.cc index 9a23dfe415f..351d94a4f08 100644 --- a/engine/src/flutter/display_list/display_list_color_source_unittests.cc +++ b/engine/src/flutter/display_list/display_list_color_source_unittests.cc @@ -8,6 +8,7 @@ #include "flutter/display_list/display_list_image.h" #include "flutter/display_list/display_list_sampling_options.h" #include "flutter/display_list/types.h" +#include "third_party/skia/include/core/SkString.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { @@ -27,6 +28,15 @@ static sk_sp MakeTestImage(int w, int h, SkColor color) { return DlImage::Make(surface->makeImageSnapshot()); } +static const sk_sp kTestRuntimeEffect1 = + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(0); }")) + .effect; +static const sk_sp kTestRuntimeEffect2 = + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(1); }")) + .effect; + static const sk_sp kTestImage1 = MakeTestImage(10, 10, SK_ColorGREEN); static const sk_sp kTestAlphaImage1 = MakeTestImage(10, 10, SK_ColorTRANSPARENT); @@ -138,6 +148,7 @@ TEST(DisplayListColorSource, FromSkiaImageShader) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaLinearGradient) { @@ -177,6 +188,7 @@ TEST(DisplayListColorSource, FromSkiaRadialGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaConicalGradient) { @@ -197,6 +209,7 @@ TEST(DisplayListColorSource, FromSkiaConicalGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaSweepGradient) { @@ -217,6 +230,7 @@ TEST(DisplayListColorSource, FromSkiaSweepGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaUnrecognizedShader) { @@ -231,6 +245,7 @@ TEST(DisplayListColorSource, FromSkiaUnrecognizedShader) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ColorConstructor) { @@ -253,6 +268,7 @@ TEST(DisplayListColorSource, ColorAsColor) { ASSERT_EQ(source.asRadialGradient(), nullptr); ASSERT_EQ(source.asConicalGradient(), nullptr); ASSERT_EQ(source.asSweepGradient(), nullptr); + ASSERT_EQ(source.asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ColorContents) { @@ -400,6 +416,7 @@ TEST(DisplayListColorSource, LinearGradientAsLinear) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, LinearGradientContents) { @@ -518,6 +535,7 @@ TEST(DisplayListColorSource, RadialGradientAsRadial) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, RadialGradientContents) { @@ -636,6 +654,7 @@ TEST(DisplayListColorSource, ConicalGradientAsConical) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ConicalGradientContents) { @@ -770,6 +789,7 @@ TEST(DisplayListColorSource, SweepGradientAsSweep) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, SweepGradientContents) { @@ -888,6 +908,7 @@ TEST(DisplayListColorSource, UnknownAsNone) { ASSERT_EQ(source.asRadialGradient(), nullptr); ASSERT_EQ(source.asConicalGradient(), nullptr); ASSERT_EQ(source.asSweepGradient(), nullptr); + ASSERT_EQ(source.asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, UnknownContents) { @@ -916,5 +937,33 @@ TEST(DisplayListColorSource, UnknownNotEquals) { TestNotEquals(source1, source2, "SkShader differs"); } +TEST(DisplayListColorSource, RuntimeEffect) { + std::shared_ptr source1 = + DlColorSource::MakeRuntimeEffect(kTestRuntimeEffect1, {}, nullptr); + std::shared_ptr source2 = + DlColorSource::MakeRuntimeEffect(kTestRuntimeEffect2, {}, nullptr); + std::shared_ptr source3 = + DlColorSource::MakeRuntimeEffect(nullptr, {}, nullptr); + + ASSERT_EQ(source1->type(), DlColorSourceType::kRuntimeEffect); + ASSERT_EQ(source1->asRuntimeEffect(), source1.get()); + ASSERT_NE(source2->asRuntimeEffect(), source1.get()); + + ASSERT_EQ(source1->asImage(), nullptr); + ASSERT_EQ(source1->asColor(), nullptr); + ASSERT_EQ(source1->asLinearGradient(), nullptr); + ASSERT_EQ(source1->asRadialGradient(), nullptr); + ASSERT_EQ(source1->asConicalGradient(), nullptr); + ASSERT_EQ(source1->asSweepGradient(), nullptr); + + ASSERT_NE(source1->skia_object(), nullptr); + ASSERT_EQ(source3->skia_object(), nullptr); + + TestEquals(source1, source1); + TestEquals(source3, source3); + TestNotEquals(source1, source2, "SkRuntimeEffect differs"); + TestNotEquals(source2, source3, "SkRuntimeEffect differs"); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/display_list/display_list_image.h b/engine/src/flutter/display_list/display_list_image.h index 6f22b31e29a..19ae1d935bf 100644 --- a/engine/src/flutter/display_list/display_list_image.h +++ b/engine/src/flutter/display_list/display_list_image.h @@ -54,6 +54,17 @@ class DlImage : public SkRefCnt { /// virtual std::shared_ptr impeller_texture() const = 0; + //---------------------------------------------------------------------------- + /// @brief If the pixel format of this image ignores alpha, this returns + /// true. This method might conservatively return false when it + /// cannot guarnatee an opaque image, for example when the pixel + /// format of the image supports alpha but the image is made up of + /// entirely opaque pixels. + /// + /// @return True if the pixel format of this image ignores alpha. + /// + virtual bool isOpaque() const = 0; + virtual bool isTextureBacked() const = 0; //---------------------------------------------------------------------------- diff --git a/engine/src/flutter/display_list/display_list_image_skia.cc b/engine/src/flutter/display_list/display_list_image_skia.cc index 2e2dddb5cba..07fd84143b8 100644 --- a/engine/src/flutter/display_list/display_list_image_skia.cc +++ b/engine/src/flutter/display_list/display_list_image_skia.cc @@ -21,6 +21,11 @@ std::shared_ptr DlImageSkia::impeller_texture() const { return nullptr; } +// |DlImage| +bool DlImageSkia::isOpaque() const { + return image_ ? image_->isOpaque() : false; +} + // |DlImage| bool DlImageSkia::isTextureBacked() const { return image_ ? image_->isTextureBacked() : false; diff --git a/engine/src/flutter/display_list/display_list_image_skia.h b/engine/src/flutter/display_list/display_list_image_skia.h index 70f98718230..7e76de802aa 100644 --- a/engine/src/flutter/display_list/display_list_image_skia.h +++ b/engine/src/flutter/display_list/display_list_image_skia.h @@ -23,6 +23,9 @@ class DlImageSkia final : public DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/engine/src/flutter/display_list/display_list_ops.h b/engine/src/flutter/display_list/display_list_ops.h index 052e7c9343b..061f29365df 100644 --- a/engine/src/flutter/display_list/display_list_ops.h +++ b/engine/src/flutter/display_list/display_list_ops.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_DISPLAY_LIST_DISPLAY_LIST_OPS_H_ #define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_OPS_H_ +#include "display_list_color_source.h" #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" #include "flutter/display_list/display_list_dispatcher.h" @@ -246,6 +247,28 @@ struct SetImageColorSourceOp : DLOp { } }; +// 56 bytes: 4 byte header, 4 byte padding, 8 for vtable, 8 * 2 for sk_sps, 24 +// for the std::vector. +struct SetRuntimeEffectColorSourceOp : DLOp { + static const auto kType = DisplayListOpType::kSetRuntimeEffectColorSource; + + SetRuntimeEffectColorSourceOp(const DlRuntimeEffectColorSource* source) + : source(source->runtime_effect(), + source->samplers(), + source->uniform_data()) {} + + const DlRuntimeEffectColorSource source; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setColorSource(&source); + } + + DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const { + return (source == other->source) ? DisplayListCompare::kEqual + : DisplayListCompare::kNotEqual; + } +}; + // 4 byte header + 16 byte payload uses 24 total bytes (4 bytes unused) struct SetSharedImageFilterOp : DLOp { static const auto kType = DisplayListOpType::kSetSharedImageFilter; diff --git a/engine/src/flutter/flow/layers/shader_mask_layer.cc b/engine/src/flutter/flow/layers/shader_mask_layer.cc index d39d508734c..b7dd47e48d9 100644 --- a/engine/src/flutter/flow/layers/shader_mask_layer.cc +++ b/engine/src/flutter/flow/layers/shader_mask_layer.cc @@ -7,12 +7,12 @@ namespace flutter { -ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, +ShaderMaskLayer::ShaderMaskLayer(std::shared_ptr shader, const SkRect& mask_rect, - SkBlendMode blend_mode) + DlBlendMode blend_mode) : CacheableContainerLayer( RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer), - shader_(shader), + shader_(std::move(shader)), mask_rect_(mask_rect), blend_mode_(blend_mode) {} @@ -60,8 +60,10 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { PaintChildren(context); SkPaint paint; - paint.setBlendMode(blend_mode_); - paint.setShader(shader_); + paint.setBlendMode(ToSk(blend_mode_)); + if (shader_) { + paint.setShader(shader_->skia_object()); + } context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); context.leaf_nodes_canvas->drawRect( SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); diff --git a/engine/src/flutter/flow/layers/shader_mask_layer.h b/engine/src/flutter/flow/layers/shader_mask_layer.h index 0dd44e3dede..63ac4421d63 100644 --- a/engine/src/flutter/flow/layers/shader_mask_layer.h +++ b/engine/src/flutter/flow/layers/shader_mask_layer.h @@ -5,16 +5,16 @@ #ifndef FLUTTER_FLOW_LAYERS_SHADER_MASK_LAYER_H_ #define FLUTTER_FLOW_LAYERS_SHADER_MASK_LAYER_H_ +#include "flutter/display_list/display_list_color_source.h" #include "flutter/flow/layers/cacheable_layer.h" -#include "third_party/skia/include/core/SkShader.h" namespace flutter { class ShaderMaskLayer : public CacheableContainerLayer { public: - ShaderMaskLayer(sk_sp shader, + ShaderMaskLayer(std::shared_ptr shader, const SkRect& mask_rect, - SkBlendMode blend_mode); + DlBlendMode blend_mode); void Diff(DiffContext* context, const Layer* old_layer) override; @@ -23,9 +23,9 @@ class ShaderMaskLayer : public CacheableContainerLayer { void Paint(PaintContext& context) const override; private: - sk_sp shader_; + std::shared_ptr shader_; SkRect mask_rect_; - SkBlendMode blend_mode_; + DlBlendMode blend_mode_; FML_DISALLOW_COPY_AND_ASSIGN(ShaderMaskLayer); }; diff --git a/engine/src/flutter/flow/layers/shader_mask_layer_unittests.cc b/engine/src/flutter/flow/layers/shader_mask_layer_unittests.cc index d57a2753d4b..5f6c38b76a0 100644 --- a/engine/src/flutter/flow/layers/shader_mask_layer_unittests.cc +++ b/engine/src/flutter/flow/layers/shader_mask_layer_unittests.cc @@ -23,7 +23,7 @@ using ShaderMaskLayerTest = LayerTest; #ifndef NDEBUG TEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) { auto layer = - std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + std::make_shared(nullptr, kEmptyRect, DlBlendMode::kSrc); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -39,7 +39,7 @@ TEST_F(ShaderMaskLayerTest, PaintBeforePrerollDies) { const SkPath child_path = SkPath().addRect(child_bounds); auto mock_layer = std::make_shared(child_path); auto layer = - std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + std::make_shared(nullptr, kEmptyRect, DlBlendMode::kSrc); layer->Add(mock_layer); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -57,7 +57,7 @@ TEST_F(ShaderMaskLayerTest, EmptyFilter) { const SkPaint child_paint = SkPaint(SkColors::kYellow); auto mock_layer = std::make_shared(child_path, child_paint); auto layer = std::make_shared(nullptr, layer_bounds, - SkBlendMode::kSrc); + DlBlendMode::kSrc); layer->Add(mock_layer); layer->Preroll(preroll_context(), initial_transform); @@ -98,9 +98,10 @@ TEST_F(ShaderMaskLayerTest, SimpleFilter) { const SkPaint child_paint = SkPaint(SkColors::kYellow); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer); layer->Preroll(preroll_context(), initial_transform); @@ -142,10 +143,11 @@ TEST_F(ShaderMaskLayerTest, MultipleChildren) { const SkPaint child_paint2 = SkPaint(SkColors::kCyan); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -197,14 +199,16 @@ TEST_F(ShaderMaskLayerTest, Nested) { const SkPaint child_paint2 = SkPaint(SkColors::kCyan); auto layer_filter1 = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter1 = DlColorSource::From(layer_filter1); auto layer_filter2 = SkPerlinNoiseShader::MakeFractalNoise(2.0f, 2.0f, 2, 2.0f); + auto dl_filter2 = DlColorSource::From(layer_filter2); auto mock_layer1 = std::make_shared(child_path1, child_paint1); auto mock_layer2 = std::make_shared(child_path2, child_paint2); - auto layer1 = std::make_shared(layer_filter1, layer_bounds, - SkBlendMode::kSrc); - auto layer2 = std::make_shared(layer_filter2, layer_bounds, - SkBlendMode::kSrc); + auto layer1 = std::make_shared(dl_filter1, layer_bounds, + DlBlendMode::kSrc); + auto layer2 = std::make_shared(dl_filter2, layer_bounds, + DlBlendMode::kSrc); layer2->Add(mock_layer2); layer1->Add(mock_layer1); layer1->Add(layer2); @@ -269,8 +273,9 @@ TEST_F(ShaderMaskLayerTest, Readback) { const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto dl_filter = DlColorSource::From(layer_filter); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); // ShaderMaskLayer does not read from surface preroll_context()->surface_needs_readback = false; @@ -289,13 +294,14 @@ TEST_F(ShaderMaskLayerTest, Readback) { TEST_F(ShaderMaskLayerTest, LayerCached) { auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); SkPaint paint; const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); auto initial_transform = SkMatrix::Translate(50.0, 25.5); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer); SkMatrix cache_ctm = initial_transform; @@ -344,7 +350,7 @@ TEST_F(ShaderMaskLayerTest, OpacityInheritance) { auto mock_layer = MockLayer::Make(child_path); const SkRect mask_rect = SkRect::MakeLTRB(10, 10, 20, 20); auto shader_mask_layer = - std::make_shared(nullptr, mask_rect, SkBlendMode::kSrc); + std::make_shared(nullptr, mask_rect, DlBlendMode::kSrc); shader_mask_layer->Add(mock_layer); // ShaderMaskLayers can always support opacity despite incompatible children diff --git a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc index f483856b6a4..0fbd95d891b 100644 --- a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc @@ -397,6 +397,7 @@ void DisplayListDispatcher::setColorSource( return; } case flutter::DlColorSourceType::kConicalGradient: + case flutter::DlColorSourceType::kRuntimeEffect: case flutter::DlColorSourceType::kUnknown: UNIMPLEMENTED; break; diff --git a/engine/src/flutter/impeller/display_list/display_list_image_impeller.cc b/engine/src/flutter/impeller/display_list/display_list_image_impeller.cc index b4627c0da67..f964010b9ae 100644 --- a/engine/src/flutter/impeller/display_list/display_list_image_impeller.cc +++ b/engine/src/flutter/impeller/display_list/display_list_image_impeller.cc @@ -29,6 +29,12 @@ std::shared_ptr DlImageImpeller::impeller_texture() const { return texture_; } +// |DlImage| +bool DlImageImpeller::isOpaque() const { + // Impeller doesn't currently implement opaque alpha types. + return false; +} + // |DlImage| bool DlImageImpeller::isTextureBacked() const { // Impeller textures are always ... textures :/ diff --git a/engine/src/flutter/impeller/display_list/display_list_image_impeller.h b/engine/src/flutter/impeller/display_list/display_list_image_impeller.h index 652b861f85c..cf7ae7501eb 100644 --- a/engine/src/flutter/impeller/display_list/display_list_image_impeller.h +++ b/engine/src/flutter/impeller/display_list/display_list_image_impeller.h @@ -23,6 +23,9 @@ class DlImageImpeller final : public flutter::DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/engine/src/flutter/lib/ui/compositing/scene_builder.cc b/engine/src/flutter/lib/ui/compositing/scene_builder.cc index 3967c20088d..9a543c284a1 100644 --- a/engine/src/flutter/lib/ui/compositing/scene_builder.cc +++ b/engine/src/flutter/lib/ui/compositing/scene_builder.cc @@ -189,8 +189,7 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle, maskRectBottom); auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); auto layer = std::make_shared( - shader->shader(sampling)->skia_object(), rect, - static_cast(blendMode)); + shader->shader(sampling), rect, static_cast(blendMode)); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); diff --git a/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.cc b/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.cc index 4580fe9801e..beeb0ccf804 100644 --- a/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.cc +++ b/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.cc @@ -51,6 +51,11 @@ std::shared_ptr DlDeferredImageGPU::impeller_texture() return nullptr; } +// |DlImage| +bool DlDeferredImageGPU::isOpaque() const { + return image_wrapper_ ? image_wrapper_->image_info().isOpaque() : false; +} + // |DlImage| bool DlDeferredImageGPU::isTextureBacked() const { return image_wrapper_ ? image_wrapper_->isTextureBacked() : false; diff --git a/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.h b/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.h index a98f4feaa42..429dcd873a1 100644 --- a/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.h +++ b/engine/src/flutter/lib/ui/painting/display_list_deferred_image_gpu.h @@ -41,6 +41,9 @@ class DlDeferredImageGPU final : public DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/engine/src/flutter/lib/ui/painting/display_list_image_gpu.cc b/engine/src/flutter/lib/ui/painting/display_list_image_gpu.cc index a9e209a9e14..ff52840568c 100644 --- a/engine/src/flutter/lib/ui/painting/display_list_image_gpu.cc +++ b/engine/src/flutter/lib/ui/painting/display_list_image_gpu.cc @@ -29,6 +29,14 @@ std::shared_ptr DlImageGPU::impeller_texture() const { return nullptr; } +// |DlImage| +bool DlImageGPU::isOpaque() const { + if (auto image = skia_image()) { + return image->isOpaque(); + } + return false; +} + // |DlImage| bool DlImageGPU::isTextureBacked() const { if (auto image = skia_image()) { diff --git a/engine/src/flutter/lib/ui/painting/display_list_image_gpu.h b/engine/src/flutter/lib/ui/painting/display_list_image_gpu.h index 9631a333c04..dbc7febc465 100644 --- a/engine/src/flutter/lib/ui/painting/display_list_image_gpu.h +++ b/engine/src/flutter/lib/ui/painting/display_list_image_gpu.h @@ -24,6 +24,9 @@ class DlImageGPU final : public DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/engine/src/flutter/lib/ui/painting/fragment_program.cc b/engine/src/flutter/lib/ui/painting/fragment_program.cc index bcd2626d28b..e55c09f3eac 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_program.cc +++ b/engine/src/flutter/lib/ui/painting/fragment_program.cc @@ -112,7 +112,8 @@ fml::RefPtr FragmentProgram::shader(Dart_Handle shader, uniform_floats[i] = uniforms[i]; } uniforms.Release(); - std::vector> sk_samplers(sampler_shaders.size()); + std::vector> dl_samplers( + sampler_shaders.size()); for (size_t i = 0; i < sampler_shaders.size(); i++) { DlImageSampling sampling = DlImageSampling::kNearestNeighbor; ImageShader* image_shader = sampler_shaders[i]; @@ -122,13 +123,14 @@ fml::RefPtr FragmentProgram::shader(Dart_Handle shader, // contain a value to be used if the developer did not specify a preference // when they constructed the ImageShader, so we will use kNearest which is // the default filterQuality in a Paint object. - sk_samplers[i] = image_shader->shader(sampling)->skia_object(); + dl_samplers[i] = image_shader->shader(sampling); uniform_floats[uniform_count + 2 * i] = image_shader->width(); uniform_floats[uniform_count + 2 * i + 1] = image_shader->height(); } - auto sk_shader = runtime_effect_->makeShader( - std::move(uniform_data), sk_samplers.data(), sk_samplers.size()); - return FragmentShader::Create(shader, std::move(sk_shader)); + return FragmentShader::Create( + shader, + DlColorSource::MakeRuntimeEffect(runtime_effect_, std::move(dl_samplers), + std::move(uniform_data))); } void FragmentProgram::Create(Dart_Handle wrapper) { diff --git a/engine/src/flutter/lib/ui/painting/fragment_shader.cc b/engine/src/flutter/lib/ui/painting/fragment_shader.cc index c7fa991bf07..39cee53482b 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_shader.cc +++ b/engine/src/flutter/lib/ui/painting/fragment_shader.cc @@ -34,15 +34,17 @@ std::shared_ptr FragmentShader::shader( return source_; } -fml::RefPtr FragmentShader::Create(Dart_Handle dart_handle, - sk_sp shader) { +fml::RefPtr FragmentShader::Create( + Dart_Handle dart_handle, + std::shared_ptr shader) { auto fragment_shader = fml::MakeRefCounted(std::move(shader)); fragment_shader->AssociateWithDartWrapper(dart_handle); return fragment_shader; } -FragmentShader::FragmentShader(sk_sp shader) - : source_(DlColorSource::From(shader)) {} +FragmentShader::FragmentShader( + std::shared_ptr shader) + : source_(std::move(shader)) {} FragmentShader::~FragmentShader() = default; diff --git a/engine/src/flutter/lib/ui/painting/fragment_shader.h b/engine/src/flutter/lib/ui/painting/fragment_shader.h index 3936f447530..f3aa48a1565 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_shader.h +++ b/engine/src/flutter/lib/ui/painting/fragment_shader.h @@ -25,15 +25,16 @@ class FragmentShader : public Shader { public: ~FragmentShader() override; - static fml::RefPtr Create(Dart_Handle dart_handle, - sk_sp shader); + static fml::RefPtr Create( + Dart_Handle dart_handle, + std::shared_ptr shader); std::shared_ptr shader(DlImageSampling) override; private: - explicit FragmentShader(sk_sp shader); + explicit FragmentShader(std::shared_ptr shader); - std::shared_ptr source_; + std::shared_ptr source_; }; } // namespace flutter diff --git a/engine/src/flutter/lib/ui/painting/image_shader.cc b/engine/src/flutter/lib/ui/painting/image_shader.cc index 4567e016056..c7416bc8665 100644 --- a/engine/src/flutter/lib/ui/painting/image_shader.cc +++ b/engine/src/flutter/lib/ui/painting/image_shader.cc @@ -32,12 +32,6 @@ Dart_Handle ImageShader::initWithImage(CanvasImage* image, return ToDart("ImageShader constructor called with non-genuine Image."); } - if (image->image()->owning_context() != DlImage::OwningContext::kIO) { - // TODO(dnfield): it should be possible to support this - // https://github.com/flutter/flutter/issues/105085 - return ToDart("ImageShader constructor with GPU image is not supported."); - } - image_ = image->image(); tonic::Float64List matrix4(matrix_handle); SkMatrix local_matrix = ToSkMatrix(matrix4); diff --git a/engine/src/flutter/lib/ui/painting/paint.cc b/engine/src/flutter/lib/ui/painting/paint.cc index 23b07b25d51..ccf0d111173 100644 --- a/engine/src/flutter/lib/ui/painting/paint.cc +++ b/engine/src/flutter/lib/ui/painting/paint.cc @@ -98,7 +98,14 @@ const SkPaint* Paint::paint(SkPaint& paint) const { Shader* decoded = tonic::DartConverter::FromDart(shader); auto sampling = ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); - paint.setShader(decoded->shader(sampling)->skia_object()); + auto color_source = decoded->shader(sampling); + // TODO(dnfield): Remove this restriction. + // This currently is only used by paragraph code. Once SkParagraph does + // not need to take an SkPaint, we won't be restricted in this way because + // we will not need to access the shader on the UI task runner. + if (color_source->owning_context() != DlImage::OwningContext::kRaster) { + paint.setShader(color_source->skia_object()); + } } Dart_Handle color_filter = values[kColorFilterIndex]; diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart index 7fdcdf27426..5fcc63f3ad3 100644 --- a/engine/src/flutter/testing/dart/fragment_shader_test.dart +++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart @@ -36,8 +36,25 @@ void main() async { samplerUniforms: [imageShader], ); await _expectShaderRendersGreen(shader); + blueGreenImage.dispose(); }); + test('blue-green image renders green - GPU image', () async { + final FragmentProgram program = await FragmentProgram.fromAsset( + 'blue_green_sampler.frag.iplr', + ); + final Image blueGreenImage = _createBlueGreenImageSync(); + final ImageShader imageShader = ImageShader( + blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([]), + samplerUniforms: [imageShader], + ); + await _expectShaderRendersGreen(shader); + blueGreenImage.dispose(); + }); + + test('shader with uniforms renders correctly', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'uniforms.frag.iplr', @@ -318,6 +335,21 @@ Future _createBlueGreenImage() async { return frame.image; } +// A 10x10 image where the left half is blue and the right half is green. +Image _createBlueGreenImageSync() { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(const Rect.fromLTWH(0, 0, 5, 10), Paint()..color = const Color(0xFF0000FF)); + canvas.drawRect(const Rect.fromLTWH(5, 0, 5, 10), Paint()..color = const Color(0xFF00FF00)); + final Picture picture = recorder.endRecording(); + try { + return picture.toImageSync(10, 10); + } finally { + picture.dispose(); + } +} + + final Float64List _identityMatrix = Float64List.fromList([ 1, 0, 0, 0, 0, 1, 0, 0, diff --git a/engine/src/flutter/testing/dart/image_shader_test.dart b/engine/src/flutter/testing/dart/image_shader_test.dart index 56c502b1325..25b8b2dafb4 100644 --- a/engine/src/flutter/testing/dart/image_shader_test.dart +++ b/engine/src/flutter/testing/dart/image_shader_test.dart @@ -25,11 +25,9 @@ void main() { final Image image = picture.toImageSync(50, 50); picture.dispose(); - // TODO(dnfield): this should not throw once - // https://github.com/flutter/flutter/issues/105085 is fixed. - expect( - () => ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)), - throwsException, - ); + final ImageShader shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)); + final Paint paint = Paint()..shader=shader; + const Rect rect = Rect.fromLTRB(0, 0, 100, 100); + testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); }); }