[Canvas] Destroy PaintOps after Serialization
PaintOps are not used after serialization except for printing, and it's
very expensive to destroy the paint ops afterwards. According to trace
result, the destroy takes even longer than serialization. This cl
combines the two steps and improved the MotionMark result on canvas
lines by 3-4%.
performance analysis:
https://docs.google.com/document/d/1h4UyWGRm5EJwCflg5ujQoG88VJDxucGixUKI9EQfp9M/edit?pli=1&resourcekey=0-atzn_PGgfoT5AZfILwKeBw#
tl;dr: improves motionmark score on canvas lines by 3-4%.
Bug: 1240730
Change-Id: I759c7b660e497c4f23fc06b130bd5d6df9a54c8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3107173
Reviewed-by: Fernando Serboncini <fserb@chromium.org>
Reviewed-by: kylechar <kylechar@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Justin Novosad <junov@chromium.org>
Commit-Queue: Yi Xu <yiyix@chromium.org>
Cr-Commit-Position: refs/heads/main@{#925985}
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 74f8029a..6cc3ac5 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -2714,6 +2714,7 @@
     const PaintOpBuffer* buffer,
     const std::vector<size_t>* offsets)
     : using_offsets_(!!offsets) {
+  DCHECK(!buffer->are_ops_destroyed());
   if (using_offsets_)
     offset_iter_.emplace(buffer, offsets);
   else
@@ -2731,7 +2732,8 @@
       has_draw_ops_(false),
       has_draw_text_ops_(false),
       has_save_layer_alpha_ops_(false),
-      has_effects_preventing_lcd_text_for_save_layer_alpha_(false) {}
+      has_effects_preventing_lcd_text_for_save_layer_alpha_(false),
+      are_ops_destroyed_(false) {}
 
 PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) {
   *this = std::move(other);
@@ -2756,6 +2758,7 @@
   has_save_layer_alpha_ops_ = other.has_save_layer_alpha_ops_;
   has_effects_preventing_lcd_text_for_save_layer_alpha_ =
       other.has_effects_preventing_lcd_text_for_save_layer_alpha_;
+  are_ops_destroyed_ = other.are_ops_destroyed_;
 
   // Make sure the other pob can destruct safely.
   other.used_ = 0;
@@ -2765,8 +2768,11 @@
 }
 
 void PaintOpBuffer::Reset() {
-  for (auto* op : Iterator(this))
-    op->DestroyThis();
+  if (!are_ops_destroyed_) {
+    for (auto* op : Iterator(this)) {
+      op->DestroyThis();
+    }
+  }
 
   // Leave data_ allocated, reserved_ unchanged. ShrinkToFit will take care of
   // that if called.
@@ -2781,6 +2787,7 @@
   has_draw_text_ops_ = false;
   has_save_layer_alpha_ops_ = false;
   has_effects_preventing_lcd_text_for_save_layer_alpha_ = false;
+  are_ops_destroyed_ = false;
 }
 
 // When |op| is a nested PaintOpBuffer, this returns the PaintOp inside
@@ -2822,6 +2829,7 @@
     const std::vector<size_t>* offsets)
     : iter_(buffer, offsets),
       folded_draw_color_(SK_ColorTRANSPARENT, SkBlendMode::kSrcOver) {
+  DCHECK(!buffer->are_ops_destroyed());
   FindNextOp();
 }
 
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 7bb77c3..4345019 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -1096,6 +1096,8 @@
   bool has_effects_preventing_lcd_text_for_save_layer_alpha() const {
     return has_effects_preventing_lcd_text_for_save_layer_alpha_;
   }
+  bool are_ops_destroyed() const { return are_ops_destroyed_; }
+  void MarkOpsDestroyed() { are_ops_destroyed_ = true; }
 
   bool NeedsAdditionalInvalidationForLCDText(
       const PaintOpBuffer& old_buffer) const;
@@ -1217,7 +1219,9 @@
 
    private:
     Iterator(const PaintOpBuffer* buffer, char* ptr, size_t op_offset)
