blob: b12980ee1a22428b138745db5013ca5db25b68a7 [file] [log] [blame]
// Copyright 2019 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/surfaces/surface_allocation_group.h"
#include <algorithm>
#include <numeric>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
namespace viz {
SurfaceAllocationGroup::SurfaceAllocationGroup(
SurfaceManager* surface_manager,
const FrameSinkId& submitter,
const base::UnguessableToken& embed_token)
: submitter_(submitter),
embed_token_(embed_token),
surface_manager_(surface_manager) {}
SurfaceAllocationGroup::~SurfaceAllocationGroup() {
DCHECK(surfaces_.empty());
DCHECK(active_embedders_.empty());
DCHECK(blocked_embedders_.empty());
}
bool SurfaceAllocationGroup::IsReadyToDestroy() const {
return surfaces_.empty() && active_embedders_.empty() &&
blocked_embedders_.empty();
}
void SurfaceAllocationGroup::RegisterSurface(Surface* surface) {
DCHECK_EQ(submitter_, surface->surface_id().frame_sink_id());
DCHECK_EQ(embed_token_,
surface->surface_id().local_surface_id().embed_token());
DCHECK(!last_created_surface() || surface->surface_id().IsNewerThan(
last_created_surface()->surface_id()));
surfaces_.push_back(surface);
}
void SurfaceAllocationGroup::UnregisterSurface(Surface* surface) {
auto it = std::ranges::find(surfaces_, surface);
CHECK(it != surfaces_.end());
surfaces_.erase(it);
MaybeMarkForDestruction();
}
void SurfaceAllocationGroup::RegisterBlockedEmbedder(
Surface* surface,
const SurfaceId& activation_dependency) {
blocked_embedders_[surface] = activation_dependency;
}
void SurfaceAllocationGroup::UnregisterBlockedEmbedder(Surface* surface,
bool did_activate) {
DCHECK(blocked_embedders_.count(surface));
blocked_embedders_.erase(surface);
// If the pending frame activated, don't notify SurfaceManager that this
// allocation group needs to be destroyed, because the embedder will soon
// call RegisterActiveEmbedder.
if (!did_activate)
MaybeMarkForDestruction();
}
bool SurfaceAllocationGroup::HasBlockedEmbedder() const {
return !blocked_embedders_.empty();
}
void SurfaceAllocationGroup::RegisterActiveEmbedder(Surface* surface) {
DCHECK(!active_embedders_.count(surface));
active_embedders_.insert(surface);
}
void SurfaceAllocationGroup::UnregisterActiveEmbedder(Surface* surface) {
DCHECK(active_embedders_.count(surface));
active_embedders_.erase(surface);
MaybeMarkForDestruction();
}
void SurfaceAllocationGroup::UpdateLastActiveReferenceAndMaybeActivate(
const SurfaceId& surface_id) {
DCHECK_EQ(submitter_, surface_id.frame_sink_id());
DCHECK_EQ(embed_token_, surface_id.local_surface_id().embed_token());
if (last_active_reference_.is_valid() &&
last_active_reference_.IsSameOrNewerThan(surface_id)) {
return;
}
last_active_reference_ = surface_id;
auto it = FindLatestSurfaceUpTo(surface_id);
if (it != surfaces_.end() && !(*it)->HasActiveFrame())
(*it)->ActivatePendingFrameForInheritedDeadline();
UpdateLastReferenceAndMaybeActivate(surface_id);
}
void SurfaceAllocationGroup::UpdateLastPendingReferenceAndMaybeActivate(
const SurfaceId& surface_id) {
UpdateLastReferenceAndMaybeActivate(surface_id);
}
const SurfaceId& SurfaceAllocationGroup::GetLastActiveReference() {
return last_active_reference_;
}
const SurfaceId& SurfaceAllocationGroup::GetLastReference() {
return last_reference_;
}
Surface* SurfaceAllocationGroup::FindLatestActiveSurfaceInRange(
const SurfaceRange& range) const {
// If the embed token of the end of the SurfaceRange matches that of this
// group, find the latest active surface that is older than or equal to the
// end, then check that it's not older than start.
if (range.end().local_surface_id().embed_token() == embed_token_) {
DCHECK_EQ(submitter_, range.end().frame_sink_id());
auto it = FindLatestActiveSurfaceUpTo(range.end());
if (it != surfaces_.end() &&
(!range.start() || !range.start()->IsNewerThan((*it)->surface_id()))) {
return *it;
} else {
return nullptr;
}
}
// If we are here, the embed token of the end of the range doesn't match this
// group's embed token. In this case, the range must have a start and its
// embed token must match this group. Simply find the last active surface, and
// check whether it's newer than the range's start.
DCHECK(range.start());
DCHECK_EQ(embed_token_, range.start()->local_surface_id().embed_token());
DCHECK_NE(embed_token_, range.end().local_surface_id().embed_token());
DCHECK_EQ(submitter_, range.start()->frame_sink_id());
Surface* result = nullptr;
// Normally there is at most one pending surface, so this for loop shouldn't
// take more than two iterations.
for (int i = surfaces_.size() - 1; i >= 0; i--) {
if (surfaces_[i]->HasActiveFrame()) {
result = surfaces_[i];
break;
}
}
if (result && range.start()->IsNewerThan(result->surface_id()))
return nullptr;
return result;
}
void SurfaceAllocationGroup::TakeAggregatedLatencyInfoUpTo(
Surface* surface,
std::vector<ui::LatencyInfo>* out) {
DCHECK_EQ(this, surface->allocation_group());
surface->TakeActiveLatencyInfo(out);
auto it = FindLatestSurfaceUpTo(surface->surface_id());
DCHECK_EQ(*it, surface);
while (it > surfaces_.begin() && !(*--it)->is_latency_info_taken())
(*it)->TakeActiveAndPendingLatencyInfo(out);
}
void SurfaceAllocationGroup::OnFirstSurfaceActivation(Surface* surface) {
for (Surface* embedder : active_embedders_)
embedder->OnChildActivatedForActiveFrame(surface->surface_id());
base::flat_map<Surface*, SurfaceId> embedders_to_notify;
for (const auto& entry : blocked_embedders_) {
if (!entry.second.IsNewerThan(surface->surface_id()))
embedders_to_notify[entry.first] = entry.second;
}
for (const auto& entry : embedders_to_notify)
blocked_embedders_.erase(entry.first);
for (const auto& entry : embedders_to_notify)
entry.first->OnActivationDependencyResolved(entry.second, this);
}
void SurfaceAllocationGroup::WillNotRegisterNewSurfaces() {
base::flat_map<Surface*, SurfaceId> embedders = std::move(blocked_embedders_);
blocked_embedders_.clear();
for (const auto& entry : embedders) {
entry.first->OnActivationDependencyResolved(entry.second, this);
}
}
void SurfaceAllocationGroup::AckLastestActiveUnAckedFrame() {
if (!last_active_reference_.is_valid())
return;
SurfaceRange range(last_active_reference_);
auto* lastest_active = FindLatestActiveSurfaceInRange(range);
// If this group is blocking another Surface, and our latest frame is unacked,
// we send the Ack now. This will allow frame production to continue for our
// client, leading to this group unblocking the other.
if (lastest_active && lastest_active->HasUnackedActiveFrame())
lastest_active->SendAckToClient();
}
std::vector<raw_ptr<Surface, VectorExperimental>>::const_iterator
SurfaceAllocationGroup::FindLatestSurfaceUpTo(
const SurfaceId& surface_id) const {
DCHECK_EQ(submitter_, surface_id.frame_sink_id());
DCHECK_EQ(embed_token_, surface_id.local_surface_id().embed_token());
// Return early if there are no surfaces in this group.
if (surfaces_.empty())
return surfaces_.end();
// If even the first surface is newer than |surface_id|, we can't find a
// surface that is older than or equal to |surface_id|.
if (!surface_id.IsSameOrNewerThan(surfaces_[0]->surface_id()))
return surfaces_.end();
// Perform a binary search the find the latest surface that is older than or
// equal to |surface_id|.
int begin = 0;
int end = surfaces_.size();
while (end - begin > 1) {
int avg = std::midpoint(begin, end);
if (!surface_id.IsSameOrNewerThan(surfaces_[avg]->surface_id()))
end = avg;
else
begin = avg;
}
DCHECK(surface_id.IsSameOrNewerThan(surfaces_[begin]->surface_id()));
return surfaces_.begin() + begin;
}
std::vector<raw_ptr<Surface, VectorExperimental>>::const_iterator
SurfaceAllocationGroup::FindLatestActiveSurfaceUpTo(
const SurfaceId& surface_id) const {
// Start from the last older or equal surface and keep iterating back until we
// find an active surface. Normally, there is only one pending surface at a
// time this shouldn't take more than two iterations.
auto it = FindLatestSurfaceUpTo(surface_id);
if (it == surfaces_.end())
return surfaces_.end();
for (; it >= surfaces_.begin(); --it) {
if ((*it)->HasActiveFrame())
return it;
}
// No active surface was found.
return surfaces_.end();
}
void SurfaceAllocationGroup::MaybeMarkForDestruction() {
if (IsReadyToDestroy())
surface_manager_->SetAllocationGroupsNeedGarbageCollection();
}
void SurfaceAllocationGroup::UpdateLastReferenceAndMaybeActivate(
const SurfaceId& surface_id) {
if (last_reference_.IsSameOrNewerThan(surface_id))
return;
last_reference_ = surface_id;
if (surfaces_.empty())
return;
auto it = FindLatestSurfaceUpTo(surface_id);
if (it == surfaces_.end())
return;
// If |surface_id| does not exist yet, notify the surface immediately prior to
// it that it is a fallback. This might activate the surface immediately
// because fallback surfaces never block.
if ((*it)->surface_id() != surface_id)
(*it)->SetIsFallbackAndMaybeActivate();
}
} // namespace viz