| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/paint/paint_op_buffer.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/compiler_specific.h" |
| #include "base/notreached.h" |
| #include "base/types/optional_util.h" |
| #include "cc/paint/display_item_list.h" |
| #include "cc/paint/paint_flags.h" |
| #include "cc/paint/paint_image_builder.h" |
| #include "cc/paint/paint_op.h" |
| #include "cc/paint/paint_op_buffer_iterator.h" |
| #include "cc/paint/paint_op_reader.h" |
| #include "cc/paint/paint_op_writer.h" |
| #include "cc/paint/paint_record.h" |
| #include "cc/paint/scoped_raster_flags.h" |
| #include "cc/paint/skottie_serialization_history.h" |
| #include "third_party/skia/include/core/SkTextBlob.h" |
| #include "third_party/skia/include/gpu/ganesh/GrRecordingContext.h" |
| #include "third_party/skia/include/gpu/graphite/Recorder.h" |
| #include "third_party/skia/include/private/chromium/Slug.h" |
| |
| namespace cc { |
| |
| PlaybackCallbacks::PlaybackCallbacks() = default; |
| PlaybackCallbacks::~PlaybackCallbacks() = default; |
| PlaybackCallbacks::PlaybackCallbacks(const PlaybackCallbacks&) = default; |
| PlaybackCallbacks& PlaybackCallbacks::operator=(const PlaybackCallbacks&) = |
| default; |
| |
| PlaybackParams::PlaybackParams(ImageProvider* image_provider, |
| const SkM44& original_ctm, |
| const PlaybackCallbacks& callbacks) |
| : image_provider(image_provider), |
| original_ctm(original_ctm), |
| callbacks(callbacks) {} |
| |
| PlaybackParams::~PlaybackParams() = default; |
| |
| PaintOpBuffer::SerializeOptions::SerializeOptions( |
| ImageProvider* image_provider, |
| TransferCacheSerializeHelper* transfer_cache, |
| ClientPaintCache* paint_cache, |
| SkStrikeServer* strike_server, |
| sk_sp<SkColorSpace> color_space, |
| SkottieSerializationHistory* skottie_serialization_history, |
| bool can_use_lcd_text, |
| bool context_supports_distance_field_text, |
| int max_texture_size, |
| const ScrollOffsetMap* raster_inducing_scroll_offsets) |
| : image_provider(image_provider), |
| transfer_cache(transfer_cache), |
| paint_cache(paint_cache), |
| strike_server(strike_server), |
| color_space(std::move(color_space)), |
| skottie_serialization_history(skottie_serialization_history), |
| can_use_lcd_text(can_use_lcd_text), |
| context_supports_distance_field_text( |
| context_supports_distance_field_text), |
| max_texture_size(max_texture_size), |
| raster_inducing_scroll_offsets(raster_inducing_scroll_offsets) {} |
| |
| PaintOpBuffer::SerializeOptions::SerializeOptions() = default; |
| PaintOpBuffer::SerializeOptions::SerializeOptions(const SerializeOptions&) = |
| default; |
| PaintOpBuffer::SerializeOptions& PaintOpBuffer::SerializeOptions::operator=( |
| const SerializeOptions&) = default; |
| PaintOpBuffer::SerializeOptions::~SerializeOptions() = default; |
| |
| PaintOpBuffer::PaintOpBuffer() = default; |
| |
| PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) { |
| *this = std::move(other); |
| } |
| |
| PaintRecord PaintOpBuffer::DeepCopyAsRecord() { |
| auto result = sk_make_sp<PaintOpBuffer>(); |
| if (data_) { |
| result->ReallocBuffer(used_); |
| } |
| |
| for (const PaintOp& op : *this) { |
| switch (op.GetType()) { |
| case PaintOpType::kAnnotate: { |
| const auto& o = static_cast<const AnnotateOp&>(op); |
| result->push<AnnotateOp>(o.annotation_type, o.rect, o.data); |
| } break; |
| case PaintOpType::kClipPath: { |
| const auto& o = static_cast<const ClipPathOp&>(op); |
| result->push<ClipPathOp>(o.path, o.op, o.antialias, o.use_cache); |
| } break; |
| case PaintOpType::kClipRect: { |
| const auto& o = static_cast<const ClipRectOp&>(op); |
| result->push<ClipRectOp>(o.rect, o.op, o.antialias); |
| } break; |
| case PaintOpType::kClipRRect: { |
| const auto& o = static_cast<const ClipRRectOp&>(op); |
| result->push<ClipRRectOp>(o.rrect, o.op, o.antialias); |
| } break; |
| case PaintOpType::kConcat: { |
| const auto& o = static_cast<const ConcatOp&>(op); |
| result->push<ConcatOp>(o.matrix); |
| } break; |
| case PaintOpType::kCustomData: { |
| const auto& o = static_cast<const CustomDataOp&>(op); |
| result->push<CustomDataOp>(o.id); |
| } break; |
| case PaintOpType::kDrawArc: { |
| const auto& o = static_cast<const DrawArcOp&>(op); |
| result->push<DrawArcOp>(o.oval, o.start_angle_degrees, |
| o.sweep_angle_degrees, o.flags); |
| } break; |
| case PaintOpType::kDrawArcLite: { |
| const auto& o = static_cast<const DrawArcLiteOp&>(op); |
| result->push<DrawArcLiteOp>(o.oval, o.start_angle_degrees, |
| o.sweep_angle_degrees, o.core_paint_flags); |
| } break; |
| case PaintOpType::kDrawColor: { |
| const auto& o = static_cast<const DrawColorOp&>(op); |
| result->push<DrawColorOp>(o.color, o.mode); |
| } break; |
| case PaintOpType::kDrawDRRect: { |
| const auto& o = static_cast<const DrawDRRectOp&>(op); |
| result->push<DrawDRRectOp>(o.outer, o.inner, o.flags); |
| } break; |
| case PaintOpType::kDrawImage: { |
| const auto& o = static_cast<const DrawImageOp&>(op); |
| result->push<DrawImageOp>(o.image, o.left, o.top, o.sampling, &o.flags); |
| } break; |
| case PaintOpType::kDrawImageRect: { |
| const auto& o = static_cast<const DrawImageRectOp&>(op); |
| result->push<DrawImageRectOp>(o.image, o.src, o.dst, o.sampling, |
| &o.flags, o.constraint); |
| } break; |
| case PaintOpType::kDrawIRect: { |
| const auto& o = static_cast<const DrawIRectOp&>(op); |
| result->push<DrawIRectOp>(o.rect, o.flags); |
| } break; |
| case PaintOpType::kDrawLine: { |
| const auto& o = static_cast<const DrawLineOp&>(op); |
| result->push<DrawLineOp>(o.x0, o.y0, o.x1, o.y1, o.flags); |
| } break; |
| case PaintOpType::kDrawLineLite: { |
| const auto& o = static_cast<const DrawLineLiteOp&>(op); |
| result->push<DrawLineLiteOp>(o.x0, o.y0, o.x1, o.y1, |
| o.core_paint_flags); |
| } break; |
| case PaintOpType::kDrawOval: { |
| const auto& o = static_cast<const DrawOvalOp&>(op); |
| result->push<DrawOvalOp>(o.oval, o.flags); |
| } break; |
| case PaintOpType::kDrawPath: { |
| const auto& o = static_cast<const DrawPathOp&>(op); |
| result->push<DrawPathOp>(o.path, o.flags, o.use_cache); |
| } break; |
| case PaintOpType::kDrawRecord: { |
| const auto& o = static_cast<const DrawRecordOp&>(op); |
| result->push<DrawRecordOp>(o.record, o.local_ctm); |
| } break; |
| case PaintOpType::kDrawRect: { |
| const auto& o = static_cast<const DrawRectOp&>(op); |
| result->push<DrawRectOp>(o.rect, o.flags); |
| } break; |
| case PaintOpType::kDrawRRect: { |
| const auto& o = static_cast<const DrawRRectOp&>(op); |
| result->push<DrawRRectOp>(o.rrect, o.flags); |
| } break; |
| case PaintOpType::kDrawScrollingContents: { |
| const auto& o = static_cast<const DrawScrollingContentsOp&>(op); |
| result->push<DrawScrollingContentsOp>(o.scroll_element_id, |
| o.display_item_list); |
| } break; |
| case PaintOpType::kDrawSkottie: { |
| const auto& o = static_cast<const DrawSkottieOp&>(op); |
| result->push<DrawSkottieOp>(o.skottie, o.dst, o.t, o.images, |
| o.color_map, o.text_map); |
| } break; |
| case PaintOpType::kDrawSlug: { |
| const auto& o = static_cast<const DrawSlugOp&>(op); |
| result->push<DrawSlugOp>(o.slug, o.flags); |
| } break; |
| case PaintOpType::kDrawTextBlob: { |
| const auto& o = static_cast<const DrawTextBlobOp&>(op); |
| result->push<DrawTextBlobOp>(o.blob, o.x, o.y, o.node_id, o.flags); |
| } break; |
| case PaintOpType::kDrawVertices: { |
| const auto& o = static_cast<const DrawVerticesOp&>(op); |
| result->push<DrawVerticesOp>(o.vertices, o.uvs, o.indices, o.flags); |
| } break; |
| case PaintOpType::kNoop: { |
| result->push<NoopOp>(); |
| } break; |
| case PaintOpType::kRestore: { |
| result->push<RestoreOp>(); |
| } break; |
| case PaintOpType::kRotate: { |
| const auto& o = static_cast<const RotateOp&>(op); |
| result->push<RotateOp>(o.degrees); |
| } break; |
| case PaintOpType::kSave: { |
| result->push<SaveOp>(); |
| } break; |
| case PaintOpType::kSaveLayer: { |
| const auto& o = static_cast<const SaveLayerOp&>(op); |
| result->push<SaveLayerOp>(o.bounds, o.flags); |
| } break; |
| case PaintOpType::kSaveLayerAlpha: { |
| const auto& o = static_cast<const SaveLayerAlphaOp&>(op); |
| result->push<SaveLayerAlphaOp>(o.bounds, o.alpha); |
| } break; |
| case PaintOpType::kSaveLayerFilters: { |
| const auto& o = static_cast<const SaveLayerFiltersOp&>(op); |
| auto f = o.filters; |
| result->push<SaveLayerFiltersOp>(std::move(f), o.flags); |
| } break; |
| case PaintOpType::kScale: { |
| const auto& o = static_cast<const ScaleOp&>(op); |
| result->push<ScaleOp>(o.sx, o.sy); |
| } break; |
| case PaintOpType::kSetMatrix: { |
| const auto& o = static_cast<const SetMatrixOp&>(op); |
| result->push<SetMatrixOp>(o.matrix); |
| } break; |
| case PaintOpType::kSetNodeId: { |
| const auto& o = static_cast<const SetNodeIdOp&>(op); |
| result->push<SetNodeIdOp>(o.node_id); |
| } break; |
| case PaintOpType::kTranslate: { |
| const auto& o = static_cast<const TranslateOp&>(op); |
| result->push<TranslateOp>(o.dx, o.dy); |
| } break; |
| } |
| } |
| |
| return PaintRecord(std::move(result)); |
| } |
| |
| PaintOpBuffer::~PaintOpBuffer() { |
| DestroyOps(); |
| } |
| |
| PaintOpBuffer& PaintOpBuffer::operator=(PaintOpBuffer&& other) { |
| data_ = std::move(other.data_); |
| DCHECK(!other.data_); |
| used_ = other.used_; |
| reserved_ = other.reserved_; |
| op_count_ = other.op_count_; |
| num_slow_paths_up_to_min_for_MSAA_ = other.num_slow_paths_up_to_min_for_MSAA_; |
| subrecord_bytes_used_ = other.subrecord_bytes_used_; |
| subrecord_op_count_ = other.subrecord_op_count_; |
| has_non_aa_paint_ = other.has_non_aa_paint_; |
| has_draw_ops_ = other.has_draw_ops_; |
| has_draw_text_ops_ = other.has_draw_text_ops_; |
| has_save_layer_ops_ = other.has_save_layer_ops_; |
| has_save_layer_alpha_ops_ = other.has_save_layer_alpha_ops_; |
| has_effects_preventing_lcd_text_for_save_layer_alpha_ = |
| other.has_effects_preventing_lcd_text_for_save_layer_alpha_; |
| has_discardable_images_ = other.has_discardable_images_; |
| content_color_usage_ = other.content_color_usage_; |
| |
| // Make sure the other pob can destruct safely or is ready for reuse. |
| other.reserved_ = 0; |
| other.ResetRetainingBuffer(); |
| return *this; |
| } |
| |
| void PaintOpBuffer::DestroyOps() { |
| if (data_) { |
| for (size_t offset = 0; offset < used_;) { |
| auto* op = UNSAFE_TODO(reinterpret_cast<PaintOp*>(data_.get() + offset)); |
| offset += op->AlignedSize(); |
| op->DestroyThis(); |
| } |
| } |
| } |
| |
| void PaintOpBuffer::Reset() { |
| DCHECK(is_mutable()); |
| DestroyOps(); |
| // Leave data_ allocated, reserved_ unchanged. ShrinkToFit() will take care |
| // of that if called. |
| ResetRetainingBuffer(); |
| } |
| |
| void PaintOpBuffer::ResetRetainingBuffer() { |
| DCHECK(is_mutable()); |
| used_ = 0; |
| op_count_ = 0; |
| num_slow_paths_up_to_min_for_MSAA_ = 0; |
| has_non_aa_paint_ = false; |
| subrecord_bytes_used_ = 0; |
| subrecord_op_count_ = 0; |
| has_draw_ops_ = false; |
| has_draw_text_ops_ = false; |
| has_save_layer_ops_ = false; |
| has_save_layer_alpha_ops_ = false; |
| has_effects_preventing_lcd_text_for_save_layer_alpha_ = false; |
| has_discardable_images_ = false; |
| content_color_usage_ = gfx::ContentColorUsage::kSRGB; |
| } |
| |
| void PaintOpBuffer::Playback(SkCanvas* canvas) const { |
| Playback(canvas, PlaybackParams(nullptr), /*local_ctm=*/true, |
| /*offsets=*/nullptr); |
| } |
| |
| void PaintOpBuffer::Playback(SkCanvas* canvas, |
| const PlaybackParams& params, |
| bool local_ctm) const { |
| Playback(canvas, params, local_ctm, /*offsets=*/nullptr); |
| } |
| |
| PaintRecord PaintOpBuffer::ReleaseAsRecord() { |
| DCHECK(is_mutable()); |
| const size_t old_reserved = reserved_; |
| auto result = sk_make_sp<PaintOpBuffer>(std::move(*this)); |
| if (BufferDataPtr old_data = result->ReallocIfNeededToFit()) { |
| // Reuse the original buffer for future recording. |
| data_ = std::move(old_data); |
| reserved_ = old_reserved; |
| } |
| return PaintRecord(std::move(result)); |
| } |
| |
| void PaintOpBuffer::Playback(SkCanvas* canvas, |
| const PlaybackParams& params, |
| bool local_ctm, |
| const std::vector<size_t>* offsets) const { |
| if (!op_count_) |
| return; |
| if (offsets && offsets->empty()) |
| return; |
| // Make sure the there are no pending saves after we are done. Add a save if |
| // this `PaintOpBuffer` isn't meant to impact the global CTM (to prevent |
| // PaintOps from having side effects back into the canvas) |
| SkAutoCanvasRestore save_restore(canvas, /*doSave=*/local_ctm); |
| |
| bool save_layer_alpha_should_preserve_lcd_text = |
| (!params.save_layer_alpha_should_preserve_lcd_text.has_value() || |
| *params.save_layer_alpha_should_preserve_lcd_text) && |
| has_draw_text_ops_ && |
| !has_effects_preventing_lcd_text_for_save_layer_alpha_; |
| if (save_layer_alpha_should_preserve_lcd_text) { |
| // Check if the canvas supports LCD text. |
| SkSurfaceProps props; |
| canvas->getProps(&props); |
| if (props.pixelGeometry() == kUnknown_SkPixelGeometry) |
| save_layer_alpha_should_preserve_lcd_text = false; |
| } |
| |
| // TODO(enne): a PaintRecord that contains a SetMatrix assumes that the |
| // SetMatrix is local to that PaintRecord itself. Said differently, if you |
| // translate(x, y), then draw a paint record with a SetMatrix(identity), |
| // the translation should be preserved instead of clobbering the top level |
| // transform. This could probably be done more efficiently. |
| PlaybackParams new_params = params; |
| if (local_ctm) { |
| new_params.original_ctm = canvas->getLocalToDevice(); |
| } |
| new_params.save_layer_alpha_should_preserve_lcd_text = |
| save_layer_alpha_should_preserve_lcd_text; |
| for (PlaybackFoldingIterator iter(*this, offsets); iter; ++iter) { |
| const PaintOp* op = iter.get(); |
| if (params.callbacks.convert_op_callback) { |
| op = params.callbacks.convert_op_callback.Run(*op); |
| if (!op) |
| continue; |
| } |
| |
| // This is an optimization to replicate the behaviour in SkCanvas |
| // which rejects ops that draw outside the current clip. In the |
| // general case we defer this to the SkCanvas but if we will be |
| // using an ImageProvider for pre-decoding images, we can save |
| // performing an expensive decode that will never be rasterized. |
| const bool skip_op = new_params.image_provider && |
| PaintOp::OpHasDiscardableImages(*op) && |
| PaintOp::QuickRejectDraw(*op, canvas); |
| if (skip_op) |
| continue; |
| |
| if (op->IsPaintOpWithFlags()) { |
| int max_texture_size; |
| if (auto* context = canvas->recordingContext()) { |
| max_texture_size = context->maxTextureSize(); |
| } else if (auto* recorder = canvas->recorder()) { |
| max_texture_size = recorder->maxTextureSize(); |
| } else { |
| // This can happen in tests. |
| max_texture_size = 0; |
| } |
| |
| const auto& flags_op = static_cast<const PaintOpWithFlags&>(*op); |
| const ScopedRasterFlags scoped_flags( |
| &flags_op.flags, new_params.image_provider, canvas->getTotalMatrix(), |
| max_texture_size, iter.alpha()); |
| if (const auto* raster_flags = scoped_flags.flags()) |
| flags_op.RasterWithFlags(canvas, raster_flags, new_params); |
| } else { |
| DCHECK_EQ(iter.alpha(), 1.0f); |
| op->Raster(canvas, new_params); |
| } |
| |
| if (!new_params.callbacks.did_draw_op_callback.is_null()) { |
| new_params.callbacks.did_draw_op_callback.Run(); |
| } |
| } |
| } |
| |
| bool PaintOpBuffer::Deserialize(const volatile void* input, |
| size_t input_size, |
| const PaintOp::DeserializeOptions& options) { |
| size_t total_bytes_read = 0u; |
| while (total_bytes_read < input_size) { |
| const volatile void* next_op = UNSAFE_TODO( |
| static_cast<const volatile char*>(input) + total_bytes_read); |
| size_t read_bytes = 0; |
| if (!PaintOp::DeserializeIntoPaintOpBuffer(next_op, |
| input_size - total_bytes_read, |
| this, &read_bytes, options)) { |
| return false; |
| } |
| total_bytes_read += read_bytes; |
| } |
| |
| DCHECK_GT(size(), 0u); |
| return true; |
| } |
| |
| // static |
| sk_sp<PaintOpBuffer> PaintOpBuffer::MakeFromMemory( |
| const volatile void* input, |
| size_t input_size, |
| const PaintOp::DeserializeOptions& options) { |
| auto buffer = sk_make_sp<PaintOpBuffer>(); |
| if (input_size == 0) |
| return buffer; |
| if (!buffer->Deserialize(input, input_size, options)) |
| return nullptr; |
| return buffer; |
| } |
| |
| // static |
| SkRect PaintOpBuffer::GetFixedScaleBounds(const SkMatrix& ctm, |
| const SkRect& bounds, |
| int max_texture_size) { |
| SkSize scale; |
| if (!ctm.decomposeScale(&scale)) { |
| // Decomposition failed, use an approximation. |
| scale.set(SkScalarSqrt(ctm.getScaleX() * ctm.getScaleX() + |
| ctm.getSkewX() * ctm.getSkewX()), |
| SkScalarSqrt(ctm.getScaleY() * ctm.getScaleY() + |
| ctm.getSkewY() * ctm.getSkewY())); |
| } |
| |
| SkScalar raster_width = bounds.width() * scale.width(); |
| SkScalar raster_height = bounds.height() * scale.height(); |
| SkScalar tile_area = raster_width * raster_height; |
| // Clamp the tile area to about 4M pixels, and per-dimension max texture size |
| // if it's provided. |
| static const SkScalar kMaxTileArea = 2048 * 2048; |
| SkScalar down_scale = 1.f; |
| if (tile_area > kMaxTileArea) { |
| down_scale = SkScalarSqrt(kMaxTileArea / tile_area); |
| } |
| if (max_texture_size > 0) { |
| // This only updates down_scale if the tile is larger than the texture size |
| // after ensuring its area is less than kMaxTileArea |
| down_scale = std::min( |
| down_scale, max_texture_size / std::max(raster_width, raster_height)); |
| } |
| |
| if (down_scale < 1.f) { |
| scale.set(down_scale * scale.width(), down_scale * scale.height()); |
| } |
| return SkRect::MakeXYWH( |
| bounds.fLeft * scale.width(), bounds.fTop * scale.height(), |
| SkScalarCeilToInt(SkScalarAbs(scale.width() * bounds.width())), |
| SkScalarCeilToInt(SkScalarAbs(scale.height() * bounds.height()))); |
| } |
| |
| PaintOpBuffer::BufferDataPtr PaintOpBuffer::ReallocBuffer(size_t new_size) { |
| DCHECK_GE(new_size, used_); |
| DCHECK(is_mutable()); |
| |
| std::unique_ptr<char, base::AlignedFreeDeleter> new_data( |
| static_cast<char*>(base::AlignedAlloc(new_size, kPaintOpAlign))); |
| if (data_) |
| UNSAFE_TODO(memcpy(new_data.get(), data_.get(), used_)); |
| BufferDataPtr old_data = std::move(data_); |
| data_ = std::move(new_data); |
| reserved_ = new_size; |
| return old_data; |
| } |
| |
| void* PaintOpBuffer::AllocatePaintOpSlowPath(uint16_t aligned_size) { |
| DCHECK(is_mutable()); |
| |
| size_t required_size = used_ + aligned_size; |
| DCHECK_GT(required_size, reserved_) << "Should not have hit the slow path"; |
| // Start reserved_ at kInitialBufferSize and then double. |
| // ShrinkToFit() can make this smaller afterwards. |
| size_t new_size = reserved_ ? reserved_ : kInitialBufferSize; |
| while (required_size > new_size) { |
| new_size *= 2; |
| } |
| ReallocBuffer(new_size); |
| DCHECK_LE(required_size, reserved_); |
| |
| return AllocatePaintOp(aligned_size); |
| } |
| |
| void PaintOpBuffer::ShrinkToFit() { |
| ReallocIfNeededToFit(); |
| } |
| |
| PaintOpBuffer::BufferDataPtr PaintOpBuffer::ReallocIfNeededToFit() { |
| if (used_ == reserved_) { |
| return nullptr; |
| } |
| if (!used_) { |
| reserved_ = 0; |
| return std::move(data_); |
| } |
| return ReallocBuffer(used_); |
| } |
| |
| bool PaintOpBuffer::EqualsForTesting(const PaintOpBuffer& other) const { |
| // Check status fields first, which is faster than checking equality of |
| // paint operations. This doesn't need to be complete, and should not check |
| // data buffer capacity related fields because they don't affect equality. |
| if (op_count_ != other.op_count_ || used_ != other.used_ || |
| num_slow_paths_up_to_min_for_MSAA_ != |
| other.num_slow_paths_up_to_min_for_MSAA_ || |
| subrecord_op_count_ != other.subrecord_op_count_ || |
| has_draw_ops_ != other.has_draw_ops_ || |
| has_draw_text_ops_ != other.has_draw_text_ops_ || |
| has_effects_preventing_lcd_text_for_save_layer_alpha_ != |
| other.has_effects_preventing_lcd_text_for_save_layer_alpha_ || |
| has_non_aa_paint_ != other.has_non_aa_paint_ || |
| has_discardable_images_ != other.has_discardable_images_) { |
| return false; |
| } |
| |
| return std::ranges::equal(*this, other, |
| [](const PaintOp& a, const PaintOp& b) { |
| return a.EqualsForTesting(b); // IN-TEST |
| }); |
| } |
| |
| bool PaintOpBuffer::NeedsAdditionalInvalidationForLCDText( |
| const PaintOpBuffer& old_buffer) const { |
| // We need this in addition to blink's raster invalidation because change of |
| // has_effects_preventing_lcd_text_for_save_layer_alpha() can affect |
| // all SaveLayerAlphaOps of the PaintOpBuffer, not just the area that the |
| // changed effects affected. |
| if (!has_draw_text_ops() || !has_save_layer_alpha_ops()) |
| return false; |
| if (!old_buffer.has_draw_text_ops() || !old_buffer.has_save_layer_alpha_ops()) |
| return false; |
| return has_effects_preventing_lcd_text_for_save_layer_alpha() != |
| old_buffer.has_effects_preventing_lcd_text_for_save_layer_alpha(); |
| } |
| |
| void PaintOpBuffer::UpdateSaveLayerBounds(size_t offset, const SkRect& bounds) { |
| CHECK_LT(offset, used_); |
| CHECK_LE(offset + sizeof(PaintOp), used_); |
| |
| auto* op = UNSAFE_TODO(reinterpret_cast<PaintOp*>(data_.get() + offset)); |
| switch (op->GetType()) { |
| case SaveLayerOp::kType: |
| CHECK_LE(offset + sizeof(SaveLayerOp), used_); |
| static_cast<SaveLayerOp*>(op)->bounds = bounds; |
| break; |
| case SaveLayerAlphaOp::kType: |
| CHECK_LE(offset + sizeof(SaveLayerAlphaOp), used_); |
| static_cast<SaveLayerAlphaOp*>(op)->bounds = bounds; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| PaintOpBuffer::Iterator PaintOpBuffer::begin() const { |
| return Iterator(*this); |
| } |
| |
| PaintOpBuffer::Iterator PaintOpBuffer::end() const { |
| return Iterator(*this).end(); |
| } |
| |
| const PaintOp& PaintOpBuffer::GetOpAtForTesting(size_t index) const { |
| for (const auto& op : *this) { |
| if (!index) { |
| return op; |
| } |
| --index; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace cc |