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