blob: 696efb5dfe4ad6f930fdb59213be16c9036583e7 [file] [log] [blame]
// Copyright 2014 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/frame_sinks/frame_sink_manager_impl.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/map_util.h"
#include "base/containers/queue.h"
#include "base/debug/alias.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/input/utils.h"
#include "components/viz/common/features.h"
#include "components/viz/common/performance_hint_utils.h"
#include "components/viz/common/surfaces/subtree_capture_id.h"
#include "components/viz/common/surfaces/video_capture_target.h"
#include "components/viz/service/display/overdraw_tracker.h"
#include "components/viz/service/display_embedder/output_surface_provider.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
#include "components/viz/service/frame_sinks/shared_image_interface_provider.h"
#include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
#include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
#include "components/viz/service/input/input_manager.h"
#include "components/viz/service/surfaces/pending_copy_output_request.h"
#include "components/viz/service/surfaces/surface.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_manager.mojom.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
namespace viz {
FrameSinkManagerImpl::InitParams::InitParams(
OutputSurfaceProvider* output_surface_provider,
GmbVideoFramePoolContextProvider* gmb_context_provider)
: output_surface_provider(output_surface_provider),
gmb_context_provider(gmb_context_provider) {}
FrameSinkManagerImpl::InitParams::InitParams(InitParams&& other) = default;
FrameSinkManagerImpl::InitParams::~InitParams() = default;
FrameSinkManagerImpl::InitParams& FrameSinkManagerImpl::InitParams::operator=(
InitParams&& other) = default;
FrameSinkManagerImpl::FrameSinkSourceMapping::FrameSinkSourceMapping() =
default;
FrameSinkManagerImpl::FrameSinkSourceMapping::FrameSinkSourceMapping(
FrameSinkSourceMapping&& other) = default;
FrameSinkManagerImpl::FrameSinkSourceMapping::~FrameSinkSourceMapping() =
default;
FrameSinkManagerImpl::FrameSinkSourceMapping&
FrameSinkManagerImpl::FrameSinkSourceMapping::operator=(
FrameSinkSourceMapping&& other) = default;
FrameSinkManagerImpl::FrameSinkData::FrameSinkData(bool report_activation)
: report_activation(report_activation) {}
FrameSinkManagerImpl::FrameSinkData::FrameSinkData(FrameSinkData&& other) =
default;
FrameSinkManagerImpl::FrameSinkData::~FrameSinkData() = default;
FrameSinkManagerImpl::FrameSinkData& FrameSinkManagerImpl::FrameSinkData::
operator=(FrameSinkData&& other) = default;
FrameSinkManagerImpl::FrameSinkManagerImpl(const InitParams& params)
: output_surface_provider_(params.output_surface_provider),
gpu_service_(params.gpu_service),
gmb_context_provider_(params.gmb_context_provider),
surface_manager_(this,
params.activation_deadline_in_frames,
params.max_uncommitted_frames),
hit_test_manager_(surface_manager()),
restart_id_(params.restart_id),
run_all_compositor_stages_before_draw_(
params.run_all_compositor_stages_before_draw),
log_capture_pipeline_in_webrtc_(params.log_capture_pipeline_in_webrtc),
debug_settings_(params.debug_renderer_settings),
host_process_id_(params.host_process_id),
hint_session_factory_(params.hint_session_factory),
frame_sink_manager_receiver_(std::in_place_type<Receiver>, this) {
if (mojo::IsDirectReceiverSupported() &&
features::IsVizDirectCompositorThreadIpcFrameSinkManagerEnabled()) {
frame_sink_manager_receiver_.emplace<DirectReceiver>(
mojo::DirectReceiverKey{}, this);
}
surface_manager_.AddObserver(&hit_test_manager_);
surface_manager_.AddObserver(this);
if (input::InputUtils::IsTransferInputToVizSupported()) {
input_manager_ = std::make_unique<InputManager>(this);
}
}
FrameSinkManagerImpl::~FrameSinkManagerImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
video_capturers_.clear();
// All mojom::CompositorFrameSinks and BeginFrameSources should be deleted by
// this point.
DCHECK(sink_map_.empty());
DCHECK(root_sink_map_.empty());
#if BUILDFLAG(IS_ANDROID)
DCHECK(cached_back_buffers_.empty());
#endif
DCHECK(registered_sources_.empty());
surface_manager_.RemoveObserver(this);
surface_manager_.RemoveObserver(&hit_test_manager_);
}
CompositorFrameSinkImpl* FrameSinkManagerImpl::GetFrameSinkImpl(
const FrameSinkId& id) {
return base::FindPtrOrNull(sink_map_, id);
}
FrameSinkBundleImpl* FrameSinkManagerImpl::GetFrameSinkBundle(
const FrameSinkBundleId& id) {
return base::FindPtrOrNull(bundle_map_, id);
}
void FrameSinkManagerImpl::BindAndSetClient(
mojo::PendingReceiver<mojom::FrameSinkManager> interface_receiver,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
mojo::PendingRemote<mojom::FrameSinkManagerClient> client,
SharedImageInterfaceProvider* shared_image_interface_provider) {
DCHECK(!client_);
DCHECK(shared_image_interface_provider);
shared_image_interface_provider_ = shared_image_interface_provider;
std::visit(absl::Overload{[&](Receiver& receiver) {
receiver.Bind(std::move(interface_receiver),
task_runner);
},
[&](DirectReceiver& receiver) {
receiver.Bind(std::move(interface_receiver));
}},
frame_sink_manager_receiver_);
client_remote_.Bind(std::move(client));
client_ = client_remote_.get();
}
void FrameSinkManagerImpl::SetLocalClient(
mojom::FrameSinkManagerClient* client,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
DCHECK(!client_remote_);
DCHECK(!ui_task_runner_);
client_ = client;
ui_task_runner_ = ui_task_runner;
}
void FrameSinkManagerImpl::SetInputManagerForTesting(
std::unique_ptr<InputManager> input_manager) {
if (!input::InputUtils::IsTransferInputToVizSupported()) {
return;
}
input_manager_ = std::move(input_manager);
}
void FrameSinkManagerImpl::RegisterFrameSinkId(const FrameSinkId& frame_sink_id,
bool report_activation) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(frame_sink_data_, frame_sink_id));
frame_sink_data_.emplace(std::make_pair(frame_sink_id, report_activation));
if (video_detector_)
video_detector_->OnFrameSinkIdRegistered(frame_sink_id);
}
void FrameSinkManagerImpl::InvalidateFrameSinkId(
const FrameSinkId& frame_sink_id,
InvalidateFrameSinkIdCallback callback) {
TRACE_EVENT("viz", "FrameSinkManagerImpl::InvalidateFrameSinkId",
"frame_sink_id", frame_sink_id);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ScopedClosureRunner callback_runner(std::move(callback));
surface_manager_.InvalidateFrameSinkId(frame_sink_id);
if (video_detector_)
video_detector_->OnFrameSinkIdInvalidated(frame_sink_id);
MaybeEraseHitTestQuery(frame_sink_id);
// Destroy the [Root]CompositorFrameSinkImpl if there is one.
sink_map_.erase(frame_sink_id);
root_sink_map_.erase(frame_sink_id);
frame_sink_data_.erase(frame_sink_id);
}
void FrameSinkManagerImpl::SetFrameSinkDebugLabel(
const FrameSinkId& frame_sink_id,
const std::string& debug_label) {
FrameSinkData* frame_sink_data =
base::FindOrNull(frame_sink_data_, frame_sink_id);
if (frame_sink_data) {
frame_sink_data->debug_label = debug_label;
if (frame_counter_) {
frame_counter_->SetFrameSinkDebugLabel(frame_sink_id, debug_label);
}
}
}
void FrameSinkManagerImpl::CreateRootCompositorFrameSink(
mojom::RootCompositorFrameSinkParamsPtr params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(root_sink_map_, params->frame_sink_id));
DCHECK(output_surface_provider_);
// We are transferring ownership of |params| so remember FrameSinkId here.
FrameSinkId frame_sink_id = params->frame_sink_id;
if (root_sink_map_.empty()) {
root_frame_sink_id_ = frame_sink_id;
}
bool create_input_receiver = false;
#if BUILDFLAG(IS_ANDROID)
create_input_receiver = params->create_input_receiver;
#endif
gpu::SurfaceHandle widget = params->widget;
// Creating RootCompositorFrameSinkImpl can fail and return null.
auto root_compositor_frame_sink = RootCompositorFrameSinkImpl::Create(
std::move(params), this, output_surface_provider_, restart_id_,
run_all_compositor_stages_before_draw_, &debug_settings_,
hint_session_factory_);
if (root_compositor_frame_sink) {
root_sink_map_[frame_sink_id] = std::move(root_compositor_frame_sink);
if (GetInputManager()) {
GetInputManager()->OnCreateCompositorFrameSink(
frame_sink_id,
/*is_root=*/true,
/*render_input_router_config=*/nullptr, create_input_receiver,
widget);
}
}
MaybeAddHitTestQuery(frame_sink_id);
}
void FrameSinkManagerImpl::CreateFrameSinkBundle(
const FrameSinkBundleId& bundle_id,
mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
mojo::PendingRemote<mojom::FrameSinkBundleClient> client) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::Contains(bundle_map_, bundle_id)) {
uint32_t client_id = bundle_id.client_id();
uint32_t bundle_id_value = bundle_id.bundle_id();
std::visit(
[](auto& receiver) {
receiver.ReportBadMessage("Duplicate FrameSinkBundle ID");
},
frame_sink_manager_receiver_);
base::debug::Alias(&client_id);
base::debug::Alias(&bundle_id_value);
return;
}
bundle_map_[bundle_id] = std::make_unique<FrameSinkBundleImpl>(
*this, bundle_id, std::move(receiver), std::move(client));
}
void FrameSinkManagerImpl::CreateCompositorFrameSink(
const FrameSinkId& frame_sink_id,
const std::optional<FrameSinkBundleId>& bundle_id,
mojo::PendingReceiver<mojom::CompositorFrameSink> receiver,
mojo::PendingRemote<mojom::CompositorFrameSinkClient> client,
input::mojom::RenderInputRouterConfigPtr render_input_router_config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT("viz", "FrameSinkManagerImpl::CreateCompositorFrameSink",
"frame_sink_id", frame_sink_id);
if (base::Contains(sink_map_, frame_sink_id)) {
std::visit(
[](auto& receiver) {
receiver.ReportBadMessage("Duplicate FrameSinkId");
},
frame_sink_manager_receiver_);
return;
}
if (bundle_id && !GetFrameSinkBundle(*bundle_id)) {
VLOG(1) << "Terminating sink established with non-existent bundle";
return;
}
sink_map_[frame_sink_id] = std::make_unique<CompositorFrameSinkImpl>(
this, frame_sink_id, bundle_id, std::move(receiver), std::move(client));
if (GetInputManager()) {
GetInputManager()->OnCreateCompositorFrameSink(
frame_sink_id,
/*is_root=*/false, std::move(render_input_router_config),
/*create_input_receiver=*/false, gpu::SurfaceHandle());
// Set BeginFrameSource here since RenderInputRouter associated with
// |frame_sink_id| would've been created by now.
FrameSinkSourceMapping* mapping =
base::FindOrNull(frame_sink_source_map_, frame_sink_id);
if (mapping && mapping->source) {
GetInputManager()->SetBeginFrameSource(frame_sink_id, mapping->source);
}
}
}
void FrameSinkManagerImpl::DestroyCompositorFrameSink(
const FrameSinkId& frame_sink_id,
DestroyCompositorFrameSinkCallback callback) {
sink_map_.erase(frame_sink_id);
root_sink_map_.erase(frame_sink_id);
std::move(callback).Run();
}
void FrameSinkManagerImpl::RegisterFrameSinkHierarchy(
const FrameSinkId& parent_frame_sink_id,
const FrameSinkId& child_frame_sink_id) {
// If it's possible to reach the parent through the child's descendant chain,
// then this will create an infinite loop. Might as well just crash here.
CHECK(!ChildContains(child_frame_sink_id, parent_frame_sink_id));
auto& children = frame_sink_source_map_[parent_frame_sink_id].children;
DCHECK(!base::Contains(children, child_frame_sink_id));
children.insert(child_frame_sink_id);
// Add `parent_frame_sink_id` as parent to the list tracking parents of
// `child_frame_sink_id`.
FrameSinkSourceMapping& mapping = frame_sink_source_map_[child_frame_sink_id];
mapping.parent.emplace_back(parent_frame_sink_id);
// Now the hierarchy has been updated, update throttling.
UpdateThrottling();
for (auto& observer : observer_list_) {
observer.OnRegisteredFrameSinkHierarchy(parent_frame_sink_id,
child_frame_sink_id);
}
// If the parent has no source, then attaching it to this child will
// not change any downstream sources.
BeginFrameSource* parent_source =
frame_sink_source_map_[parent_frame_sink_id].source;
if (!parent_source)
return;
DCHECK_EQ(registered_sources_.count(parent_source), 1u);
RecursivelyAttachBeginFrameSource(child_frame_sink_id, parent_source);
}
void FrameSinkManagerImpl::UnregisterFrameSinkHierarchy(
const FrameSinkId& parent_frame_sink_id,
const FrameSinkId& child_frame_sink_id) {
TRACE_EVENT("viz", "FrameSinkManagerImpl::UnregisterFrameSinkHierarchy",
"parent_frame_sink_id", parent_frame_sink_id,
"child_frame_sink_id", child_frame_sink_id);
// Deliberately do not check validity of either parent or child FrameSinkId
// here. They were valid during the registration, so were valid at some point
// in time. This makes it possible to invalidate parent and child FrameSinkIds
// independently of each other and not have an ordering dependency of
// unregistering the hierarchy first before either of them.
auto iter_child = frame_sink_source_map_.find(child_frame_sink_id);
CHECK(iter_child != frame_sink_source_map_.end());
auto& child_mapping = iter_child->second;
DCHECK(base::Contains(child_mapping.parent, parent_frame_sink_id));
// Delete `parent_frame_sink_id` from parent list of `child_frame_sink_id` in
// `frame_sink_source_map_`.
auto iter_find_parent =
std::find(child_mapping.parent.begin(), child_mapping.parent.end(),
parent_frame_sink_id);
child_mapping.parent.erase(iter_find_parent);
// Delete `child_frame_sink_id` entry from `frame_sink_source_map_` if empty.
if (child_mapping.children.empty() && child_mapping.parent.empty() &&
!child_mapping.source) {
frame_sink_source_map_.erase(iter_child);
}
auto iter_parent = frame_sink_source_map_.find(parent_frame_sink_id);
CHECK(iter_parent != frame_sink_source_map_.end());
// Remove |child_frame_sink_id| from parents list of children.
auto& mapping = iter_parent->second;
DCHECK(base::Contains(mapping.children, child_frame_sink_id));
mapping.children.erase(child_frame_sink_id);
for (auto& observer : observer_list_) {
observer.OnUnregisteredFrameSinkHierarchy(parent_frame_sink_id,
child_frame_sink_id);
}
// Now the hierarchy has been updated, update throttling.
UpdateThrottling();
// Delete the FrameSinkSourceMapping for |parent_frame_sink_id| if empty.
if (mapping.children.empty() && mapping.parent.empty() && !mapping.source) {
frame_sink_source_map_.erase(iter_parent);
return;
}
// If the parent does not have a begin frame source, then disconnecting it
// will not change any of its children.
BeginFrameSource* parent_source = iter_parent->second.source;
if (!parent_source)
return;
// TODO(enne): these walks could be done in one step.
RecursivelyDetachBeginFrameSource(child_frame_sink_id, parent_source);
for (auto& source_iter : registered_sources_)
RecursivelyAttachBeginFrameSource(source_iter.second, source_iter.first);
}
void FrameSinkManagerImpl::AddVideoDetectorObserver(
mojo::PendingRemote<mojom::VideoDetectorObserver> observer) {
if (!video_detector_) {
video_detector_ = std::make_unique<VideoDetector>(
GetRegisteredFrameSinkIds(), &surface_manager_);
}
video_detector_->AddObserver(std::move(observer));
}
void FrameSinkManagerImpl::CreateVideoCapturer(
mojo::PendingReceiver<mojom::FrameSinkVideoCapturer> receiver,
uint32_t capture_version_source) {
video_capturers_.emplace(std::make_unique<FrameSinkVideoCapturerImpl>(
*this, gmb_context_provider_, std::move(receiver),
std::make_unique<media::VideoCaptureOracle>(
true /* enable_auto_throttling */),
log_capture_pipeline_in_webrtc_, capture_version_source));
}
void FrameSinkManagerImpl::EvictSurfaces(
const std::vector<SurfaceId>& surface_ids) {
for (const SurfaceId& surface_id : surface_ids) {
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, surface_id.frame_sink_id());
if (!support) {
continue;
}
// Even if we try to evict the root surface, it won't actually be freed up
// since various parts of the graphics stack will keep references to its
// resources. If we need to support evicting the root surface, we can revert
// crrev.com/c/6312283.
support->EvictSurface(surface_id.local_surface_id());
if (!support->is_root()) {
continue;
}
RootCompositorFrameSinkImpl* root =
base::FindPtrOrNull(root_sink_map_, surface_id.frame_sink_id());
if (root) {
root->DidEvictSurface(surface_id);
}
}
// Trigger garbage collection immediately, otherwise the surface may not be
// evicted for a long time (e.g. not before a frame is produced).
surface_manager_.GarbageCollectSurfaces();
}
void FrameSinkManagerImpl::RequestCopyOfOutput(
const SurfaceId& surface_id,
std::unique_ptr<CopyOutputRequest> request,
bool capture_exact_surface_id) {
TRACE_EVENT0("viz", "FrameSinkManagerImpl::RequestCopyOfOutput");
PendingCopyOutputRequest pending_request(
surface_id.local_surface_id(), SubtreeCaptureId(), std::move(request),
capture_exact_surface_id);
// The exact request can be picked up by the targeted surface right away,
// instead of being queued up in the `CompositorFrameSinkSupport`. In some
// cases (e.g., a request issued against the old surface after the old
// renderer tearing down the frame sink) when the request arrives the frame
// sink is already unregistered, but the targeted surface is still kept alive.
if (capture_exact_surface_id) {
auto* exact_surface = surface_manager_.GetSurfaceForId(surface_id);
if (exact_surface) {
exact_surface->RequestCopyOfOutput(std::move(pending_request));
BeginFrameAck ack;
ack.has_damage = true;
surface_manager_.SurfaceModified(
surface_id, ack, SurfaceObserver::HandleInteraction::kNoChange);
return;
}
}
// For the exact request yet to have a surface, or the non-exact request,
// queue them up in the matching `CompositorFrameSinkSupport`.
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, surface_id.frame_sink_id());
if (!support) {
if (capture_exact_surface_id) {
// It is extremely rare for the browser to issue a copy request against
// its embedded `SurfaceId` before the surface exists (submitting a
// request before the GPU draws anything) or before the frame sink exists
// (submitting a request before the renderer loads the document). We don't
// want to crash the GPU in either cases. The ERROR log shows up in
// "chrome://gpu".
LOG(ERROR) << "The browser issued an exact CopyOutputRequest for "
<< surface_id
<< " but there is no such surface or a frame sink.";
}
// `pending_request` will send an empty result when it goes out of scope.
return;
}
support->RequestCopyOfOutput(std::move(pending_request));
}
void FrameSinkManagerImpl::DestroyFrameSinkBundle(const FrameSinkBundleId& id) {
bundle_map_.erase(id);
}
void FrameSinkManagerImpl::OnFirstSurfaceActivation(
const SurfaceInfo& surface_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(surface_info.device_scale_factor(), 0.0f);
const auto* frame_sink_data =
base::FindOrNull(frame_sink_data_, surface_info.id().frame_sink_id());
if (frame_sink_data && client_ && frame_sink_data->report_activation) {
client_->OnFirstSurfaceActivation(surface_info);
}
}
void FrameSinkManagerImpl::UpdateHitTestRegionData(
const FrameSinkId& frame_sink_id,
const std::vector<AggregatedHitTestRegion>& hit_test_data) {
if (!base::Contains(display_hit_test_query_, frame_sink_id)) {
// The corresponding HitTestQuery has already been deleted, so drop the
// in-flight hit-test data.
return;
}
// Notify observers of the updated hit test data.
for (HitTestRegionObserver& observer : hit_test_region_observers_) {
observer.OnAggregatedHitTestRegionListUpdated(frame_sink_id, hit_test_data);
}
}
void FrameSinkManagerImpl::OnAggregatedHitTestRegionListUpdated(
const FrameSinkId& frame_sink_id,
const std::vector<AggregatedHitTestRegion>& hit_test_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdateHitTestRegionData(frame_sink_id, hit_test_data);
if (client_) {
client_->OnAggregatedHitTestRegionListUpdated(frame_sink_id, hit_test_data);
}
}
std::string_view FrameSinkManagerImpl::GetFrameSinkDebugLabel(
const FrameSinkId& frame_sink_id) const {
auto* data = base::FindOrNull(frame_sink_data_, frame_sink_id);
return data ? data->debug_label : std::string_view();
}
void FrameSinkManagerImpl::AggregatedFrameSinksChanged() {
hit_test_manager_.SetNeedsSubmit();
}
bool FrameSinkManagerImpl::HasViewTransitionToken(
const blink::ViewTransitionToken& transition_token) {
return transition_token_to_animation_manager_.contains(transition_token);
}
void FrameSinkManagerImpl::AddHitTestRegionObserver(
HitTestRegionObserver* observer) {
hit_test_region_observers_.AddObserver(observer);
}
void FrameSinkManagerImpl::RemoveHitTestRegionObserver(
HitTestRegionObserver* observer) {
hit_test_region_observers_.RemoveObserver(observer);
}
const DisplayHitTestQueryMap& FrameSinkManagerImpl::GetDisplayHitTestQuery()
const {
return display_hit_test_query_;
}
void FrameSinkManagerImpl::RegisterCompositorFrameSinkSupport(
const FrameSinkId& frame_sink_id,
CompositorFrameSinkSupport* support) {
DCHECK(support);
DCHECK(!base::Contains(support_map_, frame_sink_id));
support_map_[frame_sink_id] = support;
for (auto& capturer : video_capturers_) {
if (capturer->target() &&
capturer->target()->frame_sink_id == frame_sink_id)
capturer->SetResolvedTarget(support);
}
FrameSinkSourceMapping* mapping =
base::FindOrNull(frame_sink_source_map_, frame_sink_id);
if (mapping && mapping->source) {
support->SetBeginFrameSource(mapping->source);
}
if (global_throttle_interval_) {
UpdateThrottlingRecursively(frame_sink_id,
global_throttle_interval_.value());
}
if (frame_counter_) {
frame_counter_->AddFrameSink(frame_sink_id, support->is_root(),
GetFrameSinkDebugLabel(frame_sink_id));
}
}
void FrameSinkManagerImpl::UnregisterCompositorFrameSinkSupport(
const FrameSinkId& frame_sink_id) {
TRACE_EVENT("viz",
"FrameSinkManagerImpl::UnregisterCompositorFrameSinkSupport",
"frame_sink_id", frame_sink_id);
DCHECK(base::Contains(support_map_, frame_sink_id));
for (auto& observer : observer_list_)
observer.OnDestroyedCompositorFrameSink(frame_sink_id);
for (auto& capturer : video_capturers_) {
if (capturer->target() &&
capturer->target()->frame_sink_id == frame_sink_id)
capturer->OnTargetWillGoAway();
}
support_map_.erase(frame_sink_id);
}
void FrameSinkManagerImpl::RegisterBeginFrameSource(
BeginFrameSource* source,
const FrameSinkId& frame_sink_id) {
DCHECK(source);
DCHECK_EQ(registered_sources_.count(source), 0u);
registered_sources_[source] = frame_sink_id;
RecursivelyAttachBeginFrameSource(frame_sink_id, source);
if (frame_sink_id == root_frame_sink_id_) {
root_begin_frame_source_ = source;
}
}
void FrameSinkManagerImpl::UnregisterBeginFrameSource(
BeginFrameSource* source) {
DCHECK(source);
DCHECK_EQ(registered_sources_.count(source), 1u);
FrameSinkId frame_sink_id = registered_sources_[source];
registered_sources_.erase(source);
if (frame_sink_id == root_frame_sink_id_) {
root_begin_frame_source_ = nullptr;
}
if (frame_sink_source_map_.count(frame_sink_id) == 0u)
return;
// TODO(enne): these walks could be done in one step.
// Remove this begin frame source from its subtree.
RecursivelyDetachBeginFrameSource(frame_sink_id, source);
// Then flush every remaining registered source to fix any sources that
// became null because of the previous step but that have an alternative.
for (auto source_iter : registered_sources_)
RecursivelyAttachBeginFrameSource(source_iter.second, source_iter.first);
}
void FrameSinkManagerImpl::RecursivelyAttachBeginFrameSource(
const FrameSinkId& frame_sink_id,
BeginFrameSource* source) {
FrameSinkSourceMapping& mapping = frame_sink_source_map_[frame_sink_id];
if (!mapping.source) {
mapping.source = source;
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, frame_sink_id);
if (support) {
// Updates the InputManager(or FlingScheduler) of BeginFrameSource changes
// before CompositorFrameSinkSupport since it is 1:1 with
// RenderInputRouter (for layer tree frame sinks associated CFSS) and
// updating it earlier may cause UAF bugs.
if (GetInputManager()) {
GetInputManager()->SetBeginFrameSource(frame_sink_id, source);
}
support->SetBeginFrameSource(source);
}
}
// Copy the list of children because RecursivelyAttachBeginFrameSource() can
// modify |frame_sink_source_map_| and invalidate iterators.
base::flat_set<FrameSinkId> children = mapping.children;
for (const FrameSinkId& child : children)
RecursivelyAttachBeginFrameSource(child, source);
}
void FrameSinkManagerImpl::RecursivelyDetachBeginFrameSource(
const FrameSinkId& frame_sink_id,
BeginFrameSource* source) {
auto iter = frame_sink_source_map_.find(frame_sink_id);
if (iter == frame_sink_source_map_.end())
return;
auto& mapping = iter->second;
if (mapping.source == source) {
mapping.source = nullptr;
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, frame_sink_id);
if (support) {
// Updates the InputManager(or FlingScheduler) of BeginFrameSource changes
// before CompositorFrameSinkSupport since it is 1:1 with
// RenderInputRouter (for layer tree frame sinks associated CFSS) and
// updating it earlier may cause UAF bugs.
if (GetInputManager()) {
GetInputManager()->SetBeginFrameSource(frame_sink_id, nullptr);
}
support->SetBeginFrameSource(nullptr);
}
}
// Delete the FrameSinkSourceMapping for `frame_sink_id` if both parent and
// children lists are empty.
if (mapping.children.empty() && mapping.parent.empty()) {
frame_sink_source_map_.erase(iter);
return;
}
// Copy the list of children because RecursivelyDetachBeginFrameSource() can
// modify |frame_sink_source_map_| and invalidate iterators.
base::flat_set<FrameSinkId> children = mapping.children;
for (const FrameSinkId& child : children)
RecursivelyDetachBeginFrameSource(child, source);
}
CapturableFrameSink* FrameSinkManagerImpl::FindCapturableFrameSink(
const VideoCaptureTarget& target) {
// Search the known CompositorFrameSinkSupport objects for region capture
// bounds matching the crop ID specified by |target| (if one was set), and
// return the corresponding frame sink.
if (IsRegionCapture(target.sub_target)) {
const auto crop_id = std::get<RegionCaptureCropId>(target.sub_target);
for (const auto& id_and_sink : support_map_) {
const RegionCaptureBounds& bounds =
id_and_sink.second->current_capture_bounds();
if (base::Contains(bounds.bounds(), crop_id)) {
return id_and_sink.second;
}
}
return nullptr;
}
FrameSinkId frame_sink_id = target.frame_sink_id;
if (!frame_sink_id.is_valid())
return nullptr;
return base::FindPtrOrNull(support_map_, frame_sink_id);
}
void FrameSinkManagerImpl::OnCapturerConnectionLost(
FrameSinkVideoCapturerImpl* capturer) {
video_capturers_.erase(capturer);
}
bool FrameSinkManagerImpl::ChildContains(
const FrameSinkId& child_frame_sink_id,
const FrameSinkId& search_frame_sink_id) const {
const FrameSinkSourceMapping* mapping =
base::FindOrNull(frame_sink_source_map_, child_frame_sink_id);
if (!mapping) {
return false;
}
for (const FrameSinkId& child : mapping->children) {
if (child == search_frame_sink_id)
return true;
if (ChildContains(child, search_frame_sink_id))
return true;
}
return false;
}
InputManager* FrameSinkManagerImpl::GetInputManager() {
return input_manager_.get();
}
void FrameSinkManagerImpl::SubmitHitTestRegionList(
const SurfaceId& surface_id,
uint64_t frame_index,
std::optional<HitTestRegionList> hit_test_region_list) {
hit_test_manager_.SubmitHitTestRegionList(surface_id, frame_index,
std::move(hit_test_region_list));
}
void FrameSinkManagerImpl::OnFrameTokenChangedDirect(
const FrameSinkId& frame_sink_id,
uint32_t frame_token,
base::TimeTicks activation_time) {
if (client_)
client_->OnFrameTokenChanged(frame_sink_id, frame_token, activation_time);
}
void FrameSinkManagerImpl::OnFrameTokenChanged(const FrameSinkId& frame_sink_id,
uint32_t frame_token) {
if (client_remote_ || !ui_task_runner_) {
// This is a Mojo client or a locally-connected client *without* a task
// runner. In this case, call directly.
OnFrameTokenChangedDirect(frame_sink_id, frame_token,
/* activation_time =*/base::TimeTicks::Now());
} else {
// This is a locally-connected client *with* a task runner - post task.
ui_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&FrameSinkManagerImpl::OnFrameTokenChangedDirect,
base::Unretained(this), frame_sink_id, frame_token,
/* activation_time =*/base::TimeTicks::Now()));
}
}
VideoDetector* FrameSinkManagerImpl::CreateVideoDetectorForTesting(
const base::TickClock* tick_clock,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
DCHECK(!video_detector_);
video_detector_ = std::make_unique<VideoDetector>(
GetRegisteredFrameSinkIds(), surface_manager(), tick_clock, task_runner);
return video_detector_.get();
}
void FrameSinkManagerImpl::DidBeginFrame(const FrameSinkId& frame_sink_id,
const BeginFrameArgs& args) {
for (auto& observer : observer_list_)
observer.OnFrameSinkDidBeginFrame(frame_sink_id, args);
}
void FrameSinkManagerImpl::DidFinishFrame(const FrameSinkId& frame_sink_id,
const BeginFrameArgs& args) {
for (auto& observer : observer_list_)
observer.OnFrameSinkDidFinishFrame(frame_sink_id, args);
}
void FrameSinkManagerImpl::OnFrameSinkDeviceScaleFactorChanged(
const FrameSinkId& frame_sink_id,
float device_scale_factor) {
for (auto& observer : observer_list_) {
observer.OnFrameSinkDeviceScaleFactorChanged(frame_sink_id,
device_scale_factor);
}
}
void FrameSinkManagerImpl::OnFrameSinkMobileOptimizedChanged(
const FrameSinkId& frame_sink_id,
bool is_mobile_optimized) {
for (auto& observer : observer_list_) {
observer.OnFrameSinkMobileOptimizedChanged(frame_sink_id,
is_mobile_optimized);
}
}
void FrameSinkManagerImpl::AddObserver(FrameSinkObserver* obs) {
observer_list_.AddObserver(obs);
}
void FrameSinkManagerImpl::RemoveObserver(FrameSinkObserver* obs) {
observer_list_.RemoveObserver(obs);
}
std::vector<FrameSinkId> FrameSinkManagerImpl::GetRegisteredFrameSinkIds()
const {
std::vector<FrameSinkId> frame_sink_ids;
for (auto& map_entry : frame_sink_data_)
frame_sink_ids.push_back(map_entry.first);
return frame_sink_ids;
}
FrameSinkId FrameSinkManagerImpl::GetOldestParentByChildFrameId(
const FrameSinkId& child_frame_sink_id) const {
CHECK(!base::Contains(root_sink_map_, child_frame_sink_id));
const FrameSinkSourceMapping* mapping =
base::FindOrNull(frame_sink_source_map_, child_frame_sink_id);
if (!mapping || mapping->parent.empty()) {
return FrameSinkId();
}
return mapping->parent.front();
}
int FrameSinkManagerImpl::GetNumParents(
const FrameSinkId& frame_sink_id) const {
auto* mapping = base::FindOrNull(frame_sink_source_map_, frame_sink_id);
return mapping ? mapping->parent.size() : 0;
}
FrameSinkId FrameSinkManagerImpl::GetOldestRootCompositorFrameSinkId(
const FrameSinkId& child_frame_sink_id) const {
auto parent_id = GetOldestParentByChildFrameId(child_frame_sink_id);
while (parent_id.is_valid() && !base::Contains(root_sink_map_, parent_id)) {
parent_id = GetOldestParentByChildFrameId(parent_id);
}
return parent_id;
}
base::flat_set<FrameSinkId> FrameSinkManagerImpl::GetChildrenByParent(
const FrameSinkId& parent_frame_sink_id) const {
auto* mapping =
base::FindOrNull(frame_sink_source_map_, parent_frame_sink_id);
return mapping ? mapping->children : base::flat_set<FrameSinkId>();
}
CompositorFrameSinkSupport* FrameSinkManagerImpl::GetFrameSinkForId(
const FrameSinkId& frame_sink_id) const {
return base::FindPtrOrNull(support_map_, frame_sink_id);
}
void FrameSinkManagerImpl::DiscardPendingCopyOfOutputRequests(
const BeginFrameSource* source) {
const auto& root_sink = registered_sources_.at(source);
base::queue<FrameSinkId> queue;
for (queue.push(root_sink); !queue.empty(); queue.pop()) {
auto& frame_sink_id = queue.front();
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, frame_sink_id);
if (support) {
support->ClearAllPendingCopyOutputRequests();
}
for (auto child : GetChildrenByParent(frame_sink_id))
queue.push(child);
}
}
void FrameSinkManagerImpl::OnCaptureStarted(const FrameSinkId& id) {
if (captured_frame_sink_ids_.insert(id).second) {
ClearThrottling(id);
}
for (auto& observer : observer_list_)
observer.OnCaptureStarted(id);
}
void FrameSinkManagerImpl::OnCaptureStopped(const FrameSinkId& id) {
captured_frame_sink_ids_.erase(id);
UpdateThrottling();
}
void FrameSinkManagerImpl::VerifySandboxedThreadIds(
const base::flat_set<base::PlatformThreadId>& thread_ids,
base::OnceCallback<void(bool)> verification_callback) {
#if BUILDFLAG(IS_ANDROID)
if (!CheckThreadIdsDoNotBelongToCurrentProcess(thread_ids)) {
// At least one thread belongs to the GPU process, verification failed.
std::move(verification_callback).Run(false);
return;
}
// GPU check passed, now do an async check for the Browser process.
static_assert(
std::is_same_v<int32_t, base::PlatformThreadId::UnderlyingType>);
std::vector<int32_t> tids;
tids.reserve(thread_ids.size());
std::transform(thread_ids.begin(), thread_ids.end(), std::back_inserter(tids),
[](const base::PlatformThreadId& tid) { return tid.raw(); });
client_->VerifyThreadIdsDoNotBelongToHost(tids,
std::move(verification_callback));
#else
std::move(verification_callback).Run(false);
#endif
}
#if BUILDFLAG(IS_ANDROID)
void FrameSinkManagerImpl::CacheBackBuffer(
uint32_t cache_id,
const FrameSinkId& root_frame_sink_id) {
RootCompositorFrameSinkImpl* root_frame_sink =
base::FindPtrOrNull(root_sink_map_, root_frame_sink_id);
// If creating RootCompositorFrameSinkImpl failed there might not be an entry
// in |root_sink_map_|.
if (!root_frame_sink) {
return;
}
DCHECK(!base::Contains(cached_back_buffers_, cache_id));
cached_back_buffers_[cache_id] = root_frame_sink->GetCacheBackBufferCb();
}
void FrameSinkManagerImpl::EvictBackBuffer(uint32_t cache_id,
EvictBackBufferCallback callback) {
cached_back_buffers_.erase(cache_id);
std::move(callback).Run();
}
#endif
void FrameSinkManagerImpl::UpdateDebugRendererSettings(
const DebugRendererSettings& debug_settings) {
debug_settings_ = debug_settings;
}
void FrameSinkManagerImpl::UpdateThrottlingRecursively(
const FrameSinkId& frame_sink_id,
base::TimeDelta interval) {
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, frame_sink_id);
if (support) {
support->ThrottleBeginFrame(interval);
}
auto children = GetChildrenByParent(frame_sink_id);
for (auto& id : children)
UpdateThrottlingRecursively(id, interval);
}
void FrameSinkManagerImpl::Throttle(const std::vector<FrameSinkId>& ids,
base::TimeDelta interval) {
frame_sink_ids_to_throttle_ = ids;
throttle_interval_ = interval;
UpdateThrottling();
}
void FrameSinkManagerImpl::StartThrottlingAllFrameSinks(
base::TimeDelta interval) {
global_throttle_interval_ = interval;
UpdateThrottling();
}
void FrameSinkManagerImpl::StopThrottlingAllFrameSinks() {
global_throttle_interval_ = std::nullopt;
UpdateThrottling();
}
void FrameSinkManagerImpl::UpdateThrottling() {
// Clear previous throttling effect on all frame sinks.
for (auto& support_map_item : support_map_) {
support_map_item.second->ThrottleBeginFrame(base::TimeDelta());
}
if (throttle_interval_.is_zero() &&
(!global_throttle_interval_ ||
global_throttle_interval_.value().is_zero()))
return;
if (global_throttle_interval_) {
for (const auto& support : support_map_) {
support.second->ThrottleBeginFrame(global_throttle_interval_.value());
}
}
// If the per-frame sink throttle interval is more aggressive than the global
// throttling interval, apply it to those frame sinks effectively always
// throttling a frame sink as much as possible.
if (!global_throttle_interval_ ||
throttle_interval_ > global_throttle_interval_) {
for (const auto& id : frame_sink_ids_to_throttle_) {
UpdateThrottlingRecursively(id, throttle_interval_);
}
}
// Clear throttling on frame sinks currently being captured.
for (const auto& id : captured_frame_sink_ids_) {
UpdateThrottlingRecursively(id, base::TimeDelta());
}
}
void FrameSinkManagerImpl::ClearThrottling(const FrameSinkId& id) {
UpdateThrottlingRecursively(id, base::TimeDelta());
}
void FrameSinkManagerImpl::MaybeEraseHitTestQuery(
const FrameSinkId& frame_sink_id) {
if (!input::InputUtils::IsTransferInputToVizSupported()) {
return;
}
display_hit_test_query_.erase(frame_sink_id);
}
void FrameSinkManagerImpl::MaybeAddHitTestQuery(
const FrameSinkId& frame_sink_id) {
if (!input::InputUtils::IsTransferInputToVizSupported()) {
return;
}
CompositorFrameSinkSupport* support =
base::FindPtrOrNull(support_map_, frame_sink_id);
// Only create a HitTestQuery for `frame_sink_id` if `InputOnViz` flag is
// enabled and a corresponding CompositorFrameSinkSupport* has been created
// for this RootCompositorFrameSink.
if (support) {
display_hit_test_query_[frame_sink_id] = std::make_unique<HitTestQuery>(
support->GetHitTestAggregator()->GetDataProviderSafeRef());
}
}
void FrameSinkManagerImpl::CacheSurfaceAnimationManager(
const blink::ViewTransitionToken& transition_token,
std::unique_ptr<SurfaceAnimationManager> manager) {
if (transition_token_to_animation_manager_.contains(transition_token)) {
LOG(ERROR)
<< "SurfaceAnimationManager already exists for |transition_token| : "
<< transition_token;
return;
}
transition_token_to_animation_manager_[transition_token] = std::move(manager);
for (auto& observer : observer_list_) {
observer.OnViewTransitionSaved(transition_token);
}
}
std::unique_ptr<SurfaceAnimationManager>
FrameSinkManagerImpl::TakeSurfaceAnimationManager(
const blink::ViewTransitionToken& transition_token) {
auto it = transition_token_to_animation_manager_.find(transition_token);
if (it == transition_token_to_animation_manager_.end()) {
LOG(ERROR) << "SurfaceAnimationManager missing for |transition_token| : "
<< transition_token;
return nullptr;
}
auto manager = std::move(it->second);
transition_token_to_animation_manager_.erase(it);
return manager;
}
bool FrameSinkManagerImpl::ClearSurfaceAnimationManager(
const blink::ViewTransitionToken& transition_token) {
return transition_token_to_animation_manager_.erase(transition_token);
}
void FrameSinkManagerImpl::OnScreenshotCaptured(
const blink::SameDocNavigationScreenshotDestinationToken& destination_token,
std::unique_ptr<CopyOutputResult> copy_output_result) {
client_->OnScreenshotCaptured(destination_token,
std::move(copy_output_result));
}
bool FrameSinkManagerImpl::IsFrameSinkIdInRootSinkMap(
const FrameSinkId& frame_sink_id) {
return base::Contains(root_sink_map_, frame_sink_id);
}
gpu::SharedImageInterface* FrameSinkManagerImpl::GetSharedImageInterface() {
DCHECK(shared_image_interface_provider_);
return shared_image_interface_provider_->GetSharedImageInterface();
}
void FrameSinkManagerImpl::StartFrameCounting(base::TimeTicks start_time,
base::TimeDelta bucket_size) {
DCHECK(!frame_counter_.has_value());
frame_counter_.emplace(start_time, bucket_size);
for (auto& [sink_id, support] : support_map_) {
DCHECK_EQ(sink_id, support->frame_sink_id());
frame_counter_->AddFrameSink(sink_id, support->is_root(),
GetFrameSinkDebugLabel(sink_id));
}
}
void FrameSinkManagerImpl::StopFrameCounting(
StopFrameCountingCallback callback) {
// Returns empty data if `frame_counter_` has no value. This could happen
// when gpu-process is restarted in middle of test and test scripts still
// calls this at the end.
if (!frame_counter_.has_value()) {
std::move(callback).Run(nullptr);
return;
}
std::move(callback).Run(frame_counter_->TakeData());
frame_counter_.reset();
}
void FrameSinkManagerImpl::StartOverdrawTracking(
const FrameSinkId& root_frame_sink_id,
base::TimeDelta bucket_size) {
RootCompositorFrameSinkImpl* root_frame_sink =
base::FindPtrOrNull(root_sink_map_, root_frame_sink_id);
if (!root_frame_sink) {
LOG(ERROR) << "No RootCompositorFrameSink for root_frame_sink_id:"
<< root_frame_sink_id;
return;
}
root_frame_sink->StartOverdrawTracking(bucket_size.InSeconds());
}
void FrameSinkManagerImpl::StopOverdrawTracking(
const FrameSinkId& root_frame_sink_id,
StopOverdrawTrackingCallback callback) {
RootCompositorFrameSinkImpl* root_frame_sink =
base::FindPtrOrNull(root_sink_map_, root_frame_sink_id);
if (!root_frame_sink) {
LOG(ERROR) << "No RootCompositorFrameSink for root_frame_sink_id:"
<< root_frame_sink_id;
std::move(callback).Run(std::move(nullptr));
return;
}
mojom::OverdrawDataPtr data = mojom::OverdrawData::New();
data->average_overdraws = root_frame_sink->StopOverdrawTracking();
std::move(callback).Run(std::move(data));
}
void FrameSinkManagerImpl::HasUnclaimedViewTransitionResources(
HasUnclaimedViewTransitionResourcesCallback callback) {
std::move(callback).Run(!transition_token_to_animation_manager_.empty());
}
void FrameSinkManagerImpl::SetSameDocNavigationScreenshotSize(
const gfx::Size& result_size,
SetSameDocNavigationScreenshotSizeCallback callback) {
copy_output_request_result_size_for_testing_ = result_size;
std::move(callback).Run();
}
void FrameSinkManagerImpl::GetForceEnableZoomState(
const FrameSinkId& frame_sink_id,
GetForceEnableZoomStateCallback callback) {
CHECK(GetInputManager());
CHECK(GetInputManager()->GetRenderInputRouterFromFrameSinkId(frame_sink_id));
bool enabled = GetInputManager()
->GetRenderInputRouterFromFrameSinkId(frame_sink_id)
->GetForceEnableZoom();
std::move(callback).Run(enabled);
}
void FrameSinkManagerImpl::WaitForSurfaceAnimationManager(
const FrameSinkId& frame_sink_id,
WaitForSurfaceAnimationManagerCallback callback) {
auto* support = FrameSinkManagerImpl::GetFrameSinkForId(frame_sink_id);
CHECK(support);
support->RegisterSurfaceAnimationManagerNotification(std::move(callback));
}
void FrameSinkManagerImpl::ClearUnclaimedViewTransitionResources(
const blink::ViewTransitionToken& transition_token) {
transition_token_to_animation_manager_.erase(transition_token);
}
void FrameSinkManagerImpl::CreateMetricsRecorderForTest(
mojo::PendingReceiver<mojom::FrameSinksMetricsRecorder> receiver) {
CHECK(!metrics_receiver_.is_bound());
metrics_receiver_.Bind(std::move(receiver));
}
void FrameSinkManagerImpl::EnableFrameSinkManagerTestApi(
mojo::PendingReceiver<mojom::FrameSinkManagerTestApi> receiver) {
CHECK(!test_api_receiver_.is_bound());
test_api_receiver_.Bind(std::move(receiver));
}
void FrameSinkManagerImpl::SetupRendererInputRouterDelegateRegistry(
mojo::PendingReceiver<mojom::RendererInputRouterDelegateRegistry>
receiver) {
input_manager_->SetupRendererInputRouterDelegateRegistry(std::move(receiver));
}
void FrameSinkManagerImpl::NotifyRendererBlockStateChanged(
bool blocked,
const std::vector<FrameSinkId>& render_input_routers) {
input_manager_->NotifyRendererBlockStateChanged(blocked,
render_input_routers);
}
void FrameSinkManagerImpl::RequestInputBack() {
bool success = input_manager_->ReturnInputBackToBrowser();
TRACE_EVENT_INSTANT("viz", "FrameSinkManagerImpl::RequestInputBack",
"success", success);
}
void FrameSinkManagerImpl::RequestBeginFrameForGpuService(bool toggle) {
if (root_begin_frame_source_ && gpu_service_) {
if (toggle) {
root_begin_frame_source_->AddObserver(gpu_service_);
} else {
root_begin_frame_source_->RemoveObserver(gpu_service_);
}
}
}
GpuServiceImpl* FrameSinkManagerImpl::GetGpuService() {
return gpu_service_;
}
} // namespace viz