Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
entity_pass.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
7#include <limits>
8#include <memory>
9#include <utility>
10#include <variant>
11
12#include "flutter/fml/closure.h"
13#include "flutter/fml/logging.h"
14#include "flutter/fml/trace_event.h"
30
31#ifdef IMPELLER_DEBUG
33#endif // IMPELLER_DEBUG
34
35namespace impeller {
36
37namespace {
38std::tuple<std::optional<Color>, BlendMode> ElementAsBackgroundColor(
39 const EntityPass::Element& element,
40 ISize target_size) {
41 if (const Entity* entity = std::get_if<Entity>(&element)) {
42 std::optional<Color> entity_color = entity->AsBackgroundColor(target_size);
43 if (entity_color.has_value()) {
44 return {entity_color.value(), entity->GetBlendMode()};
45 }
46 }
47 return {};
48}
49} // namespace
50
51const std::string EntityPass::kCaptureDocumentName = "EntityPass";
52
53EntityPass::EntityPass() = default;
54
55EntityPass::~EntityPass() = default;
56
57void EntityPass::SetDelegate(std::shared_ptr<EntityPassDelegate> delegate) {
58 if (!delegate) {
59 return;
60 }
61 delegate_ = std::move(delegate);
62}
63
64void EntityPass::SetBoundsLimit(std::optional<Rect> bounds_limit,
65 ContentBoundsPromise bounds_promise) {
66 bounds_limit_ = bounds_limit;
67 bounds_promise_ = bounds_limit.has_value() ? bounds_promise
69}
70
71std::optional<Rect> EntityPass::GetBoundsLimit() const {
72 return bounds_limit_;
73}
74
76 switch (bounds_promise_) {
78 // If the promise is unknown due to not having a bounds limit,
79 // then no clipping will occur. But if we have a bounds limit
80 // and it is unkown, then we can make no promises about whether
81 // it causes clipping of the entity pass contents and we
82 // conservatively return true.
83 return bounds_limit_.has_value();
85 FML_DCHECK(bounds_limit_.has_value());
86 return false;
88 FML_DCHECK(bounds_limit_.has_value());
89 return true;
90 }
92}
93
95 switch (bounds_promise_) {
97 return false;
100 FML_DCHECK(bounds_limit_.has_value());
101 return true;
102 }
104}
105
107 if (entity.GetBlendMode() == BlendMode::kSourceOver &&
108 entity.GetContents()->IsOpaque()) {
110 }
111
113 advanced_blend_reads_from_pass_texture_ = true;
114 }
115 elements_.emplace_back(std::move(entity));
116}
117
119 elements_.emplace_back(std::move(entity));
120 active_clips_.emplace_back(elements_.size() - 1);
121}
122
123void EntityPass::PopClips(size_t num_clips, uint64_t depth) {
124 if (num_clips > active_clips_.size()) {
126 << "Attempted to pop more clips than are currently active. Active: "
127 << active_clips_.size() << ", Popped: " << num_clips
128 << ", Depth: " << depth;
129 }
130
131 size_t max = std::min(num_clips, active_clips_.size());
132 for (size_t i = 0; i < max; i++) {
133 FML_DCHECK(active_clips_.back() < elements_.size());
134 Entity* element = std::get_if<Entity>(&elements_[active_clips_.back()]);
135 FML_DCHECK(element);
136 element->SetClipDepth(depth);
137 active_clips_.pop_back();
138 }
139}
140
141void EntityPass::PopAllClips(uint64_t depth) {
142 PopClips(active_clips_.size(), depth);
143}
144
145void EntityPass::SetElements(std::vector<Element> elements) {
146 elements_ = std::move(elements);
147}
148
150 size_t max_subpass_depth = 0u;
151 for (const auto& element : elements_) {
152 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
153 max_subpass_depth =
154 std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth());
155 }
156 }
157 return max_subpass_depth + 1u;
158}
159
161 std::optional<Rect> coverage_limit) const {
162 std::optional<Rect> accumulated_coverage;
163 for (const auto& element : elements_) {
164 std::optional<Rect> element_coverage;
165
166 if (auto entity = std::get_if<Entity>(&element)) {
167 element_coverage = entity->GetCoverage();
168
169 // When the coverage limit is std::nullopt, that means there is no limit,
170 // as opposed to empty coverage.
171 if (element_coverage.has_value() && coverage_limit.has_value()) {
172 const auto* filter = entity->GetContents()->AsFilter();
173 if (!filter || filter->IsTranslationOnly()) {
174 element_coverage =
175 element_coverage->Intersection(coverage_limit.value());
176 }
177 }
178 } else if (auto subpass_ptr =
179 std::get_if<std::unique_ptr<EntityPass>>(&element)) {
180 auto& subpass = *subpass_ptr->get();
181
182 std::optional<Rect> unfiltered_coverage =
183 GetSubpassCoverage(subpass, std::nullopt);
184
185 // If the current pass elements have any coverage so far and there's a
186 // backdrop filter, then incorporate the backdrop filter in the
187 // pre-filtered coverage of the subpass.
188 if (accumulated_coverage.has_value() && subpass.backdrop_filter_proc_) {
189 std::shared_ptr<FilterContents> backdrop_filter =
190 subpass.backdrop_filter_proc_(
191 FilterInput::Make(accumulated_coverage.value()),
192 subpass.transform_, Entity::RenderingMode::kSubpass);
193 if (backdrop_filter) {
194 auto backdrop_coverage = backdrop_filter->GetCoverage({});
195 unfiltered_coverage =
196 Rect::Union(unfiltered_coverage, backdrop_coverage);
197 } else {
198 VALIDATION_LOG << "The EntityPass backdrop filter proc didn't return "
199 "a valid filter.";
200 }
201 }
202
203 if (!unfiltered_coverage.has_value()) {
204 continue;
205 }
206
207 // Additionally, subpass textures may be passed through filters, which may
208 // modify the coverage.
209 //
210 // Note that we currently only assume that ImageFilters (such as blurs and
211 // matrix transforms) may modify coverage, although it's technically
212 // possible ColorFilters to affect coverage as well. For example: A
213 // ColorMatrixFilter could output a completely transparent result, and
214 // we could potentially detect this case as zero coverage in the future.
215 std::shared_ptr<FilterContents> image_filter =
216 subpass.delegate_->WithImageFilter(*unfiltered_coverage,
217 subpass.transform_);
218 if (image_filter) {
219 Entity subpass_entity;
220 subpass_entity.SetTransform(subpass.transform_);
221 element_coverage = image_filter->GetCoverage(subpass_entity);
222 } else {
223 element_coverage = unfiltered_coverage;
224 }
225
226 element_coverage = Rect::Intersection(element_coverage, coverage_limit);
227 } else {
229 }
230
231 accumulated_coverage = Rect::Union(accumulated_coverage, element_coverage);
232 }
233 return accumulated_coverage;
234}
235
237 const EntityPass& subpass,
238 std::optional<Rect> coverage_limit) const {
239 if (subpass.bounds_limit_.has_value() && subpass.GetBoundsLimitIsSnug()) {
240 return subpass.bounds_limit_->TransformBounds(subpass.transform_);
241 }
242
243 std::shared_ptr<FilterContents> image_filter =
244 subpass.delegate_->WithImageFilter(Rect(), subpass.transform_);
245
246 // If the subpass has an image filter, then its coverage space may deviate
247 // from the parent pass and make intersecting with the pass coverage limit
248 // unsafe.
249 if (image_filter && coverage_limit.has_value()) {
250 coverage_limit = image_filter->GetSourceCoverage(subpass.transform_,
251 coverage_limit.value());
252 }
253
254 auto entities_coverage = subpass.GetElementsCoverage(coverage_limit);
255 // The entities don't cover anything. There is nothing to do.
256 if (!entities_coverage.has_value()) {
257 return std::nullopt;
258 }
259
260 if (!subpass.bounds_limit_.has_value()) {
261 return entities_coverage;
262 }
263 auto user_bounds_coverage =
264 subpass.bounds_limit_->TransformBounds(subpass.transform_);
265 return entities_coverage->Intersection(user_bounds_coverage);
266}
267
269 return superpass_;
270}
271
272EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
273 if (!pass) {
274 return nullptr;
275 }
276 FML_DCHECK(pass->superpass_ == nullptr);
277 pass->superpass_ = this;
278
279 if (pass->backdrop_filter_proc_) {
280 backdrop_filter_reads_from_pass_texture_ = true;
281 }
282 if (pass->blend_mode_ > Entity::kLastPipelineBlendMode) {
283 advanced_blend_reads_from_pass_texture_ = true;
284 }
285
286 auto subpass_pointer = pass.get();
287 elements_.emplace_back(std::move(pass));
288 return subpass_pointer;
289}
290
297
299 ISize size,
300 int mip_count,
301 const Color& clear_color) {
302 const std::shared_ptr<Context>& context = renderer.GetContext();
303
304 /// All of the load/store actions are managed by `InlinePassContext` when
305 /// `RenderPasses` are created, so we just set them to `kDontCare` here.
306 /// What's important is the `StorageMode` of the textures, which cannot be
307 /// changed for the lifetime of the textures.
308
309 if (context->GetBackendType() == Context::BackendType::kOpenGLES) {
310 // TODO(https://github.com/flutter/flutter/issues/141732): Implement mip map
311 // generation on opengles.
312 mip_count = 1;
313 }
314
316 if (context->GetCapabilities()->SupportsOffscreenMSAA()) {
317 target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA(
318 /*context=*/*context,
319 /*size=*/size,
320 /*mip_count=*/mip_count,
321 /*label=*/"EntityPass",
322 /*color_attachment_config=*/
325 .resolve_storage_mode = StorageMode::kDevicePrivate,
326 .load_action = LoadAction::kDontCare,
327 .store_action = StoreAction::kMultisampleResolve,
328 .clear_color = clear_color},
329 /*stencil_attachment_config=*/
331 } else {
332 target = renderer.GetRenderTargetCache()->CreateOffscreen(
333 *context, // context
334 size, // size
335 /*mip_count=*/mip_count,
336 "EntityPass", // label
339 .load_action = LoadAction::kDontCare,
340 .store_action = StoreAction::kDontCare,
341 .clear_color = clear_color,
342 }, // color_attachment_config
343 kDefaultStencilConfig // stencil_attachment_config
344 );
345 }
346
347 return EntityPassTarget(
348 target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
349 renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
350}
351
352bool EntityPass::DoesBackdropGetRead(ContentContext& renderer) const {
353 return renderer.GetDeviceCapabilities().SupportsFramebufferFetch()
354 ? backdrop_filter_reads_from_pass_texture_
355 : backdrop_filter_reads_from_pass_texture_ ||
356 advanced_blend_reads_from_pass_texture_;
357}
358
360 const RenderTarget& render_target) const {
361 auto capture =
362 renderer.GetContext()->capture.GetDocument(kCaptureDocumentName);
363
364 renderer.GetRenderTargetCache()->Start();
365 fml::ScopedCleanupClosure reset_state([&renderer]() {
366 renderer.GetLazyGlyphAtlas()->ResetTextFrames();
367 renderer.GetRenderTargetCache()->End();
368 });
369
370 auto root_render_target = render_target;
371
372 if (root_render_target.GetColorAttachments().find(0u) ==
373 root_render_target.GetColorAttachments().end()) {
374 VALIDATION_LOG << "The root RenderTarget must have a color attachment.";
375 return false;
376 }
377 if (root_render_target.GetDepthAttachment().has_value() !=
378 root_render_target.GetStencilAttachment().has_value()) {
379 VALIDATION_LOG << "The root RenderTarget should have a stencil attachment "
380 "iff it has a depth attachment.";
381 return false;
382 }
383
384 capture.AddRect("Coverage",
385 Rect::MakeSize(root_render_target.GetRenderTargetSize()),
386 {.readonly = true});
387
388 const auto& lazy_glyph_atlas = renderer.GetLazyGlyphAtlas();
389 IterateAllEntities([&lazy_glyph_atlas](const Entity& entity) {
390 if (const auto& contents = entity.GetContents()) {
391 contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale());
392 }
393 return true;
394 });
395
397 Rect::MakeSize(root_render_target.GetRenderTargetSize()));
398
399 // In this branch path, we need to render everything to an offscreen texture
400 // and then blit the results onto the onscreen texture. If using this branch,
401 // there's no need to set up a stencil attachment on the root render target.
402 if (DoesBackdropGetRead(renderer)) {
403 EntityPassTarget offscreen_target = CreateRenderTarget(
404 renderer, root_render_target.GetRenderTargetSize(),
407
408 if (!OnRender(renderer, // renderer
409 capture, // capture
410 offscreen_target.GetRenderTarget()
411 .GetRenderTargetSize(), // root_pass_size
412 offscreen_target, // pass_target
413 Point(), // global_pass_position
414 Point(), // local_pass_position
415 0, // pass_depth
416 clip_stack // clip_coverage_stack
417 )) {
418 // Validation error messages are triggered for all `OnRender()` failure
419 // cases.
420 return false;
421 }
422
423 auto command_buffer = renderer.GetContext()->CreateCommandBuffer();
424 command_buffer->SetLabel("EntityPass Root Command Buffer");
425
426 // If the context supports blitting, blit the offscreen texture to the
427 // onscreen texture. Otherwise, draw it to the parent texture using a
428 // pipeline (slower).
429 if (renderer.GetContext()
430 ->GetCapabilities()
431 ->SupportsTextureToTextureBlits()) {
432 auto blit_pass = command_buffer->CreateBlitPass();
433 blit_pass->AddCopy(
434 offscreen_target.GetRenderTarget().GetRenderTargetTexture(),
435 root_render_target.GetRenderTargetTexture());
436 if (!blit_pass->EncodeCommands(
437 renderer.GetContext()->GetResourceAllocator())) {
438 VALIDATION_LOG << "Failed to encode root pass blit command.";
439 return false;
440 }
441 if (!renderer.GetContext()
442 ->GetCommandQueue()
443 ->Submit({command_buffer})
444 .ok()) {
445 return false;
446 }
447 } else {
448 auto render_pass = command_buffer->CreateRenderPass(root_render_target);
449 render_pass->SetLabel("EntityPass Root Render Pass");
450
451 {
452 auto size_rect = Rect::MakeSize(
453 offscreen_target.GetRenderTarget().GetRenderTargetSize());
454 auto contents = TextureContents::MakeRect(size_rect);
455 contents->SetTexture(
456 offscreen_target.GetRenderTarget().GetRenderTargetTexture());
457 contents->SetSourceRect(size_rect);
458 contents->SetLabel("Root pass blit");
459
460 Entity entity;
461 entity.SetContents(contents);
463
464 if (!entity.Render(renderer, *render_pass)) {
465 VALIDATION_LOG << "Failed to render EntityPass root blit.";
466 return false;
467 }
468 }
469
470 if (!render_pass->EncodeCommands()) {
471 VALIDATION_LOG << "Failed to encode root pass command buffer.";
472 return false;
473 }
474 if (!renderer.GetContext()
475 ->GetCommandQueue()
476 ->Submit({command_buffer})
477 .ok()) {
478 return false;
479 }
480 }
481
482 return true;
483 }
484
485 // If we make it this far, that means the context is capable of rendering
486 // everything directly to the onscreen texture.
487
488 // The safety check for fetching this color attachment is at the beginning of
489 // this method.
490 auto color0 = root_render_target.GetColorAttachments().find(0u)->second;
491
492 auto stencil_attachment = root_render_target.GetStencilAttachment();
493 auto depth_attachment = root_render_target.GetDepthAttachment();
494 if (!stencil_attachment.has_value() || !depth_attachment.has_value()) {
495 // Setup a new root stencil with an optimal configuration if one wasn't
496 // provided by the caller.
497 root_render_target.SetupDepthStencilAttachments(
498 *renderer.GetContext(), *renderer.GetContext()->GetResourceAllocator(),
499 color0.texture->GetSize(),
500 renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(),
501 "ImpellerOnscreen", kDefaultStencilConfig);
502 }
503
504 // Set up the clear color of the root pass.
505 color0.clear_color =
507 root_render_target.SetColorAttachment(color0, 0);
508
509 EntityPassTarget pass_target(
510 root_render_target,
511 renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
512 renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
513
514 return OnRender( //
515 renderer, // renderer
516 capture, // capture
517 root_render_target.GetRenderTargetSize(), // root_pass_size
518 pass_target, // pass_target
519 Point(), // global_pass_position
520 Point(), // local_pass_position
521 0, // pass_depth
522 clip_stack); // clip_coverage_stack
523}
524
525EntityPass::EntityResult EntityPass::GetEntityForElement(
526 const EntityPass::Element& element,
527 ContentContext& renderer,
528 Capture& capture,
529 InlinePassContext& pass_context,
530 ISize root_pass_size,
531 Point global_pass_position,
532 uint32_t pass_depth,
533 EntityPassClipStack& clip_coverage_stack,
534 size_t clip_height_floor) const {
535 //--------------------------------------------------------------------------
536 /// Setup entity element.
537 ///
538 if (const auto& entity = std::get_if<Entity>(&element)) {
539 Entity element_entity = entity->Clone();
540 element_entity.SetCapture(capture.CreateChild("Entity"));
541
542 if (!global_pass_position.IsZero()) {
543 // If the pass image is going to be rendered with a non-zero position,
544 // apply the negative translation to entity copies before rendering them
545 // so that they'll end up rendering to the correct on-screen position.
546 element_entity.SetTransform(
547 Matrix::MakeTranslation(Vector3(-global_pass_position)) *
548 element_entity.GetTransform());
549 }
550 return EntityPass::EntityResult::Success(std::move(element_entity));
551 }
552
553 //--------------------------------------------------------------------------
554 /// Setup subpass element.
555 ///
556 if (const auto& subpass_ptr =
557 std::get_if<std::unique_ptr<EntityPass>>(&element)) {
558 auto subpass = subpass_ptr->get();
559 if (subpass->delegate_->CanElide()) {
560 return EntityPass::EntityResult::Skip();
561 }
562
563 if (!subpass->backdrop_filter_proc_ &&
564 subpass->delegate_->CanCollapseIntoParentPass(subpass)) {
565 auto subpass_capture = capture.CreateChild("EntityPass (Collapsed)");
566 // Directly render into the parent target and move on.
567 if (!subpass->OnRender(
568 renderer, // renderer
569 subpass_capture, // capture
570 root_pass_size, // root_pass_size
571 pass_context.GetPassTarget(), // pass_target
572 global_pass_position, // global_pass_position
573 Point(), // local_pass_position
574 pass_depth, // pass_depth
575 clip_coverage_stack, // clip_coverage_stack
576 clip_height_, // clip_height_floor
577 nullptr, // backdrop_filter_contents
578 pass_context.GetRenderPass(pass_depth) // collapsed_parent_pass
579 )) {
580 // Validation error messages are triggered for all `OnRender()` failure
581 // cases.
582 return EntityPass::EntityResult::Failure();
583 }
584 return EntityPass::EntityResult::Skip();
585 }
586
587 std::shared_ptr<Contents> subpass_backdrop_filter_contents = nullptr;
588 if (subpass->backdrop_filter_proc_) {
589 auto texture = pass_context.GetTexture();
590 // Render the backdrop texture before any of the pass elements.
591 const auto& proc = subpass->backdrop_filter_proc_;
592 subpass_backdrop_filter_contents =
593 proc(FilterInput::Make(std::move(texture)),
594 subpass->transform_.Basis(), Entity::RenderingMode::kSubpass);
595
596 // If the very first thing we render in this EntityPass is a subpass that
597 // happens to have a backdrop filter, than that backdrop filter will end
598 // may wind up sampling from the raw, uncleared texture that came straight
599 // out of the texture cache. By calling `pass_context.GetRenderPass` here,
600 // we force the texture to pass through at least one RenderPass with the
601 // correct clear configuration before any sampling occurs.
602 pass_context.GetRenderPass(pass_depth);
603
604 // The subpass will need to read from the current pass texture when
605 // rendering the backdrop, so if there's an active pass, end it prior to
606 // rendering the subpass.
607 pass_context.EndPass();
608 }
609
610 if (!clip_coverage_stack.HasCoverage()) {
611 // The current clip is empty. This means the pass texture won't be
612 // visible, so skip it.
613 capture.CreateChild("Subpass Entity (Skipped: Empty clip A)");
614 return EntityPass::EntityResult::Skip();
615 }
616 auto clip_coverage_back = clip_coverage_stack.CurrentClipCoverage();
617 if (!clip_coverage_back.has_value()) {
618 capture.CreateChild("Subpass Entity (Skipped: Empty clip B)");
619 return EntityPass::EntityResult::Skip();
620 }
621
622 // The maximum coverage of the subpass. Subpasses textures should never
623 // extend outside the parent pass texture or the current clip coverage.
624 auto coverage_limit = Rect::MakeOriginSize(global_pass_position,
625 Size(pass_context.GetPassTarget()
628 .Intersection(clip_coverage_back.value());
629 if (!coverage_limit.has_value()) {
630 capture.CreateChild("Subpass Entity (Skipped: Empty coverage limit A)");
631 return EntityPass::EntityResult::Skip();
632 }
633
634 coverage_limit =
635 coverage_limit->Intersection(Rect::MakeSize(root_pass_size));
636 if (!coverage_limit.has_value()) {
637 capture.CreateChild("Subpass Entity (Skipped: Empty coverage limit B)");
638 return EntityPass::EntityResult::Skip();
639 }
640
641 auto subpass_coverage =
642 (subpass->flood_clip_ || subpass_backdrop_filter_contents)
643 ? coverage_limit
644 : GetSubpassCoverage(*subpass, coverage_limit);
645 if (!subpass_coverage.has_value()) {
646 capture.CreateChild("Subpass Entity (Skipped: Empty subpass coverage A)");
647 return EntityPass::EntityResult::Skip();
648 }
649
650 auto subpass_size = ISize(subpass_coverage->GetSize());
651 if (subpass_size.IsEmpty()) {
652 capture.CreateChild("Subpass Entity (Skipped: Empty subpass coverage B)");
653 return EntityPass::EntityResult::Skip();
654 }
655
656 auto subpass_target = CreateRenderTarget(
657 renderer, // renderer
658 subpass_size, // size
659 subpass->GetRequiredMipCount(),
660 subpass->GetClearColorOrDefault(subpass_size)); // clear_color
661
662 if (!subpass_target.IsValid()) {
663 VALIDATION_LOG << "Subpass render target is invalid.";
664 return EntityPass::EntityResult::Failure();
665 }
666
667 auto subpass_capture = capture.CreateChild("EntityPass");
668 subpass_capture.AddRect("Coverage", *subpass_coverage, {.readonly = true});
669
670 // Start non-collapsed subpasses with a fresh clip coverage stack limited by
671 // the subpass coverage. This is important because image filters applied to
672 // save layers may transform the subpass texture after it's rendered,
673 // causing parent clip coverage to get misaligned with the actual area that
674 // the subpass will affect in the parent pass.
675 clip_coverage_stack.PushSubpass(subpass_coverage, subpass->clip_height_);
676
677 // Stencil textures aren't shared between EntityPasses (as much of the
678 // time they are transient).
679 if (!subpass->OnRender(
680 renderer, // renderer
681 subpass_capture, // capture
682 root_pass_size, // root_pass_size
683 subpass_target, // pass_target
684 subpass_coverage->GetOrigin(), // global_pass_position
685 subpass_coverage->GetOrigin() -
686 global_pass_position, // local_pass_position
687 ++pass_depth, // pass_depth
688 clip_coverage_stack, // clip_coverage_stack
689 subpass->clip_height_, // clip_height_floor
690 subpass_backdrop_filter_contents // backdrop_filter_contents
691 )) {
692 // Validation error messages are triggered for all `OnRender()` failure
693 // cases.
694 return EntityPass::EntityResult::Failure();
695 }
696
697 clip_coverage_stack.PopSubpass();
698
699 // The subpass target's texture may have changed during OnRender.
700 auto subpass_texture =
701 subpass_target.GetRenderTarget().GetRenderTargetTexture();
702
703 auto offscreen_texture_contents =
704 subpass->delegate_->CreateContentsForSubpassTarget(
705 subpass_texture,
706 Matrix::MakeTranslation(Vector3{-global_pass_position}) *
707 subpass->transform_);
708
709 if (!offscreen_texture_contents) {
710 // This is an error because the subpass delegate said the pass couldn't
711 // be collapsed into its parent. Yet, when asked how it want's to
712 // postprocess the offscreen texture, it couldn't give us an answer.
713 //
714 // Theoretically, we could collapse the pass now. But that would be
715 // wasteful as we already have the offscreen texture and we don't want
716 // to discard it without ever using it. Just make the delegate do the
717 // right thing.
718 return EntityPass::EntityResult::Failure();
719 }
720 Entity element_entity;
721 Capture subpass_texture_capture =
722 capture.CreateChild("Entity (Subpass texture)");
723 element_entity.SetClipDepth(subpass->clip_depth_);
724 element_entity.SetCapture(subpass_texture_capture);
725 element_entity.SetContents(std::move(offscreen_texture_contents));
726 element_entity.SetBlendMode(subpass->blend_mode_);
727 element_entity.SetTransform(subpass_texture_capture.AddMatrix(
728 "Transform",
730 Vector3(subpass_coverage->GetOrigin() - global_pass_position))));
731
732 return EntityPass::EntityResult::Success(std::move(element_entity));
733 }
735}
736
737static void SetClipScissor(std::optional<Rect> clip_coverage,
738 RenderPass& pass,
739 Point global_pass_position) {
740 // Set the scissor to the clip coverage area. We do this prior to rendering
741 // the clip itself and all its contents.
742 IRect scissor;
743 if (clip_coverage.has_value()) {
744 clip_coverage = clip_coverage->Shift(-global_pass_position);
745 scissor = IRect::RoundOut(clip_coverage.value());
746 // The scissor rect must not exceed the size of the render target.
747 scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize()))
748 .value_or(IRect());
749 }
750 pass.SetScissor(scissor);
751}
752
753bool EntityPass::RenderElement(Entity& element_entity,
754 size_t clip_height_floor,
755 InlinePassContext& pass_context,
756 int32_t pass_depth,
757 ContentContext& renderer,
758 EntityPassClipStack& clip_coverage_stack,
759 Point global_pass_position) const {
760 auto result = pass_context.GetRenderPass(pass_depth);
761 if (!result.pass) {
762 // Failure to produce a render pass should be explained by specific errors
763 // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't
764 // append a validation log here.
765 return false;
766 }
767
768 // If the pass context returns a backdrop texture, we need to draw it to the
769 // current pass. We do this because it's faster and takes significantly less
770 // memory than storing/loading large MSAA textures. Also, it's not possible to
771 // blit the non-MSAA resolve texture of the previous pass to MSAA textures
772 // (let alone a transient one).
773 if (result.backdrop_texture) {
774 auto size_rect = Rect::MakeSize(result.pass->GetRenderTargetSize());
775 auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect);
776 msaa_backdrop_contents->SetStencilEnabled(false);
777 msaa_backdrop_contents->SetLabel("MSAA backdrop");
778 msaa_backdrop_contents->SetSourceRect(size_rect);
779 msaa_backdrop_contents->SetTexture(result.backdrop_texture);
780
781 Entity msaa_backdrop_entity;
782 msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
783 msaa_backdrop_entity.SetBlendMode(BlendMode::kSource);
784 msaa_backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
785 if (!msaa_backdrop_entity.Render(renderer, *result.pass)) {
786 VALIDATION_LOG << "Failed to render MSAA backdrop filter entity.";
787 return false;
788 }
789 }
790
791 if (result.just_created) {
792 // Restore any clips that were recorded before the backdrop filter was
793 // applied.
794 auto& replay_entities = clip_coverage_stack.GetReplayEntities();
795 for (const auto& replay : replay_entities) {
796 SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass,
797 global_pass_position);
798 if (!replay.entity.Render(renderer, *result.pass)) {
799 VALIDATION_LOG << "Failed to render entity for clip restore.";
800 }
801 }
802 }
803
804 auto current_clip_coverage = clip_coverage_stack.CurrentClipCoverage();
805 if (current_clip_coverage.has_value()) {
806 // Entity transforms are relative to the current pass position, so we need
807 // to check clip coverage in the same space.
808 current_clip_coverage = current_clip_coverage->Shift(-global_pass_position);
809 }
810
811 if (!element_entity.ShouldRender(current_clip_coverage)) {
812 return true; // Nothing to render.
813 }
814
815 auto clip_coverage = element_entity.GetClipCoverage(current_clip_coverage);
816 if (clip_coverage.coverage.has_value()) {
817 clip_coverage.coverage =
818 clip_coverage.coverage->Shift(global_pass_position);
819 }
820
821 // The coverage hint tells the rendered Contents which portion of the
822 // rendered output will actually be used, and so we set this to the current
823 // clip coverage (which is the max clip bounds). The contents may
824 // optionally use this hint to avoid unnecessary rendering work.
825 auto element_coverage_hint = element_entity.GetContents()->GetCoverageHint();
826 element_entity.GetContents()->SetCoverageHint(
827 Rect::Intersection(element_coverage_hint, current_clip_coverage));
828
829 EntityPassClipStack::ClipStateResult clip_state_result =
830 clip_coverage_stack.ApplyClipState(clip_coverage, element_entity,
831 clip_height_floor,
832 global_pass_position);
833
834 if (clip_state_result.clip_did_change) {
835 // We only need to update the pass scissor if the clip state has changed.
836 SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass,
837 global_pass_position);
838 }
839
840 if (!clip_state_result.should_render) {
841 return true;
842 }
843
844 if (!element_entity.Render(renderer, *result.pass)) {
845 VALIDATION_LOG << "Failed to render entity.";
846 return false;
847 }
848 return true;
849}
850
851bool EntityPass::OnRender(
852 ContentContext& renderer,
853 Capture& capture,
854 ISize root_pass_size,
855 EntityPassTarget& pass_target,
856 Point global_pass_position,
857 Point local_pass_position,
858 uint32_t pass_depth,
859 EntityPassClipStack& clip_coverage_stack,
860 size_t clip_height_floor,
861 std::shared_ptr<Contents> backdrop_filter_contents,
862 const std::optional<InlinePassContext::RenderPassResult>&
863 collapsed_parent_pass) const {
864 TRACE_EVENT0("impeller", "EntityPass::OnRender");
865
866 if (!active_clips_.empty()) {
868 "EntityPass (Depth=%d) contains one or more clips with an unresolved "
869 "depth value.",
870 pass_depth);
871 }
872
873 InlinePassContext pass_context(renderer, pass_target, GetElementCount(),
874 collapsed_parent_pass);
875 if (!pass_context.IsValid()) {
876 VALIDATION_LOG << SPrintF("Pass context invalid (Depth=%d)", pass_depth);
877 return false;
878 }
879 auto clear_color_size = pass_target.GetRenderTarget().GetRenderTargetSize();
880
881 if (!collapsed_parent_pass) {
882 // Always force the pass to construct the render pass object, even if there
883 // is not a clear color. This ensures that the attachment textures are
884 // cleared/transitioned to the right state.
885 pass_context.GetRenderPass(pass_depth);
886 }
887
888 if (backdrop_filter_proc_) {
889 if (!backdrop_filter_contents) {
891 << "EntityPass contains a backdrop filter, but no backdrop filter "
892 "contents was supplied by the parent pass at render time. This is "
893 "a bug in EntityPass. Parent passes are responsible for setting "
894 "up backdrop filters for their children.";
895 return false;
896 }
897
898 Entity backdrop_entity;
899 backdrop_entity.SetContents(std::move(backdrop_filter_contents));
900 backdrop_entity.SetTransform(
901 Matrix::MakeTranslation(Vector3(-local_pass_position)));
902 backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
903
904 RenderElement(backdrop_entity, clip_height_floor, pass_context, pass_depth,
905 renderer, clip_coverage_stack, global_pass_position);
906 }
907
908 bool is_collapsing_clear_colors = !collapsed_parent_pass &&
909 // Backdrop filters act as a entity before
910 // everything and disrupt the optimization.
911 !backdrop_filter_proc_;
912 for (const auto& element : elements_) {
913 // Skip elements that are incorporated into the clear color.
914 if (is_collapsing_clear_colors) {
915 auto [entity_color, _] =
916 ElementAsBackgroundColor(element, clear_color_size);
917 if (entity_color.has_value()) {
918 continue;
919 }
920 is_collapsing_clear_colors = false;
921 }
922
923 EntityResult result =
924 GetEntityForElement(element, // element
925 renderer, // renderer
926 capture, // capture
927 pass_context, // pass_context
928 root_pass_size, // root_pass_size
929 global_pass_position, // global_pass_position
930 pass_depth, // pass_depth
931 clip_coverage_stack, // clip_coverage_stack
932 clip_height_floor); // clip_height_floor
933
934 switch (result.status) {
935 case EntityResult::kSuccess:
936 break;
937 case EntityResult::kFailure:
938 // All failure cases should be covered by specific validation messages
939 // in `GetEntityForElement()`.
940 return false;
941 case EntityResult::kSkip:
942 continue;
943 };
944
945 //--------------------------------------------------------------------------
946 /// Setup advanced blends.
947 ///
948
949 if (result.entity.GetBlendMode() > Entity::kLastPipelineBlendMode) {
950 if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
951 auto src_contents = result.entity.GetContents();
952 auto contents = std::make_shared<FramebufferBlendContents>();
953 contents->SetChildContents(src_contents);
954 contents->SetBlendMode(result.entity.GetBlendMode());
955 result.entity.SetContents(std::move(contents));
956 result.entity.SetBlendMode(BlendMode::kSource);
957 } else {
958 // End the active pass and flush the buffer before rendering "advanced"
959 // blends. Advanced blends work by binding the current render target
960 // texture as an input ("destination"), blending with a second texture
961 // input ("source"), writing the result to an intermediate texture, and
962 // finally copying the data from the intermediate texture back to the
963 // render target texture. And so all of the commands that have written
964 // to the render target texture so far need to execute before it's bound
965 // for blending (otherwise the blend pass will end up executing before
966 // all the previous commands in the active pass).
967
968 if (!pass_context.EndPass()) {
970 << "Failed to end the current render pass in order to read from "
971 "the backdrop texture and apply an advanced blend.";
972 return false;
973 }
974
975 // Amend an advanced blend filter to the contents, attaching the pass
976 // texture.
977 auto texture = pass_context.GetTexture();
978 if (!texture) {
979 VALIDATION_LOG << "Failed to fetch the color texture in order to "
980 "apply an advanced blend.";
981 return false;
982 }
983
985 FilterInput::Make(texture, result.entity.GetTransform().Invert()),
986 FilterInput::Make(result.entity.GetContents())};
987 auto contents = ColorFilterContents::MakeBlend(
988 result.entity.GetBlendMode(), inputs);
989 contents->SetCoverageHint(result.entity.GetCoverage());
990 result.entity.SetContents(std::move(contents));
991 result.entity.SetBlendMode(BlendMode::kSource);
992 }
993 }
994
995 //--------------------------------------------------------------------------
996 /// Render the Element.
997 ///
998 if (!RenderElement(result.entity, clip_height_floor, pass_context,
999 pass_depth, renderer, clip_coverage_stack,
1000 global_pass_position)) {
1001 // Specific validation logs are handled in `render_element()`.
1002 return false;
1003 }
1004 }
1005
1006#ifdef IMPELLER_DEBUG
1007 //--------------------------------------------------------------------------
1008 /// Draw debug checkerboard over offscreen textures.
1009 ///
1010
1011 // When the pass depth is > 0, this EntityPass is being rendered to an
1012 // offscreen texture.
1013 if (enable_offscreen_debug_checkerboard_ &&
1014 !collapsed_parent_pass.has_value() && pass_depth > 0) {
1015 auto result = pass_context.GetRenderPass(pass_depth);
1016 if (!result.pass) {
1017 // Failure to produce a render pass should be explained by specific errors
1018 // in `InlinePassContext::GetRenderPass()`.
1019 return false;
1020 }
1021 auto checkerboard = CheckerboardContents();
1022 auto color = ColorHSB(0, // hue
1023 1, // saturation
1024 std::max(0.0, 0.6 - pass_depth / 5), // brightness
1025 0.25); // alpha
1026 checkerboard.SetColor(Color(color));
1027 checkerboard.Render(renderer, {}, *result.pass);
1028 }
1029#endif
1030
1031 return true;
1032}
1033
1035 const std::function<bool(Element&)>& iterator) {
1036 if (!iterator) {
1037 return;
1038 }
1039
1040 for (auto& element : elements_) {
1041 if (!iterator(element)) {
1042 return;
1043 }
1044 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1045 subpass->get()->IterateAllElements(iterator);
1046 }
1047 }
1048}
1049
1051 const std::function<bool(const Element&)>& iterator) const {
1052 /// TODO(gaaclarke): Remove duplication here between const and non-const
1053 /// versions.
1054 if (!iterator) {
1055 return;
1056 }
1057
1058 for (auto& element : elements_) {
1059 if (!iterator(element)) {
1060 return;
1061 }
1062 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1063 const EntityPass* entity_pass = subpass->get();
1064 entity_pass->IterateAllElements(iterator);
1065 }
1066 }
1067}
1068
1070 const std::function<bool(Entity&)>& iterator) {
1071 if (!iterator) {
1072 return;
1073 }
1074
1075 for (auto& element : elements_) {
1076 if (auto entity = std::get_if<Entity>(&element)) {
1077 if (!iterator(*entity)) {
1078 return;
1079 }
1080 continue;
1081 }
1082 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1083 subpass->get()->IterateAllEntities(iterator);
1084 continue;
1085 }
1087 }
1088}
1089
1091 const std::function<bool(const Entity&)>& iterator) const {
1092 if (!iterator) {
1093 return;
1094 }
1095
1096 for (const auto& element : elements_) {
1097 if (auto entity = std::get_if<Entity>(&element)) {
1098 if (!iterator(*entity)) {
1099 return;
1100 }
1101 continue;
1102 }
1103 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1104 const EntityPass* entity_pass = subpass->get();
1105 entity_pass->IterateAllEntities(iterator);
1106 continue;
1107 }
1109 }
1110}
1111
1113 const std::function<bool(Entity&)>& iterator) {
1114 if (!iterator) {
1115 return true;
1116 }
1117
1118 for (auto& element : elements_) {
1119 if (auto entity = std::get_if<Entity>(&element)) {
1120 if (!iterator(*entity)) {
1121 return false;
1122 }
1123 continue;
1124 }
1125 return true;
1126 }
1127 return false;
1128}
1129
1131 return elements_.size();
1132}
1133
1135 transform_ = transform;
1136}
1137
1138void EntityPass::SetClipHeight(size_t clip_height) {
1139 clip_height_ = clip_height;
1140}
1141
1143 return clip_height_;
1144}
1145
1146void EntityPass::SetClipDepth(size_t clip_depth) {
1147 clip_depth_ = clip_depth;
1148}
1149
1151 return clip_depth_;
1152}
1153
1155 blend_mode_ = blend_mode;
1156 flood_clip_ = Entity::IsBlendModeDestructive(blend_mode);
1157}
1158
1162
1163std::optional<Color> EntityPass::GetClearColor(ISize target_size) const {
1164 if (backdrop_filter_proc_) {
1165 return std::nullopt;
1166 }
1167
1168 std::optional<Color> result = std::nullopt;
1169 for (const Element& element : elements_) {
1170 auto [entity_color, blend_mode] =
1171 ElementAsBackgroundColor(element, target_size);
1172 if (!entity_color.has_value()) {
1173 break;
1174 }
1176 .Blend(entity_color.value(), blend_mode);
1177 }
1178 if (result.has_value()) {
1179 return result->Premultiply();
1180 }
1181 return result;
1182}
1183
1185 if (superpass_) {
1186 VALIDATION_LOG << "Backdrop filters cannot be set on EntityPasses that "
1187 "have already been appended to another pass.";
1188 }
1189
1190 backdrop_filter_proc_ = std::move(proc);
1191}
1192
1194 enable_offscreen_debug_checkerboard_ = enabled;
1195}
1196
1197} // namespace impeller
SkColor4f color
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition closure.h:32
Capture CreateChild(std::string_view label)
Definition capture.h:239
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
A class that tracks all clips that have been recorded in the current entity pass stencil.
std::optional< Rect > CurrentClipCoverage() const
void PushSubpass(std::optional< Rect > subpass_coverage, size_t clip_height)
const RenderTarget & GetRenderTarget() const
bool IterateUntilSubpass(const std::function< bool(Entity &)> &iterator)
Iterate entities in this pass up until the first subpass is found. This is useful for limiting look-a...
void SetElements(std::vector< Element > elements)
void IterateAllElements(const std::function< bool(Element &)> &iterator)
Iterate all elements (entities and subpasses) in this pass, recursively including elements of child p...
std::variant< Entity, std::unique_ptr< EntityPass > > Element
Definition entity_pass.h:54
size_t GetElementCount() const
Return the number of elements on this pass.
bool GetBoundsLimitMightClipContent() const
Indicates if the bounds limit set using |SetBoundsLimit()| might clip the contents of the pass.
void SetBoundsLimit(std::optional< Rect > bounds_limit, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown)
Set the bounds limit, which is provided by the user when creating a SaveLayer. This is a hint that al...
void SetEnableOffscreenCheckerboard(bool enabled)
Color GetClearColorOrDefault(ISize size=ISize::Infinite()) const
Return the premultiplied clear color of the pass entities.
void SetClipDepth(size_t clip_depth)
void AddEntity(Entity entity)
Add an entity to the current entity pass.
size_t GetClipHeight() const
void SetBackdropFilter(BackdropFilterProc proc)
bool GetBoundsLimitIsSnug() const
Indicates if the bounds limit set using |SetBoundsLimit()| is a reasonably tight estimate of the boun...
static const std::string kCaptureDocumentName
Definition entity_pass.h:56
std::optional< Rect > GetBoundsLimit() const
Get the bounds limit, which is provided by the user when creating a SaveLayer.
EntityPass * GetSuperpass() const
size_t GetSubpassesDepth() const
void PopClips(size_t num_clips, uint64_t depth)
int32_t GetRequiredMipCount() const
void SetClipHeight(size_t clip_height)
std::optional< Color > GetClearColor(ISize size=ISize::Infinite()) const
Return the premultiplied clear color of the pass entities, if any.
void SetBlendMode(BlendMode blend_mode)
void SetTransform(Matrix transform)
void IterateAllEntities(const std::function< bool(Entity &)> &iterator)
Iterate all entities in this pass, recursively including entities of child passes....
std::optional< Rect > GetSubpassCoverage(const EntityPass &subpass, std::optional< Rect > coverage_limit) const
Computes the coverage of a given subpass. This is used to determine the texture size of a given subpa...
void PopAllClips(uint64_t depth)
EntityPass * AddSubpass(std::unique_ptr< EntityPass > pass)
Appends a given pass as a subpass.
void SetDelegate(std::shared_ptr< EntityPassDelegate > delgate)
bool Render(ContentContext &renderer, const RenderTarget &render_target) const
std::optional< Rect > GetElementsCoverage(std::optional< Rect > coverage_limit) const
void PushClip(Entity entity)
uint32_t GetClipDepth() const
std::function< std::shared_ptr< FilterContents >(FilterInput::Ref, const Matrix &effect_transform, Entity::RenderingMode rendering_mode)> BackdropFilterProc
Definition entity_pass.h:61
void SetCapture(Capture capture) const
Definition entity.cc:199
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:62
const std::shared_ptr< Contents > & GetContents() const
Definition entity.cc:94
void SetClipDepth(uint32_t clip_depth)
Definition entity.cc:98
BlendMode GetBlendMode() const
Definition entity.cc:119
void SetContents(std::shared_ptr< Contents > contents)
Definition entity.cc:90
void SetBlendMode(BlendMode blend_mode)
Definition entity.cc:115
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
Definition entity.cc:173
Entity Clone() const
Definition entity.cc:195
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
Definition entity.cc:46
static constexpr BlendMode kLastPipelineBlendMode
Definition entity.h:23
static bool IsBlendModeDestructive(BlendMode blend_mode)
Returns true if the blend mode is "destructive", meaning that even fully transparent source colors wo...
Definition entity.cc:156
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
std::vector< FilterInput::Ref > Vector
EntityPassTarget & GetPassTarget() const
std::shared_ptr< Texture > GetTexture()
RenderPassResult GetRenderPass(uint32_t pass_depth)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:33
virtual void SetScissor(IRect scissor)
ISize GetRenderTargetSize() const
std::shared_ptr< Texture > GetRenderTargetTexture() const
ISize GetRenderTargetSize() const
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
GAsyncResult * result
uint32_t * target
#define FML_UNREACHABLE()
Definition logging.h:109
#define FML_DCHECK(condition)
Definition logging.h:103
static float max(float r, float g, float b)
Definition hsl.cpp:49
FlTexture * texture
static void SetClipScissor(std::optional< Rect > clip_coverage, RenderPass &pass, Point global_pass_position)
static std::unique_ptr< EntityPassTarget > CreateRenderTarget(ContentContext &renderer, ISize size, int mip_count, const Color &clear_color)
TRect< Scalar > Rect
Definition rect.h:746
TPoint< Scalar > Point
Definition point.h:316
std::string SPrintF(const char *format,...)
Definition strings.cc:12
TSize< int64_t > ISize
Definition size.h:138
BlendMode
Definition color.h:59
ContentBoundsPromise
Definition entity_pass.h:28
@ kUnknown
The caller makes no claims related to the size of the bounds.
@ kMayClipContents
The caller claims the bounds are a subset of an estimate of the reasonably tight bounds but likely cl...
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
TSize< Scalar > Size
Definition size.h:137
TRect< int64_t > IRect
Definition rect.h:747
static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
static constexpr Color BlackTransparent()
Definition color.h:262
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
constexpr bool IsZero() const
Definition point.h:234
constexpr std::optional< TRect > Intersection(const TRect &o) const
Definition rect.h:496
constexpr TRect Union(const TRect &o) const
Definition rect.h:481
RoundOut(const TRect< U > &r)
Definition rect.h:643
static constexpr TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition rect.h:140
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:146
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition rect.h:566
#define TRACE_EVENT0(category_group, name)
static void checkerboard(SkCanvas *canvas, SkColor c1, SkColor c2, int size)
#define VALIDATION_LOG
Definition validation.h:73