Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 44d788f

Browse files
author
Jonah Williams
authored
[Impeller] Add support for ImageFilter.shader (#53490)
Copy-pasta docs: ``` /// Creates an image filter from a [FragmentShader]. /// /// The fragment shader provided here has additional requirements to be used /// by the engine for filtering. The first uniform value must be a vec2, this /// will be set by the engine to the size of the bound texture. There must /// also be at least one sampler2D uniform, the first of which will be set by /// the engine to contain the filter input. /// /// For example, the following is a valid fragment shader that can be used /// with this API. Note that the uniform names are not required to have any /// particular value. /// /// ```glsl /// #include <flutter/runtime_effect.glsl> /// /// uniform vec2 u_size; /// uniform float u_time; /// /// uniform sampler2D u_texture_input; /// /// out vec4 frag_color; /// /// void main() { /// frag_color = texture(u_texture_input, FlutterFragCoord().xy / u_size) * u_time; /// /// } /// /// ``` /// /// This API is only supported when using the Impeller rendering engine. On /// other backends a [UnsupportedError] will be thrown. This error can be /// caught and used for feature detection. ``` Fixes jonahwilliams/flutter_shaders#34 Fixes jonahwilliams/flutter_shaders#26 Fixes flutter/flutter#132099
1 parent 1e5c388 commit 44d788f

35 files changed

+953
-199
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42984,6 +42984,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents
4298442984
ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h + ../../../flutter/LICENSE
4298542985
ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc + ../../../flutter/LICENSE
4298642986
ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h + ../../../flutter/LICENSE
42987+
ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc + ../../../flutter/LICENSE
42988+
ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h + ../../../flutter/LICENSE
4298742989
ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc + ../../../flutter/LICENSE
4298842990
ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h + ../../../flutter/LICENSE
4298942991
ORIGIN: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc + ../../../flutter/LICENSE
@@ -45844,6 +45846,8 @@ FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.c
4584445846
FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h
4584545847
FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc
4584645848
FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h
45849+
FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc
45850+
FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h
4584745851
FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc
4584845852
FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h
4584945853
FILE: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc

display_list/dl_builder.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,8 @@ void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) {
286286
}
287287
case DlImageFilterType::kCompose:
288288
case DlImageFilterType::kLocalMatrix:
289-
case DlImageFilterType::kColorFilter: {
289+
case DlImageFilterType::kColorFilter:
290+
case DlImageFilterType::kRuntimeEffect: {
290291
Push<SetSharedImageFilterOp>(0, filter);
291292
break;
292293
}

display_list/effects/dl_image_filter.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <utility>
99

10+
#include "display_list/effects/dl_color_source.h"
1011
#include "flutter/display_list/dl_attributes.h"
1112
#include "flutter/display_list/dl_sampling_options.h"
1213
#include "flutter/display_list/dl_tile_mode.h"
@@ -34,6 +35,7 @@ enum class DlImageFilterType {
3435
kCompose,
3536
kColorFilter,
3637
kLocalMatrix,
38+
kRuntimeEffect,
3739
};
3840

3941
class DlBlurImageFilter;
@@ -43,6 +45,7 @@ class DlMatrixImageFilter;
4345
class DlLocalMatrixImageFilter;
4446
class DlComposeImageFilter;
4547
class DlColorFilterImageFilter;
48+
class DlRuntimeEffectImageFilter;
4649

4750
class DlImageFilter : public DlAttribute<DlImageFilter, DlImageFilterType> {
4851
public:
@@ -85,6 +88,12 @@ class DlImageFilter : public DlAttribute<DlImageFilter, DlImageFilterType> {
8588
return nullptr;
8689
}
8790

91+
// Return a DlRuntimeEffectImageFilter pointer to this object iff it is a
92+
// DlRuntimeEffectImageFilter type of ImageFilter, otherwise return nullptr.
93+
virtual const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const {
94+
return nullptr;
95+
}
96+
8897
// Return a boolean indicating whether the image filtering operation will
8998
// modify transparent black. This is typically used to determine if applying
9099
// the ImageFilter to a temporary saveLayer buffer will turn the surrounding
@@ -742,6 +751,101 @@ class DlLocalMatrixImageFilter final : public DlImageFilter {
742751
std::shared_ptr<const DlImageFilter> image_filter_;
743752
};
744753

754+
class DlRuntimeEffectImageFilter final : public DlImageFilter {
755+
public:
756+
explicit DlRuntimeEffectImageFilter(
757+
sk_sp<DlRuntimeEffect> runtime_effect,
758+
std::vector<std::shared_ptr<DlColorSource>> samplers,
759+
std::shared_ptr<std::vector<uint8_t>> uniform_data)
760+
: runtime_effect_(std::move(runtime_effect)),
761+
samplers_(std::move(samplers)),
762+
uniform_data_(std::move(uniform_data)) {}
763+
764+
std::shared_ptr<DlImageFilter> shared() const override {
765+
return std::make_shared<DlRuntimeEffectImageFilter>(
766+
this->runtime_effect_, this->samplers_, this->uniform_data_);
767+
}
768+
769+
static std::shared_ptr<DlImageFilter> Make(
770+
sk_sp<DlRuntimeEffect> runtime_effect,
771+
std::vector<std::shared_ptr<DlColorSource>> samplers,
772+
std::shared_ptr<std::vector<uint8_t>> uniform_data) {
773+
return std::make_shared<DlRuntimeEffectImageFilter>(
774+
std::move(runtime_effect), std::move(samplers),
775+
std::move(uniform_data));
776+
}
777+
778+
DlImageFilterType type() const override {
779+
return DlImageFilterType::kRuntimeEffect;
780+
}
781+
size_t size() const override { return sizeof(*this); }
782+
783+
bool modifies_transparent_black() const override { return false; }
784+
785+
SkRect* map_local_bounds(const SkRect& input_bounds,
786+
SkRect& output_bounds) const override {
787+
output_bounds = input_bounds;
788+
return &output_bounds;
789+
}
790+
791+
SkIRect* map_device_bounds(const SkIRect& input_bounds,
792+
const SkMatrix& ctm,
793+
SkIRect& output_bounds) const override {
794+
output_bounds = input_bounds;
795+
return &output_bounds;
796+
}
797+
798+
SkIRect* get_input_device_bounds(const SkIRect& output_bounds,
799+
const SkMatrix& ctm,
800+
SkIRect& input_bounds) const override {
801+
input_bounds = output_bounds;
802+
return &input_bounds;
803+
}
804+
805+
const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const override {
806+
return this;
807+
}
808+
809+
const sk_sp<DlRuntimeEffect> runtime_effect() const {
810+
return runtime_effect_;
811+
}
812+
813+
const std::vector<std::shared_ptr<DlColorSource>>& samplers() const {
814+
return samplers_;
815+
}
816+
817+
const std::shared_ptr<std::vector<uint8_t>>& uniform_data() const {
818+
return uniform_data_;
819+
}
820+
821+
protected:
822+
bool equals_(const DlImageFilter& other) const override {
823+
FML_DCHECK(other.type() == DlImageFilterType::kRuntimeEffect);
824+
auto that = static_cast<const DlRuntimeEffectImageFilter*>(&other);
825+
if (runtime_effect_ != that->runtime_effect_ ||
826+
samplers_.size() != that->samplers().size() ||
827+
uniform_data_->size() != that->uniform_data()->size()) {
828+
return false;
829+
}
830+
for (auto i = 0u; i < samplers_.size(); i++) {
831+
if (samplers_[i] != that->samplers()[i]) {
832+
return false;
833+
}
834+
}
835+
for (auto i = 0u; i < uniform_data_->size(); i++) {
836+
if (uniform_data_->at(i) != that->uniform_data()->at(i)) {
837+
return false;
838+
}
839+
}
840+
return true;
841+
}
842+
843+
private:
844+
sk_sp<DlRuntimeEffect> runtime_effect_;
845+
std::vector<std::shared_ptr<DlColorSource>> samplers_;
846+
std::shared_ptr<std::vector<uint8_t>> uniform_data_;
847+
};
848+
745849
} // namespace flutter
746850

747851
#endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTER_H_

display_list/effects/dl_image_filter_unittests.cc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "flutter/display_list/utils/dl_comparable.h"
1313
#include "gtest/gtest.h"
1414

15+
#include "include/core/SkMatrix.h"
16+
#include "include/core/SkRect.h"
1517
#include "third_party/skia/include/core/SkBlendMode.h"
1618
#include "third_party/skia/include/core/SkColorFilter.h"
1719
#include "third_party/skia/include/core/SkSamplingOptions.h"
@@ -823,5 +825,87 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) {
823825
}
824826
}
825827

