chromium/components/viz/service/display/overlay_processor_android.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 "components/viz/service/display/overlay_processor_android.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/synchronization/waitable_event.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/service/display/display_compositor_memory_and_task_controller.h"
#include "components/viz/service/display/overlay_processor_on_gpu.h"
#include "components/viz/service/display/overlay_strategy_underlay.h"
#include "components/viz/service/display/skia_output_surface.h"
#include "gpu/command_buffer/service/scheduler_sequence.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace viz {
OverlayProcessorAndroid::OverlayProcessorAndroid(
    DisplayCompositorMemoryAndTaskController* display_controller)
    : OverlayProcessorUsingStrategy(),
      gpu_task_scheduler_(display_controller->gpu_task_scheduler()) {
  // Promoting video to overlay with SurfaceView overlays requires recreation of
  // main SurfaceView and Display. This leads to the situation when we
  // consider video overlay not being efficient for the first frame after we
  // updated SurfaceView and video gets demoted back to composition. To avoid
  // this, we disable heuristics that filter out not efficient quads but still
  // sort them by potential power savings.
  prioritization_config_.changing_threshold = false;
  prioritization_config_.damage_rate_threshold = false;

  // In unittests, we don't have the gpu_task_scheduler_ set up, but still want
  // to test ProcessForOverlays functionalities where we are making overlay
  // candidates correctly.
  if (gpu_task_scheduler_) {
    gpu::ScopedAllowScheduleGpuTask allow_schedule_gpu_task;
    // TODO(weiliangc): Eventually move the on gpu initialization to another
    // static function.
    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
    auto callback = base::BindOnce(
        &OverlayProcessorAndroid::InitializeOverlayProcessorOnGpu,
        base::Unretained(this), display_controller->controller_on_gpu(),
        &event);
    gpu_task_scheduler_->ScheduleGpuTask(std::move(callback), {});
    event.Wait();
  }

  // For Android, we do not have the ability to skip an overlay, since the
  // texture is already in a SurfaceView.  Ideally, we would honor a 'force
  // overlay' flag that FromDrawQuad would also check.
  // For now, though, just skip the opacity check.  We really have no idea if
  // the underlying overlay is opaque anyway; the candidate is referring to
  // a dummy resource that has no relation to what the overlay contains.
  // https://crbug.com/842931 .
  strategies_.push_back(std::make_unique<OverlayStrategyUnderlay>(
      this, OverlayStrategyUnderlay::OpaqueMode::AllowTransparentCandidates));

  overlay_candidates_.clear();
}

OverlayProcessorAndroid::~OverlayProcessorAndroid() {
  if (processor_on_gpu_) {
    gpu::ScopedAllowScheduleGpuTask allow_schedule_gpu_task;
    // If we have a |gpu_task_scheduler_|, we must have started initializing
    // a |processor_on_gpu_| on the |gpu_task_scheduler_|.
    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
    auto callback =
        base::BindOnce(&OverlayProcessorAndroid::DestroyOverlayProcessorOnGpu,
                       base::Unretained(this), &event);
    gpu_task_scheduler_->ScheduleGpuTask(std::move(callback), {});
    event.Wait();
  }
}

void OverlayProcessorAndroid::InitializeOverlayProcessorOnGpu(
    gpu::DisplayCompositorMemoryAndTaskControllerOnGpu*
        display_controller_on_gpu,
    base::WaitableEvent* event) {
  processor_on_gpu_ =
      std::make_unique<OverlayProcessorOnGpu>(display_controller_on_gpu);
  DCHECK(event);
  event->Signal();
}

void OverlayProcessorAndroid::DestroyOverlayProcessorOnGpu(
    base::WaitableEvent* event) {
  processor_on_gpu_ = nullptr;
  DCHECK(event);
  event->Signal();
}

bool OverlayProcessorAndroid::IsOverlaySupported() const {
  return true;
}

bool OverlayProcessorAndroid::NeedsSurfaceDamageRectList() const {
  return false;
}

void OverlayProcessorAndroid::ScheduleOverlays(
    DisplayResourceProvider* resource_provider) {
  if (!processor_on_gpu_)
    return;

  // Even if we don't have anything to overlay, still generate overlay locks for
  // empty frame.
  pending_overlay_locks_.emplace_back();

  // Early out if we don't have any overlay candidates.
  if (overlay_candidates_.empty())
    return;

  auto& locks = pending_overlay_locks_.back();
  std::vector<gpu::SyncToken> locks_sync_tokens;
  for (auto& candidate : overlay_candidates_) {
    locks.emplace_back(resource_provider, candidate.resource_id);
    locks_sync_tokens.push_back(locks.back().sync_token());
  }

  auto task = base::BindOnce(&OverlayProcessorOnGpu::ScheduleOverlays,
                             base::Unretained(processor_on_gpu_.get()),
                             std::move(overlay_candidates_));
  gpu_task_scheduler_->ScheduleGpuTask(std::move(task), locks_sync_tokens);
  overlay_candidates_.clear();
}

