// Copyright 2020 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/overlay_processor_webview.h"
#include <cstdlib>
#include "android_webview/browser/gfx/gpu_service_webview.h"
#include "android_webview/browser/gfx/viz_compositor_thread_runner_webview.h"
#include "base/android/android_hardware_buffer_compat.h"
#include "base/android/build_info.h"
#include "base/android/scoped_hardware_buffer_fence_sync.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/not_fatal_until.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
#include "components/viz/common/features.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/resolved_frame_data.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/gl/gpu_service_impl.h"
#include "components/viz/service/surfaces/surface.h"
#include "gpu/command_buffer/service/display_compositor_memory_and_task_controller_on_gpu.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/command_buffer/service/scheduler_sequence.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/single_task_sequence.h"
#include "ui/gfx/android/android_surface_control_compat.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace android_webview {
namespace {
constexpr gpu::CommandBufferNamespace kOverlayProcessorNamespace =
gpu::CommandBufferNamespace::IN_PROCESS;
constexpr int kMaxBuffersInFlight = 3;
scoped_refptr<gpu::SyncPointClientState> CreateSyncPointClientState(
gpu::CommandBufferId command_buffer_id,
gpu::SequenceId sequence_id) {
return GpuServiceWebView::GetInstance()
->sync_point_manager()
->CreateSyncPointClientState(kOverlayProcessorNamespace,
command_buffer_id, sequence_id);
}
} // namespace
// Manages ASurfaceControl life-cycle and handles ASurfaceTransactions. Created
// on Android RenderThread, but both used on both Android RenderThread and GPU
// Main thread, so can be destroyed on one of them.
//
// Lifetime: WebView
// Each OverlayProcessorWebView owns one Manager. Ref-counted for callbacks.
class OverlayProcessorWebView::Manager
: public base::RefCountedThreadSafe<OverlayProcessorWebView::Manager> {
private:
// Instances are either directly owned by Manager or indirectly through
// OverlaySurface.
class Resource {
public:
Resource(gpu::SharedImageManager* shared_image_manager,
gpu::MemoryTypeTracker* memory_tracker,
const gpu::Mailbox& mailbox,
const gfx::RectF& uv_rect,
const gfx::ColorSpace& color_space,
float frame_rate,
base::ScopedClosureRunner return_resource)
: color_space_(color_space),
frame_rate_(frame_rate),
return_resource(std::move(return_resource)) {
representation_ =
shared_image_manager->ProduceOverlay(mailbox, memory_tracker);
if (!representation_) {
return;
}
read_access_ = representation_->BeginScopedReadAccess();
if (!read_access_) {
LOG(ERROR) << "Couldn't access shared image for read.";
return;
}
gfx::GpuFenceHandle acquire_fence = read_access_->TakeAcquireFence();
if (!acquire_fence.is_null()) {
begin_read_fence_ = acquire_fence.Release();
}
AHardwareBuffer_Desc desc;
base::AndroidHardwareBufferCompat::GetInstance().Describe(
GetAHardwareBuffer(), &desc);
gfx::RectF scaled_rect = gfx::ScaleRect(uv_rect, desc.width, desc.height);
crop_rect_ = gfx::ToEnclosedRect(scaled_rect);
}
~Resource() {
DCHECK(!read_access_)
<< "Return() or ReturnUnused() must be called before dtor";
DCHECK(!representation_);
}
Resource(Resource&&) = default;
Resource& operator=(Resource&&) = default;
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
void Return(base::ScopedFD end_read_fence) {
// It's possible that we didn't have buffer for the first frame (see
// `GetAHardwareBuffer()`) so there will be no read_access to set fence
// to. On the other hand we shouldn't get a fence from flinger for this
// surface in this case.
if (read_access_) {
gfx::GpuFenceHandle fence_handle;
fence_handle.Adopt(std::move(end_read_fence));
read_access_->SetReleaseFence(std::move(fence_handle));
read_access_.reset();
} else {
DCHECK(!end_read_fence.is_valid());
}
representation_.reset();
}
void ReturnUnused() {
read_access_.reset();
representation_.reset();
begin_read_fence_.reset();
}
base::ScopedFD TakeBeginReadFence() { return std::move(begin_read_fence_); }
AHardwareBuffer* GetAHardwareBuffer() {
// Note, that it's possible that BeginScopedReadAccess() will fail if
// media couldn't get us a frame. We don't fail creation of resource in
// this case, because if affects Surface acks and we don't to change the
// frame submission flow. Instead we just set empty buffer to the surface.
// Note, that it should only happen for the first frame in very rare
// cases.
if (!read_access_)
return nullptr;
DCHECK(representation_);
DCHECK(read_access_);
return read_access_->GetAHardwareBuffer();
}
const gfx::Rect& crop_rect() { return crop_rect_; }
const gfx::ColorSpace& color_space() { return color_space_; }
float frame_rate() const { return frame_rate_; }
private:
gfx::Rect crop_rect_;
gfx::ColorSpace color_space_;
float frame_rate_;
base::ScopedClosureRunner return_resource;
std::unique_ptr<gpu::OverlayImageRepresentation> representation_;
std::unique_ptr<gpu::OverlayImageRepresentation::ScopedReadAccess>
read_access_;
base::ScopedFD begin_read_fence_;
};
public:
Manager(gpu::CommandBufferId command_buffer_id, gpu::SequenceId sequence_id)
: shared_image_manager_(
GpuServiceWebView::GetInstance()->shared_image_manager()),
memory_tracker_(std::make_unique<gpu::MemoryTypeTracker>(nullptr)),
sync_point_client_state_(
CreateSyncPointClientState(command_buffer_id, sequence_id)) {
DETACH_FROM_THREAD(gpu_thread_checker_);
}
void SetGpuService(viz::GpuServiceImpl* gpu_service) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
DCHECK_EQ(shared_image_manager_, gpu_service->shared_image_manager());
gpu_task_runner_ = gpu_service->main_runner();
}
// Create SurfaceControl for |overlay_id| and set it up.
void CreateOverlay(uint64_t overlay_id,
const viz::OverlayCandidate& candidate,
base::ScopedClosureRunner return_resource,
uint64_t sync_fence_release) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
TRACE_EVENT1("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::CreateOverlay",
"overlay_id", overlay_id);
auto& transaction = GetHWUITransaction();
// Use 0.f as unspecified frame rate will set proper frame rate on buffer
// update.
std::unique_ptr<Resource> resource = CreateResource(
candidate.mailbox, candidate.unclipped_uv_rect, candidate.color_space,
/*frame_rate=*/0.f, std::move(return_resource));
{
base::AutoLock lock(lock_);
bool inserted;
base::flat_map<uint64_t, OverlaySurface>::iterator it;
std::tie(it, inserted) =
overlay_surfaces_.emplace(overlay_id, GetParentSurface());
DCHECK(inserted);
auto& overlay_surface = it->second;
UpdateGeometryInTransaction(transaction, *overlay_surface.bounds_surface,
candidate);
UpdateBufferInTransaction(transaction, *overlay_surface.buffer_surface,
resource.get());
overlay_surface.buffer_update_pending = true;
}
DCHECK(!pending_resource_update_.contains(overlay_id));
pending_resource_update_[overlay_id] = std::move(resource);
sync_point_client_state_->ReleaseFenceSync(sync_fence_release);
}
// Update geometry of SurfaceControl for |overlay_id|.
void UpdateOverlayGeometry(uint64_t overlay_id,
const viz::OverlayCandidate& candidate) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
TRACE_EVENT1("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::UpdateOverlayGeometry",
"overlay_id", overlay_id);
auto& transaction = GetHWUITransaction();
base::AutoLock lock(lock_);
auto& overlay_surface = GetOverlaySurfaceLocked(overlay_id);
UpdateGeometryInTransaction(transaction, *overlay_surface.bounds_surface,
candidate);
}
// Update buffer in SurfaceControl for |overlay_id|. Called on GPU Main
// Thread.
void UpdateOverlayBuffer(uint64_t overlay_id,
gpu::Mailbox mailbox,
const gfx::ColorSpace& color_space,
const gfx::RectF& uv_rect,
float frame_rate,
base::ScopedClosureRunner return_resource) {
DCHECK_CALLED_ON_VALID_THREAD(gpu_thread_checker_);
TRACE_EVENT1("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::UpdateOverlayBuffer",
"overlay_id", overlay_id);
base::AutoLock lock(lock_);
auto& overlay_surface = GetOverlaySurfaceLocked(overlay_id);
// If we're going to remove this overlay, there is no point in updating
// buffer anymore. Resource will be unlocked by |return_resource| getting
// out of scope.
if (overlay_surface.pending_remove) {
return;
}
std::unique_ptr<Resource> resource = CreateResource(
mailbox, uv_rect, color_space, frame_rate, std::move(return_resource));
// If there is already transaction with buffer update in-flight, store this
// one. This will return any previous stored resource if any.
if (overlay_surface.buffer_update_pending) {
overlay_surface.SetPendingResource(std::move(resource));
return;
}
SubmitTransactionWithBufferLocked(overlay_id, overlay_surface,
std::move(resource));
}
// Initiate removal of SurfaceControl for |overlay_id|. Removal done in next
// steps:
// Unparent SurfaceControl, this happens synchronously with HWUI draw.
// Set buffer to nullptr, to get current_buffer back with a fence.
// Free SurfaceControl.
void RemoveOverlay(uint64_t overlay_id) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
TRACE_EVENT1("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::RemoveOverlay",
"overlay_id", overlay_id);
auto& transaction = GetHWUITransaction();
{
base::AutoLock lock(lock_);
auto& overlay_surface = GetOverlaySurfaceLocked(overlay_id);
transaction.SetParent(*overlay_surface.bounds_surface, nullptr);
}
pending_removals_.insert(overlay_id);
}
// Initiate removal of all current surfaces and drop reference to
// parent_surface. This can be called with empty array.
void RemoveOverlays(std::vector<uint64_t> overlay_ids) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
TRACE_EVENT0("gpu,benchmark,webview",
"OverlayProcessorWebview::Manager::RemoveOverlays");
parent_surface_.reset();
if (overlay_ids.empty())
return;
auto& transaction = GetHWUITransaction();
{
base::AutoLock lock(lock_);
for (auto overlay_id : overlay_ids) {
auto& overlay = GetOverlaySurfaceLocked(overlay_id);
transaction.SetParent(*overlay.bounds_surface, nullptr);
}
}
pending_removals_.insert(overlay_ids.begin(), overlay_ids.end());
}
void OnUpdateBufferTransactionAck(
uint64_t overlay_id,
std::unique_ptr<Resource> resource,
gfx::SurfaceControl::TransactionStats transaction_stats) {
DCHECK_CALLED_ON_VALID_THREAD(gpu_thread_checker_);
TRACE_EVENT2(
"gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::OnUpdateBufferTransactionAck",
"overlay_id", overlay_id, "has_resource", !!resource);
base::AutoLock lock(lock_);
auto& overlay_surface = GetOverlaySurfaceLocked(overlay_id);
DCHECK_EQ(transaction_stats.surface_stats.size(), 1u);
DCHECK_EQ(transaction_stats.surface_stats.front().surface,
overlay_surface.buffer_surface->surface());
bool empty_buffer = !resource;
overlay_surface.SetResource(
std::move(resource),
std::move(transaction_stats.surface_stats.front().fence));
if (overlay_surface.pending_resource) {
DCHECK(!overlay_surface.pending_remove);
SubmitTransactionWithBufferLocked(
overlay_id, overlay_surface,
std::move(overlay_surface.pending_resource));
}
if (overlay_surface.pending_remove) {
// If there is no resource, we can free our surface.
if (empty_buffer) {
overlay_surface.Reset();
overlay_surfaces_.erase(overlay_id);
} else {
// This means there was buffer transaction in flight when surface was
// hidden, we need to set buffer to nullptr, to free current one before
// we can free the surface.
SubmitTransactionWithBufferLocked(overlay_id, overlay_surface, nullptr);
}
}
}
void OnHWUITransactionAck(
base::flat_map<uint64_t, std::unique_ptr<Resource>> resource_updates,
base::flat_set<uint64_t> removes,
gfx::SurfaceControl::TransactionStats transaction_stats) {
DCHECK_CALLED_ON_VALID_THREAD(gpu_thread_checker_);
TRACE_EVENT0("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::OnHWUITransactionAck");
base::AutoLock lock(lock_);
for (auto& update : resource_updates) {
auto& overlay_surface = GetOverlaySurfaceLocked(update.first);
base::ScopedFD fence;
for (auto& stat : transaction_stats.surface_stats) {
if (stat.surface == overlay_surface.buffer_surface->surface()) {
DCHECK(!fence.is_valid());
fence = std::move(stat.fence);
}
}
overlay_surface.SetResource(std::move(update.second), std::move(fence));
if (overlay_surface.pending_resource) {
SubmitTransactionWithBufferLocked(
update.first, overlay_surface,
std::move(overlay_surface.pending_resource));
}
}
for (auto& overlay_id : removes) {
auto& overlay_surface = GetOverlaySurfaceLocked(overlay_id);
overlay_surface.pending_remove = true;
if (overlay_surface.pending_resource) {
overlay_surface.pending_resource->ReturnUnused();
overlay_surface.pending_resource.reset();
}
if (!overlay_surface.buffer_update_pending) {
SubmitTransactionWithBufferLocked(overlay_id, overlay_surface, nullptr);
}
}
}
std::optional<gfx::SurfaceControl::Transaction> TakeHWUITransaction() {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
std::optional<gfx::SurfaceControl::Transaction> result;
if (hwui_transaction_) {
DCHECK(gpu_task_runner_);
if (!pending_resource_update_.empty() || !pending_removals_.empty()) {
auto cb = base::BindOnce(&Manager::OnHWUITransactionAck, this,
std::move(pending_resource_update_),
std::move(pending_removals_));
hwui_transaction_->SetOnCompleteCb(std::move(cb), gpu_task_runner_);
}
result.swap(hwui_transaction_);
}
return result;
}
private:
friend class OverlayProcessorWebView::ScopedSurfaceControlAvailable;
friend class base::RefCountedThreadSafe<Manager>;
// Class that holds SurfaceControl and associated resources.
//
// Instances are owned by Manager.
class OverlaySurface {
public:
OverlaySurface(const gfx::SurfaceControl::Surface& parent)
: bounds_surface(base::MakeRefCounted<gfx::SurfaceControl::Surface>(
parent,
"webview_overlay_bounds")),
buffer_surface(base::MakeRefCounted<gfx::SurfaceControl::Surface>(
*bounds_surface,
"webview_overlay_content")) {}
OverlaySurface(OverlaySurface&& other) = default;
OverlaySurface& operator=(OverlaySurface&& other) = default;
~OverlaySurface() {
DCHECK(!bounds_surface);
DCHECK(!buffer_surface);
DCHECK(!current_resource);
}
void SetResource(std::unique_ptr<Resource> resource,
base::ScopedFD end_read_fence) {
if (current_resource) {
current_resource->Return(std::move(end_read_fence));
}
current_resource = std::move(resource);
DCHECK(buffer_update_pending);
buffer_update_pending = false;
}
void SetPendingResource(std::unique_ptr<Resource> resource) {
DCHECK(buffer_update_pending);
if (pending_resource) {
pending_resource->ReturnUnused();
}
pending_resource = std::move(resource);
}
void Reset() {
DCHECK(!pending_resource);
DCHECK(!current_resource);
bounds_surface.reset();
buffer_surface.reset();
}
// Set when we're in process of removing this overlay.
bool pending_remove = false;
// This is true when there is SurfaceControl transaction that affects buffer
// of this overlay is in-flight.
bool buffer_update_pending = false;
// Resource that is currently latched by SurfaceControl
std::unique_ptr<Resource> current_resource;
// Resource that we want to send to SurfaceControl, but there was another
// transaction with buffer update in-flight.
std::unique_ptr<Resource> pending_resource;
// SurfaceControl for this overlay.
scoped_refptr<gfx::SurfaceControl::Surface> bounds_surface;
scoped_refptr<gfx::SurfaceControl::Surface> buffer_surface;
};
~Manager() {
DCHECK(!hwui_transaction_);
DCHECK(!parent_surface_);
}
gfx::SurfaceControl::Transaction& GetHWUITransaction() {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
if (!hwui_transaction_)
hwui_transaction_.emplace();
return hwui_transaction_.value();
}
const gfx::SurfaceControl::Surface& GetParentSurface() {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
if (!parent_surface_) {
DCHECK(get_surface_control_);
parent_surface_ =
gfx::SurfaceControl::Surface::WrapUnowned(get_surface_control_());
DCHECK(parent_surface_);
}
return *parent_surface_;
}
OverlaySurface& GetOverlaySurfaceLocked(uint64_t id) {
lock_.AssertAcquired();
auto surface = overlay_surfaces_.find(id);
CHECK(surface != overlay_surfaces_.end(), base::NotFatalUntil::M130);
return surface->second;
}
std::unique_ptr<Resource> CreateResource(
const gpu::Mailbox& mailbox,
const gfx::RectF uv_rect,
const gfx::ColorSpace color_space,
float frame_rate,
base::ScopedClosureRunner return_resource) {
if (mailbox.IsZero())
return nullptr;
return std::make_unique<Resource>(
shared_image_manager_, memory_tracker_.get(), mailbox, uv_rect,
color_space, frame_rate, std::move(return_resource));
}
// Because we update different parts of geometry on different threads we use
// two surfaces to avoid races. The Bounds surface is setup the way it assumes
// that its content size is kBoundsSurfaceContentSize. The Buffer surface is
// setup to scale itself to kBoundsSurfaceContentSize. Note, that from scaling
// perspective this number doesn't matter as scales of two surfaces will be
// just multiplied inside SurfaceFlinger, but because positions and crop rects
// are integers we need the content size to be large enough to avoid rounding
// errors. To avoid floating point errors we also use power of two.
static constexpr float kBoundsSurfaceContentSize = 8192.0f;
static void UpdateGeometryInTransaction(
gfx::SurfaceControl::Transaction& transaction,
gfx::SurfaceControl::Surface& surface,
const viz::OverlayCandidate& candidate) {
DCHECK_EQ(absl::get<gfx::OverlayTransform>(candidate.transform),
gfx::OVERLAY_TRANSFORM_NONE);
gfx::Rect dst = gfx::ToEnclosingRect(candidate.unclipped_display_rect);
transaction.SetPosition(surface, dst.origin());
// Setup scale so the contents of size kBoundsSurfaceContentSize would fit
// into display_rect. The buffer surface will make sure to scale its content
// to kBoundsSurfaceContentSize.
float scale_x = dst.width() / kBoundsSurfaceContentSize;
float scale_y = dst.height() / kBoundsSurfaceContentSize;
transaction.SetScale(surface, scale_x, scale_y);
if (candidate.clip_rect) {
// Make |crop_rect| relative to |display_rect|.
auto crop_rect = dst;
crop_rect.Intersect(*candidate.clip_rect);
crop_rect.Offset(-dst.x(), -dst.y());
// Crop rect is in content space, so we need to scale it.
auto scaled_clip = gfx::ToEnclosingRect(gfx::ScaleRect(
gfx::RectF(crop_rect), 1.0f / scale_x, 1.0f / scale_y));
transaction.SetCrop(surface, scaled_clip);
}
}
static void UpdateBufferInTransaction(
gfx::SurfaceControl::Transaction& transaction,
gfx::SurfaceControl::Surface& surface,
Resource* resource) {
TRACE_EVENT1("gpu,benchmark,android_webview",
"OverlayProcessorWebview::Manager::UpdateBufferInTransaction",
"has_resource", !!resource);
auto* buffer = resource ? resource->GetAHardwareBuffer() : nullptr;
if (buffer) {
auto crop_rect = resource->crop_rect();
// Crop rect defines the valid portion of the buffer, so we use its as a
// surface size. This calculates scale from our size to bounds surface
// content size, see comment at kBoundsSurfaceContentSize.
float scale_x = kBoundsSurfaceContentSize / crop_rect.width();
float scale_y = kBoundsSurfaceContentSize / crop_rect.height();
// Crop rect is defined in buffer space, so we need to translate our
// surface to make sure crop rect origin matches bounds surface (0, 0).
// Position is defined in parent space, so we need to scale it.
transaction.SetPosition(surface,
gfx::Point(-ceil(crop_rect.x() * scale_x),
-ceil(crop_rect.y() * scale_y)));
transaction.SetScale(surface, scale_x, scale_y);
transaction.SetCrop(surface, crop_rect);
transaction.SetColorSpace(surface, resource->color_space(), std::nullopt);
transaction.SetBuffer(surface, buffer, resource->TakeBeginReadFence());
if (gfx::SurfaceControl::SupportsSetFrameRate()) {
transaction.SetFrameRate(surface, resource->frame_rate());
}
} else {
// Android T has a bug where setting empty buffer to ASurfaceControl will
// result in surface completely missing from ASurfaceTransactionStats in
// OnComplete callback. To workaround it we create 1x1 buffer instead of
// setting empty one.
const bool need_empty_buffer_workaround =
base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SDK_VERSION_T;
if (need_empty_buffer_workaround) {
// We never delete this buffer.
static AHardwareBuffer* fake_buffer = nullptr;
if (!fake_buffer) {
AHardwareBuffer_Desc hwb_desc = {};
hwb_desc.width = 1;
hwb_desc.height = 1;
hwb_desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
hwb_desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
hwb_desc.usage |= gfx::SurfaceControl::RequiredUsage();
hwb_desc.layers = 1;
// Allocate an AHardwareBuffer.
base::AndroidHardwareBufferCompat::GetInstance().Allocate(
&hwb_desc, &fake_buffer);
if (!fake_buffer) {
LOG(ERROR) << "Failed to allocate AHardwareBuffer";
}
}
buffer = fake_buffer;
}
transaction.SetBuffer(surface, buffer, base::ScopedFD());
}
}
void SubmitTransactionWithBufferLocked(uint64_t overlay_id,
OverlaySurface& overlay_surface,
std::unique_ptr<Resource> resource) {
lock_.AssertAcquired();
DCHECK(!overlay_surface.buffer_update_pending);
DCHECK(gpu_task_runner_);
overlay_surface.buffer_update_pending = true;
gfx::SurfaceControl::Transaction transaction;
UpdateBufferInTransaction(transaction, *overlay_surface.buffer_surface,
resource.get());
auto cb = base::BindOnce(&Manager::OnUpdateBufferTransactionAck, this,
overlay_id, std::move(resource));
transaction.SetOnCompleteCb(std::move(cb), gpu_task_runner_);
transaction.Apply();
}
base::Lock lock_;
// These can be accessed on any thread, but only initialized in ctor.
const raw_ptr<gpu::SharedImageManager> shared_image_manager_;
std::unique_ptr<gpu::MemoryTypeTracker> memory_tracker_;
// GPU Main Thread task runner.
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
// SyncPointClientState for render thread sequence.
scoped_refptr<gpu::SyncPointClientState> sync_point_client_state_;
// Can be accessed on both threads.
base::flat_map<uint64_t, OverlaySurface> overlay_surfaces_ GUARDED_BY(lock_);
// Pending updates for the current hwui transaction.
base::flat_map<uint64_t, std::unique_ptr<Resource>> pending_resource_update_;
base::flat_set<uint64_t> pending_removals_;
scoped_refptr<gfx::SurfaceControl::Surface> parent_surface_;
std::optional<gfx::SurfaceControl::Transaction> hwui_transaction_;
GetSurfaceControlFn get_surface_control_ = nullptr;
THREAD_CHECKER(render_thread_checker_);
THREAD_CHECKER(gpu_thread_checker_);
};
OverlayProcessorWebView::OverlayProcessorWebView(
viz::DisplayCompositorMemoryAndTaskController* display_controller,
viz::FrameSinkManagerImpl* frame_sink_manager)
: command_buffer_id_(gpu::DisplayCompositorMemoryAndTaskControllerOnGpu::
NextCommandBufferId()),
render_thread_sequence_(display_controller->gpu_task_scheduler()),
frame_sink_manager_(frame_sink_manager) {
base::ScopedAllowBaseSyncPrimitives allow_wait;
base::WaitableEvent event;
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&OverlayProcessorWebView::CreateManagerOnRT,
base::Unretained(this), command_buffer_id_,
render_thread_sequence_->GetSequenceId(), &event),
std::vector<gpu::SyncToken>());
event.Wait();
}
OverlayProcessorWebView::~OverlayProcessorWebView() {
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(
[](scoped_refptr<Manager> manager) {
// manager leaves scope.
},
std::move(manager_)),
std::vector<gpu::SyncToken>());
}
void OverlayProcessorWebView::CreateManagerOnRT(
gpu::CommandBufferId command_buffer_id,
gpu::SequenceId sequence_id,
base::WaitableEvent* event) {
manager_ = base::MakeRefCounted<Manager>(command_buffer_id, sequence_id);
event->Signal();
}
void OverlayProcessorWebView::SetOverlaysEnabledByHWUI(bool enabled) {
overlays_enabled_by_hwui_ = enabled;
}
void OverlayProcessorWebView::RemoveOverlays() {
overlays_enabled_by_hwui_ = false;
std::vector<uint64_t> ids;
ids.reserve(overlays_.size());
for (auto overlay : overlays_)
ids.push_back(overlay.second.id);
// Note, that we send it even there are no overlays, to drop reference to the
// parent surface.
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&Manager::RemoveOverlays, base::Unretained(manager_.get()),
std::move(ids)),
std::vector<gpu::SyncToken>());
overlays_.clear();
}
std::optional<gfx::SurfaceControl::Transaction>
OverlayProcessorWebView::TakeSurfaceTransactionOnRT() {
DCHECK(manager_);
return manager_->TakeHWUITransaction();
}
void OverlayProcessorWebView::CheckOverlaySupportImpl(
const viz::OverlayProcessorInterface::OutputSurfaceOverlayPlane*
primary_plane,
viz::OverlayCandidateList* candidates) {
// If HWUI doesn't want us to overlay, we shouldn't.
if (!overlays_enabled_by_hwui_)
return;
// We need GpuServiceImpl (one for Gpu Main Thread, not GpuServiceWebView) to
// use overlays. It takes time to initialize it, so we don't block
// RenderThread for it. Instead we're just polling here if it's done.
if (!gpu_thread_sequence_) {
viz::GpuServiceImpl* gpu_service =
VizCompositorThreadRunnerWebView::GetInstance()->GetGpuService();
if (!gpu_service)
return;
gpu_thread_sequence_ = std::make_unique<gpu::SchedulerSequence>(
gpu_service->GetGpuScheduler(), gpu_service->main_runner());
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&OverlayProcessorWebView::Manager::SetGpuService,
base::Unretained(manager_.get()), gpu_service),
std::vector<gpu::SyncToken>());
}
// Check candidates if they can be used with surface control.
OverlayProcessorSurfaceControl::CheckOverlaySupportImpl(primary_plane,
candidates);
}
void OverlayProcessorWebView::TakeOverlayCandidates(
viz::OverlayCandidateList* candidate_list) {
overlay_candidates_.swap(*candidate_list);
candidate_list->clear();
}
void OverlayProcessorWebView::ScheduleOverlays(
viz::DisplayResourceProvider* resource_provider) {
DCHECK(!resource_provider_ || resource_provider_ == resource_provider_);
resource_provider_ = resource_provider;
DCHECK(gpu_thread_sequence_ || overlay_candidates_.empty());
base::flat_set<viz::FrameSinkId> seen;
for (auto& candidate : overlay_candidates_) {
viz::SurfaceId surface_id =
resource_provider->GetSurfaceId(candidate.resource_id);
viz::FrameSinkId sink_id = surface_id.frame_sink_id();
seen.insert(sink_id);
auto overlay = overlays_.find(sink_id);
if (overlay != overlays_.end()) {
// Need to update only geometry.
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&Manager::UpdateOverlayGeometry,
base::Unretained(manager_.get()), overlay->second.id,
candidate),
std::vector<gpu::SyncToken>());
// If renderer embedded new surface (i.e video player size changed) we
// need to update buffer here. For all other cases it's updated in
// ProcessForFrameSinkId().
if (overlay->second.surface_id != surface_id) {
overlay->second.surface_id = surface_id;
UpdateOverlayResource(sink_id, candidate.resource_id,
candidate.unclipped_uv_rect);
}
} else {
overlay =
overlays_
.insert(std::make_pair(
sink_id, Overlay(next_overlay_id_++, candidate.resource_id,
resource_provider_->GetChildId(
candidate.resource_id))))
.first;
overlay->second.surface_id = surface_id;
overlay->second.create_sync_token =
gpu::SyncToken(kOverlayProcessorNamespace, command_buffer_id_,
++sync_fence_release_);
auto result = LockResource(overlay->second);
candidate.mailbox = result.mailbox;
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&Manager::CreateOverlay,
base::Unretained(manager_.get()), overlay->second.id,
candidate, std::move(result.unlock_cb),
overlay->second.create_sync_token.release_count()),
{result.sync_token});
}
}
for (auto it = overlays_.begin(); it != overlays_.end();) {
if (!seen.contains(it->first)) {
render_thread_sequence_->ScheduleGpuTask(
base::BindOnce(&Manager::RemoveOverlay,
base::Unretained(manager_.get()), it->second.id),
std::vector<gpu::SyncToken>());
it = overlays_.erase(it);
} else {
++it;
}
}
}
OverlayProcessorWebView::LockResult OverlayProcessorWebView::LockResource(
Overlay& overlay) {
LockResult result{};
auto resource_id = overlay.resource_id;
resource_lock_count_[overlay.surface_id.frame_sink_id()]++;
OverlayResourceLock lock = OverlayResourceLock(
static_cast<viz::DisplayResourceProviderSkia*>(resource_provider_),
resource_id);
result.sync_token = lock.sync_token();
result.mailbox = lock.mailbox();
locked_resources_.insert(std::make_pair(resource_id, std::move(lock)));
auto return_cb = base::BindOnce(&OverlayProcessorWebView::ReturnResource,
weak_ptr_factory_.GetWeakPtr(), resource_id,
overlay.surface_id);
auto return_cb_on_thread = base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(), std::move(return_cb));
result.unlock_cb = base::ScopedClosureRunner(std::move(return_cb_on_thread));
return result;
}
void OverlayProcessorWebView::UpdateOverlayResource(
viz::FrameSinkId frame_sink_id,
viz::ResourceId new_resource_id,
const gfx::RectF& uv_rect) {
DCHECK(resource_provider_);
auto overlay = overlays_.find(frame_sink_id);
CHECK(overlay != overlays_.end(), base::NotFatalUntil::M130);
DCHECK(resource_provider_->IsOverlayCandidate(new_resource_id));
if (new_resource_id != overlay->second.resource_id) {
overlay->second.resource_id = new_resource_id;
auto result = LockResource(overlay->second);
gfx::ColorSpace color_space =
OverlayProcessorSurfaceControl::GetOverrideColorSpace().value_or(
resource_provider_->GetColorSpace(new_resource_id));
gpu_thread_sequence_->ScheduleTask(
base::BindOnce(&Manager::UpdateOverlayBuffer, manager_,
overlay->second.id, result.mailbox, color_space, uv_rect,
frame_rate_, std::move(result.unlock_cb)),
{result.sync_token, overlay->second.create_sync_token});
}
}
void OverlayProcessorWebView::ReturnResource(viz::ResourceId resource_id,
viz::SurfaceId surface_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// |locked_resources_| is multimap and can contain multiple locks of the same
// resource_id. There is no difference between locks at this point, they all
// just keeping resource locked so it's safe to remove any of them when
// OverlayManager return resources. When we delete last lock resource will be
// return to the client.
auto it = locked_resources_.find(resource_id);
CHECK(it != locked_resources_.end(), base::NotFatalUntil::M130);
locked_resources_.erase(it);
DCHECK(resource_lock_count_.contains(surface_id.frame_sink_id()));
auto& count = resource_lock_count_[surface_id.frame_sink_id()];
DCHECK_GT(count, 0);
// When the lock count reaches kMaxBuffersInFlight, we don't send acks to the
// client in the ProcessForFrameSinkId. In this case we send ack here when the
// lock count drops below the threshold. Note, that because we still lock
// resource and schedule buffer update, the lock count can be larger than
// kMaxBuffersInFlight in certain cases, like quick overlay demotion and
// promotion again.
if (count == kMaxBuffersInFlight) {
auto* surface =
frame_sink_manager_->surface_manager()->GetSurfaceForId(surface_id);
if (surface) {
surface->SendAckToClient();
}
}
if (!--count)
resource_lock_count_.erase(surface_id.frame_sink_id());
}
bool OverlayProcessorWebView::ProcessForFrameSinkId(
const viz::FrameSinkId& frame_sink_id,
const viz::ResolvedFrameData* frame_data) {
auto it = overlays_.find(frame_sink_id);
CHECK(it != overlays_.end(), base::NotFatalUntil::M130);
auto& overlay = it->second;
const auto& passes = frame_data->GetResolvedPasses();
if (passes.empty()) {
return false;
}
DCHECK_EQ(passes.size(), 1u);
bool buffer_updated = false;
auto& pass = passes.back();
if (!pass.draw_quads().empty()) {
DCHECK_EQ(pass.draw_quads().size(), 1u);
auto* surface = frame_sink_manager_->surface_manager()->GetSurfaceForId(
overlay.surface_id);
// TODO(vasilyt): We should get this from surface aggregator after
// aggregator refactoring will be finished.
const auto& frame = surface->GetActiveFrame();
auto* quad = frame.render_pass_list.back()->quad_list.front();
if (gfx::SurfaceControl::SupportsSetFrameRate() &&
base::FeatureList::IsEnabled(features::kWebViewFrameRateHints)) {
float frame_rate = 0.f;
const viz::FrameIntervalInputs& frame_interval_inputs =
frame.metadata.frame_interval_inputs;
std::optional<base::TimeDelta> frame_interval;
for (const viz::ContentFrameIntervalInfo& content_info :
frame_interval_inputs.content_interval_info) {
if (!frame_interval) {
frame_interval = content_info.frame_interval;
continue;
}
if (frame_interval.value() != content_info.frame_interval) {
frame_interval.reset();
break;
}
}
if (frame_interval &&
frame_interval_inputs.has_only_content_frame_interval_updates) {
frame_rate = frame_interval->ToHz();
}
constexpr float kEpsilon = 0.005;
if (std::abs(frame_rate - frame_rate_) > kEpsilon) {
frame_rate_ = frame_rate;
}
}
// We overlay only TextureDrawQuads and only if resource
// IsOverlayCandidate(), return false otherwise so we would trigger
// invalidate and normal draw would remove this overlay candidate.
if (quad->material == viz::TextureDrawQuad::kMaterial) {
auto* texture_quad = viz::TextureDrawQuad::MaterialCast(quad);
DCHECK(texture_quad->is_stream_video);
auto uv_rect = gfx::BoundingRect(texture_quad->uv_top_left,
texture_quad->uv_bottom_right);
auto new_resource_id =
pass.draw_quads().front().remapped_resources.ids[0];
if (resource_provider_->IsOverlayCandidate(new_resource_id)) {
UpdateOverlayResource(frame_sink_id, new_resource_id, uv_rect);
buffer_updated = true;
}
}
// If resource lock count reached kMaxBuffersInFlight it means we can't
// schedule any more frames right away, in this case we delay sending ack to
// the client and will send it in ReturnResources after OverlayManager will
// process previous update.
if (resource_lock_count_[frame_sink_id] < kMaxBuffersInFlight) {
surface->SendAckToClient();
}
}
return buffer_updated;
}
viz::SurfaceId OverlayProcessorWebView::GetOverlaySurfaceId(
const viz::FrameSinkId& frame_sink_id) {
auto it = overlays_.find(frame_sink_id);
if (it != overlays_.end()) {
return it->second.surface_id;
}
return viz::SurfaceId();
}
bool OverlayProcessorWebView::IsFrameSinkOverlayed(
viz::FrameSinkId frame_sink_id) {
return overlays_.contains(frame_sink_id);
}
OverlayProcessorWebView::ScopedSurfaceControlAvailable::
ScopedSurfaceControlAvailable(OverlayProcessorWebView* processor,
GetSurfaceControlFn surface_getter)
: processor_(processor) {
DCHECK(processor_);
DCHECK(processor_->manager_);
processor_->manager_->get_surface_control_ = surface_getter;
}
OverlayProcessorWebView::ScopedSurfaceControlAvailable::
~ScopedSurfaceControlAvailable() {
processor_->manager_->get_surface_control_ = nullptr;
}
OverlayProcessorWebView::Overlay::Overlay(uint64_t id,
viz::ResourceId resource_id,
int child_id)
: id(id), resource_id(resource_id), child_id(child_id) {}
OverlayProcessorWebView::Overlay::Overlay(const Overlay&) = default;
OverlayProcessorWebView::Overlay::~Overlay() = default;
} // namespace android_webview