| // Copyright 2014 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 "platform/graphics/RecordingImageBufferSurface.h" |
| |
| #include <memory> |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "platform/Histogram.h" |
| #include "platform/graphics/CanvasHeuristicParameters.h" |
| #include "platform/graphics/CanvasMetrics.h" |
| #include "platform/graphics/GraphicsContext.h" |
| #include "platform/graphics/ImageBuffer.h" |
| #include "platform/graphics/StaticBitmapImage.h" |
| #include "platform/graphics/UnacceleratedImageBufferSurface.h" |
| #include "platform/graphics/paint/PaintRecorder.h" |
| #include "platform/wtf/CheckedNumeric.h" |
| #include "platform/wtf/PtrUtil.h" |
| |
| namespace blink { |
| |
| RecordingImageBufferSurface::RecordingImageBufferSurface( |
| const IntSize& size, |
| AllowFallback allow_fallback, |
| const CanvasColorParams& color_params) |
| : ImageBufferSurface(size, color_params), |
| allow_fallback_(allow_fallback), |
| image_buffer_(nullptr), |
| current_frame_pixel_count_(0), |
| previous_frame_pixel_count_(0), |
| frame_was_cleared_(true), |
| did_record_draw_commands_in_current_frame_(false), |
| current_frame_has_expensive_op_(false), |
| previous_frame_has_expensive_op_(false), |
| resource_host_(nullptr) { |
| InitializeCurrentFrame(); |
| } |
| |
| RecordingImageBufferSurface::~RecordingImageBufferSurface() {} |
| |
| void RecordingImageBufferSurface::InitializeCurrentFrame() { |
| current_frame_ = WTF::WrapUnique(new PaintRecorder); |
| PaintCanvas* canvas = |
| current_frame_->beginRecording(Size().Width(), Size().Height()); |
| // Always save an initial frame, to support resetting the top level matrix |
| // and clip. |
| canvas->save(); |
| |
| if (resource_host_) { |
| resource_host_->RestoreCanvasMatrixClipStack(canvas); |
| } |
| did_record_draw_commands_in_current_frame_ = false; |
| current_frame_has_expensive_op_ = false; |
| current_frame_pixel_count_ = 0; |
| } |
| |
| void RecordingImageBufferSurface::SetImageBuffer(ImageBuffer* image_buffer) { |
| image_buffer_ = image_buffer; |
| } |
| |
| bool RecordingImageBufferSurface::WritePixels(const SkImageInfo& orig_info, |
| const void* pixels, |
| size_t row_bytes, |
| int x, |
| int y) { |
| if (!fallback_surface_) { |
| IntRect write_rect(x, y, orig_info.width(), orig_info.height()); |
| if (write_rect.Contains(IntRect(IntPoint(), Size()))) |
| WillOverwriteCanvas(); |
| FallBackToRasterCanvas(kFallbackReasonWritePixels); |
| if (!fallback_surface_->IsValid()) |
| return false; |
| } |
| return fallback_surface_->WritePixels(orig_info, pixels, row_bytes, x, y); |
| } |
| |
| void RecordingImageBufferSurface::FallBackToRasterCanvas( |
| FallbackReason reason) { |
| DCHECK(allow_fallback_ == kAllowFallback); |
| CHECK(reason != kFallbackReasonUnknown); |
| |
| if (fallback_surface_) { |
| DCHECK(!current_frame_); |
| return; |
| } |
| |
| DEFINE_THREAD_SAFE_STATIC_LOCAL( |
| EnumerationHistogram, canvas_fallback_histogram, |
| ("Canvas.DisplayListFallbackReason", kFallbackReasonCount)); |
| canvas_fallback_histogram.Count(reason); |
| |
| fallback_surface_ = WTF::WrapUnique(new UnacceleratedImageBufferSurface( |
| Size(), kInitializeImagePixels, ColorParams())); |
| // If the fallback surface fails to be created, then early out. |
| if (!fallback_surface_->IsValid()) |
| return; |
| |
| if (previous_frame_) { |
| fallback_surface_->Canvas()->drawPicture(previous_frame_); |
| previous_frame_.reset(); |
| } |
| |
| if (current_frame_) { |
| sk_sp<PaintRecord> record = current_frame_->finishRecordingAsPicture(); |
| if (record) |
| fallback_surface_->Canvas()->drawPicture(record); |
| current_frame_.reset(); |
| } |
| |
| if (resource_host_) { |
| resource_host_->RestoreCanvasMatrixClipStack(fallback_surface_->Canvas()); |
| } |
| |
| CanvasMetrics::CountCanvasContextUsage( |
| CanvasMetrics::kDisplayList2DCanvasFallbackToRaster); |
| } |
| |
| static RecordingImageBufferSurface::FallbackReason |
| SnapshotReasonToFallbackReason(SnapshotReason reason) { |
| switch (reason) { |
| case kSnapshotReasonLowLatencyFrame: |
| NOTREACHED(); // Low latency frames should not cause fallbacks |
| // No 'break' on purpose. |
| case kSnapshotReasonUnknown: |
| return RecordingImageBufferSurface::kFallbackReasonUnknown; |
| case kSnapshotReasonGetImageData: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForGetImageData; |
| case kSnapshotReasonPaint: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotForPaint; |
| case kSnapshotReasonToDataURL: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotForToDataURL; |
| case kSnapshotReasonToBlob: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotForToBlob; |
| case kSnapshotReasonCanvasListenerCapture: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForCanvasListenerCapture; |
| case kSnapshotReasonDrawImage: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotForDrawImage; |
| case kSnapshotReasonCreatePattern: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForCreatePattern; |
| case kSnapshotReasonTransferToImageBitmap: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForTransferToImageBitmap; |
| case kSnapshotReasonUnitTests: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotForUnitTests; |
| case kSnapshotReasonGetCopiedImage: |
| return RecordingImageBufferSurface::kFallbackReasonSnapshotGetCopiedImage; |
| case kSnapshotReasonWebGLDrawImageIntoBuffer: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotWebGLDrawImageIntoBuffer; |
| case kSnapshotReasonWebGLTexImage2D: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForWebGLTexImage2D; |
| case kSnapshotReasonWebGLTexSubImage2D: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForWebGLTexSubImage2D; |
| case kSnapshotReasonWebGLTexImage3D: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForWebGLTexImage3D; |
| case kSnapshotReasonWebGLTexSubImage3D: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForWebGLTexSubImage3D; |
| case kSnapshotReasonCopyToClipboard: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForCopyToClipboard; |
| case kSnapshotReasonCreateImageBitmap: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSnapshotForCreateImageBitmap; |
| } |
| NOTREACHED(); |
| return RecordingImageBufferSurface::kFallbackReasonUnknown; |
| } |
| |
| scoped_refptr<StaticBitmapImage> RecordingImageBufferSurface::NewImageSnapshot( |
| AccelerationHint hint, |
| SnapshotReason reason) { |
| if (!fallback_surface_) |
| FallBackToRasterCanvas(SnapshotReasonToFallbackReason(reason)); |
| if (!fallback_surface_->IsValid()) |
| return nullptr; |
| return fallback_surface_->NewImageSnapshot(hint, reason); |
| } |
| |
| PaintCanvas* RecordingImageBufferSurface::Canvas() { |
| if (fallback_surface_) { |
| DCHECK(fallback_surface_->IsValid()); |
| return fallback_surface_->Canvas(); |
| } |
| |
| DCHECK(current_frame_->getRecordingCanvas()); |
| return current_frame_->getRecordingCanvas(); |
| } |
| |
| static RecordingImageBufferSurface::FallbackReason |
| DisableDeferralReasonToFallbackReason(DisableDeferralReason reason) { |
| switch (reason) { |
| case kDisableDeferralReasonUnknown: |
| return RecordingImageBufferSurface::kFallbackReasonUnknown; |
| case kDisableDeferralReasonExpensiveOverdrawHeuristic: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonExpensiveOverdrawHeuristic; |
| case kDisableDeferralReasonUsingTextureBackedPattern: |
| return RecordingImageBufferSurface::kFallbackReasonTextureBackedPattern; |
| case kDisableDeferralReasonDrawImageOfVideo: |
| return RecordingImageBufferSurface::kFallbackReasonDrawImageOfVideo; |
| case kDisableDeferralReasonDrawImageOfAnimated2dCanvas: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonDrawImageOfAnimated2dCanvas; |
| case kDisableDeferralReasonSubPixelTextAntiAliasingSupport: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonSubPixelTextAntiAliasingSupport; |
| case kDisableDeferralDrawImageWithTextureBackedSourceImage: |
| return RecordingImageBufferSurface:: |
| kFallbackReasonDrawImageWithTextureBackedSourceImage; |
| // The LowEndDevice reason should only be used on Canvas2DLayerBridge. |
| case kDisableDeferralReasonLowEndDevice: |
| case kDisableDeferralReasonCount: |
| NOTREACHED(); |
| break; |
| } |
| NOTREACHED(); |
| return RecordingImageBufferSurface::kFallbackReasonUnknown; |
| } |
| |
| void RecordingImageBufferSurface::DisableDeferral( |
| DisableDeferralReason reason) { |
| if (!fallback_surface_) |
| FallBackToRasterCanvas(DisableDeferralReasonToFallbackReason(reason)); |
| } |
| |
| sk_sp<PaintRecord> RecordingImageBufferSurface::GetRecord() { |
| if (fallback_surface_) |
| return nullptr; |
| |
| FallbackReason fallback_reason = kFallbackReasonUnknown; |
| bool can_use_record = FinalizeFrameInternal(&fallback_reason); |
| |
| if (can_use_record) { |
| return previous_frame_; |
| } |
| |
| if (!fallback_surface_) |
| FallBackToRasterCanvas(fallback_reason); |
| return nullptr; |
| } |
| |
| void RecordingImageBufferSurface::FinalizeFrame() { |
| if (fallback_surface_) { |
| fallback_surface_->FinalizeFrame(); |
| return; |
| } |
| |
| FallbackReason fallback_reason = kFallbackReasonUnknown; |
| if (!FinalizeFrameInternal(&fallback_reason)) |
| FallBackToRasterCanvas(fallback_reason); |
| } |
| |
| void RecordingImageBufferSurface::WillOverwriteCanvas() { |
| frame_was_cleared_ = true; |
| previous_frame_.reset(); |
| previous_frame_has_expensive_op_ = false; |
| previous_frame_pixel_count_ = 0; |
| if (did_record_draw_commands_in_current_frame_) { |
| // Discard previous draw commands |
| current_frame_->finishRecordingAsPicture(); |
| InitializeCurrentFrame(); |
| } |
| } |
| |
| void RecordingImageBufferSurface::DidDraw(const FloatRect& rect) { |
| did_record_draw_commands_in_current_frame_ = true; |
| IntRect pixel_bounds = EnclosingIntRect(rect); |
| CheckedNumeric<int> pixel_count = pixel_bounds.Width(); |
| pixel_count *= pixel_bounds.Height(); |
| pixel_count += current_frame_pixel_count_; |
| current_frame_pixel_count_ = |
| pixel_count.ValueOrDefault(std::numeric_limits<int>::max()); |
| } |
| |
| bool RecordingImageBufferSurface::FinalizeFrameInternal( |
| FallbackReason* fallback_reason) { |
| CHECK(!fallback_surface_); |
| CHECK(current_frame_); |
| DCHECK(current_frame_->getRecordingCanvas()); |
| DCHECK(fallback_reason); |
| DCHECK(*fallback_reason == kFallbackReasonUnknown); |
| if (!did_record_draw_commands_in_current_frame_) { |
| if (!previous_frame_) { |
| // Create an initial blank frame |
| previous_frame_ = current_frame_->finishRecordingAsPicture(); |
| InitializeCurrentFrame(); |
| } |
| CHECK(current_frame_); |
| return true; |
| } |
| |
| if (!frame_was_cleared_) { |
| *fallback_reason = kFallbackReasonCanvasNotClearedBetweenFrames; |
| return false; |
| } |
| |
| if (allow_fallback_ == kAllowFallback && |
| current_frame_->getRecordingCanvas()->getSaveCount() - 1 > |
| CanvasHeuristicParameters::kExpensiveRecordingStackDepth) { |
| // (getSaveCount() decremented to account for the intial recording canvas |
| // save frame.) |
| *fallback_reason = kFallbackReasonRunawayStateStack; |
| return false; |
| } |
| |
| previous_frame_ = current_frame_->finishRecordingAsPicture(); |
| previous_frame_has_expensive_op_ = current_frame_has_expensive_op_; |
| previous_frame_pixel_count_ = current_frame_pixel_count_; |
| InitializeCurrentFrame(); |
| |
| frame_was_cleared_ = false; |
| return true; |
| } |
| |
| void RecordingImageBufferSurface::Draw(GraphicsContext& context, |
| const FloatRect& dest_rect, |
| const FloatRect& src_rect, |
| SkBlendMode op) { |
| if (fallback_surface_) { |
| fallback_surface_->Draw(context, dest_rect, src_rect, op); |
| return; |
| } |
| |
| sk_sp<PaintRecord> record = GetRecord(); |
| if (record) { |
| context.CompositeRecord(std::move(record), dest_rect, src_rect, op); |
| } else { |
| ImageBufferSurface::Draw(context, dest_rect, src_rect, op); |
| } |
| } |
| |
| bool RecordingImageBufferSurface::IsExpensiveToPaint() { |
| if (fallback_surface_) |
| return fallback_surface_->IsExpensiveToPaint(); |
| |
| CheckedNumeric<int> overdraw_limit_checked = Size().Width(); |
| overdraw_limit_checked *= Size().Height(); |
| overdraw_limit_checked *= |
| CanvasHeuristicParameters::kExpensiveOverdrawThreshold; |
| int overdraw_limit = |
| overdraw_limit_checked.ValueOrDefault(std::numeric_limits<int>::max()); |
| |
| if (did_record_draw_commands_in_current_frame_) { |
| if (current_frame_has_expensive_op_) |
| return true; |
| |
| if (current_frame_pixel_count_ >= overdraw_limit) |
| return true; |
| |
| if (frame_was_cleared_) |
| return false; // early exit because previous frame is overdrawn |
| } |
| |
| if (previous_frame_) { |
| if (previous_frame_has_expensive_op_) |
| return true; |
| |
| if (previous_frame_pixel_count_ >= overdraw_limit) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Fallback passthroughs |
| |
| bool RecordingImageBufferSurface::Restore() { |
| if (fallback_surface_) |
| return fallback_surface_->Restore(); |
| return ImageBufferSurface::Restore(); |
| } |
| |
| WebLayer* RecordingImageBufferSurface::Layer() { |
| if (fallback_surface_) |
| return fallback_surface_->Layer(); |
| return ImageBufferSurface::Layer(); |
| } |
| |
| bool RecordingImageBufferSurface::IsAccelerated() const { |
| if (fallback_surface_) |
| return fallback_surface_->IsAccelerated(); |
| return ImageBufferSurface::IsAccelerated(); |
| } |
| |
| void RecordingImageBufferSurface::SetIsHidden(bool hidden) { |
| if (fallback_surface_) |
| fallback_surface_->SetIsHidden(hidden); |
| else |
| ImageBufferSurface::SetIsHidden(hidden); |
| } |
| |
| } // namespace blink |