// 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 "cc/paint/paint_op_buffer.h" #include "base/containers/stack_container.h" #include "cc/paint/display_item_list.h" #include "cc/paint/paint_record.h" #include "third_party/skia/include/core/SkAnnotation.h" namespace cc { #define TYPES(M) \ M(AnnotateOp) \ M(ClipPathOp) \ M(ClipRectOp) \ M(ClipRRectOp) \ M(ConcatOp) \ M(DrawArcOp) \ M(DrawCircleOp) \ M(DrawColorOp) \ M(DrawDisplayItemListOp) \ M(DrawDRRectOp) \ M(DrawImageOp) \ M(DrawImageRectOp) \ M(DrawIRectOp) \ M(DrawLineOp) \ M(DrawOvalOp) \ M(DrawPathOp) \ M(DrawPosTextOp) \ M(DrawRecordOp) \ M(DrawRectOp) \ M(DrawRRectOp) \ M(DrawTextOp) \ M(DrawTextBlobOp) \ M(NoopOp) \ M(RestoreOp) \ M(RotateOp) \ M(SaveOp) \ M(SaveLayerOp) \ M(SaveLayerAlphaOp) \ M(ScaleOp) \ M(SetMatrixOp) \ M(TranslateOp) using RasterFunction = void (*)(const PaintOp* op, SkCanvas* canvas, const SkMatrix& original_ctm); using RasterWithFlagsFunction = void (*)(const PaintOpWithFlags* op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm); NOINLINE static void RasterWithAlphaInternal(RasterFunction raster_fn, const PaintOp* op, SkCanvas* canvas, uint8_t alpha) { // TODO(enne): is it ok to just drop the bounds here? canvas->saveLayerAlpha(nullptr, alpha); SkMatrix unused_matrix; raster_fn(op, canvas, unused_matrix); canvas->restore(); } // Helper template to share common code for RasterWithAlpha when paint ops // have or don't have PaintFlags. template struct Rasterizer { static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) { static_assert( !T::kHasPaintFlags, "This function should not be used for a PaintOp that has PaintFlags"); DCHECK(T::kIsDrawOp); RasterWithAlphaInternal(&T::Raster, op, canvas, alpha); } }; NOINLINE static void RasterWithAlphaInternalForFlags( RasterWithFlagsFunction raster_fn, const PaintOpWithFlags* op, SkCanvas* canvas, uint8_t alpha) { SkMatrix unused_matrix; if (!op->flags.SupportsFoldingAlpha()) { canvas->saveLayerAlpha(nullptr, alpha); raster_fn(op, &op->flags, canvas, unused_matrix); canvas->restore(); } else if (alpha == 255) { raster_fn(op, &op->flags, canvas, unused_matrix); } else { PaintFlags flags = op->flags; flags.setAlpha(SkMulDiv255Round(flags.getAlpha(), alpha)); raster_fn(op, &flags, canvas, unused_matrix); } } template struct Rasterizer { static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) { static_assert(T::kHasPaintFlags, "This function expects the PaintOp to have PaintFlags"); DCHECK(T::kIsDrawOp); RasterWithAlphaInternalForFlags(&T::RasterWithFlags, op, canvas, alpha); } }; template <> struct Rasterizer { static void RasterWithAlpha(const DrawRecordOp* op, SkCanvas* canvas, uint8_t alpha) { // This "looking into records" optimization is done here instead of // in the PaintOpBuffer::Raster function as DisplayItemList calls // into RasterWithAlpha directly. if (op->record->size() == 1u) { PaintOp* single_op = op->record->GetFirstOp(); // RasterWithAlpha only supported for draw ops. if (single_op->IsDrawOp()) { single_op->RasterWithAlpha(canvas, alpha); return; } } canvas->saveLayerAlpha(nullptr, alpha); SkMatrix unused_matrix; DrawRecordOp::Raster(op, canvas, unused_matrix); canvas->restore(); } }; // TODO(enne): partially specialize RasterWithAlpha for draw color? static constexpr size_t kNumOpTypes = static_cast(PaintOpType::LastPaintOpType) + 1; // Verify that every op is in the TYPES macro. #define M(T) +1 static_assert(kNumOpTypes == TYPES(M), "Missing op in list"); #undef M using RasterFunction = void (*)(const PaintOp* op, SkCanvas* canvas, const SkMatrix& original_ctm); #define M(T) &T::Raster, static const RasterFunction g_raster_functions[kNumOpTypes] = {TYPES(M)}; #undef M using RasterAlphaFunction = void (*)(const PaintOp* op, SkCanvas* canvas, uint8_t alpha); #define M(T) \ T::kIsDrawOp ? \ [](const PaintOp* op, SkCanvas* canvas, uint8_t alpha) { \ Rasterizer::RasterWithAlpha( \ static_cast(op), canvas, alpha); \ } : static_cast(nullptr), static const RasterAlphaFunction g_raster_alpha_functions[kNumOpTypes] = { TYPES(M)}; #undef M // Most state ops (matrix, clip, save, restore) have a trivial destructor. // TODO(enne): evaluate if we need the nullptr optimization or if // we even need to differentiate trivial destructors here. using VoidFunction = void (*)(PaintOp* op); #define M(T) \ !std::is_trivially_destructible::value \ ? [](PaintOp* op) { static_cast(op)->~T(); } \ : static_cast(nullptr), static const VoidFunction g_destructor_functions[kNumOpTypes] = {TYPES(M)}; #undef M #define M(T) T::kIsDrawOp, static bool g_is_draw_op[kNumOpTypes] = {TYPES(M)}; #undef M #define M(T) \ static_assert(sizeof(T) <= sizeof(LargestPaintOp), \ #T " must be no bigger than LargestPaintOp"); TYPES(M); #undef M #define M(T) \ static_assert(ALIGNOF(T) <= PaintOpBuffer::PaintOpAlign, \ #T " must have alignment no bigger than PaintOpAlign"); TYPES(M); #undef M #undef TYPES SkRect PaintOp::kUnsetRect = {SK_ScalarInfinity, 0, 0, 0}; void AnnotateOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); switch (op->annotation_type) { case PaintCanvas::AnnotationType::URL: SkAnnotateRectWithURL(canvas, op->rect, op->data.get()); break; case PaintCanvas::AnnotationType::LINK_TO_DESTINATION: SkAnnotateLinkToDestination(canvas, op->rect, op->data.get()); break; case PaintCanvas::AnnotationType::NAMED_DESTINATION: { SkPoint point = SkPoint::Make(op->rect.x(), op->rect.y()); SkAnnotateNamedDestination(canvas, point, op->data.get()); break; } } } void ClipPathOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->clipPath(op->path, op->op, op->antialias); } void ClipRectOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->clipRect(op->rect, op->op, op->antialias); } void ClipRRectOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->clipRRect(op->rrect, op->op, op->antialias); } void ConcatOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->concat(op->matrix); } void DrawArcOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawArc(op->oval, op->start_angle, op->sweep_angle, op->use_center, ToSkPaint(*flags)); } void DrawCircleOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawCircle(op->cx, op->cy, op->radius, ToSkPaint(*flags)); } void DrawColorOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawColor(op->color, op->mode); } void DrawDisplayItemListOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); op->list->Raster(canvas); } void DrawDRRectOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawDRRect(op->outer, op->inner, ToSkPaint(*flags)); } void DrawImageOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawImage(op->image.sk_image().get(), op->left, op->top, ToSkPaint(flags)); } void DrawImageRectOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); // TODO(enne): Probably PaintCanvas should just use the skia enum directly. SkCanvas::SrcRectConstraint skconstraint = static_cast(op->constraint); canvas->drawImageRect(op->image.sk_image().get(), op->src, op->dst, ToSkPaint(flags), skconstraint); } void DrawIRectOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawIRect(op->rect, ToSkPaint(*flags)); } void DrawLineOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawLine(op->x0, op->y0, op->x1, op->y1, ToSkPaint(*flags)); } void DrawOvalOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawOval(op->oval, ToSkPaint(*flags)); } void DrawPathOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawPath(op->path, ToSkPaint(*flags)); } void DrawPosTextOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawPosText(op->GetData(), op->bytes, op->GetArray(), ToSkPaint(*flags)); } void DrawRecordOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { // Don't use drawPicture here, as it adds an implicit clip. auto* op = static_cast(base_op); op->record->playback(canvas); } void DrawRectOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawRect(op->rect, ToSkPaint(*flags)); } void DrawRRectOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawRRect(op->rrect, ToSkPaint(*flags)); } void DrawTextOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawText(op->GetData(), op->bytes, op->x, op->y, ToSkPaint(*flags)); } void DrawTextBlobOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->drawTextBlob(op->blob.get(), op->x, op->y, ToSkPaint(*flags)); } void RestoreOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { canvas->restore(); } void RotateOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->rotate(op->degrees); } void SaveOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { canvas->save(); } void SaveLayerOp::RasterWithFlags(const PaintOpWithFlags* base_op, const PaintFlags* flags, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); // See PaintOp::kUnsetRect bool unset = op->bounds.left() == SK_ScalarInfinity; canvas->saveLayer(unset ? nullptr : &op->bounds, ToSkPaint(flags)); } void SaveLayerAlphaOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); // See PaintOp::kUnsetRect bool unset = op->bounds.left() == SK_ScalarInfinity; canvas->saveLayerAlpha(unset ? nullptr : &op->bounds, op->alpha); } void ScaleOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->scale(op->sx, op->sy); } void SetMatrixOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->setMatrix(SkMatrix::Concat(original_ctm, op->matrix)); } void TranslateOp::Raster(const PaintOp* base_op, SkCanvas* canvas, const SkMatrix& original_ctm) { auto* op = static_cast(base_op); canvas->translate(op->dx, op->dy); } bool PaintOp::IsDrawOp() const { return g_is_draw_op[type]; } void PaintOp::Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const { g_raster_functions[type](this, canvas, original_ctm); } void PaintOp::RasterWithAlpha(SkCanvas* canvas, uint8_t alpha) const { g_raster_alpha_functions[type](this, canvas, alpha); } int ClipPathOp::CountSlowPaths() const { return antialias && !path.isConvex() ? 1 : 0; } int DrawLineOp::CountSlowPaths() const { if (const SkPathEffect* effect = flags.getPathEffect()) { SkPathEffect::DashInfo info; SkPathEffect::DashType dashType = effect->asADash(&info); if (flags.getStrokeCap() != PaintFlags::kRound_Cap && dashType == SkPathEffect::kDash_DashType && info.fCount == 2) { // The PaintFlags will count this as 1, so uncount that here as // this kind of line is special cased and not slow. return -1; } } return 0; } int DrawPathOp::CountSlowPaths() const { // This logic is copied from SkPathCounter instead of attempting to expose // that from Skia. if (!flags.isAntiAlias() || path.isConvex()) return 0; PaintFlags::Style paintStyle = flags.getStyle(); const SkRect& pathBounds = path.getBounds(); if (paintStyle == PaintFlags::kStroke_Style && flags.getStrokeWidth() == 0) { // AA hairline concave path is not slow. return 0; } else if (paintStyle == PaintFlags::kFill_Style && pathBounds.width() < 64.f && pathBounds.height() < 64.f && !path.isVolatile()) { // AADF eligible concave path is not slow. return 0; } else { return 1; } } int DrawRecordOp::CountSlowPaths() const { return record->numSlowPaths(); } AnnotateOp::AnnotateOp(PaintCanvas::AnnotationType annotation_type, const SkRect& rect, sk_sp data) : annotation_type(annotation_type), rect(rect), data(std::move(data)) {} AnnotateOp::~AnnotateOp() = default; DrawDisplayItemListOp::DrawDisplayItemListOp( scoped_refptr list) : list(list) {} size_t DrawDisplayItemListOp::AdditionalBytesUsed() const { return list->ApproximateMemoryUsage(); } bool DrawDisplayItemListOp::HasDiscardableImages() const { return list->has_discardable_images(); } DrawDisplayItemListOp::DrawDisplayItemListOp(const DrawDisplayItemListOp& op) = default; DrawDisplayItemListOp& DrawDisplayItemListOp::operator=( const DrawDisplayItemListOp& op) = default; DrawDisplayItemListOp::~DrawDisplayItemListOp() = default; DrawImageOp::DrawImageOp(const PaintImage& image, SkScalar left, SkScalar top, const PaintFlags* flags) : PaintOpWithFlags(flags ? *flags : PaintFlags()), image(image), left(left), top(top) {} bool DrawImageOp::HasDiscardableImages() const { // TODO(khushalsagar): Callers should not be able to change the lazy generated // state for a PaintImage. return image.sk_image()->isLazyGenerated(); } DrawImageOp::~DrawImageOp() = default; DrawImageRectOp::DrawImageRectOp(const PaintImage& image, const SkRect& src, const SkRect& dst, const PaintFlags* flags, PaintCanvas::SrcRectConstraint constraint) : PaintOpWithFlags(flags ? *flags : PaintFlags()), image(image), src(src), dst(dst), constraint(constraint) {} bool DrawImageRectOp::HasDiscardableImages() const { return image.sk_image()->isLazyGenerated(); } DrawImageRectOp::~DrawImageRectOp() = default; DrawPosTextOp::DrawPosTextOp(size_t bytes, size_t count, const PaintFlags& flags) : PaintOpWithArray(flags, bytes, count) {} DrawPosTextOp::~DrawPosTextOp() = default; DrawRecordOp::DrawRecordOp(sk_sp record) : record(std::move(record)) {} DrawRecordOp::~DrawRecordOp() = default; size_t DrawRecordOp::AdditionalBytesUsed() const { return record->bytes_used(); } bool DrawRecordOp::HasDiscardableImages() const { return record->HasDiscardableImages(); } DrawTextBlobOp::DrawTextBlobOp(sk_sp blob, SkScalar x, SkScalar y, const PaintFlags& flags) : PaintOpWithFlags(flags), blob(std::move(blob)), x(x), y(y) {} DrawTextBlobOp::~DrawTextBlobOp() = default; PaintOpBuffer::PaintOpBuffer() = default; PaintOpBuffer::~PaintOpBuffer() { Reset(); } void PaintOpBuffer::Reset() { for (auto* op : Iterator(this)) { auto func = g_destructor_functions[op->type]; if (func) func(op); } // Leave data_ allocated, reserved_ unchanged. used_ = 0; op_count_ = 0; num_slow_paths_ = 0; } static const PaintOp* NextOp(const std::vector& range_starts, const std::vector& range_indices, base::StackVector* stack_ptr, PaintOpBuffer::Iterator* iter, size_t* range_index) { auto& stack = *stack_ptr; if (stack->size()) { const PaintOp* op = stack->front(); // Shift paintops forward stack->erase(stack->begin()); return op; } if (!*iter) return nullptr; const size_t active_range = range_indices[*range_index]; DCHECK_GE(iter->op_idx(), range_starts[active_range]); // This grabs the PaintOp from the current iterator position, and advances it // to the next position immediately. We'll see we reached the end of the // buffer on the next call to this method. const PaintOp* op = **iter; ++*iter; if (active_range + 1 == range_starts.size()) { // In the last possible range, so let the iter go right to the end of the // buffer. return op; } const size_t range_end = range_starts[active_range + 1]; DCHECK_LE(iter->op_idx(), range_end); if (iter->op_idx() < range_end) { // Still inside the range, so let the iter be. return op; } if (*range_index + 1 == range_indices.size()) { // We're now past the last range that we want to iterate. *iter = iter->end(); return op; } // Move to the next range. ++(*range_index); size_t next_range_start = range_starts[range_indices[*range_index]]; while (iter->op_idx() < next_range_start) ++(*iter); return op; } void PaintOpBuffer::playback(SkCanvas* canvas, SkPicture::AbortCallback* callback) const { static auto* zero = new std::vector({0}); // Treats the entire PaintOpBuffer as a single range. PlaybackRanges(*zero, *zero, canvas, callback); } void PaintOpBuffer::PlaybackRanges(const std::vector& range_starts, const std::vector& range_indices, SkCanvas* canvas, SkPicture::AbortCallback* callback) const { if (!op_count_) return; if (callback && callback->abort()) return; #if DCHECK_IS_ON() DCHECK(!range_starts.empty()); // Don't call this then. DCHECK(!range_indices.empty()); // Don't call this then. DCHECK_EQ(0u, range_starts[0]); for (size_t i = 1; i < range_starts.size(); ++i) { DCHECK_GT(range_starts[i], range_starts[i - 1]); DCHECK_LT(range_starts[i], op_count_); } DCHECK_LT(range_indices[0], range_starts.size()); for (size_t i = 1; i < range_indices.size(); ++i) { DCHECK_GT(range_indices[i], range_indices[i - 1]); DCHECK_LT(range_indices[i], range_starts.size()); } #endif // 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. SkMatrix original = canvas->getTotalMatrix(); // FIFO queue of paint ops that have been peeked at. base::StackVector stack; // The current offset into range_indices. range_indices[range_index] is the // current offset into range_starts. size_t range_index = 0; Iterator iter(this); while (iter.op_idx() < range_starts[range_indices[range_index]]) ++iter; while (const PaintOp* op = NextOp(range_starts, range_indices, &stack, &iter, &range_index)) { // Optimize out save/restores or save/draw/restore that can be a single // draw. See also: similar code in SkRecordOpts and cc's DisplayItemList. // TODO(enne): consider making this recursive? if (op->GetType() == PaintOpType::SaveLayerAlpha) { const PaintOp* second = NextOp(range_starts, range_indices, &stack, &iter, &range_index); const PaintOp* third = nullptr; if (second) { if (second->GetType() == PaintOpType::Restore) { continue; } if (second->IsDrawOp()) { third = NextOp(range_starts, range_indices, &stack, &iter, &range_index); if (third && third->GetType() == PaintOpType::Restore) { const SaveLayerAlphaOp* save_op = static_cast(op); second->RasterWithAlpha(canvas, save_op->alpha); continue; } } // Store deferred ops for later. stack->push_back(second); if (third) stack->push_back(third); } } // TODO(enne): skip SaveLayer followed by restore with nothing in // between, however SaveLayer with image filters on it (or maybe // other PaintFlags options) are not a noop. Figure out what these // are so we can skip them correctly. op->Raster(canvas, original); if (callback && callback->abort()) return; } } void PaintOpBuffer::ReallocBuffer(size_t new_size) { DCHECK_GE(new_size, used_); std::unique_ptr new_data( static_cast(base::AlignedAlloc(new_size, PaintOpAlign))); memcpy(new_data.get(), data_.get(), used_); data_ = std::move(new_data); reserved_ = new_size; } std::pair PaintOpBuffer::AllocatePaintOp(size_t sizeof_op, size_t bytes) { if (!op_count_) { if (bytes) { // Internal first_op buffer doesn't have room for extra data. // If the op wants extra bytes, then we'll just store a Noop // in the first_op and proceed from there. This seems unlikely // to be a common case. push(); } else { op_count_++; return std::make_pair(first_op_.void_data(), 0); } } // We've filled |first_op_| by now so we need to allocate space in |data_|. DCHECK(op_count_); // Compute a skip such that all ops in the buffer are aligned to the // maximum required alignment of all ops. size_t skip = MathUtil::UncheckedRoundUp(sizeof_op + bytes, PaintOpAlign); DCHECK_LT(skip, static_cast(1) << 24); if (used_ + skip > reserved_) { // Start reserved_ at kInitialBufferSize and then double. // ShrinkToFit can make this smaller afterwards. size_t new_size = reserved_ ? reserved_ : kInitialBufferSize; while (used_ + skip > new_size) new_size *= 2; ReallocBuffer(new_size); } DCHECK_LE(used_ + skip, reserved_); void* op = data_.get() + used_; used_ += skip; op_count_++; return std::make_pair(op, skip); } void PaintOpBuffer::ShrinkToFit() { if (!used_ || used_ == reserved_) return; ReallocBuffer(used_); } } // namespace cc