blob: 30b9ab562f8b937450546f6ae5f62228f6000783 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/slim/layer_tree_impl.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "base/auto_reset.h"
#include "base/containers/adapters.h"
#include "base/ranges/algorithm.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "cc/base/region.h"
#include "cc/slim/frame_data.h"
#include "cc/slim/frame_sink_impl.h"
#include "cc/slim/layer.h"
#include "cc/slim/layer_tree_client.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/hit_test/hit_test_region_list.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/draw_quad.h"
#include "components/viz/common/quads/frame_deadline.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace cc::slim {
namespace {
class LayerTreeImplScopedKeepSurfaceAlive
: public LayerTree::ScopedKeepSurfaceAlive {
public:
LayerTreeImplScopedKeepSurfaceAlive(base::WeakPtr<LayerTreeImpl> layer_tree,
const viz::SurfaceId& surface_id)
: layer_tree_(std::move(layer_tree)), range_(surface_id, surface_id) {
layer_tree_->AddSurfaceRange(range_);
}
~LayerTreeImplScopedKeepSurfaceAlive() override {
if (layer_tree_) {
layer_tree_->RemoveSurfaceRange(range_);
}
}
private:
const base::WeakPtr<LayerTreeImpl> layer_tree_;
const viz::SurfaceRange range_;
};
} // namespace
LayerTreeImpl::PresentationCallbackInfo::PresentationCallbackInfo(
uint32_t frame_token,
std::vector<PresentationCallback> presentation_callbacks,
std::vector<SuccessfulCallback> success_callbacks)
: frame_token(frame_token),
presentation_callbacks(std::move(presentation_callbacks)),
success_callbacks(std::move(success_callbacks)) {}
LayerTreeImpl::PresentationCallbackInfo::~PresentationCallbackInfo() = default;
LayerTreeImpl::PresentationCallbackInfo::PresentationCallbackInfo(
PresentationCallbackInfo&&) = default;
LayerTreeImpl::PresentationCallbackInfo&
LayerTreeImpl::PresentationCallbackInfo::operator=(PresentationCallbackInfo&&) =
default;
LayerTreeImpl::LayerTreeImpl(LayerTreeClient* client,
uint32_t num_unneeded_begin_frame_before_stop,
int min_occlusion_tracking_dimension)
: client_(client),
num_unneeded_begin_frame_before_stop_(
num_unneeded_begin_frame_before_stop),
min_occlusion_tracking_dimension_(min_occlusion_tracking_dimension) {}
LayerTreeImpl::~LayerTreeImpl() {
SetRoot(nullptr);
}
cc::UIResourceManager* LayerTreeImpl::GetUIResourceManager() {
return &ui_resource_manager_;
}
void LayerTreeImpl::SetViewportRectAndScale(
const gfx::Rect& device_viewport_rect,
float device_scale_factor,
const viz::LocalSurfaceId& local_surface_id) {
bool id_updated =
local_surface_id_allocator_.UpdateFromParent(local_surface_id);
if (device_viewport_rect_ == device_viewport_rect &&
device_scale_factor_ == device_scale_factor && !id_updated) {
return;
}
if (frame_sink_) {
frame_sink_->SetLocalSurfaceId(
local_surface_id_allocator_.GetCurrentLocalSurfaceId());
}
device_viewport_rect_ = device_viewport_rect;
device_scale_factor_ = device_scale_factor;
damage_from_previous_frame_.clear();
SetNeedsDraw();
}
void LayerTreeImpl::set_background_color(SkColor4f color) {
if (background_color_ == color) {
return;
}
background_color_ = color;
damage_from_previous_frame_.clear();
SetNeedsDraw();
}
void LayerTreeImpl::SetVisible(bool visible) {
if (visible_ == visible) {
return;
}
visible_ = visible;
MaybeRequestFrameSink();
SetNeedsDraw();
}
bool LayerTreeImpl::IsVisible() const {
return visible_;
}
void LayerTreeImpl::RequestPresentationTimeForNextFrame(
PresentationCallback callback) {
presentation_callback_for_next_frame_.emplace_back(std::move(callback));
}
void LayerTreeImpl::RequestSuccessfulPresentationTimeForNextFrame(
SuccessfulCallback callback) {
success_callback_for_next_frame_.emplace_back(std::move(callback));
}
void LayerTreeImpl::set_display_transform_hint(gfx::OverlayTransform hint) {
display_transform_hint_ = hint;
}
void LayerTreeImpl::RequestCopyOfOutput(
std::unique_ptr<viz::CopyOutputRequest> request) {
if (request->has_source()) {
const base::UnguessableToken& source = request->source();
auto it = base::ranges::find_if(
copy_requests_for_next_frame_,
[&source](const std::unique_ptr<viz::CopyOutputRequest>& x) {
return x->has_source() && x->source() == source;
});
if (it != copy_requests_for_next_frame_.end()) {
copy_requests_for_next_frame_.erase(it);
}
}
copy_requests_for_next_frame_.push_back(std::move(request));
SetNeedsDraw();
}
base::OnceClosure LayerTreeImpl::DeferBeginFrame() {
num_defer_begin_frame_++;
UpdateNeedsBeginFrame();
return base::BindOnce(&LayerTreeImpl::ReleaseDeferBeginFrame,
weak_factory_.GetWeakPtr());
}
void LayerTreeImpl::ReleaseDeferBeginFrame() {
DCHECK_GT(num_defer_begin_frame_, 0u);
num_defer_begin_frame_--;
UpdateNeedsBeginFrame();
}
void LayerTreeImpl::UpdateTopControlsVisibleHeight(float height) {
if (top_controls_visible_height_ &&
top_controls_visible_height_.value() == height) {
return;
}
top_controls_visible_height_ = height;
SetNeedsDraw();
}
void LayerTreeImpl::SetNeedsAnimate() {
SetClientNeedsOneBeginFrame();
}
void LayerTreeImpl::MaybeCompositeNow() {
if (frame_sink_) {
frame_sink_->MaybeCompositeNow();
}
}
const scoped_refptr<Layer>& LayerTreeImpl::root() const {
return root_;
}
void LayerTreeImpl::SetRoot(scoped_refptr<Layer> root) {
if (root_ == root) {
return;
}
if (root_) {
root_->SetLayerTree(nullptr);
}
root_ = std::move(root);
if (root_) {
root_->SetLayerTree(this);
SetNeedsDraw();
}
damage_from_previous_frame_.clear();
}
void LayerTreeImpl::SetFrameSink(std::unique_ptr<FrameSink> sink) {
DCHECK(sink);
frame_sink_.reset(static_cast<FrameSinkImpl*>(sink.release()));
if (!frame_sink_->BindToClient(this)) {
frame_sink_.reset();
// This is equivalent to requesting another frame sink, so do not reset
// `frame_sink_request_pending_` to avoid extra unexpected calls to
// `RequestNewFrameSink`.
client_->DidFailToInitializeLayerTreeFrameSink();
return;
}
frame_sink_request_pending_ = false;
if (local_surface_id_allocator_.GetCurrentLocalSurfaceId().is_valid()) {
local_surface_id_allocator_.GenerateId();
frame_sink_->SetLocalSurfaceId(
local_surface_id_allocator_.GetCurrentLocalSurfaceId());
}
client_->DidInitializeLayerTreeFrameSink();
ui_resource_manager_.RecreateUIResources();
damage_from_previous_frame_.clear();
UpdateNeedsBeginFrame();
}
void LayerTreeImpl::ReleaseLayerTreeFrameSink() {
DCHECK(!IsVisible());
frame_sink_.reset();
damage_from_previous_frame_.clear();
}
std::unique_ptr<LayerTree::ScopedKeepSurfaceAlive>
LayerTreeImpl::CreateScopedKeepSurfaceAlive(const viz::SurfaceId& surface_id) {
return std::make_unique<LayerTreeImplScopedKeepSurfaceAlive>(
weak_factory_.GetWeakPtr(), surface_id);
}
const LayerTree::SurfaceRangesAndCounts&
LayerTreeImpl::GetSurfaceRangesForTesting() const {
return referenced_surfaces_;
}
void LayerTreeImpl::SetNeedsRedrawForTesting() {
// Clearing the previous damages, so that when the next BeginFrame arrives,
// the root layer will be treated as a new layer.
damage_from_previous_frame_.clear();
SetNeedsDraw();
}
bool LayerTreeImpl::BeginFrame(
const viz::BeginFrameArgs& args,
viz::CompositorFrame& out_frame,
base::flat_set<viz::ResourceId>& out_resource_ids,
viz::HitTestRegionList& out_hit_test_region_list) {
// Skip any delayed BeginFrame messages that arrive even after we no longer
// need it.
if (!NeedsDraw()) {
TRACE_EVENT_INSTANT0("cc", "EarlyOut_NotNeeded", TRACE_EVENT_SCOPE_THREAD);
num_begin_frames_with_no_draw_++;
frame_sink_->SetNeedsBeginFrame(NeedsBeginFrames());
return false;
}
num_begin_frames_with_no_draw_ = 0u;
// Unset `client_needs_one_begin_frame_` before BeginFrame. If client
// requests more frames from inside the BeginFrame call, it's for the next
// frame.
client_needs_one_begin_frame_ = false;
{
base::AutoReset<bool> reset(&update_needs_begin_frame_pending_, true);
client_->BeginFrame(args);
}
// Unset `needs_draw_` after client `BeginFrame`. Any layer or tree property
// changes made by client `BeginFrame` are about to be drawn, so there is no
// need for another frame.
needs_draw_ = false;
if (!root_ || device_viewport_rect_.IsEmpty()) {
UpdateNeedsBeginFrame();
return false;
}
GenerateCompositorFrame(args, out_frame, out_resource_ids,
out_hit_test_region_list);
UpdateNeedsBeginFrame();
return true;
}
void LayerTreeImpl::DidReceiveCompositorFrameAck() {
client_->DidReceiveCompositorFrameAck();
}
void LayerTreeImpl::DidSubmitCompositorFrame() {
client_->DidSubmitCompositorFrame();
}
void LayerTreeImpl::DidPresentCompositorFrame(
uint32_t frame_token,
const viz::FrameTimingDetails& details) {
const bool success = !details.presentation_feedback.failed();
for (auto itr = pending_presentation_callbacks_.begin();
itr != pending_presentation_callbacks_.end();) {
if (viz::FrameTokenGT(itr->frame_token, frame_token)) {
break;
}
for (auto& callback : itr->presentation_callbacks) {
std::move(callback).Run(details.presentation_feedback);
}
itr->presentation_callbacks.clear();
// Only run `success_callbacks` if successful.
if (success) {
for (auto& callback : itr->success_callbacks) {
std::move(callback).Run(details.presentation_feedback.timestamp);
}
itr->success_callbacks.clear();
}
// Keep the entry of `success_callbacks` is not empty, meaning this frame
// wasn't successful, so that it can run on a subsequent successful frame.
if (itr->success_callbacks.empty()) {
itr = pending_presentation_callbacks_.erase(itr);
} else {
itr++;
}
}
}
void LayerTreeImpl::DidLoseLayerTreeFrameSink() {
client_->DidLoseLayerTreeFrameSink();
frame_sink_.reset();
MaybeRequestFrameSink();
}
void LayerTreeImpl::NotifyTreeChanged() {
SetNeedsDraw();
}
viz::ClientResourceProvider* LayerTreeImpl::GetClientResourceProvider() {
if (!frame_sink_) {
return nullptr;
}
return frame_sink_->client_resource_provider();
}
viz::ResourceId LayerTreeImpl::GetVizResourceId(cc::UIResourceId id) {
if (!frame_sink_) {
return viz::kInvalidResourceId;
}
return frame_sink_->GetVizResourceId(id);
}
bool LayerTreeImpl::IsUIResourceOpaque(int resource_id) {
return !frame_sink_ || frame_sink_->IsUIResourceOpaque(resource_id);
}
gfx::Size LayerTreeImpl::GetUIResourceSize(int resource_id) {
if (!frame_sink_) {
return gfx::Size();
}
return frame_sink_->GetUIResourceSize(resource_id);
}
void LayerTreeImpl::AddSurfaceRange(const viz::SurfaceRange& range) {
DCHECK(range.IsValid());
DCHECK(!referenced_surfaces_.contains(range) ||
referenced_surfaces_[range] >= 1);
if (++(referenced_surfaces_[range]) == 1) {
SetNeedsDraw();
}
}
void LayerTreeImpl::RemoveSurfaceRange(const viz::SurfaceRange& range) {
DCHECK(range.IsValid());
DCHECK(referenced_surfaces_.contains(range) &&
referenced_surfaces_[range] >= 1);
if (--(referenced_surfaces_[range]) == 0) {
referenced_surfaces_.erase(range);
SetNeedsDraw();
}
}
void LayerTreeImpl::MaybeRequestFrameSink() {
if (frame_sink_ || !visible_ || frame_sink_request_pending_) {
return;
}
frame_sink_request_pending_ = true;
client_->RequestNewFrameSink();
}
void LayerTreeImpl::UpdateNeedsBeginFrame() {
if (update_needs_begin_frame_pending_) {
return;
}
if (frame_sink_ && NeedsBeginFrames()) {
frame_sink_->SetNeedsBeginFrame(true);
}
}
void LayerTreeImpl::SetClientNeedsOneBeginFrame() {
client_needs_one_begin_frame_ = true;
UpdateNeedsBeginFrame();
}
void LayerTreeImpl::SetNeedsDraw() {
needs_draw_ = true;
UpdateNeedsBeginFrame();
}
bool LayerTreeImpl::NeedsDraw() const {
if (!visible_ || !frame_sink_ || num_defer_begin_frame_ > 0u) {
return false;
}
return client_needs_one_begin_frame_ || needs_draw_;
}
bool LayerTreeImpl::NeedsBeginFrames() const {
return NeedsDraw() ||
num_begin_frames_with_no_draw_ < num_unneeded_begin_frame_before_stop_;
}
void LayerTreeImpl::GenerateCompositorFrame(
const viz::BeginFrameArgs& args,
viz::CompositorFrame& out_frame,
base::flat_set<viz::ResourceId>& out_resource_ids,
viz::HitTestRegionList& out_hit_test_region_list) {
TRACE_EVENT(
"viz,benchmark,graphics.pipeline", "Graphics.Pipeline",
perfetto::Flow::Global(args.trace_id), [&](perfetto::EventContext ctx) {
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* data = event->set_chrome_graphics_pipeline();
data->set_step(perfetto::protos::pbzero::ChromeGraphicsPipeline::
StepName::STEP_GENERATE_COMPOSITOR_FRAME);
});
for (auto& resource_request :
ui_resource_manager_.TakeUIResourcesRequests()) {
switch (resource_request.GetType()) {
case cc::UIResourceRequest::Type::kCreate:
frame_sink_->UploadUIResource(resource_request.GetId(),
resource_request.GetBitmap());
break;
case cc::UIResourceRequest::Type::kDelete:
frame_sink_->MarkUIResourceForDeletion(resource_request.GetId());
break;
}
}
out_hit_test_region_list.flags = viz::HitTestRegionFlags::kHitTestMine |
viz::HitTestRegionFlags::kHitTestMouse |
viz::HitTestRegionFlags::kHitTestTouch;
out_hit_test_region_list.bounds = device_viewport_rect_;
auto render_pass = viz::CompositorRenderPass::Create();
render_pass->SetNew(viz::CompositorRenderPassId(root_->id()),
/*output_rect=*/device_viewport_rect_,
/*damage_rect=*/device_viewport_rect_,
/*transform_to_root_target=*/gfx::Transform());
out_frame.metadata.frame_token = ++next_frame_token_;
out_frame.metadata.begin_frame_ack =
viz::BeginFrameAck(args, /*has_damage=*/true);
out_frame.metadata.device_scale_factor = device_scale_factor_;
out_frame.metadata.root_background_color = background_color_;
out_frame.metadata.referenced_surfaces.reserve(referenced_surfaces_.size());
for (const auto& [range, range_counts] : referenced_surfaces_) {
out_frame.metadata.referenced_surfaces.emplace_back(range);
}
out_frame.metadata.top_controls_visible_height = top_controls_visible_height_;
top_controls_visible_height_.reset();
out_frame.metadata.display_transform_hint = display_transform_hint_;
FrameData frame_data(out_frame, out_hit_test_region_list.regions);
Draw(*root_, *render_pass, frame_data,
/*parent_transform_to_root=*/gfx::Transform(),
/*parent_transform_to_target=*/gfx::Transform(),
/*parent_clip_in_target=*/nullptr, gfx::RectF(device_viewport_rect_),
/*opacity=*/1.0f);
render_pass->filters = root_->GetFilters();
bool background_opaque = background_color_.isOpaque();
bool viewport_fully_occluded =
frame_data.occlusion_in_target.Contains(device_viewport_rect_);
render_pass->has_transparent_background =
!background_opaque && !viewport_fully_occluded;
if (background_color_.fA && !viewport_fully_occluded) {
// Quads does not cover entire viewport. Fill in the gutters.
Region unoccluded_region(device_viewport_rect_);
for (size_t i = 0; i < frame_data.occlusion_in_target.GetRegionComplexity();
++i) {
unoccluded_region.Subtract(frame_data.occlusion_in_target.GetRect(i));
}
if (!unoccluded_region.IsEmpty()) {
viz::SharedQuadState* quad_state =
render_pass->CreateAndAppendSharedQuadState();
gfx::Rect gutter_bounding_rect = unoccluded_region.bounds();
bool contents_opaque =
background_opaque && unoccluded_region.GetRegionComplexity() <= 1;
quad_state->SetAll(gfx::Transform(), gutter_bounding_rect,
gutter_bounding_rect, gfx::MaskFilterInfo(),
/*clip=*/std::nullopt, contents_opaque,
/*opacity_f=*/1.0f, SkBlendMode::kSrcOver,
/*sorting_context=*/0, /*layer_id=*/0u,
/*fast_rounded_corner=*/false);
for (gfx::Rect unoccluded_rect : unoccluded_region) {
viz::SolidColorDrawQuad* quad =
render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
quad->SetNew(quad_state, unoccluded_rect, unoccluded_rect,
background_color_, /*anti_aliasing_off=*/false);
}
}
}
ProcessDamageForRenderPass(*render_pass, frame_data);
damage_from_previous_frame_ = std::move(frame_data.current_frame_damage);
frame_data.current_frame_damage.clear();
render_pass->copy_requests = std::move(copy_requests_for_next_frame_);
copy_requests_for_next_frame_.clear();
out_frame.render_pass_list.push_back(std::move(render_pass));
out_frame.metadata.activation_dependencies =
std::vector<viz::SurfaceId>(frame_data.activation_dependencies.begin(),
frame_data.activation_dependencies.end());
out_frame.metadata.deadline = viz::FrameDeadline(
args.frame_time, frame_data.deadline_in_frames.value_or(0u),
args.interval, frame_data.use_default_lower_bound_deadline);
for (const auto& pass : out_frame.render_pass_list) {
for (const auto* quad : pass->quad_list) {
for (viz::ResourceId resource_id : quad->resources) {
out_resource_ids.insert(resource_id);
}
}
}
if (!presentation_callback_for_next_frame_.empty() ||
!success_callback_for_next_frame_.empty()) {
pending_presentation_callbacks_.emplace_back(
out_frame.metadata.frame_token,
std::move(presentation_callback_for_next_frame_),
std::move(success_callback_for_next_frame_));
}
}
void LayerTreeImpl::Draw(Layer& layer,
viz::CompositorRenderPass& parent_pass,
FrameData& data,
const gfx::Transform& parent_transform_to_root,
const gfx::Transform& parent_transform_to_target,
const gfx::RectF* parent_clip_in_target,
const gfx::RectF& clip_in_parent,
float parent_opacity) {
DCHECK(!clip_in_parent.IsEmpty());
if (layer.hide_layer_and_subtree() || layer.opacity() == 0.0f) {
return;
}
std::optional<gfx::Transform> transform_from_parent =
layer.ComputeTransformFromParent();
// If a 2d transform isn't invertible, then it must map the whole 2d space to
// a single line or pointer, neither is visible.
if (!transform_from_parent) {
DLOG(WARNING) << "Skipping layer subtree from non-invertible transform.";
return;
}
// Compute new clip in layer space.
const bool mask_to_bounds =
layer.masks_to_bounds() || layer.HasNonTrivialMaskFilterInfo();
gfx::RectF clip_in_layer = transform_from_parent->MapRect(clip_in_parent);
if (mask_to_bounds) {
clip_in_layer.Intersect(
gfx::RectF(layer.bounds().width(), layer.bounds().height()));
}
if (clip_in_layer.IsEmpty()) {
return;
}
gfx::Transform transform_to_target = parent_transform_to_target;
gfx::Transform transform_to_root = parent_transform_to_root;
{
// new_transform = parent_transform x layer_to_parent.
const gfx::Transform transform_to_parent = layer.ComputeTransformToParent();
transform_to_target.PreConcat(transform_to_parent);
transform_to_root.PreConcat(transform_to_parent);
}
{
const int num_drawing_layers_in_subtree =
layer.GetNumDrawingLayersInSubtree();
const bool is_root = root_.get() == &layer;
const bool filters_needs_pass = layer.HasFilters() && !is_root;
// There is no way to merge 2 rounded corners, so create a render pass so
// existing rounded corners can go into RenderPassDrawQuad, and the layer's
// rounded corners can go into quad its own pass.
const bool mask_filter_needs_pass =
layer.HasNonTrivialMaskFilterInfo() &&
(data.mask_filter_info_in_target.HasRoundedCorners() ||
data.mask_filter_info_in_target.HasGradientMask());
const bool clip_needs_pass =
!is_root && mask_to_bounds &&
!transform_to_target.Preserves2dAxisAlignment();
const bool opacity_needs_pass =
layer.opacity() != 1.0f && num_drawing_layers_in_subtree > 1;
if (!filters_needs_pass && !clip_needs_pass && !mask_filter_needs_pass &&
!opacity_needs_pass) {
// Does not need new render pass.
// Compute new clip in target space.
gfx::RectF new_clip_in_target(gfx::SizeF(layer.bounds()));
const gfx::RectF* clip_in_target = parent_clip_in_target;
if (mask_to_bounds) {
new_clip_in_target = transform_to_target.MapRect(new_clip_in_target);
if (parent_clip_in_target) {
new_clip_in_target.Intersect(*parent_clip_in_target);
}
if (!new_clip_in_target.Contains(gfx::RectF(parent_pass.output_rect))) {
clip_in_target = &new_clip_in_target;
}
}
DrawChildrenAndAppendQuads(
layer, parent_pass, data, transform_to_root, transform_to_target,
clip_in_target, clip_in_layer, parent_opacity * layer.opacity());
return;
}
}
std::unique_ptr<viz::CompositorRenderPass> new_pass;
gfx::Rect new_pass_clip;
// Scale can be applied when drawing layers into the new pass, or when
// drawing the new pass into its target pass. Generally prefer the former to
// avoid visual artifacts when scaling the output of the new pass. Therefore
// the space of the new pass is the space of the layer with
// `scale_to_new_pass` applied.
// Another way to think about this: to_parent is split into scale_to_new_pass
// and new_pass_to_parent such that:
// to_parent = new_pass_to_parent x scale_to_new_pass
gfx::Vector2dF scale_to_new_pass;
gfx::Transform transform_new_pass_to_parent_target;
{
// Compute `scale_to_new_pass` first.
scale_to_new_pass = gfx::ComputeTransform2dScaleComponents(
transform_to_root, /*fallback_value=*/1.0f);
// Only allow content scale to scale down (to save memory). Slim
// compositor does support any vector content that is then rastered, so
// there is no need to scale up a render pass to avoid visual artifacts.
scale_to_new_pass.SetToMin({1.0f, 1.0f});
DCHECK_NE(scale_to_new_pass.x(), 0.0f);
DCHECK_NE(scale_to_new_pass.y(), 0.0f);
// Compute "from new pass" transforms from "from layer" transforms by
// applying inverse scale.
float inverse_scale_x = 1.0f / scale_to_new_pass.x();
float inverse_scale_y = 1.0f / scale_to_new_pass.y();
transform_new_pass_to_parent_target = transform_to_target;
transform_new_pass_to_parent_target.Scale(inverse_scale_x, inverse_scale_y);
gfx::Transform new_pass_transform_to_root = transform_to_root;
new_pass_transform_to_root.Scale(inverse_scale_x, inverse_scale_y);
// Target is the new pass, so transform is just a scale.
transform_to_target =
gfx::Transform::MakeScale(scale_to_new_pass.x(), scale_to_new_pass.y());
// First clip in layer space, then transform to parent target space.
new_pass_clip = gfx::ToEnclosedRect(clip_in_layer);
if (mask_to_bounds) {
new_pass_clip.Intersect(gfx::Rect(layer.bounds()));
}
new_pass_clip = transform_to_target.MapRect(new_pass_clip);
new_pass = viz::CompositorRenderPass::Create();
// Note output_rect and damage_rect are further updated below.
viz::CompositorRenderPassId new_pass_id(layer.id());
new_pass->SetNew(new_pass_id, /*output_rect=*/new_pass_clip,
/*damage_rect=*/new_pass_clip, new_pass_transform_to_root);
}
// If a new pass is created, then there is no target clip when drawing into
// the new pass since the bounds of the new pass already has any necessary
// clip applied.
const gfx::RectF* clip_in_target = nullptr;
SimpleEnclosedRegion occlusion_in_new_pass;
RenderPassDamageData parent_pass_damage = std::move(data.render_pass_damage);
data.render_pass_damage.clear();
{
SimpleEnclosedRegion parent_pass_occlusion = data.occlusion_in_target;
data.occlusion_in_target.Clear();
DrawChildrenAndAppendQuads(layer, *new_pass, data, transform_to_root,
transform_to_target, clip_in_target,
clip_in_layer,
/*opacity=*/1.0f);
occlusion_in_new_pass = data.occlusion_in_target;
// Apply new pass's occlusion to parent pass.
if (transform_new_pass_to_parent_target.Preserves2dAxisAlignment()) {
DCHECK(transform_new_pass_to_parent_target.Is2dTransform());
for (size_t i = 0; i < occlusion_in_new_pass.GetRegionComplexity(); ++i) {
// Use ToEnclosedRect to avoid including extra pixels as occluded due to
// rounding error.
gfx::Rect occlusion_in_parent_target =
gfx::ToEnclosedRect(transform_new_pass_to_parent_target.MapRect(
gfx::RectF(occlusion_in_new_pass.GetRect(i))));
parent_pass_occlusion.Union(occlusion_in_parent_target);
}
}
data.occlusion_in_target = parent_pass_occlusion;
}
if (new_pass->quad_list.empty()) {
data.render_pass_damage = std::move(parent_pass_damage);
// Throw away new pass if it has no quads.
return;
}
viz::SharedQuadState* shared_quad_state =
parent_pass.CreateAndAppendSharedQuadState();
// Union through quad list in new pass to compute content rect.
gfx::Rect content_rect;
for (const auto* new_pass_quad : new_pass->quad_list) {
content_rect.Union(
new_pass_quad->shared_quad_state->quad_to_target_transform.MapRect(
new_pass_quad->rect));
}
content_rect.Intersect(new_pass_clip);
// Clip to max texture size.
int max_texture_size = frame_sink_->GetMaxTextureSize();
content_rect.set_width(std::min(content_rect.width(), max_texture_size));
content_rect.set_height(std::min(content_rect.height(), max_texture_size));
// Any clip introduced by this layer is already applied by the bounds of the
// new pass, so only need to apply any clips in parents target that came
// from parent.
std::optional<gfx::Rect> clip_opt;
if (parent_clip_in_target) {
clip_opt = gfx::ToEnclosingRect(*parent_clip_in_target);
}
const bool new_pass_contents_opaque =
occlusion_in_new_pass.Contains(content_rect);
shared_quad_state->SetAll(transform_new_pass_to_parent_target, content_rect,
content_rect, data.mask_filter_info_in_target,
clip_opt, new_pass_contents_opaque,
parent_opacity * layer.opacity(),
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/true);
auto* quad =
parent_pass.CreateAndAppendDrawQuad<viz::CompositorRenderPassDrawQuad>();
gfx::RectF tex_coord_rect(gfx::Rect(content_rect.size()));
quad->SetAll(shared_quad_state, content_rect, content_rect,
/*needs_blending=*/true, new_pass->id,
/*mask_resource_id=*/viz::kInvalidResourceId,
/*mask_uv_rect=*/gfx::RectF(),
/*mask_texture_size=*/gfx::Size(),
/*filters_scale=*/scale_to_new_pass,
/*filters_origin=*/gfx::PointF(), tex_coord_rect,
/*force_anti_aliasing_off=*/false,
/*backdrop_filter_quality=*/1.f,
/*intersects_damage_under=*/true);
new_pass->output_rect = content_rect;
new_pass->filters = layer.GetFilters();
ProcessDamageForRenderPass(*new_pass, data);
parent_pass_damage.emplace_back(
layer.id(),
DamageData(new_pass->has_damage_from_contributing_content,
transform_new_pass_to_parent_target.MapRect(content_rect)));
data.render_pass_damage = std::move(parent_pass_damage);
data.frame->render_pass_list.push_back(std::move(new_pass));
}
void LayerTreeImpl::DrawChildrenAndAppendQuads(
Layer& layer,
viz::CompositorRenderPass& render_pass,
FrameData& data,
const gfx::Transform& transform_to_root,
const gfx::Transform& transform_to_target,
const gfx::RectF* clip_in_target,
const gfx::RectF& clip_in_layer,
float opacity) {
const bool subtree_property_changed =
layer.GetAndResetSubtreePropertyChanged() ||
data.subtree_property_changed_from_parent;
std::optional<base::AutoReset<gfx::MaskFilterInfo>>
auto_reset_mask_filter_info;
if (layer.HasNonTrivialMaskFilterInfo()) {
gfx::MaskFilterInfo info(gfx::RRectF(gfx::RectF(gfx::Rect(layer.bounds())),
layer.corner_radii()),
layer.gradient_mask());
info.ApplyTransform(transform_to_target);
auto_reset_mask_filter_info.emplace(&data.mask_filter_info_in_target, info);
}
{
base::AutoReset reset(&data.subtree_property_changed_from_parent,
subtree_property_changed);
for (auto& child : base::Reversed(layer.children())) {
Draw(*child, render_pass, data, transform_to_root, transform_to_target,
clip_in_target, clip_in_layer, opacity);
}
}
gfx::Rect integer_clip_in_target;
if (clip_in_target) {
integer_clip_in_target = gfx::ToEnclosingRect(*clip_in_target);
}
// Viz expects the visible rect to be a subrect of layer_rect (ie `bounds()`).
// So intersect here unconditionally in case this layer is not
// `masks_to_bounds()`.
gfx::RectF visible_rectf(layer.bounds().width(), layer.bounds().height());
visible_rectf.Intersect(clip_in_layer);
gfx::RectF visible_rectf_in_target =
transform_to_target.MapRect(visible_rectf);
if (!visible_rectf.IsEmpty() && layer.HasDrawableContent() &&
UpdateOcclusionRect(layer, data, transform_to_target, opacity,
visible_rectf_in_target, visible_rectf)) {
gfx::Rect visible_rect = gfx::ToEnclosingRect(visible_rectf);
layer.AppendQuads(render_pass, data, transform_to_root, transform_to_target,
clip_in_target ? &integer_clip_in_target : nullptr,
visible_rect, opacity);
data.render_pass_damage.emplace_back(
layer.id(), DamageData(layer.GetAndResetPropertyChanged() ||
subtree_property_changed,
gfx::ToEnclosingRect(visible_rectf_in_target)));
}
}
bool LayerTreeImpl::UpdateOcclusionRect(
Layer& layer,
FrameData& data,
const gfx::Transform& transform_to_target,
float opacity,
const gfx::RectF& visible_rectf_in_target,
gfx::RectF& visible_rect) {
// Skip occlusion calculations on non-axis aligned layers.
// Note this is to reduce complexity of occlusion tracking (eg can use
// Transform::MapRect on RectF directly and only need to worry about
// rounding). It is possible to remove this restriction.
if (!transform_to_target.Preserves2dAxisAlignment()) {
return true;
}
DCHECK(transform_to_target.Is2dTransform());
DCHECK(transform_to_target.IsInvertible());
// Use enclosing rect here to avoid false rejections due to rounding error.
if (data.occlusion_in_target.Contains(
gfx::ToEnclosingRect(visible_rectf_in_target))) {
return false;
}
// Map occlusion to layer space and try to reduce `visible_rect`.
gfx::Transform from_target;
if (transform_to_target.GetInverse(&from_target)) {
for (size_t i = 0; i < data.occlusion_in_target.GetRegionComplexity();
++i) {
visible_rect.Subtract(
from_target.MapRect(gfx::RectF(data.occlusion_in_target.GetRect(i))));
}
}
if (opacity < 1.0f || !layer.contents_opaque() ||
layer.HasNonTrivialMaskFilterInfo()) {
return true;
}
// Add unoccluded visible rect to occlusion.
if (visible_rectf_in_target.width() >= min_occlusion_tracking_dimension_ ||
visible_rectf_in_target.height() >= min_occlusion_tracking_dimension_) {
// Use ToEnclosedRect to avoid including extra pixels as occluded due to
// rounding error.
data.occlusion_in_target.Union(
gfx::ToEnclosedRect(visible_rectf_in_target));
}
return true;
}
void LayerTreeImpl::ProcessDamageForRenderPass(
viz::CompositorRenderPass& render_pass,
FrameData& data) {
// Damage contributions to this frame:
// * Damaged rect in this frame (or if it's new)
// * Rect in previous frame if it is damaged in this frame, since the area of
// the old rect may now be exposed.
// * Rects that disappeared in this frame.
// Find previous map or use empty map if pass didn't exist.
RenderPassDamageData previous_data;
{
auto itr = damage_from_previous_frame_.find(render_pass.id.value());
if (itr != damage_from_previous_frame_.end()) {
previous_data = std::move(itr->second);
damage_from_previous_frame_.erase(itr);
}
}
gfx::Rect damage;
// Sort the new rect by id. `previous_data` is already sorted.
SortRenderPassDamageData(data.render_pass_damage);
// Iterate through the two sorted structures in parallel, being careful to add
// rects in `previous_data` but not in new data to `damage`.
auto previous_data_itr = previous_data.cbegin();
for (auto& [layer_id, layer_data] : data.render_pass_damage) {
// Precondition for entering the loop is `previous_data_itr` points to the
// next item (if any) to check. Any previous items have already been
// processed.
while (previous_data_itr != previous_data.cend() &&
previous_data_itr->first < layer_id) {
// Add damage from rects that no longer exist.
if (previous_data_itr != previous_data.cend()) {
damage.Union(previous_data_itr->second.visible_rect_in_target);
}
previous_data_itr++;
}
bool layer_is_new = previous_data_itr == previous_data.cend() ||
previous_data_itr->first > layer_id;
if (layer_is_new || layer_data.property_changed) {
// If layer is new or property changed, layer contributes to damage.
damage.Union(layer_data.visible_rect_in_target);
if (!layer_is_new) {
CHECK_EQ(previous_data_itr->first, layer_id);
// If layer moved, previous visible rect may now be exposed.
damage.Union(previous_data_itr->second.visible_rect_in_target);
}
}
if (!layer_is_new) {
previous_data_itr++;
}
}
// Add damage from rects that no longer exist.
while (previous_data_itr != previous_data.cend()) {
damage.Union(previous_data_itr->second.visible_rect_in_target);
previous_data_itr++;
}
// Move pass damage data into `data.current_frame_damage`.
auto insert_result = data.current_frame_damage.try_emplace(
render_pass.id.value(), std::move(data.render_pass_damage));
CHECK(insert_result.second);
data.render_pass_damage.clear();
// Assign damage to render pass.
damage.Intersect(render_pass.output_rect);
render_pass.damage_rect = damage;
render_pass.has_damage_from_contributing_content =
!render_pass.damage_rect.IsEmpty();
}
} // namespace cc::slim