// 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 "ash/frame_sink/frame_sink_holder.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "ash/frame_sink/frame_sink_host.h"
#include "ash/frame_sink/ui_resource_manager.h"
#include "base/check.h"
#include "base/task/single_thread_task_runner.h"
#include "cc/scheduler/scheduler.h"
#include "cc/trees/layer_tree_frame_sink.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_timing_details.h"
#include "components/viz/common/hit_test/hit_test_region_list.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "ui/aura/window.h"
namespace ash {
namespace {
constexpr int32_t kPauseBeginFrameThreshold = 5;
} // namespace
FrameSinkHolder::FrameSinkHolder(
std::unique_ptr<cc::LayerTreeFrameSink> frame_sink,
const GetCompositorFrameCallback get_compositor_frame_callback,
const OnFirstFrameRequestedCallback on_first_frame_requested_callback)
: frame_sink_(std::move(frame_sink)),
get_compositor_frame_callback_(std::move(get_compositor_frame_callback)),
on_first_frame_requested_callback_(
std::move(on_first_frame_requested_callback)) {
frame_sink_->BindToClient(this);
}
FrameSinkHolder::~FrameSinkHolder() {
if (frame_sink_) {
frame_sink_->DetachFromClient();
}
}
// static.
bool FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::unique_ptr<FrameSinkHolder> frame_sink_holder,
aura::Window* host_window) {
UiResourceManager& resource_manager = frame_sink_holder->resource_manager();
if (frame_sink_holder->last_frame_size_in_pixels_.IsEmpty()) {
// Delete sink holder immediately if no frame has been submitted.
DCHECK(resource_manager.exported_resources_count() == 0);
return true;
}
// Submit an empty frame to ensure that pending release callbacks will be
// processed in a finite amount of time.
frame_sink_holder->frame_sink_->SubmitCompositorFrame(
frame_sink_holder->CreateEmptyFrame(),
/*hit_test_data_changed=*/true);
// Delete sink holder immediately if not waiting for exported resources to
// be reclaimed.
if (resource_manager.exported_resources_count() == 0) {
return true;
}
// If we have exported resources to reclaim then extend the lifetime of
// holder by deleting it later.
DCHECK(host_window);
aura::Window* root_window = host_window->GetRootWindow();
// This can be null during shutdown.
if (!root_window) {
// Since we are in shutdown process, we will not be able to recover the
// exported resources so just let the resources be marked as lost.
resource_manager.LostExportedResources();
return true;
}
// If we have exported resources to reclaim then extend the lifetime of
// holder.
frame_sink_holder.release()->SetRootWindowForDeletion(root_window);
return false;
}
viz::CompositorFrame FrameSinkHolder::CreateEmptyFrame() {
viz::CompositorFrame frame;
frame.metadata.begin_frame_ack.frame_id =
viz::BeginFrameId(viz::BeginFrameArgs::kManualSourceId,
viz::BeginFrameArgs::kStartingFrameNumber);
frame.metadata.begin_frame_ack.has_damage = true;
frame.metadata.device_scale_factor = last_frame_device_scale_factor_;
frame.metadata.frame_token = ++compositor_frame_token_generator_;
auto pass = viz::CompositorRenderPass::Create();
pass->SetNew(viz::CompositorRenderPassId{1},
gfx::Rect(last_frame_size_in_pixels_),
gfx::Rect(last_frame_size_in_pixels_), gfx::Transform());
frame.render_pass_list.push_back(std::move(pass));
return frame;
}
void FrameSinkHolder::SetRootWindowForDeletion(aura::Window* root_window) {
// The holder will delete itself when the root window is removed or when all
// exported resources have been reclaimed.
DCHECK(root_window);
root_window_observation_.Observe(root_window);
}
void FrameSinkHolder::SetAutoUpdateMode(bool mode) {
if (auto_update_ == mode) {
return;
}
auto_update_ = mode;
if (auto_update_) {
ObserveBeginFrameSource(/*start=*/true);
}
}
void FrameSinkHolder::SubmitCompositorFrame(bool synchronous_draw) {
// We cannot request to submit a frame via `SubmitCompositorFrame()` if we are
// in auto_update mode.
DCHECK(!auto_update_);
// Once the lifetime of FrameSinkHolder is extended, we should not submit new
// frames since the `get_compositor_frame_callback_` can become null.
DCHECK(!WaitingToScheduleDelete());
if (delete_pending_ || auto_update_ || WaitingToScheduleDelete()) {
return;
}
ObserveBeginFrameSource(/*start=*/true);
// If we are already submitted a frame we cannot submit a new frame until we
// get an acknowledgement from display compositor and we fall back to
// asynchronous drawing.
// Some FrameSinkHosts can request to submit a frame synchronously, even
// before viz thread is fully enabled therefore we wait till display
// compositor asks for the first frame therefore we fall to asynchronous
// drawing till signaled.
if (!synchronous_draw || pending_compositor_frame_ack_ ||
!first_frame_requested_) {
pending_compositor_frame_ = true;
return;
}
std::unique_ptr<viz::CompositorFrame> frame =
get_compositor_frame_callback_.Run(
viz::BeginFrameAck::CreateManualAckWithDamage(), resources_manager_,
auto_update_, last_frame_size_in_pixels_,
last_frame_device_scale_factor_);
if (!frame) {
return;
}
SubmitCompositorFrameInternal(std::move(frame));
}
void FrameSinkHolder::SubmitCompositorFrameInternal(
std::unique_ptr<viz::CompositorFrame> frame) {
consecutive_begin_frames_produced_no_frame_count_ = 0;
pending_compositor_frame_ = false;
pending_compositor_frame_ack_ = true;
last_frame_size_in_pixels_ = frame->size_in_pixels();
last_frame_device_scale_factor_ = frame->metadata.device_scale_factor;
frame->metadata.frame_token = ++compositor_frame_token_generator_;
frame_sink_->SubmitCompositorFrame(std::move(*frame),
/*hit_test_data_changed=*/true);
}
void FrameSinkHolder::OnBeginFrameSourcePausedChanged(bool paused) {}
bool FrameSinkHolder::OnBeginFrameDerivedImpl(const viz::BeginFrameArgs& args) {
// Once the lifetime of FrameSinkHolder is extended, we should not submit new
// frames asynchronously since the `get_compositor_frame_callback_` can become
// null.
if (WaitingToScheduleDelete()) {
return false;
}
if (!first_frame_requested_) {
first_frame_requested_ = true;
on_first_frame_requested_callback_.Run();
}
viz::BeginFrameAck current_begin_frame_ack(args, false);
if (pending_compositor_frame_ack_ ||
!(pending_compositor_frame_ || auto_update_)) {
const cc::FrameSkippedReason reason =
pending_compositor_frame_ack_ ? cc::FrameSkippedReason::kWaitingOnMain
: cc::FrameSkippedReason::kNoDamage;
DidNotProduceFrame(std::move(current_begin_frame_ack), reason);
return false;
}
std::unique_ptr<viz::CompositorFrame> frame =
get_compositor_frame_callback_.Run(
current_begin_frame_ack, resources_manager_, auto_update_,
last_frame_size_in_pixels_, last_frame_device_scale_factor_);
if (!frame) {
// Failure to produce a frame is treated as if there was no damage.
DidNotProduceFrame(std::move(current_begin_frame_ack),
cc::FrameSkippedReason::kNoDamage);
return false;
}
SubmitCompositorFrameInternal(std::move(frame));
return true;
}
void FrameSinkHolder::SetBeginFrameSource(viz::BeginFrameSource* source) {
ObserveBeginFrameSource(/*start=*/false);
begin_frame_source_ = source;
ObserveBeginFrameSource(/*start=*/true);
}
void FrameSinkHolder::ObserveBeginFrameSource(bool start) {
if (begin_frame_observation_.IsObserving() == start) {
return;
}
if (begin_frame_source_) {
consecutive_begin_frames_produced_no_frame_count_ = 0;
if (start) {
begin_frame_observation_.Observe(begin_frame_source_);
} else {
begin_frame_observation_.Reset();
}
}
}
void FrameSinkHolder::MaybeStopObservingBeingFrameSource() {
if (!auto_update_ && consecutive_begin_frames_produced_no_frame_count_ >=
kPauseBeginFrameThreshold) {
ObserveBeginFrameSource(/*start=*/false);
}
}
void FrameSinkHolder::DidNotProduceFrame(viz::BeginFrameAck&& begin_frame_ack,
cc::FrameSkippedReason reason) {
frame_sink_->DidNotProduceFrame(begin_frame_ack, reason);
++consecutive_begin_frames_produced_no_frame_count_;
MaybeStopObservingBeingFrameSource();
}
std::optional<viz::HitTestRegionList> FrameSinkHolder::BuildHitTestData() {
return std::nullopt;
}
void FrameSinkHolder::ReclaimResources(
std::vector<viz::ReturnedResource> resources) {
if (delete_pending_) {
return;
}
resource_manager().ReclaimResources(resources);
if (WaitingToScheduleDelete() &&
resource_manager().exported_resources_count() == 0) {
ScheduleDelete();
}
}
void FrameSinkHolder::SetTreeActivationCallback(
base::RepeatingClosure callback) {}
void FrameSinkHolder::DidReceiveCompositorFrameAck() {
pending_compositor_frame_ack_ = false;
}
void FrameSinkHolder::DidPresentCompositorFrame(
uint32_t frame_token,
const viz::FrameTimingDetails& details) {
if (!presentation_callback_.is_null()) {
presentation_callback_.Run(details.presentation_feedback);
}
}
void FrameSinkHolder::DidLoseLayerTreeFrameSink() {
resource_manager().LostExportedResources();
if (WaitingToScheduleDelete()) {
ScheduleDelete();
}
}
void FrameSinkHolder::OnDraw(const gfx::Transform& transform,
const gfx::Rect& viewport,
bool resourceless_software_draw,
bool skip_draw) {}
void FrameSinkHolder::SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) {}
void FrameSinkHolder::SetExternalTilePriorityConstraints(
const gfx::Rect& viewport_rect,
const gfx::Transform& transform) {}
void FrameSinkHolder::OnWindowDestroying(aura::Window* window) {
// Since we are destroying the root_window via which we were extending the
// lifetime of the layer_sink_holder, after this point we cannot recover the
// exported resources therefore just mark the exported resources as lost.
resources_manager_.LostExportedResources();
root_window_observation_.Reset();
// Detaching client from `frame_sink_` ensures that display_compositor does
// not call methods on `this` after we have scheduled the deletion of this
// holder.
frame_sink_->DetachFromClient();
frame_sink_.reset();
ScheduleDelete();
}
void FrameSinkHolder::ScheduleDelete() {
if (delete_pending_) {
return;
}
delete_pending_ = true;
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
this);
}
bool FrameSinkHolder::WaitingToScheduleDelete() const {
// We only start observing the root window after calling
// FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed. An observing
// root_window_observation_ means that we are waiting for all the exported
// resources to be returned before we can delete `this` frame sink holder.
return root_window_observation_.IsObserving();
}
} // namespace ash