828+
TEST(DisplayListImageFilter, RuntimeEffectEquality) {
829+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr},
830+
std::make_shared<std::vector<uint8_t>>());
831+
DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr},
832+
std::make_shared<std::vector<uint8_t>>());
833+
834+
EXPECT_EQ(filter_a, filter_b);
835+
836+
DlRuntimeEffectImageFilter filter_c(
837+
nullptr, {nullptr}, std::make_shared<std::vector<uint8_t>>(1));
838+
839+
EXPECT_NE(filter_a, filter_c);
840+
}
841+
842+
TEST(DisplayListImageFilter, RuntimeEffectEqualityWithSamplers) {
843+
auto image_a = std::make_shared<DlImageColorSource>(
844+
nullptr, DlTileMode::kClamp, DlTileMode::kDecal);
845+
auto image_b = std::make_shared<DlImageColorSource>(
846+
nullptr, DlTileMode::kClamp, DlTileMode::kClamp);
847+
848+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr, image_a},
849+
std::make_shared<std::vector<uint8_t>>());
850+
DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr, image_a},
851+
std::make_shared<std::vector<uint8_t>>());
852+
853+
EXPECT_EQ(filter_a, filter_b);
854+
855+
DlRuntimeEffectImageFilter filter_c(nullptr, {nullptr, image_b},
856+
std::make_shared<std::vector<uint8_t>>());
857+
858+
EXPECT_NE(filter_a, filter_c);
859+
}
860+
861+
TEST(DisplayListImageFilter, RuntimeEffectMapDeviceBounds) {
862+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr},
863+
std::make_shared<std::vector<uint8_t>>());
864+
865+
auto input_bounds = SkIRect::MakeLTRB(0, 0, 100, 100);
866+
SkMatrix identity;
867+
SkIRect output_bounds;
868+
SkIRect* result =
869+
filter_a.map_device_bounds(input_bounds, identity, output_bounds);
870+
871+
EXPECT_NE(result, nullptr);
872+
EXPECT_EQ(output_bounds, input_bounds);
873+
}
874+
875+
TEST(DisplayListImageFilter, RuntimeEffectMapInputBounds) {
876+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr},
877+
std::make_shared<std::vector<uint8_t>>());
878+
879+
auto input_bounds = SkRect::MakeLTRB(0, 0, 100, 100);
880+
881+
SkRect output_bounds;
882+
SkRect* result = filter_a.map_local_bounds(input_bounds, output_bounds);
883+
884+
EXPECT_NE(result, nullptr);
885+
EXPECT_EQ(output_bounds, input_bounds);
886+
}
887+
888+
TEST(DisplayListImageFilter, RuntimeEffectGetInputDeviceBounds) {
889+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr},
890+
std::make_shared<std::vector<uint8_t>>());
891+
892+
auto output_bounds = SkIRect::MakeLTRB(0, 0, 100, 100);
893+
894+
SkMatrix identity;
895+
SkIRect input_bounds;
896+
SkIRect* result =
897+
filter_a.get_input_device_bounds(output_bounds, identity, input_bounds);
898+
899+
EXPECT_NE(result, nullptr);
900+
EXPECT_EQ(output_bounds, input_bounds);
901+
}
902+
903+
TEST(DisplayListImageFilter, RuntimeEffectModifiesTransparentBlack) {
904+
DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr},
905+
std::make_shared<std::vector<uint8_t>>());
906+
907+
EXPECT_FALSE(filter_a.modifies_transparent_black());
908+
}
909+
826910
} // namespace testing
827911
} // namespace flutter