-        : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {}
+        : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {
+      DCHECK(!buffer->are_ops_destroyed());
+    }
 
     const PaintOpBuffer* buffer_ = nullptr;
     char* ptr_ = nullptr;
@@ -1230,6 +1234,7 @@
     OffsetIterator(const PaintOpBuffer* buffer,
                    const std::vector<size_t>* offsets)
         : buffer_(buffer), ptr_(buffer_->data_.get()), offsets_(offsets) {
+      DCHECK(!buffer->are_ops_destroyed());
       if (!offsets || offsets->empty()) {
         *this = end();
         return;
@@ -1279,10 +1284,9 @@
                    char* ptr,
                    size_t op_offset,
                    const std::vector<size_t>* offsets)
-        : buffer_(buffer),
-          ptr_(ptr),
-          offsets_(offsets),
-          op_offset_(op_offset) {}
+        : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {
+      DCHECK(!buffer->are_ops_destroyed());
+    }
 
     const PaintOpBuffer* buffer_ = nullptr;
     char* ptr_ = nullptr;
@@ -1397,6 +1401,7 @@
   bool has_draw_text_ops_ : 1;
   bool has_save_layer_alpha_ops_ : 1;
   bool has_effects_preventing_lcd_text_for_save_layer_alpha_ : 1;
+  bool are_ops_destroyed_ : 1;
 };
 
 }  // namespace cc
diff --git a/cc/paint/paint_op_buffer_serializer.cc b/cc/paint/paint_op_buffer_serializer.cc
index 1dc29cc..95134dc2 100644
--- a/cc/paint/paint_op_buffer_serializer.cc
+++ b/cc/paint/paint_op_buffer_serializer.cc
@@ -71,6 +71,25 @@
   RestoreToCount(canvas.get(), saveCount, params);
 }
 
+void PaintOpBufferSerializer::SerializeAndDestroy(
+    PaintOpBuffer* buffer,
+    const std::vector<size_t>* offsets,
+    const Preamble& preamble) {
+  std::unique_ptr<SkCanvas> canvas = MakeAnalysisCanvas(options_);
+
+  // These PlaybackParams use the initial (identity) canvas matrix, as they are
+  // only used for serializing the preamble and the initial save / final restore
+  // SerializeBuffer will create its own PlaybackParams based on the
+  // post-preamble canvas.
+  PlaybackParams params = MakeParams(canvas.get());
+
+  int saveCount = canvas->getSaveCount();
+  Save(canvas.get(), params);
+  SerializePreamble(canvas.get(), preamble, params);
+  SerializeBufferAndDestroy(canvas.get(), buffer, offsets);
+  RestoreToCount(canvas.get(), saveCount, params);
+}
+
 void PaintOpBufferSerializer::Serialize(const PaintOpBuffer* buffer) {
   std::unique_ptr<SkCanvas> canvas = MakeAnalysisCanvas(options_);
   SerializeBuffer(canvas.get(), buffer, nullptr);
@@ -180,6 +199,75 @@
   }
 }
 
