blob: 7b04b96baf2ad45bfa9948dffee8d331b2ab4ba4 [file] [log] [blame]
// Copyright 2021 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_bundle_impl.h"
#include <map>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_impl.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
#include "services/viz/public/mojom/compositing/frame_sink_bundle.mojom.h"
namespace viz {
// A SinkGroup is responsible for batching messages out to a group of
// bundled CompositorFrameSink clients who all share a common BeginFrameSource.
// FrameSinkBundleImpls may own any number of SinkGroups, and groups are created
// or destroyed as needed when a sink is added to or removed from the bundle.
//
// Note that the BeginFrameSource is only observed by this SinkGroup while there
// are active FrameSinks present who have explicitly indicated a need for
// BeginFrame notifications. This avoids generation and processing of unused
// frame events which might otherwise incur substantial overhead.
class FrameSinkBundleImpl::SinkGroup : public BeginFrameObserver {
public:
SinkGroup(FrameSinkManagerImpl& manager,
FrameSinkBundleImpl& bundle,
BeginFrameSource& source,
mojom::FrameSinkBundleClient& client)
: manager_(manager), bundle_(bundle), source_(source), client_(client) {}
~SinkGroup() override {
if (is_observing_begin_frame_) {
source_->RemoveObserver(this);
}
}
bool IsEmpty() const { return frame_sinks_.empty(); }
base::WeakPtr<SinkGroup> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void AddFrameSink(uint32_t sink_id) {
frame_sinks_.insert(sink_id);
FrameSinkId id(bundle_->id().client_id(), sink_id);
if (auto* support = manager_->GetFrameSinkForId(id)) {
if (support->needs_begin_frame()) {
frame_sinks_needing_begin_frame_.insert(sink_id);
UpdateBeginFrameObservation();
}
}
}
void RemoveFrameSink(uint32_t sink_id) {
frame_sinks_.erase(sink_id);
unacked_submissions_.erase(sink_id);
FlushMessages();
frame_sinks_needing_begin_frame_.erase(sink_id);
UpdateBeginFrameObservation();
}
void SetNeedsBeginFrame(uint32_t sink_id, bool needs_begin_frame) {
if (needs_begin_frame) {
frame_sinks_needing_begin_frame_.insert(sink_id);
} else {
frame_sinks_needing_begin_frame_.erase(sink_id);
}
UpdateBeginFrameObservation();
}
void WillSubmitFrame(uint32_t sink_id) {
unacked_submissions_.insert(sink_id);
}
void EnqueueDidReceiveCompositorFrameAck(
uint32_t sink_id,
std::vector<ReturnedResource> resources) {
pending_received_frame_acks_.push_back(
mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
unacked_submissions_.erase(sink_id);
// We expect to be notified about the consumption of all previously
// submitted frames at approximately the same time. This condition allows us
// to batch most ack messages without any significant delays. Note that
// sink IDs are added to `unacked_submissions_` in WillSubmitFrame() for
// each sink that submits a frame.
if (unacked_submissions_.empty()) {
FlushMessages();
}
}
void EnqueueOnBeginFrame(
uint32_t sink_id,
const BeginFrameArgs& args,
const base::flat_map<uint32_t, FrameTimingDetails>& details,
std::vector<ReturnedResource> resources) {
pending_on_begin_frames_.push_back(mojom::BeginFrameInfo::New(
sink_id, args, details, std::move(resources)));
if (!defer_on_begin_frames_) {
FlushMessages();
}
}
void EnqueueReclaimResources(uint32_t sink_id,
std::vector<ReturnedResource> resources) {
// We always defer ReclaimResources until the next flush, whether it's done
// for frame acks or OnBeginFrames.
pending_reclaimed_resources_.push_back(
mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
}
// BeginFrameObserver implementation:
void OnBeginFrame(const BeginFrameArgs& args) override {
last_used_begin_frame_args_ = args;
// We expect the calls below to result in reentrant calls to our own
// EnqueueOnBeginFrame(), via the sink's BundleClientProxy. In a sense this
// means we act both as the sink's BeginFrameSource and its client. The
// indirection is useful since the sink performs some non-trivial logic in
// OnBeginFrame() and passes computed data to the client, which we want to
// be able to forward in our batched OnBeginFrame notifications back to the
// real remote client.
defer_on_begin_frames_ = true;
for (const auto sink_id : frame_sinks_) {
FrameSinkId id(bundle_->id().client_id(), sink_id);
if (BeginFrameObserver* observer = manager_->GetFrameSinkForId(id)) {
observer->OnBeginFrame(args);
}
}
defer_on_begin_frames_ = false;
FlushMessages();
}
const BeginFrameArgs& LastUsedBeginFrameArgs() const override {
return last_used_begin_frame_args_;
}
void OnBeginFrameSourcePausedChanged(bool paused) override {}
bool WantsAnimateOnlyBeginFrames() const override { return false; }
void FlushMessages() {
std::vector<mojom::BundledReturnedResourcesPtr> pending_received_frame_acks;
std::swap(pending_received_frame_acks_, pending_received_frame_acks);
std::vector<mojom::BeginFrameInfoPtr> pending_on_begin_frames;
std::swap(pending_on_begin_frames_, pending_on_begin_frames);
std::vector<mojom::BundledReturnedResourcesPtr> pending_reclaimed_resources;
std::swap(pending_reclaimed_resources_, pending_reclaimed_resources);
if (!pending_received_frame_acks.empty() ||
!pending_on_begin_frames.empty() ||
!pending_reclaimed_resources.empty()) {
client_->FlushNotifications(std::move(pending_received_frame_acks),
std::move(pending_on_begin_frames),
std::move(pending_reclaimed_resources));
}
}
void DidFinishFrame() { source_->DidFinishFrame(this); }
private:
void UpdateBeginFrameObservation() {
bool should_observe_begin_frame = !frame_sinks_needing_begin_frame_.empty();
if (should_observe_begin_frame && !is_observing_begin_frame_) {
// NOTE: It's important to set this flag before adding the observer,
// because AddObserver() can synchronously enter CFSS::OnBeginFrame(),
// which can in turn re-enter this method.
is_observing_begin_frame_ = true;
source_->AddObserver(this);
return;
}
if (is_observing_begin_frame_ && !should_observe_begin_frame) {
source_->RemoveObserver(this);
is_observing_begin_frame_ = false;
}
}
const raw_ref<FrameSinkManagerImpl> manager_;
const raw_ref<FrameSinkBundleImpl> bundle_;
const raw_ref<BeginFrameSource> source_;
const raw_ref<mojom::FrameSinkBundleClient> client_;
bool defer_on_begin_frames_ = false;
std::vector<mojom::BundledReturnedResourcesPtr> pending_received_frame_acks_;
std::vector<mojom::BundledReturnedResourcesPtr> pending_reclaimed_resources_;
std::vector<mojom::BeginFrameInfoPtr> pending_on_begin_frames_;
std::set<uint32_t> frame_sinks_;
std::set<uint32_t> frame_sinks_needing_begin_frame_;
bool is_observing_begin_frame_ = false;
// Tracks which sinks in the group are still expecting an ack for a previously
// submitted frame.
std::set<uint32_t> unacked_submissions_;
BeginFrameArgs last_used_begin_frame_args_;
base::WeakPtrFactory<SinkGroup> weak_ptr_factory_{this};
};
FrameSinkBundleImpl::FrameSinkBundleImpl(
FrameSinkManagerImpl& manager,
const FrameSinkBundleId& id,
mojo::PendingReceiver<mojom::FrameSinkBundle> receiver,
mojo::PendingRemote<mojom::FrameSinkBundleClient> client)
: manager_(manager),
id_(id),
receiver_(this, std::move(receiver)),
client_(std::move(client)) {
receiver_.set_disconnect_handler(base::BindOnce(
&FrameSinkBundleImpl::OnDisconnect, base::Unretained(this)));
}
FrameSinkBundleImpl::~FrameSinkBundleImpl() = default;
void FrameSinkBundleImpl::SetSinkNeedsBeginFrame(uint32_t sink_id,
bool needs_begin_frame) {
if (auto* group = GetSinkGroup(sink_id)) {
group->SetNeedsBeginFrame(sink_id, needs_begin_frame);
}
}
void FrameSinkBundleImpl::AddFrameSink(CompositorFrameSinkSupport* support) {
uint32_t sink_id = support->frame_sink_id().sink_id();
auto* source = support->begin_frame_source();
if (!source) {
sourceless_sinks_.insert(sink_id);
return;
}
auto& group = sink_groups_[source];
if (!group) {
group =
std::make_unique<SinkGroup>(*manager_, *this, *source, *client_.get());
}
group->AddFrameSink(sink_id);
}
void FrameSinkBundleImpl::UpdateFrameSink(CompositorFrameSinkSupport* support,
BeginFrameSource* old_source) {
uint32_t sink_id = support->frame_sink_id().sink_id();
RemoveFrameSinkImpl(old_source, sink_id);
AddFrameSink(support);
}
void FrameSinkBundleImpl::RemoveFrameSink(CompositorFrameSinkSupport* support) {
auto sink_id = support->frame_sink_id().sink_id();
auto* source = support->begin_frame_source();
RemoveFrameSinkImpl(source, sink_id);
}
void FrameSinkBundleImpl::SetNeedsBeginFrame(uint32_t sink_id,
bool needs_begin_frame) {
if (auto* sink = GetFrameSink(sink_id)) {
sink->SetNeedsBeginFrame(needs_begin_frame);
}
}
void FrameSinkBundleImpl::Submit(
std::vector<mojom::BundledFrameSubmissionPtr> submissions) {
std::map<raw_ptr<SinkGroup>, base::WeakPtr<SinkGroup>> groups;
std::map<raw_ptr<SinkGroup>, base::WeakPtr<SinkGroup>> affected_groups;
// Count the frame submissions before processing anything. This ensures that
// any frames submitted here will be acked together in a batch, and not acked
// individually in case they happen to ack synchronously within
// SubmitCompositorFrame below.
//
// For sinks which can't currently be associated with a SinkGroup (because
// they have no BeginFrameSource), we count nothing and their acks will pass
// through to the client without batching.
for (auto& submission : submissions) {
if (auto* group = GetSinkGroup(submission->sink_id)) {
groups.emplace(group, group->GetWeakPtr());
if (submission->data->is_frame()) {
group->WillSubmitFrame(submission->sink_id);
affected_groups.emplace(group, group->GetWeakPtr());
}
}
}
for (auto& submission : submissions) {
if (auto* sink = GetFrameSink(submission->sink_id)) {
switch (submission->data->which()) {
case mojom::BundledFrameSubmissionData::Tag::kFrame: {
mojom::BundledCompositorFramePtr& frame =
submission->data->get_frame();
sink->SubmitCompositorFrame(
frame->local_surface_id, std::move(frame->frame),
std::move(frame->hit_test_region_list), frame->submit_time);
break;
}
case mojom::BundledFrameSubmissionData::Tag::kDidNotProduceFrame:
sink->DidNotProduceFrame(
submission->data->get_did_not_produce_frame());
break;
}
}
}
for (const auto& [unsafe_group, weak_group] : groups) {
if (weak_group) {
weak_group->DidFinishFrame();
}
}
for (const auto& [unsafe_group, weak_group] : affected_groups) {
if (weak_group) {
weak_group->FlushMessages();
}
}
}
#if BUILDFLAG(IS_ANDROID)
void FrameSinkBundleImpl::SetThreads(uint32_t sink_id,
const std::vector<Thread>& threads) {
if (auto* sink = GetFrameSink(sink_id)) {
sink->SetThreads(threads);
}
}
#endif
void FrameSinkBundleImpl::EnqueueDidReceiveCompositorFrameAck(
uint32_t sink_id,
std::vector<ReturnedResource> resources) {
if (auto* group = GetSinkGroup(sink_id)) {
group->EnqueueDidReceiveCompositorFrameAck(sink_id, std::move(resources));
} else {
// The sink has no BeginFrameSource at the moment and therefore does not
// belong to a SinkGroup. Forward directly without batching.
std::vector<mojom::BundledReturnedResourcesPtr> acks;
acks.push_back(
mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
client_->FlushNotifications(std::move(acks), {}, {});
}
}
void FrameSinkBundleImpl::EnqueueOnBeginFrame(
uint32_t sink_id,
const BeginFrameArgs& args,
const base::flat_map<uint32_t, FrameTimingDetails>& details,
std::vector<ReturnedResource> resources) {
if (auto* group = GetSinkGroup(sink_id)) {
group->EnqueueOnBeginFrame(sink_id, args, details, std::move(resources));
} else {
// The sink has no BeginFrameSource at the moment and therefore does not
// belong to a SinkGroup. Forward directly without batching.
std::vector<mojom::BeginFrameInfoPtr> begin_frames;
begin_frames.push_back(mojom::BeginFrameInfo::New(sink_id, args, details,
std::move(resources)));
client_->FlushNotifications({}, std::move(begin_frames), {});
}
}
void FrameSinkBundleImpl::EnqueueReclaimResources(
uint32_t sink_id,
std::vector<ReturnedResource> resources) {
if (auto* group = GetSinkGroup(sink_id)) {
group->EnqueueReclaimResources(sink_id, std::move(resources));
} else {
// The sink has no BeginFrameSource at the moment and therefore does not
// belong to a SinkGroup. Forward directly without batching.
std::vector<mojom::BundledReturnedResourcesPtr> reclaims;
reclaims.push_back(
mojom::BundledReturnedResources::New(sink_id, std::move(resources)));
client_->FlushNotifications({}, {}, std::move(reclaims));
}
}
void FrameSinkBundleImpl::SendOnBeginFramePausedChanged(uint32_t sink_id,
bool paused) {
client_->OnBeginFramePausedChanged(sink_id, paused);
}
void FrameSinkBundleImpl::SendOnCompositorFrameTransitionDirectiveProcessed(
uint32_t sink_id,
uint32_t sequence_id) {
client_->OnCompositorFrameTransitionDirectiveProcessed(sink_id, sequence_id);
}
void FrameSinkBundleImpl::RemoveFrameSinkImpl(BeginFrameSource* source,
uint32_t sink_id) {
if (!source) {
sourceless_sinks_.erase(sink_id);
return;
}
auto it = sink_groups_.find(source);
if (it == sink_groups_.end()) {
DVLOG(1) << "Unexpected missing SinkGroup entry for sink " << sink_id;
return;
}
it->second->RemoveFrameSink(sink_id);
if (it->second->IsEmpty()) {
sink_groups_.erase(it);
}
}
CompositorFrameSinkImpl* FrameSinkBundleImpl::GetFrameSink(
uint32_t sink_id) const {
return manager_->GetFrameSinkImpl(FrameSinkId(id_.client_id(), sink_id));
}
CompositorFrameSinkSupport* FrameSinkBundleImpl::GetFrameSinkSupport(
uint32_t sink_id) const {
return manager_->GetFrameSinkForId(FrameSinkId(id_.client_id(), sink_id));
}
FrameSinkBundleImpl::SinkGroup* FrameSinkBundleImpl::GetSinkGroup(
uint32_t sink_id) const {
auto* support = GetFrameSinkSupport(sink_id);
if (!support) {
return nullptr;
}
auto* source = support->begin_frame_source();
auto it = sink_groups_.find(source);
if (it == sink_groups_.end()) {
return nullptr;
}
return it->second.get();
}
void FrameSinkBundleImpl::OnDisconnect() {
manager_->DestroyFrameSinkBundle(id_);
}
} // namespace viz