Flutter Impeller
runtime_effect_filter_contents.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <cstring>
8 #include <optional>
9 
15 #include "impeller/geometry/size.h"
16 
17 namespace impeller {
18 
20  std::shared_ptr<RuntimeStage> runtime_stage) {
21  runtime_stage_ = std::move(runtime_stage);
22 }
23 
25  std::shared_ptr<std::vector<uint8_t>> uniforms) {
26  uniforms_ = std::move(uniforms);
27 }
28 
30  std::vector<RuntimeEffectContents::TextureInput> texture_inputs) {
31  texture_inputs_ = std::move(texture_inputs);
32 }
33 
34 // |FilterContents|
35 std::optional<Entity> RuntimeEffectFilterContents::RenderFilter(
36  const FilterInput::Vector& inputs,
37  const ContentContext& renderer,
38  const Entity& entity,
39  const Matrix& effect_transform,
40  const Rect& coverage,
41  const std::optional<Rect>& coverage_hint) const {
42  if (inputs.empty()) {
43  return std::nullopt;
44  }
45 
46  std::optional<Snapshot> input_snapshot =
47  inputs[0]->GetSnapshot("RuntimeEffectContents", renderer, entity);
48  if (!input_snapshot.has_value()) {
49  return std::nullopt;
50  }
51 
52  std::optional<Rect> maybe_input_coverage = input_snapshot->GetCoverage();
53  if (!maybe_input_coverage.has_value()) {
54  return std::nullopt;
55  }
56 
57  // If the input snapshot does not have an identity transform the
58  // ImageFilter.shader will not correctly render as it does not know what the
59  // transform is in order to incorporate this into sampling. We need to
60  // re-rasterize the input snapshot so that the transform is absorbed into the
61  // texture.
62  // We can technically render this only when the snapshot is just a translated
63  // version of the original. Unfortunately there isn't a way to test for that
64  // though. Blur with low sigmas will return a transform that doesn't scale but
65  // has a tiny offset to account for the blur radius. That's indistinguishable
66  // from `ImageFilter.compose` which slightly increase the size to account for
67  // rounding errors and add an offset. Said another way; ideally we would skip
68  // this branch for the unit test `ComposePaintRuntimeOuter`, but do it for
69  // `ComposeBackdropRuntimeOuterBlurInner`.
70  if (input_snapshot->ShouldRasterizeForRuntimeEffects()) {
71  Vector2 entity_offset =
72  Vector2(entity.GetTransform().m[12], entity.GetTransform().m[13]);
73  Matrix inverse = input_snapshot->transform.Invert();
74  Quad quad = inverse.Transform(Quad{
75  coverage.GetLeftTop(), //
76  coverage.GetRightTop(), //
77  coverage.GetLeftBottom(), //
78  coverage.GetRightBottom() //
79  });
80  TextureContents texture_contents;
81  texture_contents.SetTexture(input_snapshot->texture);
82  std::optional<Rect> bounds =
83  Rect::MakePointBounds(quad.begin(), quad.end());
84  if (bounds.has_value()) {
85  texture_contents.SetSourceRect(bounds.value());
86  texture_contents.SetDestinationRect(coverage);
87  texture_contents.SetStencilEnabled(false);
88  texture_contents.SetSamplerDescriptor(input_snapshot->sampler_descriptor);
89 
90  // Use an AnonymousContents to restore the padding around the input that
91  // may have been cut out with a clip rect to maintain the correct
92  // coordinates for the fragment shader to perform.
93  auto anonymous_contents = AnonymousContents::Make(
94  [&texture_contents](const ContentContext& renderer,
95  const Entity& entity, RenderPass& pass) -> bool {
96  return texture_contents.Render(renderer, entity, pass);
97  },
98  [maybe_input_coverage,
99  entity_offset](const Entity& entity) -> std::optional<Rect> {
100  Rect coverage = maybe_input_coverage.value();
101  // The LT values come from the offset of the clip rect, that creates
102  // the clipping effect on the content that will be rendered from
103  // the fragment shader. The RB values define the region we'll be
104  // synthesizing and ultimately defines the width and the height of
105  // the rasterized image. The LT values can be thought of shifting
106  // the window that will be rasterized. Since we are shifting from
107  // the top-left corner, that is effectively pushing the the bottom
108  // right corner lower, outside of the rendering space. So, we can
109  // clamp those values to the coverage's RB values. This doesn't
110  // cause the fragment shader's rendering to deform because the
111  // magic width/height values sent to the fragment shader don't take
112  // the rasterized image's size into account.
113  return Rect::MakeLTRB(entity_offset.x, entity_offset.y,
114  coverage.GetRight(), coverage.GetBottom());
115  });
116 
117  Entity entity;
118  // In order to maintain precise coordinates in the fragment shader we need
119  // to eliminate the padding typically given to RenderToSnapshot results.
120  input_snapshot = anonymous_contents->RenderToSnapshot(
121  renderer, entity, {.coverage_expansion = 0});
122  if (!input_snapshot.has_value()) {
123  return std::nullopt;
124  }
125  }
126  }
127 
128  // The shader is required to have at least one sampler, the first of
129  // which is treated as the input and a vec2 size uniform to compute the
130  // offsets. These are validated at the dart:ui layer, but to avoid crashes we
131  // check here too.
132  if (texture_inputs_.size() < 1 || uniforms_->size() < 8) {
134  << "Invalid fragment shader in RuntimeEffectFilterContents. "
135  << "Shader must have at least one sampler and a vec2 size uniform.";
136  return std::nullopt;
137  }
138 
139  // Update uniform values.
140  std::vector<RuntimeEffectContents::TextureInput> texture_input_copy =
141  texture_inputs_;
142  texture_input_copy[0].texture = input_snapshot->texture;
143 
144  Size size = Size(input_snapshot->texture->GetSize());
145  memcpy(uniforms_->data(), &size, sizeof(Size));
146 
147  Matrix snapshot_transform = input_snapshot->transform;
148  //----------------------------------------------------------------------------
149  /// Create AnonymousContents for rendering.
150  ///
151  RenderProc render_proc =
152  [snapshot_transform, input_snapshot, runtime_stage = runtime_stage_,
153  uniforms = uniforms_, texture_inputs = texture_input_copy](
154  const ContentContext& renderer, const Entity& entity,
155  RenderPass& pass) -> bool {
156  RuntimeEffectContents contents;
157  FillRectGeometry geom(Rect::MakeSize(input_snapshot->texture->GetSize()));
158  contents.SetRuntimeStage(runtime_stage);
159  contents.SetUniformData(uniforms);
160  contents.SetTextureInputs(texture_inputs);
161  contents.SetGeometry(&geom);
162  Entity offset_entity = entity.Clone();
163  offset_entity.SetTransform(entity.GetTransform() * snapshot_transform);
164  return contents.Render(renderer, offset_entity, pass);
165  };
166 
167  CoverageProc coverage_proc =
168  [coverage](const Entity& entity) -> std::optional<Rect> {
169  return coverage;
170  };
171 
172  auto contents = AnonymousContents::Make(render_proc, coverage_proc);
173 
174  Entity sub_entity;
175  sub_entity.SetContents(std::move(contents));
176  sub_entity.SetBlendMode(entity.GetBlendMode());
177  sub_entity.SetTransform(input_snapshot->transform *
178  snapshot_transform.Invert());
179 
180  return sub_entity;
181 }
182 
183 // |FilterContents|
184 std::optional<Rect> RuntimeEffectFilterContents::GetFilterSourceCoverage(
185  const Matrix& effect_transform,
186  const Rect& output_limit) const {
187  return output_limit;
188 }
189 
190 } // namespace impeller
static std::shared_ptr< Contents > Make(RenderProc render_proc, CoverageProc coverage_proc)
std::function< std::optional< Rect >(const Entity &entity)> CoverageProc
Definition: contents.h:40
std::function< bool(const ContentContext &renderer, const Entity &entity, RenderPass &pass)> RenderProc
Definition: contents.h:39
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
void SetTextureInputs(std::vector< RuntimeEffectContents::TextureInput > texture_inputs)
void SetUniforms(std::shared_ptr< std::vector< uint8_t >> uniforms)
void SetRuntimeStage(std::shared_ptr< RuntimeStage > runtime_stage)
Point Vector2
Definition: point.h:429
TRect< Scalar > Rect
Definition: rect.h:788
TSize< Scalar > Size
Definition: size.h:159
std::array< Point, 4 > Quad
Definition: point.h:430
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
Scalar m[16]
Definition: matrix.h:39
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:165
constexpr TPoint< T > GetLeftBottom() const
Definition: rect.h:367
constexpr TPoint< T > GetRightTop() const
Definition: rect.h:363
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr TPoint< T > GetRightBottom() const
Definition: rect.h:371
constexpr TPoint< T > GetLeftTop() const
Definition: rect.h:359
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
#define VALIDATION_LOG
Definition: validation.h:91