+bool PaintOpBufferSerializer::WillSerializeNextOp(const PaintOp* op,
+                                                  SkCanvas* canvas,
+                                                  PlaybackParams params,
+                                                  uint8_t alpha) {
+  // Skip ops outside the current clip if they have images. This saves
+  // performing an unnecessary expensive decode.
+  bool skip_op = PaintOp::OpHasDiscardableImages(op) &&
+                 PaintOp::QuickRejectDraw(op, canvas);
+  // Skip text ops if there is no SkStrikeServer.
+  skip_op |=
+      op->GetType() == PaintOpType::DrawTextBlob && !options_.strike_server;
+  if (skip_op)
+    return true;
+
+  if (op->GetType() == PaintOpType::DrawRecord) {
+    int save_count = canvas->getSaveCount();
+    Save(canvas, params);
+    SerializeBuffer(canvas, static_cast<const DrawRecordOp*>(op)->record.get(),
+                    nullptr);
+    RestoreToCount(canvas, save_count, params);
+    return true;
+  }
+
+  if (op->GetType() == PaintOpType::DrawImageRect &&
+      static_cast<const DrawImageRectOp*>(op)->image.IsPaintWorklet()) {
+    DCHECK(options_.image_provider);
+    const DrawImageRectOp* draw_op = static_cast<const DrawImageRectOp*>(op);
+    ImageProvider::ScopedResult result =
+        options_.image_provider->GetRasterContent(DrawImage(draw_op->image));
+    if (!result || !result.paint_record())
+      return true;
+
+    int save_count = canvas->getSaveCount();
+    Save(canvas, params);
+    // The following ops are copying the canvas's ops from
+    // DrawImageRectOp::RasterWithFlags.
+    SkM44 trans = SkM44(SkMatrix::RectToRect(draw_op->src, draw_op->dst));
+    ConcatOp concat_op(trans);
+    bool success = SerializeOp(canvas, &concat_op, nullptr, params);
+
+    if (!success)
+      return false;
+
+    ClipRectOp clip_rect_op(draw_op->src, SkClipOp::kIntersect, false);
+    success = SerializeOp(canvas, &clip_rect_op, nullptr, params);
+    if (!success)
+      return false;
+
+    // In DrawImageRectOp::RasterWithFlags, the save layer uses the
+    // flags_to_serialize or default(null) flags. At this point in the
+    // serialization, flags_to_serialize is always null as well.
+    SaveLayerOp save_layer_op(&draw_op->src, nullptr);
+    success = SerializeOpWithFlags(canvas, &save_layer_op, params, 255);
+    if (!success)
+      return false;
+
+    SerializeBuffer(canvas, result.paint_record(), nullptr);
+    RestoreToCount(canvas, save_count, params);
+    return true;
+  } else {
+    if (op->IsPaintOpWithFlags()) {
+      return SerializeOpWithFlags(
+          canvas, static_cast<const PaintOpWithFlags*>(op), params, alpha);
+    } else {
+      return SerializeOp(canvas, op, nullptr, params);
+    }
+  }
+}
+
 void PaintOpBufferSerializer::SerializeBuffer(
     SkCanvas* canvas,
     const PaintOpBuffer* buffer,
@@ -192,76 +280,37 @@
   for (PaintOpBuffer::PlaybackFoldingIterator iter(buffer, offsets); iter;
        ++iter) {
     const PaintOp* op = *iter;
-
-    // Skip ops outside the current clip if they have images. This saves
-    // performing an unnecessary expensive decode.
-    bool skip_op = PaintOp::OpHasDiscardableImages(op) &&
-                   PaintOp::QuickRejectDraw(op, canvas);
-    // Skip text ops if there is no SkStrikeServer.
-    skip_op |=
-        op->GetType() == PaintOpType::DrawTextBlob && !options_.strike_server;
-    if (skip_op)
-      continue;
-
-    if (op->GetType() == PaintOpType::DrawRecord) {
-      int save_count = canvas->getSaveCount();
-      Save(canvas, params);
-      SerializeBuffer(
-          canvas, static_cast<const DrawRecordOp*>(op)->record.get(), nullptr);
-      RestoreToCount(canvas, save_count, params);
-      continue;
-    }
-
-    if (op->GetType() == PaintOpType::DrawImageRect &&
-        static_cast<const DrawImageRectOp*>(op)->image.IsPaintWorklet()) {
-      DCHECK(options_.image_provider);
-      const DrawImageRectOp* draw_op = static_cast<const DrawImageRectOp*>(op);
-      ImageProvider::ScopedResult result =
-          options_.image_provider->GetRasterContent(DrawImage(draw_op->image));
-      if (!result || !result.paint_record())
-        continue;
-
-      int save_count = canvas->getSaveCount();
-      Save(canvas, params);
-      // The following ops are copying the canvas's ops from
-      // DrawImageRectOp::RasterWithFlags.
-      SkM44 trans = SkM44(SkMatrix::RectToRect(draw_op->src, draw_op->dst));
-      ConcatOp concat_op(trans);
-      bool success = SerializeOp(canvas, &concat_op, nullptr, params);
-      if (!success)
-        return;
-      ClipRectOp clip_rect_op(draw_op->src, SkClipOp::kIntersect, false);
-      success = SerializeOp(canvas, &clip_rect_op, nullptr, params);
-      if (!success)
-        return;
-      // In DrawImageRectOp::RasterWithFlags, the save layer uses the
-      // flags_to_serialize or default(null) flags. At this point in the
-      // serialization, flags_to_serialize is always null as well.
-      SaveLayerOp save_layer_op(&draw_op->src, nullptr);
-      success = SerializeOpWithFlags(canvas, &save_layer_op, params, 255);
-      if (!success)
-        return;
-
-      SerializeBuffer(canvas, result.paint_record(), nullptr);
-      RestoreToCount(canvas, save_count, params);
-
-      continue;
-    }
-
-    bool success = false;
-    if (op->IsPaintOpWithFlags()) {
-      success =
-          SerializeOpWithFlags(canvas, static_cast<const PaintOpWithFlags*>(op),
-                               params, iter.alpha());
-    } else {
-      success = SerializeOp(canvas, op, nullptr, params);
-    }
-
-    if (!success)
+    if (!WillSerializeNextOp(op, canvas, params, iter.alpha())) {
       return;
+    }
   }
 }
 
