[go: nahoru, domu]

Back PaintRecord with PaintOpBuffer instead of SkPicture

Change the backing of PaintRecord to be a data structure implemented in
cc/paint instead of using SkPicture directly.  This new class cribs heavily
from SkLiteDL.

PaintRecord used to be a typedef to an SkPicture but now is a typedef to
a PaintOpBuffer.  (This leaves some flexibility to change this in the
future to an interface without having to modify all of Chromium again.)

PaintOpBuffer stores a contiguous array of ops, with the ops stored
in place.  As an optimization, the first op is stored locally in the
PaintOpBuffer itself to avoid extra allocations for small pictures.

This patch moves slow path counting from a gpu analysis canvas into
PaintOpBuffer directly.  As ops are recorded, slow paths are counted, and
a PaintRecord now knows how many ops it has.  This is about a 1.5% savings
for record time (gpu analysis was 2% and 0.5% overhead to record later).

This patch also implements the SkRecordNoopSaveLayerDrawRestores
optimization from Skia at raster time.  This takes save layer (just
opacity) / draw / restore commands and turns them into draws with
opacity.  It moves that optimization from Blink at record time inside of
CompositingRecorder and moves it to both DisplayItemList::RasterItem
and PaintOpBuffer::playback (since a save could be either a DisplayItem
or a PaintOp).  It's not as robust as Skia's solution and so misses
a few cases that Skia catches, but the rasterize and record on 10k
page agreed that performance was good enough.

CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2

Review-Url: https://codereview.chromium.org/2768143002
Cr-Commit-Position: refs/heads/master@{#468548}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 6b7472f4..fa0bd60 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -652,6 +652,8 @@
     "test/test_occlusion_tracker.h",
     "test/test_shared_bitmap_manager.cc",
     "test/test_shared_bitmap_manager.h",
+    "test/test_skcanvas.cc",
+    "test/test_skcanvas.h",
     "test/test_task_graph_runner.cc",
     "test/test_task_graph_runner.h",
     "test/test_texture.cc",
@@ -769,6 +771,7 @@
     "output/texture_mailbox_deleter_unittest.cc",
     "paint/discardable_image_map_unittest.cc",
     "paint/display_item_list_unittest.cc",
+    "paint/paint_op_buffer_unittest.cc",
     "quads/draw_polygon_unittest.cc",
     "quads/draw_quad_unittest.cc",
     "quads/nine_patch_generator_unittest.cc",
@@ -939,6 +942,7 @@
     "//cc/ipc",
     "//cc/ipc:interfaces",
     "//cc/paint",
+    "//cc/paint",
     "//cc/surfaces",
     "//cc/surfaces:surface_id",
     "//gpu",
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index 3af5dee..11f9fdac 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -32,13 +32,19 @@
     "paint_canvas.cc",
     "paint_canvas.h",
     "paint_export.h",
+    "paint_flags.cc",
     "paint_flags.h",
     "paint_image.cc",
     "paint_image.h",
+    "paint_op_buffer.cc",
+    "paint_op_buffer.h",
+    "paint_record.cc",
     "paint_record.h",
     "paint_recorder.cc",
     "paint_recorder.h",
     "paint_shader.h",
+    "record_paint_canvas.cc",
+    "record_paint_canvas.h",
     "skia_paint_canvas.cc",
     "skia_paint_canvas.h",
     "transform_display_item.cc",
diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc
index 8359948..c1b069548 100644
--- a/cc/paint/display_item_list.cc
+++ b/cc/paint/display_item_list.cc
@@ -100,9 +100,13 @@
       if (canvas->quickReject(item.picture->cullRect()))
         break;
 
-      // SkPicture always does a wrapping save/restore on the canvas, so it is
-      // not necessary here.
+      // TODO(enne): Maybe the PaintRecord itself could know whether this
+      // was needed?  It's not clear whether these save/restore semantics
+      // that SkPicture handles during playback are things that should be
+      // kept around.
+      canvas->save();
       item.picture->playback(canvas, callback);
+      canvas->restore();
       break;
     }
     case DisplayItem::FLOAT_CLIP: {
@@ -176,6 +180,33 @@
   canvas->restore();
 }
 
+// Atttempts to merge a CompositingDisplayItem and DrawingDisplayItem
+// into a single "draw with alpha".  This function returns true if
+// it was successful.  If false, then the caller is responsible for
+// drawing these items.  This is a DisplayItemList version of the
+// SkRecord optimization SkRecordNoopSaveLayerDrawRestores.
+static bool MergeAndDrawIfPossible(const CompositingDisplayItem& save_item,
+                                   const DrawingDisplayItem& draw_item,
+                                   SkCanvas* canvas) {
+  if (save_item.color_filter)
+    return false;
+  if (save_item.xfermode != SkBlendMode::kSrcOver)
+    return false;
+  // TODO(enne): I believe that lcd_text_requires_opaque_layer is not
+  // relevant here and that lcd text is preserved post merge, but I haven't
+  // tested that.
+  const PaintRecord* record = draw_item.picture.get();
+  if (record->approximateOpCount() != 1)
+    return false;
+
+  const PaintOp* op = record->GetFirstOp();
+  if (!op->IsDrawOp())
+    return false;
+
+  op->RasterWithAlpha(canvas, save_item.alpha);
+  return true;
+}
+
 void DisplayItemList::Raster(SkCanvas* canvas,
                              SkPicture::AbortCallback* callback) const {
   gfx::Rect canvas_playback_rect;
@@ -184,14 +215,33 @@
 
   std::vector<size_t> indices;
   rtree_.Search(canvas_playback_rect, &indices);
-  for (size_t index : indices) {
-    RasterItem(items_[index], canvas, callback);
-
+  for (size_t i = 0; i < indices.size(); ++i) {
     // We use a callback during solid color analysis on the compositor thread to
     // break out early. Since we're handling a sequence of pictures via rtree
     // query results ourselves, we have to respect the callback and early out.
     if (callback && callback->abort())
       break;
+
+    const DisplayItem& item = items_[indices[i]];
+    // Optimize empty begin/end compositing and merge begin/draw/end compositing
+    // where possible.
+    // TODO(enne): remove empty clips here too?
+    // TODO(enne): does this happen recursively? Or is this good enough?
+    if (i < indices.size() - 2 && item.type == DisplayItem::COMPOSITING) {
+      const DisplayItem& second = items_[indices[i + 1]];
+      const DisplayItem& third = items_[indices[i + 2]];
+      if (second.type == DisplayItem::DRAWING &&
+          third.type == DisplayItem::END_COMPOSITING) {
+        if (MergeAndDrawIfPossible(
+                static_cast<const CompositingDisplayItem&>(item),
+                static_cast<const DrawingDisplayItem&>(second), canvas)) {
+          i += 2;
+          continue;
+        }
+      }
+    }
+
+    RasterItem(item, canvas, callback);
   }
 }
 
diff --git a/cc/paint/display_item_list_unittest.cc b/cc/paint/display_item_list_unittest.cc
index f1b9e75..b166229c6 100644
--- a/cc/paint/display_item_list_unittest.cc
+++ b/cc/paint/display_item_list_unittest.cc
@@ -17,16 +17,17 @@
 #include "cc/paint/compositing_display_item.h"
 #include "cc/paint/drawing_display_item.h"
 #include "cc/paint/filter_display_item.h"
-
 #include "cc/paint/float_clip_display_item.h"
 #include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/paint_recorder.h"
+#include "cc/paint/skia_paint_canvas.h"
 #include "cc/paint/transform_display_item.h"
 #include "cc/test/geometry_test_utils.h"
 #include "cc/test/pixel_test_utils.h"
 #include "cc/test/skia_common.h"
+#include "cc/test/test_skcanvas.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -80,6 +81,19 @@
   return recorder.finishRecordingAsPicture();
 }
 
+sk_sp<const PaintRecord> CreateRectPictureWithAlpha(const gfx::Rect& bounds,
+                                                    uint8_t alpha) {
+  PaintRecorder recorder;
+  PaintCanvas* canvas =
+      recorder.beginRecording(bounds.width(), bounds.height());
+  PaintFlags flags;
+  flags.setAlpha(alpha);
+  canvas->drawRect(
+      SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(), bounds.height()),
+      flags);
+  return recorder.finishRecordingAsPicture();
+}
+
 void AppendFirstSerializationTestPicture(scoped_refptr<DisplayItemList> list,
                                          const gfx::Size& layer_size) {
   gfx::PointF offset(2.f, 3.f);
@@ -704,4 +718,110 @@
   EXPECT_RECT_EQ(filter_bounds, list->VisualRectForTesting(3));
 }
 
+// Verify that raster time optimizations for compositing item / draw single op /
+// end compositing item can be collapsed together into a single draw op
+// with the opacity from the compositing item folded in.
+TEST(DisplayItemListTest, SaveDrawRestore) {
+  auto list = make_scoped_refptr(new DisplayItemList);
+
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      80, SkBlendMode::kSrcOver, nullptr, nullptr, false);
+  list->CreateAndAppendDrawingItem<DrawingDisplayItem>(
+      kVisualRect, CreateRectPictureWithAlpha(kVisualRect, 40));
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->Finalize();
+
+  SaveCountingCanvas canvas;
+  list->Raster(&canvas, nullptr);
+
+  EXPECT_EQ(0, canvas.save_count_);
+  EXPECT_EQ(0, canvas.restore_count_);
+  EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_);
+
+  float expected_alpha = 80 * 40 / 255.f;
+  EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f);
+}
+
+// Verify that compositing item / end compositing item is a noop.
+// Here we're testing that Skia does an optimization that skips
+// save/restore with nothing in between.  If skia stops doing this
+// then we should reimplement this optimization in display list raster.
+TEST(DisplayItemListTest, SaveRestoreNoops) {
+  auto list = make_scoped_refptr(new DisplayItemList);
+
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      80, SkBlendMode::kSrcOver, nullptr, nullptr, false);
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      255, SkBlendMode::kSrcOver, nullptr, nullptr, false);
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      255, SkBlendMode::kSrc, nullptr, nullptr, false);
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->Finalize();
+
+  SaveCountingCanvas canvas;
+  list->Raster(&canvas, nullptr);
+
+  EXPECT_EQ(0, canvas.save_count_);
+  EXPECT_EQ(0, canvas.restore_count_);
+}
+
+// The same as SaveDrawRestore, but with save flags that prevent the
+// optimization.
+TEST(DisplayItemListTest, SaveDrawRestoreFail_BadSaveFlags) {
+  auto list = make_scoped_refptr(new DisplayItemList);
+
+  // Use a blend mode that's not compatible with the SaveDrawRestore
+  // optimization.
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      80, SkBlendMode::kSrc, nullptr, nullptr, false);
+  list->CreateAndAppendDrawingItem<DrawingDisplayItem>(
+      kVisualRect, CreateRectPictureWithAlpha(kVisualRect, 40));
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->Finalize();
+
+  SaveCountingCanvas canvas;
+  list->Raster(&canvas, nullptr);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+  EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_);
+  EXPECT_LE(40, canvas.paint_.getAlpha());
+}
+
+// The same as SaveDrawRestore, but with too many ops in the PaintRecord.
+TEST(DisplayItemListTest, SaveDrawRestoreFail_TooManyOps) {
+  sk_sp<const PaintRecord> record;
+  {
+    PaintRecorder recorder;
+    PaintCanvas* canvas =
+        recorder.beginRecording(kVisualRect.width(), kVisualRect.height());
+    PaintFlags flags;
+    flags.setAlpha(40);
+    canvas->drawRect(gfx::RectToSkRect(kVisualRect), flags);
+    // Add an extra op here.
+    canvas->drawRect(gfx::RectToSkRect(kVisualRect), flags);
+    record = recorder.finishRecordingAsPicture();
+  }
+  EXPECT_GT(record->approximateOpCount(), 1);
+
+  auto list = make_scoped_refptr(new DisplayItemList);
+
+  list->CreateAndAppendPairedBeginItem<CompositingDisplayItem>(
+      80, SkBlendMode::kSrcOver, nullptr, nullptr, false);
+  list->CreateAndAppendDrawingItem<DrawingDisplayItem>(kVisualRect,
+                                                       std::move(record));
+  list->CreateAndAppendPairedEndItem<EndCompositingDisplayItem>();
+  list->Finalize();
+
+  SaveCountingCanvas canvas;
+  list->Raster(&canvas, nullptr);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+  EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_);
+  EXPECT_LE(40, canvas.paint_.getAlpha());
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_canvas.h b/cc/paint/paint_canvas.h
index 86348f0..e1fa9d0 100644
--- a/cc/paint/paint_canvas.h
+++ b/cc/paint/paint_canvas.h
@@ -11,19 +11,25 @@
 #include "build/build_config.h"
 #include "cc/paint/paint_export.h"
 #include "cc/paint/paint_image.h"
