blob: bb38c588d84af9ca1ceba1b0698f93e8560717de [file] [log] [blame]
// Copyright 2015 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/offscreencanvas/offscreen_canvas.h"
#include <memory>
#include <utility>
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/css/css_font_selector.h"
#include "third_party/blink/renderer/core/css/offscreen_font_selector.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_factory.h"
#include "third_party/blink/renderer/core/html/canvas/image_data.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/image.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/image-encoders/image_encoder_utils.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
OffscreenCanvas::OffscreenCanvas(const IntSize& size) : size_(size) {}
OffscreenCanvas* OffscreenCanvas::Create(unsigned width, unsigned height) {
return new OffscreenCanvas(
IntSize(clampTo<int>(width), clampTo<int>(height)));
}
OffscreenCanvas::~OffscreenCanvas() = default;
void OffscreenCanvas::Commit(scoped_refptr<CanvasResource> canvas_resource,
const SkIRect& damage_rect) {
if (!HasPlaceholderCanvas())
return;
base::TimeTicks commit_start_time = WTF::CurrentTimeTicks();
current_frame_damage_rect_.join(damage_rect);
GetOrCreateResourceDispatcher()->DispatchFrameSync(
canvas_resource->Bitmap(), commit_start_time, current_frame_damage_rect_);
current_frame_damage_rect_ = SkIRect::MakeEmpty();
}
void OffscreenCanvas::Dispose() {
if (context_) {
context_->DetachHost();
context_ = nullptr;
}
if (HasPlaceholderCanvas() && GetTopExecutionContext() &&
GetTopExecutionContext()->IsWorkerGlobalScope()) {
WorkerAnimationFrameProvider* animation_frame_provider =
ToWorkerGlobalScope(GetTopExecutionContext())
->GetAnimationFrameProvider();
if (animation_frame_provider) {
animation_frame_provider->DeregisterOffscreenCanvas(this);
}
}
}
void OffscreenCanvas::SetPlaceholderCanvasId(DOMNodeId canvas_id) {
placeholder_canvas_id_ = canvas_id;
if (GetTopExecutionContext() &&
GetTopExecutionContext()->IsWorkerGlobalScope()) {
WorkerAnimationFrameProvider* animation_frame_provider =
ToWorkerGlobalScope(GetTopExecutionContext())
->GetAnimationFrameProvider();
if (animation_frame_provider) {
animation_frame_provider->RegisterOffscreenCanvas(this);
}
}
}
void OffscreenCanvas::setWidth(unsigned width) {
IntSize new_size = size_;
new_size.SetWidth(clampTo<int>(width));
SetSize(new_size);
}
void OffscreenCanvas::setHeight(unsigned height) {
IntSize new_size = size_;
new_size.SetHeight(clampTo<int>(height));
SetSize(new_size);
}
void OffscreenCanvas::SetSize(const IntSize& size) {
if (context_) {
if (context_->Is3d()) {
if (size != size_)
context_->Reshape(size.Width(), size.Height());
} else if (context_->Is2d()) {
context_->Reset();
origin_clean_ = true;
}
}
size_ = size;
if (frame_dispatcher_) {
frame_dispatcher_->Reshape(size_);
}
current_frame_damage_rect_ = SkIRect::MakeWH(size_.Width(), size_.Height());
if (context_) {
context_->DidDraw();
}
}
void OffscreenCanvas::SetNeutered() {
DCHECK(!context_);
is_neutered_ = true;
size_.SetWidth(0);
size_.SetHeight(0);
}
ImageBitmap* OffscreenCanvas::transferToImageBitmap(
ScriptState* script_state,
ExceptionState& exception_state) {
if (is_neutered_) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Cannot transfer an ImageBitmap from a detached OffscreenCanvas");
return nullptr;
}
if (!context_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot transfer an ImageBitmap from an "
"OffscreenCanvas with no context");
return nullptr;
}
ImageBitmap* image = context_->TransferToImageBitmap(script_state);
if (!image) {
// Undocumented exception (not in spec)
exception_state.ThrowDOMException(DOMExceptionCode::kUnknownError,
"Out of memory");
}
return image;
}
scoped_refptr<Image> OffscreenCanvas::GetSourceImageForCanvas(
SourceImageStatus* status,
AccelerationHint hint,
const FloatSize& size) {
if (!context_) {
*status = kInvalidSourceImageStatus;
sk_sp<SkSurface> surface =
SkSurface::MakeRasterN32Premul(size_.Width(), size_.Height());
return surface ? StaticBitmapImage::Create(surface->makeImageSnapshot())
: nullptr;
}
if (!size.Width() || !size.Height()) {
*status = kZeroSizeCanvasSourceImageStatus;
return nullptr;
}
scoped_refptr<Image> image = context_->GetImage(hint);
if (!image)
image = CreateTransparentImage(Size());
*status = image ? kNormalSourceImageStatus : kInvalidSourceImageStatus;
return image;
}
IntSize OffscreenCanvas::BitmapSourceSize() const {
return size_;
}
ScriptPromise OffscreenCanvas::CreateImageBitmap(
ScriptState* script_state,
EventTarget&,
base::Optional<IntRect> crop_rect,
const ImageBitmapOptions& options) {
return ImageBitmapSource::FulfillImageBitmap(
script_state,
IsPaintable() ? ImageBitmap::Create(this, crop_rect, options) : nullptr);
}
bool OffscreenCanvas::IsOpaque() const {
if (!context_)
return false;
return !context_->CreationAttributes().alpha;
}
CanvasRenderingContext* OffscreenCanvas::GetCanvasRenderingContext(
ExecutionContext* execution_context,
const String& id,
const CanvasContextCreationAttributesCore& attributes) {
execution_context_ = execution_context;
CanvasRenderingContext::ContextType context_type =
CanvasRenderingContext::ContextTypeFromId(id);
// Unknown type.
if (context_type == CanvasRenderingContext::kContextTypeCount ||
(context_type == CanvasRenderingContext::kContextXRPresent &&
!OriginTrials::WebXREnabled(execution_context)))
return nullptr;
CanvasRenderingContextFactory* factory =
GetRenderingContextFactory(context_type);
if (!factory)
return nullptr;
if (context_) {
if (context_->GetContextType() != context_type) {
factory->OnError(
this, "OffscreenCanvas has an existing context of a different type");
return nullptr;
}
} else {
context_ = factory->Create(this, attributes);
}
return context_.Get();
}
OffscreenCanvas::ContextFactoryVector&
OffscreenCanvas::RenderingContextFactories() {
DEFINE_STATIC_LOCAL(ContextFactoryVector, context_factories,
(CanvasRenderingContext::kContextTypeCount));
return context_factories;
}
CanvasRenderingContextFactory* OffscreenCanvas::GetRenderingContextFactory(
int type) {
DCHECK_LT(type, CanvasRenderingContext::kContextTypeCount);
return RenderingContextFactories()[type].get();
}
void OffscreenCanvas::RegisterRenderingContextFactory(
std::unique_ptr<CanvasRenderingContextFactory> rendering_context_factory) {
CanvasRenderingContext::ContextType type =
rendering_context_factory->GetContextType();
DCHECK_LT(type, CanvasRenderingContext::kContextTypeCount);
DCHECK(!RenderingContextFactories()[type]);
RenderingContextFactories()[type] = std::move(rendering_context_factory);
}
bool OffscreenCanvas::OriginClean() const {
return origin_clean_ && !disable_reading_from_canvas_;
}
bool OffscreenCanvas::IsAccelerated() const {
return context_ && context_->IsAccelerated();
}
bool OffscreenCanvas::HasPlaceholderCanvas() const {
return placeholder_canvas_id_ != kInvalidDOMNodeId;
}
CanvasResourceDispatcher* OffscreenCanvas::GetOrCreateResourceDispatcher() {
DCHECK(HasPlaceholderCanvas());
// If we don't have a valid placeholder_canvas_id_, then this is a standalone
// OffscreenCanvas, and it should not have a placeholder.
if (!frame_dispatcher_) {
// The frame dispatcher connects the current thread of OffscreenCanvas
// (either main or worker) to the browser process and remains unchanged
// throughout the lifetime of this OffscreenCanvas.
frame_dispatcher_ = std::make_unique<CanvasResourceDispatcher>(
this, client_id_, sink_id_, placeholder_canvas_id_, size_);
}
return frame_dispatcher_.get();
}
void OffscreenCanvas::DiscardResourceProvider() {
CanvasResourceHost::DiscardResourceProvider();
needs_matrix_clip_restore_ = true;
}
CanvasResourceProvider* OffscreenCanvas::GetOrCreateResourceProvider() {
if (!ResourceProvider()) {
bool can_use_gpu = false;
CanvasResourceProvider::PresentationMode presentation_mode =
CanvasResourceProvider::kDefaultPresentationMode;
if (Is2d()) {
if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled()) {
presentation_mode =
CanvasResourceProvider::kAllowImageChromiumPresentationMode;
}
if (SharedGpuContext::IsGpuCompositingEnabled() &&
RuntimeEnabledFeatures::Accelerated2dCanvasEnabled()) {
can_use_gpu = true;
}
} else if (Is3d()) {
if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
presentation_mode =
CanvasResourceProvider::kAllowImageChromiumPresentationMode;
}
can_use_gpu = SharedGpuContext::IsGpuCompositingEnabled();
}
IntSize surface_size(width(), height());
CanvasResourceProvider::ResourceUsage usage;
if (can_use_gpu) {
if (HasPlaceholderCanvas()) {
usage = CanvasResourceProvider::kAcceleratedCompositedResourceUsage;
} else {
usage = CanvasResourceProvider::kAcceleratedResourceUsage;
}
} else {
if (HasPlaceholderCanvas()) {
usage = CanvasResourceProvider::kSoftwareCompositedResourceUsage;
} else {
usage = CanvasResourceProvider::kSoftwareResourceUsage;
}
}
base::WeakPtr<CanvasResourceDispatcher> dispatcher_weakptr =
HasPlaceholderCanvas() ? GetOrCreateResourceDispatcher()->GetWeakPtr()
: nullptr;
ReplaceResourceProvider(CanvasResourceProvider::Create(
surface_size, usage, SharedGpuContext::ContextProviderWrapper(), 0,
context_->ColorParams(), presentation_mode,
std::move(dispatcher_weakptr)));
// The fallback chain for k*CompositedResourceUsage should never fall
// all the way through to BitmapResourceProvider, except in unit tests.
// In non unit-test scenarios, it should always be possible to at least
// get a ResourceProviderSharedBitmap as a last resort.
// This CHECK verifies that we did indeed get a resource provider that
// supports compositing when one is required.
CHECK(!ResourceProvider() || !HasPlaceholderCanvas() ||
ResourceProvider()->SupportsDirectCompositing());
if (ResourceProvider() && ResourceProvider()->IsValid()) {
ResourceProvider()->Clear();
// Always save an initial frame, to support resetting the top level matrix
// and clip.
ResourceProvider()->Canvas()->save();
if (needs_matrix_clip_restore_) {
needs_matrix_clip_restore_ = false;
context_->RestoreCanvasMatrixClipStack(ResourceProvider()->Canvas());
}
}
}
return ResourceProvider();
}
void OffscreenCanvas::DidDraw() {
DidDraw(FloatRect(0, 0, Size().Width(), Size().Height()));
}
void OffscreenCanvas::DidDraw(const FloatRect& rect) {
if (rect.IsEmpty())
return;
if (HasPlaceholderCanvas()) {
needs_push_frame_ = true;
// TODO(fserb): perhaps we could avoid requesting begin frames here in cases
// where the draw is call from within a worker rAF?
GetOrCreateResourceDispatcher()->SetNeedsBeginFrame(true);
}
}
void OffscreenCanvas::BeginFrame() {
DCHECK(HasPlaceholderCanvas());
PushFrameIfNeeded();
GetOrCreateResourceDispatcher()->SetNeedsBeginFrame(false);
}
void OffscreenCanvas::PushFrameIfNeeded() {
if (needs_push_frame_ && context_) {
context_->PushFrame();
}
}
bool OffscreenCanvas::ShouldAccelerate2dContext() const {
base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper =
SharedGpuContext::ContextProviderWrapper();
return context_provider_wrapper &&
context_provider_wrapper->Utils()->Accelerated2DCanvasFeatureEnabled();
}
void OffscreenCanvas::PushFrame(scoped_refptr<CanvasResource> canvas_resource,
const SkIRect& damage_rect) {
DCHECK(needs_push_frame_);
needs_push_frame_ = false;
current_frame_damage_rect_.join(damage_rect);
if (current_frame_damage_rect_.isEmpty())
return;
base::TimeTicks commit_start_time = WTF::CurrentTimeTicks();
GetOrCreateResourceDispatcher()->DispatchFrame(
canvas_resource->Bitmap(), commit_start_time, current_frame_damage_rect_);
current_frame_damage_rect_ = SkIRect::MakeEmpty();
}
FontSelector* OffscreenCanvas::GetFontSelector() {
if (GetExecutionContext()->IsDocument()) {
return ToDocument(execution_context_)->GetStyleEngine().GetFontSelector();
}
return ToWorkerGlobalScope(execution_context_)->GetFontSelector();
}
void OffscreenCanvas::Trace(blink::Visitor* visitor) {
visitor->Trace(context_);
visitor->Trace(execution_context_);
EventTargetWithInlineData::Trace(visitor);
}
} // namespace blink