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_FRAG_INFO(t) \
197  t##GradientUniformFillPipeline::FragmentShader::FragInfo
198 #define UNIFORM_COLOR_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Linear)::colors)
199 #define UNIFORM_STOP_SIZE ARRAY_LEN(UNIFORM_FRAG_INFO(Linear)::stop_pairs)
201 static_assert(UNIFORM_STOP_SIZE == kMaxUniformGradientStops / 2);
202 
204  const Entity& entity,
205  RenderPass& pass) const {
206  // TODO(148651): The fast path is overly restrictive, following the design in
207  // https://github.com/flutter/flutter/issues/148651 support for more cases can
208  // be gradually added.
209  if (CanApplyFastGradient()) {
210  return FastLinearGradient(renderer, entity, pass);
211  }
212  if (renderer.GetDeviceCapabilities().SupportsSSBO()) {
213  return RenderSSBO(renderer, entity, pass);
214  }
215  if (colors_.size() <= kMaxUniformGradientStops &&
216  stops_.size() <= kMaxUniformGradientStops) {
217  return RenderUniform(renderer, entity, pass);
218  }
219  return RenderTexture(renderer, entity, pass);
220 }
221 
222 bool LinearGradientContents::RenderTexture(const ContentContext& renderer,
223  const Entity& entity,
224  RenderPass& pass) const {
227 
228  VS::FrameInfo frame_info;
229  frame_info.matrix = GetInverseEffectTransform();
230 
231  PipelineBuilderCallback pipeline_callback =
232  [&renderer](ContentContextOptions options) {
233  return renderer.GetLinearGradientFillPipeline(options);
234  };
235  return ColorSourceContents::DrawGeometry<VS>(
236  renderer, entity, pass, pipeline_callback, frame_info,
237  [this, &renderer, &entity](RenderPass& pass) {
238  auto gradient_data = CreateGradientBuffer(colors_, stops_);
239  auto gradient_texture =
240  CreateGradientTexture(gradient_data, renderer.GetContext());
241  if (gradient_texture == nullptr) {
242  return false;
243  }
244 
245  FS::FragInfo frag_info;
246  frag_info.start_point = start_point_;
247  frag_info.end_point = end_point_;
248  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
249  frag_info.decal_border_color = decal_border_color_;
250  frag_info.texture_sampler_y_coord_scale =
251  gradient_texture->GetYCoordScale();
252  frag_info.alpha =
253  GetOpacityFactor() *
255  ;
256  frag_info.half_texel =
257  Vector2(0.5 / gradient_texture->GetSize().width,
258  0.5 / gradient_texture->GetSize().height);
259 
260  pass.SetCommandLabel("LinearGradientFill");
261 
262  SamplerDescriptor sampler_desc;
263  sampler_desc.min_filter = MinMagFilter::kLinear;
264  sampler_desc.mag_filter = MinMagFilter::kLinear;
265 
266  FS::BindTextureSampler(
267  pass, std::move(gradient_texture),
268  renderer.GetContext()->GetSamplerLibrary()->GetSampler(
269  sampler_desc));
270  FS::BindFragInfo(
271  pass, renderer.GetTransientsDataBuffer().EmplaceUniform(frag_info));
272  return true;
273  });
274 }
275 
276 namespace {
277 Scalar CalculateInverseDotStartToEnd(Point start_point, Point end_point) {
278  Point start_to_end = end_point - start_point;
279  Scalar dot =
280  (start_to_end.x * start_to_end.x + start_to_end.y * start_to_end.y);
281  return dot == 0.0f ? 0.0f : 1.0f / dot;
282 }
283 } // namespace
284 
285 bool LinearGradientContents::RenderSSBO(const ContentContext& renderer,
286  const Entity& entity,
287  RenderPass& pass) const {
290 
291  VS::FrameInfo frame_info;
292  frame_info.matrix = GetInverseEffectTransform();
293 
294  PipelineBuilderCallback pipeline_callback =
295  [&renderer](ContentContextOptions options) {
296  return renderer.GetLinearGradientSSBOFillPipeline(options);
297  };
298  return ColorSourceContents::DrawGeometry<VS>(
299  renderer, entity, pass, pipeline_callback, frame_info,
300  [this, &renderer, &entity](RenderPass& pass) {
301  FS::FragInfo frag_info;
302  frag_info.start_point = start_point_;
303  frag_info.end_point = end_point_;
304  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
305  frag_info.decal_border_color = decal_border_color_;
306  frag_info.alpha =
307  GetOpacityFactor() *
308  GetGeometry()->ComputeAlphaCoverage(entity.GetTransform());
309  frag_info.start_to_end = end_point_ - start_point_;
310  frag_info.inverse_dot_start_to_end =
311  CalculateInverseDotStartToEnd(start_point_, end_point_);
312 
313  auto& data_host_buffer = renderer.GetTransientsDataBuffer();
314  auto colors = CreateGradientColors(colors_, stops_);
315 
316  frag_info.colors_length = colors.size();
317  auto color_buffer = data_host_buffer.Emplace(
318  colors.data(), colors.size() * sizeof(StopData),
319  renderer.GetDeviceCapabilities()
320  .GetMinimumStorageBufferAlignment());
321 
322  pass.SetCommandLabel("LinearGradientSSBOFill");
323 
324  FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info));
325  FS::BindColorData(pass, color_buffer);
326 
327  return true;
328  });
329 }
330 
331 bool LinearGradientContents::RenderUniform(const ContentContext& renderer,
332  const Entity& entity,
333  RenderPass& pass) const {
336 
337  VS::FrameInfo frame_info;
338  frame_info.matrix = GetInverseEffectTransform();
339 
340  PipelineBuilderCallback pipeline_callback =
341  [&renderer](ContentContextOptions options) {
342  return renderer.GetLinearGradientUniformFillPipeline(options);
343  };
344  return ColorSourceContents::DrawGeometry<VS>(
345  renderer, entity, pass, pipeline_callback, frame_info,
346  [this, &renderer, &entity](RenderPass& pass) {
347  FS::FragInfo frag_info;
348  frag_info.start_point = start_point_;
349  frag_info.start_to_end = end_point_ - start_point_;
350  frag_info.alpha =
351  GetOpacityFactor() *
352  GetGeometry()->ComputeAlphaCoverage(entity.GetTransform());
353  frag_info.tile_mode = static_cast<Scalar>(tile_mode_);
354  frag_info.colors_length = PopulateUniformGradientColors(
355  colors_, stops_, frag_info.colors, frag_info.stop_pairs);
356  frag_info.inverse_dot_start_to_end =
357  CalculateInverseDotStartToEnd(start_point_, end_point_);
358  frag_info.decal_border_color = decal_border_color_;
359 
360  pass.SetCommandLabel("LinearGradientUniformFill");
361 
362  FS::BindFragInfo(
363  pass, renderer.GetTransientsDataBuffer().EmplaceUniform(frag_info));
364 
365  return true;
366  });
367 }
368 
370  const ColorFilterProc& color_filter_proc) {
371  for (Color& color : colors_) {
372  color = color_filter_proc(color);
373  }
374  decal_border_color_ = color_filter_proc(decal_border_color_);
375  return true;
376 }
377 
378 } // 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
Set 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:125
virtual std::optional< Rect > GetCoverage(const Matrix &transform) const =0
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_SIZE
#define UNIFORM_COLOR_SIZE
Point Vector2
Definition: point.h:430
float Scalar
Definition: scalar.h:19
TRect< Scalar > Rect
Definition: rect.h:788
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