-#include "cc/paint/paint_record.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 
 namespace cc {
 
 class DisplayItemList;
 class PaintFlags;
+class PaintOpBuffer;
+
+using PaintRecord = PaintOpBuffer;
 
 class CC_PAINT_EXPORT PaintCanvas {
  public:
+  PaintCanvas() {}
   virtual ~PaintCanvas() {}
 
   virtual SkMetaData& getMetaData() = 0;
+
+  // TODO(enne): this only appears to mostly be used to determine if this is
+  // recording or not, so could be simplified or removed.
   virtual SkImageInfo imageInfo() const = 0;
 
   // TODO(enne): It would be nice to get rid of flush() entirely, as it
@@ -36,7 +42,7 @@
 
   virtual int save() = 0;
   virtual int saveLayer(const SkRect* bounds, const PaintFlags* flags) = 0;
-  virtual int saveLayerAlpha(const SkRect* bounds, U8CPU alpha) = 0;
+  virtual int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) = 0;
 
   virtual void restore() = 0;
   virtual int getSaveCount() const = 0;
@@ -87,6 +93,8 @@
   virtual bool getDeviceClipBounds(SkIRect* bounds) const = 0;
   virtual void drawColor(SkColor color, SkBlendMode mode) = 0;
   void drawColor(SkColor color) { drawColor(color, SkBlendMode::kSrcOver); }
+
+  // TODO(enne): This is a synonym for drawColor with kSrc.  Remove it.
   virtual void clear(SkColor color) = 0;
 
   virtual void drawLine(SkScalar x0,
@@ -180,6 +188,9 @@
  protected:
   friend class PaintSurface;
   friend class PaintRecorder;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PaintCanvas);
 };
 
 class CC_PAINT_EXPORT PaintCanvasAutoRestore {
diff --git a/cc/paint/paint_flags.cc b/cc/paint/paint_flags.cc
new file mode 100644
index 0000000..e16a8bb
--- /dev/null
+++ b/cc/paint/paint_flags.cc
@@ -0,0 +1,42 @@
+// 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_flags.h"
+
+namespace cc {
+
+bool PaintFlags::IsSimpleOpacity() const {
+  uint32_t color = getColor();
+  if (SK_ColorTRANSPARENT != SkColorSetA(color, SK_AlphaTRANSPARENT))
+    return false;
+  if (!isSrcOver())
+    return false;
+  if (getLooper())
+    return false;
+  if (getPathEffect())
+    return false;
+  if (getShader())
+    return false;
+  if (getMaskFilter())
+    return false;
+  if (getColorFilter())
+    return false;
+  if (getImageFilter())
+    return false;
+  return true;
+}
+
+bool PaintFlags::SupportsFoldingAlpha() const {
+  if (!isSrcOver())
+    return false;
+  if (getColorFilter())
+    return false;
+  if (getImageFilter())
+    return false;
+  if (getLooper())
+    return false;
+  return true;
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_flags.h b/cc/paint/paint_flags.h
index b7e96c6..37b460d6 100644
--- a/cc/paint/paint_flags.h
+++ b/cc/paint/paint_flags.h
@@ -7,7 +7,6 @@
 
 #include "base/compiler_specific.h"
 #include "cc/paint/paint_export.h"
-#include "cc/paint/paint_shader.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkColorFilter.h"
 #include "third_party/skia/include/core/SkDrawLooper.h"
@@ -19,6 +18,8 @@
 
 namespace cc {
 
+using PaintShader = SkShader;
+
 class CC_PAINT_EXPORT PaintFlags {
  public:
   enum Style {
@@ -198,6 +199,14 @@
     return paint_.computeFastBounds(orig, storage);
   }
 
+  bool operator==(const PaintFlags& flags) { return flags.paint_ == paint_; }
+  bool operator!=(const PaintFlags& flags) { return flags.paint_ != paint_; }
+
+  // Returns true if this just represents an opacity blend when
+  // used as saveLayer flags.
+  bool IsSimpleOpacity() const;
+  bool SupportsFoldingAlpha() const;
+
  private:
   friend const SkPaint& ToSkPaint(const PaintFlags& flags);
   friend const SkPaint* ToSkPaint(const PaintFlags* flags);
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
new file mode 100644
index 0000000..b10c2a8
--- /dev/null
+++ b/cc/paint/paint_op_buffer.cc
@@ -0,0 +1,579 @@
+// 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 "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)
+
+// 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 Raster(const T* op,
+                     SkCanvas* canvas,
+                     const SkMatrix& original_ctm) {
+    // Paint ops with kHasPaintFlags need to declare RasterWithPaintFlags
+    // otherwise, the paint op needs its own Raster function.  Without its
+    // own, this becomes an infinite loop as PaintOp::Raster calls itself.
+    static_assert(
+        !std::is_same<decltype(&PaintOp::Raster), decltype(&T::Raster)>::value,
+        "No Raster function");
+
+    op->Raster(canvas);
+  }
+  static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) {
+    DCHECK(T::kIsDrawOp);
+    // TODO(enne): is it ok to just drop the bounds here?
+    canvas->saveLayerAlpha(nullptr, alpha);
+    op->Raster(canvas);
+    canvas->restore();
+  }
+};
+
+template <typename T>
+struct Rasterizer<T, true> {
+  static void Raster(const T* op,
+                     SkCanvas* canvas,
+                     const SkMatrix& original_ctm) {
+    op->RasterWithFlags(canvas, op->flags);
+  }
+  static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) {
+    DCHECK(T::kIsDrawOp);
+    SkMatrix unused_matrix;
+    if (alpha == 255) {
+      Raster(op, canvas, unused_matrix);
+    } else if (op->flags.SupportsFoldingAlpha()) {
+      PaintFlags flags = op->flags;
+      flags.setAlpha(SkMulDiv255Round(flags.getAlpha(), alpha));
+      op->RasterWithFlags(canvas, flags);
+    } else {
+      canvas->saveLayerAlpha(nullptr, alpha);
+      op->RasterWithFlags(canvas, op->flags);
+      canvas->restore();
+    }
+  }
+};
+
+template <>
+struct Rasterizer<SetMatrixOp, false> {
+  static void Raster(const SetMatrixOp* op,
+                     SkCanvas* canvas,
+                     const SkMatrix& original_ctm) {
+    op->Raster(canvas, original_ctm);
+  }
+  static void RasterWithAlpha(const SetMatrixOp* op,
+                              SkCanvas* canvas,
+                              uint8_t alpha) {
+    NOTREACHED();
+  }
+};
+
+template <>
+struct Rasterizer<DrawRecordOp, false> {
+  static void Raster(const DrawRecordOp* op,
+                     SkCanvas* canvas,
+                     const SkMatrix& original_ctm) {
+    op->Raster(canvas);
+  }
+  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->approximateOpCount() == 1) {
+      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);
+    op->Raster(canvas);
+    canvas->restore();
+  }
+};
+
+// 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)                                                              \
+  [](const PaintOp* op, SkCanvas* canvas, const SkMatrix& original_ctm) { \
+    Rasterizer<T, T::kHasPaintFlags>::Raster(static_cast<const T*>(op),   \
+                                             canvas, original_ctm);       \
+  },
+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<T, T::kHasPaintFlags>::RasterWithAlpha( \
+        static_cast<const T*>(op), canvas, 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(SkCanvas* canvas) const {
+  switch (annotation_type) {
+    case PaintCanvas::AnnotationType::URL:
+      SkAnnotateRectWithURL(canvas, rect, data.get());
+      break;
+    case PaintCanvas::AnnotationType::LINK_TO_DESTINATION:
+      SkAnnotateLinkToDestination(canvas, rect, data.get());
+      break;
+    case PaintCanvas::AnnotationType::NAMED_DESTINATION: {
+      SkPoint point = SkPoint::Make(rect.x(), rect.y());
+      SkAnnotateNamedDestination(canvas, point, data.get());
+      break;
+    }
+  }
+}
+
+void ClipPathOp::Raster(SkCanvas* canvas) const {
+  canvas->clipPath(path, op, antialias);
+}
+
+void ClipRectOp::Raster(SkCanvas* canvas) const {
+  canvas->clipRect(rect, op, antialias);
+}
+
+void ClipRRectOp::Raster(SkCanvas* canvas) const {
+  canvas->clipRRect(rrect, op, antialias);
+}
+
+void ConcatOp::Raster(SkCanvas* canvas) const {
+  canvas->concat(matrix);
+}
+
+void DrawArcOp::RasterWithFlags(SkCanvas* canvas,
+                                const PaintFlags& flags) const {
+  canvas->drawArc(oval, start_angle, sweep_angle, use_center, ToSkPaint(flags));
+}
+
+void DrawCircleOp::RasterWithFlags(SkCanvas* canvas,
+                                   const PaintFlags& flags) const {
+  canvas->drawCircle(cx, cy, radius, ToSkPaint(flags));
+}
+
+void DrawColorOp::Raster(SkCanvas* canvas) const {
+  canvas->drawColor(color, mode);
+}
+
+void DrawDisplayItemListOp::Raster(SkCanvas* canvas) const {
+  list->Raster(canvas, nullptr);
+}
+
+void DrawDRRectOp::RasterWithFlags(SkCanvas* canvas,
+                                   const PaintFlags& flags) const {
+  canvas->drawDRRect(outer, inner, ToSkPaint(flags));
+}
+
+void DrawImageOp::RasterWithFlags(SkCanvas* canvas,
+                                  const PaintFlags& flags) const {
+  canvas->drawImage(image.sk_image().get(), left, top, ToSkPaint(&flags));
+}
+
+void DrawImageRectOp::RasterWithFlags(SkCanvas* canvas,
+                                      const PaintFlags& flags) const {
+  // TODO(enne): Probably PaintCanvas should just use the skia enum directly.
+  SkCanvas::SrcRectConstraint skconstraint =
+      static_cast<SkCanvas::SrcRectConstraint>(constraint);
+  canvas->drawImageRect(image.sk_image().get(), src, dst, ToSkPaint(&flags),
+                        skconstraint);
+}
+
+void DrawIRectOp::RasterWithFlags(SkCanvas* canvas,
+                                  const PaintFlags& flags) const {
+  canvas->drawIRect(rect, ToSkPaint(flags));
+}
+
+void DrawLineOp::RasterWithFlags(SkCanvas* canvas,
+                                 const PaintFlags& flags) const {
+  canvas->drawLine(x0, y0, x1, y1, ToSkPaint(flags));
+}
+
+void DrawOvalOp::RasterWithFlags(SkCanvas* canvas,
+                                 const PaintFlags& flags) const {
+  canvas->drawOval(oval, ToSkPaint(flags));
+}
+
+void DrawPathOp::RasterWithFlags(SkCanvas* canvas,
+                                 const PaintFlags& flags) const {
+  canvas->drawPath(path, ToSkPaint(flags));
+}
+
+void DrawPosTextOp::RasterWithFlags(SkCanvas* canvas,
+                                    const PaintFlags& flags) const {
+  canvas->drawPosText(GetData(), bytes, GetArray(), ToSkPaint(flags));
+}
+
+void DrawRecordOp::Raster(SkCanvas* canvas) const {
+  record->playback(canvas);
+}
+
+void DrawRectOp::RasterWithFlags(SkCanvas* canvas,
+                                 const PaintFlags& flags) const {
+  canvas->drawRect(rect, ToSkPaint(flags));
+}
+
+void DrawRRectOp::RasterWithFlags(SkCanvas* canvas,
+                                  const PaintFlags& flags) const {
+  canvas->drawRRect(rrect, ToSkPaint(flags));
+}
+
+void DrawTextOp::RasterWithFlags(SkCanvas* canvas,
+                                 const PaintFlags& flags) const {
+  canvas->drawText(GetData(), bytes, x, y, ToSkPaint(flags));
+}
+
+void DrawTextBlobOp::RasterWithFlags(SkCanvas* canvas,
+                                     const PaintFlags& flags) const {
+  canvas->drawTextBlob(blob.get(), x, y, ToSkPaint(flags));
+}
+
+void RestoreOp::Raster(SkCanvas* canvas) const {
+  canvas->restore();
+}
+
+void RotateOp::Raster(SkCanvas* canvas) const {
+  canvas->rotate(degrees);
+}
+
+void SaveOp::Raster(SkCanvas* canvas) const {
+  canvas->save();
+}
+
+void SaveLayerOp::RasterWithFlags(SkCanvas* canvas,
+                                  const PaintFlags& flags) const {
+  // See PaintOp::kUnsetRect
+  bool unset = bounds.left() == SK_ScalarInfinity;
+
+  canvas->saveLayer(unset ? nullptr : &bounds, ToSkPaint(&flags));
+}
+
+void SaveLayerAlphaOp::Raster(SkCanvas* canvas) const {
+  // See PaintOp::kUnsetRect
+  bool unset = bounds.left() == SK_ScalarInfinity;
+  canvas->saveLayerAlpha(unset ? nullptr : &bounds, alpha);
+}
+
+void ScaleOp::Raster(SkCanvas* canvas) const {
+  canvas->scale(sx, sy);
+}
+
+void SetMatrixOp::Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const {
+  canvas->setMatrix(SkMatrix::Concat(original_ctm, matrix));
+}
+
+void TranslateOp::Raster(SkCanvas* canvas) const {
+  canvas->translate(dx, 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;
+  }
+}
+
+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->ApproximateMemoryUsage();
+}
+
+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)
+    : image(image),
+      left(left),
+      top(top),
+      flags(flags ? *flags : PaintFlags()) {}
+
+DrawImageOp::~DrawImageOp() = default;
+
+DrawImageRectOp::DrawImageRectOp(const PaintImage& image,
+                                 const SkRect& src,
+                                 const SkRect& dst,
+                                 const PaintFlags* flags,
+                                 PaintCanvas::SrcRectConstraint constraint)
+    : image(image),
+      flags(flags ? *flags : PaintFlags()),
+      src(src),
+      dst(dst),
+      constraint(constraint) {}
+
+DrawImageRectOp::~DrawImageRectOp() = default;
+
+DrawPosTextOp::DrawPosTextOp(size_t bytes,
+                             size_t count,
+                             const PaintFlags& flags)
+    : PaintOpWithArray(bytes, count), flags(flags) {}
+
+DrawPosTextOp::~DrawPosTextOp() = default;
+
+DrawRecordOp::DrawRecordOp(sk_sp<const PaintRecord> record)
+    : record(std::move(record)) {}
+
+DrawRecordOp::~DrawRecordOp() = default;
+
+size_t DrawRecordOp::AdditionalBytesUsed() const {
+  return record->approximateBytesUsed();
+}
+
+DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                               SkScalar x,
+                               SkScalar y,
+                               const PaintFlags& flags)
+    : blob(std::move(blob)), x(x), y(y), flags(flags) {}
+
+DrawTextBlobOp::~DrawTextBlobOp() = default;
+
+PaintOpBuffer::PaintOpBuffer() : cull_rect_(SkRect::MakeEmpty()) {}
+
+PaintOpBuffer::PaintOpBuffer(const SkRect& cull_rect) : cull_rect_(cull_rect) {}
+
+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;
+}
+
+void PaintOpBuffer::playback(SkCanvas* canvas) const {
+  // 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();
+
+  for (Iterator iter(this); iter; ++iter) {
+    // 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?
+    const PaintOp* op = *iter;
+    if (op->GetType() == PaintOpType::SaveLayerAlpha) {
+      const PaintOp* second = iter.peek1();
+      if (second) {
+        if (second->GetType() == PaintOpType::Restore) {
+          ++iter;
+          continue;
+        }
+        if (second->IsDrawOp()) {
+          const PaintOp* third = iter.peek2();
+          if (third && third->GetType() == PaintOpType::Restore) {
+            const SaveLayerAlphaOp* save_op =
+                static_cast<const SaveLayerAlphaOp*>(op);
+            second->RasterWithAlpha(canvas, save_op->alpha);
+            ++iter;
+            ++iter;
+            continue;
+          }
+        }
+      }
+    }
+    // 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::playback(SkCanvas* canvas,
+                             SkPicture::AbortCallback* callback) const {
+  // The abort callback is only used for analysis, in general, so
+  // this playback code can be more straightforward and not do the
+  // optimizations in the other function.
+  if (!callback) {
+    playback(canvas);
+    return;
+  }
+
+  SkMatrix original = canvas->getTotalMatrix();
+
+  // TODO(enne): ideally callers would just iterate themselves and we
+  // can remove the entire notion of an abort callback.
+  for (auto* op : Iterator(this)) {
+    op->Raster(canvas, original);
+    if (callback && callback->abort())
+      return;
+  }
+}
+
+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;
+}
+
+void PaintOpBuffer::ShrinkToFit() {
+  if (!used_ || used_ == reserved_)
+    return;
+  ReallocBuffer(used_);
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
new file mode 100644
index 0000000..36eb1d3
--- /dev/null
+++ b/cc/paint/paint_op_buffer.h
@@ -0,0 +1,836 @@
+// 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.
+
+#ifndef CC_PAINT_PAINT_OP_BUFFER_H_
+#define CC_PAINT_PAINT_OP_BUFFER_H_
+
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "base/memory/aligned_memory.h"
+#include "cc/base/math_util.h"
+#include "cc/paint/paint_canvas.h"
+#include "cc/paint/paint_export.h"
+#include "cc/paint/paint_flags.h"
+#include "third_party/skia/include/core/SkPicture.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkTextBlob.h"
+
+// PaintOpBuffer is a reimplementation of SkLiteDL.
+// See: third_party/skia/src/core/SkLiteDL.h.
+
+namespace cc {
+
+class DisplayItemList;
+
+class CC_PAINT_EXPORT ThreadsafeMatrix : public SkMatrix {
+ public:
+  explicit ThreadsafeMatrix(const SkMatrix& matrix) : SkMatrix(matrix) {
+    (void)getType();
+  }
+};
+
+class CC_PAINT_EXPORT ThreadsafePath : public SkPath {
+ public:
+  explicit ThreadsafePath(const SkPath& path) : SkPath(path) {
+    updateBoundsCache();
+  }
+};
+
+enum class PaintOpType : uint8_t {
+  Annotate,
+  ClipPath,
+  ClipRect,
+  ClipRRect,
+  Concat,
+  DrawArc,
+  DrawCircle,
+  DrawColor,
+  DrawDisplayItemList,
+  DrawDRRect,
+  DrawImage,
+  DrawImageRect,
+  DrawIRect,
+  DrawLine,
+  DrawOval,
+  DrawPath,
+  DrawPosText,
+  DrawRecord,
+  DrawRect,
+  DrawRRect,
+  DrawText,
+  DrawTextBlob,
+  Noop,
+  Restore,
+  Rotate,
+  Save,
+  SaveLayer,
+  SaveLayerAlpha,
+  Scale,
+  SetMatrix,
+  Translate,
+  LastPaintOpType = Translate,
+};
+
+struct CC_PAINT_EXPORT PaintOp {
+  uint32_t type : 8;
+  uint32_t skip : 24;
+
+  PaintOpType GetType() const { return static_cast<PaintOpType>(type); }
+
+  void Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const;
+  bool IsDrawOp() const;
+
+  // Only valid for draw ops.
+  void RasterWithAlpha(SkCanvas* canvas, uint8_t alpha) const;
+
+  int CountSlowPaths() const { return 0; }
+
+  // Returns the number of bytes used by this op in referenced sub records
+  // and display lists.  This doesn't count other objects like paths or blobs.
+  size_t AdditionalBytesUsed() const { return 0; }
+
+  static constexpr bool kIsDrawOp = false;
+  // If an op has |kHasPaintFlags| set to true, it must:
+  // (1) Provide a PaintFlags member called |flags|
+  // (2) Provide a RasterWithFlags function instead of a Raster function.
+  static constexpr bool kHasPaintFlags = false;
+  static SkRect kUnsetRect;
+};
+
+struct CC_PAINT_EXPORT PaintOpWithData : PaintOp {
+  // Having data is just a helper for ops that have a varying amount of data and
+  // want a way to store that inline.  This is for ops that pass in a
+  // void* and a length.  The void* data is assumed to not have any alignment
+  // requirements.
+  explicit PaintOpWithData(size_t bytes) : bytes(bytes) {}
+
+  // Get data out by calling paint_op_data.  This can't be part of the class
+  // because it needs to know the size of the derived type.
+  size_t bytes;
+
+ protected:
+  // For some derived object T, return the internally stored data.
+  // This needs the fully derived type to know how much to offset
+  // from the start of the top to the data.
+  template <typename T>
+  const void* GetDataForThis(const T* op) const {
+    static_assert(std::is_convertible<T, PaintOpWithData>::value,
+                  "T is not a PaintOpWithData");
+    // Arbitrary data for a PaintOp is stored after the PaintOp itself
+    // in the PaintOpBuffer.  Therefore, to access this data, it's
+    // pointer math to increment past the size of T.  Accessing the
+    // next op in the buffer is ((char*)op) + op->skip, with the data
+    // fitting between.
+    return op + 1;
+  }
+
+  template <typename T>
+  void* GetDataForThis(T* op) {
+    return const_cast<void*>(
+        const_cast<const PaintOpWithData*>(this)->GetDataForThis(
+            const_cast<const T*>(op)));
+  }
+};
+
+struct CC_PAINT_EXPORT PaintOpWithArrayBase : PaintOp {};
+
+template <typename M>
+struct CC_PAINT_EXPORT PaintOpWithArray : PaintOpWithArrayBase {
+  // Paint op that has a M[count] and a char[bytes].
+  // Array data is stored first so that it can be aligned with T's alignment
+  // with the arbitrary unaligned char data after it.
+  // Memory layout here is: | op | M[count] | char[bytes] | padding | next op |
+  // Next op is located at (char*)(op) + op->skip.
+  PaintOpWithArray(size_t bytes, size_t count) : bytes(bytes), count(count) {}
+
+  size_t bytes;
+  size_t count;
+
+ protected:
+  template <typename T>
+  const void* GetDataForThis(const T* op) const {
+    static_assert(std::is_convertible<T, PaintOpWithArrayBase>::value,
+                  "T is not a PaintOpWithData");
+    const char* start_array =
+        reinterpret_cast<const char*>(GetArrayForThis(op));
+    return start_array + sizeof(M) * count;
+  }
+
+  template <typename T>
+  void* GetDataForThis(T* op) {
+    return const_cast<void*>(
+        const_cast<const PaintOpWithArray*>(this)->GetDataForThis(
+            const_cast<T*>(op)));
+  }
+
+  template <typename T>
+  const M* GetArrayForThis(const T* op) const {
+    static_assert(std::is_convertible<T, PaintOpWithArrayBase>::value,
+                  "T is not a PaintOpWithData");
+    // As an optimization to not have to store an additional offset,
+    // assert that T has the same or more alignment requirements than M.  Thus,
+    // if T is aligned, and M's alignment needs are a multiple of T's size, then
+    // M will also be aligned when placed immediately after T.
+    static_assert(
+        sizeof(T) % ALIGNOF(M) == 0,
+        "T must be padded such that an array of M is aligned after it");
+    static_assert(
+        ALIGNOF(T) >= ALIGNOF(M),
+        "T must have not have less alignment requirements than the array data");
+    return reinterpret_cast<const M*>(op + 1);
+  }
+
+  template <typename T>
+  M* GetArrayForThis(T* op) {
+    return const_cast<M*>(
+        const_cast<const PaintOpWithArray*>(this)->GetArrayForThis(
+            const_cast<T*>(op)));
+  }
+};
+
+struct CC_PAINT_EXPORT AnnotateOp final : PaintOp {
+  enum class AnnotationType {
+    URL,
+    LinkToDestination,
+    NamedDestination,
+  };
+
+  static constexpr PaintOpType kType = PaintOpType::Annotate;
+  AnnotateOp(PaintCanvas::AnnotationType annotation_type,
+             const SkRect& rect,
+             sk_sp<SkData> data);
+  ~AnnotateOp();
+  void Raster(SkCanvas* canvas) const;
+
+  PaintCanvas::AnnotationType annotation_type;
+  SkRect rect;
+  sk_sp<SkData> data;
+};
+
+struct CC_PAINT_EXPORT ClipPathOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::ClipPath;
+  ClipPathOp(SkPath path, SkClipOp op, bool antialias)
+      : path(path), op(op), antialias(antialias) {}
+  void Raster(SkCanvas* canvas) const;
+  int CountSlowPaths() const;
+
+  ThreadsafePath path;
+  SkClipOp op;
+  bool antialias;
+};
+
+struct CC_PAINT_EXPORT ClipRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::ClipRect;
+  ClipRectOp(const SkRect& rect, SkClipOp op, bool antialias)
+      : rect(rect), op(op), antialias(antialias) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkRect rect;
+  SkClipOp op;
+  bool antialias;
+};
+
+struct CC_PAINT_EXPORT ClipRRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::ClipRRect;
+  ClipRRectOp(const SkRRect& rrect, SkClipOp op, bool antialias)
+      : rrect(rrect), op(op), antialias(antialias) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkRRect rrect;
+  SkClipOp op;
+  bool antialias;
+};
+
+struct CC_PAINT_EXPORT ConcatOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Concat;
+  explicit ConcatOp(const SkMatrix& matrix) : matrix(matrix) {}
+  void Raster(SkCanvas* canvas) const;
+
+  ThreadsafeMatrix matrix;
+};
+
+struct CC_PAINT_EXPORT DrawArcOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawArc;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawArcOp(const SkRect& oval,
+            SkScalar start_angle,
+            SkScalar sweep_angle,
+            bool use_center,
+            const PaintFlags& flags)
+      : oval(oval),
+        start_angle(start_angle),
+        sweep_angle(sweep_angle),
+        use_center(use_center),
+        flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRect oval;
+  SkScalar start_angle;
+  SkScalar sweep_angle;
+  bool use_center;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawCircleOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawCircle;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawCircleOp(SkScalar cx,
+               SkScalar cy,
+               SkScalar radius,
+               const PaintFlags& flags)
+      : cx(cx), cy(cy), radius(radius), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkScalar cx;
+  SkScalar cy;
+  SkScalar radius;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawColorOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawColor;
+  static constexpr bool kIsDrawOp = true;
+  DrawColorOp(SkColor color, SkBlendMode mode) : color(color), mode(mode) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkColor color;
+  SkBlendMode mode;
+};
+
+struct CC_PAINT_EXPORT DrawDisplayItemListOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawDisplayItemList;
+  static constexpr bool kIsDrawOp = true;
+  explicit DrawDisplayItemListOp(scoped_refptr<DisplayItemList> list);
+  // Windows wants to generate these when types are exported, so
+  // provide them here explicitly so that DisplayItemList doesn't have
+  // to be defined in this header.
+  DrawDisplayItemListOp(const DrawDisplayItemListOp& op);
+  DrawDisplayItemListOp& operator=(const DrawDisplayItemListOp& op);
+  ~DrawDisplayItemListOp();
+  void Raster(SkCanvas* canvas) const;
+  size_t AdditionalBytesUsed() const;
+  // TODO(enne): DisplayItemList should know number of slow paths.
+
+  scoped_refptr<DisplayItemList> list;
+};
+
+struct CC_PAINT_EXPORT DrawDRRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawDRRect;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawDRRectOp(const SkRRect& outer,
+               const SkRRect& inner,
+               const PaintFlags& flags)
+      : outer(outer), inner(inner), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRRect outer;
+  SkRRect inner;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawImageOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawImage;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawImageOp(const PaintImage& image,
+              SkScalar left,
+              SkScalar top,
+              const PaintFlags* flags);
+  ~DrawImageOp();
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  PaintImage image;
+  SkScalar left;
+  SkScalar top;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawImageRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawImageRect;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawImageRectOp(const PaintImage& image,
+                  const SkRect& src,
+                  const SkRect& dst,
+                  const PaintFlags* flags,
+                  PaintCanvas::SrcRectConstraint constraint);
+  ~DrawImageRectOp();
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  PaintImage image;
+  PaintFlags flags;
+  SkRect src;
+  SkRect dst;
+  PaintCanvas::SrcRectConstraint constraint;
+};
+
+struct CC_PAINT_EXPORT DrawIRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawIRect;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawIRectOp(const SkIRect& rect, const PaintFlags& flags)
+      : rect(rect), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkIRect rect;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawLineOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawLine;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawLineOp(SkScalar x0,
+             SkScalar y0,
+             SkScalar x1,
+             SkScalar y1,
+             const PaintFlags& flags)
+      : x0(x0), y0(y0), x1(x1), y1(y1), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+  int CountSlowPaths() const;
+
+  SkScalar x0;
+  SkScalar y0;
+  SkScalar x1;
+  SkScalar y1;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawOvalOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawOval;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawOvalOp(const SkRect& oval, const PaintFlags& flags)
+      : oval(oval), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRect oval;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawPathOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawPath;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawPathOp(const SkPath& path, const PaintFlags& flags)
+      : path(path), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+  int CountSlowPaths() const;
+
+  ThreadsafePath path;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawPosTextOp final : PaintOpWithArray<SkPoint> {
+  static constexpr PaintOpType kType = PaintOpType::DrawPosText;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawPosTextOp(size_t bytes, size_t count, const PaintFlags& flags);
+  ~DrawPosTextOp();
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  const void* GetData() const { return GetDataForThis(this); }
+  void* GetData() { return GetDataForThis(this); }
+  const SkPoint* GetArray() const { return GetArrayForThis(this); }
+  SkPoint* GetArray() { return GetArrayForThis(this); }
+
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawRecordOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawRecord;
+  static constexpr bool kIsDrawOp = true;
+  explicit DrawRecordOp(sk_sp<const PaintRecord> record);
+  ~DrawRecordOp();
+  void Raster(SkCanvas* canvas) const;
+  size_t AdditionalBytesUsed() const;
+
+  sk_sp<const PaintRecord> record;
+};
+
+struct CC_PAINT_EXPORT DrawRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawRect;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawRectOp(const SkRect& rect, const PaintFlags& flags)
+      : rect(rect), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRect rect;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawRRectOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawRRect;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawRRectOp(const SkRRect& rrect, const PaintFlags& flags)
+      : rrect(rrect), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRRect rrect;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawTextOp final : PaintOpWithData {
+  static constexpr PaintOpType kType = PaintOpType::DrawText;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawTextOp(size_t bytes, SkScalar x, SkScalar y, const PaintFlags& flags)
+      : PaintOpWithData(bytes), x(x), y(y), flags(flags) {}
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  void* GetData() { return GetDataForThis(this); }
+  const void* GetData() const { return GetDataForThis(this); }
+
+  SkScalar x;
+  SkScalar y;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT DrawTextBlobOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::DrawTextBlob;
+  static constexpr bool kIsDrawOp = true;
+  static constexpr bool kHasPaintFlags = true;
+  DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                 SkScalar x,
+                 SkScalar y,
+                 const PaintFlags& flags);
+  ~DrawTextBlobOp();
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  sk_sp<SkTextBlob> blob;
+  SkScalar x;
+  SkScalar y;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT NoopOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Noop;
+  void Raster(SkCanvas* canvas) const {}
+};
+
+struct CC_PAINT_EXPORT RestoreOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Restore;
+  void Raster(SkCanvas* canvas) const;
+};
+
+struct CC_PAINT_EXPORT RotateOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Rotate;
+  explicit RotateOp(SkScalar degrees) : degrees(degrees) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkScalar degrees;
+};
+
+struct CC_PAINT_EXPORT SaveOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Save;
+  void Raster(SkCanvas* canvas) const;
+};
+
+struct CC_PAINT_EXPORT SaveLayerOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::SaveLayer;
+  static constexpr bool kHasPaintFlags = true;
+  SaveLayerOp(const SkRect* bounds, const PaintFlags* flags)
+      : bounds(bounds ? *bounds : kUnsetRect) {
+    if (flags)
+      this->flags = *flags;
+  }
+  void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const;
+
+  SkRect bounds;
+  PaintFlags flags;
+};
+
+struct CC_PAINT_EXPORT SaveLayerAlphaOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::SaveLayerAlpha;
+  SaveLayerAlphaOp(const SkRect* bounds, uint8_t alpha)
+      : bounds(bounds ? *bounds : kUnsetRect), alpha(alpha) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkRect bounds;
+  uint8_t alpha;
+};
+
+struct CC_PAINT_EXPORT ScaleOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Scale;
+  ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkScalar sx;
+  SkScalar sy;
+};
+
+struct CC_PAINT_EXPORT SetMatrixOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::SetMatrix;
+  explicit SetMatrixOp(const SkMatrix& matrix) : matrix(matrix) {}
+  // This is the only op that needs the original ctm of the SkCanvas
+  // used for raster (since SetMatrix is relative to the recording origin and
+  // shouldn't clobber the SkCanvas raster origin).
+  //
+  // TODO(enne): Find some cleaner way to do this, possibly by making
+  // all SetMatrix calls Concat??
+  void Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const;
+
+  ThreadsafeMatrix matrix;
+};
+
+struct CC_PAINT_EXPORT TranslateOp final : PaintOp {
+  static constexpr PaintOpType kType = PaintOpType::Translate;
+  TranslateOp(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {}
+  void Raster(SkCanvas* canvas) const;
+
+  SkScalar dx;
+  SkScalar dy;
+};
+
+using LargestPaintOp = DrawDRRectOp;
+
+class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
+ public:
+  enum { kInitialBufferSize = 4096 };
+  // It's not necessarily the case that the op with the maximum alignment
+  // requirements is also the biggest op, but for now that's true.
+  static constexpr size_t PaintOpAlign = ALIGNOF(DrawDRRectOp);
+
+  PaintOpBuffer();
+  explicit PaintOpBuffer(const SkRect& cull_rect);
+  ~PaintOpBuffer() override;
+
+  void Reset();
+
+  void playback(SkCanvas* canvas) const;
+  void playback(SkCanvas* canvas, SkPicture::AbortCallback* callback) const;
+
+  // TODO(enne): These are no longer approximate.  Rename these.
+  int approximateOpCount() const { return op_count_; }
+  size_t approximateBytesUsed() const {
+    return sizeof(*this) + reserved_ + subrecord_bytes_used_;
+  }
+  int numSlowPaths() const { return num_slow_paths_; }
+
+  // Resize the PaintOpBuffer to exactly fit the current amount of used space.
+  void ShrinkToFit();
+
+  const SkRect& cullRect() const { return cull_rect_; }
+
+  PaintOp* GetFirstOp() const {
+    return const_cast<PaintOp*>(first_op_.data_as<PaintOp>());
+  }
+
+  template <typename T, typename... Args>
+  void push(Args&&... args) {
+    static_assert(std::is_convertible<T, PaintOp>::value, "T not a PaintOp.");
+    static_assert(!std::is_convertible<T, PaintOpWithData>::value,
+                  "Type needs to use push_with_data");
+    push_internal<T>(0, std::forward<Args>(args)...);
+  }
+
+  template <typename T, typename... Args>
+  void push_with_data(const void* data, size_t bytes, Args&&... args) {
+    static_assert(std::is_convertible<T, PaintOpWithData>::value,
+                  "T is not a PaintOpWithData");
+    static_assert(!std::is_convertible<T, PaintOpWithArrayBase>::value,
+                  "Type needs to use push_with_array");
+    DCHECK_GE(bytes, 0u);
+    T* op = push_internal<T>(bytes, bytes, std::forward<Args>(args)...);
+    memcpy(op->GetData(), data, bytes);
+
+#if DCHECK_IS_ON()
+    // Double check the data fits between op and next op and doesn't clobber.
+    char* op_start = reinterpret_cast<char*>(op);
+    char* op_end = op_start + sizeof(T);
+    char* next_op = op_start + op->skip;
+    char* data_start = reinterpret_cast<char*>(op->GetData());
+    char* data_end = data_start + bytes;
+    DCHECK_GE(data_start, op_end);
+    DCHECK_LT(data_start, next_op);
+    DCHECK_LE(data_end, next_op);
+#endif
+  }
+
+  template <typename T, typename M, typename... Args>
+  void push_with_array(const void* data,
+                       size_t bytes,
+                       const M* array,
+                       size_t count,
+                       Args&&... args) {
+    static_assert(std::is_convertible<T, PaintOpWithArray<M>>::value,
+                  "T is not a PaintOpWithArray");
+    DCHECK_GE(bytes, 0u);
+    DCHECK_GE(count, 0u);
+    size_t array_size = sizeof(M) * count;
+    size_t total_size = bytes + array_size;
+    T* op =
+        push_internal<T>(total_size, bytes, count, std::forward<Args>(args)...);
+    memcpy(op->GetData(), data, bytes);
+    memcpy(op->GetArray(), array, array_size);
+
+#if DCHECK_IS_ON()
+    // Double check data and array don't clobber op, next op, or each other
+    char* op_start = reinterpret_cast<char*>(op);
+    char* op_end = op_start + sizeof(T);
+    char* array_start = reinterpret_cast<char*>(op->GetArray());
+    char* array_end = array_start + array_size;
+    char* data_start = reinterpret_cast<char*>(op->GetData());
+    char* data_end = data_start + bytes;
+    char* next_op = op_start + op->skip;
+    DCHECK_GE(array_start, op_end);
+    DCHECK_LE(array_start, data_start);
+    DCHECK_GE(data_start, array_end);
+    DCHECK_LE(data_end, next_op);
+#endif
+  }
+
+  class Iterator {
+   public:
+    explicit Iterator(const PaintOpBuffer* buffer)
+        : buffer_(buffer), ptr_(buffer_->data_.get()) {}
+
+    PaintOp* operator->() const {
+      return op_idx_ ? reinterpret_cast<PaintOp*>(ptr_) : buffer_->GetFirstOp();
+    }
+    PaintOp* operator*() const { return operator->(); }
+    Iterator begin() { return Iterator(buffer_, buffer_->data_.get(), 0); }
+    Iterator end() {
+      return Iterator(buffer_, buffer_->data_.get() + buffer_->used_,
+                      buffer_->approximateOpCount());
+    }
+    bool operator!=(const Iterator& other) {
+      // Not valid to compare iterators on different buffers.
+      DCHECK_EQ(other.buffer_, buffer_);
+      return other.op_idx_ != op_idx_;
+    }
+    Iterator& operator++() {
+      if (!op_idx_++)
+        return *this;
+      PaintOp* op = **this;
+      uint32_t type = op->type;
+      CHECK_LE(type, static_cast<uint32_t>(PaintOpType::LastPaintOpType));
+      ptr_ += op->skip;
+      return *this;
+    }
+    operator bool() const { return op_idx_ < buffer_->approximateOpCount(); }
+
+    int op_idx() const { return op_idx_; }
+
+    // Return the next op without advancing the iterator, or nullptr if none.
+    PaintOp* peek1() const {
+      if (op_idx_ + 1 >= buffer_->approximateOpCount())
+        return nullptr;
+      if (!op_idx_)
+        return reinterpret_cast<PaintOp*>(ptr_);
+      return reinterpret_cast<PaintOp*>(ptr_ + (*this)->skip);
+    }
+
+    // Return the op two ops ahead without advancing the iterator, or nullptr if
+    // none.
+    PaintOp* peek2() const {
+      if (op_idx_ + 2 >= buffer_->approximateOpCount())
+        return nullptr;
+      char* next = ptr_ + reinterpret_cast<PaintOp*>(ptr_)->skip;
+      PaintOp* next_op = reinterpret_cast<PaintOp*>(next);
+      if (!op_idx_)
+        return next_op;
+      return reinterpret_cast<PaintOp*>(next + next_op->skip);
+    }
+
+   private:
+    Iterator(const PaintOpBuffer* buffer, char* ptr, int op_idx)
+        : buffer_(buffer), ptr_(ptr), op_idx_(op_idx) {}
+
+    const PaintOpBuffer* buffer_ = nullptr;
+    char* ptr_ = nullptr;
+    int op_idx_ = 0;
+  };
+
+ private:
+  template <typename T, bool HasFlags>
+  struct CountSlowPathsFromFlags {
+    static int Count(const T* op) { return 0; }
+  };
+
+  template <typename T>
+  struct CountSlowPathsFromFlags<T, true> {
+    static int Count(const T* op) { return op->flags.getPathEffect() ? 1 : 0; }
+  };
+
+  void ReallocBuffer(size_t new_size);
+
+  template <typename T, typename... Args>
+  T* push_internal(size_t bytes, Args&&... args) {
+    // 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(T) + bytes, PaintOpAlign);
+    DCHECK_LT(skip, static_cast<size_t>(1) << 24);
+    if (used_ + skip > reserved_ || !op_count_) {
+      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<NoopOp>();
+        } else {
+          auto* op = reinterpret_cast<T*>(first_op_.data_as<T>());
+          new (op) T{std::forward<Args>(args)...};
+          op->type = static_cast<uint32_t>(T::kType);
+          op->skip = 0;
+          AnalyzeAddedOp(op);
+          op_count_++;
+          return op;
+        }
+      }
+
+      // 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_);
+
+    T* op = reinterpret_cast<T*>(data_.get() + used_);
+    used_ += skip;
+    new (op) T(std::forward<Args>(args)...);
+    op->type = static_cast<uint32_t>(T::kType);
+    op->skip = skip;
+    AnalyzeAddedOp(op);
+    op_count_++;
+    return op;
+  }
+
+  template <typename T>
+  void AnalyzeAddedOp(const T* op) {
+    num_slow_paths_ += CountSlowPathsFromFlags<T, T::kHasPaintFlags>::Count(op);
+    num_slow_paths_ += op->CountSlowPaths();
+
+    subrecord_bytes_used_ += op->AdditionalBytesUsed();
+  }
+
+  // As a performance optimization because n=1 is an extremely common case just
+  // store the first op in the PaintOpBuffer itself to avoid an extra alloc.
+  base::AlignedMemory<sizeof(LargestPaintOp), PaintOpAlign> first_op_;
+  std::unique_ptr<char, base::AlignedFreeDeleter> data_;
+  size_t used_ = 0;
+  size_t reserved_ = 0;
+  int op_count_ = 0;
+
+  // Record paths for veto-to-msaa for gpu raster.
+  int num_slow_paths_ = 0;
+  // Record additional bytes used by referenced sub-records and display lists.
+  size_t subrecord_bytes_used_ = 0;
+  SkRect cull_rect_;
+
+  DISALLOW_COPY_AND_ASSIGN(PaintOpBuffer);
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_PAINT_OP_BUFFER_H_
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
new file mode 100644
index 0000000..9f77775
--- /dev/null
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -0,0 +1,420 @@
+// 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 "cc/test/test_skcanvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+template <typename T>
+void CheckRefCnt(const T& obj, int32_t count) {
+// Skia doesn't define getRefCnt in all builds.
+#ifdef SK_DEBUG
+  EXPECT_EQ(obj->getRefCnt(), count);
+#endif
+}
+
+}  // namespace
+
+namespace cc {
+
+TEST(PaintOpBufferTest, Empty) {
+  PaintOpBuffer buffer;
+  EXPECT_EQ(buffer.approximateOpCount(), 0);
+  EXPECT_EQ(buffer.approximateBytesUsed(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false);
+
+  buffer.Reset();
+  EXPECT_EQ(buffer.approximateOpCount(), 0);
+  EXPECT_EQ(buffer.approximateBytesUsed(), sizeof(PaintOpBuffer));
+  EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false);
+}
+
+TEST(PaintOpBufferTest, SimpleAppend) {
+  SkRect rect = SkRect::MakeXYWH(2, 3, 4, 5);
+  PaintFlags flags;
+  flags.setColor(SK_ColorMAGENTA);
+  flags.setAlpha(100);
+  SkColor draw_color = SK_ColorRED;
+  SkBlendMode blend = SkBlendMode::kSrc;
+
+  PaintOpBuffer buffer;
+  buffer.push<SaveLayerOp>(&rect, &flags);
+  buffer.push<SaveOp>();
+  buffer.push<DrawColorOp>(draw_color, blend);
+  buffer.push<RestoreOp>();
+
+  EXPECT_EQ(buffer.approximateOpCount(), 4);
+
+  PaintOpBuffer::Iterator iter(&buffer);
+  ASSERT_EQ(iter->GetType(), PaintOpType::SaveLayer);
+  SaveLayerOp* save_op = static_cast<SaveLayerOp*>(*iter);
+  EXPECT_EQ(save_op->bounds, rect);
+  EXPECT_TRUE(save_op->flags == flags);
+  ++iter;
+
+  ASSERT_EQ(iter->GetType(), PaintOpType::Save);
+  ++iter;
+
+  ASSERT_EQ(iter->GetType(), PaintOpType::DrawColor);
+  DrawColorOp* op = static_cast<DrawColorOp*>(*iter);
+  EXPECT_EQ(op->color, draw_color);
+  EXPECT_EQ(op->mode, blend);
+  ++iter;
+
+  ASSERT_EQ(iter->GetType(), PaintOpType::Restore);
+  ++iter;
+
+  EXPECT_FALSE(iter);
+}
+
+// PaintOpBuffer has a special case for first ops stored locally, so
+// make sure that appending different kind of ops as a first op works
+// properly, as well as resetting and reusing the first local op.
+TEST(PaintOpBufferTest, FirstOpWithAndWithoutData) {
+  PaintOpBuffer buffer;
+  char text[] = "asdf";
+
+  // Use a color filter and its ref count to verify that the destructor
+  // is called on ops after reset.
+  PaintFlags flags;
+  sk_sp<SkColorFilter> filter =
+      SkColorFilter::MakeModeFilter(SK_ColorMAGENTA, SkBlendMode::kSrcOver);
+  flags.setColorFilter(filter);
+  CheckRefCnt(filter, 2);
+
+  buffer.push_with_data<DrawTextOp>(text, arraysize(text), 0.f, 0.f, flags);
+  CheckRefCnt(filter, 3);
+
+  // Verify that when the first op has data, which may not fit in the
+  // PaintRecord internal buffer, that it adds a noop as the first op
+  // and then appends the "op with data" into the heap buffer.
+  ASSERT_EQ(buffer.approximateOpCount(), 2);
+  EXPECT_EQ(buffer.GetFirstOp()->GetType(), PaintOpType::Noop);
+
+  // Verify iteration behavior and brief smoke test of op state.
+  {
+    PaintOpBuffer::Iterator iter(&buffer);
+    PaintOp* noop = *iter;
+    EXPECT_EQ(buffer.GetFirstOp(), noop);
+    ++iter;
+
+    PaintOp* op = *iter;
+    ASSERT_EQ(op->GetType(), PaintOpType::DrawText);
+    DrawTextOp* draw_text_op = static_cast<DrawTextOp*>(op);
+    EXPECT_EQ(draw_text_op->bytes, arraysize(text));
+
+    const void* data = draw_text_op->GetData();
+    EXPECT_EQ(memcmp(data, text, arraysize(text)), 0);
+
+    ++iter;
+    EXPECT_FALSE(iter);
+  }
+
+  // Reset, verify state, and append an op that will fit in the first slot.
+  buffer.Reset();
+  CheckRefCnt(filter, 2);
+
+  ASSERT_EQ(buffer.approximateOpCount(), 0);
+  EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false);
+
+  SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4);
+  buffer.push<DrawRectOp>(rect, flags);
+  CheckRefCnt(filter, 3);
+
+  ASSERT_EQ(buffer.approximateOpCount(), 1);
+  EXPECT_EQ(buffer.GetFirstOp()->GetType(), PaintOpType::DrawRect);
+
+  PaintOpBuffer::Iterator iter(&buffer);
+  ASSERT_EQ(iter->GetType(), PaintOpType::DrawRect);
+  DrawRectOp* draw_rect_op = static_cast<DrawRectOp*>(*iter);
+  EXPECT_EQ(draw_rect_op->rect, rect);
+
+  ++iter;
+  EXPECT_FALSE(iter);
+
+  buffer.Reset();
+  ASSERT_EQ(buffer.approximateOpCount(), 0);
+  CheckRefCnt(filter, 2);
+}
+
+TEST(PaintOpBufferTest, Peek) {
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+  PaintFlags draw_flags;
+  buffer.push<DrawRectOp>(SkRect::MakeXYWH(1, 2, 3, 4), draw_flags);
+  buffer.push<RestoreOp>();
+  buffer.push<SaveOp>();
+  buffer.push<NoopOp>();
+  buffer.push<RestoreOp>();
+
+  PaintOpBuffer::Iterator init_iter(&buffer);
+  PaintOp* peek[2] = {*init_iter, init_iter.peek1()};
+
+  // Expect that while iterating that next = current.peek1() and that
+  // next.peek1() == current.peek2().
+  for (PaintOpBuffer::Iterator iter(&buffer); iter; ++iter) {
+    EXPECT_EQ(*iter, peek[0]) << iter.op_idx();
+    EXPECT_EQ(iter.peek1(), peek[1]) << iter.op_idx();
+
+    peek[0] = iter.peek1();
+    peek[1] = iter.peek2();
+  }
+}
+
+// Verify that PaintOps with data are stored properly.
+TEST(PaintOpBufferTest, PaintOpData) {
+  PaintOpBuffer buffer;
+
+  buffer.push<SaveOp>();
+  PaintFlags flags;
+  char text1[] = "asdfasdf";
+  buffer.push_with_data<DrawTextOp>(text1, arraysize(text1), 0.f, 0.f, flags);
+
+  char text2[] = "qwerty";
+  buffer.push_with_data<DrawTextOp>(text2, arraysize(text2), 0.f, 0.f, flags);
+
+  ASSERT_EQ(buffer.approximateOpCount(), 3);
+
+  // Verify iteration behavior and brief smoke test of op state.
+  PaintOpBuffer::Iterator iter(&buffer);
+  PaintOp* save_op = *iter;
+  EXPECT_EQ(save_op->GetType(), PaintOpType::Save);
+  ++iter;
+
+  PaintOp* op1 = *iter;
+  ASSERT_EQ(op1->GetType(), PaintOpType::DrawText);
+  DrawTextOp* draw_text_op1 = static_cast<DrawTextOp*>(op1);
+  EXPECT_EQ(draw_text_op1->bytes, arraysize(text1));
+  const void* data1 = draw_text_op1->GetData();
+  EXPECT_EQ(memcmp(data1, text1, arraysize(text1)), 0);
+  ++iter;
+
+  PaintOp* op2 = *iter;
+  ASSERT_EQ(op2->GetType(), PaintOpType::DrawText);
+  DrawTextOp* draw_text_op2 = static_cast<DrawTextOp*>(op2);
+  EXPECT_EQ(draw_text_op2->bytes, arraysize(text2));
+  const void* data2 = draw_text_op2->GetData();
+  EXPECT_EQ(memcmp(data2, text2, arraysize(text2)), 0);
+  ++iter;
+
+  EXPECT_FALSE(iter);
+}
+
+// Verify that PaintOps with arrays are stored properly.
+TEST(PaintOpBufferTest, PaintOpArray) {
+  PaintOpBuffer buffer;
+  buffer.push<SaveOp>();
+
+  // arbitrary data
+  std::string texts[] = {"xyz", "abcdefg", "thingerdoo"};
+  SkPoint point1[] = {SkPoint::Make(1, 2), SkPoint::Make(2, 3),
+                      SkPoint::Make(3, 4)};
+  SkPoint point2[] = {SkPoint::Make(8, -12)};
+  SkPoint point3[] = {SkPoint::Make(0, 0),   SkPoint::Make(5, 6),
+                      SkPoint::Make(-1, -1), SkPoint::Make(9, 9),
+                      SkPoint::Make(50, 50), SkPoint::Make(100, 100)};
+  SkPoint* points[] = {point1, point2, point3};
+  size_t counts[] = {arraysize(point1), arraysize(point2), arraysize(point3)};
+
+  for (size_t i = 0; i < arraysize(texts); ++i) {
+    PaintFlags flags;
+    flags.setAlpha(i);
+    buffer.push_with_array<DrawPosTextOp>(texts[i].c_str(), texts[i].length(),
+                                          points[i], counts[i], flags);
+  }
+
+  PaintOpBuffer::Iterator iter(&buffer);
+  PaintOp* save_op = *iter;
+  EXPECT_EQ(save_op->GetType(), PaintOpType::Save);
+  ++iter;
+
+  for (size_t i = 0; i < arraysize(texts); ++i) {
+    ASSERT_EQ(iter->GetType(), PaintOpType::DrawPosText);
+    DrawPosTextOp* op = static_cast<DrawPosTextOp*>(*iter);
+
+    EXPECT_EQ(op->flags.getAlpha(), i);
+
+    EXPECT_EQ(op->bytes, texts[i].length());
+    const void* data = op->GetData();
+    EXPECT_EQ(memcmp(data, texts[i].c_str(), op->bytes), 0);
+
+    EXPECT_EQ(op->count, counts[i]);
+    const SkPoint* op_points = op->GetArray();
+    for (size_t k = 0; k < op->count; ++k)
+      EXPECT_EQ(op_points[k], points[i][k]);
+
+    ++iter;
+  }
+
+  EXPECT_FALSE(iter);
+}
+
+TEST(PaintOpBufferTest, PeekEmpty) {
+  PaintOpBuffer empty;
+  PaintOpBuffer::Iterator empty_iter(&empty);
+  EXPECT_EQ(nullptr, empty_iter.peek1());
+  EXPECT_EQ(nullptr, empty_iter.peek2());
+}
+
+// Verify that a SaveLayerAlpha / Draw / Restore can be optimized to just
+// a draw with opacity.
+TEST(PaintOpBufferTest, SaveDrawRestore) {
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+
+  PaintFlags draw_flags;
+  draw_flags.setColor(SK_ColorMAGENTA);
+  draw_flags.setAlpha(50);
+  EXPECT_TRUE(draw_flags.SupportsFoldingAlpha());
+  SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4);
+  buffer.push<DrawRectOp>(rect, draw_flags);
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(0, canvas.save_count_);
+  EXPECT_EQ(0, canvas.restore_count_);
+  EXPECT_EQ(rect, canvas.draw_rect_);
+
+  // Expect the alpha from the draw and the save layer to be folded together.
+  // Since alpha is stored in a uint8_t and gets rounded, so use tolerance.
+  float expected_alpha = alpha * 50 / 255.f;
+  EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f);
+}
+
+// The same as SaveDrawRestore, but test that the optimization doesn't apply
+// when the drawing op's flags are not compatible with being folded into the
+// save layer with opacity.
+TEST(PaintOpBufferTest, SaveDrawRestoreFail_BadFlags) {
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+
+  PaintFlags draw_flags;
+  draw_flags.setColor(SK_ColorMAGENTA);
+  draw_flags.setAlpha(50);
+  draw_flags.setBlendMode(SkBlendMode::kSrc);
+  EXPECT_FALSE(draw_flags.SupportsFoldingAlpha());
+  SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4);
+  buffer.push<DrawRectOp>(rect, draw_flags);
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+  EXPECT_EQ(rect, canvas.draw_rect_);
+  EXPECT_EQ(draw_flags.getAlpha(), canvas.paint_.getAlpha());
+}
+
+// The same as SaveDrawRestore, but test that the optimization doesn't apply
+// when there are more than one ops between the save and restore.
+TEST(PaintOpBufferTest, SaveDrawRestoreFail_TooManyOps) {
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+
+  PaintFlags draw_flags;
+  draw_flags.setColor(SK_ColorMAGENTA);
+  draw_flags.setAlpha(50);
+  draw_flags.setBlendMode(SkBlendMode::kSrcOver);
+  EXPECT_TRUE(draw_flags.SupportsFoldingAlpha());
+  SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4);
+  buffer.push<DrawRectOp>(rect, draw_flags);
+  buffer.push<NoopOp>();
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+  EXPECT_EQ(rect, canvas.draw_rect_);
+  EXPECT_EQ(draw_flags.getAlpha(), canvas.paint_.getAlpha());
+}
+
+// Verify that the save draw restore code works with a single op
+// that's not a draw op, and the optimization does not kick in.
+TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpNotADrawOp) {
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+
+  buffer.push<NoopOp>();
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+}
+
+// Test that the save/draw/restore optimization applies if the single op
+// is a DrawRecord that itself has a single draw op.
+TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpRecordWithSingleOp) {
+  sk_sp<PaintRecord> record = sk_make_sp<PaintRecord>();
+
+  PaintFlags draw_flags;
+  draw_flags.setColor(SK_ColorMAGENTA);
+  draw_flags.setAlpha(50);
+  EXPECT_TRUE(draw_flags.SupportsFoldingAlpha());
+  SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4);
+  record->push<DrawRectOp>(rect, draw_flags);
+  EXPECT_EQ(record->approximateOpCount(), 1);
+
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+  buffer.push<DrawRecordOp>(std::move(record));
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(0, canvas.save_count_);
+  EXPECT_EQ(0, canvas.restore_count_);
+  EXPECT_EQ(rect, canvas.draw_rect_);
+
+  float expected_alpha = alpha * 50 / 255.f;
+  EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f);
+}
+
+// The same as the above SingleOpRecord test, but the single op is not
+// a draw op.  So, there's no way to fold in the save layer optimization.
+// Verify that the optimization doesn't apply and that this doesn't crash.
+// See: http://crbug.com/712093.
+TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpRecordWithSingleNonDrawOp) {
+  sk_sp<PaintRecord> record = sk_make_sp<PaintRecord>();
+  record->push<NoopOp>();
+  EXPECT_EQ(record->approximateOpCount(), 1);
+  EXPECT_FALSE(record->GetFirstOp()->IsDrawOp());
+
+  PaintOpBuffer buffer;
+
+  uint8_t alpha = 100;
+  buffer.push<SaveLayerAlphaOp>(nullptr, alpha);
+  buffer.push<DrawRecordOp>(std::move(record));
+  buffer.push<RestoreOp>();
+
+  SaveCountingCanvas canvas;
+  buffer.playback(&canvas);
+
+  EXPECT_EQ(1, canvas.save_count_);
+  EXPECT_EQ(1, canvas.restore_count_);
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_record.cc b/cc/paint/paint_record.cc
new file mode 100644
index 0000000..52cb2524
--- /dev/null
+++ b/cc/paint/paint_record.cc
@@ -0,0 +1,26 @@
+// 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_record.h"
+
+#include "cc/paint/paint_op_buffer.h"
+#include "third_party/skia/include/core/SkPictureRecorder.h"
+
+namespace cc {
+
+sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record) {
+  SkPictureRecorder recorder;
+  SkCanvas* canvas = recorder.beginRecording(record->cullRect());
+  record->playback(canvas);
+  return recorder.finishRecordingAsPicture();
+}
+
+sk_sp<const SkPicture> ToSkPicture(sk_sp<const PaintRecord> record) {
+  SkPictureRecorder recorder;
+  SkCanvas* canvas = recorder.beginRecording(record->cullRect());
+  record->playback(canvas);
+  return recorder.finishRecordingAsPicture();
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_record.h b/cc/paint/paint_record.h
index 8506606..daeee004 100644
--- a/cc/paint/paint_record.h
+++ b/cc/paint/paint_record.h
@@ -5,19 +5,22 @@
 #ifndef CC_PAINT_PAINT_RECORD_H_
 #define CC_PAINT_PAINT_RECORD_H_
 
+#include "cc/paint/paint_export.h"
+#include "cc/paint/paint_op_buffer.h"
 #include "third_party/skia/include/core/SkPicture.h"
 
 namespace cc {
 
-using PaintRecord = SkPicture;
+// TODO(enne): Don't want to rename the world for this.  Using these as the
+// same types for now prevents an extra allocation.  Probably PaintRecord
+// will become an interface in the future.
+using PaintRecord = PaintOpBuffer;
 
-inline sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record) {
-  return record;
-}
+// TODO(enne): Remove these if possible, they are really expensive.
+CC_PAINT_EXPORT sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record);
 
-inline sk_sp<const SkPicture> ToSkPicture(sk_sp<const PaintRecord> record) {
-  return record;
-}
+CC_PAINT_EXPORT sk_sp<const SkPicture> ToSkPicture(
+    sk_sp<const PaintRecord> record);
 
 }  // namespace cc
 
