blob: 0fbd624cb93a13326dca265e6fa0783e2e1c2443 [file] [log] [blame]
// 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 "cc/slim/frame_sink_impl.h"
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/threading/platform_thread.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "cc/slim/constants.h"
#include "cc/slim/delayed_scheduler.h"
#include "cc/slim/frame_sink_impl_client.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/resources/platform_color.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/color_space.h"
namespace cc::slim {
FrameSinkImpl::UploadedUIResource::UploadedUIResource() = default;
FrameSinkImpl::UploadedUIResource::~UploadedUIResource() = default;
FrameSinkImpl::UploadedUIResource::UploadedUIResource(
const UploadedUIResource&) = default;
FrameSinkImpl::UploadedUIResource& FrameSinkImpl::UploadedUIResource::operator=(
const UploadedUIResource&) = default;
FrameSinkImpl::FrameSinkImpl(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink>
compositor_frame_sink_associated_remote,
mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient>
client_receiver,
scoped_refptr<viz::RasterContextProvider> context_provider,
base::PlatformThreadId io_thread_id,
std::unique_ptr<Scheduler> scheduler)
: task_runner_(std::move(task_runner)),
scheduler_(std::move(scheduler)),
pending_compositor_frame_sink_associated_remote_(
std::move(compositor_frame_sink_associated_remote)),
pending_client_receiver_(std::move(client_receiver)),
context_provider_(std::move(context_provider)),
io_thread_id_(io_thread_id) {
scheduler_->Initialize(this);
}
FrameSinkImpl::~FrameSinkImpl() {
// Iterate a copy of the viz_resource_ids since `uploaded_resources_` might
// be modified when `UIResourceReleased()` is called.
// Also note that the DestroySharedImage() call in UIResourceRelease()
// requires the `ClientSharedImage` stored in the to-be-released resource to
// have precisely one reference. Therefore, it is advisable to avoid any
// operation that might alter the `ClientSharedImage`'s refcount, e.g.
// creating a full copy of `uploaded_resources_`.
auto resource_ids = base::MakeFlatSet<viz::ResourceId>(
uploaded_resources_, {},
[](auto& resource_pair) { return resource_pair.second.viz_resource_id; });
for (const auto& uploaded_resource_id : resource_ids) {
resource_provider_.RemoveImportedResource(uploaded_resource_id);
}
resource_provider_.ShutdownAndReleaseAllResources();
}
void FrameSinkImpl::SetLocalSurfaceId(
const viz::LocalSurfaceId& local_surface_id) {
if (local_surface_id_ == local_surface_id) {
return;
}
local_surface_id_ = local_surface_id;
hit_test_region_list_.reset();
}
bool FrameSinkImpl::BindToClient(FrameSinkImplClient* client) {
DCHECK(client);
if (context_provider_) {
context_provider_->AddObserver(this);
auto result = context_provider_->BindToCurrentSequence();
if (result != gpu::ContextResult::kSuccess) {
context_provider_->RemoveObserver(this);
context_provider_ = nullptr;
return false;
}
}
client_ = client;
frame_sink_remote_.Bind(
std::move(pending_compositor_frame_sink_associated_remote_));
frame_sink_remote_.set_disconnect_handler(
base::BindOnce(&FrameSinkImpl::OnContextLost, base::Unretained(this)));
client_receiver_.Bind(std::move(pending_client_receiver_), task_runner_);
frame_sink_ = frame_sink_remote_.get();
frame_sink_->InitializeCompositorFrameSinkType(
viz::mojom::CompositorFrameSinkType::kLayerTree);
#if BUILDFLAG(IS_ANDROID)
std::vector<int32_t> thread_ids;
thread_ids.push_back(base::PlatformThread::CurrentId());
if (io_thread_id_ != base::kInvalidThreadId) {
thread_ids.push_back(io_thread_id_);
}
frame_sink_->SetThreadIds(thread_ids);
#endif
return true;
}
void FrameSinkImpl::OnContextLost() {
client_->DidLoseLayerTreeFrameSink();
}
void FrameSinkImpl::SetNeedsBeginFrame(bool needs_begin_frame) {
if (needs_begin_frame_ == needs_begin_frame) {
return;
}
needs_begin_frame_ = needs_begin_frame;
scheduler_->SetNeedsBeginFrame(needs_begin_frame);
frame_sink_->SetNeedsBeginFrame(needs_begin_frame);
}
void FrameSinkImpl::MaybeCompositeNow() {
scheduler_->MaybeCompositeNow();
}
void FrameSinkImpl::UploadUIResource(cc::UIResourceId resource_id,
cc::UIResourceBitmap resource_bitmap) {
gfx::Size size = resource_bitmap.GetSize();
TRACE_EVENT1("cc", "slim::FrameSinkImpl::UploadUIResource", "size",
size.ToString());
const gpu::Capabilities& caps = context_provider_->ContextCapabilities();
if (size.width() > caps.max_texture_size ||
size.height() > caps.max_texture_size) {
LOG(ERROR) << "Size exceeds max texture size";
return;
}
viz::SharedImageFormat format = viz::SinglePlaneFormat::kRGBA_8888;
switch (resource_bitmap.GetFormat()) {
case cc::UIResourceBitmap::RGBA8:
format = viz::PlatformColor::BestSupportedTextureFormat(caps);
break;
case cc::UIResourceBitmap::ALPHA_8:
format = viz::SinglePlaneFormat::kALPHA_8;
break;
case cc::UIResourceBitmap::ETC1:
format = viz::SinglePlaneFormat::kETC1;
break;
}
// CreateSharedImage() with initial pixels doesn't support specifying
// non-standard stride so data must be exactly the minimum size required to
// hold all pixels.
DCHECK_EQ(format.EstimatedSizeInBytes(size), resource_bitmap.SizeInBytes());
UploadedUIResource uploaded_resource;
auto* sii = context_provider_->SharedImageInterface();
constexpr gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB();
uint32_t shared_image_usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
uploaded_resource.shared_image = sii->CreateSharedImage(
format, resource_bitmap.GetSize(), color_space, kTopLeft_GrSurfaceOrigin,
kPremul_SkAlphaType, shared_image_usage, "SlimCompositorUIResource",
base::span<const uint8_t>(resource_bitmap.GetPixels(),
resource_bitmap.SizeInBytes()));
CHECK(uploaded_resource.shared_image);
gpu::SyncToken sync_token = sii->GenUnverifiedSyncToken();
GLenum texture_target = gpu::GetBufferTextureTarget(
gfx::BufferUsage::SCANOUT,
viz::SinglePlaneSharedImageFormatToBufferFormat(format), caps);
uploaded_resource.viz_resource_id = resource_provider_.ImportResource(
viz::TransferableResource::MakeGpu(
uploaded_resource.shared_image, texture_target, sync_token,
resource_bitmap.GetSize(), format, /*is_overlay_candidate=*/false,
viz::TransferableResource::ResourceSource::kUI),
base::BindOnce(&FrameSinkImpl::UIResourceReleased, base::Unretained(this),
resource_id));
uploaded_resource.size = resource_bitmap.GetSize();
uploaded_resource.is_opaque = resource_bitmap.GetOpaque();
DCHECK(!uploaded_resources_.contains(resource_id));
uploaded_resources_.emplace(resource_id, uploaded_resource);
}
void FrameSinkImpl::UIResourceReleased(cc::UIResourceId ui_resource_id,
const gpu::SyncToken& sync_token,
bool is_lost) {
auto itr = uploaded_resources_.find(ui_resource_id);
DCHECK(itr != uploaded_resources_.end());
auto* sii = context_provider_->SharedImageInterface();
sii->DestroySharedImage(sync_token, std::move(itr->second.shared_image));
uploaded_resources_.erase(itr);
}
void FrameSinkImpl::MarkUIResourceForDeletion(cc::UIResourceId resource_id) {
auto itr = uploaded_resources_.find(resource_id);
if (itr == uploaded_resources_.end()) {
return;
}
resource_provider_.RemoveImportedResource(itr->second.viz_resource_id);
}
viz::ResourceId FrameSinkImpl::GetVizResourceId(cc::UIResourceId resource_id) {
auto itr = uploaded_resources_.find(resource_id);
if (itr == uploaded_resources_.end()) {
return viz::kInvalidResourceId;
}
return itr->second.viz_resource_id;
}
bool FrameSinkImpl::IsUIResourceOpaque(cc::UIResourceId resource_id) {
auto it = uploaded_resources_.find(resource_id);
if (it == uploaded_resources_.end()) {
return true;
}
return it->second.is_opaque;
}
gfx::Size FrameSinkImpl::GetUIResourceSize(cc::UIResourceId resource_id) {
auto it = uploaded_resources_.find(resource_id);
if (it == uploaded_resources_.end()) {
return gfx::Size();
}
return it->second.size;
}
int FrameSinkImpl::GetMaxTextureSize() const {
if (context_provider_) {
return context_provider_->ContextCapabilities().max_texture_size;
}
return kSoftwareMaxTextureSize;
}
void FrameSinkImpl::DidReceiveCompositorFrameAck(
std::vector<viz::ReturnedResource> resources) {
ReclaimResources(std::move(resources));
DCHECK_GT(num_unacked_frames_, 0u);
num_unacked_frames_--;
if (!num_unacked_frames_) {
scheduler_->SetIsSwapThrottled(false);
}
client_->DidReceiveCompositorFrameAck();
}
void FrameSinkImpl::ReclaimResources(
std::vector<viz::ReturnedResource> resources) {
resource_provider_.ReceiveReturnsFromParent(std::move(resources));
}
void FrameSinkImpl::OnBeginFrame(
const viz::BeginFrameArgs& begin_frame_args,
const viz::FrameTimingDetailsMap& timing_details,
bool frame_ack,
std::vector<viz::ReturnedResource> resources) {
if (features::IsOnBeginFrameAcksEnabled()) {
if (frame_ack) {
DidReceiveCompositorFrameAck(std::move(resources));
} else if (!resources.empty()) {
ReclaimResources(std::move(resources));
}
}
// Note order here is expected to be in order w.r.t viz::FrameTokenGT. This
// mostly holds because `FrameTimingDetailsMap` is a flat_map which is sorted.
// However this doesn't hold when frame token wraps.
for (const auto& pair : timing_details) {
client_->DidPresentCompositorFrame(pair.first, pair.second);
}
scheduler_->OnBeginFrameFromViz(begin_frame_args);
}
bool FrameSinkImpl::DoBeginFrame(const viz::BeginFrameArgs& begin_frame_args) {
if (num_unacked_frames_) {
return false;
}
if (!local_surface_id_.is_valid()) {
return false;
}
TRACE_EVENT0("cc", "slim::FrameSinkImpl::DoBeginFrame");
viz::CompositorFrame frame;
base::flat_set<viz::ResourceId> viz_resource_ids;
viz::HitTestRegionList hit_test_region_list;
if (!client_->BeginFrame(begin_frame_args, frame, viz_resource_ids,
hit_test_region_list)) {
return false;
}
if (local_surface_id_ == last_submitted_local_surface_id_) {
DCHECK_EQ(last_submitted_device_scale_factor_, frame.device_scale_factor());
DCHECK_EQ(last_submitted_size_in_pixels_.height(),
frame.size_in_pixels().height());
DCHECK_EQ(last_submitted_size_in_pixels_.width(),
frame.size_in_pixels().width());
}
resource_provider_.PrepareSendToParent(std::move(viz_resource_ids).extract(),
&frame.resource_list,
context_provider_.get());
bool send_new_hit_test_region_list = false;
if (!hit_test_region_list_ ||
!viz::HitTestRegionList::IsEqual(*hit_test_region_list_,
hit_test_region_list)) {
send_new_hit_test_region_list = true;
hit_test_region_list_ = std::move(hit_test_region_list);
}
{
TRACE_EVENT(
"viz,benchmark,graphics.pipeline", "Graphics.Pipeline",
perfetto::Flow::Global(begin_frame_args.trace_id),
[&](perfetto::EventContext ctx) {
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* data = event->set_chrome_graphics_pipeline();
data->set_step(perfetto::protos::pbzero::ChromeGraphicsPipeline::
StepName::STEP_SUBMIT_COMPOSITOR_FRAME);
});
frame_sink_->SubmitCompositorFrame(
local_surface_id_, std::move(frame),
send_new_hit_test_region_list ? hit_test_region_list_ : std::nullopt,
0);
}
num_unacked_frames_++;
if (num_unacked_frames_ == 1) {
scheduler_->SetIsSwapThrottled(true);
}
client_->DidSubmitCompositorFrame();
return true;
}
void FrameSinkImpl::SendDidNotProduceFrame(
const viz::BeginFrameArgs& begin_frame_args) {
TRACE_EVENT(
"viz,benchmark,graphics.pipeline", "Graphics.Pipeline",
perfetto::Flow::Global(begin_frame_args.trace_id),
[&](perfetto::EventContext ctx) {
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* data = event->set_chrome_graphics_pipeline();
data->set_step(perfetto::protos::pbzero::ChromeGraphicsPipeline::
StepName::STEP_DID_NOT_PRODUCE_FRAME);
});
frame_sink_->DidNotProduceFrame(viz::BeginFrameAck(begin_frame_args, false));
}
} // namespace cc::slim