blob: 355e6b0ad0373fbc890ba570dfea3a7196cbf695 [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 "components/viz/service/layers/layer_context_impl.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/expected_macros.h"
#include "cc/debug/rendering_stats_instrumentation.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/solid_color_layer_impl.h"
#include "cc/trees/layer_tree_host_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/layer_tree_settings.h"
#include "cc/trees/property_tree.h"
#include "cc/trees/task_runner_provider.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
namespace viz {
namespace {
int GenerateNextDisplayTreeId() {
static int next_id = 1;
return next_id++;
}
cc::LayerTreeSettings GetDisplayTreeSettings() {
cc::LayerTreeSettings settings;
settings.use_layer_lists = true;
settings.is_display_tree = true;
return settings;
}
std::unique_ptr<cc::LayerImpl> CreateLayer(cc::LayerTreeImpl& tree,
cc::mojom::LayerType type,
int id) {
switch (type) {
case cc::mojom::LayerType::kLayer:
return cc::LayerImpl::Create(&tree, id);
default:
// TODO(rockot): Support other layer types.
return cc::SolidColorLayerImpl::Create(&tree, id);
}
}
template <typename TreeType>
bool IsPropertyTreeIndexValid(const TreeType& tree, int32_t index) {
return index >= 0 && index < tree.next_available_id();
}
template <typename TreeType>
bool IsOptionalPropertyTreeIndexValid(const TreeType& tree, int32_t index) {
return index == cc::kInvalidPropertyNodeId ||
IsPropertyTreeIndexValid(tree, index);
}
base::expected<void, std::string> UpdatePropertyTreeNode(
cc::PropertyTrees& trees,
cc::TransformNode& node,
const mojom::TransformNode& wire) {
auto& tree = trees.transform_tree_mutable();
if (!IsOptionalPropertyTreeIndexValid(tree, wire.parent_frame_id)) {
return base::unexpected("Invalid parent_frame_id");
}
node.parent_frame_id = wire.parent_frame_id;
node.element_id = wire.element_id;
if (node.element_id) {
tree.SetElementIdForNodeId(node.id, node.element_id);
}
node.local = wire.local;
node.origin = wire.origin;
node.scroll_offset = wire.scroll_offset;
node.visible_frame_element_id = wire.visible_frame_element_id;
node.transform_changed = true;
return base::ok();
}
base::expected<void, std::string> UpdatePropertyTreeNode(
cc::PropertyTrees& trees,
cc::ClipNode& node,
const mojom::ClipNode& wire) {
if (!IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id)) {
return base::unexpected("Invalid transform_id for clip node");
}
node.transform_id = wire.transform_id;
node.clip = wire.clip;
return base::ok();
}
base::expected<void, std::string> UpdatePropertyTreeNode(
cc::PropertyTrees& trees,
cc::EffectNode& node,
const mojom::EffectNode& wire) {
if (!IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id)) {
return base::unexpected("Invalid transform_id for effect node");
}
if (!IsPropertyTreeIndexValid(trees.clip_tree(), wire.clip_id)) {
return base::unexpected("Invalid clip_id for effect node");
}
node.transform_id = wire.transform_id;
node.clip_id = wire.clip_id;
node.element_id = wire.element_id;
if (node.element_id) {
trees.effect_tree_mutable().SetElementIdForNodeId(node.id, node.element_id);
}
node.opacity = wire.opacity;
node.effect_changed = true;
if (wire.has_render_surface) {
// TODO(rockot): Plumb the real reason over IPC. It's only used for metrics
// so we make something up for now.
node.render_surface_reason = cc::RenderSurfaceReason::kRoot;
} else {
node.render_surface_reason = cc::RenderSurfaceReason::kNone;
}
return base::ok();
}
base::expected<void, std::string> UpdatePropertyTreeNode(
cc::PropertyTrees& trees,
cc::ScrollNode& node,
const mojom::ScrollNode& wire) {
if (!IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id)) {
return base::unexpected("Invalid transform_id for scroll node");
}
node.transform_id = wire.transform_id;
node.container_bounds = wire.container_bounds;
node.bounds = wire.bounds;
node.element_id = wire.element_id;
if (node.element_id) {
trees.scroll_tree_mutable().SetElementIdForNodeId(node.id, node.element_id);
}
node.scrolls_inner_viewport = wire.scrolls_inner_viewport;
node.scrolls_outer_viewport = wire.scrolls_outer_viewport;
node.user_scrollable_horizontal = wire.user_scrollable_horizontal;
node.user_scrollable_vertical = wire.user_scrollable_vertical;
return base::ok();
}
template <typename TreeType, typename WireContainerType>
base::expected<bool, std::string> UpdatePropertyTree(
cc::PropertyTrees& trees,
TreeType& tree,
const WireContainerType& wire_updates,
uint32_t num_nodes) {
const bool changed_anything =
!wire_updates.empty() || num_nodes < tree.nodes().size();
if (num_nodes < tree.nodes().size()) {
tree.RemoveNodes(tree.nodes().size() - num_nodes);
} else {
using NodeType = typename TreeType::NodeType;
for (size_t i = tree.nodes().size(); i < num_nodes; ++i) {
tree.Insert(NodeType(), cc::kRootPropertyNodeId);
}
}
for (const auto& wire : wire_updates) {
if (!IsPropertyTreeIndexValid(tree, wire->id)) {
return base::unexpected("Invalid property tree node ID");
}
if (!IsOptionalPropertyTreeIndexValid(tree, wire->parent_id)) {
return base::unexpected("Invalid property tree node parent_id");
}
if (wire->parent_id == cc::kInvalidPropertyNodeId &&
wire->id != cc::kRootPropertyNodeId &&
wire->id != cc::kSecondaryRootPropertyNodeId) {
return base::unexpected(
"Invalid parent_id for non-root property tree node");
}
auto& node = *tree.Node(wire->id);
node.id = wire->id;
node.parent_id = wire->parent_id;
RETURN_IF_ERROR(UpdatePropertyTreeNode(trees, node, *wire));
}
return changed_anything;
}
base::expected<void, std::string> AddOrUpdateLayer(
cc::LayerTreeImpl& tree,
mojom::Layer& wire,
cc::LayerImpl* existing_layer) {
cc::LayerImpl* layer;
if (existing_layer) {
// TODO(rockot): Also validate existing layer type here. We don't yet fully
// honor the type given by the client, so validation doesn't make sense yet.
if (existing_layer->id() != wire.id) {
return base::unexpected("Layer ID mismatch");
}
layer = existing_layer;
} else {
auto new_layer = CreateLayer(tree, wire.type, wire.id);
layer = new_layer.get();
tree.AddLayer(std::move(new_layer));
}
DCHECK(layer);
layer->SetBounds(wire.bounds);
layer->SetContentsOpaque(wire.contents_opaque);
layer->SetContentsOpaqueForText(wire.contents_opaque_for_text);
layer->SetDrawsContent(wire.is_drawable);
layer->SetBackgroundColor(wire.background_color);
layer->SetSafeOpaqueBackgroundColor(wire.safe_opaque_background_color);
layer->SetElementId(wire.element_id);
layer->UnionUpdateRect(wire.update_rect);
layer->SetOffsetToTransformParent(wire.offset_to_transform_parent);
const cc::PropertyTrees& property_trees = *tree.property_trees();
if (!IsPropertyTreeIndexValid(property_trees.transform_tree(),
wire.transform_tree_index)) {
return base::unexpected(
base::StrCat({"Invalid transform tree ID: ",
base::NumberToString(wire.transform_tree_index)}));
}
if (!IsPropertyTreeIndexValid(property_trees.clip_tree(),
wire.clip_tree_index)) {
return base::unexpected(
base::StrCat({"Invalid clip tree ID: ",
base::NumberToString(wire.clip_tree_index)}));
}
if (!IsPropertyTreeIndexValid(property_trees.effect_tree(),
wire.effect_tree_index)) {
return base::unexpected(
base::StrCat({"Invalid effect tree ID: ",
base::NumberToString(wire.effect_tree_index)}));
}
if (!IsPropertyTreeIndexValid(property_trees.scroll_tree(),
wire.scroll_tree_index)) {
return base::unexpected(
base::StrCat({"Invalid scroll tree ID: ",
base::NumberToString(wire.scroll_tree_index)}));
}
layer->SetTransformTreeIndex(wire.transform_tree_index);
layer->SetClipTreeIndex(wire.clip_tree_index);
layer->SetEffectTreeIndex(wire.effect_tree_index);
layer->SetScrollTreeIndex(wire.scroll_tree_index);
layer->UpdateScrollable();
return base::ok();
}
base::expected<void, std::string> UpdateViewportPropertyIds(
cc::LayerTreeImpl& layers,
cc::PropertyTrees& trees,
mojom::LayerTreeUpdate& update) {
const auto& transform_tree = trees.transform_tree();
const auto& scroll_tree = trees.scroll_tree();
const auto& clip_tree = trees.clip_tree();
if (!IsOptionalPropertyTreeIndexValid(
transform_tree, update.overscroll_elasticity_transform)) {
return base::unexpected("Invalid overscroll_elasticity_transform");
}
if (!IsOptionalPropertyTreeIndexValid(transform_tree,
update.page_scale_transform)) {
return base::unexpected("Invalid page_scale_transform");
}
if (!IsOptionalPropertyTreeIndexValid(scroll_tree, update.inner_scroll)) {
return base::unexpected("Invalid inner_scroll");
}
if (update.inner_scroll == cc::kInvalidPropertyNodeId &&
(update.outer_clip != cc::kInvalidPropertyNodeId ||
update.outer_scroll != cc::kInvalidPropertyNodeId)) {
return base::unexpected(
"Cannot set outer_clip or outer_scroll without valid inner_scroll");
}
if (!IsOptionalPropertyTreeIndexValid(clip_tree, update.outer_clip)) {
return base::unexpected("Invalid outer_clip");
}
if (!IsOptionalPropertyTreeIndexValid(scroll_tree, update.outer_scroll)) {
return base::unexpected("Invalid outer_scroll");
}
layers.SetViewportPropertyIds(cc::ViewportPropertyIds{
.overscroll_elasticity_transform = update.overscroll_elasticity_transform,
.page_scale_transform = update.page_scale_transform,
.inner_scroll = update.inner_scroll,
.outer_clip = update.outer_clip,
.outer_scroll = update.outer_scroll,
});
return base::ok();
}
} // namespace
LayerContextImpl::LayerContextImpl(CompositorFrameSinkSupport* compositor_sink,
mojom::PendingLayerContext& context)
: compositor_sink_(compositor_sink),
receiver_(this, std::move(context.receiver)),
client_(std::move(context.client)),
task_runner_provider_(cc::TaskRunnerProvider::CreateForDisplayTree(
base::SingleThreadTaskRunner::GetCurrentDefault())),
rendering_stats_(cc::RenderingStatsInstrumentation::Create()),
host_impl_(
cc::LayerTreeHostImpl::Create(GetDisplayTreeSettings(),
this,
task_runner_provider_.get(),
rendering_stats_.get(),
/*task_graph_runner=*/nullptr,
animation_host_->CreateImplInstance(),
/*dark_mode_filter=*/nullptr,
GenerateNextDisplayTreeId(),
/*image_worker_task_runner=*/nullptr,
/*scheduling_client=*/nullptr)) {
CHECK(host_impl_->InitializeFrameSink(this));
}
LayerContextImpl::~LayerContextImpl() {
host_impl_->ReleaseLayerTreeFrameSink();
}
void LayerContextImpl::BeginFrame(const BeginFrameArgs& args) {
// TODO(rockot): Manage these flags properly.
const bool has_damage = true;
compositor_sink_->SetLayerContextWantsBeginFrames(false);
if (!host_impl_->CanDraw()) {
return;
}
host_impl_->WillBeginImplFrame(args);
cc::LayerTreeHostImpl::FrameData frame;
frame.begin_frame_ack = BeginFrameAck(args, has_damage);
frame.origin_begin_main_frame_args = args;
host_impl_->PrepareToDraw(&frame);
host_impl_->DrawLayers(&frame);
host_impl_->DidDrawAllLayers(frame);
host_impl_->DidFinishImplFrame(args);
}
void LayerContextImpl::DidLoseLayerTreeFrameSinkOnImplThread() {
NOTREACHED_NORETURN();
}
void LayerContextImpl::SetBeginFrameSource(BeginFrameSource* source) {}
void LayerContextImpl::DidReceiveCompositorFrameAckOnImplThread() {
NOTIMPLEMENTED();
}
void LayerContextImpl::OnCanDrawStateChanged(bool can_draw) {}
void LayerContextImpl::NotifyReadyToActivate() {}
bool LayerContextImpl::IsReadyToActivate() {
return false;
}
void LayerContextImpl::NotifyReadyToDraw() {}
void LayerContextImpl::SetNeedsRedrawOnImplThread(cc::RedrawReason reason) {
compositor_sink_->SetLayerContextWantsBeginFrames(true);
}
void LayerContextImpl::SetNeedsOneBeginImplFrameOnImplThread() {
NOTIMPLEMENTED();
}
void LayerContextImpl::SetNeedsUpdateDisplayTreeOnImplThread() {
NOTREACHED_NORETURN();
}
void LayerContextImpl::SetNeedsPrepareTilesOnImplThread() {
NOTREACHED_NORETURN();
}
void LayerContextImpl::SetNeedsCommitOnImplThread() {
NOTIMPLEMENTED();
}
void LayerContextImpl::SetVideoNeedsBeginFrames(bool needs_begin_frames) {}
void LayerContextImpl::SetDeferBeginMainFrameFromImpl(
bool defer_begin_main_frame) {}
bool LayerContextImpl::IsInsideDraw() {
return false;
}
void LayerContextImpl::RenewTreePriority() {}
void LayerContextImpl::PostDelayedAnimationTaskOnImplThread(
base::OnceClosure task,
base::TimeDelta delay) {}
void LayerContextImpl::DidActivateSyncTree() {}
void LayerContextImpl::DidPrepareTiles() {}
void LayerContextImpl::DidCompletePageScaleAnimationOnImplThread() {}
void LayerContextImpl::OnDrawForLayerTreeFrameSink(
bool resourceless_software_draw,
bool skip_draw) {}
void LayerContextImpl::SetNeedsImplSideInvalidation(
bool needs_first_draw_on_activation,
cc::RedrawReason reason) {}
void LayerContextImpl::NotifyImageDecodeRequestFinished(int request_id,
bool decode_succeeded) {
}
void LayerContextImpl::NotifyTransitionRequestFinished(uint32_t sequence_id) {}
void LayerContextImpl::DidPresentCompositorFrameOnImplThread(
uint32_t frame_token,
cc::PresentationTimeCallbackBuffer::PendingCallbacks callbacks,
const FrameTimingDetails& details) {
NOTIMPLEMENTED();
}
void LayerContextImpl::NotifyAnimationWorkletStateChange(
cc::AnimationWorkletMutationState state,
cc::ElementListType element_list_type) {}
void LayerContextImpl::NotifyPaintWorkletStateChange(
cc::Scheduler::PaintWorkletState state) {}
void LayerContextImpl::NotifyThroughputTrackerResults(
cc::CustomTrackerResults results) {}
bool LayerContextImpl::IsInSynchronousComposite() const {
return false;
}
void LayerContextImpl::FrameSinksToThrottleUpdated(
const base::flat_set<FrameSinkId>& ids) {}
void LayerContextImpl::ClearHistory() {}
void LayerContextImpl::SetHasActiveThreadedScroll(bool is_scrolling) {}
void LayerContextImpl::SetWaitingForScrollEvent(bool waiting_for_scroll_event) {
}
size_t LayerContextImpl::CommitDurationSampleCountForTesting() const {
return 0;
}
void LayerContextImpl::DidObserveFirstScrollDelay(
int source_frame_number,
base::TimeDelta first_scroll_delay,
base::TimeTicks first_scroll_timestamp) {}
bool LayerContextImpl::BindToClient(cc::LayerTreeFrameSinkClient* client) {
frame_sink_client_ = client;
return true;
}
void LayerContextImpl::DetachFromClient() {
frame_sink_client_ = nullptr;
}
void LayerContextImpl::SetLocalSurfaceId(
const LocalSurfaceId& local_surface_id) {
host_impl_->SetTargetLocalSurfaceId(local_surface_id);
}
void LayerContextImpl::SubmitCompositorFrame(CompositorFrame frame,
bool hit_test_data_changed) {
if (!host_impl_->target_local_surface_id().is_valid()) {
return;
}
compositor_sink_->SubmitCompositorFrame(host_impl_->target_local_surface_id(),
std::move(frame));
}
void LayerContextImpl::DidNotProduceFrame(const BeginFrameAck& ack,
cc::FrameSkippedReason reason) {
compositor_sink_->DidNotProduceFrame(ack);
}
void LayerContextImpl::DidAllocateSharedBitmap(
base::ReadOnlySharedMemoryRegion region,
const SharedBitmapId& id) {}
void LayerContextImpl::DidDeleteSharedBitmap(const SharedBitmapId& id) {}
void LayerContextImpl::SetVisible(bool visible) {
host_impl_->SetVisible(visible);
}
void LayerContextImpl::UpdateDisplayTree(mojom::LayerTreeUpdatePtr update) {
auto result = DoUpdateDisplayTree(std::move(update));
if (!result.has_value()) {
receiver_.ReportBadMessage(result.error());
}
}
base::expected<void, std::string> LayerContextImpl::DoUpdateDisplayTree(
mojom::LayerTreeUpdatePtr update) {
cc::LayerTreeImpl& layers = *host_impl_->active_tree();
// We update property trees first, as they may change dimensions here and we
// need to validate tree node references when updating layers below. The order
// of tree update also matters here because clip, effect, and scroll trees all
// validate some fields against the updated transform tree, and effect trees
// also validate fields against the updated clip tree.
cc::PropertyTrees& property_trees = *layers.property_trees();
ASSIGN_OR_RETURN(const bool transform_changed,
UpdatePropertyTree(
property_trees, property_trees.transform_tree_mutable(),
update->transform_nodes, update->num_transform_nodes));
ASSIGN_OR_RETURN(
const bool clip_changed,
UpdatePropertyTree(property_trees, property_trees.clip_tree_mutable(),
update->clip_nodes, update->num_clip_nodes));
ASSIGN_OR_RETURN(
const bool effect_changed,
UpdatePropertyTree(property_trees, property_trees.effect_tree_mutable(),
update->effect_nodes, update->num_effect_nodes));
ASSIGN_OR_RETURN(
const bool scroll_changed,
UpdatePropertyTree(property_trees, property_trees.scroll_tree_mutable(),
update->scroll_nodes, update->num_scroll_nodes));
if (layers.RemoveLayers(update->removed_layers) !=
update->removed_layers.size()) {
return base::unexpected("Invalid layer removal");
}
if (update->root_layer) {
RETURN_IF_ERROR(
AddOrUpdateLayer(layers, *update->root_layer, layers.root_layer()));
} else if (!layers.root_layer() && !update->layers.empty()) {
return base::unexpected(
"Initial non-empty tree update missing root layer.");
}
for (auto& wire : update->layers) {
RETURN_IF_ERROR(
AddOrUpdateLayer(layers, *wire, layers.LayerById(wire->id)));
}
if (update->local_surface_id_from_parent) {
host_impl_->SetTargetLocalSurfaceId(*update->local_surface_id_from_parent);
}
layers.set_background_color(update->background_color);
layers.set_source_frame_number(update->source_frame_number);
layers.set_trace_id(update->trace_id);
layers.SetDeviceViewportRect(update->device_viewport);
if (update->device_scale_factor <= 0) {
return base::unexpected("Invalid device scale factor");
}
layers.SetDeviceScaleFactor(update->device_scale_factor);
if (update->local_surface_id_from_parent) {
layers.SetLocalSurfaceIdFromParent(*update->local_surface_id_from_parent);
}
RETURN_IF_ERROR(UpdateViewportPropertyIds(layers, property_trees, *update));
property_trees.UpdateChangeTracking();
property_trees.transform_tree_mutable().set_needs_update(
transform_changed || property_trees.transform_tree().needs_update());
property_trees.clip_tree_mutable().set_needs_update(
clip_changed || property_trees.clip_tree().needs_update());
property_trees.effect_tree_mutable().set_needs_update(
effect_changed || property_trees.effect_tree().needs_update());
property_trees.set_changed(transform_changed || clip_changed ||
effect_changed || scroll_changed);
std::vector<std::unique_ptr<cc::RenderSurfaceImpl>> old_render_surfaces;
property_trees.effect_tree_mutable().TakeRenderSurfaces(&old_render_surfaces);
const bool render_surfaces_changed =
property_trees.effect_tree_mutable().CreateOrReuseRenderSurfaces(
&old_render_surfaces, &layers);
if (render_surfaces_changed) {
layers.set_needs_update_draw_properties();
}
compositor_sink_->SetLayerContextWantsBeginFrames(true);
return base::ok();
}
} // namespace viz