+void PaintOpBufferSerializer::SerializeBufferAndDestroy(
+    SkCanvas* canvas,
+    PaintOpBuffer* buffer,
+    const std::vector<size_t>* offsets) {
+  DCHECK(buffer);
+  // This updates the original_ctm to reflect the canvas transformation at
+  // start of this call to SerializeBuffer.
+  PlaybackParams params = MakeParams(canvas);
+  bool destroy_op_only = false;
+
+  for (PaintOpBuffer::PlaybackFoldingIterator iter(buffer, offsets); iter;
+       ++iter) {
+    PaintOp* op = const_cast<PaintOp*>(*iter);
+    if (!destroy_op_only) {
+      // If serialization failed, destroy PaintOps in |buffer|.
+      destroy_op_only = !WillSerializeNextOp(op, canvas, params, iter.alpha());
+    }
+    op->DestroyThis();
+  }
+
+  // Each PaintOp in |buffer| is destroyed. Update the flag |ops_destroyed| to
+  // true, so it skip calling destruction in PaintOpsBuffer::Reset().
+  const_cast<PaintOpBuffer*>(buffer)->MarkOpsDestroyed();
+}
+
 bool PaintOpBufferSerializer::SerializeOpWithFlags(
     SkCanvas* canvas,
     const PaintOpWithFlags* flags_op,
diff --git a/cc/paint/paint_op_buffer_serializer.h b/cc/paint/paint_op_buffer_serializer.h
index 9783440f..34863f62 100644
--- a/cc/paint/paint_op_buffer_serializer.h
+++ b/cc/paint/paint_op_buffer_serializer.h
@@ -56,6 +56,11 @@
   void Serialize(const PaintOpBuffer* buffer,
                  const std::vector<size_t>* offsets,
                  const Preamble& preamble);
+  // Sereialize the buffer as |Serialize| with a preamble. This function also
+  // destroys the PaintOps in |buffer| after serialization.
+  void SerializeAndDestroy(PaintOpBuffer* buffer,
+                           const std::vector<size_t>* offsets,
+                           const Preamble& preamble);
   // Serialize the buffer without a preamble. This function serializes the whole
   // buffer without any extra ops added.  No clearing is done.  This should
   // generally be used for internal PaintOpBuffers that want to be sent as-is.
@@ -79,6 +84,15 @@
   void SerializeBuffer(SkCanvas* canvas,
                        const PaintOpBuffer* buffer,
                        const std::vector<size_t>* offsets);
+  void SerializeBufferAndDestroy(SkCanvas* canvas,
+                                 PaintOpBuffer* buffer,
+                                 const std::vector<size_t>* offsets);
+  // Returns whether searilization of |op| succeeded and we need to serialize
+  // the next PaintOp in the PaintOpBuffer.
+  bool WillSerializeNextOp(const PaintOp* op,
+                           SkCanvas* canvas,
+                           PlaybackParams params,
+                           uint8_t alpha);
   bool SerializeOpWithFlags(SkCanvas* canvas,
                             const PaintOpWithFlags* flags_op,
                             const PlaybackParams& params,
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 3280a5d..476dfcf 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -2112,6 +2112,29 @@
   }
 }
 
