blob: 0689974b04d07ce7ceaf3080101616ab86733cb6 [file] [log] [blame]
// 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"
#include "third_party/skia/include/core/SkCanvas.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,
const SkRect& bounds,
uint8_t alpha) {
bool unset = bounds.x() == PaintOp::kUnsetRect.x();
canvas->saveLayerAlpha(unset ? nullptr : &bounds, 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 <typename T, bool HasFlags>
struct Rasterizer {
static void RasterWithAlpha(const T* op,
SkCanvas* canvas,
const SkRect& bounds,
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, bounds, alpha);
}
};
NOINLINE static void RasterWithAlphaInternalForFlags(
RasterWithFlagsFunction raster_fn,
const PaintOpWithFlags* op,
SkCanvas* canvas,
const SkRect& bounds,
uint8_t alpha) {
SkMatrix unused_matrix;
if (alpha == 255) {
raster_fn(op, &op->flags, canvas, unused_matrix);
} else if (op->flags.SupportsFoldingAlpha()) {
PaintFlags flags = op->flags;
flags.setAlpha(SkMulDiv255Round(flags.getAlpha(), alpha));
raster_fn(op, &flags, canvas, unused_matrix);
} else {
bool unset = bounds.x() == PaintOp::kUnsetRect.x();
canvas->saveLayerAlpha(unset ? nullptr : &bounds, alpha);
raster_fn(op, &op->flags, canvas, unused_matrix);
canvas->restore();
}
}
template <typename T>
struct Rasterizer<T, true> {
static void RasterWithAlpha(const T* op,
SkCanvas* canvas,
const SkRect& bounds,
uint8_t alpha) {
static_assert(T::kHasPaintFlags,
"This function expects the PaintOp to have PaintFlags");
DCHECK(T::kIsDrawOp);
RasterWithAlphaInternalForFlags(&T::RasterWithFlags, op, canvas, bounds,
alpha);
}
};
// These should never be used, as we should recurse into them to draw their
// contained op with alpha instead.
template <bool HasFlags>
struct Rasterizer<DrawRecordOp, HasFlags> {
static void RasterWithAlpha(const DrawRecordOp* op,
SkCanvas* canvas,
const SkRect& bounds,
uint8_t alpha) {
NOTREACHED();
}
};
template <bool HasFlags>
struct Rasterizer<DrawDisplayItemListOp, HasFlags> {
static void RasterWithAlpha(const DrawDisplayItemListOp* op,
SkCanvas* canvas,
const SkRect& bounds,
uint8_t alpha) {
NOTREACHED();
}
};
// TODO(enne): partially specialize RasterWithAlpha for draw color?
static constexpr size_t kNumOpTypes =
static_cast<size_t>(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,
const SkRect& bounds,
uint8_t alpha);
#define M(T) \
T::kIsDrawOp ? [](const PaintOp* op, SkCanvas* canvas, const SkRect& bounds, \
uint8_t alpha) { \
Rasterizer<T, T::kHasPaintFlags>::RasterWithAlpha( \
static_cast<const T*>(op), canvas, bounds, alpha); \
} : static_cast<RasterAlphaFunction>(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<T>::value \
? [](PaintOp* op) { static_cast<T*>(op)->~T(); } \
: static_cast<VoidFunction>(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<const AnnotateOp*>(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<const ClipPathOp*>(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<const ClipRectOp*>(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<const ClipRRectOp*>(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<const ConcatOp*>(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<const DrawArcOp*>(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<const DrawCircleOp*>(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<const DrawColorOp*>(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<const DrawDisplayItemListOp*>(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<const DrawDRRectOp*>(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<const DrawImageOp*>(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<const DrawImageRectOp*>(base_op);
// TODO(enne): Probably PaintCanvas should just use the skia enum directly.
SkCanvas::SrcRectConstraint skconstraint =
static_cast<SkCanvas::SrcRectConstraint>(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<const DrawIRectOp*>(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<const DrawLineOp*>(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<const DrawOvalOp*>(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<const DrawPathOp*>(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<const DrawPosTextOp*>(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<const DrawRecordOp*>(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<const DrawRectOp*>(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<const DrawRRectOp*>(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<const DrawTextOp*>(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<const DrawTextBlobOp*>(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<const RotateOp*>(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<const SaveLayerOp*>(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<const SaveLayerAlphaOp*>(base_op);
// See PaintOp::kUnsetRect
bool unset = op->bounds.left() == SK_ScalarInfinity;
if (op->preserve_lcd_text_requests) {
SkPaint paint;
paint.setAlpha(op->alpha);
canvas->saveLayerPreserveLCDTextRequests(unset ? nullptr : &op->bounds,
&paint);
} else {
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<const ScaleOp*>(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<const SetMatrixOp*>(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<const TranslateOp*>(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,
const SkRect& bounds,
uint8_t alpha) const {
g_raster_alpha_functions[type](this, canvas, bounds, alpha);
}
int ClipPathOp::CountSlowPaths() const {
return antialias && !path.isConvex() ? 1 : 0;
}
int DrawDisplayItemListOp::CountSlowPaths() const {
return list->NumSlowPaths();
}
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<SkData> data)
: annotation_type(annotation_type), rect(rect), data(std::move(data)) {}
AnnotateOp::~AnnotateOp() = default;
DrawDisplayItemListOp::DrawDisplayItemListOp(
scoped_refptr<DisplayItemList> list)
: list(list) {}
size_t DrawDisplayItemListOp::AdditionalBytesUsed() const {
return list->BytesUsed();
}
bool DrawDisplayItemListOp::HasDiscardableImages() const {
return list->HasDiscardableImages();
}
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<const PaintRecord> 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<SkTextBlob> 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<size_t>& range_starts,
const std::vector<size_t>& range_indices,
base::StackVector<const PaintOp*, 3>* 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;
}
// When |op| is a nested PaintOpBuffer, this returns the PaintOp inside
// that buffer if the buffer contains a single drawing op, otherwise it
// returns null. This searches recursively if the PaintOpBuffer contains only
// another PaintOpBuffer.
static const PaintOp* GetNestedSingleDrawingOp(const PaintOp* op) {
if (!op->IsDrawOp())
return nullptr;
while (op->GetType() == PaintOpType::DrawRecord ||
op->GetType() == PaintOpType::DrawDisplayItemList) {
if (op->GetType() == PaintOpType::DrawDisplayItemList) {
// TODO(danakj): If we could inspect the PaintOpBuffer here, then
// we could see if it is a single draw op.
return nullptr;
}
auto* draw_record_op = static_cast<const DrawRecordOp*>(op);
if (draw_record_op->record->size() > 1) {
// If there's more than one op, then we need to keep the
// SaveLayer.
return nullptr;
}
// Recurse into the single-op DrawRecordOp and make sure it's a
// drawing op.
op = draw_record_op->record->GetFirstOp();
if (!op->IsDrawOp())
return nullptr;
}
return op;
}
void PaintOpBuffer::playback(SkCanvas* canvas,
SkPicture::AbortCallback* callback) const {
static auto* zero = new std::vector<size_t>({0});
// Treats the entire PaintOpBuffer as a single range.
PlaybackRanges(*zero, *zero, canvas, callback);
}
void PaintOpBuffer::PlaybackRanges(const std::vector<size_t>& range_starts,
const std::vector<size_t>& range_indices,
SkCanvas* canvas,
SkPicture::AbortCallback* callback) const {
if (!op_count_)
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
// Prevent PaintOpBuffers from having side effects back into the canvas.
SkAutoCanvasRestore save_restore(canvas, true);
// 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<const PaintOp*, 3> 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)) {
// Check if we should abort. This should happen at the start of loop since
// there are a couple of raster branches below, and we need to ensure that
// we do this check after every one of them.
if (callback && callback->abort())
return;
// Optimize out save/restores or save/draw/restore that can be a single
// draw. See also: similar code in SkRecordOpts.
// TODO(enne): consider making this recursive?
// TODO(enne): should we avoid this if the SaveLayerAlphaOp has bounds?
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;
}
// Find a nested drawing PaintOp to replace |second| if possible, while
// holding onto the pointer to |second| in case we can't find a nested
// drawing op to replace it with.
const PaintOp* draw_op = GetNestedSingleDrawingOp(second);
if (draw_op) {
third =
NextOp(range_starts, range_indices, &stack, &iter, &range_index);
if (third && third->GetType() == PaintOpType::Restore) {
auto* save_op = static_cast<const SaveLayerAlphaOp*>(op);
draw_op->RasterWithAlpha(canvas, save_op->bounds, 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);
}
}
void PaintOpBuffer::ReallocBuffer(size_t new_size) {
DCHECK_GE(new_size, used_);
std::unique_ptr<char, base::AlignedFreeDeleter> new_data(
static_cast<char*>(base::AlignedAlloc(new_size, PaintOpAlign)));
memcpy(new_data.get(), data_.get(), used_);
data_ = std::move(new_data);
reserved_ = new_size;
}
std::pair<void*, size_t> PaintOpBuffer::AllocatePaintOp(size_t sizeof_op,
size_t bytes) {
// 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<size_t>(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