display_list/skia/dl_sk_conversions.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ sk_sp<SkImageFilter> ToSk(const DlImageFilter* filter) {
231231
}
232232
return skia_filter->makeWithLocalMatrix(lm_filter->matrix());
233233
}
234+
case DlImageFilterType::kRuntimeEffect:
235+
// UNSUPPORTED.
236+
return nullptr;
234237
}
235238
}
236239

impeller/display_list/aiks_dl_runtime_effect_unittests.cc

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
#include <memory>
66

7-
#include "display_list/effects/dl_color_source.h"
8-
#include "flutter/impeller/display_list/aiks_unittests.h"
9-
107
#include "flutter/display_list/dl_builder.h"
118
#include "flutter/display_list/dl_paint.h"
9+
#include "flutter/display_list/effects/dl_color_source.h"
10+
#include "flutter/display_list/effects/dl_image_filter.h"
11+
#include "flutter/display_list/effects/dl_runtime_effect.h"
12+
#include "flutter/impeller/display_list/aiks_unittests.h"
1213

1314
#include "include/core/SkPath.h"
1415
#include "include/core/SkRRect.h"
@@ -83,5 +84,32 @@ TEST_P(AiksTest, DrawPaintTransformsBounds) {
8384
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
8485
}
8586

87+
TEST_P(AiksTest, CanRenderRuntimeEffectFilter) {
88+
auto runtime_stages =
89+
OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr");
90+
91+
std::shared_ptr<RuntimeStage> runtime_stage =
92+
runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
93+
ASSERT_TRUE(runtime_stage);
94+
ASSERT_TRUE(runtime_stage->IsDirty());
95+
96+
std::vector<std::shared_ptr<DlColorSource>> sampler_inputs = {
97+
nullptr,
98+
};
99+
auto uniform_data = std::make_shared<std::vector<uint8_t>>();
100+
uniform_data->resize(sizeof(Vector2));
101+
102+
DlPaint paint;
103+
paint.setColor(DlColor::kAqua());
104+
paint.setImageFilter(std::make_shared<DlRuntimeEffectImageFilter>(
105+
DlRuntimeEffect::MakeImpeller(runtime_stage), sampler_inputs,
106+
uniform_data));
107+
108+
DisplayListBuilder builder;
109+
builder.DrawRect(SkRect::MakeXYWH(0, 0, 400, 400), paint);
110+
111+
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
112+
}
113+
86114
} // namespace testing
87115
} // namespace impeller

