chromium/android_webview/browser/gfx/root_frame_sink.cc

// 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 "android_webview/browser/gfx/root_frame_sink.h"

#include "android_webview/browser/gfx/child_frame.h"
#include "android_webview/browser/gfx/display_scheduler_webview.h"
#include "android_webview/browser/gfx/viz_compositor_thread_runner_webview.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/frame_sink_id_allocator.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/frame_index_constants.h"
#include "components/viz/service/surfaces/surface.h"

namespace android_webview {

namespace {

viz::FrameSinkId AllocateParentSinkId() {
  static viz::FrameSinkIdAllocator allocator(0u);
  return allocator.NextFrameSinkId();
}

}  // namespace

// Lifetime: WebView
// Instance owned by RootFrameSink
class RootFrameSink::ChildCompositorFrameSink
    : public viz::mojom::CompositorFrameSinkClient {
 public:
  ChildCompositorFrameSink(RootFrameSink* owner,
                           uint32_t layer_tree_frame_sink_id,
                           viz::FrameSinkId frame_sink_id)
      : owner_(owner),
        layer_tree_frame_sink_id_(layer_tree_frame_sink_id),
        frame_sink_id_(frame_sink_id),
        support_(std::make_unique<viz::CompositorFrameSinkSupport>(
            this,
            owner->GetFrameSinkManager(),
            frame_sink_id,
            false)) {
    support_->SetBeginFrameSource(nullptr);
  }

  void DidReceiveCompositorFrameAck(
      std::vector<viz::ReturnedResource> resources) override {
    ReclaimResources(std::move(resources));
  }
  void OnBeginFrame(const viz::BeginFrameArgs& args,
                    const viz::FrameTimingDetailsMap& feedbacks,
                    bool frame_ack,
                    std::vector<viz::ReturnedResource> resources) override {}
  void OnBeginFramePausedChanged(bool paused) override {}
  void ReclaimResources(std::vector<viz::ReturnedResource> resources) override {
    owner_->ReturnResources(frame_sink_id_, layer_tree_frame_sink_id_,
                            std::move(resources));
  }
  void OnCompositorFrameTransitionDirectiveProcessed(
      uint32_t sequence_id) override {
    owner_->OnCompositorFrameTransitionDirectiveProcessed(
        frame_sink_id_, layer_tree_frame_sink_id_, sequence_id);
  }
  void OnSurfaceEvicted(const viz::LocalSurfaceId& local_surface_id) override {}

  const viz::FrameSinkId frame_sink_id() { return frame_sink_id_; }

  uint32_t layer_tree_frame_sink_id() { return layer_tree_frame_sink_id_; }

  viz::CompositorFrameSinkSupport* support() { return support_.get(); }
  gfx::Size size() { return size_; }

  void SubmitCompositorFrame(
      const viz::LocalSurfaceId& local_surface_id,
      viz::CompositorFrame frame,
      std::optional<viz::HitTestRegionList> hit_test_region_list) {
    size_ = frame.size_in_pixels();
    support()->SubmitCompositorFrame(local_surface_id, std::move(frame),
                                     std::move(hit_test_region_list));
  }

  void EvictSurface(viz::SurfaceId surface_id) {
    if (surface_id.frame_sink_id() == frame_sink_id_)
      support_->EvictSurface(surface_id.local_surface_id());
  }

 private:
  const raw_ptr<RootFrameSink> owner_;
  const uint32_t layer_tree_frame_sink_id_;
  const viz::FrameSinkId frame_sink_id_;
  std::unique_ptr<viz::CompositorFrameSinkSupport> support_;
  gfx::Size size_;
};

RootFrameSink::RootFrameSink(RootFrameSinkClient* client)
    : root_frame_sink_id_(AllocateParentSinkId()),
      client_(client),
      use_new_invalidate_heuristic_(
          features::UseWebViewNewInvalidateHeuristic()) {
  constexpr bool is_root = true;
  GetFrameSinkManager()->RegisterFrameSinkId(root_frame_sink_id_,
                                             false /* report_activationa */);
  support_ = std::make_unique<viz::CompositorFrameSinkSupport>(
      this, GetFrameSinkManager(), root_frame_sink_id_, is_root);
  begin_frame_source_ = std::make_unique<viz::ExternalBeginFrameSource>(this);
  GetFrameSinkManager()->RegisterBeginFrameSource(begin_frame_source_.get(),
                                                  root_frame_sink_id_);

  // Note, that this technically not part of the new heuristic. Without this
  // line root CF will "request" BeginFrames for delivery of presentation
  // feedback that we don't care about which leads to more begin frame requested
  // than necessary. But to avoid any side effects on invalidation, fixing this
  // is gates under same feature flag.
  if (use_new_invalidate_heuristic_)
    support_->SetBeginFrameSource(nullptr);
}

RootFrameSink::~RootFrameSink() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  GetFrameSinkManager()->UnregisterBeginFrameSource(begin_frame_source_.get());
  begin_frame_source_.reset();
  support_.reset();
  GetFrameSinkManager()->InvalidateFrameSinkId(root_frame_sink_id_);
}

