blob: fd548d858962ee0fe69f840b6978e6efe1629298 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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/core/html/canvas/canvas_rendering_context_host.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_image_encode_options.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
CanvasRenderingContextHost::CanvasRenderingContextHost(HostType host_type)
: host_type_(host_type) {}
void CanvasRenderingContextHost::RecordCanvasSizeToUMA(const IntSize& size) {
if (did_record_canvas_size_to_uma_)
return;
did_record_canvas_size_to_uma_ = true;
if (host_type_ == kCanvasHost) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Blink.Canvas.SqrtNumberOfPixels",
std::sqrt(size.Area()), 1, 5000, 100);
} else if (host_type_ == kOffscreenCanvasHost) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Blink.OffscreenCanvas.SqrtNumberOfPixels",
std::sqrt(size.Area()), 1, 5000, 100);
} else {
NOTREACHED();
}
}
scoped_refptr<StaticBitmapImage>
CanvasRenderingContextHost::CreateTransparentImage(const IntSize& size) const {
if (!IsValidImageSize(size))
return nullptr;
CanvasColorParams color_params = CanvasColorParams();
if (RenderingContext())
color_params = RenderingContext()->CanvasRenderingContextColorParams();
SkImageInfo info = SkImageInfo::Make(
size.Width(), size.Height(), color_params.GetSkColorType(),
kPremul_SkAlphaType, color_params.GetSkColorSpace());
sk_sp<SkSurface> surface =
SkSurface::MakeRaster(info, info.minRowBytes(), nullptr);
if (!surface)
return nullptr;
return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
}
void CanvasRenderingContextHost::Commit(scoped_refptr<CanvasResource>,
const SkIRect&) {
NOTIMPLEMENTED();
}
bool CanvasRenderingContextHost::IsPaintable() const {
return (RenderingContext() && RenderingContext()->IsPaintable()) ||
IsValidImageSize(Size());
}
void CanvasRenderingContextHost::RestoreCanvasMatrixClipStack(
cc::PaintCanvas* canvas) const {
if (RenderingContext())
RenderingContext()->RestoreCanvasMatrixClipStack(canvas);
}
bool CanvasRenderingContextHost::Is3d() const {
return RenderingContext() && RenderingContext()->Is3d();
}
bool CanvasRenderingContextHost::IsRenderingContext2D() const {
return RenderingContext() && RenderingContext()->IsRenderingContext2D();
}
CanvasResourceProvider*
CanvasRenderingContextHost::GetOrCreateCanvasResourceProvider(
RasterModeHint hint) {
return GetOrCreateCanvasResourceProviderImpl(hint);
}
CanvasResourceProvider*
CanvasRenderingContextHost::GetOrCreateCanvasResourceProviderImpl(
RasterModeHint hint) {
if (!ResourceProvider() && !did_fail_to_create_resource_provider_) {
if (IsValidImageSize(Size())) {
if (Is3d()) {
CreateCanvasResourceProvider3D();
} else {
CreateCanvasResourceProvider2D(hint);
}
}
if (!ResourceProvider())
did_fail_to_create_resource_provider_ = true;
}
return ResourceProvider();
}
void CanvasRenderingContextHost::CreateCanvasResourceProvider3D() {
DCHECK(Is3d());
base::WeakPtr<CanvasResourceDispatcher> dispatcher =
GetOrCreateResourceDispatcher()
? GetOrCreateResourceDispatcher()->GetWeakPtr()
: nullptr;
std::unique_ptr<CanvasResourceProvider> provider;
const CanvasResourceParams resource_params =
ColorParams().GetAsResourceParams();
if (SharedGpuContext::IsGpuCompositingEnabled() && LowLatencyEnabled()) {
// If LowLatency is enabled, we need a resource that is able to perform well
// in such mode. It will first try a PassThrough provider and, if that is
// not possible, it will try a SharedImage with the appropriate flags.
if ((RenderingContext() && RenderingContext()->UsingSwapChain()) ||
RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
// If either SwapChain is enabled or WebGLImage mode is enabled, we can
// try a passthrough provider.
DCHECK(LowLatencyEnabled());
provider = CanvasResourceProvider::CreatePassThroughProvider(
Size(), FilterQuality(), resource_params,
SharedGpuContext::ContextProviderWrapper(), dispatcher,
RenderingContext()->IsOriginTopLeft());
}
if (!provider) {
// If PassThrough failed, try a SharedImage with usage display enabled,
// and if WebGLImageChromium is enabled, add concurrent read write and
// usage scanout (overlay).
uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
shared_image_usage_flags |=
gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
}
provider = CanvasResourceProvider::CreateSharedImageProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
RenderingContext()->IsOriginTopLeft(), shared_image_usage_flags);
}
} else if (SharedGpuContext::IsGpuCompositingEnabled()) {
// If there is no LawLatency mode, and GPU is enabled, will try a GPU
// SharedImage that should support Usage Display and probably Usage Canbout
// if WebGLImageChromium is enabled.
uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
}
provider = CanvasResourceProvider::CreateSharedImageProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
RenderingContext()->IsOriginTopLeft(), shared_image_usage_flags);
}
// If either of the other modes failed and / or it was not possible to do, we
// will backup with a SharedBitmap, and if that was not possible with a Bitmap
// provider.
if (!provider) {
provider = CanvasResourceProvider::CreateSharedBitmapProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear, dispatcher);
}
if (!provider) {
provider = CanvasResourceProvider::CreateBitmapProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear);
}
ReplaceResourceProvider(std::move(provider));
if (ResourceProvider() && ResourceProvider()->IsValid()) {
base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
ResourceProvider()->IsAccelerated());
base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
ResourceProvider()->GetType());
}
}
void CanvasRenderingContextHost::CreateCanvasResourceProvider2D(
RasterModeHint hint) {
DCHECK(IsRenderingContext2D());
base::WeakPtr<CanvasResourceDispatcher> dispatcher =
GetOrCreateResourceDispatcher()
? GetOrCreateResourceDispatcher()->GetWeakPtr()
: nullptr;
std::unique_ptr<CanvasResourceProvider> provider;
const CanvasResourceParams resource_params =
ColorParams().GetAsResourceParams();
const bool use_gpu =
hint == RasterModeHint::kPreferGPU && ShouldAccelerate2dContext();
// It is important to not use the context's IsOriginTopLeft() here
// because that denotes the current state and could change after the
// new resource provider is created e.g. due to switching between
// unaccelerated and accelerated modes during tab switching.
const bool is_origin_top_left = !use_gpu || LowLatencyEnabled();
if (use_gpu && LowLatencyEnabled()) {
// If we can use the gpu and low latency is enabled, we will try to use a
// SwapChain if possible.
if (base::FeatureList::IsEnabled(features::kLowLatencyCanvas2dSwapChain)) {
provider = CanvasResourceProvider::CreateSwapChainProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), dispatcher,
is_origin_top_left);
}
// If SwapChain failed or it was not possible, we will try a SharedImage
// with a set of flags trying to add Usage Display and Usage Scanout and
// Concurrent Read and Write if possible.
if (!provider) {
uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled() ||
base::FeatureList::IsEnabled(
features::kLowLatencyCanvas2dImageChromium)) {
shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
shared_image_usage_flags |=
gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
}
provider = CanvasResourceProvider::CreateSharedImageProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
is_origin_top_left, shared_image_usage_flags);
}
} else if (use_gpu) {
// First try to be optimized for displaying on screen. In the case we are
// hardware compositing, we also try to enable the usage of the image as
// scanout buffer (overlay)
uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled())
shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
provider = CanvasResourceProvider::CreateSharedImageProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
is_origin_top_left, shared_image_usage_flags);
} else if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled()) {
const uint32_t shared_image_usage_flags =
gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
provider = CanvasResourceProvider::CreateSharedImageProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear,
SharedGpuContext::ContextProviderWrapper(), RasterMode::kCPU,
is_origin_top_left, shared_image_usage_flags);
}
// If either of the other modes failed and / or it was not possible to do, we
// will backup with a SharedBitmap, and if that was not possible with a Bitmap
// provider.
if (!provider) {
provider = CanvasResourceProvider::CreateSharedBitmapProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear, dispatcher);
}
if (!provider) {
provider = CanvasResourceProvider::CreateBitmapProvider(
Size(), FilterQuality(), resource_params,
CanvasResourceProvider::ShouldInitialize::kCallClear);
}
ReplaceResourceProvider(std::move(provider));
if (ResourceProvider()) {
if (ResourceProvider()->IsValid()) {
base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
ResourceProvider()->IsAccelerated());
base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
ResourceProvider()->GetType());
}
ResourceProvider()->SetFilterQuality(FilterQuality());
ResourceProvider()->SetResourceRecyclingEnabled(true);
}
}
CanvasColorParams CanvasRenderingContextHost::ColorParams() const {
if (RenderingContext())
return RenderingContext()->CanvasRenderingContextColorParams();
return CanvasColorParams();
}
ScriptPromise CanvasRenderingContextHost::convertToBlob(
ScriptState* script_state,
const ImageEncodeOptions* options,
ExceptionState& exception_state,
const CanvasRenderingContext* const context) {
WTF::String object_name = "Canvas";
if (this->IsOffscreenCanvas())
object_name = "OffscreenCanvas";
std::stringstream error_msg;
if (this->IsOffscreenCanvas() && this->IsNeutered()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"OffscreenCanvas object is detached.");
return ScriptPromise();
}
if (!this->OriginClean()) {
error_msg << "Tainted " << object_name << " may not be exported.";
exception_state.ThrowSecurityError(error_msg.str().c_str());
return ScriptPromise();
}
// It's possible that there are recorded commands that have not been resolved
// Finalize frame will be called in GetImage, but if there's no
// resourceProvider yet then the IsPaintable check will fail
if (RenderingContext())
RenderingContext()->FinalizeFrame();
if (!this->IsPaintable() || Size().IsEmpty()) {
error_msg << "The size of " << object_name << " is zero.";
exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
error_msg.str().c_str());
return ScriptPromise();
}
if (!RenderingContext()) {
error_msg << object_name << " has no rendering context.";
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
error_msg.str().c_str());
return ScriptPromise();
}
base::TimeTicks start_time = base::TimeTicks::Now();
scoped_refptr<StaticBitmapImage> image_bitmap =
RenderingContext()->GetImage();
if (image_bitmap) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
CanvasAsyncBlobCreator::ToBlobFunctionType function_type =
CanvasAsyncBlobCreator::kHTMLCanvasConvertToBlobPromise;
if (this->IsOffscreenCanvas()) {
function_type =
CanvasAsyncBlobCreator::kOffscreenCanvasConvertToBlobPromise;
}
auto* execution_context = ExecutionContext::From(script_state);
auto* async_creator = MakeGarbageCollected<CanvasAsyncBlobCreator>(
image_bitmap, options, function_type, start_time, execution_context,
IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kCanvasReadback)
? IdentifiabilityInputDigest(context)
: 0,
resolver);
async_creator->ScheduleAsyncBlobCreation(options->quality());
return resolver->Promise();
}
exception_state.ThrowDOMException(DOMExceptionCode::kNotReadableError,
"Readback of the source image has failed.");
return ScriptPromise();
}
bool CanvasRenderingContextHost::IsOffscreenCanvas() const {
return host_type_ == kOffscreenCanvasHost;
}
IdentifiableToken CanvasRenderingContextHost::IdentifiabilityInputDigest(
const CanvasRenderingContext* const context) const {
const uint64_t context_digest =
context ? context->IdentifiableTextToken().ToUkmMetricValue() : 0;
const IdentifiabilityPaintOpDigest* const identifiability_paintop_digest =
ResourceProvider()
? &(ResourceProvider()->GetIdentifiablityPaintOpDigest())
: nullptr;
const uint64_t canvas_digest =
identifiability_paintop_digest
? identifiability_paintop_digest->GetToken().ToUkmMetricValue()
: 0;
const uint64_t context_type =
context ? context->GetContextType()
: CanvasRenderingContext::kContextTypeUnknown;
const bool encountered_skipped_ops =
(context && context->IdentifiabilityEncounteredSkippedOps()) ||
(identifiability_paintop_digest &&
identifiability_paintop_digest->encountered_skipped_ops());
const bool encountered_sensitive_ops =
context && context->IdentifiabilityEncounteredSensitiveOps();
const bool encountered_partially_digested_image =
identifiability_paintop_digest &&
identifiability_paintop_digest->encountered_partially_digested_image();
// Bits [0-3] are the context type, bits [4-6] are skipped ops, sensitive
// ops, and partial image ops bits, respectively. The remaining bits are
// for the canvas digest.
uint64_t final_digest =
((context_digest ^ canvas_digest) << 7) | context_type;
if (encountered_skipped_ops)
final_digest |= IdentifiableSurface::CanvasTaintBit::kSkipped;
if (encountered_sensitive_ops)
final_digest |= IdentifiableSurface::CanvasTaintBit::kSensitive;
if (encountered_partially_digested_image)
final_digest |= IdentifiableSurface::CanvasTaintBit::kPartiallyDigested;
return final_digest;
}
} // namespace blink