diff --git a/cc/paint/paint_recorder.cc b/cc/paint/paint_recorder.cc
index 672f0712..2ed17c7 100644
--- a/cc/paint/paint_recorder.cc
+++ b/cc/paint/paint_recorder.cc
@@ -4,9 +4,36 @@
 
 #include "cc/paint/paint_recorder.h"
 
+#include "cc/paint/paint_op_buffer.h"
+
 namespace cc {
 
 PaintRecorder::PaintRecorder() = default;
+
 PaintRecorder::~PaintRecorder() = default;
 
+PaintCanvas* PaintRecorder::beginRecording(const SkRect& bounds) {
+  buffer_.reset(new PaintOpBuffer(bounds));
+  canvas_.emplace(buffer_.get());
+  return getRecordingCanvas();
+}
+
+sk_sp<PaintRecord> PaintRecorder::finishRecordingAsPicture() {
+  // SkPictureRecorder users expect that their saves are automatically
+  // closed for them.
+  //
+  // NOTE: Blink paint in general doesn't appear to need this, but the
+  // RecordingImageBufferSurface::fallBackToRasterCanvas finishing off the
+  // current frame depends on this.  Maybe we could remove this assumption and
+  // just have callers do it.
+  canvas_->restoreToCount(1);
+
+  // Some users (e.g. printing) use the existence of the recording canvas
+  // to know if recording is finished, so reset it here.
+  canvas_.reset();
+
+  buffer_->ShrinkToFit();
+  return std::move(buffer_);
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_recorder.h b/cc/paint/paint_recorder.h
index 2bbea83b..7f582b85 100644
--- a/cc/paint/paint_recorder.h
+++ b/cc/paint/paint_recorder.h
@@ -9,47 +9,36 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/optional.h"
-#include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_record.h"
-#include "cc/paint/skia_paint_canvas.h"
-#include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "cc/paint/record_paint_canvas.h"
 
 namespace cc {
 
+class PaintOpBuffer;
+
 class CC_PAINT_EXPORT PaintRecorder {
  public:
   PaintRecorder();
   ~PaintRecorder();
 
-  ALWAYS_INLINE PaintCanvas* beginRecording(const SkRect& bounds) {
-    uint32_t record_flags = 0;
-    canvas_.emplace(recorder_.beginRecording(bounds, nullptr, record_flags));
-    return getRecordingCanvas();
-  }
+  PaintCanvas* beginRecording(const SkRect& bounds);
 
-  ALWAYS_INLINE PaintCanvas* beginRecording(SkScalar width, SkScalar height) {
-    uint32_t record_flags = 0;
-    canvas_.emplace(
-        recorder_.beginRecording(width, height, nullptr, record_flags));
-    return getRecordingCanvas();
+  // TODO(enne): should make everything go through the non-rect version.
+  // See comments in RecordPaintCanvas ctor for why.
+  PaintCanvas* beginRecording(SkScalar width, SkScalar height) {
+    return beginRecording(SkRect::MakeWH(width, height));
   }
 
   // Only valid between between and finish recording.
-  ALWAYS_INLINE PaintCanvas* getRecordingCanvas() {
+  ALWAYS_INLINE RecordPaintCanvas* getRecordingCanvas() {
     return canvas_.has_value() ? &canvas_.value() : nullptr;
   }
 
-  ALWAYS_INLINE sk_sp<PaintRecord> finishRecordingAsPicture() {
-    sk_sp<SkPicture> picture = recorder_.finishRecordingAsPicture();
-    // Some users (e.g. printing) use the existence of the recording canvas
-    // to know if recording is finished, so reset it here.
-    canvas_.reset();
-    return sk_ref_sp(static_cast<PaintRecord*>(picture.get()));
-  }
+  sk_sp<PaintRecord> finishRecordingAsPicture();
 
  private:
-  SkPictureRecorder recorder_;
-  base::Optional<SkiaPaintCanvas> canvas_;
+  sk_sp<PaintOpBuffer> buffer_;
+  base::Optional<RecordPaintCanvas> canvas_;
 
   DISALLOW_COPY_AND_ASSIGN(PaintRecorder);
 };
diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc
new file mode 100644
index 0000000..5e768a0
--- /dev/null
+++ b/cc/paint/record_paint_canvas.cc
@@ -0,0 +1,371 @@
+// 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/record_paint_canvas.h"
+
+#include "base/memory/ptr_util.h"
+#include "cc/paint/display_item_list.h"
+#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_record.h"
+#include "cc/paint/paint_recorder.h"
+#include "third_party/skia/include/core/SkAnnotation.h"
+#include "third_party/skia/include/core/SkMetaData.h"
+#include "third_party/skia/include/utils/SkNWayCanvas.h"
+
+namespace cc {
+
+RecordPaintCanvas::RecordPaintCanvas(PaintOpBuffer* buffer) : buffer_(buffer) {
+  DCHECK(buffer_);
+}
+
+RecordPaintCanvas::~RecordPaintCanvas() = default;
+
+SkMetaData& RecordPaintCanvas::getMetaData() {
+  // This could just be SkMetaData owned by RecordPaintCanvas, but since
+  // SkCanvas already has one, we might as well use it directly.
+  return GetCanvas()->getMetaData();
+}
+
+SkImageInfo RecordPaintCanvas::imageInfo() const {
+  return GetCanvas()->imageInfo();
+}
+
+void RecordPaintCanvas::flush() {
+  // This is a noop when recording.
+}
+
+int RecordPaintCanvas::save() {
+  buffer_->push<SaveOp>();
+  return GetCanvas()->save();
+}
+
+int RecordPaintCanvas::saveLayer(const SkRect* bounds,
+                                 const PaintFlags* flags) {
+  if (flags) {
+    if (flags->IsSimpleOpacity()) {
+      // TODO(enne): maybe more callers should know this and call
+      // saveLayerAlpha instead of needing to check here.
+      uint8_t alpha = SkColorGetA(flags->getColor());
+      return saveLayerAlpha(bounds, alpha);
+    }
+
+    // TODO(enne): it appears that image filters affect matrices and color
+    // matrices affect transparent flags on SkCanvas layers, but it's not clear
+    // whether those are actually needed and we could just skip ToSkPaint here.
+    buffer_->push<SaveLayerOp>(bounds, flags);
+    const SkPaint& paint = ToSkPaint(*flags);
+    return GetCanvas()->saveLayer(bounds, &paint);
+  }
+  buffer_->push<SaveLayerOp>(bounds, flags);
+  return GetCanvas()->saveLayer(bounds, nullptr);
+}
+
+int RecordPaintCanvas::saveLayerAlpha(const SkRect* bounds, uint8_t alpha) {
+  buffer_->push<SaveLayerAlphaOp>(bounds, alpha);
+  return GetCanvas()->saveLayerAlpha(bounds, alpha);
+}
+
+void RecordPaintCanvas::restore() {
+  buffer_->push<RestoreOp>();
+  GetCanvas()->restore();
+}
+
+int RecordPaintCanvas::getSaveCount() const {
+  return GetCanvas()->getSaveCount();
+}
+
+void RecordPaintCanvas::restoreToCount(int save_count) {
+  if (!canvas_) {
+    DCHECK_EQ(save_count, 1);
+    return;
+  }
+
+  DCHECK_GE(save_count, 1);
+  int diff = GetCanvas()->getSaveCount() - save_count;
+  DCHECK_GE(diff, 0);
+  for (int i = 0; i < diff; ++i)
+    restore();
+}
+
+void RecordPaintCanvas::translate(SkScalar dx, SkScalar dy) {
+  buffer_->push<TranslateOp>(dx, dy);
+  GetCanvas()->translate(dx, dy);
+}
+
+void RecordPaintCanvas::scale(SkScalar sx, SkScalar sy) {
+  buffer_->push<ScaleOp>(sx, sy);
+  GetCanvas()->scale(sx, sy);
+}
+
+void RecordPaintCanvas::rotate(SkScalar degrees) {
+  buffer_->push<RotateOp>(degrees);
+  GetCanvas()->rotate(degrees);
+}
+
+void RecordPaintCanvas::concat(const SkMatrix& matrix) {
+  buffer_->push<ConcatOp>(matrix);
+  GetCanvas()->concat(matrix);
+}
+
+void RecordPaintCanvas::setMatrix(const SkMatrix& matrix) {
+  buffer_->push<SetMatrixOp>(matrix);
+  GetCanvas()->setMatrix(matrix);
+}
+
+void RecordPaintCanvas::clipRect(const SkRect& rect,
+                                 SkClipOp op,
+                                 bool antialias) {
+  buffer_->push<ClipRectOp>(rect, op, antialias);
+  GetCanvas()->clipRect(rect, op, antialias);
+}
+
+void RecordPaintCanvas::clipRRect(const SkRRect& rrect,
+                                  SkClipOp op,
+                                  bool antialias) {
+  // TODO(enne): does this happen? Should the caller know this?
+  if (rrect.isRect()) {
+    clipRect(rrect.getBounds(), op, antialias);
+    return;
+  }
+  buffer_->push<ClipRRectOp>(rrect, op, antialias);
+  GetCanvas()->clipRRect(rrect, op, antialias);
+}
+
+void RecordPaintCanvas::clipPath(const SkPath& path,
+                                 SkClipOp op,
+                                 bool antialias) {
+  if (!path.isInverseFillType() &&
+      GetCanvas()->getTotalMatrix().rectStaysRect()) {
+    // TODO(enne): do these cases happen? should the caller know that this isn't
+    // a path?
+    SkRect rect;
+    if (path.isRect(&rect)) {
+      clipRect(rect, op, antialias);
+      return;
+    }
+    SkRRect rrect;
+    if (path.isOval(&rect)) {
+      rrect.setOval(rect);
+      clipRRect(rrect, op, antialias);
+      return;
+    }
+    if (path.isRRect(&rrect)) {
+      clipRRect(rrect, op, antialias);
+      return;
+    }
+  }
+
+  buffer_->push<ClipPathOp>(path, op, antialias);
+  GetCanvas()->clipPath(path, op, antialias);
+  return;
+}
+
+bool RecordPaintCanvas::quickReject(const SkRect& rect) const {
+  return GetCanvas()->quickReject(rect);
+}
+
+bool RecordPaintCanvas::quickReject(const SkPath& path) const {
+  return GetCanvas()->quickReject(path);
+}
+
+SkRect RecordPaintCanvas::getLocalClipBounds() const {
+  return GetCanvas()->getLocalClipBounds();
+}
+
+bool RecordPaintCanvas::getLocalClipBounds(SkRect* bounds) const {
+  return GetCanvas()->getLocalClipBounds(bounds);
+}
+
+SkIRect RecordPaintCanvas::getDeviceClipBounds() const {
+  return GetCanvas()->getDeviceClipBounds();
+}
+
+bool RecordPaintCanvas::getDeviceClipBounds(SkIRect* bounds) const {
+  return GetCanvas()->getDeviceClipBounds(bounds);
+}
+
+void RecordPaintCanvas::drawColor(SkColor color, SkBlendMode mode) {
+  buffer_->push<DrawColorOp>(color, mode);
+}
+
+void RecordPaintCanvas::clear(SkColor color) {
+  buffer_->push<DrawColorOp>(color, SkBlendMode::kSrc);
+}
+
+void RecordPaintCanvas::drawLine(SkScalar x0,
+                                 SkScalar y0,
+                                 SkScalar x1,
+                                 SkScalar y1,
+                                 const PaintFlags& flags) {
+  buffer_->push<DrawLineOp>(x0, y0, x1, y1, flags);
+}
+
+void RecordPaintCanvas::drawRect(const SkRect& rect, const PaintFlags& flags) {
+  buffer_->push<DrawRectOp>(rect, flags);
+}
+
+void RecordPaintCanvas::drawIRect(const SkIRect& rect,
+                                  const PaintFlags& flags) {
+  buffer_->push<DrawIRectOp>(rect, flags);
+}
+
+void RecordPaintCanvas::drawOval(const SkRect& oval, const PaintFlags& flags) {
+  buffer_->push<DrawOvalOp>(oval, flags);
+}
+
+void RecordPaintCanvas::drawRRect(const SkRRect& rrect,
+                                  const PaintFlags& flags) {
+  buffer_->push<DrawRRectOp>(rrect, flags);
+}
+
+void RecordPaintCanvas::drawDRRect(const SkRRect& outer,
+                                   const SkRRect& inner,
+                                   const PaintFlags& flags) {
+  if (outer.isEmpty())
+    return;
+  if (inner.isEmpty()) {
+    drawRRect(outer, flags);
+    return;
+  }
+  buffer_->push<DrawDRRectOp>(outer, inner, flags);
+}
+
+void RecordPaintCanvas::drawCircle(SkScalar cx,
+                                   SkScalar cy,
+                                   SkScalar radius,
+                                   const PaintFlags& flags) {
+  buffer_->push<DrawCircleOp>(cx, cy, radius, flags);
+}
+
+void RecordPaintCanvas::drawArc(const SkRect& oval,
+                                SkScalar start_angle,
+                                SkScalar sweep_angle,
+                                bool use_center,
+                                const PaintFlags& flags) {
+  buffer_->push<DrawArcOp>(oval, start_angle, sweep_angle, use_center, flags);
+}
+
+void RecordPaintCanvas::drawRoundRect(const SkRect& rect,
+                                      SkScalar rx,
+                                      SkScalar ry,
+                                      const PaintFlags& flags) {
+  // TODO(enne): move this into base class?
+  if (rx > 0 && ry > 0) {
+    SkRRect rrect;
+    rrect.setRectXY(rect, rx, ry);
+    drawRRect(rrect, flags);
+  } else {
+    drawRect(rect, flags);
+  }
+}
+
+void RecordPaintCanvas::drawPath(const SkPath& path, const PaintFlags& flags) {
+  buffer_->push<DrawPathOp>(path, flags);
+}
+
+void RecordPaintCanvas::drawImage(const PaintImage& image,
+                                  SkScalar left,
+                                  SkScalar top,
+                                  const PaintFlags* flags) {
+  buffer_->push<DrawImageOp>(image, left, top, flags);
+}
+
+void RecordPaintCanvas::drawImageRect(const PaintImage& image,
+                                      const SkRect& src,
+                                      const SkRect& dst,
+                                      const PaintFlags* flags,
+                                      SrcRectConstraint constraint) {
+  buffer_->push<DrawImageRectOp>(image, src, dst, flags, constraint);
+}
+
+void RecordPaintCanvas::drawBitmap(const SkBitmap& bitmap,
+                                   SkScalar left,
+                                   SkScalar top,
+                                   const PaintFlags* flags) {
+  // TODO(enne): Move into base class?
+  if (bitmap.drawsNothing())
+    return;
+  drawImage(PaintImage(SkImage::MakeFromBitmap(bitmap),
+                       PaintImage::AnimationType::UNKNOWN,
+                       PaintImage::CompletionState::UNKNOWN),
+            left, top, flags);
+}
+
+void RecordPaintCanvas::drawText(const void* text,
+                                 size_t byte_length,
+                                 SkScalar x,
+                                 SkScalar y,
+                                 const PaintFlags& flags) {
+  buffer_->push_with_data<DrawTextOp>(text, byte_length, x, y, flags);
+}
+
+void RecordPaintCanvas::drawPosText(const void* text,
+                                    size_t byte_length,
+                                    const SkPoint pos[],
+                                    const PaintFlags& flags) {
+  size_t count = ToSkPaint(flags).countText(text, byte_length);
+  buffer_->push_with_array<DrawPosTextOp>(text, byte_length, pos, count, flags);
+}
+
+void RecordPaintCanvas::drawTextBlob(sk_sp<SkTextBlob> blob,
+                                     SkScalar x,
+                                     SkScalar y,
+                                     const PaintFlags& flags) {
+  buffer_->push<DrawTextBlobOp>(blob, x, y, flags);
+}
+
+void RecordPaintCanvas::drawDisplayItemList(
+    scoped_refptr<DisplayItemList> list) {
+  buffer_->push<DrawDisplayItemListOp>(list);
+}
+
+void RecordPaintCanvas::drawPicture(sk_sp<const PaintRecord> record) {
+  // TODO(enne): If this is small, maybe flatten it?
+  buffer_->push<DrawRecordOp>(record);
+}
+
+bool RecordPaintCanvas::isClipEmpty() const {
+  return GetCanvas()->isClipEmpty();
+}
+
+bool RecordPaintCanvas::isClipRect() const {
+  return GetCanvas()->isClipRect();
+}
+
+const SkMatrix& RecordPaintCanvas::getTotalMatrix() const {
+  return GetCanvas()->getTotalMatrix();
+}
+
+void RecordPaintCanvas::Annotate(AnnotationType type,
+                                 const SkRect& rect,
+                                 sk_sp<SkData> data) {
+  buffer_->push<AnnotateOp>(type, rect, data);
+}
+
+void RecordPaintCanvas::PlaybackPaintRecord(sk_sp<const PaintRecord> record) {
+  drawPicture(record);
+}
+
+const SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() const {
+  return const_cast<RecordPaintCanvas*>(this)->GetCanvas();
+}
+
+SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() {
+  if (canvas_)
+    return &*canvas_;
+
+  SkIRect rect = buffer_->cullRect().roundOut();
+  canvas_.emplace(rect.right(), rect.bottom());
+
+  // This is part of the "recording canvases have a size, but why" dance.
+  // By creating a canvas of size (right x bottom) and then clipping it,
+  // It makes getDeviceClipBounds return the original cull rect, which code
+  // in GraphicsContextCanvas on Mac expects.  (Just creating an SkNoDrawCanvas
+  // with the cull_rect makes a canvas of size (width x height) instead
+  // which is incorrect.  SkRecorder cheats with private resetForNextCanvas.
+  canvas_->clipRect(SkRect::Make(rect), SkClipOp::kIntersect, false);
+  return &*canvas_;
+}
+
+}  // namespace cc
diff --git a/cc/paint/record_paint_canvas.h b/cc/paint/record_paint_canvas.h
new file mode 100644
index 0000000..4677512
--- /dev/null
+++ b/cc/paint/record_paint_canvas.h
@@ -0,0 +1,159 @@
+// 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.
+
+#ifndef CC_PAINT_RECORD_PAINT_CANVAS_H_
+#define CC_PAINT_RECORD_PAINT_CANVAS_H_
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "build/build_config.h"
+#include "cc/paint/paint_canvas.h"
+#include "cc/paint/paint_flags.h"
+#include "cc/paint/paint_record.h"
+#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
+
+namespace cc {
+
+class PaintOpBuffer;
+class PaintFlags;
+
+class CC_PAINT_EXPORT RecordPaintCanvas final : public PaintCanvas {
+ public:
+  explicit RecordPaintCanvas(PaintOpBuffer* buffer);
+  ~RecordPaintCanvas() override;
+
+  SkMetaData& getMetaData() override;
+  SkImageInfo imageInfo() const override;
+
+  void flush() override;
+
+  int save() override;
+  int saveLayer(const SkRect* bounds, const PaintFlags* flags) override;
+  int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override;
+
+  void restore() override;
+  int getSaveCount() const override;
+  void restoreToCount(int save_count) override;
+  void translate(SkScalar dx, SkScalar dy) override;
+  void scale(SkScalar sx, SkScalar sy) override;
+  void rotate(SkScalar degrees) override;
+  void concat(const SkMatrix& matrix) override;
+  void setMatrix(const SkMatrix& matrix) override;
+
+  void clipRect(const SkRect& rect, SkClipOp op, bool antialias) override;
+  void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) override;
+  void clipPath(const SkPath& path, SkClipOp op, bool antialias) override;
+  bool quickReject(const SkRect& rect) const override;
+  bool quickReject(const SkPath& path) const override;
+  SkRect getLocalClipBounds() const override;
+  bool getLocalClipBounds(SkRect* bounds) const override;
+  SkIRect getDeviceClipBounds() const override;
+  bool getDeviceClipBounds(SkIRect* bounds) const override;
+  void drawColor(SkColor color, SkBlendMode mode) override;
+  void clear(SkColor color) override;
+
+  void drawLine(SkScalar x0,
+                SkScalar y0,
+                SkScalar x1,
+                SkScalar y1,
+                const PaintFlags& flags) override;
+  void drawRect(const SkRect& rect, const PaintFlags& flags) override;
+  void drawIRect(const SkIRect& rect, const PaintFlags& flags) override;
+  void drawOval(const SkRect& oval, const PaintFlags& flags) override;
+  void drawRRect(const SkRRect& rrect, const PaintFlags& flags) override;
+  void drawDRRect(const SkRRect& outer,
+                  const SkRRect& inner,
+                  const PaintFlags& flags) override;
+  void drawCircle(SkScalar cx,
+                  SkScalar cy,
+                  SkScalar radius,
+                  const PaintFlags& flags) override;
+  void drawArc(const SkRect& oval,
+               SkScalar start_angle,
+               SkScalar sweep_angle,
+               bool use_center,
+               const PaintFlags& flags) override;
+  void drawRoundRect(const SkRect& rect,
+                     SkScalar rx,
+                     SkScalar ry,
+                     const PaintFlags& flags) override;
+  void drawPath(const SkPath& path, const PaintFlags& flags) override;
+  void drawImage(const PaintImage& image,
+                 SkScalar left,
+                 SkScalar top,
+                 const PaintFlags* flags) override;
+  void drawImageRect(const PaintImage& image,
+                     const SkRect& src,
+                     const SkRect& dst,
+                     const PaintFlags* flags,
+                     SrcRectConstraint constraint) override;
+  void drawBitmap(const SkBitmap& bitmap,
+                  SkScalar left,
+                  SkScalar top,
+                  const PaintFlags* flags) override;
+
+  void drawText(const void* text,
+                size_t byte_length,
+                SkScalar x,
+                SkScalar y,
+                const PaintFlags& flags) override;
+  void drawPosText(const void* text,
+                   size_t byte_length,
+                   const SkPoint pos[],
+                   const PaintFlags& flags) override;
+  void drawTextBlob(sk_sp<SkTextBlob> blob,
+                    SkScalar x,
+                    SkScalar y,
+                    const PaintFlags& flags) override;
+
+  void drawDisplayItemList(
+      scoped_refptr<DisplayItemList> display_item_list) override;
+
+  void drawPicture(sk_sp<const PaintRecord> record) override;
+
+  bool isClipEmpty() const override;
+  bool isClipRect() const override;
+  const SkMatrix& getTotalMatrix() const override;
+
+  void Annotate(AnnotationType type,
+                const SkRect& rect,
+                sk_sp<SkData> data) override;
+
+  void PlaybackPaintRecord(sk_sp<const PaintRecord> record) override;
+
+  // Don't shadow non-virtual helper functions.
+  using PaintCanvas::clipRect;
+  using PaintCanvas::clipRRect;
+  using PaintCanvas::clipPath;
+  using PaintCanvas::drawBitmap;
+  using PaintCanvas::drawColor;
+  using PaintCanvas::drawImage;
+  using PaintCanvas::drawPicture;
+
+ private:
+  const SkNoDrawCanvas* GetCanvas() const;
+  SkNoDrawCanvas* GetCanvas();
+
+  PaintOpBuffer* buffer_;
+
+  // TODO(enne): Although RecordPaintCanvas is mostly a write-only interface
+  // where paint commands are stored, occasionally users of PaintCanvas want
+  // to ask stateful questions mid-stream of clip and transform state.
+  // To avoid duplicating all this code (for now?), just forward to an SkCanvas
+  // that's not backed by anything but can answer these questions.
+  //
+  // This is mutable so that const functions (e.g. quickReject) that may
+  // lazy initialize the canvas can still be const.
+  mutable base::Optional<SkNoDrawCanvas> canvas_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordPaintCanvas);
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_RECORD_PAINT_CANVAS_H_
diff --git a/cc/paint/skia_paint_canvas.cc b/cc/paint/skia_paint_canvas.cc
index 9c1a20a..47953bd 100644
--- a/cc/paint/skia_paint_canvas.cc
+++ b/cc/paint/skia_paint_canvas.cc
@@ -22,7 +22,6 @@
                                  const SkSurfaceProps& props)
     : canvas_(new SkCanvas(bitmap, props)), owned_(canvas_) {}
 
-SkiaPaintCanvas::SkiaPaintCanvas(SkiaPaintCanvas&& other) = default;
 SkiaPaintCanvas::~SkiaPaintCanvas() = default;
 
 SkMetaData& SkiaPaintCanvas::getMetaData() {
@@ -45,7 +44,7 @@
   return canvas_->saveLayer(bounds, ToSkPaint(flags));
 }
 
-int SkiaPaintCanvas::saveLayerAlpha(const SkRect* bounds, U8CPU alpha) {
+int SkiaPaintCanvas::saveLayerAlpha(const SkRect* bounds, uint8_t alpha) {
   return canvas_->saveLayerAlpha(bounds, alpha);
 }
 
diff --git a/cc/paint/skia_paint_canvas.h b/cc/paint/skia_paint_canvas.h
index 8e016f0..720104df 100644
--- a/cc/paint/skia_paint_canvas.h
+++ b/cc/paint/skia_paint_canvas.h
@@ -28,11 +28,8 @@
   explicit SkiaPaintCanvas(SkCanvas* canvas);
   explicit SkiaPaintCanvas(const SkBitmap& bitmap);
   explicit SkiaPaintCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props);
-  explicit SkiaPaintCanvas(SkiaPaintCanvas&& other);
   ~SkiaPaintCanvas() override;
 
-  SkiaPaintCanvas& operator=(SkiaPaintCanvas&& other) = default;
-
   SkMetaData& getMetaData() override;
   SkImageInfo imageInfo() const override;
 
@@ -40,7 +37,7 @@
 
   int save() override;
   int saveLayer(const SkRect* bounds, const PaintFlags* flags) override;
-  int saveLayerAlpha(const SkRect* bounds, U8CPU alpha) override;
+  int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override;
 
   void restore() override;
   int getSaveCount() const override;
diff --git a/cc/test/test_skcanvas.cc b/cc/test/test_skcanvas.cc
new file mode 100644
index 0000000..e45f42de
--- /dev/null
+++ b/cc/test/test_skcanvas.cc
@@ -0,0 +1,26 @@
+// 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/test/test_skcanvas.h"
+
+namespace cc {
+
+SaveCountingCanvas::SaveCountingCanvas() : SkNoDrawCanvas(100, 100) {}
+
+SkCanvas::SaveLayerStrategy SaveCountingCanvas::getSaveLayerStrategy(
+    const SaveLayerRec& rec) {
+  save_count_++;
+  return SkNoDrawCanvas::getSaveLayerStrategy(rec);
+}
+
+void SaveCountingCanvas::willRestore() {
+  restore_count_++;
+}
+
+void SaveCountingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
+  draw_rect_ = rect;
+  paint_ = paint;
+}
+
+}  // namespace cc
diff --git a/cc/test/test_skcanvas.h b/cc/test/test_skcanvas.h
new file mode 100644
index 0000000..2b130a4
--- /dev/null
+++ b/cc/test/test_skcanvas.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef CC_TEST_TEST_SKCANVAS_H_
+#define CC_TEST_TEST_SKCANVAS_H_
+
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
+
+namespace cc {
+
+class SaveCountingCanvas : public SkNoDrawCanvas {
+ public:
+  SaveCountingCanvas();
+
+  // Note: getSaveLayerStrategy is used as "willSave", as willSave
+  // is not always called.
+  SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override;
+  void willRestore() override;
+  void onDrawRect(const SkRect& rect, const SkPaint& paint) override;
+
+  int save_count_ = 0;
+  int restore_count_ = 0;
+  SkRect draw_rect_;
+  SkPaint paint_;
+};
+
+}  // namespace cc
+
+#endif  // CC_TEST_TEST_SKCANVAS_H_
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 9935918..901375a 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_recorder.h"
+#include "cc/paint/paint_shader.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
diff --git a/printing/pdf_metafile_skia.cc b/printing/pdf_metafile_skia.cc
index dd91494..4e15484 100644
--- a/printing/pdf_metafile_skia.cc
+++ b/printing/pdf_metafile_skia.cc
@@ -12,9 +12,9 @@
 #include "base/files/file.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
-#include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/paint_recorder.h"
+#include "cc/paint/skia_paint_canvas.h"
 #include "printing/print_settings.h"
 #include "third_party/skia/include/core/SkDocument.h"
 #include "third_party/skia/include/core/SkStream.h"
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html
deleted file mode 100644
index 97d5761f..0000000
--- a/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<style>
-    .multicol {
-        width: 300px;
-        height: 100px;
-        line-height: 20px;
-        border: 5px solid maroon;
-    }
-    .column {
-        width: 100px;
-        float: left;
-    }
-    .multicol[dir="rtl"] > .column {
-        float: right;
-    }
-    .block {
-        display: inline-block;
-        width: 1em;
-        height: 10px;
-        background-color: green;
-    }
-    .opacity {
-        opacity: 0.5;
-        color: green;
-    }
-    .relative {
-        position: relative;
-        top: -4px;
-        color: green;
-    }
-</style>
-<p>
-    Test layers which are fully contained within a single column.
-</p>
-LTR:
-<div class="multicol">
-    <div class="column">
-        line1<br>
-        line2<br>
-        line3<br>
-        line4<br>
-        line5<br>
-    </div>
-    <div class="column">
-        line6<br>
-        <div class="block"></div> line7<br>
-        line8<br>
-        <span class="relative">relative9</span><br>
-        line10<br>
-    </div>
-    <div class="column">
-        line11<br>
-        line12<br>
-        <!-- The extra inner span below forces the creation of a transparency layer in Skia to work
-             around optimizations that would cause blending differences between the test and the
-             expectation. -->
-        <span class="opacity">opacity<span>13</span></span><br>
-        line14
-    </div>
-</div>
-
-RTL:
-<div class="multicol" dir="rtl">
-    <div class="column">
-        line1<br>
-        line2<br>
-        line3<br>
-        line4<br>
-        line5<br>
-    </div>
-    <div class="column">
-        line6<br>
-        <div class="block"></div> line7<br>
-        line8<br>
-        <span class="relative">relative9</span><br>
-        line10<br>
-    </div>
-    <div class="column">
-        line11<br>
-        line12<br>
-        <!-- The extra inner span below forces the creation of a transparency layer in Skia to work
-             around optimizations that would cause blending differences between the test and the
-             expectation. -->
-        <span class="opacity">opacity<span>13</span></span><br>
-        line14
-    </div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html
deleted file mode 100644
index 8bbd920..0000000
--- a/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html
+++ /dev/null
@@ -1,90 +0,0 @@
-<!DOCTYPE html>
-<style>
-    .container {
-        margin-right: 4px;
-        position: absolute;
-    }
-    .multicol {
-        width: 110px;
-        height: 150px;
-        border: 5px solid black;
-    }
-    .multicol > div {
-        float: left;
-        width: 50px;
-        height: 50px;
-    }
-
-    .row1_left { background-color: black; }
-    .row1_right { background-color: #0000b0; }
-    .row2_left { background-color: #0000f0; }
-    .row2_right { background-color: #000090; }
-    .row3_left { background-color: #0000d0; }
-    .row3_right { background-color: black; }
-
-    .row1_right,
-    .row2_right,
-    .row3_right {
-        margin-left: 10px;
-    }
-
-    #opacity .row1_right,
-    #opacity .row2_left,
-    #opacity .row2_right,
-    #opacity .row3_left {
-        opacity: 0.99;
-    }
-
-    .pos1 { left: 10px; top: 10px; }
-    .pos2 { left: 150px; top: 10px; }
-    .pos3 { left: 10px; top: 200px; }
-    .pos4 { left: 150px; top: 200px; }
-
-</style>
-<div class="container pos1">
-    Overflow:
-    <div class="multicol">
-        <div class="row1_left"></div>
-        <div class="row1_right"></div>
-        <div class="row2_left"></div>
-        <div class="row2_right"></div>
-        <div class="row3_left"></div>
-        <div class="row3_right"></div>
-    </div>
-</div>
-<div class="container pos2">
-    Transforms:
-    <div class="multicol">
-        <div class="row1_left"></div>
-        <div class="row1_right"></div>
-        <div class="row2_left"></div>
-        <div class="row2_right"></div>
-        <div class="row3_left"></div>
-        <div class="row3_right"></div>
-    </div>
-</div>
-<div class="container pos3">
-    Relative Pos.:
-    <div class="multicol">
-        <div class="row1_left"></div>
-        <div class="row1_right"></div>
-        <div class="row2_left"></div>
-        <div class="row2_right"></div>
-        <div class="row3_left"></div>
-        <div class="row3_right"></div>
-    </div>
-</div>
-<div class="container pos4" id="opacity">
-    Opacity:
-    <div class="multicol">
-        <div class="row1_left"></div>
-        <!-- The extra &nbsp;s below force the creation of transparency layers in Skia to work
-             around optimizations that would cause blending differences between the test and the
-             expectation. -->
-        <div class="row1_right">&nbsp;</div>
-        <div class="row2_left">&nbsp;</div>
-        <div class="row2_right">&nbsp;</div>
-        <div class="row3_left">&nbsp;</div>
-        <div class="row3_right"></div>
-    </div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html
deleted file mode 100644
index 0b67873..0000000
--- a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<div style="position:relative; width:420px;border:2px solid black; height:200px">
-<!-- The extra &nbsp; below forces the creation of a transparency layer in Skia to work around
-     optimizations that would cause blending differences between the test and the expectation. -->
-<div style="opacity:0.5; position:absolute;width:200px;height:100px;background-color:green;right:0;top:0">&nbsp;</div>
-</div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png
new file mode 100644
index 0000000..1ebb6e8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png b/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png
index a92e6a8..c6cabcb 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png
new file mode 100644
index 0000000..44fc4a4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png
new file mode 100644
index 0000000..27fbf58
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index b024e71..5e1fff8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png
index 47485d6..ab92965 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png
index 8d5f6fd3..6f98684 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index b024e71..5e1fff8 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
index 47485d6..ab92965 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png
index 3a881330..0acea10 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 07e019c..991aaf7a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png
index 11b5d71..84b89da 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png
index f409f9e..1693941 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 07e019c..991aaf7a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png
new file mode 100644
index 0000000..9243efa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png
new file mode 100644
index 0000000..86cf379
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 56d7e380c..af00c64c0 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png
index 300a55b..aa3d62632 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png b/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png
index 7e1243a1..d5d8ba4 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png
index 497919fc..70f55312 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png b/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png
index 38e6870..c57f417b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 56d7e380c..af00c64c0 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
index 300a55b..aa3d62632 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png
new file mode 100644
index 0000000..62b98ea
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png
new file mode 100644
index 0000000..608b610
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 8829276..6eb1bf6 100644
--- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png
index a281c42..7733c19 100644
--- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png
index 6837fed..5b597ed 100644
--- a/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index 8829276..6eb1bf6 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
index a281c42..7733c19 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index bbb8278..27dad6f 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png
index 7e9778db..855bfdf8 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
index bbb8278..27dad6f 100644
--- a/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png
Binary files differ
diff --git a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
index c72135b..a57d2f7c 100644
--- a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
+++ b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
@@ -365,7 +365,7 @@
   FloatRect float_bounds(FloatPoint(), size);
   const SkRect bounds(float_bounds);
 
-  flags.setShader(SkShader::MakePictureShader(
+  flags.setShader(MakePaintShaderRecord(
       PaintRecordForCurrentFrame(float_bounds, url), SkShader::kRepeat_TileMode,
       SkShader::kRepeat_TileMode, &local_matrix, &bounds));
 
diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp
index 75f9fdd2..a215a535 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp
@@ -23,11 +23,12 @@
   list->AppendClipPathItem(clip_path_, true);
 }
 
-void BeginClipPathDisplayItem::AnalyzeForGpuRasterization(
-    SkPictureGpuAnalyzer& analyzer) const {
+int BeginClipPathDisplayItem::NumberOfSlowPaths() const {
   // Temporarily disabled (pref regressions due to GPU veto stickiness:
   // http://crbug.com/603969).
   // analyzer.analyzeClipPath(m_clipPath, SkRegion::kIntersect_Op, true);
+  // TODO(enne): fixup this code to return an int.
+  return 0;
 }
 
 void EndClipPathDisplayItem::Replay(GraphicsContext& context) const {
diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h
index 42434ae..3952c87 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h
+++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h
@@ -24,7 +24,7 @@
   void AppendToWebDisplayItemList(const IntRect&,
                                   WebDisplayItemList*) const override;
 
-  void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const override;
+  int NumberOfSlowPaths() const override;
 
  private:
 #ifndef NDEBUG
diff --git a/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp b/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp
index cdcd8fd..7eac3491 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp
@@ -29,58 +29,8 @@
 CompositingRecorder::~CompositingRecorder() {
   if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
     return;
-  // If the end of the current display list is of the form
-  // [BeginCompositingDisplayItem] [DrawingDisplayItem], then fold the
-  // BeginCompositingDisplayItem into a new DrawingDisplayItem that replaces
-  // them both. This allows Skia to optimize for the case when the
-  // BeginCompositingDisplayItem represents a simple opacity/color that can be
-  // merged into the opacity/color of the drawing. See crbug.com/628831 for more
-  // details.
-  PaintController& paint_controller = graphics_context_.GetPaintController();
-  const DisplayItem* last_display_item = paint_controller.LastDisplayItem(0);
-  const DisplayItem* second_to_last_display_item =
-      paint_controller.LastDisplayItem(1);
-  // TODO(chrishtr): remove the call to LastDisplayItemIsSubsequenceEnd when
-  // https://codereview.chromium.org/2768143002 lands.
-  if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && last_display_item &&
-      second_to_last_display_item && last_display_item->DrawsContent() &&
-      second_to_last_display_item->GetType() ==
-          DisplayItem::kBeginCompositing &&
-      !paint_controller.LastDisplayItemIsSubsequenceEnd()) {
-    FloatRect cull_rect(
-        ((DrawingDisplayItem*)last_display_item)->GetPaintRecord()->cullRect());
-    const DisplayItemClient& display_item_client = last_display_item->Client();
-    DisplayItem::Type display_item_type = last_display_item->GetType();
-
-    // Re-record the last two DisplayItems into a new drawing. The new item
-    // cannot be cached, because it is a mutation of the DisplayItem the client
-    // thought it was painting.
-    paint_controller.BeginSkippingCache();
-    {
-#if DCHECK_IS_ON()
-      // In the recorder's scope we remove the last two display items which
-      // are combined into a new drawing.
-      DisableListModificationCheck disabler;
-#endif
-      DrawingRecorder new_recorder(graphics_context_, display_item_client,
-                                   display_item_type, cull_rect);
-      DCHECK(!DrawingRecorder::UseCachedDrawingIfPossible(
-          graphics_context_, display_item_client, display_item_type));
-
-      second_to_last_display_item->Replay(graphics_context_);
-      last_display_item->Replay(graphics_context_);
-      EndCompositingDisplayItem(client_).Replay(graphics_context_);
-
-      // Remove the DrawingDisplayItem.
-      paint_controller.RemoveLastDisplayItem();
-      // Remove the BeginCompositingDisplayItem.
-      paint_controller.RemoveLastDisplayItem();
-    }
-    paint_controller.EndSkippingCache();
-  } else {
-    graphics_context_.GetPaintController().EndItem<EndCompositingDisplayItem>(
-        client_);
-  }
+  graphics_context_.GetPaintController().EndItem<EndCompositingDisplayItem>(
+      client_);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h
index a9afcd1..d5f344e6 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h
+++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h
@@ -17,8 +17,6 @@
 #include "platform/wtf/text/WTFString.h"
 #endif
 
-class SkPictureGpuAnalyzer;
-
 namespace blink {
 
 class GraphicsContext;
@@ -337,7 +335,7 @@
   virtual bool DrawsContent() const { return false; }
 
   // Override to implement specific analysis strategies.
-  virtual void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const {}
+  virtual int NumberOfSlowPaths() const { return 0; }
 
 #ifndef NDEBUG
   static WTF::String TypeAsDebugString(DisplayItem::Type);
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp
index 8691f50c..694762a 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp
@@ -7,7 +7,6 @@
 #include "platform/graphics/LoggingCanvas.h"
 #include "platform/graphics/paint/DrawingDisplayItem.h"
 #include "platform/graphics/paint/PaintChunk.h"
-#include "third_party/skia/include/core/SkPictureAnalyzer.h"
 
 #ifndef NDEBUG
 #include "platform/wtf/text/WTFString.h"
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp
index d88ea7c..6a76f3be 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp
@@ -10,7 +10,6 @@
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkData.h"
-#include "third_party/skia/include/core/SkPictureAnalyzer.h"
 
 namespace blink {
 
@@ -30,14 +29,8 @@
   return record_.get();
 }
 
-void DrawingDisplayItem::AnalyzeForGpuRasterization(
-    SkPictureGpuAnalyzer& analyzer) const {
-  // TODO(enne): Need an SkPictureGpuAnalyzer on PictureRecord.
-  // This is a bit overkill to ToSkPicture a record just to get
-  // numSlowPaths.
-  if (!record_)
-    return;
-  analyzer.analyzePicture(ToSkPicture(record_).get());
+int DrawingDisplayItem::NumberOfSlowPaths() const {
+  return record_ ? record_->numSlowPaths() : 0;
 }
 
 #ifndef NDEBUG
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h
index e5f989f..3dedbcbd 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h
+++ b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h
@@ -41,7 +41,7 @@
     return known_to_be_opaque_;
   }
 
-  void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const override;
+  int NumberOfSlowPaths() const override;
 
  private:
 #ifndef NDEBUG
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
index a703076..0f99802 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp
@@ -16,6 +16,8 @@
 #include <stdio.h>
 #endif
 
+static constexpr int kMaxNumberOfSlowPathsBeforeVeto = 5;
+
 namespace blink {
 
 void PaintController::SetTracksRasterInvalidations(bool value) {
@@ -536,7 +538,7 @@
       !new_display_item_list_.IsEmpty())
     GenerateChunkRasterInvalidationRects(new_paint_chunks_.LastChunk());
 
-  SkPictureGpuAnalyzer gpu_analyzer;
+  int num_slow_paths = 0;
 
   current_cache_generation_ =
       DisplayItemClient::CacheGenerationOrInvalidationReason::Next();
@@ -555,8 +557,8 @@
   Vector<const DisplayItemClient*> skipped_cache_clients;
   for (const auto& item : new_display_item_list_) {
     // No reason to continue the analysis once we have a veto.
-    if (gpu_analyzer.suitableForGpuRasterization())
-      item.AnalyzeForGpuRasterization(gpu_analyzer);
+    if (num_slow_paths <= kMaxNumberOfSlowPathsBeforeVeto)
+      num_slow_paths += item.NumberOfSlowPaths();
 
     // TODO(wkorman): Only compute and append visual rect for drawings.
     new_display_item_list_.AppendVisualRect(
@@ -594,7 +596,7 @@
   new_display_item_list_.ShrinkToFit();
   current_paint_artifact_ = PaintArtifact(
       std::move(new_display_item_list_), new_paint_chunks_.ReleasePaintChunks(),
-      gpu_analyzer.suitableForGpuRasterization());
+      num_slow_paths <= kMaxNumberOfSlowPathsBeforeVeto);
   ResetCurrentListIndices();
   out_of_order_item_indices_.clear();
   out_of_order_chunk_indices_.clear();
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp
index c497fbade..9904efe 100644
--- a/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp
@@ -2273,40 +2273,6 @@
     DisplayItemClient::EndShouldKeepAliveAllClients();
 #endif
   }
-
-  void TestFoldCompositingDrawingInSubsequence() {
-    FakeDisplayItemClient container("container");
-    FakeDisplayItemClient content("content");
-    GraphicsContext context(GetPaintController());
-
-    {
-      SubsequenceRecorder subsequence(context, container);
-      CompositingRecorder compositing(context, content, SkBlendMode::kSrc, 0.5);
-      DrawRect(context, content, kBackgroundDrawingType,
-               FloatRect(100, 100, 300, 300));
-    }
-    GetPaintController().CommitNewDisplayItems();
-    EXPECT_EQ(
-        1u,
-        GetPaintController().GetPaintArtifact().GetDisplayItemList().size());
-
-    {
-      EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible(
-          context, container));
-      SubsequenceRecorder subsequence(context, container);
-      CompositingRecorder compositing(context, content, SkBlendMode::kSrc, 0.5);
-      DrawRect(context, content, kBackgroundDrawingType,
-               FloatRect(100, 100, 300, 300));
-    }
-    GetPaintController().CommitNewDisplayItems();
-    EXPECT_EQ(
-        1u,
-        GetPaintController().GetPaintArtifact().GetDisplayItemList().size());
-
-#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
-    DisplayItemClient::EndShouldKeepAliveAllClients();
-#endif
-  }
 };
 
 TEST_F(PaintControllerUnderInvalidationTest, ChangeDrawing) {
@@ -2356,11 +2322,6 @@
   TestInvalidationInSubsequence();
 }
 
-TEST_F(PaintControllerUnderInvalidationTest,
-       FoldCompositingDrawingInSubsequence) {
-  TestFoldCompositingDrawingInSubsequence();
-}
-
 #endif  // defined(GTEST_HAS_DEATH_TEST) && !OS(ANDROID)
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm b/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm
index 6379d6a..e3eada5 100644
--- a/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm
+++ b/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm
@@ -24,6 +24,7 @@
 #include "platform/graphics/paint/PaintCanvas.h"
 #include "platform/mac/ThemeMac.h"
 #include "platform_canvas.h"
+#include "third_party/skia/include/core/SkRegion.h"
 
 namespace blink {
 
diff --git a/ui/gfx/canvas.cc b/ui/gfx/canvas.cc
index 31ef57f..80eeda5 100644
--- a/ui/gfx/canvas.cc
+++ b/ui/gfx/canvas.cc
@@ -584,7 +584,7 @@
   // Ensure that the bitmap is zeroed, since the code expects that.
   memset(bitmap_->getPixels(), 0, bitmap_->getSafeSize());
 
-  owned_canvas_ = cc::SkiaPaintCanvas(bitmap_.value());
+  owned_canvas_.emplace(bitmap_.value());
   return &owned_canvas_.value();
 }
 
diff --git a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
index c77bdf4..ce75522 100644
--- a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
+++ b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
@@ -5,6 +5,7 @@
 #import "ui/views/controls/scrollbar/cocoa_scroll_bar.h"
 
 #import "base/mac/sdk_forward_declarations.h"
+#include "cc/paint/paint_shader.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/effects/SkGradientShader.h"
 #include "ui/compositor/layer.h"