viz::FrameSinkManagerImpl* RootFrameSink::GetFrameSinkManager() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // FrameSinkManagerImpl is global and not owned by this class, which is
  // per-AwContents.
  return VizCompositorThreadRunnerWebView::GetInstance()->GetFrameSinkManager();
}

const viz::LocalSurfaceId& RootFrameSink::SubmitRootCompositorFrame(
    viz::CompositorFrame frame) {
  frame.metadata.frame_token = ++next_root_frame_token_;

  if (!root_local_surface_id_allocator_.HasValidLocalSurfaceId() ||
      root_surface_size_ != frame.size_in_pixels() ||
      root_device_scale_factor_ != frame.device_scale_factor()) {
    root_local_surface_id_allocator_.GenerateId();
    root_surface_size_ = frame.size_in_pixels();
    root_device_scale_factor_ = frame.device_scale_factor();
  }

  const auto& local_surface_id =
      root_local_surface_id_allocator_.GetCurrentLocalSurfaceId();
  support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
  return local_surface_id;
}

void RootFrameSink::EvictRootSurface(
    const viz::LocalSurfaceId& local_surface_id) {
  const auto& current_local_surface_id =
      root_local_surface_id_allocator_.GetCurrentLocalSurfaceId();

  DLOG_IF(FATAL, !current_local_surface_id.IsSameOrNewerThan(local_surface_id))
      << "Evicting newer surface: " << local_surface_id.ToString()
      << " old: " << current_local_surface_id.ToString();
  if (current_local_surface_id == local_surface_id) {
    root_surface_size_ = gfx::Size();
    root_device_scale_factor_ = 0.0f;
  }
  support_->EvictSurface(local_surface_id);
}

void RootFrameSink::DidReceiveCompositorFrameAck(
    std::vector<viz::ReturnedResource> resources) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  ReclaimResources(std::move(resources));
}

void RootFrameSink::ReclaimResources(
    std::vector<viz::ReturnedResource> resources) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  // Root surface should have no resources to return.
  CHECK(resources.empty());
}

void RootFrameSink::OnNeedsBeginFrames(bool needs_begin_frames) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  TRACE_EVENT_INSTANT1("android_webview", "RootFrameSink::OnNeedsBeginFrames",
                       TRACE_EVENT_SCOPE_THREAD, "needs_begin_frames",
                       needs_begin_frames);
  clients_need_begin_frames_ = needs_begin_frames;

  // Old heuristic doesn't need extra begin frames, so just forward client
  // needs.
  if (!use_new_invalidate_heuristic_) {
    UpdateNeedsBeginFrames(clients_need_begin_frames_);
    return;
  }

  // Make sure that we subscribed to BF if client needs them. We don't
  // unsubscribe from BF here to make sure that we invalidated for the latest
  // frames in necessary. We will stop observing them later in BeginFrame()
  // once we are done.
  if (clients_need_begin_frames_)
    UpdateNeedsBeginFrames(true);
}

void RootFrameSink::AddChildFrameSinkId(const viz::FrameSinkId& frame_sink_id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  child_frame_sink_ids_.insert(frame_sink_id);
  GetFrameSinkManager()->RegisterFrameSinkHierarchy(root_frame_sink_id_,
                                                    frame_sink_id);
}

void RootFrameSink::RemoveChildFrameSinkId(
    const viz::FrameSinkId& frame_sink_id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  child_frame_sink_ids_.erase(frame_sink_id);
  GetFrameSinkManager()->UnregisterFrameSinkHierarchy(root_frame_sink_id_,
                                                      frame_sink_id);
}

void RootFrameSink::SetContainedSurfaces(
    const base::flat_set<viz::SurfaceId>& ids) {
  contained_surfaces_ = ids;
  for (auto it = last_invalidated_frame_index_.begin();
       it != last_invalidated_frame_index_.end();) {
    if (!contained_surfaces_.contains(it->first))
      it = last_invalidated_frame_index_.erase(it);
    else
      ++it;
  }
}

void RootFrameSink::UpdateNeedsBeginFrames(bool needs_begin_frames) {
  if (needs_begin_frames_ != needs_begin_frames) {
    needs_begin_frames_ = needs_begin_frames;
    if (client_)
      client_->SetNeedsBeginFrames(needs_begin_frames_);
  }
}

