blob: 481c5daa5c26186b55d6e464e7b19c122de6ee30 [file] [log] [blame]
// Copyright 2019 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/modules/webgpu/gpu_canvas_context.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_htmlcanvaselement_offscreencanvas.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_canvasrenderingcontext2d_gpucanvascontext_imagebitmaprenderingcontext_webgl2renderingcontext_webglrenderingcontext.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpucanvascontext_imagebitmaprenderingcontext_offscreencanvasrenderingcontext2d_webgl2renderingcontext_webglrenderingcontext.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
namespace blink {
GPUCanvasContext::Factory::Factory() {}
GPUCanvasContext::Factory::~Factory() {}
CanvasRenderingContext* GPUCanvasContext::Factory::Create(
CanvasRenderingContextHost* host,
const CanvasContextCreationAttributesCore& attrs) {
CanvasRenderingContext* rendering_context =
MakeGarbageCollected<GPUCanvasContext>(host, attrs);
DCHECK(host);
return rendering_context;
}
CanvasRenderingContext::ContextType GPUCanvasContext::Factory::GetContextType()
const {
return CanvasRenderingContext::kContextWebGPU;
}
GPUCanvasContext::GPUCanvasContext(
CanvasRenderingContextHost* host,
const CanvasContextCreationAttributesCore& attrs)
: CanvasRenderingContext(host, attrs, CanvasRenderingAPI::kWebgpu) {}
GPUCanvasContext::~GPUCanvasContext() {}
void GPUCanvasContext::Trace(Visitor* visitor) const {
visitor->Trace(swapchain_);
visitor->Trace(configured_device_);
CanvasRenderingContext::Trace(visitor);
}
const IntSize& GPUCanvasContext::CanvasSize() const {
return Host()->Size();
}
// CanvasRenderingContext implementation
CanvasRenderingContext::ContextType GPUCanvasContext::GetContextType() const {
return CanvasRenderingContext::kContextWebGPU;
}
V8RenderingContext* GPUCanvasContext::AsV8RenderingContext() {
return MakeGarbageCollected<V8RenderingContext>(this);
}
V8OffscreenRenderingContext* GPUCanvasContext::AsV8OffscreenRenderingContext() {
return MakeGarbageCollected<V8OffscreenRenderingContext>(this);
}
void GPUCanvasContext::Stop() {
if (swapchain_) {
swapchain_->Neuter();
swapchain_ = nullptr;
}
configured_device_ = nullptr;
stopped_ = true;
}
cc::Layer* GPUCanvasContext::CcLayer() const {
if (swapchain_) {
return swapchain_->CcLayer();
}
return nullptr;
}
scoped_refptr<StaticBitmapImage> GPUCanvasContext::GetImage() {
if (!swapchain_)
return nullptr;
CanvasResourceParams resource_params;
resource_params.SetSkColorType(viz::ResourceFormatToClosestSkColorType(
/*gpu_compositing=*/true, swapchain_->Format()));
auto resource_provider = CanvasResourceProvider::CreateWebGPUImageProvider(
IntSize(swapchain_->Size()), resource_params,
/*is_origin_top_left=*/true);
if (!resource_provider)
return nullptr;
if (!swapchain_->CopyToResourceProvider(resource_provider.get()))
return nullptr;
return resource_provider->Snapshot();
}
bool GPUCanvasContext::PaintRenderingResultsToCanvas(
SourceDrawingBuffer source_buffer) {
DCHECK_EQ(source_buffer, kBackBuffer);
if (!swapchain_)
return false;
if (Host()->ResourceProvider() &&
Host()->ResourceProvider()->Size() != IntSize(swapchain_->Size())) {
Host()->DiscardResourceProvider();
}
CanvasResourceProvider* resource_provider =
Host()->GetOrCreateCanvasResourceProvider(RasterModeHint::kPreferGPU);
return CopyRenderingResultsFromDrawingBuffer(resource_provider,
source_buffer);
}
bool GPUCanvasContext::CopyRenderingResultsFromDrawingBuffer(
CanvasResourceProvider* resource_provider,
SourceDrawingBuffer source_buffer) {
DCHECK_EQ(source_buffer, kBackBuffer);
if (swapchain_)
return swapchain_->CopyToResourceProvider(resource_provider);
return false;
}
void GPUCanvasContext::SetFilterQuality(SkFilterQuality filter_quality) {
if (filter_quality != filter_quality_) {
filter_quality_ = filter_quality;
if (swapchain_) {
swapchain_->SetFilterQuality(filter_quality);
}
}
}
bool GPUCanvasContext::PushFrame() {
DCHECK(Host());
DCHECK(Host()->IsOffscreenCanvas());
auto canvas_resource = swapchain_->ExportCanvasResource();
if (!canvas_resource)
return false;
const int width = canvas_resource->Size().Width();
const int height = canvas_resource->Size().Height();
return Host()->PushFrame(std::move(canvas_resource),
SkIRect::MakeWH(width, height));
}
ImageBitmap* GPUCanvasContext::TransferToImageBitmap(
ScriptState* script_state) {
return MakeGarbageCollected<ImageBitmap>(
swapchain_->TransferToStaticBitmapImage());
}
// gpu_presentation_context.idl
V8UnionHTMLCanvasElementOrOffscreenCanvas*
GPUCanvasContext::getHTMLOrOffscreenCanvas() const {
if (Host()->IsOffscreenCanvas()) {
return MakeGarbageCollected<V8UnionHTMLCanvasElementOrOffscreenCanvas>(
static_cast<OffscreenCanvas*>(Host()));
}
return MakeGarbageCollected<V8UnionHTMLCanvasElementOrOffscreenCanvas>(
static_cast<HTMLCanvasElement*>(Host()));
}
void GPUCanvasContext::configure(const GPUCanvasConfiguration* descriptor,
ExceptionState& exception_state) {
ConfigureInternal(descriptor, exception_state);
}
void GPUCanvasContext::unconfigure() {
if (stopped_) {
return;
}
if (swapchain_) {
// Tell any previous swapchain that it will no longer be used and can
// destroy all its resources (and produce errors when used).
swapchain_->Neuter();
swapchain_ = nullptr;
}
configured_device_ = nullptr;
}
String GPUCanvasContext::getPreferredFormat(const GPUAdapter* adapter) {
// TODO(crbug.com/1007166): Return actual preferred format for the swap chain.
return "bgra8unorm";
}
GPUTexture* GPUCanvasContext::getCurrentTexture(
ExceptionState& exception_state) {
if (!configured_device_) {
exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
"context is not configured");
return nullptr;
}
if (!swapchain_) {
configured_device_->InjectError(WGPUErrorType_Validation,
"context configuration is invalid.");
return GPUTexture::CreateError(configured_device_);
}
return swapchain_->getCurrentTexture();
}
// gpu_canvas_context.idl (Deprecated)
GPUSwapChain* GPUCanvasContext::configureSwapChain(
const GPUCanvasConfiguration* descriptor,
ExceptionState& exception_state) {
descriptor->device()->AddConsoleWarning(
"configureSwapChain() is deprecated. Use configure() instead and call "
"getCurrentTexture() directly on the context. Note that configure() must "
"also be called if you want to change the size of the textures returned "
"by getCurrentTexture()");
ConfigureInternal(descriptor, exception_state, true);
return swapchain_;
}
String GPUCanvasContext::getSwapChainPreferredFormat(
ExecutionContext* execution_context,
GPUAdapter* adapter) {
adapter->AddConsoleWarning(
execution_context,
"getSwapChainPreferredFormat() is deprecated. Use getPreferredFormat() "
"instead.");
return getPreferredFormat(adapter);
}
void GPUCanvasContext::ConfigureInternal(
const GPUCanvasConfiguration* descriptor,
ExceptionState& exception_state,
bool deprecated_resize_behavior) {
DCHECK(descriptor);
if (stopped_) {
// This is probably not possible, or at least would only happen during page
// shutdown.
exception_state.ThrowDOMException(DOMExceptionCode::kUnknownError,
"canvas has been destroyed");
return;
}
if (swapchain_) {
// Tell any previous swapchain that it will no longer be used and can
// destroy all its resources (and produce errors when used).
swapchain_->Neuter();
swapchain_ = nullptr;
}
// Store the configured device separately, even if the configuration fails, so
// that errors can be generated in the appropriate error scope.
configured_device_ = descriptor->device();
WGPUTextureUsage usage = AsDawnEnum<WGPUTextureUsage>(descriptor->usage());
WGPUTextureFormat format =
AsDawnEnum<WGPUTextureFormat>(descriptor->format());
switch (format) {
case WGPUTextureFormat_BGRA8Unorm:
break;
case WGPUTextureFormat_RGBA16Float:
configured_device_->InjectError(
WGPUErrorType_Validation,
"rgba16float swap chain is not yet supported");
return;
default:
configured_device_->InjectError(WGPUErrorType_Validation,
"unsupported swap chain format");
return;
}
// Set the default size.
IntSize size;
if (deprecated_resize_behavior) {
// A negative size will indicate to the swap chain that it should follow the
// deprecated behavior of resizing to match the canvas size each frame.
size = IntSize(-1, -1);
} else if (descriptor->hasSize()) {
WGPUExtent3D dawn_extent = AsDawnType(descriptor->size());
size = IntSize(dawn_extent.width, dawn_extent.height);
if (dawn_extent.depthOrArrayLayers != 1) {
configured_device_->InjectError(
WGPUErrorType_Validation,
"swap chain size must have depthOrArrayLayers set to 1");
return;
}
if (size.IsEmpty()) {
configured_device_->InjectError(
WGPUErrorType_Validation,
"context width and height must be greater than 0");
return;
}
} else {
size = CanvasSize();
}
swapchain_ = MakeGarbageCollected<GPUSwapChain>(
this, configured_device_, usage, format, filter_quality_, size);
swapchain_->CcLayer()->SetContentsOpaque(!CreationAttributes().alpha);
if (descriptor->hasLabel())
swapchain_->setLabel(descriptor->label());
// If we don't notify the host that something has changed it may never check
// for the new cc::Layer.
Host()->SetNeedsCompositingUpdate();
}
} // namespace blink