Flutter Impeller
linear_gradient_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 
10 #include "impeller/entity/entity.h"
15 
16 namespace impeller {
17 
19  : geometry_(geometry) {}
20 
22 
24  return geometry_;
25 }
26 
27 void LinearGradientContents::SetEndPoints(Point start_point, Point end_point) {
28  start_point_ = start_point;
29  end_point_ = end_point;
30 }
31 
32 void LinearGradientContents::SetColors(std::vector<Color> colors) {
33  colors_ = std::move(colors);
34 }
35 
36 void LinearGradientContents::SetStops(std::vector<Scalar> stops) {
37  stops_ = std::move(stops);
38 }
39 
40 const std::vector<Color>& LinearGradientContents::GetColors() const {
41  return colors_;
42 }
43 
44 const std::vector<Scalar>& LinearGradientContents::GetStops() const {
45  return stops_;
46 }
47 
49  tile_mode_ = tile_mode;
50 }
51 
53  if (GetOpacityFactor() < 1 || tile_mode_ == Entity::TileMode::kDecal) {
54  return false;
55  }
56  for (auto color : colors_) {
57  if (!color.IsOpaque()) {
58  return false;
59  }
60  }
62 }
63 
64 bool LinearGradientContents::CanApplyFastGradient() const {
65  if (!GetInverseEffectTransform().IsIdentity()) {
66  return false;
67  }
68  std::optional<Rect> maybe_rect = GetGeometry()->GetCoverage(Matrix());
69  if (!maybe_rect.has_value()) {
70  return false;
71  }
72  Rect rect = maybe_rect.value();
73 
74  if (ScalarNearlyEqual(start_point_.x, end_point_.x)) {
75  // Sort start and end to make on-rect comparisons easier.
76  Point start = (start_point_.y < end_point_.y) ? start_point_ : end_point_;
77  Point end = (start_point_.y < end_point_.y) ? end_point_ : start_point_;
78  // The exact x positon doesn't matter for a vertical gradient, but the y
79  // position must be nearly on the rectangle.
80  if (ScalarNearlyEqual(start.y, rect.GetTop()) &&
81  ScalarNearlyEqual(end.y, rect.GetBottom())) {
82  return true;
83  }
84  return false;
85  }
86 
87  if (ScalarNearlyEqual(start_point_.y, end_point_.y)) {
88  // Sort start and end to make on-rect comparisons easier.
89  Point start = (start_point_.x < end_point_.x) ? start_point_ : end_point_;
90  Point end = (start_point_.x < end_point_.x) ? end_point_ : start_point_;
91  // The exact y positon doesn't matter for a horizontal gradient, but the x
92  // position must be nearly on the rectangle.
93  if (ScalarNearlyEqual(start.x, rect.GetLeft()) &&
94  ScalarNearlyEqual(end.x, rect.GetRight())) {
95  return true;
96  }
97  return false;
98  }
99 
100  return false;
101 }
102 
103 // A much faster (in terms of ALU) linear gradient that uses vertex
104 // interpolation to perform all color computation. Requires that the geometry of
105 // the gradient is divided into regions based on the stop values.
106 // Currently restricted to rect geometry where the start and end points are
107 // perfectly horizontal/vertical, but could easily be expanded to StC cases
108 // provided that the start/end are on or outside of the coverage rect.
109 bool LinearGradientContents::FastLinearGradient(const ContentContext& renderer,
110  const Entity& entity,
111  RenderPass& pass) const {
114 
115  const Geometry* geometry = GetGeometry();
116  bool force_stencil = !geometry->IsAxisAlignedRect();
117 
118  auto geom_callback = [&](const ContentContext& renderer, const Entity& entity,
119  RenderPass& pass,
120  const Geometry* geometry) -> GeometryResult {
121  // We already know this is an axis aligned rectangle, so the coverage will
122  // be approximately the same as the geometry. For non axis-algined
123  // rectangles, we can force stencil then cover (not done here). We give an
124  // identity transform to avoid double transforming the gradient.
125  std::optional<Rect> maybe_rect = geometry->GetCoverage(Matrix());
126  if (!maybe_rect.has_value()) {
127  return {};
128  }
129  Rect rect = maybe_rect.value();
130  bool horizontal_axis = start_point_.y == end_point_.y;
131 
132  // Compute the locations of each breakpoint along the primary axis, then
133  // create a rectangle that joins each segment. There will be two triangles
134  // between each pair of points.
135  VertexBufferBuilder<VS::PerVertexData> vtx_builder;
136  vtx_builder.Reserve(6 * (stops_.size() - 1));
137  Point prev = start_point_;
138  for (auto i = 1u; i < stops_.size(); i++) {
139  Scalar t = stops_[i];
140  Point current = (1.0 - t) * start_point_ + t * end_point_;
141  Rect section = horizontal_axis
142  ? Rect::MakeXYWH(prev.x, rect.GetY(),
143  current.x - prev.x, rect.GetHeight())
144 
145  : Rect::MakeXYWH(rect.GetX(), prev.y, rect.GetWidth(),
146  current.y - prev.y);
147  vtx_builder.AddVertices({
148  {section.GetLeftTop(), colors_[i - 1]},
149  {section.GetRightTop(),
150  horizontal_axis ? colors_[i] : colors_[i - 1]},
151  {section.GetLeftBottom(),
152  horizontal_axis ? colors_[i - 1] : colors_[i]},
153  {section.GetRightTop(),
154  horizontal_axis ? colors_[i] : colors_[i - 1]},
155  {section.GetLeftBottom(),
156  horizontal_axis ? colors_[i - 1] : colors_[i]},
157  {section.GetRightBottom(), colors_[i]},
158  });
159  prev = current;
160  }
161  return GeometryResult{
162  .type = PrimitiveType::kTriangle,
163  .vertex_buffer = vtx_builder.CreateVertexBuffer(
164  renderer.GetTransientsDataBuffer(),
165  renderer.GetTransientsIndexesBuffer()),
166  .transform = entity.GetShaderTransform(pass),
167  };
168  };
169 
170  pass.SetLabel("LinearGradient");
171 
172  VS::FrameInfo frame_info;
173 
174  PipelineBuilderCallback pipeline_callback =
175  [&renderer](ContentContextOptions options) {
176  return renderer.GetFastGradientPipeline(options);
177  };
178  return ColorSourceContents::DrawGeometry<VS>(
179  renderer, entity, pass, pipeline_callback, frame_info,
180  [this, &renderer, &entity](RenderPass& pass) {
181  auto& data_host_buffer = renderer.GetTransientsDataBuffer();
182 
183  FS::FragInfo frag_info;
184  frag_info.alpha =
185  GetOpacityFactor() *
186  GetGeometry()->ComputeAlphaCoverage(entity.GetTransform());
187 
188  FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info));
189 
190  return true;
191  },
192  /*force_stencil=*/force_stencil, geom_callback);
193 }
194 
195 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
196 #define UNIFORM_COLORS_INFO(t) \
197  t##GradientUniformFillPipeline::FragmentShader::ColorsInfo
198 #define UNIFORM_STOP_PAIRS_INFO(t) \
199  t##GradientUniformFillPipeline::FragmentShader::StopPairsInfo
200 #define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_COLORS_INFO(Linear)::colors)
201 #define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_STOP_PAIRS_INFO(Linear)::stop_pairs)
203 static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2);
204 static_assert(sizeof(UNIFORM_COLORS_INFO(Linear)) ==
205  sizeof(UNIFORM_COLORS_INFO(Linear)::colors));
206 static_assert(sizeof(UNIFORM_STOP_PAIRS_INFO(Linear)) ==
207  sizeof(UNIFORM_STOP_PAIRS_INFO(Linear)::stop_pairs));
208 
210  const Entity& entity,
211  RenderPass& pass) const {
212  // TODO(148651): The fast path is overly restrictive, following the design in
213  // https://github.com/flutter/flutter/issues/148651 support for more cases can
214  // be gradually added.
215  if (CanApplyFastGradient()) {
216  return FastLinearGradient(renderer, entity, pass);
217  }
218  if (renderer.GetDeviceCapabilities().SupportsSSBO()) {
219  return RenderSSBO(renderer, entity, pass);
220  }
221  if (colors_.size() <= kMaxUniformGradientStops &&
222  stops_.size() <= kMaxUniformGradientStops) {
223  return RenderUniform(renderer, entity, pass);
224  }
225  return RenderTexture(renderer, entity, pass);
226 }
227 
228 bool LinearGradientContents::RenderTexture(const ContentContext& renderer,
229  const Entity& entity,
230  RenderPass& pass) const {
233 
234  VS::FrameInfo frame_info;
235  frame_info.matrix = GetInverseEffectTransform();
236 
237  PipelineBuilderCallback pipeline_callback =
238  [&renderer](ContentContextOptions options) {
239  return renderer.GetLinearGradientFillPipeline(options);
240  };
241  return ColorSourceContents::DrawGeometry<VS>(
242  renderer, entity, pass, pipeline_callback, frame_info,
243  [this, &renderer, &entity](RenderPass& pass) {
244  auto gradient_data = CreateGradientBuffer(colors_, stops_);
245  auto gradient_texture =
246  CreateGradientTexture(gradient_data, renderer.GetContext());
247  if (gradient_texture == nullptr) {
248  return false;
249  }
250 
251  FS::FragInfo frag_info;
252  frag_info.start_point = start_point_;
253  frag_info.end_point = end_point_;
254  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
255  frag_info.decal_border_color = decal_border_color_;
256  frag_info.alpha =
257  GetOpacityFactor() *
259  ;
260  frag_info.half_texel =
261  Vector2(0.5 / gradient_texture->GetSize().width,
262  0.5 / gradient_texture->GetSize().height);
263 
264  pass.SetCommandLabel("LinearGradientFill");
265 
266  SamplerDescriptor sampler_desc;
267  sampler_desc.min_filter = MinMagFilter::kLinear;
268  sampler_desc.mag_filter = MinMagFilter::kLinear;
269 
270  FS::BindTextureSampler(
271  pass, std::move(gradient_texture),
272  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
273  sampler_desc));
274  FS::BindFragInfo(
275  pass, renderer.GetTransientsDataBuffer().EmplaceUniform(frag_info));
276  return true;
277  });
278 }
279 
280 namespace {
281 Scalar CalculateInverseDotStartToEnd(Point start_point, Point end_point) {
282  Point start_to_end = end_point - start_point;
283  Scalar dot =
284  (start_to_end.x * start_to_end.x + start_to_end.y * start_to_end.y);
285  return dot == 0.0f ? 0.0f : 1.0f / dot;
286 }
287 } // namespace
288 
289 bool LinearGradientContents::RenderSSBO(const ContentContext& renderer,
290  const Entity& entity,
291  RenderPass& pass) const {
294 
295  VS::FrameInfo frame_info;
296  frame_info.matrix = GetInverseEffectTransform();
297 
298  PipelineBuilderCallback pipeline_callback =
299  [&renderer](ContentContextOptions options) {
300  return renderer.GetLinearGradientSSBOFillPipeline(options);
301  };
302  return ColorSourceContents::DrawGeometry<VS>(
303  renderer, entity, pass, pipeline_callback, frame_info,
304  [this, &renderer, &entity](RenderPass& pass) {
305  FS::FragInfo frag_info;
306  frag_info.start_point = start_point_;
307  frag_info.end_point = end_point_;
308  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
309  frag_info.decal_border_color = decal_border_color_;
310  frag_info.alpha =
311  GetOpacityFactor() *
312  GetGeometry()->ComputeAlphaCoverage(entity.GetTransform());
313  frag_info.start_to_end = end_point_ - start_point_;
314  frag_info.inverse_dot_start_to_end =
315  CalculateInverseDotStartToEnd(start_point_, end_point_);
316 
317  auto& data_host_buffer = renderer.GetTransientsDataBuffer();
318  auto colors = CreateGradientColors(colors_, stops_);
319 
320  frag_info.colors_length = colors.size();
321  auto color_buffer = data_host_buffer.Emplace(
322  colors.data(), colors.size() * sizeof(StopData),
323  renderer.GetDeviceCapabilities()
324  .GetMinimumStorageBufferAlignment());
325 
326  pass.SetCommandLabel("LinearGradientSSBOFill");
327 
328  FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info));
329  FS::BindColorData(pass, color_buffer);
330 
331  return true;
332  });
333 }
334 
335 bool LinearGradientContents::RenderUniform(const ContentContext& renderer,
336  const Entity& entity,
337  RenderPass& pass) const {
340 
341  VS::FrameInfo frame_info;
342  frame_info.matrix = GetInverseEffectTransform();
343 
344  PipelineBuilderCallback pipeline_callback =
345  [&renderer](ContentContextOptions options) {
346  return renderer.GetLinearGradientUniformFillPipeline(options);
347  };
348  return ColorSourceContents::DrawGeometry<VS>(
349  renderer, entity, pass, pipeline_callback, frame_info,
350  [this, &renderer, &entity](RenderPass& pass) {
351  FS::FragInfo frag_info;
352  FS::ColorsInfo colors_info;
353  FS::StopPairsInfo stop_pairs_info;
354 
355  frag_info.start_point = start_point_;
356  frag_info.start_to_end = end_point_ - start_point_;
357  frag_info.alpha =
358  GetOpacityFactor() *
359  GetGeometry()->ComputeAlphaCoverage(entity.GetTransform());
360  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
361  frag_info.colors_length = PopulateUniformGradientColors(
362  colors_, stops_, colors_info.colors, stop_pairs_info.stop_pairs);
363  frag_info.inverse_dot_start_to_end =
364  CalculateInverseDotStartToEnd(start_point_, end_point_);
365  frag_info.decal_border_color = decal_border_color_;
366 
367  pass.SetCommandLabel("LinearGradientUniformFill");
368 
369  auto& transients_buffer = renderer.GetTransientsDataBuffer();
370  FS::BindFragInfo(pass, transients_buffer.EmplaceUniform(frag_info));
371  FS::BindColorsInfo(pass, transients_buffer.EmplaceUniform(colors_info));
372  FS::BindStopPairsInfo(
373  pass, transients_buffer.EmplaceUniform(stop_pairs_info));
374 
375  return true;
376  });
377 }
378 
380  const ColorFilterProc& color_filter_proc) {
381  for (Color& color : colors_) {
382  color = color_filter_proc(color);
383  }
384  decal_border_color_ = color_filter_proc(decal_border_color_);
385  return true;
386 }
387 
388 } // namespace impeller
virtual bool SupportsSSBO() const =0
Whether the context backend supports binding Shader Storage Buffer Objects (SSBOs) to pipelines.
Scalar GetOpacityFactor() const
Get the opacity factor for this color source.
bool AppliesAlphaForStrokeCoverage(const Matrix &transform) const
Whether the entity should be treated as non-opaque due to stroke geometry requiring alpha for coverag...
const Matrix & GetInverseEffectTransform() const
Get the inverted effect transform for this color source.
std::function< PipelineRef(ContentContextOptions)> PipelineBuilderCallback
PipelineRef GetLinearGradientFillPipeline(ContentContextOptions opts) const
const Capabilities & GetDeviceCapabilities() const
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
std::shared_ptr< Context > GetContext() const
std::function< Color(Color)> ColorFilterProc
Definition: contents.h:35
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
virtual Scalar ComputeAlphaCoverage(const Matrix &transform) const
Definition: geometry.h:135
virtual std::optional< Rect > GetCoverage(const Matrix &transform) const =0
The coverage rectangle of this geometry, transformed by the transform argument.
BufferView EmplaceUniform(const UniformType &uniform)
Emplace uniform data onto the host buffer. Ensure that backend specific uniform alignment requirement...
Definition: host_buffer.h:47
LinearGradientContents(const Geometry *geometry)
const std::vector< Color > & GetColors() const
void SetTileMode(Entity::TileMode tile_mode)
const std::vector< Scalar > & GetStops() const
void SetColors(std::vector< Color > colors)
bool ApplyColorFilter(const ColorFilterProc &color_filter_proc) override
If possible, applies a color filter to this contents inputs on the CPU.
const Geometry * GetGeometry() const override
Get the geometry that this contents will use to render.
bool Render(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
void SetEndPoints(Point start_point, Point end_point)
void SetStops(std::vector< Scalar > stops)
bool IsOpaque(const Matrix &transform) const override
Whether this Contents only emits opaque source colors from the fragment stage. This value does not ac...
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
FragmentShader_ FragmentShader
Definition: pipeline.h:169
#define UNIFORM_STOP_PAIRS_INFO(t)
#define UNIFORM_STOP_SIZE
#define UNIFORM_COLORS_INFO(t)
#define UNIFORM_COLOR_SIZE
Point Vector2
Definition: point.h:430
float Scalar
Definition: scalar.h:19
TRect< Scalar > Rect
Definition: rect.h:857
TPoint< Scalar > Point
Definition: point.h:426
LinePipeline::FragmentShader FS
int PopulateUniformGradientColors(const std::vector< Color > &colors, const std::vector< Scalar > &stops, Vector4 frag_info_colors[kMaxUniformGradientStops], Vector4 frag_info_stop_pairs[kMaxUniformGradientStops/2])
Populate 2 arrays with the colors and stop data for a gradient.
std::vector< StopData > CreateGradientColors(const std::vector< Color > &colors, const std::vector< Scalar > &stops)
Populate a vector with the color and stop data for a gradient.
LinePipeline::VertexShader VS
std::shared_ptr< Texture > CreateGradientTexture(const GradientData &gradient_data, const std::shared_ptr< impeller::Context > &context)
Create a host visible texture that contains the gradient defined by the provided gradient data.
GradientData CreateGradientBuffer(const std::vector< Color > &colors, const std::vector< Scalar > &stops)
Populate a vector with the interpolated color bytes for the linear gradient described by colors and s...
Definition: gradient.cc:20
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:36
static constexpr uint32_t kMaxUniformGradientStops
Scalar alpha
Definition: color.h:143
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
const size_t start
const size_t end