blob: 444739eba8ea4177e89ee9b433ebeb0cd77dc851 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "cc/layers/texture_layer.h"
#include "cc/resources/cross_thread_shared_bitmap.h"
#include "components/viz/common/resources/bitmap_allocation.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/common/resources/shared_bitmap.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/canvas_color_params.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/color_behavior.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/image_orientation.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "ui/gfx/geometry/size.h"
namespace blink {
namespace {
scoped_refptr<StaticBitmapImage> MakeAccelerated(
const scoped_refptr<StaticBitmapImage>& source,
base::WeakPtr<WebGraphicsContext3DProviderWrapper>
context_provider_wrapper) {
#if BUILDFLAG(IS_MAC)
// On MacOS, if |source| is not an overlay candidate, it is worth copying it
// to a new buffer that is an overlay candidate, even when |source| is
// already on the GPU.
if (source->IsOverlayCandidate()) {
#else
if (source->IsTextureBacked()) {
#endif
return source;
}
auto paint_image = source->PaintImageForCurrentFrame();
auto image_info = paint_image.GetSkImageInfo().makeWH(
source->Size().width(), source->Size().height());
// Always request gpu::SHARED_IMAGE_USAGE_SCANOUT when using gpu compositing,
// if possible. This is safe because the prerequisite capabilities are checked
// downstream in CanvasResourceProvider::CreateSharedImageProvider.
auto provider = CanvasResourceProvider::CreateSharedImageProvider(
image_info, cc::PaintFlags::FilterQuality::kLow,
CanvasResourceProvider::ShouldInitialize::kNo, context_provider_wrapper,
RasterMode::kGPU, source->IsOriginTopLeft(),
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT);
if (!provider || !provider->IsAccelerated())
return nullptr;
cc::PaintFlags paint;
paint.setBlendMode(SkBlendMode::kSrc);
provider->Canvas()->drawImage(paint_image, 0, 0, SkSamplingOptions(), &paint);
return provider->Snapshot();
}
} // namespace
ImageLayerBridge::ImageLayerBridge(OpacityMode opacity_mode)
: opacity_mode_(opacity_mode) {
layer_ = cc::TextureLayer::CreateForMailbox(this);
layer_->SetIsDrawable(true);
layer_->SetHitTestable(true);
layer_->SetNearestNeighbor(filter_quality_ ==
cc::PaintFlags::FilterQuality::kNone);
if (opacity_mode_ == kOpaque) {
layer_->SetContentsOpaque(true);
layer_->SetBlendBackgroundColor(false);
}
}
ImageLayerBridge::~ImageLayerBridge() {
if (!disposed_)
Dispose();
}
void ImageLayerBridge::SetImage(scoped_refptr<StaticBitmapImage> image) {
if (disposed_)
return;
// There could be the case that the current PaintImage is null, meaning
// that something went wrong during the creation of the image and we should
// not try and setImage with it
if (image && !image->PaintImageForCurrentFrame())
return;
image_ = std::move(image);
if (image_) {
if (opacity_mode_ == kNonOpaque) {
layer_->SetContentsOpaque(image_->CurrentFrameKnownToBeOpaque());
layer_->SetBlendBackgroundColor(!image_->CurrentFrameKnownToBeOpaque());
}
if (opacity_mode_ == kOpaque) {
// If we in opaque mode but image might have transparency we need to
// ensure its opacity is not used.
layer_->SetForceTextureToOpaque(!image_->CurrentFrameKnownToBeOpaque());
}
if (!has_presented_since_last_set_image_ && image_->IsTextureBacked()) {
// If the layer bridge is not presenting, the GrContext may not be getting
// flushed regularly. The flush is normally triggered inside the
// m_image->EnsureMailbox() call of
// ImageLayerBridge::PrepareTransferableResource. To prevent a potential
// memory leak we must flush the GrContext here.
image_->PaintImageForCurrentFrame().FlushPendingSkiaOps();
}
}
has_presented_since_last_set_image_ = false;
}
void ImageLayerBridge::SetUV(const gfx::PointF& left_top,
const gfx::PointF& right_bottom) {
if (disposed_)
return;
layer_->SetUV(left_top, right_bottom);
}
void ImageLayerBridge::Dispose() {
if (layer_) {
layer_->ClearClient();
layer_ = nullptr;
}
image_ = nullptr;
disposed_ = true;
}
bool ImageLayerBridge::PrepareTransferableResource(
cc::SharedBitmapIdRegistrar* bitmap_registrar,
viz::TransferableResource* out_resource,
viz::ReleaseCallback* out_release_callback) {
if (disposed_)
return false;
if (!image_)
return false;
if (has_presented_since_last_set_image_)
return false;
has_presented_since_last_set_image_ = true;
const bool gpu_compositing = SharedGpuContext::IsGpuCompositingEnabled();
const ImageOrientation origin = image_->IsOriginTopLeft()
? ImageOrientationEnum::kOriginTopLeft
: ImageOrientationEnum::kOriginBottomLeft;
const bool image_flipped = image_->CurrentFrameOrientation() != origin;
layer_->SetFlipped(image_flipped);
if (gpu_compositing) {
scoped_refptr<StaticBitmapImage> image_for_compositor =
MakeAccelerated(image_, SharedGpuContext::ContextProviderWrapper());
if (!image_for_compositor || !image_for_compositor->ContextProvider())
return false;
const gfx::Size size(image_for_compositor->width(),
image_for_compositor->height());
uint32_t filter = filter_quality_ == cc::PaintFlags::FilterQuality::kNone
? GL_NEAREST
: GL_LINEAR;
auto mailbox_holder = image_for_compositor->GetMailboxHolder();
if (mailbox_holder.mailbox.IsZero()) {
// This can happen, for example, if an ImageBitmap is produced from a
// WebGL-rendered OffscreenCanvas and then the WebGL context is forcibly
// lost. This seems to be the only reliable point where this can be
// detected.
return false;
}
auto* sii = image_for_compositor->ContextProvider()->SharedImageInterface();
bool is_overlay_candidate = sii->UsageForMailbox(mailbox_holder.mailbox) &
gpu::SHARED_IMAGE_USAGE_SCANOUT;
SkColorType color_type = image_for_compositor->GetSkColorInfo().colorType();
*out_resource = viz::TransferableResource::MakeGpu(
mailbox_holder.mailbox, filter, mailbox_holder.texture_target,
mailbox_holder.sync_token, size,
viz::SkColorTypeToResourceFormat(color_type), is_overlay_candidate);
// If the transferred ImageBitmap contained in this ImageLayerBridge was
// originated in a WebGPU context, we need to set the layer to be flipped.
// Canvas2D and WebGL contexts handle this aspect internally, whereas
// WebGPU does not.
if (sii->UsageForMailbox(mailbox_holder.mailbox) &
gpu::SHARED_IMAGE_USAGE_WEBGPU_SWAP_CHAIN_TEXTURE) {
// Using image_for_compositor->IsOriginTopLeft() and remove
// implementation of sii->UsageForMailbox()?
layer_->SetFlipped(false);
}
auto func = WTF::BindOnce(&ImageLayerBridge::ResourceReleasedGpu,
WrapWeakPersistent(this),
std::move(image_for_compositor));
*out_release_callback = std::move(func);
} else {
// Readback if needed and retain the readback in image_ to prevent future
// readbacks
image_ = image_->MakeUnaccelerated();
if (!image_)
return false;
sk_sp<SkImage> sk_image =
image_->PaintImageForCurrentFrame().GetSwSkImage();
if (!sk_image)
return false;
const gfx::Size size(image_->width(), image_->height());
// Always convert to N32 format. This is a constraint of the software
// compositor.
constexpr SkColorType dst_color_type = kN32_SkColorType;
// TODO(vasilyt): this used to be
// viz::SkColorTypeToResourceFormat(dst_color_type), but on some
// platforms (including Mac), kN32_SkColorType is BGRA8888 which
// is disallowed as a bitmap format. Deeper refactorings are
// needed to fix this properly; in the meantime, force the use of
// viz::RGBA_8888 as the resource format. This addresses assertion
// failures when serializing these bitmaps to the GPU process.
viz::ResourceFormat resource_format = viz::RGBA_8888;
RegisteredBitmap registered =
CreateOrRecycleBitmap(size, resource_format, bitmap_registrar);
SkImageInfo dst_info =
SkImageInfo::Make(size.width(), size.height(), dst_color_type,
kPremul_SkAlphaType, sk_image->refColorSpace());
void* pixels = registered.bitmap->memory();
// Copy from SkImage into SharedMemory owned by |registered|.
if (!sk_image->readPixels(dst_info, pixels, dst_info.minRowBytes(), 0, 0))
return false;
*out_resource = viz::TransferableResource::MakeSoftware(
registered.bitmap->id(), size, resource_format);
out_resource->color_space = sk_image->colorSpace()
? gfx::ColorSpace(*sk_image->colorSpace())
: gfx::ColorSpace::CreateSRGB();
auto func = WTF::BindOnce(&ImageLayerBridge::ResourceReleasedSoftware,
WrapWeakPersistent(this), std::move(registered));
*out_release_callback = std::move(func);
}
return true;
}
ImageLayerBridge::RegisteredBitmap ImageLayerBridge::CreateOrRecycleBitmap(
const gfx::Size& size,
viz::ResourceFormat format,
cc::SharedBitmapIdRegistrar* bitmap_registrar) {
auto* it = std::remove_if(
recycled_bitmaps_.begin(), recycled_bitmaps_.end(),
[&size, &format](const RegisteredBitmap& registered) {
unsigned src_bytes_per_pixel =
viz::BitsPerPixel(registered.bitmap->format()) / 8;
unsigned target_bytes_per_pixel = viz::BitsPerPixel(format) / 8;
return (registered.bitmap->size().GetArea() * src_bytes_per_pixel !=
size.GetArea() * target_bytes_per_pixel);
});
recycled_bitmaps_.Shrink(
static_cast<wtf_size_t>(it - recycled_bitmaps_.begin()));
if (!recycled_bitmaps_.empty()) {
RegisteredBitmap registered = std::move(recycled_bitmaps_.back());
recycled_bitmaps_.pop_back();
DCHECK(registered.bitmap->size() == size);
return registered;
}
// There are no bitmaps to recycle so allocate a new one.
viz::SharedBitmapId id = viz::SharedBitmap::GenerateId();
base::MappedReadOnlyRegion shm =
viz::bitmap_allocation::AllocateSharedBitmap(size, format);
RegisteredBitmap registered;
registered.bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
id, std::move(shm), size, format);
registered.registration =
bitmap_registrar->RegisterSharedBitmapId(id, registered.bitmap);
return registered;
}
void ImageLayerBridge::ResourceReleasedGpu(
scoped_refptr<StaticBitmapImage> image,
const gpu::SyncToken& token,
bool lost_resource) {
if (image && image->IsValid()) {
DCHECK(image->IsTextureBacked());
if (token.HasData() && image->ContextProvider() &&
image->ContextProvider()->InterfaceBase()) {
image->ContextProvider()->InterfaceBase()->WaitSyncTokenCHROMIUM(
token.GetConstData());
}
}
// let 'image' go out of scope to release gpu resources.
}
void ImageLayerBridge::ResourceReleasedSoftware(
RegisteredBitmap registered,
const gpu::SyncToken& sync_token,
bool lost_resource) {
DCHECK(!sync_token.HasData()); // No sync tokens for software resources.
if (!disposed_ && !lost_resource)
recycled_bitmaps_.push_back(std::move(registered));
}
cc::Layer* ImageLayerBridge::CcLayer() const {
return layer_.get();
}
ImageLayerBridge::RegisteredBitmap::RegisteredBitmap() = default;
ImageLayerBridge::RegisteredBitmap::RegisteredBitmap(RegisteredBitmap&& other) =
default;
ImageLayerBridge::RegisteredBitmap& ImageLayerBridge::RegisteredBitmap::
operator=(RegisteredBitmap&& other) = default;
} // namespace blink