| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h" |
| |
| #include "base/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/process_memory_dump.h" |
| #include "build/build_config.h" |
| #include "cc/paint/decode_stashing_image_provider.h" |
| #include "cc/paint/display_item_list.h" |
| #include "cc/tiles/software_image_decode_cache.h" |
| #include "components/viz/common/resources/resource_format_utils.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/raster_interface.h" |
| #include "gpu/command_buffer/common/capabilities.h" |
| #include "gpu/config/gpu_driver_bug_workaround_type.h" |
| #include "gpu/config/gpu_feature_info.h" |
| #include "skia/buildflags.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/instrumentation/canvas_memory_dump_provider.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/gpu/GrDirectContext.h" |
| |
| namespace blink { |
| |
| class FlushForImageListener { |
| // With deferred rendering it's possible for a drawImage operation on a canvas |
| // to trigger a copy-on-write if another canvas has a read reference to it. |
| // This can cause serious regressions due to extra allocations: |
| // crbug.com/1030108. FlushForImageListener keeps a list of all active 2d |
| // contexts on a thread and notifies them when one is attempting copy-on |
| // write. If the notified context has a read reference to the canvas |
| // attempting a copy-on-write it then flushes so as to make the copy-on-write |
| // unnecessary. |
| public: |
| static FlushForImageListener* GetFlushForImageListener(); |
| void AddObserver(CanvasResourceProvider* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void RemoveObserver(CanvasResourceProvider* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void NotifyFlushForImage(cc::PaintImage::ContentId content_id) { |
| for (CanvasResourceProvider& obs : observers_) |
| obs.OnFlushForImage(content_id); |
| } |
| |
| private: |
| friend class WTF::ThreadSpecific<FlushForImageListener>; |
| base::ObserverList<CanvasResourceProvider> observers_; |
| }; |
| |
| static FlushForImageListener* GetFlushForImageListener() { |
| DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<FlushForImageListener>, |
| flush_for_image_listener, ()); |
| return flush_for_image_listener; |
| } |
| |
| namespace { |
| |
| bool IsGMBAllowed(IntSize size, |
| const CanvasResourceParams& params, |
| const gpu::Capabilities& caps) { |
| return gpu::IsImageSizeValidForGpuMemoryBufferFormat( |
| gfx::Size(size), params.GetBufferFormat(), |
| gfx::BufferPlane::DEFAULT) && |
| gpu::IsImageFromGpuMemoryBufferFormatSupported( |
| params.GetBufferFormat(), caps); |
| } |
| |
| } // namespace |
| |
| class CanvasResourceProvider::CanvasImageProvider : public cc::ImageProvider { |
| public: |
| CanvasImageProvider(cc::ImageDecodeCache* cache_n32, |
| cc::ImageDecodeCache* cache_f16, |
| const gfx::ColorSpace& target_color_space, |
| SkColorType target_color_type, |
| cc::PlaybackImageProvider::RasterMode raster_mode); |
| CanvasImageProvider(const CanvasImageProvider&) = delete; |
| CanvasImageProvider& operator=(const CanvasImageProvider&) = delete; |
| ~CanvasImageProvider() override = default; |
| |
| // cc::ImageProvider implementation. |
| cc::ImageProvider::ScopedResult GetRasterContent( |
| const cc::DrawImage&) override; |
| |
| void ReleaseLockedImages() { locked_images_.clear(); } |
| |
| private: |
| void CanUnlockImage(ScopedResult); |
| void CleanupLockedImages(); |
| bool IsHardwareDecodeCache() const; |
| |
| cc::PlaybackImageProvider::RasterMode raster_mode_; |
| bool cleanup_task_pending_ = false; |
| Vector<ScopedResult> locked_images_; |
| absl::optional<cc::PlaybackImageProvider> playback_image_provider_n32_; |
| absl::optional<cc::PlaybackImageProvider> playback_image_provider_f16_; |
| |
| base::WeakPtrFactory<CanvasImageProvider> weak_factory_{this}; |
| }; |
| |
| // * Renders to a Skia RAM-backed bitmap. |
| // * Mailboxing is not supported : cannot be directly composited. |
| class CanvasResourceProviderBitmap : public CanvasResourceProvider { |
| public: |
| CanvasResourceProviderBitmap( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) |
| : CanvasResourceProvider(kBitmap, |
| size, |
| filter_quality, |
| params, |
| true /*is_origin_top_left*/, |
| nullptr /*context_provider_wrapper*/, |
| std::move(resource_dispatcher)) {} |
| |
| ~CanvasResourceProviderBitmap() override = default; |
| |
| bool IsValid() const final { return GetSkSurface(); } |
| bool IsAccelerated() const final { return false; } |
| bool SupportsDirectCompositing() const override { return false; } |
| |
| private: |
| scoped_refptr<CanvasResource> ProduceCanvasResource() override { |
| return nullptr; // Does not support direct compositing |
| } |
| |
| scoped_refptr<StaticBitmapImage> Snapshot( |
| const ImageOrientation& orientation) override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderBitmap::Snapshot"); |
| return SnapshotInternal(orientation); |
| } |
| |
| sk_sp<SkSurface> CreateSkSurface() const override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderBitmap::CreateSkSurface"); |
| |
| SkImageInfo info = SkImageInfo::Make( |
| Size().Width(), Size().Height(), ColorParams().GetSkColorType(), |
| kPremul_SkAlphaType, ColorParams().GetSkColorSpace()); |
| SkSurfaceProps props = ColorParams().GetSkSurfaceProps(); |
| return SkSurface::MakeRaster(info, &props); |
| } |
| }; |
| |
| // * Renders to a shared memory bitmap. |
| // * Uses SharedBitmaps to pass frames directly to the compositor. |
| class CanvasResourceProviderSharedBitmap : public CanvasResourceProviderBitmap { |
| public: |
| CanvasResourceProviderSharedBitmap( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) |
| : CanvasResourceProviderBitmap(size, |
| filter_quality, |
| params, |
| std::move(resource_dispatcher)) { |
| DCHECK(ResourceDispatcher()); |
| type_ = kSharedBitmap; |
| } |
| ~CanvasResourceProviderSharedBitmap() override = default; |
| bool SupportsDirectCompositing() const override { return true; } |
| |
| private: |
| scoped_refptr<CanvasResource> CreateResource() final { |
| CanvasResourceParams params = ColorParams(); |
| if (!IsBitmapFormatSupported(params.TransferableResourceFormat())) { |
| // If the rendering format is not supported, downgrate to 8-bits. |
| // TODO(junov): Should we try 12-12-12-12 and 10-10-10-2? |
| params.SetSkColorType(kN32_SkColorType); |
| } |
| |
| return CanvasResourceSharedBitmap::Create(Size(), params, CreateWeakPtr(), |
| FilterQuality()); |
| } |
| |
| scoped_refptr<CanvasResource> ProduceCanvasResource() final { |
| DCHECK(GetSkSurface()); |
| scoped_refptr<CanvasResource> output_resource = NewOrRecycledResource(); |
| if (!output_resource) |
| return nullptr; |
| |
| auto paint_image = MakeImageSnapshot(); |
| if (!paint_image) |
| return nullptr; |
| DCHECK(!paint_image.IsTextureBacked()); |
| |
| output_resource->TakeSkImage(paint_image.GetSwSkImage()); |
| |
| return output_resource; |
| } |
| }; |
| |
| // * Renders to a SharedImage, which manages memory internally. |
| // * Layers are overlay candidates. |
| class CanvasResourceProviderSharedImage : public CanvasResourceProvider { |
| public: |
| CanvasResourceProviderSharedImage( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> |
| context_provider_wrapper, |
| bool is_origin_top_left, |
| bool is_accelerated, |
| bool skia_use_dawn, |
| uint32_t shared_image_usage_flags) |
| : CanvasResourceProvider( |
| skia_use_dawn ? kSkiaDawnSharedImage : kSharedImage, |
| size, |
| filter_quality, |
| params, |
| is_origin_top_left, |
| std::move(context_provider_wrapper), |
| nullptr /* resource_dispatcher */), |
| is_accelerated_(is_accelerated), |
| shared_image_usage_flags_(shared_image_usage_flags), |
| use_oop_rasterization_(is_accelerated && ContextProviderWrapper() |
| ->ContextProvider() |
| ->GetCapabilities() |
| .supports_oop_raster) { |
| resource_ = NewOrRecycledResource(); |
| GetFlushForImageListener()->AddObserver(this); |
| |
| if (resource_) |
| EnsureWriteAccess(); |
| } |
| |
| ~CanvasResourceProviderSharedImage() override { |
| GetFlushForImageListener()->RemoveObserver(this); |
| } |
| |
| bool IsAccelerated() const final { return is_accelerated_; } |
| bool SupportsDirectCompositing() const override { return true; } |
| bool IsValid() const final { |
| if (!use_oop_rasterization_) |
| return GetSkSurface() && !IsGpuContextLost(); |
| else |
| return !IsGpuContextLost(); |
| } |
| |
| bool SupportsSingleBuffering() const override { |
| return shared_image_usage_flags_ & |
| gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE; |
| } |
| gpu::Mailbox GetBackingMailboxForOverwrite( |
| MailboxSyncMode sync_mode) override { |
| DCHECK(is_accelerated_); |
| |
| if (IsGpuContextLost()) |
| return gpu::Mailbox(); |
| |
| WillDrawInternal(false); |
| return resource_->GetOrCreateGpuMailbox(sync_mode); |
| } |
| |
| GLenum GetBackingTextureTarget() const override { |
| return resource()->TextureTarget(); |
| } |
| |
| uint32_t GetSharedImageUsageFlags() const override { |
| return shared_image_usage_flags_; |
| } |
| |
| bool WritePixels(const SkImageInfo& orig_info, |
| const void* pixels, |
| size_t row_bytes, |
| int x, |
| int y) override { |
| if (!use_oop_rasterization_) { |
| return CanvasResourceProvider::WritePixels(orig_info, pixels, row_bytes, |
| x, y); |
| } |
| |
| TRACE_EVENT0("blink", "CanvasResourceProviderSharedImage::WritePixels"); |
| if (IsGpuContextLost()) |
| return false; |
| |
| WillDrawInternal(true); |
| RasterInterface()->WritePixels( |
| GetBackingMailboxForOverwrite(kOrderingBarrier), x, y, |
| GetBackingTextureTarget(), row_bytes, orig_info, pixels); |
| return true; |
| } |
| |
| scoped_refptr<CanvasResource> CreateResource() final { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSharedImage::CreateResource"); |
| if (IsGpuContextLost()) |
| return nullptr; |
| |
| #if BUILDFLAG(SKIA_USE_DAWN) |
| if (type_ == kSkiaDawnSharedImage) { |
| return CanvasResourceSkiaDawnSharedImage::Create( |
| Size(), ContextProviderWrapper(), CreateWeakPtr(), FilterQuality(), |
| ColorParams(), IsOriginTopLeft(), shared_image_usage_flags_); |
| } |
| #endif |
| |
| return CanvasResourceRasterSharedImage::Create( |
| Size(), ContextProviderWrapper(), CreateWeakPtr(), FilterQuality(), |
| ColorParams(), IsOriginTopLeft(), is_accelerated_, |
| shared_image_usage_flags_); |
| } |
| |
| bool UseOopRasterization() final { return use_oop_rasterization_; } |
| |
| void NotifyTexParamsModified(const CanvasResource* resource) override { |
| if (!is_accelerated_ || use_oop_rasterization_) |
| return; |
| |
| if (resource_.get() == resource) { |
| DCHECK(!current_resource_has_write_access_); |
| // Note that the call below is guarenteed to not issue any GPU work for |
| // the backend texture since we ensure that all skia work on the resource |
| // is issued before releasing write access. |
| surface_->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess) |
| .glTextureParametersModified(); |
| } |
| } |
| |
| void RestoreBackBuffer(const cc::PaintImage& image) override { |
| if (!use_oop_rasterization_) { |
| CanvasResourceProvider::RestoreBackBuffer(image); |
| return; |
| } |
| RestoreBackBufferOOP(image); |
| } |
| |
| protected: |
| scoped_refptr<CanvasResource> ProduceCanvasResource() override { |
| TRACE_EVENT0("blink", |
| "CanvasResourceProviderSharedImage::ProduceCanvasResource"); |
| if (IsGpuContextLost()) |
| return nullptr; |
| |
| FlushCanvas(); |
| // Its important to end read access and ref the resource before the WillDraw |
| // call below. Since it relies on resource ref-count to trigger |
| // copy-on-write and asserts that we only have write access when the |
| // provider has the only ref to the resource, to ensure there are no other |
| // readers. |
| EndWriteAccess(); |
| scoped_refptr<CanvasResource> resource = resource_; |
| resource->SetFilterQuality(FilterQuality()); |
| if (ContextProviderWrapper() |
| ->ContextProvider() |
| ->GetCapabilities() |
| .disable_2d_canvas_copy_on_write) { |
| // A readback operation may alter the texture parameters, which may affect |
| // the compositor's behavior. Therefore, we must trigger copy-on-write |
| // even though we are not technically writing to the texture, only to its |
| // parameters. This issue is Android-WebView specific: crbug.com/585250. |
| WillDraw(); |
| } |
| |
| return resource; |
| } |
| |
| scoped_refptr<StaticBitmapImage> Snapshot( |
| const ImageOrientation& orientation) override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSharedImage::Snapshot"); |
| if (!IsValid()) |
| return nullptr; |
| |
| // We don't need to EndWriteAccess here since that's required to make the |
| // rendering results visible on the GpuMemoryBuffer while we return cpu |
| // memory, rendererd to by skia, here. |
| if (!is_accelerated_) |
| return SnapshotInternal(orientation); |
| |
| if (!cached_snapshot_) { |
| FlushCanvas(); |
| EndWriteAccess(); |
| cached_snapshot_ = resource_->Bitmap(); |
| |
| // We'll record its content_id to be used by the FlushForImageListener. |
| // This will be needed in WillDrawInternal, but we are doing it now, as we |
| // don't know if later on we will be in the same thread the |
| // cached_snapshot_ was created and we wouldn't be able to |
| // PaintImageForCurrentFrame in AcceleratedStaticBitmapImage just to check |
| // the content_id. ShouldReplaceTargetBuffer needs this ID in order to let |
| // other contexts know to flush to avoid unnecessary copy-on-writes. |
| if (cached_snapshot_) { |
| cached_content_id_ = |
| cached_snapshot_->PaintImageForCurrentFrame().GetContentIdForFrame( |
| 0u); |
| } |
| } |
| |
| DCHECK(cached_snapshot_); |
| DCHECK(!current_resource_has_write_access_); |
| return cached_snapshot_; |
| } |
| |
| void WillDrawIfNeeded() final { |
| if (cached_snapshot_) { |
| WillDraw(); |
| } |
| } |
| |
| void WillDrawInternal(bool write_to_local_texture) { |
| DCHECK(resource_); |
| |
| if (IsGpuContextLost()) |
| return; |
| |
| // Since the resource will be updated, the cached snapshot is no longer |
| // valid. Note that it is important to release this reference here to not |
| // trigger copy-on-write below from the resource ref in the snapshot. |
| // Note that this is valid for single buffered mode also, since while the |
| // resource/mailbox remains the same, the snapshot needs an updated sync |
| // token for these writes. |
| cached_snapshot_.reset(); |
| |
| // We don't need to do copy-on-write for the resource here since writes to |
| // the GMB are deferred until it needs to be dispatched to the display |
| // compositor via ProduceCanvasResource. |
| if (is_accelerated_ && ShouldReplaceTargetBuffer(cached_content_id_)) { |
| cached_content_id_ = PaintImage::kInvalidContentId; |
| DCHECK(!current_resource_has_write_access_) |
| << "Write access must be released before sharing the resource"; |
| |
| auto old_resource = std::move(resource_); |
| auto* old_resource_shared_image = |
| static_cast<CanvasResourceSharedImage*>(old_resource.get()); |
| resource_ = NewOrRecycledResource(); |
| DCHECK(resource_); |
| |
| auto* raster_interface = RasterInterface(); |
| if (raster_interface) { |
| if (!use_oop_rasterization_) |
| TearDownSkSurface(); |
| |
| if (mode_ == SkSurface::kRetain_ContentChangeMode) { |
| auto old_mailbox = old_resource_shared_image->GetOrCreateGpuMailbox( |
| kOrderingBarrier); |
| auto mailbox = resource()->GetOrCreateGpuMailbox(kOrderingBarrier); |
| |
| raster_interface->CopySubTexture( |
| old_mailbox, mailbox, GetBackingTextureTarget(), 0, 0, 0, 0, |
| Size().Width(), Size().Height(), false /* unpack_flip_y */, |
| false /* unpack_premultiply_alpha */); |
| } else if (use_oop_rasterization_) { |
| // If we're not copying over the previous contents, we need to ensure |
| // that the image is cleared on the next BeginRasterCHROMIUM. |
| is_cleared_ = false; |
| } |
| |
| // In non-OOPR mode we need to update the client side SkSurface with the |
| // copied texture. Recreating SkSurface here matches the GPU process |
| // behaviour that will happen in OOPR mode. |
| if (!use_oop_rasterization_) |
| GetSkSurface(); |
| } else { |
| EnsureWriteAccess(); |
| if (surface_) { |
| // Take read access to the outgoing resource for the skia copy below. |
| if (!old_resource_shared_image->HasReadAccess()) { |
| old_resource_shared_image->BeginReadAccess(); |
| } |
| surface_->replaceBackendTexture(CreateGrTextureForResource(), |
| GetGrSurfaceOrigin(), mode_); |
| if (!old_resource_shared_image->HasReadAccess()) { |
| old_resource_shared_image->EndReadAccess(); |
| } |
| } |
| } |
| UMA_HISTOGRAM_BOOLEAN("Blink.Canvas.ContentChangeMode", |
| mode_ == SkSurface::kRetain_ContentChangeMode); |
| mode_ = SkSurface::kRetain_ContentChangeMode; |
| } |
| |
| if (write_to_local_texture) |
| EnsureWriteAccess(); |
| else |
| EndWriteAccess(); |
| |
| resource()->WillDraw(); |
| } |
| |
| void WillDraw() override { WillDrawInternal(true); } |
| |
| void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override { |
| if (!use_oop_rasterization_) { |
| CanvasResourceProvider::RasterRecord(std::move(last_recording)); |
| return; |
| } |
| WillDrawInternal(true); |
| const bool needs_clear = !is_cleared_; |
| is_cleared_ = true; |
| RasterRecordOOP(last_recording, needs_clear, |
| resource()->GetOrCreateGpuMailbox(kUnverifiedSyncToken)); |
| } |
| |
| bool ShouldReplaceTargetBuffer( |
| PaintImage::ContentId content_id = PaintImage::kInvalidContentId) { |
| // If the canvas is single buffered, concurrent read/writes to the resource |
| // are allowed. Note that we ignore the resource lost case as well since |
| // that only indicates that we did not get a sync token for read/write |
| // synchronization which is not a requirement for single buffered canvas. |
| if (IsSingleBuffered()) |
| return false; |
| |
| // If the resource was lost, we can not use it for writes again. |
| if (resource()->IsLost()) |
| return true; |
| |
| // We have the only ref to the resource which implies there are no active |
| // readers. |
| if (resource_->HasOneRef()) |
| return false; |
| |
| // Its possible to have deferred work in skia which uses this resource. Try |
| // flushing once to see if that releases the read refs. We can avoid a copy |
| // by queuing this work before writing to this resource. |
| if (is_accelerated_) { |
| // Another context may have a read reference to this resource. Flush the |
| // deferred queue in that context so that we don't need to copy. |
| GetFlushForImageListener()->NotifyFlushForImage(content_id); |
| |
| if (!use_oop_rasterization_) |
| surface_->flushAndSubmit(); |
| } |
| |
| return !resource_->HasOneRef(); |
| } |
| |
| sk_sp<SkSurface> CreateSkSurface() const override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSharedImage::CreateSkSurface"); |
| if (IsGpuContextLost() || !resource_) |
| return nullptr; |
| |
| SkSurfaceProps props = ColorParams().GetSkSurfaceProps(); |
| if (is_accelerated_) { |
| return SkSurface::MakeFromBackendTexture( |
| GetGrContext(), CreateGrTextureForResource(), GetGrSurfaceOrigin(), |
| 0 /* msaa_sample_count */, ColorParams().GetSkColorType(), |
| ColorParams().GetSkColorSpace(), &props); |
| } |
| |
| // For software raster path, we render into cpu memory managed internally |
| // by SkSurface and copy the rendered results to the GMB before dispatching |
| // it to the display compositor. |
| return SkSurface::MakeRaster(resource_->CreateSkImageInfo(), &props); |
| } |
| |
| GrBackendTexture CreateGrTextureForResource() const { |
| DCHECK(is_accelerated_); |
| |
| return resource()->CreateGrTexture(); |
| } |
| |
| void FlushGrContext() { |
| DCHECK(is_accelerated_); |
| |
| // The resource may have been imported and used in skia. Make sure any |
| // operations using this resource are flushed to the underlying context. |
| // Note that its not sufficient to flush the SkSurface here since it will |
| // only perform a GrContext flush if that SkSurface has any pending ops. And |
| // this resource may be written to or read from skia without using the |
| // SkSurface here. |
| if (IsGpuContextLost()) |
| return; |
| GetGrContext()->flushAndSubmit(); |
| } |
| |
| void EnsureWriteAccess() { |
| DCHECK(resource_); |
| // In software mode, we don't need write access to the resource during |
| // drawing since it is executed on cpu memory managed by skia. We ensure |
| // exclusive access to the resource when the results are copied onto the |
| // GMB in EndWriteAccess. |
| DCHECK(resource_->HasOneRef() || IsSingleBuffered() || !is_accelerated_) |
| << "Write access requires exclusive access to the resource"; |
| DCHECK(!resource()->is_cross_thread()) |
| << "Write access is only allowed on the owning thread"; |
| |
| if (current_resource_has_write_access_ || IsGpuContextLost()) |
| return; |
| |
| if (is_accelerated_ && !use_oop_rasterization_) { |
| resource()->BeginWriteAccess(); |
| } |
| |
| // For the non-accelerated path, we don't need a texture for writes since |
| // its on the CPU, but we set this bit to know whether the GMB needs to be |
| // updated. |
| current_resource_has_write_access_ = true; |
| } |
| |
| void EndWriteAccess() { |
| DCHECK(!resource()->is_cross_thread()); |
| |
| if (!current_resource_has_write_access_ || IsGpuContextLost()) |
| return; |
| |
| if (is_accelerated_) { |
| // We reset |mode_| here since the draw commands which overwrite the |
| // complete canvas must have been flushed at this point without triggering |
| // copy-on-write. |
| mode_ = SkSurface::kRetain_ContentChangeMode; |
| |
| if (!use_oop_rasterization_) { |
| // Issue any skia work using this resource before releasing write |
| // access. |
| FlushGrContext(); |
| resource()->EndWriteAccess(); |
| } |
| } else { |
| // Currently we never use OOP raster when the resource is not accelerated |
| // so we check that assumption here. |
| DCHECK(!use_oop_rasterization_); |
| if (ShouldReplaceTargetBuffer()) |
| resource_ = NewOrRecycledResource(); |
| resource()->CopyRenderingResultsToGpuMemoryBuffer( |
| surface_->makeImageSnapshot()); |
| } |
| |
| current_resource_has_write_access_ = false; |
| } |
| |
| CanvasResourceSharedImage* resource() { |
| return static_cast<CanvasResourceSharedImage*>(resource_.get()); |
| } |
| const CanvasResourceSharedImage* resource() const { |
| return static_cast<const CanvasResourceSharedImage*>(resource_.get()); |
| } |
| |
| // For WebGpu RecyclableCanvasResource. |
| void OnAcquireRecyclableCanvasResource() override { EnsureWriteAccess(); } |
| void OnDestroyRecyclableCanvasResource( |
| const gpu::SyncToken& sync_token) override { |
| // RecyclableCanvasResource should be the only one that holds onto |
| // |resource_|. |
| DCHECK(resource_->HasOneRef()); |
| resource_->WaitSyncToken(sync_token); |
| } |
| |
| const bool is_accelerated_; |
| const uint32_t shared_image_usage_flags_; |
| bool current_resource_has_write_access_ = false; |
| const bool use_oop_rasterization_; |
| bool is_cleared_ = false; |
| scoped_refptr<CanvasResource> resource_; |
| scoped_refptr<StaticBitmapImage> cached_snapshot_; |
| PaintImage::ContentId cached_content_id_ = PaintImage::kInvalidContentId; |
| }; |
| |
| // This class does nothing except answering to ProduceCanvasResource() by piping |
| // it to NewOrRecycledResource(). This ResourceProvider is meant to be used |
| // with an imported external CanvasResource, and all drawing and lifetime logic |
| // must be kept at a higher level. |
| class CanvasResourceProviderPassThrough final : public CanvasResourceProvider { |
| public: |
| CanvasResourceProviderPassThrough( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> |
| context_provider_wrapper, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher, |
| bool is_origin_top_left) |
| : CanvasResourceProvider(kPassThrough, |
| size, |
| filter_quality, |
| params, |
| is_origin_top_left, |
| std::move(context_provider_wrapper), |
| std::move(resource_dispatcher)) {} |
| |
| ~CanvasResourceProviderPassThrough() override = default; |
| bool IsValid() const final { return true; } |
| bool IsAccelerated() const final { return true; } |
| bool SupportsDirectCompositing() const override { return true; } |
| bool SupportsSingleBuffering() const override { return true; } |
| |
| private: |
| scoped_refptr<CanvasResource> CreateResource() final { |
| // This class has no CanvasResource to provide: this must be imported via |
| // ImportResource() and kept in the parent class. |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| scoped_refptr<CanvasResource> ProduceCanvasResource() final { |
| return NewOrRecycledResource(); |
| } |
| |
| sk_sp<SkSurface> CreateSkSurface() const override { |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| scoped_refptr<StaticBitmapImage> Snapshot(const ImageOrientation&) override { |
| auto resource = GetImportedResource(); |
| if (IsGpuContextLost() || !resource) |
| return nullptr; |
| return resource->Bitmap(); |
| } |
| }; |
| |
| // * Renders to back buffer of a shared image swap chain. |
| // * Presents swap chain and exports front buffer mailbox to compositor to |
| // support low latency mode. |
| // * Layers are overlay candidates. |
| class CanvasResourceProviderSwapChain final : public CanvasResourceProvider { |
| public: |
| CanvasResourceProviderSwapChain( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> |
| context_provider_wrapper, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) |
| : CanvasResourceProvider(kSwapChain, |
| size, |
| filter_quality, |
| params, |
| true /*is_origin_top_left*/, |
| std::move(context_provider_wrapper), |
| std::move(resource_dispatcher)), |
| use_oop_rasterization_(ContextProviderWrapper() |
| ->ContextProvider() |
| ->GetCapabilities() |
| .supports_oop_raster) { |
| resource_ = CanvasResourceSwapChain::Create( |
| Size(), ColorParams(), ContextProviderWrapper(), CreateWeakPtr(), |
| FilterQuality()); |
| // CanvasResourceProviderSwapChain can only operate in a single buffered |
| // mode so enable it as soon as possible. |
| TryEnableSingleBuffering(); |
| DCHECK(IsSingleBuffered()); |
| } |
| ~CanvasResourceProviderSwapChain() override = default; |
| |
| bool IsValid() const final { |
| if (!use_oop_rasterization_) |
| return GetSkSurface() && !IsGpuContextLost(); |
| else |
| return !IsGpuContextLost(); |
| } |
| |
| bool IsAccelerated() const final { return true; } |
| bool SupportsDirectCompositing() const override { return true; } |
| bool SupportsSingleBuffering() const override { return true; } |
| |
| private: |
| void WillDraw() override { |
| needs_present_ = true; |
| needs_flush_ = true; |
| } |
| |
| scoped_refptr<CanvasResource> CreateResource() final { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::CreateResource"); |
| return resource_; |
| } |
| |
| scoped_refptr<CanvasResource> ProduceCanvasResource() override { |
| DCHECK(IsSingleBuffered()); |
| TRACE_EVENT0("blink", |
| "CanvasResourceProviderSwapChain::ProduceCanvasResource"); |
| if (!IsValid()) |
| return nullptr; |
| |
| FlushIfNeeded(); |
| |
| if (needs_present_) { |
| resource_->PresentSwapChain(); |
| needs_present_ = false; |
| } |
| return resource_; |
| } |
| |
| scoped_refptr<StaticBitmapImage> Snapshot(const ImageOrientation&) override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::Snapshot"); |
| |
| if (!IsValid()) |
| return nullptr; |
| |
| FlushIfNeeded(); |
| |
| return resource_->Bitmap(); |
| } |
| |
| sk_sp<SkSurface> CreateSkSurface() const override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::CreateSkSurface"); |
| if (IsGpuContextLost() || !resource_) |
| return nullptr; |
| |
| GrGLTextureInfo texture_info = {}; |
| texture_info.fID = resource_->GetBackBufferTextureId(); |
| texture_info.fTarget = resource_->TextureTarget(); |
| texture_info.fFormat = ColorParams().GLSizedInternalFormat(); |
| |
| auto backend_texture = GrBackendTexture(Size().Width(), Size().Height(), |
| GrMipMapped::kNo, texture_info); |
| |
| SkSurfaceProps props = ColorParams().GetSkSurfaceProps(); |
| return SkSurface::MakeFromBackendTexture( |
| GetGrContext(), backend_texture, kTopLeft_GrSurfaceOrigin, |
| 0 /* msaa_sample_count */, ColorParams().GetSkColorType(), |
| ColorParams().GetSkColorSpace(), &props); |
| } |
| |
| void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::RasterRecord"); |
| if (!use_oop_rasterization_) { |
| CanvasResourceProvider::RasterRecord(std::move(last_recording)); |
| return; |
| } |
| WillDraw(); |
| RasterRecordOOP(last_recording, initial_needs_clear_, |
| resource_->GetBackBufferMailbox()); |
| initial_needs_clear_ = false; |
| } |
| |
| void RestoreBackBuffer(const cc::PaintImage& image) override { |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::RestoreBackBuffer"); |
| if (!use_oop_rasterization_) { |
| CanvasResourceProvider::RestoreBackBuffer(image); |
| return; |
| } |
| RestoreBackBufferOOP(image); |
| } |
| |
| bool UseOopRasterization() final { return use_oop_rasterization_; } |
| |
| bool WritePixels(const SkImageInfo& orig_info, |
| const void* pixels, |
| size_t row_bytes, |
| int x, |
| int y) override { |
| if (!use_oop_rasterization_) { |
| return CanvasResourceProvider::WritePixels(orig_info, pixels, row_bytes, |
| x, y); |
| } |
| |
| TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::WritePixels"); |
| if (IsGpuContextLost()) |
| return false; |
| |
| WillDraw(); |
| RasterInterface()->WritePixels(resource_->GetBackBufferMailbox(), x, y, |
| GetBackingTextureTarget(), row_bytes, |
| orig_info, pixels); |
| return true; |
| } |
| |
| void FlushIfNeeded() { |
| if (needs_flush_) { |
| // This only flushes recorded draw ops. |
| FlushCanvas(); |
| // Call flushAndSubmit() explicitly so that any non-draw-op rendering by |
| // Skia is flushed to GL. This is needed specifically for WritePixels(). |
| if (!use_oop_rasterization_) |
| GetGrContext()->flushAndSubmit(); |
| |
| needs_flush_ = false; |
| } |
| } |
| |
| bool needs_present_ = false; |
| bool needs_flush_ = false; |
| const bool use_oop_rasterization_; |
| // This only matters for the initial backbuffer mailbox, since the frontbuffer |
| // will always have the back texture copied to it prior to any new commands. |
| bool initial_needs_clear_ = true; |
| scoped_refptr<CanvasResourceSwapChain> resource_; |
| }; |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreateBitmapProvider( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| ShouldInitialize should_initialize) { |
| auto provider = std::make_unique<CanvasResourceProviderBitmap>( |
| size, filter_quality, params, nullptr /*resource_dispatcher*/); |
| if (provider->IsValid()) { |
| if (should_initialize == |
| CanvasResourceProvider::ShouldInitialize::kCallClear) |
| provider->Clear(); |
| return provider; |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreateSharedBitmapProvider( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| ShouldInitialize should_initialize, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) { |
| // SharedBitmapProvider has to have a valid resource_dispatecher to be able to |
| // be created. |
| if (!resource_dispatcher) |
| return nullptr; |
| |
| auto provider = std::make_unique<CanvasResourceProviderSharedBitmap>( |
| size, filter_quality, params, std::move(resource_dispatcher)); |
| if (provider->IsValid()) { |
| if (should_initialize == |
| CanvasResourceProvider::ShouldInitialize::kCallClear) |
| provider->Clear(); |
| return provider; |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreateSharedImageProvider( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| ShouldInitialize should_initialize, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper, |
| RasterMode raster_mode, |
| bool is_origin_top_left, |
| uint32_t shared_image_usage_flags) { |
| // IsGpuCompositingEnabled can re-create the context if it has been lost, do |
| // this up front so that we can fail early and not expose ourselves to |
| // use after free bugs (crbug.com/1126424) |
| const bool is_gpu_compositing_enabled = |
| SharedGpuContext::IsGpuCompositingEnabled(); |
| |
| // If the context is lost we don't want to re-create it here, the resulting |
| // resource provider would be invalid anyway |
| if (!context_provider_wrapper || |
| context_provider_wrapper->ContextProvider()->IsContextLost()) |
| return nullptr; |
| |
| const auto& capabilities = |
| context_provider_wrapper->ContextProvider()->GetCapabilities(); |
| bool skia_use_dawn = |
| raster_mode == RasterMode::kGPU && |
| base::FeatureList::IsEnabled(blink::features::kDawn2dCanvas); |
| // TODO(senorblanco): once Dawn reports maximum texture size, Dawn Canvas |
| // should respect it. http://crbug.com/1082760 |
| if (!skia_use_dawn && (size.Width() < 1 || size.Height() < 1 || |
| size.Width() > capabilities.max_texture_size || |
| size.Height() > capabilities.max_texture_size)) { |
| return nullptr; |
| } |
| |
| const bool is_accelerated = raster_mode == RasterMode::kGPU; |
| |
| CanvasResourceParams adjusted_params = params; |
| // TODO(https://crbug.com/1210946): Pass in params as is for all cases. |
| // Overriding the params to use RGBA instead of N32 is needed because code |
| // elsewhere assumes RGBA. OTOH the software path seems to be assuming N32 |
| // somewhere in the later pipeline but for offscreen canvas only. |
| if (!(shared_image_usage_flags & gpu::SHARED_IMAGE_USAGE_WEBGPU)) { |
| adjusted_params = CanvasResourceParams( |
| params.ColorSpace(), |
| is_accelerated && params.GetSkColorType() != kRGBA_F16_SkColorType |
| ? kRGBA_8888_SkColorType |
| : params.GetSkColorType(), |
| params.GetSkAlphaType()); |
| } |
| |
| const bool is_gpu_memory_buffer_image_allowed = |
| is_gpu_compositing_enabled && |
| IsGMBAllowed(size, adjusted_params, capabilities) && |
| Platform::Current()->GetGpuMemoryBufferManager(); |
| |
| if (raster_mode == RasterMode::kCPU && !is_gpu_memory_buffer_image_allowed) |
| return nullptr; |
| |
| // If we cannot use overlay, we have to remove the scanout flag and the |
| // concurrent read write flag. |
| if (!is_gpu_memory_buffer_image_allowed || |
| (is_accelerated && !capabilities.texture_storage_image)) { |
| shared_image_usage_flags &= ~gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE; |
| shared_image_usage_flags &= ~gpu::SHARED_IMAGE_USAGE_SCANOUT; |
| } |
| |
| auto provider = std::make_unique<CanvasResourceProviderSharedImage>( |
| size, filter_quality, adjusted_params, context_provider_wrapper, |
| is_origin_top_left, is_accelerated, skia_use_dawn, |
| shared_image_usage_flags); |
| if (provider->IsValid()) { |
| if (should_initialize == |
| CanvasResourceProvider::ShouldInitialize::kCallClear) |
| provider->Clear(); |
| return provider; |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreateWebGPUImageProvider( |
| const IntSize& size, |
| const CanvasResourceParams& params, |
| bool is_origin_top_left) { |
| auto context_provider_wrapper = SharedGpuContext::ContextProviderWrapper(); |
| return CreateSharedImageProvider( |
| size, kLow_SkFilterQuality, params, |
| CanvasResourceProvider::ShouldInitialize::kNo, |
| std::move(context_provider_wrapper), RasterMode::kGPU, is_origin_top_left, |
| gpu::SHARED_IMAGE_USAGE_WEBGPU); |
| } |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreatePassThroughProvider( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher, |
| bool is_origin_top_left) { |
| // SharedGpuContext::IsGpuCompositingEnabled can potentially replace the |
| // context_provider_wrapper, so it's important to call that first as it can |
| // invalidate the weak pointer. |
| if (!SharedGpuContext::IsGpuCompositingEnabled() || !context_provider_wrapper) |
| return nullptr; |
| |
| const auto& capabilities = |
| context_provider_wrapper->ContextProvider()->GetCapabilities(); |
| if (size.Width() > capabilities.max_texture_size || |
| size.Height() > capabilities.max_texture_size) { |
| return nullptr; |
| } |
| |
| // Either swap_chain or gpu memory buffer should be enabled for this be used |
| if (!capabilities.shared_image_swap_chain && |
| (!IsGMBAllowed(size, params, capabilities) || |
| !Platform::Current()->GetGpuMemoryBufferManager())) |
| return nullptr; |
| |
| auto provider = std::make_unique<CanvasResourceProviderPassThrough>( |
| size, filter_quality, params, context_provider_wrapper, |
| resource_dispatcher, is_origin_top_left); |
| if (provider->IsValid()) { |
| // All the other type of resources are doing a clear here. As a |
| // CanvasResourceProvider of type PassThrough is used to delegate the |
| // internal parts of the resource and provider to other classes, we should |
| // not attempt to do a clear here. clear is not needed here. |
| return provider; |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<CanvasResourceProvider> |
| CanvasResourceProvider::CreateSwapChainProvider( |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| ShouldInitialize should_initialize, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher, |
| bool is_origin_top_left) { |
| DCHECK(is_origin_top_left); |
| // SharedGpuContext::IsGpuCompositingEnabled can potentially replace the |
| // context_provider_wrapper, so it's important to call that first as it can |
| // invalidate the weak pointer. |
| if (!SharedGpuContext::IsGpuCompositingEnabled() || !context_provider_wrapper) |
| return nullptr; |
| |
| const auto& capabilities = |
| context_provider_wrapper->ContextProvider()->GetCapabilities(); |
| if (size.Width() > capabilities.max_texture_size || |
| size.Height() > capabilities.max_texture_size || |
| !capabilities.shared_image_swap_chain) { |
| return nullptr; |
| } |
| |
| auto provider = std::make_unique<CanvasResourceProviderSwapChain>( |
| size, filter_quality, params, context_provider_wrapper, |
| resource_dispatcher); |
| if (provider->IsValid()) { |
| if (should_initialize == |
| CanvasResourceProvider::ShouldInitialize::kCallClear) |
| provider->Clear(); |
| return provider; |
| } |
| |
| return nullptr; |
| } |
| |
| CanvasResourceProvider::CanvasImageProvider::CanvasImageProvider( |
| cc::ImageDecodeCache* cache_n32, |
| cc::ImageDecodeCache* cache_f16, |
| const gfx::ColorSpace& target_color_space, |
| SkColorType canvas_color_type, |
| cc::PlaybackImageProvider::RasterMode raster_mode) |
| : raster_mode_(raster_mode) { |
| absl::optional<cc::PlaybackImageProvider::Settings> settings = |
| cc::PlaybackImageProvider::Settings(); |
| settings->raster_mode = raster_mode_; |
| |
| playback_image_provider_n32_.emplace(cache_n32, target_color_space, |
| std::move(settings)); |
| // If the image provider may require to decode to half float instead of |
| // uint8, create a f16 PlaybackImageProvider with the passed cache. |
| if (canvas_color_type == kRGBA_F16_SkColorType) { |
| DCHECK(cache_f16); |
| settings = cc::PlaybackImageProvider::Settings(); |
| settings->raster_mode = raster_mode_; |
| playback_image_provider_f16_.emplace(cache_f16, target_color_space, |
| std::move(settings)); |
| } |
| } |
| |
| cc::ImageProvider::ScopedResult |
| CanvasResourceProvider::CanvasImageProvider::GetRasterContent( |
| const cc::DrawImage& draw_image) { |
| // TODO(xidachen): Ensure this function works for paint worklet generated |
| // images. |
| // If we like to decode high bit depth image source to half float backed |
| // image, we need to sniff the image bit depth here to avoid double decoding. |
| ImageProvider::ScopedResult scoped_decoded_image; |
| if (playback_image_provider_f16_ && |
| draw_image.paint_image().is_high_bit_depth()) { |
| DCHECK(playback_image_provider_f16_); |
| scoped_decoded_image = |
| playback_image_provider_f16_->GetRasterContent(draw_image); |
| } else { |
| scoped_decoded_image = |
| playback_image_provider_n32_->GetRasterContent(draw_image); |
| } |
| |
| // Holding onto locked images here is a performance optimization for the |
| // gpu image decode cache. For that cache, it is expensive to lock and |
| // unlock gpu discardable, and so it is worth it to hold the lock on |
| // these images across multiple potential decodes. In the software case, |
| // locking in this manner makes it easy to run out of discardable memory |
| // (backed by shared memory sometimes) because each per-colorspace image |
| // decode cache has its own limit. In the software case, just unlock |
| // immediately and let the discardable system manage the cache logic |
| // behind the scenes. |
| if (!scoped_decoded_image.needs_unlock() || !IsHardwareDecodeCache()) { |
| return scoped_decoded_image; |
| } |
| |
| constexpr int kMaxLockedImagesCount = 500; |
| if (!scoped_decoded_image.decoded_image().is_budgeted() || |
| locked_images_.size() > kMaxLockedImagesCount) { |
| // If we have exceeded the budget, ReleaseLockedImages any locked decodes. |
| ReleaseLockedImages(); |
| } |
| |
| auto decoded_draw_image = scoped_decoded_image.decoded_image(); |
| return ScopedResult(decoded_draw_image, |
| base::BindOnce(&CanvasImageProvider::CanUnlockImage, |
| weak_factory_.GetWeakPtr(), |
| std::move(scoped_decoded_image))); |
| } |
| |
| void CanvasResourceProvider::CanvasImageProvider::CanUnlockImage( |
| ScopedResult image) { |
| // We should early out and avoid calling this function for software decodes. |
| DCHECK(IsHardwareDecodeCache()); |
| |
| // Because these image decodes are being done in javascript calling into |
| // canvas code, there's no obvious time to do the cleanup. To handle this, |
| // post a cleanup task to run after javascript is done running. |
| if (!cleanup_task_pending_) { |
| cleanup_task_pending_ = true; |
| Thread::Current()->GetTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&CanvasImageProvider::CleanupLockedImages, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| locked_images_.push_back(std::move(image)); |
| } |
| |
| void CanvasResourceProvider::CanvasImageProvider::CleanupLockedImages() { |
| cleanup_task_pending_ = false; |
| ReleaseLockedImages(); |
| } |
| |
| bool CanvasResourceProvider::CanvasImageProvider::IsHardwareDecodeCache() |
| const { |
| return raster_mode_ != cc::PlaybackImageProvider::RasterMode::kSoftware; |
| } |
| |
| CanvasResourceProvider::CanvasResourceProvider( |
| const ResourceProviderType& type, |
| const IntSize& size, |
| SkFilterQuality filter_quality, |
| const CanvasResourceParams& params, |
| bool is_origin_top_left, |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper, |
| base::WeakPtr<CanvasResourceDispatcher> resource_dispatcher) |
| : type_(type), |
| context_provider_wrapper_(std::move(context_provider_wrapper)), |
| resource_dispatcher_(resource_dispatcher), |
| size_(size), |
| filter_quality_(filter_quality), |
| params_(params), |
| is_origin_top_left_(is_origin_top_left), |
| snapshot_paint_image_id_(cc::PaintImage::GetNextId()), |
| identifiability_paint_op_digest_(size_) { |
| if (context_provider_wrapper_) |
| context_provider_wrapper_->AddObserver(this); |
| CanvasMemoryDumpProvider::Instance()->RegisterClient(this); |
| } |
| |
| CanvasResourceProvider::~CanvasResourceProvider() { |
| UMA_HISTOGRAM_EXACT_LINEAR("Blink.Canvas.MaximumInflightResources", |
| max_inflight_resources_, 20); |
| if (context_provider_wrapper_) |
| context_provider_wrapper_->RemoveObserver(this); |
| CanvasMemoryDumpProvider::Instance()->UnregisterClient(this); |
| } |
| |
| SkSurface* CanvasResourceProvider::GetSkSurface() const { |
| if (!surface_) |
| surface_ = CreateSkSurface(); |
| return surface_.get(); |
| } |
| |
| void CanvasResourceProvider::EnsureSkiaCanvas() { |
| WillDraw(); |
| |
| if (skia_canvas_) |
| return; |
| |
| cc::SkiaPaintCanvas::ContextFlushes context_flushes; |
| if (IsAccelerated() && ContextProviderWrapper() && |
| !ContextProviderWrapper() |
| ->ContextProvider() |
| ->GetGpuFeatureInfo() |
| .IsWorkaroundEnabled(gpu::DISABLE_2D_CANVAS_AUTO_FLUSH)) { |
| context_flushes.enable = true; |
| context_flushes.max_draws_before_flush = kMaxDrawsBeforeContextFlush; |
| } |
| skia_canvas_ = std::make_unique<cc::SkiaPaintCanvas>( |
| GetSkSurface()->getCanvas(), GetOrCreateCanvasImageProvider(), |
| context_flushes); |
| } |
| |
| CanvasResourceProvider::CanvasImageProvider* |
| CanvasResourceProvider::GetOrCreateCanvasImageProvider() { |
| if (!canvas_image_provider_) { |
| // Create an ImageDecodeCache for half float images only if the canvas is |
| // using half float back storage. |
| cc::ImageDecodeCache* cache_f16 = nullptr; |
| if (ColorParams().GetSkColorType() == kRGBA_F16_SkColorType) |
| cache_f16 = ImageDecodeCacheF16(); |
| |
| auto raster_mode = cc::PlaybackImageProvider::RasterMode::kSoftware; |
| if (UseHardwareDecodeCache()) { |
| raster_mode = UseOopRasterization() |
| ? cc::PlaybackImageProvider::RasterMode::kOop |
| : cc::PlaybackImageProvider::RasterMode::kGpu; |
| } |
| canvas_image_provider_ = std::make_unique<CanvasImageProvider>( |
| ImageDecodeCacheRGBA8(), cache_f16, |
| ColorParams().GetStorageGfxColorSpace(), params_.GetSkColorType(), |
| raster_mode); |
| } |
| return canvas_image_provider_.get(); |
| } |
| |
| cc::PaintCanvas* CanvasResourceProvider::Canvas(bool needs_will_draw) { |
| // TODO(https://crbug.com/1211912): Video frames don't work without |
| // WillDrawIfNeeded(), but we are getting memory leak on CreatePattern |
| // with it. There should be a better way to solve this. |
| if (needs_will_draw) |
| WillDrawIfNeeded(); |
| |
| if (!recorder_) { |
| // A raw pointer is safe here because the callback is only used by the |
| // |recorder_|. |
| recorder_ = std::make_unique<MemoryManagedPaintRecorder>(WTF::BindRepeating( |
| &CanvasResourceProvider::SetNeedsFlush, WTF::Unretained(this))); |
| |
| return recorder_->beginRecording(Size().Width(), Size().Height()); |
| } |
| return recorder_->getRecordingCanvas(); |
| } |
| |
| void CanvasResourceProvider::OnContextDestroyed() { |
| if (skia_canvas_) |
| skia_canvas_->reset_image_provider(); |
| canvas_image_provider_.reset(); |
| } |
| |
| void CanvasResourceProvider::OnFlushForImage(PaintImage::ContentId content_id) { |
| if (Canvas()) { |
| MemoryManagedPaintCanvas* canvas = |
| static_cast<MemoryManagedPaintCanvas*>(Canvas()); |
| if (canvas->IsCachingImage(content_id)) |
| FlushCanvas(); |
| } |
| } |
| |
| void CanvasResourceProvider::ReleaseLockedImages() { |
| if (canvas_image_provider_) |
| canvas_image_provider_->ReleaseLockedImages(); |
| } |
| |
| scoped_refptr<StaticBitmapImage> CanvasResourceProvider::SnapshotInternal( |
| const ImageOrientation& orientation) { |
| if (!IsValid()) |
| return nullptr; |
| |
| auto paint_image = MakeImageSnapshot(); |
| DCHECK(!paint_image.IsTextureBacked()); |
| return UnacceleratedStaticBitmapImage::Create(std::move(paint_image), |
| orientation); |
| } |
| |
| cc::PaintImage CanvasResourceProvider::MakeImageSnapshot() { |
| FlushCanvas(); |
| auto sk_image = GetSkSurface()->makeImageSnapshot(); |
| if (!sk_image) |
| return cc::PaintImage(); |
| |
| auto last_snapshot_sk_image_id = snapshot_sk_image_id_; |
| snapshot_sk_image_id_ = sk_image->uniqueID(); |
| |
| // Ensure that a new PaintImage::ContentId is used only when the underlying |
| // SkImage changes. This is necessary to ensure that the same image results |
| // in a cache hit in cc's ImageDecodeCache. |
| if (snapshot_paint_image_content_id_ == PaintImage::kInvalidContentId || |
| last_snapshot_sk_image_id != snapshot_sk_image_id_) { |
| snapshot_paint_image_content_id_ = PaintImage::GetNextContentId(); |
| } |
| |
| return PaintImageBuilder::WithDefault() |
| .set_id(snapshot_paint_image_id_) |
| .set_image(std::move(sk_image), snapshot_paint_image_content_id_) |
| .TakePaintImage(); |
| } |
| |
| gpu::gles2::GLES2Interface* CanvasResourceProvider::ContextGL() const { |
| if (!context_provider_wrapper_) |
| return nullptr; |
| return context_provider_wrapper_->ContextProvider()->ContextGL(); |
| } |
| |
| gpu::raster::RasterInterface* CanvasResourceProvider::RasterInterface() const { |
| if (!context_provider_wrapper_) |
| return nullptr; |
| return context_provider_wrapper_->ContextProvider()->RasterInterface(); |
| } |
| |
| GrDirectContext* CanvasResourceProvider::GetGrContext() const { |
| if (!context_provider_wrapper_) |
| return nullptr; |
| return context_provider_wrapper_->ContextProvider()->GetGrContext(); |
| } |
| |
| sk_sp<cc::PaintRecord> CanvasResourceProvider::FlushCanvas() { |
| if (!HasRecordedDrawOps()) |
| return nullptr; |
| // Get PaintOp count before finishRecordingAsPicture() adds more, as these |
| // additional ops don't correspond to canvas context operations. |
| const size_t initial_paint_ops = recorder_->num_paint_ops(); |
| sk_sp<cc::PaintRecord> last_recording = recorder_->finishRecordingAsPicture(); |
| RasterRecord(last_recording); |
| needs_flush_ = false; |
| cc::PaintCanvas* canvas = |
| recorder_->beginRecording(Size().Width(), Size().Height()); |
| if (restore_clip_stack_callback_) |
| restore_clip_stack_callback_.Run(canvas); |
| identifiability_paint_op_digest_.MaybeUpdateDigest(last_recording, |
| initial_paint_ops); |
| // restore_clip_stack_callback_ also adds PaintOps -- these need to be skipped |
| // during identifiability digest calculation. |
| identifiability_paint_op_digest_.SetPrefixSkipCount( |
| recorder_->num_paint_ops()); |
| return last_recording; |
| } |
| |
| void CanvasResourceProvider::RasterRecord( |
| sk_sp<cc::PaintRecord> last_recording) { |
| EnsureSkiaCanvas(); |
| skia_canvas_->drawPicture(std::move(last_recording)); |
| GetSkSurface()->flushAndSubmit(); |
| } |
| |
| void CanvasResourceProvider::RasterRecordOOP( |
| sk_sp<cc::PaintRecord> last_recording, |
| bool needs_clear, |
| gpu::Mailbox mailbox) { |
| if (IsGpuContextLost()) |
| return; |
| gpu::raster::RasterInterface* ri = RasterInterface(); |
| SkColor background_color = |
| ColorParams().GetSkAlphaType() == kOpaque_SkAlphaType |
| ? SK_ColorBLACK |
| : SK_ColorTRANSPARENT; |
| |
| auto list = base::MakeRefCounted<cc::DisplayItemList>( |
| cc::DisplayItemList::kTopLevelDisplayItemList); |
| |
| list->StartPaint(); |
| list->push<cc::DrawRecordOp>(std::move(last_recording)); |
| list->EndPaintOfUnpaired(gfx::Rect(Size().Width(), Size().Height())); |
| list->Finalize(); |
| |
| gfx::Size size(Size().Width(), Size().Height()); |
| size_t max_op_size_hint = gpu::raster::RasterInterface::kDefaultMaxOpSizeHint; |
| gfx::Rect full_raster_rect(Size().Width(), Size().Height()); |
| gfx::Rect playback_rect(Size().Width(), Size().Height()); |
| gfx::Vector2dF post_translate(0.f, 0.f); |
| gfx::Vector2dF post_scale(1.f, 1.f); |
| |
| ri->BeginRasterCHROMIUM( |
| background_color, needs_clear, /*msaa_sample_count=*/0, |
| ColorParams().CanUseLcdText(), ColorParams().GetStorageGfxColorSpace(), |
| mailbox.name); |
| |
| ri->RasterCHROMIUM(list.get(), GetOrCreateCanvasImageProvider(), size, |
| full_raster_rect, playback_rect, post_translate, |
| post_scale, false /* requires_clear */, &max_op_size_hint); |
| |
| ri->EndRasterCHROMIUM(); |
| } |
| |
| bool CanvasResourceProvider::IsGpuContextLost() const { |
| if (type_ == kSkiaDawnSharedImage) { |
| return false; |
| } |
| auto* raster_interface = RasterInterface(); |
| return !raster_interface || |
| raster_interface->GetGraphicsResetStatusKHR() != GL_NO_ERROR; |
| } |
| |
| bool CanvasResourceProvider::WritePixels(const SkImageInfo& orig_info, |
| const void* pixels, |
| size_t row_bytes, |
| int x, |
| int y) { |
| TRACE_EVENT0("blink", "CanvasResourceProvider::WritePixels"); |
| |
| DCHECK(IsValid()); |
| DCHECK(!HasRecordedDrawOps()); |
| |
| EnsureSkiaCanvas(); |
| |
| return GetSkSurface()->getCanvas()->writePixels(orig_info, pixels, row_bytes, |
| x, y); |
| } |
| |
| void CanvasResourceProvider::Clear() { |
| // Clear the background transparent or opaque, as required. This should only |
| // be called when a new resource provider is created to ensure that we're |
| // not leaking data or displaying bad pixels (in the case of kOpaque |
| // canvases). Instead of adding these commands to our deferred queue, we'll |
| // send them directly through to Skia so that they're not replayed for |
| // printing operations. See crbug.com/1003114 |
| DCHECK(IsValid()); |
| if (params_.GetSkAlphaType() == kOpaque_SkAlphaType) |
| Canvas()->clear(SK_ColorBLACK); |
| else |
| Canvas()->clear(SK_ColorTRANSPARENT); |
| |
| FlushCanvas(); |
| } |
| |
| uint32_t CanvasResourceProvider::ContentUniqueID() const { |
| return GetSkSurface()->generationID(); |
| } |
| |
| scoped_refptr<CanvasResource> CanvasResourceProvider::CreateResource() { |
| // Needs to be implemented in subclasses that use resource recycling. |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| cc::ImageDecodeCache* CanvasResourceProvider::ImageDecodeCacheRGBA8() { |
| if (UseHardwareDecodeCache()) { |
| return context_provider_wrapper_->ContextProvider()->ImageDecodeCache( |
| kN32_SkColorType); |
| } |
| |
| return &Image::SharedCCDecodeCache(kN32_SkColorType); |
| } |
| |
| cc::ImageDecodeCache* CanvasResourceProvider::ImageDecodeCacheF16() { |
| if (UseHardwareDecodeCache()) { |
| return context_provider_wrapper_->ContextProvider()->ImageDecodeCache( |
| kRGBA_F16_SkColorType); |
| } |
| return &Image::SharedCCDecodeCache(kRGBA_F16_SkColorType); |
| } |
| |
| void CanvasResourceProvider::RecycleResource( |
| scoped_refptr<CanvasResource> resource) { |
| // We don't want to keep an arbitrary large number of canvases. |
| if (canvas_resources_.size() > |
| static_cast<unsigned int>(kMaxRecycledCanvasResources)) |
| return; |
| |
| // Need to check HasOneRef() because if there are outstanding references to |
| // the resource, it cannot be safely recycled. |
| if (resource->HasOneRef() && resource_recycling_enabled_ && |
| !is_single_buffered_) { |
| canvas_resources_.push_back(std::move(resource)); |
| } |
| } |
| |
| void CanvasResourceProvider::SetResourceRecyclingEnabled(bool value) { |
| resource_recycling_enabled_ = value; |
| if (!resource_recycling_enabled_) |
| ClearRecycledResources(); |
| } |
| |
| void CanvasResourceProvider::ClearRecycledResources() { |
| canvas_resources_.clear(); |
| } |
| |
| void CanvasResourceProvider::OnDestroyResource() { |
| --num_inflight_resources_; |
| } |
| |
| const IdentifiabilityPaintOpDigest& |
| CanvasResourceProvider::GetIdentifiablityPaintOpDigest() { |
| FlushCanvas(); |
| return identifiability_paint_op_digest_; |
| } |
| |
| scoped_refptr<CanvasResource> CanvasResourceProvider::NewOrRecycledResource() { |
| if (canvas_resources_.IsEmpty()) { |
| canvas_resources_.push_back(CreateResource()); |
| ++num_inflight_resources_; |
| if (num_inflight_resources_ > max_inflight_resources_) |
| max_inflight_resources_ = num_inflight_resources_; |
| } |
| |
| if (IsSingleBuffered()) { |
| DCHECK_EQ(canvas_resources_.size(), 1u); |
| return canvas_resources_.back(); |
| } |
| |
| scoped_refptr<CanvasResource> resource = std::move(canvas_resources_.back()); |
| canvas_resources_.pop_back(); |
| return resource; |
| } |
| |
| void CanvasResourceProvider::TryEnableSingleBuffering() { |
| if (IsSingleBuffered() || !SupportsSingleBuffering()) |
| return; |
| is_single_buffered_ = true; |
| ClearRecycledResources(); |
| } |
| |
| bool CanvasResourceProvider::ImportResource( |
| scoped_refptr<CanvasResource> resource) { |
| if (!IsSingleBuffered() || !SupportsSingleBuffering()) |
| return false; |
| canvas_resources_.clear(); |
| canvas_resources_.push_back(std::move(resource)); |
| return true; |
| } |
| |
| scoped_refptr<CanvasResource> CanvasResourceProvider::GetImportedResource() |
| const { |
| if (!IsSingleBuffered() || !SupportsSingleBuffering()) |
| return nullptr; |
| DCHECK_LE(canvas_resources_.size(), 1u); |
| if (canvas_resources_.IsEmpty()) |
| return nullptr; |
| return canvas_resources_.back(); |
| } |
| |
| void CanvasResourceProvider::SkipQueuedDrawCommands() { |
| // Note that this function only gets called when canvas needs a full repaint, |
| // so always update the |mode_| to discard the old copy of canvas content. |
| mode_ = SkSurface::kDiscard_ContentChangeMode; |
| |
| if (!HasRecordedDrawOps()) |
| return; |
| recorder_->finishRecordingAsPicture(); |
| cc::PaintCanvas* canvas = |
| recorder_->beginRecording(Size().Width(), Size().Height()); |
| if (restore_clip_stack_callback_) |
| restore_clip_stack_callback_.Run(canvas); |
| } |
| |
| void CanvasResourceProvider::SetRestoreClipStackCallback( |
| RestoreMatrixClipStackCb callback) { |
| DCHECK(restore_clip_stack_callback_.is_null() || callback.is_null()); |
| restore_clip_stack_callback_ = std::move(callback); |
| } |
| |
| void CanvasResourceProvider::RestoreBackBuffer(const cc::PaintImage& image) { |
| DCHECK_EQ(image.height(), Size().Height()); |
| DCHECK_EQ(image.width(), Size().Width()); |
| EnsureSkiaCanvas(); |
| cc::PaintFlags copy_paint; |
| copy_paint.setBlendMode(SkBlendMode::kSrc); |
| skia_canvas_->drawImage(image, 0, 0, SkSamplingOptions(), ©_paint); |
| } |
| |
| void CanvasResourceProvider::RestoreBackBufferOOP(const cc::PaintImage& image) { |
| DCHECK_EQ(image.height(), Size().Height()); |
| DCHECK_EQ(image.width(), Size().Width()); |
| |
| auto sk_image = image.GetSwSkImage(); |
| DCHECK(sk_image); |
| SkPixmap map; |
| // We know this SkImage is software backed because it's guaranteed by |
| // PaintImage::GetSwSkImage above |
| sk_image->peekPixels(&map); |
| WritePixels(map.info(), map.addr(), map.rowBytes(), /*x=*/0, /*y=*/0); |
| } |
| |
| bool CanvasResourceProvider::HasRecordedDrawOps() const { |
| return recorder_ && recorder_->ListHasDrawOps(); |
| } |
| |
| void CanvasResourceProvider::TearDownSkSurface() { |
| skia_canvas_ = nullptr; |
| surface_ = nullptr; |
| } |
| |
| size_t CanvasResourceProvider::ComputeSurfaceSize() const { |
| if (!surface_) |
| return 0; |
| |
| SkImageInfo info = surface_->imageInfo(); |
| return info.computeByteSize(info.minRowBytes()); |
| } |
| |
| void CanvasResourceProvider::OnMemoryDump( |
| base::trace_event::ProcessMemoryDump* pmd) { |
| if (!surface_) |
| return; |
| |
| std::string dump_name = |
| base::StringPrintf("canvas/ResourceProvider/SkSurface/0x%" PRIXPTR, |
| reinterpret_cast<uintptr_t>(surface_.get())); |
| auto* dump = pmd->CreateAllocatorDump(dump_name); |
| |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| ComputeSurfaceSize()); |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, |
| base::trace_event::MemoryAllocatorDump::kUnitsObjects, 1); |
| |
| // SkiaMemoryDumpProvider reports only sk_glyph_cache and sk_resource_cache. |
| // So the SkSurface is suballocation of malloc, not SkiaDumpProvider. |
| if (const char* system_allocator_name = |
| base::trace_event::MemoryDumpManager::GetInstance() |
| ->system_allocator_pool_name()) { |
| pmd->AddSuballocation(dump->guid(), system_allocator_name); |
| } |
| } |
| |
| size_t CanvasResourceProvider::GetSize() const { |
| return ComputeSurfaceSize(); |
| } |
| |
| } // namespace blink |