blob: 53f49464cbe71d0d3b3f6a9fd4c0b986a6ce1568 [file] [log] [blame]
// Copyright 2019 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/modules/webgpu/gpu_canvas_context.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/config/gpu_finch_features.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_alpha_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_tone_mapping.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_tone_mapping_mode.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/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/canvas/predefined_color_space.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/gpu.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_queue.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_supported_features.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.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/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_texture_alpha_clearer.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/skia/include/core/SkImage.h"
namespace blink {
GPUCanvasContext::Factory::~Factory() = default;
CanvasRenderingContext* GPUCanvasContext::Factory::Create(
CanvasRenderingContextHost* host,
const CanvasContextCreationAttributesCore& attrs) {
CanvasRenderingContext* rendering_context =
MakeGarbageCollected<GPUCanvasContext>(host, attrs);
DCHECK(host);
return rendering_context;
}
CanvasRenderingContext::CanvasRenderingAPI
GPUCanvasContext::Factory::GetRenderingAPI() const {
return CanvasRenderingContext::CanvasRenderingAPI::kWebgpu;
}
GPUCanvasContext::GPUCanvasContext(
CanvasRenderingContextHost* host,
const CanvasContextCreationAttributesCore& attrs)
: CanvasRenderingContext(host, attrs, CanvasRenderingAPI::kWebgpu) {
texture_descriptor_ = {};
}
GPUCanvasContext::~GPUCanvasContext() {
copy_to_swap_texture_required_ = false;
// Perform destruction that's safe to do inside a GC (as in it doesn't touch
// other GC objects).
if (swap_buffers_) {
swap_buffers_->Neuter();
}
}
void GPUCanvasContext::Trace(Visitor* visitor) const {
visitor->Trace(device_);
visitor->Trace(texture_);
visitor->Trace(swap_texture_);
CanvasRenderingContext::Trace(visitor);
}
// CanvasRenderingContext implementation
V8RenderingContext* GPUCanvasContext::AsV8RenderingContext() {
return MakeGarbageCollected<V8RenderingContext>(this);
}
V8OffscreenRenderingContext* GPUCanvasContext::AsV8OffscreenRenderingContext() {
return MakeGarbageCollected<V8OffscreenRenderingContext>(this);
}
SkColorInfo GPUCanvasContext::CanvasRenderingContextSkColorInfo() const {
if (!swap_buffers_)
return CanvasRenderingContext::CanvasRenderingContextSkColorInfo();
return SkColorInfo(viz::ToClosestSkColorType(
/*gpu_compositing=*/true, swap_buffers_->Format()),
alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque
? kOpaque_SkAlphaType
: kPremul_SkAlphaType,
PredefinedColorSpaceToSkColorSpace(color_space_));
}
void GPUCanvasContext::Stop() {
ReplaceDrawingBuffer(/*destroy_swap_buffers*/ true);
stopped_ = true;
}
cc::Layer* GPUCanvasContext::CcLayer() const {
if (swap_buffers_) {
return swap_buffers_->CcLayer();
}
return nullptr;
}
void GPUCanvasContext::Reshape(int width, int height) {
if (stopped_) {
return;
}
// Steps for canvas context resizing:
// 1. Replace the drawing buffer of context.
ReplaceDrawingBuffer(/* destroy_swap_buffers */ false);
// 2. Let configuration be context.[[configuration]]
// 3. If configuration is not null:
// 1. Set context.[[textureDescriptor]] to the GPUTextureDescriptor for the
// canvas and configuration(canvas, configuration).
texture_descriptor_.size = {static_cast<uint32_t>(width),
static_cast<uint32_t>(height), 1};
swap_texture_descriptor_.size = texture_descriptor_.size;
// If we don't notify the host that something has changed it may never check
// for the new cc::Layer.
Host()->SetNeedsCompositingUpdate();
}
scoped_refptr<StaticBitmapImage> GPUCanvasContext::GetImage(FlushReason) {
if (!swap_buffers_)
return nullptr;
// If there is a current texture, create a snapshot from it.
if (texture_ && !texture_->Destroyed()) {
return SnapshotInternal(texture_->GetHandle(), swap_buffers_->Size());
} else if (swap_texture_) {
return SnapshotInternal(swap_texture_->GetHandle(), swap_buffers_->Size());
}
// If there is no current texture, we need to get the information of the last
// texture reserved, that contains the last mailbox, create a new texture for
// it, and use it to create the resource provider.
auto mailbox_texture = swap_buffers_->GetLastWebGPUMailboxTexture();
if (!mailbox_texture) {
return nullptr;
}
return SnapshotInternal(mailbox_texture->GetTexture(),
gfx::Size(mailbox_texture->GetTexture().GetWidth(),
mailbox_texture->GetTexture().GetHeight()));
}
bool GPUCanvasContext::PaintRenderingResultsToCanvas(
SourceDrawingBuffer source_buffer) {
if (!swap_buffers_) {
return false;
}
if (Host()->ResourceProvider() &&
Host()->ResourceProvider()->Size() != swap_buffers_->Size()) {
Host()->DiscardResourceProvider();
}
CanvasResourceProvider* resource_provider =
Host()->GetOrCreateCanvasResourceProvider(RasterModeHint::kPreferGPU);
if (!resource_provider) {
return false;
}
return CopyRenderingResultsFromDrawingBuffer(resource_provider,
source_buffer);
}
bool GPUCanvasContext::CopyRenderingResultsFromDrawingBuffer(
CanvasResourceProvider* resource_provider,
SourceDrawingBuffer source_buffer) {
// TODO(crbug.com/1367056): Handle source_buffer == kFrontBuffer.
// By returning false here the canvas will show up as black in the scenarios
// that copy the front buffer, such as printing.
if (source_buffer != kBackBuffer) {
return false;
}
if (!texture_) {
return false;
}
return CopyTextureToResourceProvider(
texture_->GetHandle(), swap_buffers_->Size(), resource_provider);
}
bool GPUCanvasContext::CopyRenderingResultsToVideoFrame(
WebGraphicsContext3DVideoFramePool* frame_pool,
SourceDrawingBuffer src_buffer,
const gfx::ColorSpace& dst_color_space,
VideoFrameCopyCompletedCallback callback) {
if (!swap_buffers_) {
return false;
}
return swap_buffers_->CopyToVideoFrame(frame_pool, src_buffer,
dst_color_space, std::move(callback));
}
void GPUCanvasContext::SetFilterQuality(
cc::PaintFlags::FilterQuality filter_quality) {
if (filter_quality != filter_quality_) {
filter_quality_ = filter_quality;
if (swap_buffers_) {
swap_buffers_->SetFilterQuality(filter_quality);
}
}
}
bool GPUCanvasContext::PushFrame() {
DCHECK(Host());
DCHECK(Host()->IsOffscreenCanvas());
if (!swap_buffers_)
return false;
// NOTE: It is necessary to call this before calling
// PrepareTransferableResource(), as the latter moves the current swapbuffer
// into `release_callback`.
// TODO(crbug.com/353744937): Eliminate this code needing to prepare the
// TransferableResource altogether in favor of it happening further down the
// line via the ClientSI.
auto client_si = swap_buffers_->GetCurrentSharedImage();
viz::TransferableResource transferable_resource;
viz::ReleaseCallback release_callback;
if (!swap_buffers_->PrepareTransferableResource(
nullptr, &transferable_resource, &release_callback)) {
return false;
}
// If it was possible to prepare the transferable resource, the
// ClientSharedImage must also be valid.
CHECK(client_si);
auto canvas_resource = ExternalCanvasResource::Create(
std::move(client_si), transferable_resource, std::move(release_callback),
GetContextProviderWeakPtr(), /*resource_provider=*/nullptr,
filter_quality_,
/*is_origin_top_left=*/kBottomLeft_GrSurfaceOrigin);
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,
ExceptionState& exception_state) {
auto MakeFallbackImageBitmap =
[this](V8GPUCanvasAlphaMode::Enum alpha_mode) -> ImageBitmap* {
// It is not possible to create an empty image bitmap, return null in that
// case which will fail ImageBitmap creation with an exception instead.
gfx::Size size = Host()->Size();
if (size.IsEmpty()) {
return nullptr;
}
// We intentionally leave the image in legacy color space.
SkBitmap black_bitmap;
if (!black_bitmap.tryAllocN32Pixels(size.width(), size.height())) {
// It is not possible to create such a big image bitmap, return null in
// that case which will fail ImageBitmap creation with an exception
// instead.
return nullptr;
}
if (alpha_mode == V8GPUCanvasAlphaMode::Enum::kOpaque) {
black_bitmap.eraseARGB(255, 0, 0, 0);
} else {
black_bitmap.eraseARGB(0, 0, 0, 0);
}
return MakeGarbageCollected<ImageBitmap>(
UnacceleratedStaticBitmapImage::Create(
SkImages::RasterFromBitmap(black_bitmap)));
};
// If the canvas configuration is invalid, WebGPU requires that we give a
// fallback black ImageBitmap if possible.
if (!swap_buffers_) {
return MakeFallbackImageBitmap(V8GPUCanvasAlphaMode::Enum::kOpaque);
}
// NOTE: It is necessary to call this before calling
// PrepareTransferableResource(), as the latter moves the current swapbuffer
// into `release_callback`.
// TODO(crbug.com/353744937): Eliminate this code needing to prepare the
// TransferableResource altogether in favor of it happening further down the
// line via the ClientSI.
auto client_si = swap_buffers_->GetCurrentSharedImage();
viz::TransferableResource transferable_resource;
viz::ReleaseCallback release_callback;
if (!swap_buffers_->PrepareTransferableResource(
nullptr, &transferable_resource, &release_callback)) {
// If we can't get a mailbox, return an transparent black ImageBitmap.
// The only situation in which this could happen is when two or more calls
// to transferToImageBitmap are made back-to-back, or when the context gets
// lost.
return MakeFallbackImageBitmap(alpha_mode_);
}
DCHECK(release_callback);
CHECK(client_si);
// Use the sync token generated after producing the mailbox. Waiting for this
// before trying to use the mailbox with some other context will ensure it is
// valid.
const auto& sk_image_sync_token = transferable_resource.sync_token();
auto sk_color_type = viz::ToClosestSkColorType(
/*gpu_compositing=*/true, transferable_resource.format);
const SkImageInfo sk_image_info = SkImageInfo::Make(
texture_descriptor_.size.width, texture_descriptor_.size.height,
sk_color_type, kPremul_SkAlphaType);
return MakeGarbageCollected<ImageBitmap>(
AcceleratedStaticBitmapImage::CreateFromCanvasSharedImage(
std::move(client_si), sk_image_sync_token,
/* shared_image_texture_id = */ 0, sk_image_info,
transferable_resource.texture_target(),
/* is_origin_top_left = */ kBottomLeft_GrSurfaceOrigin,
GetContextProviderWeakPtr(), base::PlatformThread::CurrentRef(),
ThreadScheduler::Current()->CleanupTaskRunner(),
std::move(release_callback),
/*supports_display_compositing=*/true,
transferable_resource.is_overlay_candidate));
}
// 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) {
DCHECK(descriptor);
if (stopped_ || !Host()) {
// 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 (!descriptor->device()->ValidateTextureFormatUsage(descriptor->format(),
exception_state)) {
return;
}
for (auto view_format : descriptor->viewFormats()) {
if (!descriptor->device()->ValidateTextureFormatUsage(view_format,
exception_state)) {
return;
}
}
// As soon as the validation for extensions for usage and formats passes, the
// canvas is "configured" and calls to getNextTexture() will return GPUTexture
// objects (valid or invalid) and not throw.
configured_ = true;
view_formats_ = AsDawnEnum<wgpu::TextureFormat>(descriptor->viewFormats());
gfx::Size host_size = Host()->Size();
// Set the default values of the member corresponding to
// GPUCanvasContext.[[texture_descriptor]] in the WebGPU spec.
texture_descriptor_ = {
// Set the values from the configuration descriptor
.usage = AsDawnFlags<wgpu::TextureUsage>(descriptor->usage()),
.size = {static_cast<uint32_t>(host_size.width()),
static_cast<uint32_t>(host_size.height())},
.format = AsDawnEnum(descriptor->format()),
.viewFormatCount = descriptor->viewFormats().size(),
.viewFormats = view_formats_.data(),
// Set the size of the texture in case there was no Reshape() since the
// creation of the context.
};
// Reconfiguring the context discards previous drawing buffers but we also
// destroy the swap buffers so that any validation error below will cause
// swap_buffers_ to be nullptr and getCurrentTexture() to fail.
ReplaceDrawingBuffer(/*destroy_swap_buffers*/ true);
// Store the configured device separately, even if the configuration fails, so
// that errors can be generated in the appropriate error scope.
device_ = descriptor->device();
// The WebGPU spec requires that a validation error be produced if the
// descriptor is invalid. However no call to AssociateMailbox is done in
// configure() which would produce the error. Directly request that the
// descriptor be validated instead.
device_->GetHandle().ValidateTextureDescriptor(&texture_descriptor_);
copy_to_swap_texture_required_ = false;
switch (texture_descriptor_.format) {
case wgpu::TextureFormat::BGRA8Unorm:
#if BUILDFLAG(IS_ANDROID)
// BGRA8Unorm is not natively supported by Android's compositor.
copy_to_swap_texture_required_ = true;
#endif
break;
case wgpu::TextureFormat::RGBA8Unorm:
#if BUILDFLAG(IS_MAC)
// RGBA8Unorm is not natively supported by MacOS's compositor.
copy_to_swap_texture_required_ = true;
#endif
break;
case wgpu::TextureFormat::RGBA16Float:
break;
default:
device_->InjectError(
wgpu::ErrorType::Validation,
("Unsupported canvas context format \"" +
std::string(FromDawnEnum(texture_descriptor_.format)) + "\"")
.c_str());
return;
}
// If the context is configured with STORAGE_BINDING texture usage and
// "bgra8unorm" is the preferred format but the adapter doesn't support the
// "bgra8unorm-storage" feature, we can guess that the app is using the
// non-preferred texture format here because it's the only way to get a
// storage-compatible canvas texture. As such, we'll suppress the warning
// about not using the preferred format.
suppress_preferred_format_warning_ = false;
if (copy_to_swap_texture_required_ &&
GPU::preferred_canvas_format() == wgpu::TextureFormat::BGRA8Unorm &&
texture_descriptor_.usage & wgpu::TextureUsage::StorageBinding &&
!device_->adapter()->features()->has(
V8GPUFeatureName::Enum::kBgra8UnormStorage)) {
suppress_preferred_format_warning_ = true;
}
alpha_mode_ = descriptor->alphaMode().AsEnum();
// There are two scenarios that require special configuration here:
// * In the scenario that the system doesn't support the requested format but
// it's one that WebGPU is required to offer, a separate texture will be
// returned instead with the desired format and the texture will be copied
// to the swap buffer provider's texture with the system-supported format
// when we're ready to present.
// * In the alternative scenario where the texture returned to the user will
// be the swap buffer texture, the texture will have various internal
// operations done to it depending on the alpha mode.
// First configure `texture_descriptor_` as necessary in the case where the
// swap buffer texture will be returned to the user. Note that it is necessary
// to do this *before* copying `texture_descriptor_` to
// `swap_texture_descriptor_`: Each can end up being used in operations on the
// texture depending on whether the operation is on `texture_` or
// `swap_texture_` (which will of course be the same texture in this case).
// NOTE: We gate these additions under the
// `kDawnSIRepsUseClientProvidedInternalUsages` feature here just to be safe
// while rolling out this feature. In reality, setting internal usages on
// `texture_` will be a no-op in this case if the feature is disabled, as the
// SI rep backing the texture will use hardcoded internal usages rather than
// taking them from the client.
if (!copy_to_swap_texture_required_ &&
base::FeatureList::IsEnabled(
features::kDawnSIRepsUseClientProvidedInternalUsages)) {
// `texture_` will be used as the source of either CopyTextureForBrowser()
// or CopyTextureToTexture() operations (the former if the alpha mode is
// opaque, the latter if it is not). In either case, CopySrc is required.
texture_internal_usage_ = {{
.internalUsage = wgpu::TextureUsage::CopySrc,
}};
if (alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque) {
// `texture_` will be used as the source of CopyTextureForBrowser()
// operations and will have alpha clearing done on it. The former requires
// the TextureBinding usage (in addition to the already-present CopySrc),
// while the latter requires RenderAttachment.
texture_internal_usage_.internalUsage |=
wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::RenderAttachment;
}
texture_descriptor_.nextInChain = &texture_internal_usage_;
}
// Copy `texture_descriptor_` to `swap_texture_descriptor_` before making any
// distinct configuration on each of them that is necessary in the case where
// they will be distinct textures.
swap_texture_descriptor_ = texture_descriptor_;
if (copy_to_swap_texture_required_) {
// The texture returned to the user will require both the CopySrc and
// TextureBinding usages in order to be used with CopyTextureForBrowser.
texture_internal_usage_ = {{
.internalUsage =
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding,
}};
texture_descriptor_.nextInChain = &texture_internal_usage_;
// The swap buffer texture will require both CopyDst and RenderAttachment
// in order to be used with CopyTextureForBrowser.
swap_texture_descriptor_.usage =
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
// In cases where a copy is necessary the swap buffers will always use the
// preferred canvas format.
swap_texture_descriptor_.format = GPU::preferred_canvas_format();
// The swap buffer texture doesn't need any view formats.
swap_texture_descriptor_.viewFormats = nullptr;
swap_texture_descriptor_.viewFormatCount = 0;
}
if (!ValidateAndConvertColorSpace(descriptor->colorSpace(), color_space_,
exception_state)) {
return;
}
gfx::HDRMetadata hdr_metadata;
if (descriptor->hasToneMapping()) {
if (descriptor->toneMapping()->hasMode()) {
switch (descriptor->toneMapping()->mode().AsEnum()) {
case V8GPUCanvasToneMappingMode::Enum::kStandard:
break;
case V8GPUCanvasToneMappingMode::Enum::kExtended:
hdr_metadata.extended_range.emplace(
/*current_headroom=*/gfx::HdrMetadataExtendedRange::
kDefaultHdrHeadroom,
/*desired_headroom=*/gfx::HdrMetadataExtendedRange::
kDefaultHdrHeadroom);
break;
}
}
} else if (descriptor->hasHdrOptions()) {
// TODO(https://crbug.com/333967627): Remove support for this older version
// of the API once the new API lands.
ParseCanvasHighDynamicRangeOptions(descriptor->hdrOptions(), hdr_metadata);
}
const wgpu::DawnTextureInternalUsageDescriptor* internal_usage_desc = nullptr;
if (const wgpu::ChainedStruct* next_in_chain =
swap_texture_descriptor_.nextInChain) {
// The internal usage descriptor is the only valid struct to chain.
CHECK_EQ(next_in_chain->sType,
wgpu::SType::DawnTextureInternalUsageDescriptor);
internal_usage_desc =
static_cast<const wgpu::DawnTextureInternalUsageDescriptor*>(
next_in_chain);
}
auto internal_usage = internal_usage_desc ? internal_usage_desc->internalUsage
: wgpu::TextureUsage::None;
swap_buffers_ = base::AdoptRef(new WebGPUSwapBufferProvider(
this, device_->GetDawnControlClient(), device_->GetHandle(),
swap_texture_descriptor_.usage, internal_usage,
swap_texture_descriptor_.format, color_space_, hdr_metadata));
swap_buffers_->SetFilterQuality(filter_quality_);
// Note: SetContentsOpaque is only an optimization hint. It doesn't
// actually make the contents opaque.
switch (alpha_mode_) {
case V8GPUCanvasAlphaMode::Enum::kOpaque: {
if (!alpha_clearer_ ||
!alpha_clearer_->IsCompatible(device_->GetHandle(),
swap_texture_descriptor_.format)) {
alpha_clearer_ = base::MakeRefCounted<WebGPUTextureAlphaClearer>(
device_->GetDawnControlClient(), device_->GetHandle(),
swap_texture_descriptor_.format);
}
break;
}
case V8GPUCanvasAlphaMode::Enum::kPremultiplied:
alpha_clearer_ = nullptr;
break;
}
// If we don't notify the host that something has changed it may never check
// for the new cc::Layer.
Host()->SetNeedsCompositingUpdate();
}
void GPUCanvasContext::unconfigure() {
if (stopped_) {
return;
}
ReplaceDrawingBuffer(/*destroy_swap_buffers*/ true);
// When developers call unconfigure from the page, one of the reasons for
// doing so is to expressly release the GPUCanvasContext's device reference.
// In order to fully release it, any TextureAlphaClearer that has been created
// also needs to be released.
alpha_clearer_ = nullptr;
device_ = nullptr;
configured_ = false;
}
GPUTexture* GPUCanvasContext::getCurrentTexture(
ScriptState* script_state,
ExceptionState& exception_state) {
if (!configured_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"context is not configured");
return nullptr;
}
DCHECK(device_);
// Calling getCurrentTexture returns a texture that is valid until the
// animation frame it gets presented. If getCurrentTexture is called multiple
// time, the same texture should be returned. |texture_| is set to
// null when presented so that we know we should create a new one.
if (texture_ && !new_texture_required_) {
return texture_.Get();
}
new_texture_required_ = false;
if (!swap_buffers_) {
device_->InjectError(wgpu::ErrorType::Validation,
"context configuration is invalid.");
return GPUTexture::CreateError(device_.Get(), &texture_descriptor_);
}
ReplaceDrawingBuffer(/* destroy_swap_buffers */ false);
// Simply requesting a new canvas texture with WebGPU is enough to mark it as
// "dirty", so always call DidDraw() when a new texture is created.
DidDraw(CanvasPerformanceMonitor::DrawType::kOther);
SkAlphaType alpha_type = alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque
? kOpaque_SkAlphaType
: kPremul_SkAlphaType;
scoped_refptr<WebGPUMailboxTexture> mailbox_texture =
swap_buffers_->GetNewTexture(swap_texture_descriptor_, alpha_type);
if (!mailbox_texture) {
// Try to give a helpful message for the most common cause for mailbox
// texture creation failure.
if (texture_descriptor_.size.width == 0 ||
texture_descriptor_.size.height == 0) {
device_->InjectError(wgpu::ErrorType::Validation,
"Could not create a swapchain texture of size 0.");
} else {
device_->InjectError(wgpu::ErrorType::Validation,
"Could not create the swapchain texture.");
}
texture_ = swap_texture_ =
GPUTexture::CreateError(device_, &texture_descriptor_);
return texture_.Get();
}
mailbox_texture->SetNeedsPresent(true);
mailbox_texture->SetAlphaClearer(alpha_clearer_);
swap_texture_ = MakeGarbageCollected<GPUTexture>(
device_, swap_texture_descriptor_.format,
static_cast<wgpu::TextureUsage>(swap_texture_descriptor_.usage),
std::move(mailbox_texture), String(swap_texture_descriptor_.label));
if (copy_to_swap_texture_required_) {
texture_ = MakeGarbageCollected<GPUTexture>(
device_, device_->GetHandle().CreateTexture(&texture_descriptor_),
String(texture_descriptor_.label));
// If the user manually destroys the texture before yielding control back
// to the browser, do the copy just prior to the texture destruction.
texture_->SetBeforeDestroyCallback(WTF::BindOnce(
[](GPUCanvasContext* context, GPUTexture* texture) {
context->CopyToSwapTexture();
texture->ClearBeforeDestroyCallback();
},
WrapPersistent(this), WrapPersistent(texture_.Get())));
} else {
texture_ = swap_texture_;
}
ExecutionContext* execution_context = ExecutionContext::From(script_state);
UseCounter::Count(execution_context,
WebFeature::kWebGPUCanvasContextGetCurrentTexture);
return texture_.Get();
}
void GPUCanvasContext::ReplaceDrawingBuffer(bool destroy_swap_buffers) {
if (swap_texture_) {
DCHECK(swap_buffers_);
swap_buffers_->DiscardCurrentSwapBuffer();
swap_texture_ = nullptr;
}
if (copy_to_swap_texture_required_ && texture_) {
texture_->ClearBeforeDestroyCallback();
texture_->destroy();
}
texture_ = nullptr;
if (swap_buffers_ && destroy_swap_buffers) {
// Tell any previous swapbuffers that it will no longer be used and can
// destroy all its resources (and produce errors when used).
swap_buffers_->Neuter();
swap_buffers_ = nullptr;
}
}
void GPUCanvasContext::FinalizeFrame(FlushReason) {
// In some cases, such as when a canvas is hidden or offscreen, compositing
// will never happen and thus OnTextureTransferred will never be called. In
// those cases, getCurrentTexture is still required to return a new texture
// after the current frame has ended, so we'll mark that a new texture is
// required here.
new_texture_required_ = true;
}
// WebGPUSwapBufferProvider::Client implementation
void GPUCanvasContext::OnTextureTransferred() {
DCHECK(texture_);
DCHECK(swap_texture_);
if (copy_to_swap_texture_required_ && texture_ && !texture_->Destroyed()) {
CopyToSwapTexture();
texture_->ClearBeforeDestroyCallback();
texture_->destroy();
}
texture_ = nullptr;
swap_texture_ = nullptr;
}
void GPUCanvasContext::SetNeedsCompositingUpdate() {
if (Host()) {
Host()->SetNeedsCompositingUpdate();
}
}
void GPUCanvasContext::CopyToSwapTexture() {
DCHECK(copy_to_swap_texture_required_);
DCHECK(texture_);
DCHECK(swap_texture_);
if (!suppress_preferred_format_warning_) {
// Will only display once per device
device_->AddSingletonWarning(GPUSingletonWarning::kNonPreferredFormat);
}
wgpu::ImageCopyTexture source = {
.texture = texture_->GetHandle(),
.aspect = wgpu::TextureAspect::All,
};
wgpu::ImageCopyTexture destination = {
.texture = swap_texture_->GetHandle(),
.aspect = wgpu::TextureAspect::All,
};
gfx::Size size = swap_buffers_->Size();
wgpu::Extent3D copy_size = {
.width = static_cast<uint32_t>(size.width()),
.height = static_cast<uint32_t>(size.height()),
.depthOrArrayLayers = 1,
};
wgpu::AlphaMode copy_alpha_mode =
alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque
? wgpu::AlphaMode::Opaque
: wgpu::AlphaMode::Premultiplied;
wgpu::CopyTextureForBrowserOptions options = {
.srcAlphaMode = copy_alpha_mode,
.dstAlphaMode = copy_alpha_mode,
.internalUsage = true,
};
device_->queue()->GetHandle().CopyTextureForBrowser(&source, &destination,
&copy_size, &options);
}
bool GPUCanvasContext::CopyTextureToResourceProvider(
const wgpu::Texture& texture,
const gfx::Size& size,
CanvasResourceProvider* resource_provider) const {
DCHECK(resource_provider);
DCHECK_EQ(resource_provider->Size(), size);
// This method will copy the contents of `texture` to `resource_provider`'s
// backing SharedImage via the WebGPU interface. Hence, WEBGPU_WRITE usage
// must be included on that backing SharedImage.
DCHECK(resource_provider->GetSharedImageUsageFlags() &
gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE);
DCHECK(resource_provider->IsOriginTopLeft());
base::WeakPtr<WebGraphicsContext3DProviderWrapper> shared_context_wrapper =
SharedGpuContext::ContextProviderWrapper();
if (!shared_context_wrapper || !shared_context_wrapper->ContextProvider())
return false;
auto dst_client_si =
resource_provider->GetBackingClientSharedImageForOverwrite();
if (!dst_client_si) {
return false;
}
auto* ri = shared_context_wrapper->ContextProvider()->RasterInterface();
if (!GetContextProviderWeakPtr()) {
return false;
}
// todo(crbug/1267244) Use WebGPUMailboxTexture here instead of doing things
// manually.
gpu::webgpu::WebGPUInterface* webgpu =
GetContextProviderWeakPtr()->ContextProvider()->WebGPUInterface();
gpu::webgpu::ReservedTexture reservation =
webgpu->ReserveTexture(device_->GetHandle().Get());
DCHECK(reservation.texture);
wgpu::Texture reserved_texture = wgpu::Texture::Acquire(reservation.texture);
gpu::SyncToken sync_token;
ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
webgpu->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
wgpu::TextureUsage usage =
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
webgpu->AssociateMailbox(reservation.deviceId, reservation.deviceGeneration,
reservation.id, reservation.generation,
static_cast<uint64_t>(usage),
dst_client_si->mailbox());
wgpu::ImageCopyTexture source = {
.texture = texture,
.aspect = wgpu::TextureAspect::All,
};
wgpu::ImageCopyTexture destination = {
.texture = reserved_texture,
.aspect = wgpu::TextureAspect::All,
};
wgpu::Extent3D copy_size = {
.width = static_cast<uint32_t>(size.width()),
.height = static_cast<uint32_t>(size.height()),
.depthOrArrayLayers = 1,
};
bool isOpaque = alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque;
// If either the texture is opaque or the texture format does not match the
// resource provider's format then CopyTextureForBrowser will be used, which
// performs a blit and can fix up the texture data during the copy.
if (isOpaque || copy_to_swap_texture_required_) {
wgpu::AlphaMode srcAlphaMode =
isOpaque ? wgpu::AlphaMode::Opaque : wgpu::AlphaMode::Premultiplied;
// Issue a copyTextureForBrowser call with internal usage turned on.
// There is a special step for srcAlphaMode == wgpu::AlphaMode::Opaque that
// clears alpha channel to one.
SkImageInfo sk_dst_image_info = resource_provider->GetSkImageInfo();
wgpu::AlphaMode dstAlphaMode;
switch (sk_dst_image_info.alphaType()) {
case SkAlphaType::kPremul_SkAlphaType:
dstAlphaMode = wgpu::AlphaMode::Premultiplied;
break;
case SkAlphaType::kUnpremul_SkAlphaType:
dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
break;
case SkAlphaType::kOpaque_SkAlphaType:
dstAlphaMode = wgpu::AlphaMode::Opaque;
break;
default:
// Unknown dst alpha type, default to equal to src alpha mode
dstAlphaMode = srcAlphaMode;
break;
}
wgpu::CopyTextureForBrowserOptions options = {
.flipY = !resource_provider->IsOriginTopLeft(),
.srcAlphaMode = srcAlphaMode,
.dstAlphaMode = dstAlphaMode,
.internalUsage = true,
};
device_->queue()->GetHandle().CopyTextureForBrowser(&source, &destination,
&copy_size, &options);
} else {
// Create a command encoder and call copyTextureToTexture for the image
// copy.
wgpu::DawnEncoderInternalUsageDescriptor internal_usage_desc = {{
.useInternalUsages = true,
}};
wgpu::CommandEncoderDescriptor command_encoder_desc = {
.nextInChain = &internal_usage_desc,
};
wgpu::CommandEncoder command_encoder =
device_->GetHandle().CreateCommandEncoder(&command_encoder_desc);
command_encoder.CopyTextureToTexture(&source, &destination, &copy_size);
wgpu::CommandBuffer command_buffer = command_encoder.Finish();
command_encoder = nullptr;
device_->queue()->GetHandle().Submit(1u, &command_buffer);
command_buffer = nullptr;
}
webgpu->DissociateMailbox(reservation.id, reservation.generation);
webgpu->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
ri->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
return true;
}
scoped_refptr<StaticBitmapImage> GPUCanvasContext::SnapshotInternal(
const wgpu::Texture& texture,
const gfx::Size& size) const {
const auto canvas_context_color = CanvasRenderingContextSkColorInfo();
const auto info =
SkImageInfo::Make(gfx::SizeToSkISize(size), canvas_context_color);
// We tag the SharedImage inside the WebGPUImageProvider with display usages
// since there are uncommon paths which may use this snapshot for compositing.
// These paths are usually related to either printing or either video and
// usually related to OffscreenCanvas; in cases where the image created from
// this Snapshot will be sent eventually to the Display Compositor.
auto resource_provider = CanvasResourceProvider::CreateWebGPUImageProvider(
info, swap_buffers_->GetSharedImageUsagesForDisplay());
if (!resource_provider)
return nullptr;
if (!CopyTextureToResourceProvider(texture, size, resource_provider.get()))
return nullptr;
return resource_provider->Snapshot(FlushReason::kNone);
}
base::WeakPtr<WebGraphicsContext3DProviderWrapper>
GPUCanvasContext::GetContextProviderWeakPtr() const {
return device_->GetDawnControlClient()->GetContextProviderWeakPtr();
}
} // namespace blink