bool RootFrameSink::HasPendingDependency(const viz::SurfaceId& surface_id) {
  auto* surface =
      GetFrameSinkManager()->surface_manager()->GetSurfaceForId(surface_id);

  if (!surface || !surface->HasActiveFrame())
    return true;

  for (auto& range : surface->GetActiveFrame().metadata.referenced_surfaces) {
    if (HasPendingDependency(range.end()))
      return true;
  }
  return false;
}

bool RootFrameSink::ProcessVisibleSurfacesInvalidation() {
  if (!use_new_invalidate_heuristic_) {
    // This handles only invalidation of sub clients, root client invalidation
    // is handled by Invalidate() from cc to |SynchronousLayerTreeFrameSink|. So
    // we return false unless we already have damage.
    return needs_draw_;
  }

  bool invalidate = false;

  // There are few possible cases:
  // * viz::Surface is visible (i.e was embedded last frame and any scheduled
  // draws don't change that). In this case surface is in `contained_surfaces`
  // and we need to invalidate for any CompositorFrame that we haven't
  // invalidated yet. This is a steady state.
  // * viz::Surface is visible, but there are scheduled draws that remove it. In
  // this case surface is in `contained_surfaces`, but technically there is no
  // need to invalidate it. We can't know that it will disappear, so we
  // invalidate anyway.
  // * viz::Surface is visible, but has pending dependencies (embedded surfaces
  // without active frame). In this case surface is in `contained_surfaces`, but
  // the dependents aren't. Invalidate in this case pessimistically assuming
  // there are uncommitted frames that can be activated on commit in dependent
  // frames.
  // * viz::Surface is not visible yet, but there is a pending draw that will
  // embed it. In this case the surface is not in `contained_surfaces` yet, so
  // we can't process it here. After the draw will happen it's possible that
  // there are uncommitted frames that are already scheduled to draw, but have
  // not been processed here. This can cause extra invalidation.
  // * viz::Surface is not visible and there is no pending draw. This shouldn't
  // be possible because the only way to embed a child surface is for the root
  // renderer to submit a compositor frame and invalidation of it is handled
  // separately.

  // If there is pending dependency, invalidate.
  if (root_local_surface_id_allocator_.HasValidLocalSurfaceId()) {
    auto surface_id = viz::SurfaceId(
        root_frame_sink_id(),
        root_local_surface_id_allocator_.GetCurrentLocalSurfaceId());
    invalidate = invalidate || HasPendingDependency(surface_id);
  }

  for (auto& surface_id : contained_surfaces_) {
    auto* surface =
        GetFrameSinkManager()->surface_manager()->GetSurfaceForId(surface_id);
    if (surface) {
      // Track last frame_index that we invalidated for. Note, that this doesn't
      // take into account what current frame is or what display compositor will
      // draw. The intent here is to invalidate once for each CompositorFrame in
      // the Surface we see.
      auto& last_invalidated_index = last_invalidated_frame_index_[surface_id];
      auto uncommited_frame_index =
          last_invalidated_index > viz::kInvalidFrameIndex
              ? surface->GetUncommitedFrameIndexNewerThan(
                    last_invalidated_index)
              : surface->GetFirstUncommitedFrameIndex();
      if (uncommited_frame_index.has_value()) {
        invalidate = true;
        last_invalidated_index = uncommited_frame_index.value();
      }
    }
  }

  return invalidate;
}

bool RootFrameSink::BeginFrame(const viz::BeginFrameArgs& args,
                               bool had_input_event) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  // We call ProcessVisibleSurfacesInvalidation() to make sure heuristic updated
  // it's state (e.g last invalidated begin frame args).
  bool invalidate = ProcessVisibleSurfacesInvalidation() || had_input_event;

  TRACE_EVENT_INSTANT1("android_webview", "RootFrameSink::BeginFrame",
                       TRACE_EVENT_SCOPE_THREAD, "invalidate", invalidate);

  if (clients_need_begin_frames_) {
    begin_frame_source_->OnBeginFrame(args);
  } else if (!invalidate) {
    if (use_new_invalidate_heuristic_) {
      // Client don't need begin frames and we didn't invalidate, so we don't
      // need them either.
      UpdateNeedsBeginFrames(false);
    }
  }

  return invalidate;
}

void RootFrameSink::SetBeginFrameSourcePaused(bool paused) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  begin_frame_source_->OnSetBeginFrameSourcePaused(paused);
}

void RootFrameSink::SetNeedsDraw(bool needs_draw) {
  // Only old heuristic needs this.
  DCHECK(!use_new_invalidate_heuristic_);

  needs_draw_ = needs_draw;

  // It's possible that client submitted last frame and unsubscribed from
  // BeginFrames, but we haven't draw it yet.
  if (!needs_begin_frames_ && needs_draw) {
    if (client_)
      client_->Invalidate();
  }
}