+TEST(PaintOpSerializationTest, DoNotPreservePaintOps) {
+  PaintOpBuffer buffer;
+  PushDrawIRectOps(&buffer);
+
+  PaintOpBufferSerializer::Preamble preamble;
+  preamble.content_size = gfx::Size(1000, 1000);
+  preamble.playback_rect = gfx::Rect(preamble.content_size);
+  preamble.full_raster_rect = preamble.playback_rect;
+  preamble.requires_clear = true;
+
+  std::unique_ptr<char, base::AlignedFreeDeleter> memory(
+      static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
+                                            PaintOpBuffer::PaintOpAlign)));
+  TestOptionsProvider options_provider;
+  SimpleBufferSerializer serializer(memory.get(),
+                                    PaintOpBuffer::kInitialBufferSize,
+                                    options_provider.serialize_options());
+  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
+  ASSERT_NE(serializer.written(), 0u);
+
+  EXPECT_TRUE(buffer.are_ops_destroyed());
+}
+
 TEST(PaintOpSerializationTest, Preamble) {
   PaintOpBufferSerializer::Preamble preamble;
   preamble.content_size = gfx::Size(30, 40);
@@ -2131,7 +2154,7 @@
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
                                     options_provider.serialize_options());
-  serializer.Serialize(&buffer, nullptr, preamble);
+  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
   ASSERT_NE(serializer.written(), 0u);
 
   auto deserialized_buffer =
@@ -2306,7 +2329,7 @@
     // Avoid clearing.
     preamble.content_size = gfx::Size(1000, 1000);
     preamble.requires_clear = false;
-    serializer.Serialize(&buffer, nullptr, preamble);
+    serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
     ASSERT_NE(serializer.written(), 0u);
 
     auto deserialized_buffer =
@@ -2359,7 +2382,7 @@
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
                                     options_provider.serialize_options());
-  serializer.Serialize(&buffer, nullptr, preamble);
+  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
   ASSERT_NE(serializer.written(), 0u);
 
   auto deserialized_buffer =
diff --git a/cc/raster/raster_buffer_provider_unittest.cc b/cc/raster/raster_buffer_provider_unittest.cc
index 9ba0950..fcdf00e 100644
--- a/cc/raster/raster_buffer_provider_unittest.cc
+++ b/cc/raster/raster_buffer_provider_unittest.cc
@@ -188,7 +188,8 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint) override {}
+                      size_t* max_op_size_hint,
+                      bool preserve_recording = true) override {}
   void EndRasterCHROMIUM() override {}
 };
 
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index b802e7c2..40c92c6 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1351,7 +1351,8 @@
                                           const gfx::Vector2dF& post_translate,
                                           const gfx::Vector2dF& post_scale,
                                           bool requires_clear,
-                                          size_t* max_op_size_hint) {
+                                          size_t* max_op_size_hint,
+                                          bool preserve_recording) {
   TRACE_EVENT1("gpu", "RasterImplementation::RasterCHROMIUM",
                "raster_chromium_id", ++raster_chromium_id_);
   DCHECK(max_op_size_hint);
@@ -1403,8 +1404,13 @@
           raster_properties_->color_space, raster_properties_->can_use_lcd_text,
           capabilities().context_supports_distance_field_text,
           capabilities().max_texture_size));
