| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "platform/graphics/Canvas2DLayerBridge.h" |
| |
| #include <memory> |
| #include "base/memory/ptr_util.h" |
| #include "components/viz/common/quads/texture_mailbox.h" |
| #include "components/viz/common/resources/single_release_callback.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "gpu/command_buffer/client/gpu_memory_buffer_manager.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 "platform/Histogram.h" |
| #include "platform/WebTaskRunner.h" |
| #include "platform/graphics/AcceleratedStaticBitmapImage.h" |
| #include "platform/graphics/CanvasHeuristicParameters.h" |
| #include "platform/graphics/CanvasMetrics.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/graphics/ImageBuffer.h" |
| #include "platform/graphics/WebGraphicsContext3DProviderWrapper.h" |
| #include "platform/graphics/gpu/SharedContextRateLimiter.h" |
| #include "platform/graphics/gpu/SharedGpuContext.h" |
| #include "platform/graphics/paint/PaintCanvas.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/runtime_enabled_features.h" |
| #include "platform/scheduler/child/web_scheduler.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebCompositorSupport.h" |
| #include "public/platform/WebGraphicsContext3DProvider.h" |
| #include "public/platform/WebTraceLocation.h" |
| #include "skia/ext/texture_handle.h" |
| #include "third_party/skia/include/core/SkColorSpaceXformCanvas.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/gpu/GrContext.h" |
| #include "third_party/skia/include/gpu/GrTexture.h" |
| #include "third_party/skia/include/gpu/gl/GrGLTypes.h" |
| |
| namespace { |
| enum { |
| InvalidMailboxIndex = -1, |
| MaxCanvasAnimationBacklog = 2, // Make sure the the GPU is never more than |
| // two animation frames behind. |
| }; |
| |
| static void ReleaseMailboxImageResource( |
| scoped_refptr<blink::StaticBitmapImage>&& image, |
| bool lost_resource) { |
| if (lost_resource) |
| image->Abandon(); |
| // Image going out of scope takes care of resource clean-up in |
| // AccelStaticBitmapImage and MailboxTextureHolder destructors. |
| } |
| |
| // Resets Skia's texture bindings. This method should be called after |
| // changing texture bindings. |
| static void ResetSkiaTextureBinding( |
| WeakPtr<blink::WebGraphicsContext3DProviderWrapper> |
| context_provider_wrapper) { |
| if (!context_provider_wrapper) |
| return; |
| GrContext* gr_context = |
| context_provider_wrapper->ContextProvider()->GetGrContext(); |
| if (gr_context) |
| gr_context->resetContext(kTextureBinding_GrGLBackendState); |
| } |
| |
| // Releases all resources associated with a CHROMIUM image. |
| static void DeleteCHROMIUMImage( |
| WeakPtr<blink::WebGraphicsContext3DProviderWrapper> |
| context_provider_wrapper, |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer, |
| const GLuint& image_id, |
| const GLuint& texture_id) { |
| if (!context_provider_wrapper) |
| return; |
| gpu::gles2::GLES2Interface* gl = |
| context_provider_wrapper->ContextProvider()->ContextGL(); |
| |
| if (gl) { |
| GLenum target = GC3D_TEXTURE_RECTANGLE_ARB; |
| gl->BindTexture(target, texture_id); |
| gl->ReleaseTexImage2DCHROMIUM(target, image_id); |
| gl->DestroyImageCHROMIUM(image_id); |
| gl->DeleteTextures(1, &texture_id); |
| gl->BindTexture(target, 0); |
| gpu_memory_buffer.reset(); |
| |
| ResetSkiaTextureBinding(context_provider_wrapper); |
| } |
| } |
| |
| } // namespace |
| |
| namespace blink { |
| |
| struct Canvas2DLayerBridge::ImageInfo : public RefCounted<ImageInfo> { |
| ImageInfo(std::unique_ptr<gfx::GpuMemoryBuffer>, |
| GLuint image_id, |
| GLuint texture_id); |
| ~ImageInfo(); |
| |
| // The backing buffer. |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer_; |
| |
| // The id of the CHROMIUM image. |
| const GLuint image_id_; |
| |
| // The id of the texture bound to the CHROMIUM image. |
| const GLuint texture_id_; |
| }; |
| |
| static sk_sp<SkSurface> CreateSkSurface(GrContext* gr, |
| const IntSize& size, |
| int msaa_sample_count, |
| const CanvasColorParams& color_params, |
| bool* surface_is_accelerated) { |
| if (gr) |
| gr->resetContext(); |
| |
| SkImageInfo info = SkImageInfo::Make( |
| size.Width(), size.Height(), color_params.GetSkColorType(), |
| color_params.GetSkAlphaType(), |
| color_params.GetSkColorSpaceForSkSurfaces()); |
| SkSurfaceProps disable_lcd_props(0, kUnknown_SkPixelGeometry); |
| sk_sp<SkSurface> surface; |
| |
| if (gr) { |
| *surface_is_accelerated = true; |
| surface = SkSurface::MakeRenderTarget(gr, SkBudgeted::kNo, info, |
| msaa_sample_count, |
| color_params.GetSkSurfaceProps()); |
| } |
| |
| if (!surface) { |
| *surface_is_accelerated = false; |
| surface = SkSurface::MakeRaster(info, color_params.GetSkSurfaceProps()); |
| } |
| |
| if (surface) { |
| if (color_params.GetOpacityMode() == kOpaque) { |
| surface->getCanvas()->clear(SK_ColorBLACK); |
| } else { |
| surface->getCanvas()->clear(SK_ColorTRANSPARENT); |
| } |
| } |
| return surface; |
| } |
| |
| Canvas2DLayerBridge::Canvas2DLayerBridge(const IntSize& size, |
| int msaa_sample_count, |
| AccelerationMode acceleration_mode, |
| const CanvasColorParams& color_params, |
| bool is_unit_test) |
| : ImageBufferSurface(size, color_params), |
| logger_(WTF::WrapUnique(new Logger)), |
| weak_ptr_factory_(this), |
| image_buffer_(nullptr), |
| msaa_sample_count_(msaa_sample_count), |
| bytes_allocated_(0), |
| have_recorded_draw_commands_(false), |
| destruction_in_progress_(false), |
| filter_quality_(kLow_SkFilterQuality), |
| is_hidden_(false), |
| is_deferral_enabled_(true), |
| software_rendering_while_hidden_(false), |
| last_image_id_(0), |
| last_filter_(GL_LINEAR), |
| acceleration_mode_(acceleration_mode), |
| size_(size), |
| color_params_(color_params) { |
| if (acceleration_mode != kDisableAcceleration) { |
| context_provider_wrapper_ = SharedGpuContext::ContextProviderWrapper(); |
| DCHECK(context_provider_wrapper_); |
| DCHECK(SharedGpuContext::IsGpuCompositingEnabled()); |
| // See whether the use of GpuMemoryBuffers was blacklisted since |
| // the time the browser determined whether to use the feature |
| // based on the command line flags. This is the only way to avoid |
| // race conditions when determining whether to use this feature. |
| use_gpu_memory_buffers_ = |
| RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled() && |
| !context_provider_wrapper_->ContextProvider() |
| ->GetGpuFeatureInfo() |
| .IsWorkaroundEnabled( |
| gpu::DISABLE_GPU_MEMORY_BUFFERS_AS_RENDER_TARGETS); |
| } |
| // Used by browser tests to detect the use of a Canvas2DLayerBridge. |
| TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation", |
| TRACE_EVENT_SCOPE_GLOBAL); |
| StartRecording(); |
| if (!is_unit_test) |
| Init(); |
| } |
| |
| Canvas2DLayerBridge::~Canvas2DLayerBridge() { |
| BeginDestruction(); |
| DCHECK(destruction_in_progress_); |
| if (use_gpu_memory_buffers_) |
| ClearCHROMIUMImageCache(); |
| layer_.reset(); |
| } |
| |
| void Canvas2DLayerBridge::Init() { |
| Clear(); |
| if (CheckSurfaceValid()) |
| FlushInternal(); |
| } |
| |
| void Canvas2DLayerBridge::StartRecording() { |
| DCHECK(is_deferral_enabled_); |
| recorder_ = WTF::WrapUnique(new PaintRecorder); |
| PaintCanvas* canvas = |
| recorder_->beginRecording(size_.Width(), size_.Height()); |
| // Always save an initial frame, to support resetting the top level matrix |
| // and clip. |
| canvas->save(); |
| |
| if (image_buffer_) { |
| image_buffer_->ResetCanvas(canvas); |
| } |
| recording_pixel_count_ = 0; |
| } |
| |
| void Canvas2DLayerBridge::SetLoggerForTesting(std::unique_ptr<Logger> logger) { |
| logger_ = std::move(logger); |
| } |
| |
| void Canvas2DLayerBridge::ResetSurface() { |
| surface_paint_canvas_.reset(); |
| surface_.reset(); |
| } |
| |
| bool Canvas2DLayerBridge::ShouldAccelerate(AccelerationHint hint) const { |
| bool accelerate; |
| if (software_rendering_while_hidden_) { |
| accelerate = false; |
| } else if (acceleration_mode_ == kForceAccelerationForTesting) { |
| accelerate = true; |
| } else if (acceleration_mode_ == kDisableAcceleration) { |
| accelerate = false; |
| } else { |
| accelerate = hint == kPreferAcceleration || |
| hint == kPreferAccelerationAfterVisibilityChange; |
| } |
| |
| if (accelerate && (!context_provider_wrapper_ || |
| context_provider_wrapper_->ContextProvider() |
| ->ContextGL() |
| ->GetGraphicsResetStatusKHR() != GL_NO_ERROR)) |
| accelerate = false; |
| return accelerate; |
| } |
| |
| bool Canvas2DLayerBridge::IsAccelerated() const { |
| if (acceleration_mode_ == kDisableAcceleration) |
| return false; |
| if (IsHibernating()) |
| return false; |
| if (software_rendering_while_hidden_) |
| return false; |
| if (layer_) { |
| // We don't check |m_surface|, so this returns true if context was lost |
| // (|m_surface| is null) with restoration pending. |
| return true; |
| } |
| if (surface_) // && !m_layer is implied |
| return false; |
| |
| // Whether or not to accelerate is not yet resolved. Determine whether |
| // immediate presentation of the canvas would result in the canvas being |
| // accelerated. Presentation is assumed to be a 'PreferAcceleration' |
| // operation. |
| return ShouldAccelerate(kPreferAcceleration); |
| } |
| |
| GLenum Canvas2DLayerBridge::GetGLFilter() { |
| return filter_quality_ == kNone_SkFilterQuality ? GL_NEAREST : GL_LINEAR; |
| } |
| |
| bool Canvas2DLayerBridge::PrepareGpuMemoryBufferMailboxFromImage( |
| SkImage* image, |
| MailboxInfo* info, |
| viz::TextureMailbox* out_mailbox) { |
| // Need to flush skia's internal queue, because the texture is about to be |
| // accessed directly. |
| GrContext* gr_context = |
| context_provider_wrapper_->ContextProvider()->GetGrContext(); |
| gr_context->flush(); |
| |
| scoped_refptr<ImageInfo> image_info = CreateGpuMemoryBufferBackedTexture(); |
| if (!image_info) |
| return false; |
| |
| gpu::gles2::GLES2Interface* gl = ContextGL(); |
| if (!gl) |
| return false; |
| |
| GLuint image_texture = |
| skia::GrBackendObjectToGrGLTextureInfo(image->getTextureHandle(true)) |
| ->fID; |
| GLenum texture_target = GC3D_TEXTURE_RECTANGLE_ARB; |
| gl->CopySubTextureCHROMIUM( |
| image_texture, 0, texture_target, image_info->texture_id_, 0, 0, 0, 0, 0, |
| size_.Width(), size_.Height(), GL_FALSE, GL_FALSE, GL_FALSE); |
| |
| gpu::Mailbox mailbox; |
| gl->GenMailboxCHROMIUM(mailbox.name); |
| gl->ProduceTextureDirectCHROMIUM(image_info->texture_id_, texture_target, |
| mailbox.name); |
| |
| const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM(); |
| gl->Flush(); |
| gpu::SyncToken sync_token; |
| gl->GenSyncTokenCHROMIUM(fence_sync, sync_token.GetData()); |
| |
| info->image_info_ = image_info; |
| bool is_overlay_candidate = true; |
| |
| *out_mailbox = viz::TextureMailbox(mailbox, sync_token, texture_target, |
| gfx::Size(size_), is_overlay_candidate); |
| out_mailbox->set_color_space(color_params_.GetSamplerGfxColorSpace()); |
| image_info->gpu_memory_buffer_->SetColorSpace( |
| color_params_.GetStorageGfxColorSpace()); |
| |
| gl->BindTexture(GC3D_TEXTURE_RECTANGLE_ARB, 0); |
| |
| // Because we are changing the texture binding without going through skia, |
| // we must dirty the context. |
| ResetSkiaTextureBinding(context_provider_wrapper_); |
| |
| return true; |
| } |
| |
| scoped_refptr<Canvas2DLayerBridge::ImageInfo> |
| Canvas2DLayerBridge::CreateGpuMemoryBufferBackedTexture() { |
| if (!image_info_cache_.IsEmpty()) { |
| scoped_refptr<Canvas2DLayerBridge::ImageInfo> info = |
| image_info_cache_.back(); |
| image_info_cache_.pop_back(); |
| return info; |
| } |
| |
| gpu::gles2::GLES2Interface* gl = ContextGL(); |
| if (!gl) |
| return nullptr; |
| |
| gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager = |
| Platform::Current()->GetGpuMemoryBufferManager(); |
| if (!gpu_memory_buffer_manager) |
| return nullptr; |
| |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer = |
| gpu_memory_buffer_manager->CreateGpuMemoryBuffer( |
| gfx::Size(size_), gfx::BufferFormat::RGBA_8888, |
| gfx::BufferUsage::SCANOUT, gpu::kNullSurfaceHandle); |
| if (!gpu_memory_buffer) |
| return nullptr; |
| |
| GLuint image_id = |
| gl->CreateImageCHROMIUM(gpu_memory_buffer->AsClientBuffer(), |
| size_.Width(), size_.Height(), GL_RGBA); |
| if (!image_id) |
| return nullptr; |
| |
| GLuint texture_id = 0; |
| gl->GenTextures(1, &texture_id); |
| GLenum target = GC3D_TEXTURE_RECTANGLE_ARB; |
| gl->BindTexture(target, texture_id); |
| gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GetGLFilter()); |
| gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GetGLFilter()); |
| gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl->BindTexImage2DCHROMIUM(target, image_id); |
| |
| ResetSkiaTextureBinding(context_provider_wrapper_); |
| |
| return WTF::AdoptRef(new Canvas2DLayerBridge::ImageInfo( |
| std::move(gpu_memory_buffer), image_id, texture_id)); |
| } |
| |
| void Canvas2DLayerBridge::ClearCHROMIUMImageCache() { |
| for (const auto& it : image_info_cache_) { |
| DeleteCHROMIUMImage(context_provider_wrapper_, |
| std::move(it->gpu_memory_buffer_), it->image_id_, |
| it->texture_id_); |
| } |
| image_info_cache_.clear(); |
| } |
| |
| bool Canvas2DLayerBridge::PrepareMailboxFromImage( |
| scoped_refptr<StaticBitmapImage>&& image, |
| MailboxInfo* mailbox_info, |
| viz::TextureMailbox* out_mailbox) { |
| if (!context_provider_wrapper_) |
| return false; |
| |
| GrContext* gr_context = |
| context_provider_wrapper_->ContextProvider()->GetGrContext(); |
| if (!gr_context) { |
| mailbox_info->image_ = std::move(image); |
| // For testing, skip GL stuff when using a mock graphics context. |
| return true; |
| } |
| |
| sk_sp<SkImage> skia_image = image->PaintImageForCurrentFrame().GetSkImage(); |
| |
| // This check should not be necessary, it is a speculative fix for |
| // crbug.com/759412 |
| if (!skia_image || !skia_image->getTexture()) |
| return false; |
| |
| if (use_gpu_memory_buffers_) { |
| if (PrepareGpuMemoryBufferMailboxFromImage(skia_image.get(), mailbox_info, |
| out_mailbox)) |
| return true; |
| // Note: if GpuMemoryBuffer-backed texture creation failed we fall back to |
| // the non-GpuMemoryBuffer path. |
| } |
| |
| mailbox_info->image_ = std::move(image); |
| |
| if (context_provider_wrapper_->ContextProvider() |
| ->GetCapabilities() |
| .disable_2d_canvas_copy_on_write) { |
| surface_->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); |
| } |
| |
| // Need to flush skia's internal queue, because the texture is about to be |
| // accessed directly. |
| gr_context->flush(); |
| |
| // Because of texture sharing with the compositor, we must invalidate |
| // the state cached in skia so that the deferred copy on write |
| // in SkSurface_Gpu does not make any false assumptions. |
| skia_image->getTexture()->textureParamsModified(); |
| |
| gpu::gles2::GLES2Interface* gl = ContextGL(); |
| if (!gl) |
| return false; |
| |
| GLuint texture_id = |
| skia::GrBackendObjectToGrGLTextureInfo(skia_image->getTextureHandle(true)) |
| ->fID; |
| gl->BindTexture(GL_TEXTURE_2D, texture_id); |
| gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GetGLFilter()); |
| gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GetGLFilter()); |
| gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| mailbox_info->image_->EnsureMailbox(kUnverifiedSyncToken); |
| |
| *out_mailbox = |
| viz::TextureMailbox(mailbox_info->image_->GetMailbox(), |
| mailbox_info->image_->GetSyncToken(), GL_TEXTURE_2D); |
| |
| if (IsHidden()) { |
| // With hidden canvases, we release the SkImage immediately because |
| // there is no need for animations to be double buffered. Deleteing |
| // the SkImage will resulting skia's copy-on-write being skipped. |
| mailbox_info->image_ = nullptr; |
| } |
| |
| gl->BindTexture(GL_TEXTURE_2D, 0); |
| // Because we are changing the texture binding without going through skia, |
| // we must dirty the context. |
| ResetSkiaTextureBinding(context_provider_wrapper_); |
| return true; |
| } |
| |
| static void HibernateWrapper(WeakPtr<Canvas2DLayerBridge> bridge, |
| double /*idleDeadline*/) { |
| if (bridge) { |
| bridge->Hibernate(); |
| } else { |
| Canvas2DLayerBridge::Logger local_logger; |
| local_logger.ReportHibernationEvent( |
| Canvas2DLayerBridge:: |
| kHibernationAbortedDueToDestructionWhileHibernatePending); |
| } |
| } |
| |
| static void HibernateWrapperForTesting(WeakPtr<Canvas2DLayerBridge> bridge) { |
| HibernateWrapper(bridge, 0); |
| } |
| |
| void Canvas2DLayerBridge::Hibernate() { |
| DCHECK(!IsHibernating()); |
| DCHECK(hibernation_scheduled_); |
| |
| hibernation_scheduled_ = false; |
| |
| if (destruction_in_progress_) { |
| logger_->ReportHibernationEvent(kHibernationAbortedDueToPendingDestruction); |
| return; |
| } |
| |
| if (!surface_) { |
| logger_->ReportHibernationEvent(kHibernationAbortedBecauseNoSurface); |
| return; |
| } |
| |
| if (!IsHidden()) { |
| logger_->ReportHibernationEvent(kHibernationAbortedDueToVisibilityChange); |
| return; |
| } |
| |
| if (!IsValid()) { |
| logger_->ReportHibernationEvent(kHibernationAbortedDueGpuContextLoss); |
| return; |
| } |
| |
| if (!IsAccelerated()) { |
| logger_->ReportHibernationEvent( |
| kHibernationAbortedDueToSwitchToUnacceleratedRendering); |
| return; |
| } |
| |
| TRACE_EVENT0("blink", "Canvas2DLayerBridge::hibernate"); |
| sk_sp<SkSurface> temp_hibernation_surface = |
| SkSurface::MakeRasterN32Premul(size_.Width(), size_.Height()); |
| if (!temp_hibernation_surface) { |
| logger_->ReportHibernationEvent(kHibernationAbortedDueToAllocationFailure); |
| return; |
| } |
| // No HibernationEvent reported on success. This is on purppose to avoid |
| // non-complementary stats. Each HibernationScheduled event is paired with |
| // exactly one failure or exit event. |
| FlushRecordingOnly(); |
| // The following checks that the flush succeeded, which should always be the |
| // case because flushRecordingOnly should only fail it it fails to allocate |
| // a surface, and we have an early exit at the top of this function for when |
| // 'this' does not already have a surface. |
| DCHECK(!have_recorded_draw_commands_); |
| SkPaint copy_paint; |
| copy_paint.setBlendMode(SkBlendMode::kSrc); |
| surface_->draw(temp_hibernation_surface->getCanvas(), 0, 0, |
| ©_paint); // GPU readback |
| hibernation_image_ = temp_hibernation_surface->makeImageSnapshot(); |
| ResetSurface(); |
| layer_->ClearTexture(); |
| if (use_gpu_memory_buffers_) |
| ClearCHROMIUMImageCache(); |
| // shouldBeDirectComposited() may have changed. |
| if (image_buffer_) |
| image_buffer_->SetNeedsCompositingUpdate(); |
| logger_->DidStartHibernating(); |
| } |
| |
| void Canvas2DLayerBridge::ReportSurfaceCreationFailure() { |
| if (!surface_creation_failed_at_least_once_) { |
| // Only count the failure once per instance so that the histogram may |
| // reflect the proportion of Canvas2DLayerBridge instances with surface |
| // allocation failures. |
| CanvasMetrics::CountCanvasContextUsage( |
| CanvasMetrics::kGPUAccelerated2DCanvasSurfaceCreationFailed); |
| surface_creation_failed_at_least_once_ = true; |
| } |
| } |
| |
| SkSurface* Canvas2DLayerBridge::GetOrCreateSurface(AccelerationHint hint) { |
| if (surface_) |
| return surface_.get(); |
| |
| if (layer_ && !IsHibernating() && hint == kPreferAcceleration && |
| acceleration_mode_ != kDisableAcceleration) { |
| return nullptr; // re-creation will happen through restore() |
| } |
| |
| bool want_acceleration = ShouldAccelerate(hint); |
| if (CANVAS2D_BACKGROUND_RENDER_SWITCH_TO_CPU && IsHidden() && |
| want_acceleration) { |
| want_acceleration = false; |
| software_rendering_while_hidden_ = true; |
| } |
| |
| GrContext* gr = |
| want_acceleration && context_provider_wrapper_ |
| ? context_provider_wrapper_->ContextProvider()->GetGrContext() |
| : nullptr; |
| |
| bool surface_is_accelerated; |
| surface_ = CreateSkSurface(gr, size_, msaa_sample_count_, color_params_, |
| &surface_is_accelerated); |
| if (!surface_) |
| return nullptr; |
| surface_paint_canvas_ = color_params_.WrapCanvas(surface_->getCanvas()); |
| |
| if (surface_) { |
| // Always save an initial frame, to support resetting the top level matrix |
| // and clip. |
| surface_paint_canvas_->save(); |
| } else { |
| ReportSurfaceCreationFailure(); |
| } |
| |
| if (surface_ && surface_is_accelerated && !layer_) { |
| layer_ = |
| Platform::Current()->CompositorSupport()->CreateExternalTextureLayer( |
| this); |
| layer_->SetOpaque(ColorParams().GetOpacityMode() == kOpaque); |
| layer_->SetBlendBackgroundColor(ColorParams().GetOpacityMode() != kOpaque); |
| GraphicsLayer::RegisterContentsLayer(layer_->Layer()); |
| layer_->SetNearestNeighbor(filter_quality_ == kNone_SkFilterQuality); |
| } |
| |
| if (surface_ && IsHibernating()) { |
| if (surface_is_accelerated) { |
| logger_->ReportHibernationEvent(kHibernationEndedNormally); |
| } else { |
| if (IsHidden()) { |
| logger_->ReportHibernationEvent( |
| kHibernationEndedWithSwitchToBackgroundRendering); |
| } else { |
| logger_->ReportHibernationEvent(kHibernationEndedWithFallbackToSW); |
| } |
| } |
| |
| SkPaint copy_paint; |
| copy_paint.setBlendMode(SkBlendMode::kSrc); |
| surface_->getCanvas()->drawImage(hibernation_image_.get(), 0, 0, |
| ©_paint); |
| hibernation_image_.reset(); |
| |
| if (image_buffer_) { |
| image_buffer_->UpdateGPUMemoryUsage(); |
| |
| if (!is_deferral_enabled_) |
| image_buffer_->ResetCanvas(surface_paint_canvas_.get()); |
| |
| // shouldBeDirectComposited() may have changed. |
| image_buffer_->SetNeedsCompositingUpdate(); |
| } |
| } |
| |
| return surface_.get(); |
| } |
| |
| PaintCanvas* Canvas2DLayerBridge::Canvas() { |
| if (!is_deferral_enabled_) { |
| GetOrCreateSurface(); |
| return surface_paint_canvas_.get(); |
| } |
| return recorder_->getRecordingCanvas(); |
| } |
| |
| void Canvas2DLayerBridge::DisableDeferral(DisableDeferralReason reason) { |
| // Disabling deferral is permanent: once triggered by disableDeferral() |
| // we stay in immediate mode indefinitely. This is a performance heuristic |
| // that significantly helps a number of use cases. The rationale is that if |
| // immediate rendering was needed once, it is likely to be needed at least |
| // once per frame, which eliminates the possibility for inter-frame |
| // overdraw optimization. Furthermore, in cases where immediate mode is |
| // required multiple times per frame, the repeated flushing of deferred |
| // commands would cause significant overhead, so it is better to just stop |
| // trying to defer altogether. |
| if (!is_deferral_enabled_) |
| return; |
| |
| DEFINE_STATIC_LOCAL(EnumerationHistogram, gpu_disabled_histogram, |
| ("Canvas.GPUAccelerated2DCanvasDisableDeferralReason", |
| kDisableDeferralReasonCount)); |
| gpu_disabled_histogram.Count(reason); |
| CanvasMetrics::CountCanvasContextUsage( |
| CanvasMetrics::kGPUAccelerated2DCanvasDeferralDisabled); |
| FlushRecordingOnly(); |
| // Because we will be discarding the recorder, if the flush failed |
| // content will be lost -> force m_haveRecordedDrawCommands to false |
| have_recorded_draw_commands_ = false; |
| |
| is_deferral_enabled_ = false; |
| recorder_.reset(); |
| // install the current matrix/clip stack onto the immediate canvas |
| GetOrCreateSurface(); |
| if (image_buffer_ && surface_paint_canvas_) |
| image_buffer_->ResetCanvas(surface_paint_canvas_.get()); |
| } |
| |
| void Canvas2DLayerBridge::SetImageBuffer(ImageBuffer* image_buffer) { |
| image_buffer_ = image_buffer; |
| if (image_buffer_ && is_deferral_enabled_) { |
| image_buffer_->ResetCanvas(recorder_->getRecordingCanvas()); |
| } |
| } |
| |
| void Canvas2DLayerBridge::BeginDestruction() { |
| if (destruction_in_progress_) |
| return; |
| if (IsHibernating()) |
| logger_->ReportHibernationEvent(kHibernationEndedWithTeardown); |
| hibernation_image_.reset(); |
| recorder_.reset(); |
| image_buffer_ = nullptr; |
| destruction_in_progress_ = true; |
| SetIsHidden(true); |
| ResetSurface(); |
| |
| if (layer_ && acceleration_mode_ != kDisableAcceleration) { |
| GraphicsLayer::UnregisterContentsLayer(layer_->Layer()); |
| layer_->ClearTexture(); |
| // Orphaning the layer is required to trigger the recration of a new layer |
| // in the case where destruction is caused by a canvas resize. Test: |
| // virtual/gpu/fast/canvas/canvas-resize-after-paint-without-layout.html |
| layer_->Layer()->RemoveFromParent(); |
| } |
| |
| DCHECK(!bytes_allocated_); |
| } |
| |
| void Canvas2DLayerBridge::SetFilterQuality(SkFilterQuality filter_quality) { |
| DCHECK(!destruction_in_progress_); |
| filter_quality_ = filter_quality; |
| if (layer_) |
| layer_->SetNearestNeighbor(filter_quality_ == kNone_SkFilterQuality); |
| } |
| |
| void Canvas2DLayerBridge::SetIsHidden(bool hidden) { |
| bool new_hidden_value = hidden || destruction_in_progress_; |
| if (is_hidden_ == new_hidden_value) |
| return; |
| |
| is_hidden_ = new_hidden_value; |
| if (CANVAS2D_HIBERNATION_ENABLED && surface_ && IsHidden() && |
| !destruction_in_progress_ && !hibernation_scheduled_) { |
| if (layer_) |
| layer_->ClearTexture(); |
| logger_->ReportHibernationEvent(kHibernationScheduled); |
| hibernation_scheduled_ = true; |
| if (dont_use_idle_scheduling_for_testing_) { |
| Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostTask( |
| BLINK_FROM_HERE, WTF::Bind(&HibernateWrapperForTesting, |
| weak_ptr_factory_.CreateWeakPtr())); |
| } else { |
| Platform::Current()->CurrentThread()->Scheduler()->PostIdleTask( |
| BLINK_FROM_HERE, |
| WTF::Bind(&HibernateWrapper, weak_ptr_factory_.CreateWeakPtr())); |
| } |
| } |
| if (!IsHidden() && software_rendering_while_hidden_) { |
| FlushRecordingOnly(); |
| SkPaint copy_paint; |
| copy_paint.setBlendMode(SkBlendMode::kSrc); |
| |
| sk_sp<SkSurface> old_surface = std::move(surface_); |
| ResetSurface(); |
| |
| software_rendering_while_hidden_ = false; |
| SkSurface* new_surface = |
| GetOrCreateSurface(kPreferAccelerationAfterVisibilityChange); |
| if (new_surface) { |
| if (old_surface) |
| old_surface->draw(new_surface->getCanvas(), 0, 0, ©_paint); |
| if (image_buffer_ && !is_deferral_enabled_) { |
| image_buffer_->ResetCanvas(surface_paint_canvas_.get()); |
| } |
| } |
| } |
| if (!IsHidden() && IsHibernating()) { |
| GetOrCreateSurface(); // Rude awakening |
| } |
| } |
| |
| bool Canvas2DLayerBridge::WritePixels(const SkImageInfo& orig_info, |
| const void* pixels, |
| size_t row_bytes, |
| int x, |
| int y) { |
| if (!GetOrCreateSurface()) |
| return false; |
| if (x <= 0 && y <= 0 && x + orig_info.width() >= size_.Width() && |
| y + orig_info.height() >= size_.Height()) { |
| SkipQueuedDrawCommands(); |
| } else { |
| FlushInternal(); |
| } |
| DCHECK(!have_recorded_draw_commands_); |
| // call write pixels on the surface, not the recording canvas. |
| // No need to call beginDirectSurfaceAccessModeIfNeeded() because writePixels |
| // ignores the matrix and clip state. |
| return GetOrCreateSurface()->getCanvas()->writePixels(orig_info, pixels, |
| row_bytes, x, y); |
| } |
| |
| void Canvas2DLayerBridge::SkipQueuedDrawCommands() { |
| if (have_recorded_draw_commands_) { |
| recorder_->finishRecordingAsPicture(); |
| StartRecording(); |
| have_recorded_draw_commands_ = false; |
| } |
| |
| if (is_deferral_enabled_) { |
| if (rate_limiter_) |
| rate_limiter_->Reset(); |
| } |
| } |
| |
| void Canvas2DLayerBridge::FlushRecordingOnly() { |
| DCHECK(!destruction_in_progress_); |
| |
| if (have_recorded_draw_commands_ && GetOrCreateSurface()) { |
| TRACE_EVENT0("cc", "Canvas2DLayerBridge::flushRecordingOnly"); |
| |
| // For legacy and sRGB canvases, transform all input colors and images to |
| // the target space using a SkCreateColorSpaceXformCanvas. This ensures |
| // blending will be done using target space pixel values. |
| SkCanvas* canvas = GetOrCreateSurface()->getCanvas(); |
| std::unique_ptr<PaintCanvas> color_transform_canvas = |
| color_params_.WrapCanvas(canvas); |
| |
| { |
| sk_sp<PaintRecord> recording = recorder_->finishRecordingAsPicture(); |
| color_transform_canvas->drawPicture(recording); |
| } |
| |
| if (is_deferral_enabled_) |
| StartRecording(); |
| have_recorded_draw_commands_ = false; |
| } |
| } |
| |
| void Canvas2DLayerBridge::FlushInternal() { |
| if (!did_draw_since_last_flush_) |
| return; |
| TRACE_EVENT0("cc", "Canvas2DLayerBridge::flush"); |
| if (!GetOrCreateSurface()) |
| return; |
| FlushRecordingOnly(); |
| GetOrCreateSurface()->getCanvas()->flush(); |
| did_draw_since_last_flush_ = false; |
| } |
| |
| void Canvas2DLayerBridge::Flush(FlushReason reason) { |
| FlushInternal(); |
| } |
| |
| void Canvas2DLayerBridge::FlushGpuInternal() { |
| FlushInternal(); |
| gpu::gles2::GLES2Interface* gl = ContextGL(); |
| if (IsAccelerated() && gl && did_draw_since_last_gpu_flush_) { |
| TRACE_EVENT0("cc", "Canvas2DLayerBridge::flushGpu"); |
| gl->Flush(); |
| did_draw_since_last_gpu_flush_ = false; |
| } |
| } |
| |
| void Canvas2DLayerBridge::FlushGpu(FlushReason reason) { |
| FlushGpuInternal(); |
| } |
| |
| gpu::gles2::GLES2Interface* Canvas2DLayerBridge::ContextGL() { |
| // Check on m_layer is necessary because contextGL() may be called during |
| // the destruction of m_layer |
| if (layer_ && acceleration_mode_ != kDisableAcceleration && |
| !destruction_in_progress_) { |
| // Call checkSurfaceValid to ensure the rate limiter is disabled if the |
| // context is lost. |
| if (!IsValid()) |
| return nullptr; |
| } |
| return context_provider_wrapper_ |
| ? context_provider_wrapper_->ContextProvider()->ContextGL() |
| : nullptr; |
| } |
| |
| bool Canvas2DLayerBridge::IsValid() const { |
| return const_cast<Canvas2DLayerBridge*>(this)->CheckSurfaceValid(); |
| } |
| |
| bool Canvas2DLayerBridge::CheckSurfaceValid() { |
| DCHECK(!destruction_in_progress_); |
| if (destruction_in_progress_) |
| return false; |
| if (IsHibernating()) |
| return true; |
| if (!layer_ || acceleration_mode_ == kDisableAcceleration) |
| return true; |
| if (!surface_) |
| return false; |
| if (!context_provider_wrapper_ || |
| context_provider_wrapper_->ContextProvider() |
| ->ContextGL() |
| ->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { |
| ResetSurface(); |
| if (image_buffer_) |
| image_buffer_->NotifySurfaceInvalid(); |
| CanvasMetrics::CountCanvasContextUsage( |
| CanvasMetrics::kAccelerated2DCanvasGPUContextLost); |
| } |
| return surface_.get(); |
| } |
| |
| bool Canvas2DLayerBridge::Restore() { |
| DCHECK(!destruction_in_progress_); |
| if (destruction_in_progress_ || !IsAccelerated()) |
| return false; |
| DCHECK(!surface_); |
| |
| gpu::gles2::GLES2Interface* shared_gl = nullptr; |
| layer_->ClearTexture(); |
| context_provider_wrapper_ = SharedGpuContext::ContextProviderWrapper(); |
| if (context_provider_wrapper_) |
| shared_gl = context_provider_wrapper_->ContextProvider()->ContextGL(); |
| |
| if (shared_gl && shared_gl->GetGraphicsResetStatusKHR() == GL_NO_ERROR) { |
| GrContext* gr_ctx = |
| context_provider_wrapper_->ContextProvider()->GetGrContext(); |
| bool surface_is_accelerated; |
| sk_sp<SkSurface> surface(CreateSkSurface(gr_ctx, size_, msaa_sample_count_, |
| color_params_, |
| &surface_is_accelerated)); |
| if (!surface_) |
| ReportSurfaceCreationFailure(); |
| |
| // The current paradigm does not support switching from accelerated to |
| // non-accelerated, which would be tricky due to changes to the layer tree, |
| // which can only happen at specific times during the document lifecycle. |
| // Therefore, we can only accept the restored surface if it is accelerated. |
| if (surface && surface_is_accelerated) { |
| surface_ = std::move(surface); |
| // FIXME: draw sad canvas picture into new buffer crbug.com/243842 |
| } |
| } |
| if (image_buffer_) |
| image_buffer_->UpdateGPUMemoryUsage(); |
| |
| return surface_.get(); |
| } |
| |
| bool Canvas2DLayerBridge::PrepareTextureMailbox( |
| viz::TextureMailbox* out_mailbox, |
| std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { |
| if (destruction_in_progress_) { |
| // It can be hit in the following sequence. |
| // 1. Canvas draws something. |
| // 2. The compositor begins the frame. |
| // 3. Javascript makes a context be lost. |
| // 4. Here. |
| return false; |
| } |
| |
| frames_since_last_commit_ = 0; |
| if (rate_limiter_) { |
| rate_limiter_->Reset(); |
| } |
| |
| // If the context is lost, we don't know if we should be producing GPU or |
| // software frames, until we get a new context, since the compositor will |
| // be trying to get a new context and may change modes. |
| if (!context_provider_wrapper_ || |
| context_provider_wrapper_->ContextProvider() |
| ->ContextGL() |
| ->GetGraphicsResetStatusKHR() != GL_NO_ERROR) |
| return false; |
| |
| DCHECK(IsAccelerated() || IsHibernating() || |
| software_rendering_while_hidden_); |
| |
| // if hibernating but not hidden, we want to wake up from |
| // hibernation |
| if ((IsHibernating() || software_rendering_while_hidden_) && IsHidden()) |
| return false; |
| |
| scoped_refptr<StaticBitmapImage> image = |
| NewImageSnapshot(kPreferAcceleration, kSnapshotReasonUnknown); |
| if (!image || !image->IsValid() || !image->IsTextureBacked()) |
| return false; |
| |
| { |
| sk_sp<SkImage> skImage = image->PaintImageForCurrentFrame().GetSkImage(); |
| DCHECK(skImage->isTextureBacked()); |
| // Early exit if canvas was not drawn to since last prepareMailbox. |
| GLenum filter = GetGLFilter(); |
| if (skImage->uniqueID() == last_image_id_ && filter == last_filter_) |
| return false; |
| last_image_id_ = skImage->uniqueID(); |
| last_filter_ = filter; |
| } |
| |
| std::unique_ptr<MailboxInfo> info = WTF::WrapUnique(new MailboxInfo()); |
| if (!PrepareMailboxFromImage(std::move(image), info.get(), out_mailbox)) |
| return false; |
| out_mailbox->set_nearest_neighbor(GetGLFilter() == GL_NEAREST); |
| out_mailbox->set_color_space(color_params_.GetSamplerGfxColorSpace()); |
| |
| auto func = |
| WTF::Bind(&ReleaseFrameResources, weak_ptr_factory_.CreateWeakPtr(), |
| context_provider_wrapper_, WTF::Passed(std::move(info)), |
| out_mailbox->mailbox()); |
| *out_release_callback = viz::SingleReleaseCallback::Create( |
| ConvertToBaseCallback(std::move(func))); |
| return true; |
| } |
| |
| // TODO(xidachen): Make this a static local function once we deprecate the |
| // MailboxInfo structure and pass all the resources in that structure as params |
| // to this function. |
| void Canvas2DLayerBridge::ReleaseFrameResources( |
| WeakPtr<Canvas2DLayerBridge> layer_bridge, |
| WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper, |
| std::unique_ptr<MailboxInfo> released_mailbox_info, |
| const gpu::Mailbox& mailbox, |
| const gpu::SyncToken& sync_token, |
| bool lost_resource) { |
| if (sync_token.HasData() && context_provider_wrapper) { |
| context_provider_wrapper->ContextProvider() |
| ->ContextGL() |
| ->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); |
| } |
| bool context_or_layer_bridge_lost = true; |
| if (layer_bridge) { |
| DCHECK(layer_bridge->IsAccelerated() || layer_bridge->IsHibernating()); |
| context_or_layer_bridge_lost = |
| !layer_bridge->IsHibernating() && |
| (!layer_bridge->surface_ || !layer_bridge->context_provider_wrapper_ || |
| layer_bridge->context_provider_wrapper_->ContextProvider() |
| ->ContextGL() |
| ->GetGraphicsResetStatusKHR() != GL_NO_ERROR); |
| } |
| |
| scoped_refptr<ImageInfo>& info = released_mailbox_info->image_info_; |
| if (info) { |
| if (lost_resource || context_or_layer_bridge_lost) { |
| DeleteCHROMIUMImage(context_provider_wrapper, |
| std::move(info->gpu_memory_buffer_), info->image_id_, |
| info->texture_id_); |
| } else { |
| layer_bridge->image_info_cache_.push_back(info); |
| } |
| } |
| |
| // Invalidate texture state in case the compositor altered it since the |
| // copy-on-write. |
| if (released_mailbox_info->image_) { |
| DCHECK(!released_mailbox_info->image_info_); |
| bool layer_bridge_with_valid_context = |
| layer_bridge && !context_or_layer_bridge_lost; |
| if (layer_bridge_with_valid_context || !layer_bridge) { |
| ReleaseMailboxImageResource(std::move(released_mailbox_info->image_), |
| lost_resource); |
| } |
| } |
| |
| if (layer_bridge && layer_bridge->acceleration_mode_ == kDisableAcceleration) |
| layer_bridge->layer_.reset(); |
| } |
| |
| WebLayer* Canvas2DLayerBridge::Layer() const { |
| DCHECK(!destruction_in_progress_); |
| DCHECK(layer_); |
| return layer_->Layer(); |
| } |
| |
| void Canvas2DLayerBridge::DidDraw(const FloatRect& rect) { |
| if (is_deferral_enabled_) { |
| have_recorded_draw_commands_ = true; |
| IntRect pixel_bounds = EnclosingIntRect(rect); |
| CheckedNumeric<int> pixel_bounds_size = pixel_bounds.Width(); |
| pixel_bounds_size *= pixel_bounds.Height(); |
| recording_pixel_count_ += pixel_bounds_size; |
| if (!recording_pixel_count_.IsValid()) { |
| DisableDeferral(kDisableDeferralReasonExpensiveOverdrawHeuristic); |
| return; |
| } |
| CheckedNumeric<int> threshold_size = size_.Width(); |
| threshold_size *= size_.Height(); |
| threshold_size *= CanvasHeuristicParameters::kExpensiveOverdrawThreshold; |
| if (!threshold_size.IsValid()) { |
| DisableDeferral(kDisableDeferralReasonExpensiveOverdrawHeuristic); |
| return; |
| } |
| if (recording_pixel_count_.ValueOrDie() >= threshold_size.ValueOrDie()) { |
| DisableDeferral(kDisableDeferralReasonExpensiveOverdrawHeuristic); |
| } |
| } |
| did_draw_since_last_flush_ = true; |
| did_draw_since_last_gpu_flush_ = true; |
| } |
| |
| void Canvas2DLayerBridge::FinalizeFrame() { |
| TRACE_EVENT0("blink", "Canvas2DLayerBridge::finalizeFrame"); |
| DCHECK(!destruction_in_progress_); |
| |
| // Make sure surface is ready for painting: fix the rendering mode now |
| // because it will be too late during the paint invalidation phase. |
| GetOrCreateSurface(kPreferAcceleration); |
| |
| ++frames_since_last_commit_; |
| |
| if (frames_since_last_commit_ >= 2) { |
| if (IsAccelerated()) { |
| FlushGpuInternal(); |
| if (!rate_limiter_) { |
| rate_limiter_ = |
| SharedContextRateLimiter::Create(MaxCanvasAnimationBacklog); |
| } |
| } else { |
| FlushInternal(); |
| } |
| } |
| |
| if (rate_limiter_) { |
| rate_limiter_->Tick(); |
| } |
| } |
| |
| void Canvas2DLayerBridge::DoPaintInvalidation(const FloatRect& dirty_rect) { |
| DCHECK(!destruction_in_progress_); |
| if (layer_ && acceleration_mode_ != kDisableAcceleration) |
| layer_->Layer()->InvalidateRect(EnclosingIntRect(dirty_rect)); |
| } |
| |
| scoped_refptr<StaticBitmapImage> Canvas2DLayerBridge::NewImageSnapshot( |
| AccelerationHint hint, |
| SnapshotReason) { |
| if (IsHibernating()) |
| return StaticBitmapImage::Create(hibernation_image_); |
| if (!IsValid()) |
| return nullptr; |
| if (!GetOrCreateSurface(hint)) |
| return nullptr; |
| FlushInternal(); |
| if (IsAccelerated()) { |
| // 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. |
| GetOrCreateSurface()->notifyContentWillChange( |
| SkSurface::kRetain_ContentChangeMode); |
| } |
| scoped_refptr<StaticBitmapImage> image = StaticBitmapImage::Create( |
| surface_->makeImageSnapshot(), ContextProviderWrapper()); |
| if (image->IsTextureBacked()) { |
| static_cast<AcceleratedStaticBitmapImage*>(image.get()) |
| ->RetainOriginalSkImageForCopyOnWrite(); |
| } |
| return image; |
| } |
| |
| void Canvas2DLayerBridge::WillOverwriteCanvas() { |
| SkipQueuedDrawCommands(); |
| } |
| |
| Canvas2DLayerBridge::ImageInfo::ImageInfo( |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer, |
| GLuint image_id, |
| GLuint texture_id) |
| : gpu_memory_buffer_(std::move(gpu_memory_buffer)), |
| image_id_(image_id), |
| texture_id_(texture_id) { |
| DCHECK(gpu_memory_buffer_); |
| DCHECK(image_id_); |
| DCHECK(texture_id_); |
| } |
| |
| Canvas2DLayerBridge::ImageInfo::~ImageInfo() {} |
| |
| Canvas2DLayerBridge::MailboxInfo::MailboxInfo() = default; |
| Canvas2DLayerBridge::MailboxInfo::MailboxInfo(const MailboxInfo& other) = |
| default; |
| |
| void Canvas2DLayerBridge::Logger::ReportHibernationEvent( |
| HibernationEvent event) { |
| DEFINE_STATIC_LOCAL(EnumerationHistogram, hibernation_histogram, |
| ("Canvas.HibernationEvents", kHibernationEventCount)); |
| hibernation_histogram.Count(event); |
| } |
| |
| } // namespace blink |