void RootFrameSink::OnNewUncommittedFrame(const viz::SurfaceId& surface_id) {
  // Only new heurstic needs this.
  if (!use_new_invalidate_heuristic_)
    return;

  // If there is new uncommitted frame in the surface that affects display, make
  // sure we request a begin frame to check if we need to invalidate next frame.
  UpdateNeedsBeginFrames(true);
}

bool RootFrameSink::IsChildSurface(const viz::FrameSinkId& frame_sink_id) {
  return child_frame_sink_ids_.contains(frame_sink_id);
}

void RootFrameSink::ReturnResources(
    viz::FrameSinkId frame_sink_id,
    uint32_t layer_tree_frame_sink_id,
    std::vector<viz::ReturnedResource> resources) {
  if (client_)
    client_->ReturnResources(frame_sink_id, layer_tree_frame_sink_id,
                             std::move(resources));
}

void RootFrameSink::OnCompositorFrameTransitionDirectiveProcessed(
    viz::FrameSinkId frame_sink_id,
    uint32_t layer_tree_frame_sink_id,
    uint32_t sequence_id) {
  if (client_) {
    client_->OnCompositorFrameTransitionDirectiveProcessed(
        frame_sink_id, layer_tree_frame_sink_id, sequence_id);
  }
}

void RootFrameSink::DettachClient() {
  client_ = nullptr;
}

void RootFrameSink::SubmitChildCompositorFrame(ChildFrame* child_frame) {
  DCHECK(child_frame->frame);
  DCHECK(child_frame->local_surface_id.is_valid());
  if (!child_sink_support_ ||
      child_sink_support_->frame_sink_id() != child_frame->frame_sink_id ||
      child_sink_support_->layer_tree_frame_sink_id() !=
          child_frame->layer_tree_frame_sink_id) {
    child_sink_support_.reset();

    child_sink_support_ = std::make_unique<ChildCompositorFrameSink>(
        this, child_frame->layer_tree_frame_sink_id,
        child_frame->frame_sink_id);
    child_frame_renderer_thread_ids_ = {};
  }
  if (child_frame_renderer_thread_ids_ != child_frame->renderer_thread_ids) {
    child_frame_renderer_thread_ids_ = child_frame->renderer_thread_ids;
    // Thread IDs from a sandboxed renderer process, thus untrusted and
    // require verification.
    // child_frame_renderer_thread_ids_ are used only to avoid unnessary
    // reverification, they shouldn't be used a source of truth in
    // GetChildFrameRendererThreadIds.
    child_sink_support_->support()->SetThreadIds(
        /*from_untrusted_client=*/true, child_frame->renderer_thread_ids);
  }

  // Root renderer frame MUST be presented synchronously with UI, so we can't
  // delay activation. Note, it's not part of invalidation heuristic, but for
  // safety we update deadline only on the new path, on the old path there are
  // almost no embedded surfaces anyway.
  if (use_new_invalidate_heuristic_) {
    child_frame->frame->metadata.deadline = viz::FrameDeadline::MakeZero();
  }

  child_sink_support_->SubmitCompositorFrame(
      child_frame->local_surface_id, std::move(*child_frame->frame),
      std::move(child_frame->hit_test_region_list));
  child_frame->frame.reset();
}

viz::FrameTimingDetailsMap RootFrameSink::TakeChildFrameTimingDetailsMap() {
  // Take timing for root CompositorFrameSinkSupport to avoid them accumulating.
  // We don't use them anyhow.
  std::ignore = support_->TakeFrameTimingDetailsMap();

  if (child_sink_support_)
    return child_sink_support_->support()->TakeFrameTimingDetailsMap();
  return viz::FrameTimingDetailsMap();
}

gfx::Size RootFrameSink::GetChildFrameSize() {
  // TODO(vasilyt): This is not going to work with VizFrameSubmissionForWebView.
  if (child_sink_support_) {
    return child_sink_support_->size();
  }
  return gfx::Size();
}

base::flat_set<base::PlatformThreadId>
RootFrameSink::GetChildFrameRendererThreadIds() {
  if (child_sink_support_) {
    return child_sink_support_->support()->GetThreadIds();
  }
  return {};
}

void RootFrameSink::EvictChildSurface(const viz::SurfaceId& surface_id) {
  DCHECK(child_sink_support_);
  child_sink_support_->EvictSurface(surface_id);
}

void RootFrameSink::OnCaptureStarted(const viz::FrameSinkId& frame_sink_id) {
  if (!base::Contains(contained_surfaces_, frame_sink_id,
                      &viz::SurfaceId::frame_sink_id)) {
    return;
  }
  // When a capture is started we need to force an invalidate.
  if (client_)
    client_->Invalidate();
}

void RootFrameSink::InvalidateForOverlays() {
  if (client_) {
    client_->Invalidate();
  }
}

}  // namespace android_webview