diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1ea652b8f9378..266ef0dc35e39 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -42987,6 +42987,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents ORIGIN: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc + ../../../flutter/LICENSE @@ -45850,6 +45852,8 @@ FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.c FILE: ../../../flutter/impeller/entity/contents/filters/matrix_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h +FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/runtime_effect_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/yuv_to_rgb_filter_contents.cc diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 06164781fb7cc..c973f0abcfe4d 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -286,7 +286,8 @@ void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { } case DlImageFilterType::kCompose: case DlImageFilterType::kLocalMatrix: - case DlImageFilterType::kColorFilter: { + case DlImageFilterType::kColorFilter: + case DlImageFilterType::kRuntimeEffect: { Push(0, filter); break; } diff --git a/display_list/effects/dl_image_filter.h b/display_list/effects/dl_image_filter.h index 1e6b01529429c..0aa85d0f6b3bc 100644 --- a/display_list/effects/dl_image_filter.h +++ b/display_list/effects/dl_image_filter.h @@ -7,6 +7,7 @@ #include +#include "display_list/effects/dl_color_source.h" #include "flutter/display_list/dl_attributes.h" #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/dl_tile_mode.h" @@ -34,6 +35,7 @@ enum class DlImageFilterType { kCompose, kColorFilter, kLocalMatrix, + kRuntimeEffect, }; class DlBlurImageFilter; @@ -43,6 +45,7 @@ class DlMatrixImageFilter; class DlLocalMatrixImageFilter; class DlComposeImageFilter; class DlColorFilterImageFilter; +class DlRuntimeEffectImageFilter; class DlImageFilter : public DlAttribute { public: @@ -85,6 +88,12 @@ class DlImageFilter : public DlAttribute { return nullptr; } + // Return a DlRuntimeEffectImageFilter pointer to this object iff it is a + // DlRuntimeEffectImageFilter type of ImageFilter, otherwise return nullptr. + virtual const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const { + return nullptr; + } + // Return a boolean indicating whether the image filtering operation will // modify transparent black. This is typically used to determine if applying // the ImageFilter to a temporary saveLayer buffer will turn the surrounding @@ -742,6 +751,101 @@ class DlLocalMatrixImageFilter final : public DlImageFilter { std::shared_ptr image_filter_; }; +class DlRuntimeEffectImageFilter final : public DlImageFilter { + public: + explicit DlRuntimeEffectImageFilter( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) + : runtime_effect_(std::move(runtime_effect)), + samplers_(std::move(samplers)), + uniform_data_(std::move(uniform_data)) {} + + std::shared_ptr shared() const override { + return std::make_shared( + this->runtime_effect_, this->samplers_, this->uniform_data_); + } + + static std::shared_ptr Make( + sk_sp runtime_effect, + std::vector> samplers, + std::shared_ptr> uniform_data) { + return std::make_shared( + std::move(runtime_effect), std::move(samplers), + std::move(uniform_data)); + } + + DlImageFilterType type() const override { + return DlImageFilterType::kRuntimeEffect; + } + size_t size() const override { return sizeof(*this); } + + bool modifies_transparent_black() const override { return false; } + + SkRect* map_local_bounds(const SkRect& input_bounds, + SkRect& output_bounds) const override { + output_bounds = input_bounds; + return &output_bounds; + } + + SkIRect* map_device_bounds(const SkIRect& input_bounds, + const SkMatrix& ctm, + SkIRect& output_bounds) const override { + output_bounds = input_bounds; + return &output_bounds; + } + + SkIRect* get_input_device_bounds(const SkIRect& output_bounds, + const SkMatrix& ctm, + SkIRect& input_bounds) const override { + input_bounds = output_bounds; + return &input_bounds; + } + + const DlRuntimeEffectImageFilter* asRuntimeEffectFilter() const override { + return this; + } + + const sk_sp runtime_effect() const { + return runtime_effect_; + } + + const std::vector>& samplers() const { + return samplers_; + } + + const std::shared_ptr>& uniform_data() const { + return uniform_data_; + } + + protected: + bool equals_(const DlImageFilter& other) const override { + FML_DCHECK(other.type() == DlImageFilterType::kRuntimeEffect); + auto that = static_cast(&other); + if (runtime_effect_ != that->runtime_effect_ || + samplers_.size() != that->samplers().size() || + uniform_data_->size() != that->uniform_data()->size()) { + return false; + } + for (auto i = 0u; i < samplers_.size(); i++) { + if (samplers_[i] != that->samplers()[i]) { + return false; + } + } + for (auto i = 0u; i < uniform_data_->size(); i++) { + if (uniform_data_->at(i) != that->uniform_data()->at(i)) { + return false; + } + } + return true; + } + + private: + sk_sp runtime_effect_; + std::vector> samplers_; + std::shared_ptr> uniform_data_; +}; + } // namespace flutter #endif // FLUTTER_DISPLAY_LIST_EFFECTS_DL_IMAGE_FILTER_H_ diff --git a/display_list/effects/dl_image_filter_unittests.cc b/display_list/effects/dl_image_filter_unittests.cc index 70925455aa999..f01bb8987c5b1 100644 --- a/display_list/effects/dl_image_filter_unittests.cc +++ b/display_list/effects/dl_image_filter_unittests.cc @@ -12,6 +12,8 @@ #include "flutter/display_list/utils/dl_comparable.h" #include "gtest/gtest.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkRect.h" #include "third_party/skia/include/core/SkBlendMode.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/core/SkSamplingOptions.h" @@ -823,5 +825,87 @@ TEST(DisplayListImageFilter, LocalImageFilterBounds) { } } +TEST(DisplayListImageFilter, RuntimeEffectEquality) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr}, + std::make_shared>()); + + EXPECT_EQ(filter_a, filter_b); + + DlRuntimeEffectImageFilter filter_c( + nullptr, {nullptr}, std::make_shared>(1)); + + EXPECT_NE(filter_a, filter_c); +} + +TEST(DisplayListImageFilter, RuntimeEffectEqualityWithSamplers) { + auto image_a = std::make_shared( + nullptr, DlTileMode::kClamp, DlTileMode::kDecal); + auto image_b = std::make_shared( + nullptr, DlTileMode::kClamp, DlTileMode::kClamp); + + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr, image_a}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr, image_a}, + std::make_shared>()); + + EXPECT_EQ(filter_a, filter_b); + + DlRuntimeEffectImageFilter filter_c(nullptr, {nullptr, image_b}, + std::make_shared>()); + + EXPECT_NE(filter_a, filter_c); +} + +TEST(DisplayListImageFilter, RuntimeEffectMapDeviceBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto input_bounds = SkIRect::MakeLTRB(0, 0, 100, 100); + SkMatrix identity; + SkIRect output_bounds; + SkIRect* result = + filter_a.map_device_bounds(input_bounds, identity, output_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectMapInputBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto input_bounds = SkRect::MakeLTRB(0, 0, 100, 100); + + SkRect output_bounds; + SkRect* result = filter_a.map_local_bounds(input_bounds, output_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectGetInputDeviceBounds) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + auto output_bounds = SkIRect::MakeLTRB(0, 0, 100, 100); + + SkMatrix identity; + SkIRect input_bounds; + SkIRect* result = + filter_a.get_input_device_bounds(output_bounds, identity, input_bounds); + + EXPECT_NE(result, nullptr); + EXPECT_EQ(output_bounds, input_bounds); +} + +TEST(DisplayListImageFilter, RuntimeEffectModifiesTransparentBlack) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + + EXPECT_FALSE(filter_a.modifies_transparent_black()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/skia/dl_sk_conversions.cc b/display_list/skia/dl_sk_conversions.cc index f891fa83e59a4..de0db51cb73ae 100644 --- a/display_list/skia/dl_sk_conversions.cc +++ b/display_list/skia/dl_sk_conversions.cc @@ -231,6 +231,9 @@ sk_sp ToSk(const DlImageFilter* filter) { } return skia_filter->makeWithLocalMatrix(lm_filter->matrix()); } + case DlImageFilterType::kRuntimeEffect: + // UNSUPPORTED. + return nullptr; } } diff --git a/impeller/display_list/aiks_dl_runtime_effect_unittests.cc b/impeller/display_list/aiks_dl_runtime_effect_unittests.cc index e090dd6f5112c..705ddc50f2699 100644 --- a/impeller/display_list/aiks_dl_runtime_effect_unittests.cc +++ b/impeller/display_list/aiks_dl_runtime_effect_unittests.cc @@ -4,11 +4,12 @@ #include -#include "display_list/effects/dl_color_source.h" -#include "flutter/impeller/display_list/aiks_unittests.h" - #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_paint.h" +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/effects/dl_image_filter.h" +#include "flutter/display_list/effects/dl_runtime_effect.h" +#include "flutter/impeller/display_list/aiks_unittests.h" #include "include/core/SkPath.h" #include "include/core/SkRRect.h" @@ -83,5 +84,32 @@ TEST_P(AiksTest, DrawPaintTransformsBounds) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, CanRenderRuntimeEffectFilter) { + auto runtime_stages = + OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr"); + + std::shared_ptr runtime_stage = + runtime_stages[PlaygroundBackendToRuntimeStageBackend(GetBackend())]; + ASSERT_TRUE(runtime_stage); + ASSERT_TRUE(runtime_stage->IsDirty()); + + std::vector> sampler_inputs = { + nullptr, + }; + auto uniform_data = std::make_shared>(); + uniform_data->resize(sizeof(Vector2)); + + DlPaint paint; + paint.setColor(DlColor::kAqua()); + paint.setImageFilter(std::make_shared( + DlRuntimeEffect::MakeImpeller(runtime_stage), sampler_inputs, + uniform_data)); + + DisplayListBuilder builder; + builder.DrawRect(SkRect::MakeXYWH(0, 0, 400, 400), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 037286c352c18..b754c721f804c 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -10,7 +10,7 @@ #include #include -#include "display_list/effects/dl_color_source.h" +#include "display_list/dl_sampling_options.h" #include "display_list/effects/dl_image_filter.h" #include "flutter/fml/logging.h" #include "impeller/core/formats.h" diff --git a/impeller/display_list/image_filter.cc b/impeller/display_list/image_filter.cc index 2f0af79c66ea9..50d2489df771b 100644 --- a/impeller/display_list/image_filter.cc +++ b/impeller/display_list/image_filter.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/display_list/image_filter.h" +#include "display_list/effects/dl_image_filter.h" #include "fml/logging.h" #include "impeller/display_list/color_filter.h" #include "impeller/display_list/skia_conversions.h" @@ -102,6 +103,43 @@ std::shared_ptr WrapInput(const flutter::DlImageFilter* filter, outer_dl_filter.get(), FilterInput::Make(WrapInput(inner_dl_filter.get(), input))); } + case flutter::DlImageFilterType::kRuntimeEffect: { + const flutter::DlRuntimeEffectImageFilter* runtime_filter = + filter->asRuntimeEffectFilter(); + FML_DCHECK(runtime_filter); + std::shared_ptr runtime_stage = + runtime_filter->runtime_effect()->runtime_stage(); + + std::vector texture_inputs; + size_t index = 0; + for (const std::shared_ptr& sampler : + runtime_filter->samplers()) { + if (index == 0 && sampler == nullptr) { + // Insert placeholder for filter. + texture_inputs.push_back( + {.sampler_descriptor = skia_conversions::ToSamplerDescriptor({}), + .texture = nullptr}); + continue; + } + if (sampler == nullptr) { + return nullptr; + } + auto* image = sampler->asImage(); + if (!image) { + return nullptr; + } + FML_DCHECK(image->image()->impeller_texture()); + index++; + texture_inputs.push_back({ + .sampler_descriptor = + skia_conversions::ToSamplerDescriptor(image->sampling()), + .texture = image->image()->impeller_texture(), + }); + } + return FilterContents::MakeRuntimeEffect(input, std::move(runtime_stage), + runtime_filter->uniform_data(), + std::move(texture_inputs)); + } } FML_UNREACHABLE(); } diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 9fcee9c7afd54..9311d35eb0cf7 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -139,6 +139,8 @@ impeller_component("entity") { "contents/filters/matrix_filter_contents.h", "contents/filters/morphology_filter_contents.cc", "contents/filters/morphology_filter_contents.h", + "contents/filters/runtime_effect_filter_contents.cc", + "contents/filters/runtime_effect_filter_contents.h", "contents/filters/srgb_to_linear_filter_contents.cc", "contents/filters/srgb_to_linear_filter_contents.h", "contents/filters/yuv_to_rgb_filter_contents.cc", diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 988d6638a05af..ddfaa2c6388c5 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -21,12 +21,14 @@ #include "impeller/entity/contents/filters/local_matrix_filter_contents.h" #include "impeller/entity/contents/filters/matrix_filter_contents.h" #include "impeller/entity/contents/filters/morphology_filter_contents.h" +#include "impeller/entity/contents/filters/runtime_effect_filter_contents.h" #include "impeller/entity/contents/filters/yuv_to_rgb_filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_pass.h" +#include "impeller/runtime_stage/runtime_stage.h" namespace impeller { @@ -114,6 +116,19 @@ std::shared_ptr FilterContents::MakeYUVToRGBFilter( return filter; } +std::shared_ptr FilterContents::MakeRuntimeEffect( + FilterInput::Ref input, + std::shared_ptr runtime_stage, + std::shared_ptr> uniforms, + std::vector texture_inputs) { + auto filter = std::make_shared(); + filter->SetInputs({std::move(input)}); + filter->SetRuntimeStage(std::move(runtime_stage)); + filter->SetUniforms(std::move(uniforms)); + filter->SetTextureInputs(std::move(texture_inputs)); + return filter; +} + FilterContents::FilterContents() = default; FilterContents::~FilterContents() = default; diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 9b5c019541e78..3177cf7518ee3 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -12,10 +12,12 @@ #include "impeller/core/formats.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/runtime_effect_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/geometry/geometry.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/sigma.h" +#include "impeller/runtime_stage/runtime_stage.h" namespace impeller { @@ -77,6 +79,12 @@ class FilterContents : public Contents { std::shared_ptr uv_texture, YUVColorSpace yuv_color_space); + static std::shared_ptr MakeRuntimeEffect( + FilterInput::Ref input, + std::shared_ptr runtime_stage, + std::shared_ptr> uniforms, + std::vector texture_inputs); + FilterContents(); ~FilterContents() override; diff --git a/impeller/entity/contents/filters/runtime_effect_filter_contents.cc b/impeller/entity/contents/filters/runtime_effect_filter_contents.cc new file mode 100644 index 0000000000000..4bc87c443efd9 --- /dev/null +++ b/impeller/entity/contents/filters/runtime_effect_filter_contents.cc @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/filters/runtime_effect_filter_contents.h" + +#include +#include + +#include "impeller/base/validation.h" +#include "impeller/entity/contents/anonymous_contents.h" +#include "impeller/entity/contents/runtime_effect_contents.h" +#include "impeller/geometry/size.h" + +namespace impeller { + +void RuntimeEffectFilterContents::SetRuntimeStage( + std::shared_ptr runtime_stage) { + runtime_stage_ = std::move(runtime_stage); +} + +void RuntimeEffectFilterContents::SetUniforms( + std::shared_ptr> uniforms) { + uniforms_ = std::move(uniforms); +} + +void RuntimeEffectFilterContents::SetTextureInputs( + std::vector texture_inputs) { + texture_inputs_ = std::move(texture_inputs); +} + +// |FilterContents| +std::optional RuntimeEffectFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage, + const std::optional& coverage_hint) const { + if (inputs.empty()) { + return std::nullopt; + } + + auto input_snapshot = + inputs[0]->GetSnapshot("RuntimeEffectContents", renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + std::optional maybe_input_coverage = input_snapshot->GetCoverage(); + if (!maybe_input_coverage.has_value()) { + return std::nullopt; + } + Rect input_coverage = maybe_input_coverage.value(); + // The shader is required to have at least one sampler, the first of + // which is treated as the input and a vec2 size uniform to compute the + // offsets. These are validated at the dart:ui layer, but to avoid crashes we + // check here too. + if (texture_inputs_.size() < 1 || uniforms_->size() < 8) { + VALIDATION_LOG + << "Invalid fragment shader in RuntimeEffectFilterContents. " + << "Shader must have at least one sampler and a vec2 size uniform."; + return std::nullopt; + } + + // Update uniform values. + std::vector texture_input_copy = + texture_inputs_; + texture_input_copy[0].texture = input_snapshot->texture; + + Size size = Size(input_snapshot->texture->GetSize()); + memcpy(uniforms_->data(), &size, sizeof(Size)); + + //---------------------------------------------------------------------------- + /// Create AnonymousContents for rendering. + /// + RenderProc render_proc = + [input_snapshot, runtime_stage = runtime_stage_, uniforms = uniforms_, + texture_inputs = texture_input_copy, + input_coverage](const ContentContext& renderer, const Entity& entity, + RenderPass& pass) -> bool { + RuntimeEffectContents contents; + RectGeometry geom(Rect::MakeSize(input_coverage.GetSize())); + contents.SetRuntimeStage(runtime_stage); + contents.SetUniformData(uniforms); + contents.SetTextureInputs(texture_inputs); + contents.SetGeometry(&geom); + return contents.Render(renderer, entity, pass); + }; + + CoverageProc coverage_proc = + [coverage](const Entity& entity) -> std::optional { + return coverage.TransformBounds(entity.GetTransform()); + }; + + auto contents = AnonymousContents::Make(render_proc, coverage_proc); + + Entity sub_entity; + sub_entity.SetContents(std::move(contents)); + sub_entity.SetBlendMode(entity.GetBlendMode()); + sub_entity.SetTransform(input_snapshot->transform); + return sub_entity; +} + +// |FilterContents| +std::optional RuntimeEffectFilterContents::GetFilterSourceCoverage( + const Matrix& effect_transform, + const Rect& output_limit) const { + return output_limit; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/runtime_effect_filter_contents.h b/impeller/entity/contents/filters/runtime_effect_filter_contents.h new file mode 100644 index 0000000000000..3a68ecd67ab15 --- /dev/null +++ b/impeller/entity/contents/filters/runtime_effect_filter_contents.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ + +#include "impeller/entity/contents/filters/filter_contents.h" + +namespace impeller { + +/// A filter that applies a runtime effect shader +class RuntimeEffectFilterContents final : public FilterContents { + public: + RuntimeEffectFilterContents() {} + + ~RuntimeEffectFilterContents() = default; + + void SetRuntimeStage(std::shared_ptr runtime_stage); + + void SetUniforms(std::shared_ptr> uniforms); + + void SetTextureInputs( + std::vector texture_inputs); + + private: + std::shared_ptr runtime_stage_; + std::shared_ptr> uniforms_; + std::vector texture_inputs_; + + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage, + const std::optional& coverage_hint) const override; + + // |FilterContents| + std::optional GetFilterSourceCoverage( + const Matrix& effect_transform, + const Rect& output_limit) const override; + + RuntimeEffectFilterContents(const RuntimeEffectFilterContents&) = delete; + + RuntimeEffectFilterContents& operator=(const RuntimeEffectFilterContents&) = + delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_FILTERS_RUNTIME_EFFECT_FILTER_CONTENTS_H_ diff --git a/impeller/entity/contents/runtime_effect_contents.cc b/impeller/entity/contents/runtime_effect_contents.cc index d690b66924f78..e099df6f0530e 100644 --- a/impeller/entity/contents/runtime_effect_contents.cc +++ b/impeller/entity/contents/runtime_effect_contents.cc @@ -207,7 +207,6 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, for (const auto& uniform : runtime_stage_->GetUniforms()) { std::shared_ptr metadata = MakeShaderMetadata(uniform); - switch (uniform.type) { case kSampledImage: { // Sampler uniforms are ordered in the IPLR according to their @@ -249,8 +248,8 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, FML_DCHECK(renderer.GetContext()->GetBackendType() == Context::BackendType::kVulkan); ShaderUniformSlot uniform_slot; - uniform_slot.name = uniform.name.c_str(); uniform_slot.binding = uniform.location; + uniform_slot.name = uniform.name.c_str(); // TODO(jonahwilliams): rewrite this to emplace directly into // HostBuffer. diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index dfeaae94b1720..0e97078395dd9 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -82,6 +82,7 @@ impellerc("runtime_stages") { shaders = [ "ink_sparkle.frag", "runtime_stage_example.frag", + "runtime_stage_filter_example.frag", "runtime_stage_simple.frag", "runtime_stage_position.frag", "gradient.frag", diff --git a/impeller/fixtures/runtime_stage_filter_example.frag b/impeller/fixtures/runtime_stage_filter_example.frag new file mode 100644 index 0000000000000..83b8a4f2145bc --- /dev/null +++ b/impeller/fixtures/runtime_stage_filter_example.frag @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +uniform vec2 u_size; +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + frag_color = texture(u_texture, FlutterFragCoord().xy / u_size) * + (sin(FlutterFragCoord().y / u_size.y * 3.14) * + cos(FlutterFragCoord().x / u_size.x * 3.14)); +} diff --git a/impeller/renderer/command.cc b/impeller/renderer/command.cc index b95d93cb06952..0ca99cbd31155 100644 --- a/impeller/renderer/command.cc +++ b/impeller/renderer/command.cc @@ -31,7 +31,27 @@ bool Command::BindResource(ShaderStage stage, const ShaderUniformSlot& slot, const ShaderMetadata& metadata, BufferView view) { - return DoBindResource(stage, slot, &metadata, std::move(view)); + FML_DCHECK(slot.ext_res_0 != VertexDescriptor::kReservedVertexBufferIndex); + if (!view) { + return false; + } + + switch (stage) { + case ShaderStage::kVertex: + vertex_bindings.buffers.emplace_back(BufferAndUniformSlot{ + .slot = slot, .view = BufferResource(metadata, std::move(view))}); + return true; + case ShaderStage::kFragment: + fragment_bindings.buffers.emplace_back(BufferAndUniformSlot{ + .slot = slot, .view = BufferResource(metadata, std::move(view))}); + return true; + case ShaderStage::kCompute: + VALIDATION_LOG << "Use ComputeCommands for compute shader stages."; + case ShaderStage::kUnknown: + return false; + } + + return false; } bool Command::BindResource( @@ -40,14 +60,6 @@ bool Command::BindResource( const ShaderUniformSlot& slot, const std::shared_ptr& metadata, BufferView view) { - return DoBindResource(stage, slot, metadata, std::move(view)); -} - -template -bool Command::DoBindResource(ShaderStage stage, - const ShaderUniformSlot& slot, - const T metadata, - BufferView view) { FML_DCHECK(slot.ext_res_0 != VertexDescriptor::kReservedVertexBufferIndex); if (!view) { return false; @@ -56,11 +68,11 @@ bool Command::DoBindResource(ShaderStage stage, switch (stage) { case ShaderStage::kVertex: vertex_bindings.buffers.emplace_back(BufferAndUniformSlot{ - .slot = slot, .view = BufferResource(metadata, std::move(view))}); + .slot = slot, .view = BufferResource(*metadata, std::move(view))}); return true; case ShaderStage::kFragment: fragment_bindings.buffers.emplace_back(BufferAndUniformSlot{ - .slot = slot, .view = BufferResource(metadata, std::move(view))}); + .slot = slot, .view = BufferResource(*metadata, std::move(view))}); return true; case ShaderStage::kCompute: VALIDATION_LOG << "Use ComputeCommands for compute shader stages."; @@ -88,14 +100,14 @@ bool Command::BindResource(ShaderStage stage, case ShaderStage::kVertex: vertex_bindings.sampled_images.emplace_back(TextureAndSampler{ .slot = slot, - .texture = {&metadata, std::move(texture)}, + .texture = TextureResource(metadata, std::move(texture)), .sampler = &sampler, }); return true; case ShaderStage::kFragment: fragment_bindings.sampled_images.emplace_back(TextureAndSampler{ .slot = slot, - .texture = {&metadata, std::move(texture)}, + .texture = TextureResource(metadata, std::move(texture)), .sampler = &sampler, }); return true; diff --git a/impeller/renderer/command.h b/impeller/renderer/command.h index fee137d669f66..62553269fd6e3 100644 --- a/impeller/renderer/command.h +++ b/impeller/renderer/command.h @@ -38,9 +38,9 @@ struct Resource { Resource(const ShaderMetadata* metadata, ResourceType p_resource) : resource(p_resource), metadata_(metadata) {} - Resource(std::shared_ptr& metadata, - ResourceType p_resource) - : resource(p_resource), dynamic_metadata_(metadata) {} + Resource(const ShaderMetadata& metadata, ResourceType p_resource) + : resource(p_resource), + dynamic_metadata_(std::make_shared(metadata)) {} const ShaderMetadata* GetMetadata() const { return dynamic_metadata_ ? dynamic_metadata_.get() : metadata_; @@ -51,7 +51,7 @@ struct Resource { const ShaderMetadata* metadata_ = nullptr; // Dynamically generated shader metadata. - std::shared_ptr dynamic_metadata_; + std::shared_ptr dynamic_metadata_ = nullptr; }; using BufferResource = Resource; diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 1e2bc7f9a2e9c..d8509e37e3a25 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -134,173 +134,175 @@ typedef CanvasPath Path; // - Resolve the native function pointer associated with an @Native function. // If there is a mismatch between names or parameter count an @Native is // trying to resolve, an exception will be thrown. -#define FFI_METHOD_LIST(V) \ - V(Canvas, clipPath) \ - V(Canvas, clipRect) \ - V(Canvas, clipRRect) \ - V(Canvas, drawArc) \ - V(Canvas, drawAtlas) \ - V(Canvas, drawCircle) \ - V(Canvas, drawColor) \ - V(Canvas, drawDRRect) \ - V(Canvas, drawImage) \ - V(Canvas, drawImageNine) \ - V(Canvas, drawImageRect) \ - V(Canvas, drawLine) \ - V(Canvas, drawOval) \ - V(Canvas, drawPaint) \ - V(Canvas, drawPath) \ - V(Canvas, drawPicture) \ - V(Canvas, drawPoints) \ - V(Canvas, drawRRect) \ - V(Canvas, drawRect) \ - V(Canvas, drawShadow) \ - V(Canvas, drawVertices) \ - V(Canvas, getDestinationClipBounds) \ - V(Canvas, getLocalClipBounds) \ - V(Canvas, getSaveCount) \ - V(Canvas, getTransform) \ - V(Canvas, restore) \ - V(Canvas, restoreToCount) \ - V(Canvas, rotate) \ - V(Canvas, save) \ - V(Canvas, saveLayer) \ - V(Canvas, saveLayerWithoutBounds) \ - V(Canvas, scale) \ - V(Canvas, skew) \ - V(Canvas, transform) \ - V(Canvas, translate) \ - V(Codec, dispose) \ - V(Codec, frameCount) \ - V(Codec, getNextFrame) \ - V(Codec, repetitionCount) \ - V(ColorFilter, initLinearToSrgbGamma) \ - V(ColorFilter, initMatrix) \ - V(ColorFilter, initMode) \ - V(ColorFilter, initSrgbToLinearGamma) \ - V(EngineLayer, dispose) \ - V(FragmentProgram, initFromAsset) \ - V(ReusableFragmentShader, Dispose) \ - V(ReusableFragmentShader, SetImageSampler) \ - V(ReusableFragmentShader, ValidateSamplers) \ - V(Gradient, initLinear) \ - V(Gradient, initRadial) \ - V(Gradient, initSweep) \ - V(Gradient, initTwoPointConical) \ - V(Image, dispose) \ - V(Image, width) \ - V(Image, height) \ - V(Image, toByteData) \ - V(Image, colorSpace) \ - V(ImageDescriptor, bytesPerPixel) \ - V(ImageDescriptor, dispose) \ - V(ImageDescriptor, height) \ - V(ImageDescriptor, instantiateCodec) \ - V(ImageDescriptor, width) \ - V(ImageFilter, initBlur) \ - V(ImageFilter, initDilate) \ - V(ImageFilter, initErode) \ - V(ImageFilter, initColorFilter) \ - V(ImageFilter, initComposeFilter) \ - V(ImageFilter, initMatrix) \ - V(ImageShader, dispose) \ - V(ImageShader, initWithImage) \ - V(ImmutableBuffer, dispose) \ - V(ImmutableBuffer, length) \ - V(ParagraphBuilder, addPlaceholder) \ - V(ParagraphBuilder, addText) \ - V(ParagraphBuilder, build) \ - V(ParagraphBuilder, pop) \ - V(ParagraphBuilder, pushStyle) \ - V(Paragraph, alphabeticBaseline) \ - V(Paragraph, computeLineMetrics) \ - V(Paragraph, didExceedMaxLines) \ - V(Paragraph, dispose) \ - V(Paragraph, getClosestGlyphInfo) \ - V(Paragraph, getGlyphInfoAt) \ - V(Paragraph, getLineBoundary) \ - V(Paragraph, getLineMetricsAt) \ - V(Paragraph, getLineNumberAt) \ - V(Paragraph, getNumberOfLines) \ - V(Paragraph, getPositionForOffset) \ - V(Paragraph, getRectsForPlaceholders) \ - V(Paragraph, getRectsForRange) \ - V(Paragraph, getWordBoundary) \ - V(Paragraph, height) \ - V(Paragraph, ideographicBaseline) \ - V(Paragraph, layout) \ - V(Paragraph, longestLine) \ - V(Paragraph, maxIntrinsicWidth) \ - V(Paragraph, minIntrinsicWidth) \ - V(Paragraph, paint) \ - V(Paragraph, width) \ - V(PathMeasure, setPath) \ - V(PathMeasure, getLength) \ - V(PathMeasure, getPosTan) \ - V(PathMeasure, getSegment) \ - V(PathMeasure, isClosed) \ - V(PathMeasure, nextContour) \ - V(Path, addArc) \ - V(Path, addOval) \ - V(Path, addPath) \ - V(Path, addPathWithMatrix) \ - V(Path, addPolygon) \ - V(Path, addRRect) \ - V(Path, addRect) \ - V(Path, arcTo) \ - V(Path, arcToPoint) \ - V(Path, clone) \ - V(Path, close) \ - V(Path, conicTo) \ - V(Path, contains) \ - V(Path, cubicTo) \ - V(Path, extendWithPath) \ - V(Path, extendWithPathAndMatrix) \ - V(Path, getBounds) \ - V(Path, getFillType) \ - V(Path, lineTo) \ - V(Path, moveTo) \ - V(Path, op) \ - V(Path, quadraticBezierTo) \ - V(Path, relativeArcToPoint) \ - V(Path, relativeConicTo) \ - V(Path, relativeCubicTo) \ - V(Path, relativeLineTo) \ - V(Path, relativeMoveTo) \ - V(Path, relativeQuadraticBezierTo) \ - V(Path, reset) \ - V(Path, setFillType) \ - V(Path, shift) \ - V(Path, transform) \ - V(PictureRecorder, endRecording) \ - V(Picture, GetAllocationSize) \ - V(Picture, dispose) \ - V(Picture, toImage) \ - V(Picture, toImageSync) \ - V(SceneBuilder, addPerformanceOverlay) \ - V(SceneBuilder, addPicture) \ - V(SceneBuilder, addPlatformView) \ - V(SceneBuilder, addRetained) \ - V(SceneBuilder, addTexture) \ - V(SceneBuilder, build) \ - V(SceneBuilder, pop) \ - V(SceneBuilder, pushBackdropFilter) \ - V(SceneBuilder, pushClipPath) \ - V(SceneBuilder, pushClipRRect) \ - V(SceneBuilder, pushClipRect) \ - V(SceneBuilder, pushColorFilter) \ - V(SceneBuilder, pushImageFilter) \ - V(SceneBuilder, pushOffset) \ - V(SceneBuilder, pushOpacity) \ - V(SceneBuilder, pushShaderMask) \ - V(SceneBuilder, pushTransformHandle) \ - V(Scene, dispose) \ - V(Scene, toImage) \ - V(Scene, toImageSync) \ - V(SemanticsUpdateBuilder, build) \ - V(SemanticsUpdateBuilder, updateCustomAction) \ - V(SemanticsUpdateBuilder, updateNode) \ - V(SemanticsUpdate, dispose) \ +#define FFI_METHOD_LIST(V) \ + V(Canvas, clipPath) \ + V(Canvas, clipRect) \ + V(Canvas, clipRRect) \ + V(Canvas, drawArc) \ + V(Canvas, drawAtlas) \ + V(Canvas, drawCircle) \ + V(Canvas, drawColor) \ + V(Canvas, drawDRRect) \ + V(Canvas, drawImage) \ + V(Canvas, drawImageNine) \ + V(Canvas, drawImageRect) \ + V(Canvas, drawLine) \ + V(Canvas, drawOval) \ + V(Canvas, drawPaint) \ + V(Canvas, drawPath) \ + V(Canvas, drawPicture) \ + V(Canvas, drawPoints) \ + V(Canvas, drawRRect) \ + V(Canvas, drawRect) \ + V(Canvas, drawShadow) \ + V(Canvas, drawVertices) \ + V(Canvas, getDestinationClipBounds) \ + V(Canvas, getLocalClipBounds) \ + V(Canvas, getSaveCount) \ + V(Canvas, getTransform) \ + V(Canvas, restore) \ + V(Canvas, restoreToCount) \ + V(Canvas, rotate) \ + V(Canvas, save) \ + V(Canvas, saveLayer) \ + V(Canvas, saveLayerWithoutBounds) \ + V(Canvas, scale) \ + V(Canvas, skew) \ + V(Canvas, transform) \ + V(Canvas, translate) \ + V(Codec, dispose) \ + V(Codec, frameCount) \ + V(Codec, getNextFrame) \ + V(Codec, repetitionCount) \ + V(ColorFilter, initLinearToSrgbGamma) \ + V(ColorFilter, initMatrix) \ + V(ColorFilter, initMode) \ + V(ColorFilter, initSrgbToLinearGamma) \ + V(EngineLayer, dispose) \ + V(FragmentProgram, initFromAsset) \ + V(ReusableFragmentShader, Dispose) \ + V(ReusableFragmentShader, SetImageSampler) \ + V(ReusableFragmentShader, ValidateSamplers) \ + V(ReusableFragmentShader, ValidateImageFilter) \ + V(Gradient, initLinear) \ + V(Gradient, initRadial) \ + V(Gradient, initSweep) \ + V(Gradient, initTwoPointConical) \ + V(Image, dispose) \ + V(Image, width) \ + V(Image, height) \ + V(Image, toByteData) \ + V(Image, colorSpace) \ + V(ImageDescriptor, bytesPerPixel) \ + V(ImageDescriptor, dispose) \ + V(ImageDescriptor, height) \ + V(ImageDescriptor, instantiateCodec) \ + V(ImageDescriptor, width) \ + V(ImageFilter, initBlur) \ + V(ImageFilter, initDilate) \ + V(ImageFilter, initErode) \ + V(ImageFilter, initColorFilter) \ + V(ImageFilter, initComposeFilter) \ + V(ImageFilter, initShader) \ + V(ImageFilter, initMatrix) \ + V(ImageShader, dispose) \ + V(ImageShader, initWithImage) \ + V(ImmutableBuffer, dispose) \ + V(ImmutableBuffer, length) \ + V(ParagraphBuilder, addPlaceholder) \ + V(ParagraphBuilder, addText) \ + V(ParagraphBuilder, build) \ + V(ParagraphBuilder, pop) \ + V(ParagraphBuilder, pushStyle) \ + V(Paragraph, alphabeticBaseline) \ + V(Paragraph, computeLineMetrics) \ + V(Paragraph, didExceedMaxLines) \ + V(Paragraph, dispose) \ + V(Paragraph, getClosestGlyphInfo) \ + V(Paragraph, getGlyphInfoAt) \ + V(Paragraph, getLineBoundary) \ + V(Paragraph, getLineMetricsAt) \ + V(Paragraph, getLineNumberAt) \ + V(Paragraph, getNumberOfLines) \ + V(Paragraph, getPositionForOffset) \ + V(Paragraph, getRectsForPlaceholders) \ + V(Paragraph, getRectsForRange) \ + V(Paragraph, getWordBoundary) \ + V(Paragraph, height) \ + V(Paragraph, ideographicBaseline) \ + V(Paragraph, layout) \ + V(Paragraph, longestLine) \ + V(Paragraph, maxIntrinsicWidth) \ + V(Paragraph, minIntrinsicWidth) \ + V(Paragraph, paint) \ + V(Paragraph, width) \ + V(PathMeasure, setPath) \ + V(PathMeasure, getLength) \ + V(PathMeasure, getPosTan) \ + V(PathMeasure, getSegment) \ + V(PathMeasure, isClosed) \ + V(PathMeasure, nextContour) \ + V(Path, addArc) \ + V(Path, addOval) \ + V(Path, addPath) \ + V(Path, addPathWithMatrix) \ + V(Path, addPolygon) \ + V(Path, addRRect) \ + V(Path, addRect) \ + V(Path, arcTo) \ + V(Path, arcToPoint) \ + V(Path, clone) \ + V(Path, close) \ + V(Path, conicTo) \ + V(Path, contains) \ + V(Path, cubicTo) \ + V(Path, extendWithPath) \ + V(Path, extendWithPathAndMatrix) \ + V(Path, getBounds) \ + V(Path, getFillType) \ + V(Path, lineTo) \ + V(Path, moveTo) \ + V(Path, op) \ + V(Path, quadraticBezierTo) \ + V(Path, relativeArcToPoint) \ + V(Path, relativeConicTo) \ + V(Path, relativeCubicTo) \ + V(Path, relativeLineTo) \ + V(Path, relativeMoveTo) \ + V(Path, relativeQuadraticBezierTo) \ + V(Path, reset) \ + V(Path, setFillType) \ + V(Path, shift) \ + V(Path, transform) \ + V(PictureRecorder, endRecording) \ + V(Picture, GetAllocationSize) \ + V(Picture, dispose) \ + V(Picture, toImage) \ + V(Picture, toImageSync) \ + V(SceneBuilder, addPerformanceOverlay) \ + V(SceneBuilder, addPicture) \ + V(SceneBuilder, addPlatformView) \ + V(SceneBuilder, addRetained) \ + V(SceneBuilder, addTexture) \ + V(SceneBuilder, build) \ + V(SceneBuilder, pop) \ + V(SceneBuilder, pushBackdropFilter) \ + V(SceneBuilder, pushClipPath) \ + V(SceneBuilder, pushClipRRect) \ + V(SceneBuilder, pushClipRect) \ + V(SceneBuilder, pushColorFilter) \ + V(SceneBuilder, pushImageFilter) \ + V(SceneBuilder, pushOffset) \ + V(SceneBuilder, pushOpacity) \ + V(SceneBuilder, pushShaderMask) \ + V(SceneBuilder, pushTransformHandle) \ + V(Scene, dispose) \ + V(Scene, toImage) \ + V(Scene, toImageSync) \ + V(SemanticsUpdateBuilder, build) \ + V(SemanticsUpdateBuilder, updateCustomAction) \ + V(SemanticsUpdateBuilder, updateNode) \ + V(SemanticsUpdate, dispose) \ V(Vertices, dispose) #define FFI_FUNCTION_INSERT(FUNCTION) \ diff --git a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn index 9e9eb0be00812..d14c68b0abdf9 100644 --- a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn +++ b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn @@ -17,6 +17,9 @@ if (enable_unittests) { "uniforms.frag", "uniforms_sorted.frag", "uniform_arrays.frag", + "filter_shader.frag", + "missing_size.frag", + "missing_texture.frag", ] group("general_shaders") { diff --git a/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag b/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag new file mode 100644 index 0000000000000..a7181e09060c9 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/filter_shader.frag @@ -0,0 +1,15 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +uniform vec2 u_size; +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + // swap color channels. + frag_color = texture(u_texture, FlutterFragCoord().xy / u_size).bgra; +} diff --git a/lib/ui/fixtures/shaders/general_shaders/missing_size.frag b/lib/ui/fixtures/shaders/general_shaders/missing_size.frag new file mode 100644 index 0000000000000..e6cc02dd1f750 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/missing_size.frag @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + frag_color = texture(u_texture, FlutterFragCoord().xy / vec2(100)).bgra; +} diff --git a/lib/ui/fixtures/shaders/general_shaders/missing_texture.frag b/lib/ui/fixtures/shaders/general_shaders/missing_texture.frag new file mode 100644 index 0000000000000..20f80ae397397 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/missing_texture.frag @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +uniform vec2 u_size; + +out vec4 frag_color; + +void main() { + frag_color = vec4(u_size.x, u_size.y, 0, 1); +} diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 2561fa96e3834..2cfec8d08af2c 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -4063,6 +4063,61 @@ abstract class ImageFilter { return _ComposeImageFilter(innerFilter: inner, outerFilter: outer); } + /// 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 + /// + /// 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. To check at runtime + /// whether this API is suppored use [isShaderFilterSupported]. + factory ImageFilter.shader(FragmentShader shader) { + if (!_impellerEnabled) { + throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); + } + final bool invalidFloats = shader._floats.length < 2; + final bool invalidSampler = !shader._validateImageFilter(); + if (invalidFloats || invalidSampler) { + final StringBuffer buffer = StringBuffer( + 'ImageFilter.shader requires that the first uniform is a vec2 and at ' + 'least one sampler uniform is present.\n'); + if (invalidFloats) { + buffer.write('The shader has fewer than two float uniforms.\n'); + } + if (invalidSampler) { + buffer.write('The shader is missing a sampler uniform.\n'); + } + } + return _FragmentShaderImageFilter(shader); + } + + /// Whether [ImageFilter.shader] is supported on the current backend. + static bool get isShaderFilterSupported => _impellerEnabled; + // Converts this to a native DlImageFilter. See the comments of this method in // subclasses for the exact type of DlImageFilter this method converts to. _ImageFilter _toNativeImageFilter(); @@ -4237,6 +4292,35 @@ class _ComposeImageFilter implements ImageFilter { int get hashCode => Object.hash(innerFilter, outerFilter); } +class _FragmentShaderImageFilter implements ImageFilter { + _FragmentShaderImageFilter(this.shader); + + final FragmentShader shader; + + late final _ImageFilter nativeFilter = _ImageFilter.shader(this); + + @override + _ImageFilter _toNativeImageFilter() => nativeFilter; + + @override + String get _shortDescription => 'shader'; + + @override + String toString() => 'ImageFilter.shader(Shader#${shader.hashCode})'; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is _FragmentShaderImageFilter + && other.shader == shader; + } + + @override + int get hashCode => shader.hashCode; +} + /// An [ImageFilter] that is backed by a native DlImageFilter. /// /// This is a private class, rather than being the implementation of the public @@ -4296,6 +4380,12 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { _initComposed(nativeFilterOuter, nativeFilterInner); } + _ImageFilter.shader(_FragmentShaderImageFilter filter) + : creator = filter { + _constructor(); + _initShader(filter.shader); + } + @Native(symbol: 'ImageFilter::Create') external void _constructor(); @@ -4317,6 +4407,9 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { @Native, Pointer, Pointer)>(symbol: 'ImageFilter::initComposeFilter') external void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter); + @Native, Pointer)>(symbol: 'ImageFilter::initShader') + external void _initShader(FragmentShader shader); + /// The original Dart object that created the native wrapper, which retains /// the values used for the filter. final ImageFilter creator; @@ -4969,6 +5062,9 @@ base class FragmentShader extends Shader { @Native)>(symbol: 'ReusableFragmentShader::ValidateSamplers') external bool _validateSamplers(); + @Native)>(symbol: 'ReusableFragmentShader::ValidateImageFilter') + external bool _validateImageFilter(); + @Native)>(symbol: 'ReusableFragmentShader::Dispose') external void _dispose(); } diff --git a/lib/ui/painting/fragment_program.cc b/lib/ui/painting/fragment_program.cc index 6e63cbd6161ae..2bb8b0e7a9cfb 100644 --- a/lib/ui/painting/fragment_program.cc +++ b/lib/ui/painting/fragment_program.cc @@ -5,6 +5,7 @@ #include #include +#include "display_list/effects/dl_image_filter.h" #include "display_list/effects/dl_runtime_effect.h" #include "flutter/lib/ui/painting/fragment_program.h" @@ -144,6 +145,13 @@ std::shared_ptr FragmentProgram::MakeDlColorSource( std::move(float_uniforms)); } +std::shared_ptr FragmentProgram::MakeDlImageFilter( + std::shared_ptr> float_uniforms, + const std::vector>& children) { + return DlRuntimeEffectImageFilter::Make(runtime_effect_, children, + std::move(float_uniforms)); +} + void FragmentProgram::Create(Dart_Handle wrapper) { auto res = fml::MakeRefCounted(); res->AssociateWithDartWrapper(wrapper); diff --git a/lib/ui/painting/fragment_program.h b/lib/ui/painting/fragment_program.h index 65d4d8809fd9a..65eb460658c51 100644 --- a/lib/ui/painting/fragment_program.h +++ b/lib/ui/painting/fragment_program.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_ #define FLUTTER_LIB_UI_PAINTING_FRAGMENT_PROGRAM_H_ +#include "display_list/effects/dl_image_filter.h" #include "flutter/display_list/effects/dl_runtime_effect.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/shader.h" @@ -38,6 +39,10 @@ class FragmentProgram : public RefCountedDartWrappable { std::shared_ptr> float_uniforms, const std::vector>& children); + std::shared_ptr MakeDlImageFilter( + std::shared_ptr> float_uniforms, + const std::vector>& children); + private: FragmentProgram(); sk_sp runtime_effect_; diff --git a/lib/ui/painting/fragment_shader.cc b/lib/ui/painting/fragment_shader.cc index 491d312e7e6c0..ecf67cafe7be7 100644 --- a/lib/ui/painting/fragment_shader.cc +++ b/lib/ui/painting/fragment_shader.cc @@ -9,15 +9,8 @@ #include "flutter/display_list/dl_tile_mode.h" #include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/fragment_program.h" -#include "flutter/lib/ui/ui_dart_state.h" -#include "third_party/skia/include/core/SkString.h" #include "third_party/tonic/converter/dart_converter.h" -#include "third_party/tonic/dart_args.h" -#include "third_party/tonic/dart_binding_macros.h" -#include "third_party/tonic/dart_library_natives.h" -#include "third_party/tonic/typed_data/typed_list.h" namespace flutter { @@ -55,7 +48,7 @@ Dart_Handle ReusableFragmentShader::Create(Dart_Handle wrapper, } bool ReusableFragmentShader::ValidateSamplers() { - for (auto i = 0u; i < samplers_.size(); i += 1) { + for (auto i = 0u; i < samplers_.size(); i++) { if (samplers_[i] == nullptr) { return false; } @@ -93,6 +86,19 @@ void ReusableFragmentShader::SetImageSampler(Dart_Handle index_handle, uniform_floats[float_count_ + 2 * index + 1] = image->height(); } +std::shared_ptr ReusableFragmentShader::as_image_filter() const { + FML_CHECK(program_); + + // The lifetime of this object is longer than a frame, and the uniforms can be + // continually changed on the UI thread. So we take a copy of the uniforms + // before handing it to the DisplayList for consumption on the render thread. + auto uniform_data = std::make_shared>(); + uniform_data->resize(uniform_data_->size()); + memcpy(uniform_data->data(), uniform_data_->bytes(), uniform_data->size()); + + return program_->MakeDlImageFilter(std::move(uniform_data), samplers_); +} + std::shared_ptr ReusableFragmentShader::shader( DlImageSampling sampling) { FML_CHECK(program_); @@ -111,6 +117,24 @@ std::shared_ptr ReusableFragmentShader::shader( return source; } +// Image filters require at least one uniform sampler input to bind +// the input texture. +bool ReusableFragmentShader::ValidateImageFilter() { + if (samplers_.size() < 1) { + return false; + } + // The first sampler does not need to be set. + for (auto i = 1u; i < samplers_.size(); i++) { + if (samplers_[i] == nullptr) { + return false; + } + // The samplers should have been checked as they were added, this + // is a double-sanity-check. + FML_DCHECK(samplers_[i]->isUIThreadSafe()); + } + return true; +} + void ReusableFragmentShader::Dispose() { uniform_data_.reset(); program_ = nullptr; diff --git a/lib/ui/painting/fragment_shader.h b/lib/ui/painting/fragment_shader.h index e6756b09edd30..e82a22418156a 100644 --- a/lib/ui/painting/fragment_shader.h +++ b/lib/ui/painting/fragment_shader.h @@ -38,11 +38,15 @@ class ReusableFragmentShader : public Shader { bool ValidateSamplers(); + bool ValidateImageFilter(); + void Dispose(); // |Shader| std::shared_ptr shader(DlImageSampling) override; + std::shared_ptr as_image_filter() const; + private: ReusableFragmentShader(fml::RefPtr program, uint64_t float_count, diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc index 5c276e1f42e05..edc2d522f67d7 100644 --- a/lib/ui/painting/image_filter.cc +++ b/lib/ui/painting/image_filter.cc @@ -4,9 +4,13 @@ #include "flutter/lib/ui/painting/image_filter.h" +#include "display_list/dl_sampling_options.h" +#include "display_list/effects/dl_image_filter.h" #include "flutter/lib/ui/floating_point.h" #include "flutter/lib/ui/painting/matrix.h" #include "flutter/lib/ui/ui_dart_state.h" +#include "lib/ui/painting/fragment_program.h" +#include "lib/ui/painting/fragment_shader.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" @@ -116,4 +120,9 @@ void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) { inner->filter(DlTileMode::kClamp)); } +void ImageFilter::initShader(ReusableFragmentShader* shader) { + FML_DCHECK(shader); + filter_ = shader->as_image_filter(); +} + } // namespace flutter diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index af6835de40bd2..642d34950bca7 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -9,6 +9,7 @@ #include "flutter/display_list/effects/dl_image_filter.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/color_filter.h" +#include "lib/ui/painting/fragment_shader.h" #include "third_party/tonic/typed_data/typed_list.h" namespace tonic { @@ -34,6 +35,7 @@ class ImageFilter : public RefCountedDartWrappable { void initMatrix(const tonic::Float64List& matrix4, int filter_quality_index); void initColorFilter(ColorFilter* colorFilter); void initComposeFilter(ImageFilter* outer, ImageFilter* inner); + void initShader(ReusableFragmentShader* shader); const std::shared_ptr filter(DlTileMode mode) const; diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index dce324c9431ca..bb007afc84f02 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -614,6 +614,13 @@ class ImageFilter { factory ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) => engine.renderer.composeImageFilters(outer: outer, inner: inner); + + // ignore: avoid_unused_constructor_parameters + factory ImageFilter.shader(FragmentShader shader) { + throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); + } + + static bool get isShaderFilterSupported => false; } enum ColorSpace { diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart index fbe9f1a01b009..e065a2eb7d186 100644 --- a/testing/dart/fragment_shader_test.dart +++ b/testing/dart/fragment_shader_test.dart @@ -345,6 +345,68 @@ void main() async { shader.dispose(); }); + test('ImageFilter.shader errors if shader does not have correct uniform layout', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } + const List shaders = [ + 'no_uniforms.frag.iplr', + 'missing_size.frag.iplr', + 'missing_texture.frag.iplr' + ]; + const List<(bool, bool)> errors = [ + (true, true), + (true, false), + (false, false) + ]; + for (int i = 0; i < 3; i++) { + final String fileName = shaders[i]; + final FragmentProgram program = await FragmentProgram.fromAsset( + fileName + ); + final FragmentShader shader = program.fragmentShader(); + + Object? error; + try { + ImageFilter.shader(shader); + } catch (err) { + error = err; + } + expect(error is StateError, true); + final (floatError, samplerError) = errors[i]; + if (floatError) { + expect(error.toString(), contains('shader has fewer than two float')); + } + if (samplerError) { + expect(error.toString(), contains('shader is missing a sampler uniform')); + } + } + }); + + test('ImageFilter.shader can be applied to canvas operations', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } + final FragmentProgram program = await FragmentProgram.fromAsset( + 'filter_shader.frag.iplr', + ); + final FragmentShader shader = program.fragmentShader(); + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawPaint( + Paint() + ..color = const Color(0xFFFF0000) + ..imageFilter = ImageFilter.shader(shader) + ); + final Image image = await recorder.endRecording().toImage(1, 1); + final ByteData data = (await image.toByteData())!; + final Color color = Color(data.buffer.asUint32List()[0]); + + expect(color, const Color(0xFF00FF00)); + }); + if (impellerEnabled) { print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); return; diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index c1815e741af11..88bd51d6e2ea6 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -8,6 +8,7 @@ #include #include "flutter/display_list/display_list.h" +#include "flutter/display_list/effects/dl_image_filter.h" namespace flutter { namespace testing { @@ -662,6 +663,12 @@ void DisplayListStreamDispatcher::out(const DlImageFilter& filter) { startl() << ")"; break; } + case flutter::DlImageFilterType::kRuntimeEffect: { + [[maybe_unused]] const DlRuntimeEffectImageFilter* runtime_effect = filter.asRuntimeEffectFilter(); + FML_DCHECK(runtime_effect); + os_ << "DlRuntimeEffectImageFilter()"; + break; + } } } void DisplayListStreamDispatcher::out(const DlImageFilter* filter) { diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h index 96b15e67ccd4d..6dc8f8136ca37 100644 --- a/testing/display_list_testing.h +++ b/testing/display_list_testing.h @@ -7,6 +7,7 @@ #include +#include "display_list/effects/dl_image_filter.h" #include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_op_receiver.h" @@ -285,6 +286,7 @@ class DisplayListGeneralReceiver : public DlOpReceiver { case DlImageFilterType::kCompose: case DlImageFilterType::kLocalMatrix: case DlImageFilterType::kColorFilter: + case DlImageFilterType::kRuntimeEffect: RecordByType(DisplayListOpType::kSetSharedImageFilter); break; } diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index f1a11f0a62a4d..58897eb92243b 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -418,6 +418,9 @@ impeller_Play_AiksTest_CanRenderRadialGradient_Vulkan.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_Metal.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_OpenGLES.png impeller_Play_AiksTest_CanRenderRoundedRectWithNonUniformRadii_Vulkan.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_Metal.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_OpenGLES.png +impeller_Play_AiksTest_CanRenderRuntimeEffectFilter_Vulkan.png impeller_Play_AiksTest_CanRenderSimpleClips_Metal.png impeller_Play_AiksTest_CanRenderSimpleClips_OpenGLES.png impeller_Play_AiksTest_CanRenderSimpleClips_Vulkan.png