void OverlayProcessorAndroid::OverlayPresentationComplete() {
  // If there is not |processor_on_gpu_| to send information to, we won't have
  // overlay resources locked, and don't need to clear the locks.
  if (!processor_on_gpu_)
    return;

  // This is a signal from Display::DidReceiveSwapBuffersAck. We use this to
  // help clear locks on resources from the old frame.
  committed_overlay_locks_.clear();
  std::swap(committed_overlay_locks_, pending_overlay_locks_.front());
  pending_overlay_locks_.pop_front();
}

void OverlayProcessorAndroid::CheckOverlaySupportImpl(
    const OverlayProcessorInterface::OutputSurfaceOverlayPlane* primary_plane,
    OverlayCandidateList* candidates) {
  // For pre-SurfaceControl Android we should not have output surface as overlay
  // plane.
  DCHECK(!primary_plane);

  // There should only be at most a single overlay candidate: the
  // video quad.
  // There's no check that the presented candidate is really a video frame for
  // a fullscreen video. Instead it's assumed that if a quad is marked as
  // overlayable, it's a fullscreen video quad.
  DCHECK_LE(candidates->size(), 1u);

  if (!candidates->empty()) {
    OverlayCandidate& candidate = candidates->front();

    // This quad either will be promoted, or would be if it were backed by a
    // SurfaceView.  Record that it should get a promotion hint.
    promotion_hint_info_map_[candidate.resource_id] = candidate.display_rect;

    if (!candidate.is_video_in_surface_view) {
      // This quad would be promoted if it were backed by a SurfaceView.  Since
      // it isn't, we can't promote it.
      return;
    }

    candidate.display_rect =
        gfx::RectF(gfx::ToEnclosingRect(candidate.display_rect));
    candidate.overlay_handled = true;
    candidate.plane_z_order = -1;

    // This quad will be promoted.  We clear the promotable hints here, since
    // we can only promote a single quad.  Otherwise, somebody might try to
    // back one of the promotable quads with a SurfaceView, and either it or
    // |candidate| would have to fall back to a texture.
    promotion_hint_info_map_.clear();
    promotion_hint_info_map_[candidate.resource_id] = candidate.display_rect;
  }
}

gfx::Rect OverlayProcessorAndroid::GetOverlayDamageRectForOutputSurface(
    const OverlayCandidate& overlay) const {
  return ToEnclosedRect(overlay.display_rect);
}

void OverlayProcessorAndroid::TakeOverlayCandidates(
    OverlayCandidateList* candidate_list) {
  overlay_candidates_.swap(*candidate_list);
  candidate_list->clear();
}

void OverlayProcessorAndroid::NotifyOverlayPromotion(
    DisplayResourceProvider* resource_provider,
    const CandidateList& candidates,
    const QuadList& quad_list) {
  // If we don't have a processor_on_gpu_, there is nothing to send the overlay
  // promotions to.
  if (!processor_on_gpu_) {
    promotion_hint_info_map_.clear();
    return;
  }

  // Set of resources that have requested a promotion hint that also have quads
  // that use them.
  ResourceIdSet promotion_hint_requestor_set;

  for (auto* quad : quad_list) {
    if (quad->material != DrawQuad::Material::kTextureContent)
      continue;
    const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad);
    if (!texture_quad->is_stream_video)
      continue;
    ResourceId id = texture_quad->resource_id();
    if (!resource_provider->DoesResourceWantPromotionHint(id))
      continue;
    promotion_hint_requestor_set.insert(id);
  }

  if (promotion_hint_requestor_set.empty()) {
    promotion_hint_info_map_.clear();
    return;
  }

  base::flat_set<gpu::Mailbox> promotion_denied;
  base::flat_map<gpu::Mailbox, gfx::Rect> possible_promotions;

  DCHECK(candidates.empty() || candidates.size() == 1u);

  std::vector<
      std::unique_ptr<DisplayResourceProvider::ScopedReadLockSharedImage>>
      locks;
  for (auto& request : promotion_hint_requestor_set) {
    // If we successfully promote one candidate, then that promotion hint
    // should be sent later when we schedule the overlay.
    if (!candidates.empty() && candidates.front().resource_id == request)
      continue;

    locks.emplace_back(
        std::make_unique<DisplayResourceProvider::ScopedReadLockSharedImage>(
            resource_provider, request));
    auto iter = promotion_hint_info_map_.find(request);
    if (iter != promotion_hint_info_map_.end()) {
      // This is a possible promotion.
      possible_promotions.emplace(locks.back()->mailbox(),
                                  gfx::ToEnclosedRect(iter->second));
    } else {
      promotion_denied.insert(locks.back()->mailbox());
    }
  }

  std::vector<gpu::SyncToken> locks_sync_tokens;
  for (auto& read_lock : locks)
    locks_sync_tokens.push_back(read_lock->sync_token());

  if (gpu_task_scheduler_) {
    auto task = base::BindOnce(&OverlayProcessorOnGpu::NotifyOverlayPromotions,
                               base::Unretained(processor_on_gpu_.get()),
                               std::move(promotion_denied),
                               std::move(possible_promotions));
    gpu_task_scheduler_->ScheduleGpuTask(std::move(task), locks_sync_tokens);
  }
  promotion_hint_info_map_.clear();
}

}  // namespace viz