Flutter Impeller
experimental_canvas.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 #include "fml/logging.h"
7 #include "impeller/aiks/canvas.h"
12 #include "impeller/entity/entity.h"
16 
17 namespace impeller {
18 
19 namespace {
20 
21 static void SetClipScissor(std::optional<Rect> clip_coverage,
22  RenderPass& pass,
23  Point global_pass_position) {
24  // Set the scissor to the clip coverage area. We do this prior to rendering
25  // the clip itself and all its contents.
26  IRect scissor;
27  if (clip_coverage.has_value()) {
28  clip_coverage = clip_coverage->Shift(-global_pass_position);
29  scissor = IRect::RoundOut(clip_coverage.value());
30  // The scissor rect must not exceed the size of the render target.
31  scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize()))
32  .value_or(IRect());
33  }
34  pass.SetScissor(scissor);
35 }
36 
37 static void ApplyFramebufferBlend(Entity& entity) {
38  auto src_contents = entity.GetContents();
39  auto contents = std::make_shared<FramebufferBlendContents>();
40  contents->SetChildContents(src_contents);
41  contents->SetBlendMode(entity.GetBlendMode());
42  entity.SetContents(std::move(contents));
43  entity.SetBlendMode(BlendMode::kSource);
44 }
45 
46 } // namespace
47 
51  .load_action = LoadAction::kDontCare,
52  .store_action = StoreAction::kDontCare,
53  };
54 
55 static std::unique_ptr<EntityPassTarget> CreateRenderTarget(
56  ContentContext& renderer,
57  ISize size,
58  int mip_count,
59  const Color& clear_color) {
60  const std::shared_ptr<Context>& context = renderer.GetContext();
61 
62  /// All of the load/store actions are managed by `InlinePassContext` when
63  /// `RenderPasses` are created, so we just set them to `kDontCare` here.
64  /// What's important is the `StorageMode` of the textures, which cannot be
65  /// changed for the lifetime of the textures.
66 
67  if (context->GetBackendType() == Context::BackendType::kOpenGLES) {
68  // TODO(https://github.com/flutter/flutter/issues/141732): Implement mip map
69  // generation on opengles.
70  mip_count = 1;
71  }
72 
73  RenderTarget target;
74  if (context->GetCapabilities()->SupportsOffscreenMSAA()) {
75  target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA(
76  /*context=*/*context,
77  /*size=*/size,
78  /*mip_count=*/mip_count,
79  /*label=*/"EntityPass",
80  /*color_attachment_config=*/
83  .resolve_storage_mode = StorageMode::kDevicePrivate,
84  .load_action = LoadAction::kDontCare,
85  .store_action = StoreAction::kMultisampleResolve,
86  .clear_color = clear_color},
87  /*stencil_attachment_config=*/kDefaultStencilConfig);
88  } else {
89  target = renderer.GetRenderTargetCache()->CreateOffscreen(
90  *context, // context
91  size, // size
92  /*mip_count=*/mip_count,
93  "EntityPass", // label
96  .load_action = LoadAction::kDontCare,
97  .store_action = StoreAction::kDontCare,
98  .clear_color = clear_color,
99  }, // color_attachment_config
101  );
102  }
103 
104  return std::make_unique<EntityPassTarget>(
105  target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
107 }
108 
110  RenderTarget& render_target,
111  bool requires_readback)
112  : Canvas(),
113  renderer_(renderer),
114  render_target_(render_target),
115  requires_readback_(requires_readback),
116  clip_coverage_stack_(EntityPassClipStack(
117  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
118  SetupRenderPass();
119 }
120 
122  RenderTarget& render_target,
123  bool requires_readback,
124  Rect cull_rect)
125  : Canvas(cull_rect),
126  renderer_(renderer),
127  render_target_(render_target),
128  requires_readback_(requires_readback),
129  clip_coverage_stack_(EntityPassClipStack(
130  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
131  SetupRenderPass();
132 }
133 
135  RenderTarget& render_target,
136  bool requires_readback,
137  IRect cull_rect)
138  : Canvas(cull_rect),
139  renderer_(renderer),
140  render_target_(render_target),
141  requires_readback_(requires_readback),
142  clip_coverage_stack_(EntityPassClipStack(
143  Rect::MakeSize(render_target.GetRenderTargetSize()))) {
144  SetupRenderPass();
145 }
146 
147 void ExperimentalCanvas::SetupRenderPass() {
148  renderer_.GetRenderTargetCache()->Start();
149  auto color0 = render_target_.GetColorAttachments().find(0u)->second;
150 
151  auto& stencil_attachment = render_target_.GetStencilAttachment();
152  auto& depth_attachment = render_target_.GetDepthAttachment();
153  if (!stencil_attachment.has_value() || !depth_attachment.has_value()) {
154  // Setup a new root stencil with an optimal configuration if one wasn't
155  // provided by the caller.
156  render_target_.SetupDepthStencilAttachments(
157  *renderer_.GetContext(),
158  *renderer_.GetContext()->GetResourceAllocator(),
159  color0.texture->GetSize(),
160  renderer_.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(),
161  "ImpellerOnscreen", kDefaultStencilConfig);
162  }
163 
164  // Set up the clear color of the root pass.
165  color0.clear_color = Color::BlackTransparent();
166  render_target_.SetColorAttachment(color0, 0);
167 
168  // If requires_readback is true, then there is a backdrop filter or emulated
169  // advanced blend in the first save layer. This requires a readback, which
170  // isn't supported by onscreen textures. To support this, we immediately begin
171  // a second save layer with the same dimensions as the onscreen. When
172  // rendering is completed, we must blit this saveLayer to the onscreen.
173  if (requires_readback_) {
174  auto entity_pass_target = CreateRenderTarget(
175  renderer_, //
176  color0.texture->GetSize(), //
177  // Note: this is incorrect, we also need to know what kind of filter.
178  /*mip_count=*/4, //
179  /*clear_color=*/Color::BlackTransparent());
180  render_passes_.push_back(
181  LazyRenderingConfig(renderer_, std::move(entity_pass_target)));
182  } else {
183  auto entity_pass_target = std::make_unique<EntityPassTarget>(
184  render_target_, //
187  );
188  render_passes_.push_back(
189  LazyRenderingConfig(renderer_, std::move(entity_pass_target)));
190  }
191 }
192 
193 void ExperimentalCanvas::Save(uint32_t total_content_depth) {
194  auto entry = CanvasStackEntry{};
195  entry.transform = transform_stack_.back().transform;
196  entry.cull_rect = transform_stack_.back().cull_rect;
197  entry.clip_depth = current_depth_ + total_content_depth;
198  entry.distributed_opacity = transform_stack_.back().distributed_opacity;
199  FML_CHECK(entry.clip_depth <= transform_stack_.back().clip_depth)
200  << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth
201  << " after allocating " << total_content_depth;
202  entry.clip_height = transform_stack_.back().clip_height;
203  entry.rendering_mode = Entity::RenderingMode::kDirect;
204  transform_stack_.emplace_back(entry);
205 }
206 
208  const Paint& paint,
209  std::optional<Rect> bounds,
210  const std::shared_ptr<ImageFilter>& backdrop_filter,
211  ContentBoundsPromise bounds_promise,
212  uint32_t total_content_depth,
213  bool can_distribute_opacity) {
214  // Can we always guarantee that we get a bounds? Does a lack of bounds
215  // indicate something?
216  if (!bounds.has_value()) {
217  bounds = Rect::MakeSize(render_target_.GetRenderTargetSize());
218  }
219 
220  // SaveLayer is a no-op, depending on the bounds promise. Should/Can DL elide
221  // this?
222  if (bounds->IsEmpty()) {
223  Save(total_content_depth);
224  return;
225  }
226 
227  // The maximum coverage of the subpass. Subpasses textures should never
228  // extend outside the parent pass texture or the current clip coverage.
229  Rect coverage_limit = Rect::MakeOriginSize(
230  GetGlobalPassPosition(),
231  Size(render_passes_.back().inline_pass_context->GetTexture()->GetSize()));
232 
233  // BDF No-op. need to do some precomputation to ensure this is fully skipped.
234  if (backdrop_filter) {
235  if (!clip_coverage_stack_.HasCoverage()) {
236  Save(total_content_depth);
237  return;
238  }
239  auto maybe_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
240  if (!maybe_clip_coverage.has_value()) {
241  Save(total_content_depth);
242  return;
243  }
244  auto clip_coverage = maybe_clip_coverage.value();
245  if (clip_coverage.IsEmpty() ||
246  !coverage_limit.IntersectsWithRect(clip_coverage)) {
247  Save(total_content_depth);
248  return;
249  }
250  }
251 
252  if (can_distribute_opacity && !backdrop_filter &&
254  Save(total_content_depth);
255  transform_stack_.back().distributed_opacity *= paint.color.alpha;
256  return;
257  }
258 
259  // Backdrop filter state, ignored if there is no BDF.
260  std::shared_ptr<FilterContents> backdrop_filter_contents;
261  Point local_position = {0, 0};
262  if (backdrop_filter) {
263  auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
264  if (current_clip_coverage.has_value()) {
265  local_position =
266  current_clip_coverage->GetOrigin() - GetGlobalPassPosition();
267  }
268  EntityPass::BackdropFilterProc backdrop_filter_proc =
269  [backdrop_filter = backdrop_filter->Clone()](
270  const FilterInput::Ref& input, const Matrix& effect_transform,
271  Entity::RenderingMode rendering_mode) {
272  auto filter = backdrop_filter->WrapInput(input);
273  filter->SetEffectTransform(effect_transform);
274  filter->SetRenderingMode(rendering_mode);
275  return filter;
276  };
277 
278  auto rendering_config = std::move(render_passes_.back());
279  render_passes_.pop_back();
280 
281  // If the very first thing we render in this EntityPass is a subpass that
282  // happens to have a backdrop filter, than that backdrop filter will end
283  // may wind up sampling from the raw, uncleared texture that came straight
284  // out of the texture cache. By calling `pass_context.GetRenderPass` here,
285  // we force the texture to pass through at least one RenderPass with the
286  // correct clear configuration before any sampling occurs.
287  rendering_config.inline_pass_context->GetRenderPass(0);
288 
289  ISize restore_size =
290  rendering_config.inline_pass_context->GetTexture()->GetSize();
291 
292  std::shared_ptr<Texture> input_texture =
293  rendering_config.entity_pass_target->Flip(
294  *renderer_.GetContext()->GetResourceAllocator());
295 
296  backdrop_filter_contents = backdrop_filter_proc(
297  FilterInput::Make(std::move(input_texture)),
298  transform_stack_.back().transform.Basis(),
299  // When the subpass has a translation that means the math with
300  // the snapshot has to be different.
301  transform_stack_.back().transform.HasTranslation()
304 
305  // The subpass will need to read from the current pass texture when
306  // rendering the backdrop, so if there's an active pass, end it prior to
307  // rendering the subpass.
308  rendering_config.inline_pass_context->EndPass();
309 
310  // Create a new render pass that the backdrop filter contents will be
311  // restored to in order to continue rendering.
312  render_passes_.push_back(LazyRenderingConfig(
313  renderer_, std::move(rendering_config.entity_pass_target)));
314  // Eagerly restore the BDF contents.
315 
316  // If the pass context returns a backdrop texture, we need to draw it to the
317  // current pass. We do this because it's faster and takes significantly less
318  // memory than storing/loading large MSAA textures. Also, it's not possible
319  // to blit the non-MSAA resolve texture of the previous pass to MSAA
320  // textures (let alone a transient one).
321  Rect size_rect = Rect::MakeSize(restore_size);
322  auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect);
323  msaa_backdrop_contents->SetStencilEnabled(false);
324  msaa_backdrop_contents->SetLabel("MSAA backdrop");
325  msaa_backdrop_contents->SetSourceRect(size_rect);
326  msaa_backdrop_contents->SetTexture(
327  rendering_config.inline_pass_context->GetTexture());
328 
329  Entity msaa_backdrop_entity;
330  msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
331  msaa_backdrop_entity.SetBlendMode(BlendMode::kSource);
332  msaa_backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
333  if (!msaa_backdrop_entity.Render(renderer_,
334  *render_passes_.back()
335  .inline_pass_context->GetRenderPass(0)
336  .pass)) {
337  VALIDATION_LOG << "Failed to render MSAA backdrop filter entity.";
338  return;
339  }
340 
341  // Restore any clips that were recorded before the backdrop filter was
342  // applied.
343  auto& replay_entities = clip_coverage_stack_.GetReplayEntities();
344  for (const auto& replay : replay_entities) {
346  clip_coverage_stack_.CurrentClipCoverage(),
347  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass,
348  GetGlobalPassPosition());
349  if (!replay.entity.Render(renderer_,
350  *render_passes_.back()
351  .inline_pass_context->GetRenderPass(0)
352  .pass)) {
353  VALIDATION_LOG << "Failed to render entity for clip restore.";
354  }
355  }
356  }
357 
358  // When applying a save layer, absorb any pending distributed opacity.
359  Paint paint_copy = paint;
360  paint_copy.color.alpha *= transform_stack_.back().distributed_opacity;
361  transform_stack_.back().distributed_opacity = 1.0;
362 
363  // Backdrop Filter must expand bounds to at least the clip stack, otherwise
364  // the coverage of the parent render pass.
365  Rect subpass_coverage = bounds->TransformBounds(GetCurrentTransform());
366  if (backdrop_filter_contents) {
367  FML_CHECK(clip_coverage_stack_.HasCoverage());
368  // We should never hit this case as we check the intersection above.
369  // NOLINTBEGIN(bugprone-unchecked-optional-access)
370  subpass_coverage =
371  coverage_limit
372  .Intersection(clip_coverage_stack_.CurrentClipCoverage().value())
373  .value();
374  // NOLINTEND(bugprone-unchecked-optional-access)
375  }
376 
377  render_passes_.push_back(LazyRenderingConfig(
378  renderer_, //
379  CreateRenderTarget(renderer_, //
380  ISize(subpass_coverage.GetSize()), //
381  1u, Color::BlackTransparent())));
382  save_layer_state_.push_back(SaveLayerState{paint_copy, subpass_coverage});
383 
384  CanvasStackEntry entry;
385  entry.transform = transform_stack_.back().transform;
386  entry.cull_rect = transform_stack_.back().cull_rect;
387  entry.clip_depth = current_depth_ + total_content_depth;
388  FML_CHECK(entry.clip_depth <= transform_stack_.back().clip_depth)
389  << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth
390  << " after allocating " << total_content_depth;
391  entry.clip_height = transform_stack_.back().clip_height;
393  transform_stack_.emplace_back(entry);
394 
395  // Start non-collapsed subpasses with a fresh clip coverage stack limited by
396  // the subpass coverage. This is important because image filters applied to
397  // save layers may transform the subpass texture after it's rendered,
398  // causing parent clip coverage to get misaligned with the actual area that
399  // the subpass will affect in the parent pass.
400  clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight());
401 
402  if (backdrop_filter_contents) {
403  // Render the backdrop entity.
404  Entity backdrop_entity;
405  backdrop_entity.SetContents(std::move(backdrop_filter_contents));
406  backdrop_entity.SetTransform(
407  Matrix::MakeTranslation(Vector3(-local_position)));
408  backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
409 
410  backdrop_entity.Render(
411  renderer_,
412  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass);
413  }
414 }
415 
417  FML_DCHECK(transform_stack_.size() > 0);
418  if (transform_stack_.size() == 1) {
419  return false;
420  }
421 
422  // This check is important to make sure we didn't exceed the depth
423  // that the clips were rendered at while rendering any of the
424  // rendering ops. It is OK for the current depth to equal the
425  // outgoing clip depth because that means the clipping would have
426  // been successful up through the last rendering op, but it cannot
427  // be greater.
428  // Also, we bump the current rendering depth to the outgoing clip
429  // depth so that future rendering operations are not clipped by
430  // any of the pixels set by the expiring clips. It is OK for the
431  // estimates used to determine the clip depth in save/saveLayer
432  // to be overly conservative, but we need to jump the depth to
433  // the clip depth so that the next rendering op will get a
434  // larger depth (it will pre-increment the current_depth_ value).
435  FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth)
436  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
437  current_depth_ = transform_stack_.back().clip_depth;
438 
439  if (transform_stack_.back().rendering_mode ==
441  transform_stack_.back().rendering_mode ==
443  auto lazy_render_pass = std::move(render_passes_.back());
444  render_passes_.pop_back();
445  // Force the render pass to be constructed if it never was.
446  lazy_render_pass.inline_pass_context->GetRenderPass(0);
447 
448  SaveLayerState save_layer_state = save_layer_state_.back();
449  save_layer_state_.pop_back();
450 
451  std::shared_ptr<Contents> contents =
452  PaintPassDelegate(save_layer_state.paint)
454  lazy_render_pass.inline_pass_context->GetTexture(),
455  transform_stack_.back().transform);
456 
457  lazy_render_pass.inline_pass_context->EndPass();
458 
459  // Round the subpass texture position for pixel alignment with the parent
460  // pass render target. By default, we draw subpass textures with nearest
461  // sampling, so aligning here is important for avoiding visual nearest
462  // sampling errors caused by limited floating point precision when
463  // straddling a half pixel boundary.
464  //
465  // We do this in lieu of expanding/rounding out the subpass coverage in
466  // order to keep the bounds wrapping consistently tight around subpass
467  // elements. Which is necessary to avoid intense flickering in cases
468  // where a subpass texture has a large blur filter with clamp sampling.
469  //
470  // See also this bug: https://github.com/flutter/flutter/issues/144213
471  Point subpass_texture_position =
472  (save_layer_state.coverage.GetOrigin() - GetGlobalPassPosition())
473  .Round();
474 
475  Entity element_entity;
476  element_entity.SetClipDepth(++current_depth_);
477  element_entity.SetContents(std::move(contents));
478  element_entity.SetBlendMode(save_layer_state.paint.blend_mode);
479  element_entity.SetTransform(
480  Matrix::MakeTranslation(Vector3(subpass_texture_position)));
481 
482  if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
484  ApplyFramebufferBlend(element_entity);
485  } else {
486  VALIDATION_LOG << "Emulated advanced blends are currently unsupported.";
487  element_entity.SetBlendMode(BlendMode::kSourceOver);
488  }
489  }
490 
491  element_entity.Render(
492  renderer_, //
493  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass //
494  );
495  clip_coverage_stack_.PopSubpass();
496  transform_stack_.pop_back();
497 
498  // We don't need to restore clips if a saveLayer was performed, as the clip
499  // state is per render target, and no more rendering operations will be
500  // performed as the render target workloaded is completed in the restore.
501  return true;
502  }
503 
504  size_t num_clips = transform_stack_.back().num_clips;
505  transform_stack_.pop_back();
506 
507  if (num_clips > 0) {
508  Entity entity;
509  entity.SetTransform(
510  Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) *
512  // This path is empty because ClipRestoreContents just generates a quad that
513  // takes up the full render target.
514  auto clip_restore = std::make_shared<ClipRestoreContents>();
515  clip_restore->SetRestoreHeight(GetClipHeight());
516  entity.SetContents(std::move(clip_restore));
517 
518  auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
519  if (current_clip_coverage.has_value()) {
520  // Entity transforms are relative to the current pass position, so we need
521  // to check clip coverage in the same space.
522  current_clip_coverage =
523  current_clip_coverage->Shift(-GetGlobalPassPosition());
524  }
525 
526  auto clip_coverage = entity.GetClipCoverage(current_clip_coverage);
527  if (clip_coverage.coverage.has_value()) {
528  clip_coverage.coverage =
529  clip_coverage.coverage->Shift(GetGlobalPassPosition());
530  }
531 
532  EntityPassClipStack::ClipStateResult clip_state_result =
533  clip_coverage_stack_.ApplyClipState(clip_coverage, entity,
534  GetClipHeightFloor(),
535  GetGlobalPassPosition());
536 
537  if (clip_state_result.clip_did_change) {
538  // We only need to update the pass scissor if the clip state has changed.
540  clip_coverage_stack_.CurrentClipCoverage(), //
541  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, //
542  GetGlobalPassPosition() //
543  );
544  }
545 
546  if (!clip_state_result.should_render) {
547  return true;
548  }
549 
550  entity.Render(
551  renderer_,
552  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass);
553  }
554 
555  return true;
556 }
557 
559  const std::shared_ptr<TextFrame>& text_frame,
560  Point position,
561  const Paint& paint) {
562  Entity entity;
563  entity.SetClipDepth(GetClipHeight());
564  entity.SetBlendMode(paint.blend_mode);
565 
566  auto text_contents = std::make_shared<TextContents>();
567  text_contents->SetTextFrame(text_frame);
568  text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value());
569  text_contents->SetScale(GetCurrentTransform().GetMaxBasisLengthXY());
570  text_contents->SetColor(paint.color);
571  text_contents->SetOffset(position);
572  text_contents->SetTextProperties(paint.color, //
573  paint.style == Paint::Style::kStroke, //
574  paint.stroke_width, //
575  paint.stroke_cap, //
576  paint.stroke_join, //
577  paint.stroke_miter //
578  );
579 
581  Matrix::MakeTranslation(position));
582 
583  // TODO(bdero): This mask blur application is a hack. It will always wind up
584  // doing a gaussian blur that affects the color source itself
585  // instead of just the mask. The color filter text support
586  // needs to be reworked in order to interact correctly with
587  // mask filters.
588  // https://github.com/flutter/flutter/issues/133297
589  entity.SetContents(paint.WithFilters(paint.WithMaskBlur(
590  std::move(text_contents), true, GetCurrentTransform())));
591 
592  AddRenderEntityToCurrentPass(std::move(entity), false);
593 }
594 
595 void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity,
596  bool reuse_depth) {
597  entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity);
598 
599  auto transform = entity.GetTransform();
600  entity.SetTransform(
601  Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform);
602  if (!reuse_depth) {
603  ++current_depth_;
604  }
605  // We can render at a depth up to and including the depth of the currently
606  // active clips and we will still be clipped out, but we cannot render at
607  // a depth that is greater than the current clips or we will not be clipped.
608  FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth)
609  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
611 
614  ApplyFramebufferBlend(entity);
615  } else {
616  VALIDATION_LOG << "Emulated advanced blends are currently unsupported.";
617  return;
618  }
619  }
620 
621  InlinePassContext::RenderPassResult result =
622  render_passes_.back().inline_pass_context->GetRenderPass(0);
623  if (!result.pass) {
624  // Failure to produce a render pass should be explained by specific errors
625  // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't
626  // append a validation log here.
627  return;
628  }
629 
630  entity.Render(renderer_, *result.pass);
631 }
632 
633 void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
634  auto transform = entity.GetTransform();
635  entity.SetTransform(
636  Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform);
637  // Ideally the clip depth would be greater than the current rendering
638  // depth because any rendering calls that follow this clip operation will
639  // pre-increment the depth and then be rendering above our clip depth,
640  // but that case will be caught by the CHECK in AddRenderEntity above.
641  // In practice we sometimes have a clip set with no rendering after it
642  // and in such cases the current depth will equal the clip depth.
643  // Eventually the DisplayList should optimize these out, but it is hard
644  // to know if a clip will actually be used in advance of storing it in
645  // the DisplayList buffer.
646  // See https://github.com/flutter/flutter/issues/147021
647  FML_CHECK(current_depth_ <= transform_stack_.back().clip_depth)
648  << current_depth_ << " <=? " << transform_stack_.back().clip_depth;
649  entity.SetClipDepth(transform_stack_.back().clip_depth);
650 
651  auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage();
652  if (current_clip_coverage.has_value()) {
653  // Entity transforms are relative to the current pass position, so we need
654  // to check clip coverage in the same space.
655  current_clip_coverage =
656  current_clip_coverage->Shift(-GetGlobalPassPosition());
657  }
658 
659  auto clip_coverage = entity.GetClipCoverage(current_clip_coverage);
660  if (clip_coverage.coverage.has_value()) {
661  clip_coverage.coverage =
662  clip_coverage.coverage->Shift(GetGlobalPassPosition());
663  }
664 
665  EntityPassClipStack::ClipStateResult clip_state_result =
666  clip_coverage_stack_.ApplyClipState(
667  clip_coverage, entity, GetClipHeightFloor(), GetGlobalPassPosition());
668 
669  if (clip_state_result.clip_did_change) {
670  // We only need to update the pass scissor if the clip state has changed.
672  clip_coverage_stack_.CurrentClipCoverage(),
673  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass,
674  GetGlobalPassPosition());
675  }
676 
677  if (!clip_state_result.should_render) {
678  return;
679  }
680 
681  entity.Render(
682  renderer_,
683  *render_passes_.back().inline_pass_context->GetRenderPass(0).pass);
684 }
685 
686 bool ExperimentalCanvas::BlitToOnscreen() {
687  auto command_buffer = renderer_.GetContext()->CreateCommandBuffer();
688  command_buffer->SetLabel("EntityPass Root Command Buffer");
689  auto offscreen_target = render_passes_.back()
690  .inline_pass_context->GetPassTarget()
691  .GetRenderTarget();
692 
693  if (renderer_.GetContext()
694  ->GetCapabilities()
695  ->SupportsTextureToTextureBlits()) {
696  auto blit_pass = command_buffer->CreateBlitPass();
697  blit_pass->AddCopy(offscreen_target.GetRenderTargetTexture(),
698  render_target_.GetRenderTargetTexture());
699  if (!blit_pass->EncodeCommands(
700  renderer_.GetContext()->GetResourceAllocator())) {
701  VALIDATION_LOG << "Failed to encode root pass blit command.";
702  return false;
703  }
704  if (!renderer_.GetContext()
705  ->GetCommandQueue()
706  ->Submit({command_buffer})
707  .ok()) {
708  return false;
709  }
710  } else {
711  auto render_pass = command_buffer->CreateRenderPass(render_target_);
712  render_pass->SetLabel("EntityPass Root Render Pass");
713 
714  {
715  auto size_rect = Rect::MakeSize(offscreen_target.GetRenderTargetSize());
716  auto contents = TextureContents::MakeRect(size_rect);
717  contents->SetTexture(offscreen_target.GetRenderTargetTexture());
718  contents->SetSourceRect(size_rect);
719  contents->SetLabel("Root pass blit");
720 
721  Entity entity;
722  entity.SetContents(contents);
723  entity.SetBlendMode(BlendMode::kSource);
724 
725  if (!entity.Render(renderer_, *render_pass)) {
726  VALIDATION_LOG << "Failed to render EntityPass root blit.";
727  return false;
728  }
729  }
730 
731  if (!render_pass->EncodeCommands()) {
732  VALIDATION_LOG << "Failed to encode root pass command buffer.";
733  return false;
734  }
735  if (!renderer_.GetContext()
736  ->GetCommandQueue()
737  ->Submit({command_buffer})
738  .ok()) {
739  return false;
740  }
741  }
742  return true;
743 }
744 
746  FML_DCHECK(render_passes_.size() == 1u);
747  render_passes_.back().inline_pass_context->EndPass();
748 
749  // If requires_readback_ was true, then we rendered to an offscreen texture
750  // instead of to the onscreen provided in the render target. Now we need to
751  // draw or blit the offscreen back to the onscreen.
752  if (requires_readback_) {
753  BlitToOnscreen();
754  }
755 
756  render_passes_.clear();
757  renderer_.GetRenderTargetCache()->End();
758 
759  Reset();
761 }
762 
763 } // namespace impeller
text_contents.h
impeller::ISize
ISize64 ISize
Definition: size.h:140
impeller::StoreAction::kMultisampleResolve
@ kMultisampleResolve
impeller::Entity::SetClipDepth
void SetClipDepth(uint32_t clip_depth)
Definition: entity.cc:98
impeller::PaintPassDelegate::CreateContentsForSubpassTarget
std::shared_ptr< Contents > CreateContentsForSubpassTarget(std::shared_ptr< Texture > target, const Matrix &effect_transform) override
Definition: paint_pass_delegate.cc:34
impeller::Entity::kLastPipelineBlendMode
static constexpr BlendMode kLastPipelineBlendMode
Definition: entity.h:22
impeller::EntityPassClipStack::ClipStateResult::should_render
bool should_render
Definition: entity_pass_clip_stack.h:34
impeller::Capabilities::SupportsReadFromResolve
virtual bool SupportsReadFromResolve() const =0
Whether the context backend supports binding the current RenderPass attachments. This is supported if...
impeller::EntityPass::BackdropFilterProc
std::function< std::shared_ptr< FilterContents >(FilterInput::Ref, const Matrix &effect_transform, Entity::RenderingMode rendering_mode)> BackdropFilterProc
Definition: entity_pass.h:59
impeller::Entity::SetBlendMode
void SetBlendMode(BlendMode blend_mode)
Definition: entity.cc:115
impeller::EntityPassClipStack::PushSubpass
void PushSubpass(std::optional< Rect > subpass_coverage, size_t clip_height)
Definition: entity_pass_clip_stack.cc:31
impeller::Entity::GetTransform
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition: entity.cc:46
impeller::ExperimentalCanvas::SaveLayer
void SaveLayer(const Paint &paint, std::optional< Rect > bounds, const std::shared_ptr< ImageFilter > &backdrop_filter, ContentBoundsPromise bounds_promise, uint32_t total_content_depth, bool can_distribute_opacity) override
Definition: experimental_canvas.cc:207
impeller::Paint::Style::kStroke
@ kStroke
entity.h
impeller::CanvasStackEntry
Definition: canvas.h:31
impeller::Paint
Definition: paint.h:23
impeller::CanvasStackEntry::cull_rect
std::optional< Rect > cull_rect
Definition: canvas.h:34
impeller::Color
Definition: color.h:124
impeller::ExperimentalCanvas::SaveLayerState::paint
Paint paint
Definition: experimental_canvas.h:76
impeller::FilterInput::Make
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
impeller::LazyRenderingConfig
Definition: experimental_canvas.h:21
impeller::Paint::color
Color color
Definition: paint.h:68
impeller::RenderTarget::SetColorAttachment
RenderTarget & SetColorAttachment(const ColorAttachment &attachment, size_t index)
Definition: render_target.cc:169
paint_pass_delegate.h
impeller::BlendMode::kSource
@ kSource
impeller::Canvas
Definition: canvas.h:60
impeller::Entity::RenderingMode::kSubpassAppendSnapshotTransform
@ kSubpassAppendSnapshotTransform
impeller::TRect::TransformBounds
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:463
impeller::EntityPassClipStack::HasCoverage
bool HasCoverage() const
Definition: entity_pass_clip_stack.cc:27
impeller::TRect::Intersection
constexpr std::optional< TRect > Intersection(const TRect &o) const
Definition: rect.h:519
impeller::FilterInput::Ref
std::shared_ptr< FilterInput > Ref
Definition: filter_input.h:32
impeller::Color::alpha
Scalar alpha
Definition: color.h:143
impeller::StoreAction::kDontCare
@ kDontCare
impeller::ContentBoundsPromise
ContentBoundsPromise
Definition: entity_pass.h:28
impeller::ExperimentalCanvas::Save
void Save(uint32_t total_content_depth) override
Definition: experimental_canvas.cc:193
impeller::Size
TSize< Scalar > Size
Definition: size.h:137
impeller::RenderTarget::GetColorAttachments
const std::map< size_t, ColorAttachment > & GetColorAttachments() const
Definition: render_target.cc:198
framebuffer_blend_contents.h
impeller::CanvasStackEntry::rendering_mode
Entity::RenderingMode rendering_mode
Definition: canvas.h:40
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
validation.h
impeller::TRect::GetOrigin
constexpr TPoint< Type > GetOrigin() const
Returns the upper left corner of the rectangle as specified by the left/top or x/y values when it was...
Definition: rect.h:310
impeller::Canvas::initial_cull_rect_
std::optional< Rect > initial_cull_rect_
Definition: canvas.h:182
impeller::Canvas::GetCurrentTransform
const Matrix & GetCurrentTransform() const
Definition: canvas.cc:298
impeller::RenderTarget::AttachmentConfigMSAA
Definition: render_target.h:47
impeller::TRect::IntersectsWithRect
constexpr bool IntersectsWithRect(const TRect &o) const
Definition: rect.h:533
impeller::TRect::Shift
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:589
impeller::RenderTarget::AttachmentConfig
Definition: render_target.h:40
impeller::Entity::SetContents
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:90
impeller::TRect::RoundOut
RoundOut(const TRect< U > &r)
Definition: rect.h:666
impeller::CanvasStackEntry::clip_height
size_t clip_height
Definition: canvas.h:36
impeller::kDefaultStencilConfig
static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig
Definition: experimental_canvas.cc:48
impeller::Canvas::Initialize
void Initialize(std::optional< Rect > cull_rect)
Definition: canvas.cc:164
impeller::Entity
Definition: entity.h:20
impeller::EntityPassClipStack::ApplyClipState
ClipStateResult ApplyClipState(Contents::ClipCoverage global_clip_coverage, Entity &entity, size_t clip_height_floor, Point global_pass_position)
Applies the current clip state to an Entity. If the given Entity is a clip operation,...
Definition: entity_pass_clip_stack.cc:51
impeller::TSize
Definition: size.h:19
impeller::EntityPassClipStack::GetReplayEntities
const std::vector< ReplayResult > & GetReplayEntities() const
Definition: entity_pass_clip_stack.cc:158
impeller::RenderTarget::GetDepthAttachment
const std::optional< DepthAttachment > & GetDepthAttachment() const
Definition: render_target.cc:203
impeller::StorageMode::kDeviceTransient
@ kDeviceTransient
impeller::Point
TPoint< Scalar > Point
Definition: point.h:322
impeller::Contents::ClipCoverage::coverage
std::optional< Rect > coverage
Definition: contents.h:41
impeller::RenderTarget::GetRenderTargetTexture
std::shared_ptr< Texture > GetRenderTargetTexture() const
Definition: render_target.cc:144
impeller::Entity::Render
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition: entity.cc:173
impeller::EntityPassClipStack::CurrentClipCoverage
std::optional< Rect > CurrentClipCoverage() const
Definition: entity_pass_clip_stack.cc:23
impeller::Context::BackendType::kOpenGLES
@ kOpenGLES
impeller::Entity::SetInheritedOpacity
bool SetInheritedOpacity(Scalar alpha)
Definition: entity.cc:134
transform
Matrix transform
Definition: gaussian_blur_filter_contents.cc:231
impeller::Capabilities::SupportsImplicitResolvingMSAA
virtual bool SupportsImplicitResolvingMSAA() const =0
Whether the context backend supports multisampled rendering to the on-screen surface without requirin...
impeller::ContentContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: content_context.cc:553
impeller::StorageMode::kDevicePrivate
@ kDevicePrivate
impeller::TRect< Scalar >::MakeOriginSize
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:140
experimental_canvas.h
impeller::CreateRenderTarget
static std::unique_ptr< EntityPassTarget > CreateRenderTarget(ContentContext &renderer, ISize size, int mip_count, const Color &clear_color)
Definition: experimental_canvas.cc:55
impeller::Entity::RenderingMode::kDirect
@ kDirect
impeller::RenderTarget::AttachmentConfig::storage_mode
StorageMode storage_mode
Definition: render_target.h:41
impeller::CanvasStackEntry::clip_depth
uint32_t clip_depth
Definition: canvas.h:35
canvas.h
impeller::ExperimentalCanvas::Restore
bool Restore() override
Definition: experimental_canvas.cc:416
impeller::RenderTarget
Definition: render_target.h:38
impeller::ExperimentalCanvas::DrawTextFrame
void DrawTextFrame(const std::shared_ptr< TextFrame > &text_frame, Point position, const Paint &paint) override
Definition: experimental_canvas.cc:558
impeller::SetClipScissor
static void SetClipScissor(std::optional< Rect > clip_coverage, RenderPass &pass, Point global_pass_position)
Definition: entity_pass.cc:725
impeller::EntityPassClipStack::PopSubpass
void PopSubpass()
Definition: entity_pass_clip_stack.cc:42
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:119
impeller::ExperimentalCanvas::ExperimentalCanvas
ExperimentalCanvas(ContentContext &renderer, RenderTarget &render_target, bool requires_readback)
Definition: experimental_canvas.cc:109
entity_pass_clip_stack.h
impeller::Canvas::current_depth_
uint64_t current_depth_
Definition: canvas.h:183
impeller::RenderTarget::GetRenderTargetSize
ISize GetRenderTargetSize() const
Definition: render_target.cc:139
impeller::ExperimentalCanvas::EndReplay
void EndReplay()
Definition: experimental_canvas.cc:745
impeller::CanvasStackEntry::transform
Matrix transform
Definition: canvas.h:32
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:73
impeller::Canvas::GetClipHeight
size_t GetClipHeight() const
Definition: canvas.cc:825
impeller::ContentContext::GetRenderTargetCache
const std::shared_ptr< RenderTargetAllocator > & GetRenderTargetCache() const
Definition: content_context.h:724
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
impeller::EntityPassClipStack::ClipStateResult::clip_did_change
bool clip_did_change
Definition: entity_pass_clip_stack.h:37
impeller::TRect::GetSize
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:317
impeller::ContentContext::GetDeviceCapabilities
const Capabilities & GetDeviceCapabilities() const
Definition: content_context.cc:557
impeller::Entity::RenderingMode::kSubpassPrependSnapshotTransform
@ kSubpassPrependSnapshotTransform
impeller::RenderTarget::AttachmentConfigMSAA::storage_mode
StorageMode storage_mode
Definition: render_target.h:48
impeller::TRect::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:146
impeller::PaintPassDelegate
Definition: paint_pass_delegate.h:15
impeller::LoadAction::kDontCare
@ kDontCare
impeller::TPoint< Scalar >
impeller::Color::BlackTransparent
static constexpr Color BlackTransparent()
Definition: color.h:272
impeller::ExperimentalCanvas::SaveLayerState::coverage
Rect coverage
Definition: experimental_canvas.h:77
paint
const Paint & paint
Definition: color_source.cc:38
color.h
impeller::Entity::GetClipCoverage
Contents::ClipCoverage GetClipCoverage(const std::optional< Rect > &current_clip_coverage) const
Definition: entity.cc:74
impeller::EntityPassClipStack
A class that tracks all clips that have been recorded in the current entity pass stencil.
Definition: entity_pass_clip_stack.h:24
impeller::IRect
IRect64 IRect
Definition: rect.h:772
impeller::Paint::CanApplyOpacityPeephole
static bool CanApplyOpacityPeephole(const Paint &paint)
Whether or not a save layer with the provided paint can perform the opacity peephole optimization.
Definition: paint.h:36
impeller::Entity::RenderingMode
RenderingMode
Definition: entity.h:27
render_target.h
impeller::RenderTarget::GetStencilAttachment
const std::optional< StencilAttachment > & GetStencilAttachment() const
Definition: render_target.cc:207
impeller::ExperimentalCanvas::SaveLayerState
Definition: experimental_canvas.h:75
impeller::RenderTarget::SetupDepthStencilAttachments
void SetupDepthStencilAttachments(const Context &context, Allocator &allocator, ISize size, bool msaa, const std::string &label="Offscreen", RenderTarget::AttachmentConfig stencil_attachment_config=RenderTarget::kDefaultStencilAttachmentConfig, const std::shared_ptr< Texture > &depth_stencil_texture=nullptr)
Definition: render_target.cc:413
impeller::Capabilities::SupportsFramebufferFetch
virtual bool SupportsFramebufferFetch() const =0
Whether the context backend is able to support pipelines with shaders that read from the framebuffer ...
impeller::EntityPassClipStack::ClipStateResult
Definition: entity_pass_clip_stack.h:31
impeller::TextureContents::MakeRect
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
Definition: texture_contents.cc:27
impeller
Definition: aiks_blend_unittests.cc:18
impeller::Canvas::transform_stack_
std::deque< CanvasStackEntry > transform_stack_
Definition: canvas.h:181
impeller::ContentContext
Definition: content_context.h:366
impeller::TRect< Scalar >
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Vector3
Definition: vector.h:20
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::Paint::blend_mode
BlendMode blend_mode
Definition: paint.h:76
impeller::Canvas::Reset
void Reset()
Definition: canvas.cc:177