-  serializer.Serialize(&list->paint_op_buffer_, &temp_raster_offsets_,
-                       preamble);
+  if (preserve_recording) {
+    serializer.Serialize(&list->paint_op_buffer_, &temp_raster_offsets_,
+                         preamble);
+  } else {
+    auto* buffer = const_cast<cc::PaintOpBuffer*>(&list->paint_op_buffer_);
+    serializer.SerializeAndDestroy(buffer, &temp_raster_offsets_, preamble);
+  }
   // TODO(piman): raise error if !serializer.valid()?
   op_serializer.SendSerializedData();
 }
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index 80ecd6d..f733cb7d 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -161,7 +161,8 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint) override;
+                      size_t* max_op_size_hint,
+                      bool preserve_recording = true) override;
   SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
                                 const gfx::Size& output_size,
                                 uint32_t transfer_cache_entry_id,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index a305a72b..42b2f82 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -236,7 +236,8 @@
     const gfx::Vector2dF& post_translate,
     const gfx::Vector2dF& post_scale,
     bool requires_clear,
-    size_t* max_op_size_hint) {
+    size_t* max_op_size_hint,
+    bool preserve_recording) {
   NOTREACHED();
 }
 
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index 63338e4..efb0426 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -105,7 +105,8 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint) override;
+                      size_t* max_op_size_hint,
+                      bool preserve_recording = true) override;
   void EndRasterCHROMIUM() override;
 
   // Image decode acceleration.
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index ff82163..96f90982 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -103,7 +103,8 @@
                               const gfx::Vector2dF& post_translate,
                               const gfx::Vector2dF& post_scale,
                               bool requires_clear,
-                              size_t* max_op_size_hint) = 0;
+                              size_t* max_op_size_hint,
+                              bool preserve_recording = true) = 0;
 
   // Schedules a hardware-accelerated image decode and a sync token that's
   // released when the image decode is complete. If the decode could not be
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
index 509128c..5fbfb912 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
@@ -498,13 +498,15 @@
     timer.emplace();
   }
 
-  last_recording_ = ResourceProvider()->FlushCanvas();
-  last_record_tainted_by_write_pixels_ = false;
   if (!clear_frame_ || !resource_host_ || !resource_host_->IsPrinting()) {
-    last_recording_ = nullptr;
+    last_recording_ = ResourceProvider()->FlushCanvas();
     clear_frame_ = false;
+  } else {
+    last_recording_ = ResourceProvider()->FlushCanvasAndPreserveRecording();
   }
 
+  last_record_tainted_by_write_pixels_ = false;
+
   // Finish up the timing operation
   if (measure_raster_metric) {
     if (IsAccelerated()) {
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index 0b313d2..91529c4 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -499,16 +499,19 @@
 
   void WillDraw() override { WillDrawInternal(true); }
 
-  void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override {
+  void RasterRecord(sk_sp<cc::PaintRecord> last_recording,
+                    bool preserve_recording) override {
     if (!use_oop_rasterization_) {
-      CanvasResourceProvider::RasterRecord(std::move(last_recording));
+      CanvasResourceProvider::RasterRecord(std::move(last_recording),
+                                           preserve_recording);
       return;
     }
     WillDrawInternal(true);
     const bool needs_clear = !is_cleared_;
     is_cleared_ = true;
     RasterRecordOOP(last_recording, needs_clear,
-                    resource()->GetOrCreateGpuMailbox(kUnverifiedSyncToken));
+                    resource()->GetOrCreateGpuMailbox(kUnverifiedSyncToken),
+                    preserve_recording);
   }
 
   bool ShouldReplaceTargetBuffer(
@@ -821,15 +824,17 @@
         ColorParams().GetSkColorSpace(), &props);
   }
 
-  void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override {
+  void RasterRecord(sk_sp<cc::PaintRecord> last_recording,
+                    bool preserve_recording) override {
     TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::RasterRecord");
     if (!use_oop_rasterization_) {
-      CanvasResourceProvider::RasterRecord(std::move(last_recording));
+      CanvasResourceProvider::RasterRecord(std::move(last_recording),
+                                           preserve_recording);
       return;
     }
     WillDraw();
     RasterRecordOOP(last_recording, initial_needs_clear_,
-                    resource_->GetBackBufferMailbox());
+                    resource_->GetBackBufferMailbox(), preserve_recording);
     initial_needs_clear_ = false;
   }
 
@@ -1370,20 +1375,32 @@
 }
 
 sk_sp<cc::PaintRecord> CanvasResourceProvider::FlushCanvas() {
+  return FlushCanvasInternal(false);
+}
+
+sk_sp<cc::PaintRecord>
+CanvasResourceProvider::FlushCanvasAndPreserveRecording() {
+  return FlushCanvasInternal(true);
+}
+
+sk_sp<cc::PaintRecord> CanvasResourceProvider::FlushCanvasInternal(
+    bool preserve_recording) {
   if (!HasRecordedDrawOps())
     return nullptr;
   sk_sp<cc::PaintRecord> last_recording = recorder_->finishRecordingAsPicture();
-  RasterRecord(last_recording);
+  RasterRecord(last_recording, preserve_recording);
   total_pinned_image_bytes_ = 0;
   cc::PaintCanvas* canvas =
       recorder_->beginRecording(Size().Width(), Size().Height());
   if (restore_clip_stack_callback_)
     restore_clip_stack_callback_.Run(canvas);
+  if (!preserve_recording)
+    return nullptr;
   return last_recording;
 }
 