impeller/display_list/dl_dispatcher.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include <optional>
1111
#include <vector>
1212

13-
#include "display_list/effects/dl_color_source.h"
13+
#include "display_list/dl_sampling_options.h"
1414
#include "display_list/effects/dl_image_filter.h"
1515
#include "flutter/fml/logging.h"
1616
#include "impeller/core/formats.h"

impeller/display_list/image_filter.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#include "impeller/display_list/image_filter.h"
6+
#include "display_list/effects/dl_image_filter.h"
67
#include "fml/logging.h"
78
#include "impeller/display_list/color_filter.h"
89
#include "impeller/display_list/skia_conversions.h"
@@ -102,6 +103,43 @@ std::shared_ptr<FilterContents> WrapInput(const flutter::DlImageFilter* filter,
102103
outer_dl_filter.get(),
103104
FilterInput::Make(WrapInput(inner_dl_filter.get(), input)));
104105
}
106+
case flutter::DlImageFilterType::kRuntimeEffect: {
107+
const flutter::DlRuntimeEffectImageFilter* runtime_filter =
108+
filter->asRuntimeEffectFilter();
109+
FML_DCHECK(runtime_filter);
110+
std::shared_ptr<impeller::RuntimeStage> runtime_stage =
111+
runtime_filter->runtime_effect()->runtime_stage();
112+
113+
std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
114+
size_t index = 0;
115+
for (const std::shared_ptr<flutter::DlColorSource>& sampler :
116+
runtime_filter->samplers()) {
117+
if (index == 0 && sampler == nullptr) {
118+
// Insert placeholder for filter.
119+
texture_inputs.push_back(
120+
{.sampler_descriptor = skia_conversions::ToSamplerDescriptor({}),
121+
.texture = nullptr});
122+
continue;
123+
}
124+
if (sampler == nullptr) {
125+
return nullptr;
126+
}
127+
auto* image = sampler->asImage();
128+
if (!image) {
129+
return nullptr;
130+
}
131+
FML_DCHECK(image->image()->impeller_texture());
132+
index++;
133+
texture_inputs.push_back({
134+
.sampler_descriptor =
135+
skia_conversions::ToSamplerDescriptor(image->sampling()),
136+
.texture = image->image()->impeller_texture(),
137+
});
138+
}
139+
return FilterContents::MakeRuntimeEffect(input, std::move(runtime_stage),
140+
runtime_filter->uniform_data(),
141+
std::move(texture_inputs));
142+
}
105143
}
106144
FML_UNREACHABLE();
107145
}

impeller/entity/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ impeller_component("entity") {
139139
"contents/filters/matrix_filter_contents.h",
140140
"contents/filters/morphology_filter_contents.cc",
141141
"contents/filters/morphology_filter_contents.h",
142+
"contents/filters/runtime_effect_filter_contents.cc",
143+
"contents/filters/runtime_effect_filter_contents.h",
142144
"contents/filters/srgb_to_linear_filter_contents.cc",
143145
"contents/filters/srgb_to_linear_filter_contents.h",
144146
"contents/filters/yuv_to_rgb_filter_contents.cc",

0 commit comments

Comments
 (0)