// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "impeller/entity/entity_pass.h" #include #include #include #include #include "flutter/fml/closure.h" #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/core/formats.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/color_filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/framebuffer_blend_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/color.h" #include "impeller/geometry/rect.h" #include "impeller/geometry/size.h" #include "impeller/renderer/command_buffer.h" namespace impeller { namespace { std::tuple, BlendMode> ElementAsBackgroundColor( const EntityPass::Element& element, ISize target_size) { if (const Entity* entity = std::get_if(&element)) { std::optional entity_color = entity->AsBackgroundColor(target_size); if (entity_color.has_value()) { return {entity_color.value(), entity->GetBlendMode()}; } } return {}; } } // namespace EntityPass::EntityPass() = default; EntityPass::~EntityPass() = default; void EntityPass::SetDelegate(std::shared_ptr delegate) { if (!delegate) { return; } delegate_ = std::move(delegate); } void EntityPass::SetBoundsLimit(std::optional bounds_limit, ContentBoundsPromise bounds_promise) { bounds_limit_ = bounds_limit; bounds_promise_ = bounds_limit.has_value() ? bounds_promise : ContentBoundsPromise::kUnknown; } std::optional EntityPass::GetBoundsLimit() const { return bounds_limit_; } bool EntityPass::GetBoundsLimitMightClipContent() const { switch (bounds_promise_) { case ContentBoundsPromise::kUnknown: // If the promise is unknown due to not having a bounds limit, // then no clipping will occur. But if we have a bounds limit // and it is unkown, then we can make no promises about whether // it causes clipping of the entity pass contents and we // conservatively return true. return bounds_limit_.has_value(); case ContentBoundsPromise::kContainsContents: FML_DCHECK(bounds_limit_.has_value()); return false; case ContentBoundsPromise::kMayClipContents: FML_DCHECK(bounds_limit_.has_value()); return true; } FML_UNREACHABLE(); } bool EntityPass::GetBoundsLimitIsSnug() const { switch (bounds_promise_) { case ContentBoundsPromise::kUnknown: return false; case ContentBoundsPromise::kContainsContents: case ContentBoundsPromise::kMayClipContents: FML_DCHECK(bounds_limit_.has_value()); return true; } FML_UNREACHABLE(); } void EntityPass::AddEntity(Entity entity) { if (entity.GetBlendMode() == BlendMode::kSourceOver && entity.GetContents()->IsOpaque()) { entity.SetBlendMode(BlendMode::kSource); } if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { advanced_blend_reads_from_pass_texture_ = true; } elements_.emplace_back(std::move(entity)); } void EntityPass::PushClip(Entity entity) { elements_.emplace_back(std::move(entity)); active_clips_.emplace_back(elements_.size() - 1); } void EntityPass::PopClips(size_t num_clips, uint64_t depth) { if (num_clips > active_clips_.size()) { VALIDATION_LOG << "Attempted to pop more clips than are currently active. Active: " << active_clips_.size() << ", Popped: " << num_clips << ", Depth: " << depth; } size_t max = std::min(num_clips, active_clips_.size()); for (size_t i = 0; i < max; i++) { FML_DCHECK(active_clips_.back() < elements_.size()); Entity* element = std::get_if(&elements_[active_clips_.back()]); FML_DCHECK(element); element->SetClipDepth(depth); active_clips_.pop_back(); } } void EntityPass::PopAllClips(uint64_t depth) { PopClips(active_clips_.size(), depth); } void EntityPass::SetElements(std::vector elements) { elements_ = std::move(elements); } size_t EntityPass::GetSubpassesDepth() const { size_t max_subpass_depth = 0u; for (const auto& element : elements_) { if (auto subpass = std::get_if>(&element)) { max_subpass_depth = std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth()); } } return max_subpass_depth + 1u; } std::optional EntityPass::GetElementsCoverage( std::optional coverage_limit) const { std::optional accumulated_coverage; for (const auto& element : elements_) { std::optional element_coverage; if (auto entity = std::get_if(&element)) { element_coverage = entity->GetCoverage(); // When the coverage limit is std::nullopt, that means there is no limit, // as opposed to empty coverage. if (element_coverage.has_value() && coverage_limit.has_value()) { const auto* filter = entity->GetContents()->AsFilter(); if (!filter || filter->IsTranslationOnly()) { element_coverage = element_coverage->Intersection(coverage_limit.value()); } } } else if (auto subpass_ptr = std::get_if>(&element)) { auto& subpass = *subpass_ptr->get(); std::optional unfiltered_coverage = GetSubpassCoverage(subpass, std::nullopt); // If the current pass elements have any coverage so far and there's a // backdrop filter, then incorporate the backdrop filter in the // pre-filtered coverage of the subpass. if (accumulated_coverage.has_value() && subpass.backdrop_filter_proc_) { std::shared_ptr backdrop_filter = subpass.backdrop_filter_proc_( FilterInput::Make(accumulated_coverage.value()), subpass.transform_, Entity::RenderingMode::kSubpassAppendSnapshotTransform); if (backdrop_filter) { auto backdrop_coverage = backdrop_filter->GetCoverage({}); unfiltered_coverage = Rect::Union(unfiltered_coverage, backdrop_coverage); } else { VALIDATION_LOG << "The EntityPass backdrop filter proc didn't return " "a valid filter."; } } if (!unfiltered_coverage.has_value()) { continue; } // Additionally, subpass textures may be passed through filters, which may // modify the coverage. // // Note that we currently only assume that ImageFilters (such as blurs and // matrix transforms) may modify coverage, although it's technically // possible ColorFilters to affect coverage as well. For example: A // ColorMatrixFilter could output a completely transparent result, and // we could potentially detect this case as zero coverage in the future. std::shared_ptr image_filter = subpass.delegate_->WithImageFilter(*unfiltered_coverage, subpass.transform_); if (image_filter) { Entity subpass_entity; subpass_entity.SetTransform(subpass.transform_); element_coverage = image_filter->GetCoverage(subpass_entity); } else { element_coverage = unfiltered_coverage; } element_coverage = Rect::Intersection(element_coverage, coverage_limit); } else { FML_UNREACHABLE(); } accumulated_coverage = Rect::Union(accumulated_coverage, element_coverage); } return accumulated_coverage; } std::optional EntityPass::GetSubpassCoverage( const EntityPass& subpass, std::optional coverage_limit) const { if (subpass.bounds_limit_.has_value() && subpass.GetBoundsLimitIsSnug()) { return subpass.bounds_limit_->TransformBounds(subpass.transform_); } std::shared_ptr image_filter = subpass.delegate_->WithImageFilter(Rect(), subpass.transform_); // If the subpass has an image filter, then its coverage space may deviate // from the parent pass and make intersecting with the pass coverage limit // unsafe. if (image_filter && coverage_limit.has_value()) { coverage_limit = image_filter->GetSourceCoverage(subpass.transform_, coverage_limit.value()); } auto entities_coverage = subpass.GetElementsCoverage(coverage_limit); // The entities don't cover anything. There is nothing to do. if (!entities_coverage.has_value()) { return std::nullopt; } if (!subpass.bounds_limit_.has_value()) { return entities_coverage; } auto user_bounds_coverage = subpass.bounds_limit_->TransformBounds(subpass.transform_); return entities_coverage->Intersection(user_bounds_coverage); } EntityPass* EntityPass::GetSuperpass() const { return superpass_; } EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { if (!pass) { return nullptr; } FML_DCHECK(pass->superpass_ == nullptr); pass->superpass_ = this; if (pass->backdrop_filter_proc_) { backdrop_filter_reads_from_pass_texture_ = true; } if (pass->blend_mode_ > Entity::kLastPipelineBlendMode) { advanced_blend_reads_from_pass_texture_ = true; } auto subpass_pointer = pass.get(); elements_.emplace_back(std::move(pass)); return subpass_pointer; } static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = RenderTarget::AttachmentConfig{ .storage_mode = StorageMode::kDeviceTransient, .load_action = LoadAction::kDontCare, .store_action = StoreAction::kDontCare, }; static EntityPassTarget CreateRenderTarget(ContentContext& renderer, ISize size, int mip_count, const Color& clear_color) { const std::shared_ptr& context = renderer.GetContext(); /// All of the load/store actions are managed by `InlinePassContext` when /// `RenderPasses` are created, so we just set them to `kDontCare` here. /// What's important is the `StorageMode` of the textures, which cannot be /// changed for the lifetime of the textures. if (context->GetBackendType() == Context::BackendType::kOpenGLES) { // TODO(https://github.com/flutter/flutter/issues/141732): Implement mip map // generation on opengles. mip_count = 1; } RenderTarget target; if (context->GetCapabilities()->SupportsOffscreenMSAA()) { target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA( /*context=*/*context, /*size=*/size, /*mip_count=*/mip_count, /*label=*/"EntityPass", /*color_attachment_config=*/ RenderTarget::AttachmentConfigMSAA{ .storage_mode = StorageMode::kDeviceTransient, .resolve_storage_mode = StorageMode::kDevicePrivate, .load_action = LoadAction::kDontCare, .store_action = StoreAction::kMultisampleResolve, .clear_color = clear_color}, /*stencil_attachment_config=*/ kDefaultStencilConfig); } else { target = renderer.GetRenderTargetCache()->CreateOffscreen( *context, // context size, // size /*mip_count=*/mip_count, "EntityPass", // label RenderTarget::AttachmentConfig{ .storage_mode = StorageMode::kDevicePrivate, .load_action = LoadAction::kDontCare, .store_action = StoreAction::kDontCare, .clear_color = clear_color, }, // color_attachment_config kDefaultStencilConfig // stencil_attachment_config ); } return EntityPassTarget( target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(), renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); } bool EntityPass::DoesBackdropGetRead(ContentContext& renderer) const { return renderer.GetDeviceCapabilities().SupportsFramebufferFetch() ? backdrop_filter_reads_from_pass_texture_ : backdrop_filter_reads_from_pass_texture_ || advanced_blend_reads_from_pass_texture_; } bool EntityPass::Render(ContentContext& renderer, const RenderTarget& render_target) const { renderer.GetRenderTargetCache()->Start(); fml::ScopedCleanupClosure reset_state([&renderer]() { renderer.GetLazyGlyphAtlas()->ResetTextFrames(); renderer.GetRenderTargetCache()->End(); }); auto root_render_target = render_target; if (root_render_target.GetColorAttachments().find(0u) == root_render_target.GetColorAttachments().end()) { VALIDATION_LOG << "The root RenderTarget must have a color attachment."; return false; } if (root_render_target.GetDepthAttachment().has_value() != root_render_target.GetStencilAttachment().has_value()) { VALIDATION_LOG << "The root RenderTarget should have a stencil attachment " "iff it has a depth attachment."; return false; } const auto& lazy_glyph_atlas = renderer.GetLazyGlyphAtlas(); IterateAllEntities([&lazy_glyph_atlas](const Entity& entity) { if (const auto& contents = entity.GetContents()) { contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale()); } return true; }); EntityPassClipStack clip_stack = EntityPassClipStack( Rect::MakeSize(root_render_target.GetRenderTargetSize())); // In this branch path, we need to render everything to an offscreen texture // and then blit the results onto the onscreen texture. If using this branch, // there's no need to set up a stencil attachment on the root render target. if (DoesBackdropGetRead(renderer)) { EntityPassTarget offscreen_target = CreateRenderTarget( renderer, root_render_target.GetRenderTargetSize(), GetRequiredMipCount(), GetClearColorOrDefault(render_target.GetRenderTargetSize())); if (!OnRender(renderer, // renderer offscreen_target.GetRenderTarget() .GetRenderTargetSize(), // root_pass_size offscreen_target, // pass_target Point(), // global_pass_position Point(), // local_pass_position 0, // pass_depth clip_stack // clip_coverage_stack )) { // Validation error messages are triggered for all `OnRender()` failure // cases. return false; } auto command_buffer = renderer.GetContext()->CreateCommandBuffer(); command_buffer->SetLabel("EntityPass Root Command Buffer"); // If the context supports blitting, blit the offscreen texture to the // onscreen texture. Otherwise, draw it to the parent texture using a // pipeline (slower). if (renderer.GetContext() ->GetCapabilities() ->SupportsTextureToTextureBlits()) { auto blit_pass = command_buffer->CreateBlitPass(); blit_pass->AddCopy( offscreen_target.GetRenderTarget().GetRenderTargetTexture(), root_render_target.GetRenderTargetTexture()); if (!blit_pass->EncodeCommands( renderer.GetContext()->GetResourceAllocator())) { VALIDATION_LOG << "Failed to encode root pass blit command."; return false; } if (!renderer.GetContext() ->GetCommandQueue() ->Submit({command_buffer}) .ok()) { return false; } } else { auto render_pass = command_buffer->CreateRenderPass(root_render_target); render_pass->SetLabel("EntityPass Root Render Pass"); { auto size_rect = Rect::MakeSize( offscreen_target.GetRenderTarget().GetRenderTargetSize()); auto contents = TextureContents::MakeRect(size_rect); contents->SetTexture( offscreen_target.GetRenderTarget().GetRenderTargetTexture()); contents->SetSourceRect(size_rect); contents->SetLabel("Root pass blit"); Entity entity; entity.SetContents(contents); entity.SetBlendMode(BlendMode::kSource); if (!entity.Render(renderer, *render_pass)) { VALIDATION_LOG << "Failed to render EntityPass root blit."; return false; } } if (!render_pass->EncodeCommands()) { VALIDATION_LOG << "Failed to encode root pass command buffer."; return false; } if (!renderer.GetContext() ->GetCommandQueue() ->Submit({command_buffer}) .ok()) { return false; } } return true; } // If we make it this far, that means the context is capable of rendering // everything directly to the onscreen texture. // The safety check for fetching this color attachment is at the beginning of // this method. auto color0 = root_render_target.GetColorAttachments().find(0u)->second; auto stencil_attachment = root_render_target.GetStencilAttachment(); auto depth_attachment = root_render_target.GetDepthAttachment(); if (!stencil_attachment.has_value() || !depth_attachment.has_value()) { // Setup a new root stencil with an optimal configuration if one wasn't // provided by the caller. root_render_target.SetupDepthStencilAttachments( *renderer.GetContext(), *renderer.GetContext()->GetResourceAllocator(), color0.texture->GetSize(), renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(), "ImpellerOnscreen", kDefaultStencilConfig); } // Set up the clear color of the root pass. color0.clear_color = GetClearColorOrDefault(render_target.GetRenderTargetSize()); root_render_target.SetColorAttachment(color0, 0); EntityPassTarget pass_target( root_render_target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(), renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); return OnRender( // renderer, // renderer root_render_target.GetRenderTargetSize(), // root_pass_size pass_target, // pass_target Point(), // global_pass_position Point(), // local_pass_position 0, // pass_depth clip_stack); // clip_coverage_stack } EntityPass::EntityResult EntityPass::GetEntityForElement( const EntityPass::Element& element, ContentContext& renderer, InlinePassContext& pass_context, ISize root_pass_size, Point global_pass_position, uint32_t pass_depth, EntityPassClipStack& clip_coverage_stack, size_t clip_height_floor) const { //-------------------------------------------------------------------------- /// Setup entity element. /// if (const auto& entity = std::get_if(&element)) { Entity element_entity = entity->Clone(); if (!global_pass_position.IsZero()) { // If the pass image is going to be rendered with a non-zero position, // apply the negative translation to entity copies before rendering them // so that they'll end up rendering to the correct on-screen position. element_entity.SetTransform( Matrix::MakeTranslation(Vector3(-global_pass_position)) * element_entity.GetTransform()); } return EntityPass::EntityResult::Success(std::move(element_entity)); } //-------------------------------------------------------------------------- /// Setup subpass element. /// if (const auto& subpass_ptr = std::get_if>(&element)) { auto subpass = subpass_ptr->get(); if (subpass->delegate_->CanElide()) { return EntityPass::EntityResult::Skip(); } if (!subpass->backdrop_filter_proc_ && subpass->delegate_->CanCollapseIntoParentPass(subpass)) { // Directly render into the parent target and move on. if (!subpass->OnRender( renderer, // renderer root_pass_size, // root_pass_size pass_context.GetPassTarget(), // pass_target global_pass_position, // global_pass_position Point(), // local_pass_position pass_depth, // pass_depth clip_coverage_stack, // clip_coverage_stack clip_height_, // clip_height_floor nullptr, // backdrop_filter_contents pass_context.GetRenderPass(pass_depth) // collapsed_parent_pass )) { // Validation error messages are triggered for all `OnRender()` failure // cases. return EntityPass::EntityResult::Failure(); } return EntityPass::EntityResult::Skip(); } std::shared_ptr subpass_backdrop_filter_contents = nullptr; if (subpass->backdrop_filter_proc_) { auto texture = pass_context.GetTexture(); // Render the backdrop texture before any of the pass elements. const auto& proc = subpass->backdrop_filter_proc_; subpass_backdrop_filter_contents = proc( FilterInput::Make(std::move(texture)), subpass->transform_.Basis(), // When the subpass has a translation that means the math with // the snapshot has to be different. subpass->transform_.HasTranslation() ? Entity::RenderingMode::kSubpassPrependSnapshotTransform : Entity::RenderingMode::kSubpassAppendSnapshotTransform); // If the very first thing we render in this EntityPass is a subpass that // happens to have a backdrop filter, than that backdrop filter will end // may wind up sampling from the raw, uncleared texture that came straight // out of the texture cache. By calling `pass_context.GetRenderPass` here, // we force the texture to pass through at least one RenderPass with the // correct clear configuration before any sampling occurs. pass_context.GetRenderPass(pass_depth); // The subpass will need to read from the current pass texture when // rendering the backdrop, so if there's an active pass, end it prior to // rendering the subpass. pass_context.EndPass(); } if (!clip_coverage_stack.HasCoverage()) { // The current clip is empty. This means the pass texture won't be // visible, so skip it. return EntityPass::EntityResult::Skip(); } auto clip_coverage_back = clip_coverage_stack.CurrentClipCoverage(); if (!clip_coverage_back.has_value()) { return EntityPass::EntityResult::Skip(); } // The maximum coverage of the subpass. Subpasses textures should never // extend outside the parent pass texture or the current clip coverage. auto coverage_limit = Rect::MakeOriginSize(global_pass_position, Size(pass_context.GetPassTarget() .GetRenderTarget() .GetRenderTargetSize())) .Intersection(clip_coverage_back.value()); if (!coverage_limit.has_value()) { return EntityPass::EntityResult::Skip(); } coverage_limit = coverage_limit->Intersection(Rect::MakeSize(root_pass_size)); if (!coverage_limit.has_value()) { return EntityPass::EntityResult::Skip(); } auto subpass_coverage = (subpass->flood_clip_ || subpass_backdrop_filter_contents) ? coverage_limit : GetSubpassCoverage(*subpass, coverage_limit); if (!subpass_coverage.has_value()) { return EntityPass::EntityResult::Skip(); } auto subpass_size = ISize(subpass_coverage->GetSize()); if (subpass_size.IsEmpty()) { return EntityPass::EntityResult::Skip(); } auto subpass_target = CreateRenderTarget( renderer, // renderer subpass_size, // size subpass->GetRequiredMipCount(), subpass->GetClearColorOrDefault(subpass_size)); // clear_color if (!subpass_target.IsValid()) { VALIDATION_LOG << "Subpass render target is invalid."; return EntityPass::EntityResult::Failure(); } // Start non-collapsed subpasses with a fresh clip coverage stack limited by // the subpass coverage. This is important because image filters applied to // save layers may transform the subpass texture after it's rendered, // causing parent clip coverage to get misaligned with the actual area that // the subpass will affect in the parent pass. clip_coverage_stack.PushSubpass(subpass_coverage, subpass->clip_height_); // Stencil textures aren't shared between EntityPasses (as much of the // time they are transient). if (!subpass->OnRender( renderer, // renderer root_pass_size, // root_pass_size subpass_target, // pass_target subpass_coverage->GetOrigin(), // global_pass_position subpass_coverage->GetOrigin() - global_pass_position, // local_pass_position ++pass_depth, // pass_depth clip_coverage_stack, // clip_coverage_stack subpass->clip_height_, // clip_height_floor subpass_backdrop_filter_contents // backdrop_filter_contents )) { // Validation error messages are triggered for all `OnRender()` failure // cases. return EntityPass::EntityResult::Failure(); } clip_coverage_stack.PopSubpass(); // The subpass target's texture may have changed during OnRender. auto subpass_texture = subpass_target.GetRenderTarget().GetRenderTargetTexture(); auto offscreen_texture_contents = subpass->delegate_->CreateContentsForSubpassTarget( subpass_texture, Matrix::MakeTranslation(Vector3{-global_pass_position}) * subpass->transform_); if (!offscreen_texture_contents) { // This is an error because the subpass delegate said the pass couldn't // be collapsed into its parent. Yet, when asked how it want's to // postprocess the offscreen texture, it couldn't give us an answer. // // Theoretically, we could collapse the pass now. But that would be // wasteful as we already have the offscreen texture and we don't want // to discard it without ever using it. Just make the delegate do the // right thing. return EntityPass::EntityResult::Failure(); } // Round the subpass texture position for pixel alignment with the parent // pass render target. By default, we draw subpass textures with nearest // sampling, so aligning here is important for avoiding visual nearest // sampling errors caused by limited floating point precision when // straddling a half pixel boundary. // // We do this in lieu of expanding/rounding out the subpass coverage in // order to keep the bounds wrapping consistently tight around subpass // elements. Which is necessary to avoid intense flickering in cases // where a subpass texture has a large blur filter with clamp sampling. // // See also this bug: https://github.com/flutter/flutter/issues/144213 Point subpass_texture_position = (subpass_coverage->GetOrigin() - global_pass_position).Round(); Entity element_entity; element_entity.SetClipDepth(subpass->clip_depth_); element_entity.SetContents(std::move(offscreen_texture_contents)); element_entity.SetBlendMode(subpass->blend_mode_); element_entity.SetTransform( Matrix::MakeTranslation(Vector3(subpass_texture_position))); return EntityPass::EntityResult::Success(std::move(element_entity)); } FML_UNREACHABLE(); } static void SetClipScissor(std::optional clip_coverage, RenderPass& pass, Point global_pass_position) { // Set the scissor to the clip coverage area. We do this prior to rendering // the clip itself and all its contents. IRect scissor; if (clip_coverage.has_value()) { clip_coverage = clip_coverage->Shift(-global_pass_position); scissor = IRect::RoundOut(clip_coverage.value()); // The scissor rect must not exceed the size of the render target. scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize())) .value_or(IRect()); } pass.SetScissor(scissor); } bool EntityPass::RenderElement(Entity& element_entity, size_t clip_height_floor, InlinePassContext& pass_context, int32_t pass_depth, ContentContext& renderer, EntityPassClipStack& clip_coverage_stack, Point global_pass_position) const { auto result = pass_context.GetRenderPass(pass_depth); if (!result.pass) { // Failure to produce a render pass should be explained by specific errors // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't // append a validation log here. return false; } // If the pass context returns a backdrop texture, we need to draw it to the // current pass. We do this because it's faster and takes significantly less // memory than storing/loading large MSAA textures. Also, it's not possible to // blit the non-MSAA resolve texture of the previous pass to MSAA textures // (let alone a transient one). if (result.backdrop_texture) { auto size_rect = Rect::MakeSize(result.pass->GetRenderTargetSize()); auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); msaa_backdrop_contents->SetStencilEnabled(false); msaa_backdrop_contents->SetLabel("MSAA backdrop"); msaa_backdrop_contents->SetSourceRect(size_rect); msaa_backdrop_contents->SetTexture(result.backdrop_texture); Entity msaa_backdrop_entity; msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); if (!msaa_backdrop_entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render MSAA backdrop filter entity."; return false; } } if (result.just_created) { // Restore any clips that were recorded before the backdrop filter was // applied. auto& replay_entities = clip_coverage_stack.GetReplayEntities(); for (const auto& replay : replay_entities) { SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass, global_pass_position); if (!replay.entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render entity for clip restore."; } } } auto current_clip_coverage = clip_coverage_stack.CurrentClipCoverage(); if (current_clip_coverage.has_value()) { // Entity transforms are relative to the current pass position, so we need // to check clip coverage in the same space. current_clip_coverage = current_clip_coverage->Shift(-global_pass_position); } if (!element_entity.ShouldRender(current_clip_coverage)) { return true; // Nothing to render. } auto clip_coverage = element_entity.GetClipCoverage(current_clip_coverage); if (clip_coverage.coverage.has_value()) { clip_coverage.coverage = clip_coverage.coverage->Shift(global_pass_position); } // The coverage hint tells the rendered Contents which portion of the // rendered output will actually be used, and so we set this to the current // clip coverage (which is the max clip bounds). The contents may // optionally use this hint to avoid unnecessary rendering work. auto element_coverage_hint = element_entity.GetContents()->GetCoverageHint(); element_entity.GetContents()->SetCoverageHint( Rect::Intersection(element_coverage_hint, current_clip_coverage)); EntityPassClipStack::ClipStateResult clip_state_result = clip_coverage_stack.ApplyClipState(clip_coverage, element_entity, clip_height_floor, global_pass_position); if (clip_state_result.clip_did_change) { // We only need to update the pass scissor if the clip state has changed. SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass, global_pass_position); } if (!clip_state_result.should_render) { return true; } if (!element_entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render entity."; return false; } return true; } bool EntityPass::OnRender( ContentContext& renderer, ISize root_pass_size, EntityPassTarget& pass_target, Point global_pass_position, Point local_pass_position, uint32_t pass_depth, EntityPassClipStack& clip_coverage_stack, size_t clip_height_floor, std::shared_ptr backdrop_filter_contents, const std::optional& collapsed_parent_pass) const { TRACE_EVENT0("impeller", "EntityPass::OnRender"); if (!active_clips_.empty()) { VALIDATION_LOG << SPrintF( "EntityPass (Depth=%d) contains one or more clips with an unresolved " "depth value.", pass_depth); } InlinePassContext pass_context(renderer, pass_target, GetElementCount(), collapsed_parent_pass); if (!pass_context.IsValid()) { VALIDATION_LOG << SPrintF("Pass context invalid (Depth=%d)", pass_depth); return false; } auto clear_color_size = pass_target.GetRenderTarget().GetRenderTargetSize(); if (!collapsed_parent_pass) { // Always force the pass to construct the render pass object, even if there // is not a clear color. This ensures that the attachment textures are // cleared/transitioned to the right state. pass_context.GetRenderPass(pass_depth); } if (backdrop_filter_proc_) { if (!backdrop_filter_contents) { VALIDATION_LOG << "EntityPass contains a backdrop filter, but no backdrop filter " "contents was supplied by the parent pass at render time. This is " "a bug in EntityPass. Parent passes are responsible for setting " "up backdrop filters for their children."; return false; } Entity backdrop_entity; backdrop_entity.SetContents(std::move(backdrop_filter_contents)); backdrop_entity.SetTransform( Matrix::MakeTranslation(Vector3(-local_pass_position))); backdrop_entity.SetClipDepth(std::numeric_limits::max()); RenderElement(backdrop_entity, clip_height_floor, pass_context, pass_depth, renderer, clip_coverage_stack, global_pass_position); } bool is_collapsing_clear_colors = !collapsed_parent_pass && // Backdrop filters act as a entity before // everything and disrupt the optimization. !backdrop_filter_proc_; for (const auto& element : elements_) { // Skip elements that are incorporated into the clear color. if (is_collapsing_clear_colors) { auto [entity_color, _] = ElementAsBackgroundColor(element, clear_color_size); if (entity_color.has_value()) { continue; } is_collapsing_clear_colors = false; } EntityResult result = GetEntityForElement(element, // element renderer, // renderer pass_context, // pass_context root_pass_size, // root_pass_size global_pass_position, // global_pass_position pass_depth, // pass_depth clip_coverage_stack, // clip_coverage_stack clip_height_floor); // clip_height_floor switch (result.status) { case EntityResult::kSuccess: break; case EntityResult::kFailure: // All failure cases should be covered by specific validation messages // in `GetEntityForElement()`. return false; case EntityResult::kSkip: continue; }; //-------------------------------------------------------------------------- /// Setup advanced blends. /// if (result.entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) { auto src_contents = result.entity.GetContents(); auto contents = std::make_shared(); contents->SetChildContents(src_contents); contents->SetBlendMode(result.entity.GetBlendMode()); result.entity.SetContents(std::move(contents)); result.entity.SetBlendMode(BlendMode::kSource); } else { // End the active pass and flush the buffer before rendering "advanced" // blends. Advanced blends work by binding the current render target // texture as an input ("destination"), blending with a second texture // input ("source"), writing the result to an intermediate texture, and // finally copying the data from the intermediate texture back to the // render target texture. And so all of the commands that have written // to the render target texture so far need to execute before it's bound // for blending (otherwise the blend pass will end up executing before // all the previous commands in the active pass). if (!pass_context.EndPass()) { VALIDATION_LOG << "Failed to end the current render pass in order to read from " "the backdrop texture and apply an advanced blend."; return false; } // Amend an advanced blend filter to the contents, attaching the pass // texture. auto texture = pass_context.GetTexture(); if (!texture) { VALIDATION_LOG << "Failed to fetch the color texture in order to " "apply an advanced blend."; return false; } FilterInput::Vector inputs = { FilterInput::Make(texture, result.entity.GetTransform().Invert()), FilterInput::Make(result.entity.GetContents())}; auto contents = ColorFilterContents::MakeBlend( result.entity.GetBlendMode(), inputs); contents->SetCoverageHint(result.entity.GetCoverage()); result.entity.SetContents(std::move(contents)); result.entity.SetBlendMode(BlendMode::kSource); } } //-------------------------------------------------------------------------- /// Render the Element. /// if (!RenderElement(result.entity, clip_height_floor, pass_context, pass_depth, renderer, clip_coverage_stack, global_pass_position)) { // Specific validation logs are handled in `render_element()`. return false; } } return true; } void EntityPass::IterateAllElements( const std::function& iterator) { if (!iterator) { return; } for (auto& element : elements_) { if (!iterator(element)) { return; } if (auto subpass = std::get_if>(&element)) { subpass->get()->IterateAllElements(iterator); } } } void EntityPass::IterateAllElements( const std::function& iterator) const { /// TODO(gaaclarke): Remove duplication here between const and non-const /// versions. if (!iterator) { return; } for (auto& element : elements_) { if (!iterator(element)) { return; } if (auto subpass = std::get_if>(&element)) { const EntityPass* entity_pass = subpass->get(); entity_pass->IterateAllElements(iterator); } } } void EntityPass::IterateAllEntities( const std::function& iterator) { if (!iterator) { return; } for (auto& element : elements_) { if (auto entity = std::get_if(&element)) { if (!iterator(*entity)) { return; } continue; } if (auto subpass = std::get_if>(&element)) { subpass->get()->IterateAllEntities(iterator); continue; } FML_UNREACHABLE(); } } void EntityPass::IterateAllEntities( const std::function& iterator) const { if (!iterator) { return; } for (const auto& element : elements_) { if (auto entity = std::get_if(&element)) { if (!iterator(*entity)) { return; } continue; } if (auto subpass = std::get_if>(&element)) { const EntityPass* entity_pass = subpass->get(); entity_pass->IterateAllEntities(iterator); continue; } FML_UNREACHABLE(); } } bool EntityPass::IterateUntilSubpass( const std::function& iterator) { if (!iterator) { return true; } for (auto& element : elements_) { if (auto entity = std::get_if(&element)) { if (!iterator(*entity)) { return false; } continue; } return true; } return false; } size_t EntityPass::GetElementCount() const { return elements_.size(); } void EntityPass::SetTransform(Matrix transform) { transform_ = transform; } void EntityPass::SetClipHeight(size_t clip_height) { clip_height_ = clip_height; } size_t EntityPass::GetClipHeight() const { return clip_height_; } void EntityPass::SetClipDepth(size_t clip_depth) { clip_depth_ = clip_depth; } uint32_t EntityPass::GetClipDepth() const { return clip_depth_; } void EntityPass::SetBlendMode(BlendMode blend_mode) { blend_mode_ = blend_mode; flood_clip_ = Entity::IsBlendModeDestructive(blend_mode); } Color EntityPass::GetClearColorOrDefault(ISize size) const { return GetClearColor(size).value_or(Color::BlackTransparent()); } std::optional EntityPass::GetClearColor(ISize target_size) const { if (backdrop_filter_proc_) { return std::nullopt; } std::optional result = std::nullopt; for (const Element& element : elements_) { auto [entity_color, blend_mode] = ElementAsBackgroundColor(element, target_size); if (!entity_color.has_value()) { break; } result = result.value_or(Color::BlackTransparent()) .Blend(entity_color.value(), blend_mode); } if (result.has_value()) { return result->Premultiply(); } return result; } void EntityPass::SetBackdropFilter(BackdropFilterProc proc) { if (superpass_) { VALIDATION_LOG << "Backdrop filters cannot be set on EntityPasses that " "have already been appended to another pass."; } backdrop_filter_proc_ = std::move(proc); } } // namespace impeller