blob: d8e419a142d07bc8d32924fad250b30448493ee7 [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 "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "components/performance_manager/scenario_api/performance_scenario_observer.h"
#include "components/performance_manager/scenario_api/performance_scenarios.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_config.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/skia_span_util.h"
#if BUILDFLAG(IS_ANDROID)
#include "ui/android/resources/etc1_utils.h"
#endif
namespace content {
#if BUILDFLAG(IS_ANDROID)
namespace {
BASE_FEATURE(NavigationEntryScreenshotCompression,
base::FEATURE_ENABLED_BY_DEFAULT);
static bool g_disable_compression_for_testing = false;
using CompressionDoneCallback = base::OnceCallback<void(sk_sp<SkPixelRef>)>;
void CompressNavigationScreenshotOnWorkerThread(
SkBitmap bitmap,
bool supports_etc_non_power_of_two,
CompressionDoneCallback done_callback) {
SCOPED_UMA_HISTOGRAM_TIMER("Navigation.GestureTransition.CompressionTime");
TRACE_EVENT0("navigation", "CompressNavigationScreenshotOnWorkerThread");
sk_sp<SkPixelRef> compressed_bitmap = nullptr;
if (base::FeatureList::IsEnabled(ui::kCompressBitmapAtBackgroundPriority)) {
compressed_bitmap = ui::Etc1::CompressBitmapAtBackgroundPriority(
bitmap, supports_etc_non_power_of_two);
} else {
compressed_bitmap =
ui::Etc1::CompressBitmap(bitmap, supports_etc_non_power_of_two);
}
if (compressed_bitmap) {
std::move(done_callback).Run(std::move(compressed_bitmap));
}
}
} // namespace
#endif
// static
const void* const NavigationEntryScreenshot::kUserDataKey =
&NavigationEntryScreenshot::kUserDataKey;
// static
void NavigationEntryScreenshot::SetDisableCompressionForTesting(bool disable) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if BUILDFLAG(IS_ANDROID)
g_disable_compression_for_testing = disable;
#endif
}
NavigationEntryScreenshot::NavigationEntryScreenshot(
const SkBitmap& bitmap,
NavigationTransitionData::UniqueId unique_id,
bool supports_etc_non_power_of_two)
: performance_scenarios::MatchingScenarioObserver(
performance_scenarios::kDefaultIdleScenarios),
bitmap_(cc::UIResourceBitmap(bitmap)),
unique_id_(unique_id),
dimensions_without_compression_(bitmap_->GetSize()),
supports_etc_non_power_of_two_(supports_etc_non_power_of_two) {
CHECK(NavigationTransitionConfig::AreBackForwardTransitionsEnabled());
DCHECK_CURRENTLY_ON(BrowserThread::UI);
SetupCompressionTask(bitmap, supports_etc_non_power_of_two);
}
NavigationEntryScreenshot::NavigationEntryScreenshot(
scoped_refptr<gpu::ClientSharedImage> shared_image,
NavigationTransitionData::UniqueId unique_id,
bool supports_etc_non_power_of_two,
scoped_refptr<viz::RasterContextProvider> context_provider,
ScreenshotCallback screenshot_callback)
: performance_scenarios::MatchingScenarioObserver(
performance_scenarios::kDefaultIdleScenarios),
shared_image_(std::move(shared_image)),
unique_id_(unique_id),
dimensions_without_compression_(shared_image_->size()),
supports_etc_non_power_of_two_(supports_etc_non_power_of_two),
context_provider_(std::move(context_provider)),
screenshot_callback_(std::move(screenshot_callback)) {
DCHECK(NavigationTransitionConfig::AreBackForwardTransitionsEnabled());
DCHECK_CURRENTLY_ON(BrowserThread::UI);
context_provider_->AddObserver(this);
auto observer_list =
performance_scenarios::PerformanceScenarioObserverList::GetForScope(
performance_scenarios::ScenarioScope::kGlobal);
if (observer_list) {
observer_list->AddMatchingObserver(this);
read_back_needed_ = true;
return;
}
ReadBack();
}
NavigationEntryScreenshot::~NavigationEntryScreenshot() {
if (cache_) {
cache_->OnNavigationEntryGone(unique_id_);
}
if (read_back_needed_ || compression_task_) {
auto observer_list =
performance_scenarios::PerformanceScenarioObserverList::GetForScope(
performance_scenarios::ScenarioScope::kGlobal);
if (observer_list) {
observer_list->RemoveMatchingObserver(this);
}
}
ResetContextProvider();
}
cc::UIResourceBitmap NavigationEntryScreenshot::GetBitmap(cc::UIResourceId uid,
bool resource_lost) {
// TODO(liuwilliam): Currently none of the impls of `GetBitmap` uses `uid` or
// `resource_lost`. Consider deleting them from the interface.
return GetBitmap();
}
size_t NavigationEntryScreenshot::SetCache(
NavigationEntryScreenshotCache* cache) {
CHECK(!cache_ || !cache);
cache_ = cache;
if (cache_ && compressed_bitmap_) {
bitmap_.reset();
}
if (shared_image_) {
return SkColorTypeBytesPerPixel(kN32_SkColorType) *
shared_image_->size().Area64();
}
return GetBitmap().SizeInBytes();
}
void NavigationEntryScreenshot::OnScenarioMatchChanged(
performance_scenarios::ScenarioScope scope,
bool matches_pattern) {
if (!matches_pattern) {
return;
}
if (read_back_needed_) {
ReadBack();
read_back_needed_ = false;
performance_scenarios::PerformanceScenarioObserverList::GetForScope(
performance_scenarios::ScenarioScope::kGlobal)
->RemoveMatchingObserver(this);
return;
}
if (compression_task_) {
StartCompression();
performance_scenarios::PerformanceScenarioObserverList::GetForScope(
performance_scenarios::ScenarioScope::kGlobal)
->RemoveMatchingObserver(this);
}
}
void NavigationEntryScreenshot::OnContextLost() {
ResetContextProvider();
}
SkBitmap NavigationEntryScreenshot::GetBitmapForTesting() const {
return GetBitmap().GetBitmapForTesting(); // IN-TEST
}
size_t NavigationEntryScreenshot::CompressedSizeForTesting() const {
return !bitmap_ ? compressed_bitmap_->SizeInBytes() : 0u;
}
void NavigationEntryScreenshot::SetupCompressionTask(
const SkBitmap& bitmap,
bool supports_etc_non_power_of_two) {
#if BUILDFLAG(IS_ANDROID)
if (!base::FeatureList::IsEnabled(kNavigationEntryScreenshotCompression) ||
g_disable_compression_for_testing) {
return;
}
CompressionDoneCallback done_callback = base::BindPostTask(
GetUIThreadTaskRunner(),
base::BindOnce(&NavigationEntryScreenshot::OnCompressionFinished,
weak_factory_.GetWeakPtr()));
compression_task_ =
base::BindOnce(&CompressNavigationScreenshotOnWorkerThread, bitmap,
supports_etc_non_power_of_two, std::move(done_callback));
if (NavigationTransitionConfig::ShouldCompressScreenshotWhenQuiet() &&
!performance_scenarios::CurrentScenariosMatch(
performance_scenarios::ScenarioScope::kGlobal, scenario_pattern())) {
auto observer_list =
performance_scenarios::PerformanceScenarioObserverList::GetForScope(
performance_scenarios::ScenarioScope::kGlobal);
if (observer_list) {
observer_list->AddMatchingObserver(this);
return;
}
}
StartCompression();
#endif
}
void NavigationEntryScreenshot::StartCompression() {
base::ThreadPool::PostTask(FROM_HERE,
{base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
std::move(compression_task_));
}
void NavigationEntryScreenshot::ResetContextProvider() {
if (context_provider_) {
context_provider_->RemoveObserver(this);
context_provider_.reset();
}
}
void NavigationEntryScreenshot::ReadBack() {
TRACE_EVENT("content", "NavigationEntryScreenshot::ReadBack");
DCHECK_CURRENTLY_ON(BrowserThread::UI);
SkImageInfo info = SkImageInfo::MakeN32(shared_image_->size().width(),
shared_image_->size().height(),
shared_image_->alpha_type());
read_back_bitmap_.emplace();
if (!read_back_bitmap_->tryAllocPixels(info)) {
OnReadBack(false);
return;
}
gfx::Point src_point;
if (!context_provider_) {
OnReadBack(false);
}
auto* raster_interface = context_provider_->RasterInterface();
DCHECK(raster_interface);
auto scoped_access = shared_image_->BeginRasterAccess(
raster_interface, shared_image_->creation_sync_token(),
/*readonly=*/true);
raster_interface->ReadbackARGBPixelsAsync(
shared_image_->mailbox(), shared_image_->GetTextureTarget(),
shared_image_->surface_origin(), shared_image_->size(), src_point, info,
info.minRowBytes(),
gfx::SkPixmapToWritableSpan(read_back_bitmap_->pixmap()),
base::BindOnce(&NavigationEntryScreenshot::OnReadBack,
weak_factory_.GetWeakPtr()));
}
void NavigationEntryScreenshot::OnReadBack(bool success) {
TRACE_EVENT("content", "NavigationEntryScreenshot::OnReadBack");
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// The context provider will no longer be used.
ResetContextProvider();
if (!success) {
read_back_bitmap_.reset();
shared_image_.reset();
if (screenshot_callback_) {
SkBitmap override_unused;
screenshot_callback_.Run({}, false, override_unused);
}
return;
}
if (screenshot_callback_) {
SkBitmap bitmap_copy(*read_back_bitmap_);
bitmap_copy.setImmutable();
SkBitmap bitmap_override;
screenshot_callback_.Run(bitmap_copy, true, bitmap_override);
if (!bitmap_override.drawsNothing()) {
read_back_bitmap_ = bitmap_override;
}
}
read_back_bitmap_->setImmutable();
bitmap_ = cc::UIResourceBitmap(*read_back_bitmap_);
shared_image_.reset();
SetupCompressionTask(*read_back_bitmap_, supports_etc_non_power_of_two_);
read_back_bitmap_.reset();
}
void NavigationEntryScreenshot::OnCompressionFinished(
sk_sp<SkPixelRef> compressed_bitmap) {
CHECK(!compressed_bitmap_);
CHECK(bitmap_);
CHECK(compressed_bitmap);
const auto size =
gfx::Size(compressed_bitmap->width(), compressed_bitmap->height());
compressed_bitmap_ = cc::UIResourceBitmap(std::move(compressed_bitmap), size);
TRACE_EVENT("navigation", "NavigationEntryScreenshot::OnCompressionFinished",
"old_size", bitmap_->SizeInBytes(), "new_size",
compressed_bitmap_->SizeInBytes());
// We defer discarding the uncompressed bitmap if there is no cache since it
// may still be in use in the UI.
if (cache_) {
bitmap_.reset();
cache_->OnScreenshotCompressed(unique_id_, GetBitmap().SizeInBytes());
}
}
bool NavigationEntryScreenshot::IsBitmapReady() const {
return bitmap_ || compressed_bitmap_;
}
const cc::UIResourceBitmap& NavigationEntryScreenshot::GetBitmap() const {
return bitmap_ ? *bitmap_ : *compressed_bitmap_;
}
} // namespace content