| 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
5 #include "impeller/aiks/experimental_canvas.h"
6 #include "fml/logging.h"
7 #include "impeller/aiks/canvas.h"
8 #include "impeller/aiks/paint_pass_delegate.h"
9 #include "impeller/base/validation.h"
10 #include "impeller/entity/contents/framebuffer_blend_contents.h"
11 #include "impeller/entity/contents/text_contents.h"
12 #include "impeller/entity/entity.h"
13 #include "impeller/entity/entity_pass_clip_stack.h"
14 #include "impeller/geometry/color.h"
15 #include "impeller/renderer/render_target.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
48 static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig =
49 RenderTarget::AttachmentConfig{
50 .storage_mode = StorageMode::kDeviceTransient,
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=*/
81 RenderTarget::AttachmentConfigMSAA{
82 .storage_mode = StorageMode::kDeviceTransient,
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
94 RenderTarget::AttachmentConfig{
95 .storage_mode = StorageMode::kDevicePrivate,
96 .load_action = LoadAction::kDontCare,
97 .store_action = StoreAction::kDontCare,
98 .clear_color = clear_color,
99 }, // color_attachment_config
100 kDefaultStencilConfig //
101 );
102 }
103
104 return std::make_unique<EntityPassTarget>(
105 target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
106 renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
107 }
108
109 ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
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
121 ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
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
134 ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer,
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_, //
185 renderer_.GetDeviceCapabilities().SupportsReadFromResolve(), //
186 renderer_.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() //
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
207 void ExperimentalCanvas::SaveLayer(
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 &&
253 Paint::CanApplyOpacityPeephole(paint)) {
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()
302 ? Entity::RenderingMode::kSubpassPrependSnapshotTransform
303 : Entity::RenderingMode::kSubpassAppendSnapshotTransform);
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) {
345 SetClipScissor(
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;
392 entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform;
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
416 bool ExperimentalCanvas::Restore() {
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 ==
440 Entity::RenderingMode::kSubpassAppendSnapshotTransform ||
441 transform_stack_.back().rendering_mode ==
442 Entity::RenderingMode::kSubpassPrependSnapshotTransform) {
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)
453 .CreateContentsForSubpassTarget(
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) {
483 if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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())) *
511 GetCurrentTransform());
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.
539 SetClipScissor(
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
558 void ExperimentalCanvas::DrawTextFrame(
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
580 entity.SetTransform(GetCurrentTransform() *
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;
610 entity.SetClipDepth(current_depth_);
611
612 if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
613 if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) {
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.
671 SetClipScissor(
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
745 void ExperimentalCanvas::EndReplay() {
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();
760 Initialize(initial_cull_rect_);
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