| // 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_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/skia/skia_utils.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 { |
| |
| namespace { |
| |
| bool IsContextFormatSupported(V8GPUTextureFormat::Enum format) { |
| switch (format) { |
| case V8GPUTextureFormat::Enum::kBgra8Unorm: |
| case V8GPUTextureFormat::Enum::kRgba8Unorm: |
| case V8GPUTextureFormat::Enum::kRgba16Float: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| 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_); |
| ScriptWrappable::Trace(visitor); |
| CanvasRenderingContext::Trace(visitor); |
| } |
| |
| // CanvasRenderingContext implementation |
| V8RenderingContext* GPUCanvasContext::AsV8RenderingContext() { |
| return MakeGarbageCollected<V8RenderingContext>(this); |
| } |
| |
| V8OffscreenRenderingContext* GPUCanvasContext::AsV8OffscreenRenderingContext() { |
| return MakeGarbageCollected<V8OffscreenRenderingContext>(this); |
| } |
| |
| SkAlphaType GPUCanvasContext::GetAlphaType() const { |
| if (!swap_buffers_) { |
| return kPremul_SkAlphaType; |
| } |
| return alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque |
| ? kOpaque_SkAlphaType |
| : kPremul_SkAlphaType; |
| } |
| |
| viz::SharedImageFormat GPUCanvasContext::GetSharedImageFormat() const { |
| if (!swap_buffers_) { |
| return GetN32FormatForCanvas(); |
| ; |
| } |
| return swap_buffers_->Format(); |
| } |
| |
| gfx::ColorSpace GPUCanvasContext::GetColorSpace() const { |
| if (!swap_buffers_) { |
| return gfx::ColorSpace::CreateSRGB(); |
| } |
| return PredefinedColorSpaceToGfxColorSpace(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 (device_->IsDestroyed()) { |
| return MakeFallbackStaticBitmapImage(alpha_mode_); |
| } |
| |
| // If there is a current texture, create a snapshot from it. |
| if (texture_ && !texture_->IsDestroyed()) { |
| return SnapshotInternal(texture_->GetHandle()); |
| } else if (swap_texture_) { |
| return SnapshotInternal(swap_texture_->GetHandle()); |
| } |
| |
| // If there is no current texture, return a snapshot of the front buffer if |
| // possible. |
| auto front_buffer_texture = GetFrontBufferMailboxTexture(); |
| if (!front_buffer_texture) { |
| return nullptr; |
| } |
| |
| return SnapshotInternal(front_buffer_texture->GetTexture()); |
| } |
| |
| 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(); |
| if (!resource_provider) { |
| return false; |
| } |
| |
| if (device_->IsDestroyed()) { |
| SkColor4f color = alpha_mode_ == V8GPUCanvasAlphaMode::Enum::kOpaque |
| ? SkColors::kBlack |
| : SkColors::kTransparent; |
| resource_provider->Canvas().clear(color); |
| resource_provider->FlushCanvas(FlushReason::kClear); |
| return false; |
| } |
| |
| wgpu::Texture texture; |
| |
| scoped_refptr<WebGPUMailboxTexture> front_buffer_texture; |
| if (source_buffer == kFrontBuffer) { |
| #if BUILDFLAG(IS_LINUX) |
| // By returning false here the canvas will show up as black in the scenarios |
| // that copy the front buffer, such as printing. |
| // TODO(crbug.com/40902474): Support concurrent SharedImage reads via Dawn |
| // on Linux backings and enable the below codepath. |
| return false; |
| #else |
| // Create a WebGPU texture backed by the front buffer's SharedImage. |
| front_buffer_texture = GetFrontBufferMailboxTexture(); |
| if (!front_buffer_texture) { |
| return false; |
| } |
| |
| texture = front_buffer_texture->GetTexture(); |
| #endif |
| } else { |
| texture = texture_->GetHandle(); |
| } |
| |
| if (!texture) { |
| return false; |
| } |
| |
| return CopyTextureToResourceProvider(texture, 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)); |
| } |
| |
| bool GPUCanvasContext::PushFrame() { |
| DCHECK(Host()); |
| DCHECK(Host()->IsOffscreenCanvas()); |
| |
| if (!swap_buffers_) { |
| return false; |
| } |
| |
| gpu::SyncToken sync_token; |
| viz::ReleaseCallback release_callback; |
| auto client_si = |
| swap_buffers_->ExportCurrentSharedImage(sync_token, &release_callback); |
| if (!client_si) { |
| return false; |
| } |
| |
| auto canvas_resource = ExternalCanvasResource::Create( |
| std::move(client_si), sync_token, |
| viz::TransferableResource::ResourceSource::kWebGPUSwapBuffer, |
| swap_buffers_->GetHDRMetadata(), std::move(release_callback), |
| GetContextProviderWeakPtr(), |
| /*resource_provider=*/nullptr); |
| 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) { |
| // If the canvas configuration is invalid, WebGPU requires that we give a |
| // fallback black ImageBitmap if possible. |
| if (!swap_buffers_) { |
| auto staticBitmapImage = |
| MakeFallbackStaticBitmapImage(V8GPUCanvasAlphaMode::Enum::kOpaque); |
| return staticBitmapImage |
| ? MakeGarbageCollected<ImageBitmap>(staticBitmapImage) |
| : nullptr; |
| } |
| |
| gpu::SyncToken sk_image_sync_token; |
| viz::ReleaseCallback release_callback; |
| auto client_si = swap_buffers_->ExportCurrentSharedImage(sk_image_sync_token, |
| &release_callback); |
| if (!client_si) { |
| // 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. |
| auto staticBitmapImage = MakeFallbackStaticBitmapImage(alpha_mode_); |
| return staticBitmapImage |
| ? MakeGarbageCollected<ImageBitmap>(staticBitmapImage) |
| : nullptr; |
| } |
| DCHECK(release_callback); |
| |
| auto format = client_si->format(); |
| |
| return MakeGarbageCollected<ImageBitmap>( |
| AcceleratedStaticBitmapImage::CreateFromCanvasSharedImage( |
| std::move(client_si), sk_image_sync_token, |
| /* shared_image_texture_id = */ 0, |
| gfx::Size(texture_descriptor_.size.width, |
| texture_descriptor_.size.height), |
| format, kPremul_SkAlphaType, gfx::ColorSpace::CreateSRGB(), |
| GetContextProviderWeakPtr(), base::PlatformThread::CurrentRef(), |
| ThreadScheduler::Current()->CleanupTaskRunner(), |
| std::move(release_callback))); |
| } |
| |
| 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; |
| } |
| } |
| |
| if (!IsContextFormatSupported(descriptor->format().AsEnum())) { |
| exception_state.ThrowTypeError( |
| String::Format("Unsupported canvas context format '%s'.", |
| V8GPUTextureFormat(descriptor->format()).AsCStr())); |
| 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()), |
| .dimension = wgpu::TextureDimension::e2D, |
| .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; |
| #if BUILDFLAG(IS_ANDROID) |
| if (texture_descriptor_.format == wgpu::TextureFormat::BGRA8Unorm) { |
| // BGRA8Unorm is not natively supported by Android's compositor. |
| copy_to_swap_texture_required_ = true; |
| } |
| if ((texture_descriptor_.usage & wgpu::TextureUsage::StorageBinding) != 0) { |
| // Storage images are not supported on some AHB/gralloc versions. |
| copy_to_swap_texture_required_ = true; |
| } |
| #endif |
| #if BUILDFLAG(IS_MAC) |
| if (texture_descriptor_.format == wgpu::TextureFormat::RGBA8Unorm) { |
| // RGBA8Unorm is not natively supported by MacOS's compositor. |
| copy_to_swap_texture_required_ = true; |
| } |
| #endif |
| |
| // 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::GetPreferredCanvasFormat() == 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). |
| if (!copy_to_swap_texture_required_) { |
| // `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::GetPreferredCanvasFormat(); |
| |
| // 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() && descriptor->toneMapping()->hasMode()) { |
| tone_mapping_mode_ = descriptor->toneMapping()->mode().AsEnum(); |
| switch (tone_mapping_mode_) { |
| 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; |
| } |
| } |
| |
| 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)); |
| |
| // 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; |
| } |
| |
| GPUCanvasConfiguration* GPUCanvasContext::getConfiguration() { |
| if (!configured_) { |
| return nullptr; |
| } |
| |
| GPUCanvasConfiguration* configuration = GPUCanvasConfiguration::Create(); |
| configuration->setDevice(device_); |
| configuration->setFormat(FromDawnEnum(texture_descriptor_.format)); |
| configuration->setUsage(static_cast<uint32_t>(texture_descriptor_.usage)); |
| |
| Vector<V8GPUTextureFormat> view_formats; |
| for (size_t i = 0; i < texture_descriptor_.viewFormatCount; ++i) { |
| view_formats.push_back(FromDawnEnum(view_formats_[i])); |
| } |
| configuration->setViewFormats(view_formats); |
| |
| configuration->setColorSpace(PredefinedColorSpaceToV8(color_space_)); |
| configuration->setAlphaMode(alpha_mode_); |
| |
| GPUCanvasToneMapping* tone_mapping = GPUCanvasToneMapping::Create(); |
| tone_mapping->setMode(tone_mapping_mode_); |
| configuration->setToneMapping(tone_mapping); |
| |
| return configuration; |
| } |
| |
| 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 = GetAlphaType(); |
| 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::FromUTF8(swap_texture_descriptor_.label)); |
| |
| if (copy_to_swap_texture_required_) { |
| texture_ = MakeGarbageCollected<GPUTexture>( |
| device_, device_->GetHandle().CreateTexture(&texture_descriptor_), |
| String::FromUTF8(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(); |
| } |
| |
| scoped_refptr<WebGPUMailboxTexture> |
| GPUCanvasContext::GetFrontBufferMailboxTexture() { |
| auto front_buffer_si = swap_buffers_->GetFrontBufferSharedImage(); |
| if (!front_buffer_si) { |
| return nullptr; |
| } |
| wgpu::TextureUsage front_buffer_usage = |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding; |
| wgpu::DawnTextureInternalUsageDescriptor front_buffer_usage_desc = {{ |
| .internalUsage = front_buffer_usage, |
| }}; |
| wgpu::TextureDescriptor desc = { |
| .size = {base::checked_cast<uint32_t>(front_buffer_si->size().width()), |
| base::checked_cast<uint32_t>(front_buffer_si->size().height())}, |
| .format = swap_buffers_->TextureFormat(), |
| }; |
| desc.nextInChain = &front_buffer_usage_desc; |
| |
| return WebGPUMailboxTexture::FromExistingSharedImage( |
| device_->GetDawnControlClient(), device_->GetHandle(), desc, |
| front_buffer_si, swap_buffers_->GetFrontBufferSyncToken()); |
| } |
| |
| 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_->IsDestroyed()) { |
| CopyToSwapTexture(); |
| texture_->ClearBeforeDestroyCallback(); |
| texture_->destroy(); |
| } |
| texture_ = nullptr; |
| swap_texture_ = nullptr; |
| } |
| |
| void GPUCanvasContext::InitializeLayer(cc::Layer* layer) { |
| if (Host()) { |
| Host()->InitializeLayerWithCSSProperties(layer); |
| } |
| } |
| |
| void GPUCanvasContext::SetNeedsCompositingUpdate() { |
| if (Host()) { |
| Host()->SetNeedsCompositingUpdate(); |
| } |
| } |
| |
| bool GPUCanvasContext::IsGPUDeviceDestroyed() { |
| return device_->IsDestroyed(); |
| } |
| |
| 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::TexelCopyTextureInfo source = { |
| .texture = texture_->GetHandle(), |
| .aspect = wgpu::TextureAspect::All, |
| }; |
| wgpu::TexelCopyTextureInfo 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, |
| ©_size, &options); |
| } |
| |
| bool GPUCanvasContext::CopyTextureToResourceProvider( |
| const wgpu::Texture& texture, |
| CanvasResourceProvider* resource_provider) const { |
| DCHECK(resource_provider); |
| |
| gfx::Size size(texture.GetWidth(), texture.GetHeight()); |
| 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().Has( |
| gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE)); |
| |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> shared_context_wrapper = |
| SharedGpuContext::ContextProviderWrapper(); |
| if (!shared_context_wrapper) { |
| return false; |
| } |
| |
| gpu::SyncToken sync_token; |
| auto dst_client_si = |
| resource_provider->GetBackingClientSharedImageForExternalWrite( |
| &sync_token, gpu::SharedImageUsageSet()); |
| 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); |
| |
| 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::TexelCopyTextureInfo source = { |
| .texture = texture, |
| .aspect = wgpu::TextureAspect::All, |
| }; |
| wgpu::TexelCopyTextureInfo 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. |
| wgpu::AlphaMode dstAlphaMode; |
| switch (resource_provider->GetAlphaType()) { |
| 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 = dst_client_si->surface_origin() == kBottomLeft_GrSurfaceOrigin, |
| .srcAlphaMode = srcAlphaMode, |
| .dstAlphaMode = dstAlphaMode, |
| .internalUsage = true, |
| }; |
| |
| device_->queue()->GetHandle().CopyTextureForBrowser(&source, &destination, |
| ©_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, ©_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(texture.GetWidth(), texture.GetHeight()); |
| |
| // 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( |
| size, GetSharedImageFormat(), GetAlphaType(), GetColorSpace(), |
| swap_buffers_->GetSharedImageUsagesForDisplay()); |
| if (!resource_provider) |
| return nullptr; |
| |
| if (!CopyTextureToResourceProvider(texture, resource_provider.get())) { |
| return nullptr; |
| } |
| |
| return resource_provider->Snapshot(FlushReason::kNone); |
| } |
| |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> |
| GPUCanvasContext::GetContextProviderWeakPtr() const { |
| return device_->GetDawnControlClient()->GetContextProviderWeakPtr(); |
| } |
| |
| scoped_refptr<StaticBitmapImage> |
| GPUCanvasContext::MakeFallbackStaticBitmapImage( |
| V8GPUCanvasAlphaMode::Enum alpha_mode) { |
| // 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); |
| } |
| |
| // Mark the bitmap as immutable to avoid an unnecessary copy in the |
| // following RasterFromBitmap() call. |
| black_bitmap.setImmutable(); |
| return UnacceleratedStaticBitmapImage::Create( |
| SkImages::RasterFromBitmap(black_bitmap)); |
| } |
| |
| } // namespace blink |