-void CanvasResourceProvider::RasterRecord(
-    sk_sp<cc::PaintRecord> last_recording) {
+void CanvasResourceProvider::RasterRecord(sk_sp<cc::PaintRecord> last_recording,
+                                          bool) {
   EnsureSkiaCanvas();
   skia_canvas_->drawPicture(std::move(last_recording));
   GetSkSurface()->flushAndSubmit();
@@ -1392,7 +1409,8 @@
 void CanvasResourceProvider::RasterRecordOOP(
     sk_sp<cc::PaintRecord> last_recording,
     bool needs_clear,
-    gpu::Mailbox mailbox) {
+    gpu::Mailbox mailbox,
+    bool preserve_recording) {
   if (IsGpuContextLost())
     return;
   gpu::raster::RasterInterface* ri = RasterInterface();
@@ -1423,7 +1441,8 @@
 
   ri->RasterCHROMIUM(list.get(), GetOrCreateCanvasImageProvider(), size,
                      full_raster_rect, playback_rect, post_translate,
-                     post_scale, false /* requires_clear */, &max_op_size_hint);
+                     post_scale, false /* requires_clear */, &max_op_size_hint,
+                     preserve_recording);
 
   ri->EndRasterCHROMIUM();
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
index f05ccd3..b6a306c 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
@@ -162,7 +162,10 @@
 
   cc::PaintCanvas* Canvas(bool needs_will_draw = false);
   void ReleaseLockedImages();
+  // FlushCanvas and do not preserve recordings.
   sk_sp<cc::PaintRecord> FlushCanvas();
+  // FlushCanvas and preserve recordings.
+  sk_sp<cc::PaintRecord> FlushCanvasAndPreserveRecording();
   const CanvasResourceParams& ColorParams() const { return params_; }
   void SetFilterQuality(cc::PaintFlags::FilterQuality quality) {
     filter_quality_ = quality;
@@ -283,6 +286,7 @@
   }
   scoped_refptr<StaticBitmapImage> SnapshotInternal(const ImageOrientation&);
   scoped_refptr<CanvasResource> GetImportedResource() const;
+  sk_sp<cc::PaintRecord> FlushCanvasInternal(bool preserve_recording);
 
   CanvasResourceProvider(const ResourceProviderType&,
                          const IntSize&,
@@ -298,10 +302,11 @@
   // decodes/uploads in the cache is invalidated only when the canvas contents
   // change.
   cc::PaintImage MakeImageSnapshot();
-  virtual void RasterRecord(sk_sp<cc::PaintRecord>);
+  virtual void RasterRecord(sk_sp<cc::PaintRecord>, bool preserve_recording);
   void RasterRecordOOP(sk_sp<cc::PaintRecord> last_recording,
                        bool needs_clear,
-                       gpu::Mailbox mailbox);
+                       gpu::Mailbox mailbox,
+                       bool preserve_recording);
   void RestoreBackBufferOOP(const cc::PaintImage&);
 
   CanvasImageProvider* GetOrCreateCanvasImageProvider();