[go: nahoru, domu]

PaintOpBuffer: Adds serializer for Skottie

This patch implements the serializer function for skottie draw ops. It
utilizes the transfer cache to avoid moving the entire skottie byte
data across IPC for every frame update.

This functionality is not included in android since android will not be
using it.

To achieve this the following modifications were also
made to the SkottieWrapper class:
  - A unique id is generated using fast hash for a given animation
    byte data.
  - Store the raw byte data in the object.
  - Modify the constructor to differentiate between serializable &
    non-serializable constructors.

Test=Added unit tests for transfer cache and paint op buffer.

Bug: 894635
Change-Id: I9fb7b5e7f761a84e6b6112d6e73e5681e9d86342
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2003809
Commit-Queue: Malay Keshav <malaykeshav@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Khushal <khushalsagar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734676}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 95eadcfab..79ca2276 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -742,6 +742,7 @@
 
   if (!is_android) {
     data = [ "//components/viz/test/data/" ]
+    sources += [ "paint/skottie_transfer_cache_entry_unittest.cc" ]
   }
 
   if (is_win) {
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index 7064a90..6c29489 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -109,6 +109,13 @@
     "//ui/gfx/animation",
     "//ui/gfx/ipc/color",
   ]
+
+  if (!is_android) {
+    sources += [
+      "skottie_transfer_cache_entry.cc",
+      "skottie_transfer_cache_entry.h",
+    ]
+  }
 }
 
 fuzzer_test("paint_op_buffer_fuzzer") {
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 1e83664..c56efcb 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -4,6 +4,7 @@
 
 #include "cc/paint/paint_op_buffer.h"
 
+#include "build/build_config.h"
 #include "cc/paint/decoded_draw_image.h"
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/image_provider.h"
@@ -599,14 +600,23 @@
   return helper.size();
 }
 
-size_t DrawSkottieOp::Serialize(const PaintOp* op,
+size_t DrawSkottieOp::Serialize(const PaintOp* base_op,
                                 void* memory,
                                 size_t size,
                                 const SerializeOptions& options) {
-  // TODO(malaykeshav): these must be flattened.  Serializing this will not do
-  // anything. See https://crbug.com/894635
+#if defined(OS_ANDROID)
+  // Skottie is not used in android, so to keep apk size small it is excluded
+  // from the build.
   NOTREACHED();
   return 0u;
+#else
+  auto* op = static_cast<const DrawSkottieOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->dst);
+  helper.Write(SkFloatToScalar(op->t));
+  helper.Write(op->skottie);
+  return helper.size();
+#endif  // OS_ANDROID
 }
 
 size_t DrawTextBlobOp::Serialize(const PaintOp* base_op,
@@ -1023,9 +1033,30 @@
                                     void* output,
                                     size_t output_size,
                                     const DeserializeOptions& options) {
-  // TODO(malaykeshav): these must be flattened and not sent directly.
-  // See https://crbug.com/894635
+#if defined(OS_ANDROID)
+  // Skottie is not used on Android. To keep apk size small the skottie library
+  // is excluded from the binary.
   return nullptr;
+#else
+  DCHECK_GE(output_size, sizeof(DrawSkottieOp));
+  DrawSkottieOp* op = new (output) DrawSkottieOp;
+
+  PaintOpReader helper(input, input_size, options);
+  helper.Read(&op->dst);
+
+  SkScalar t;
+  helper.Read(&t);
+  op->t = SkScalarToFloat(t);
+
+  helper.Read(&op->skottie);
+
+  if (!helper.valid() || !op->IsValid()) {
+    op->~DrawSkottieOp();
+    return nullptr;
+  }
+  UpdateTypeAndSkip(op);
+  return op;
+#endif  // OS_ANDROID
 }
 
 PaintOp* DrawTextBlobOp::Deserialize(const volatile void* input,
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 3c0b636..5f038f4 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -5,6 +5,7 @@
 #include "cc/paint/paint_op_buffer.h"
 
 #include "base/bind.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "cc/paint/decoded_draw_image.h"
@@ -16,6 +17,8 @@
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
 #include "cc/paint/shader_transfer_cache_entry.h"
+#include "cc/paint/skottie_wrapper.h"
+#include "cc/paint/transfer_cache_entry.h"
 #include "cc/test/geometry_test_utils.h"
 #include "cc/test/paint_op_helper.h"
 #include "cc/test/skia_common.h"
@@ -42,6 +45,40 @@
 // unit test suite as generally deserialized ops are smaller.
 static constexpr size_t kBufferBytesPerOp = 1000 + sizeof(LargestPaintOp);
 
+#ifndef OS_ANDROID
+// A skottie animation with solid green color for the first 2.5 seconds and then
+// a solid blue color for the next 2.5 seconds.
+constexpr char kSkottieData[] =
+    "{"
+    "  \"v\" : \"4.12.0\","
+    "  \"fr\": 30,"
+    "  \"w\" : 400,"
+    "  \"h\" : 200,"
+    "  \"ip\": 0,"
+    "  \"op\": 150,"
+    "  \"assets\": [],"
+
+    "  \"layers\": ["
+    "    {"
+    "      \"ty\": 1,"
+    "      \"sw\": 400,"
+    "      \"sh\": 200,"
+    "      \"sc\": \"#00ff00\","
+    "      \"ip\": 0,"
+    "      \"op\": 75"
+    "    },"
+    "    {"
+    "      \"ty\": 1,"
+    "      \"sw\": 400,"
+    "      \"sh\": 200,"
+    "      \"sc\": \"#0000ff\","
+    "      \"ip\": 76,"
+    "      \"op\": 150"
+    "    }"
+    "  ]"
+    "}";
+#endif  // OS_ANDROID
+
 template <typename T>
 void ValidateOps(PaintOpBuffer* buffer) {
   // Make sure all test data is valid before serializing it.
@@ -3280,6 +3317,105 @@
   EXPECT_TRUE(!!rect_op->flags.getShader()->GetSkShader());
 }
 
+#ifndef OS_ANDROID
+TEST(PaintOpBufferTest, DrawSkottieOpSerialization) {
+  std::unique_ptr<char, base::AlignedFreeDeleter> memory(
+      static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
+                                            PaintOpBuffer::PaintOpAlign)));
+  std::vector<uint8_t> data(std::strlen(kSkottieData));
+  data.assign(reinterpret_cast<const uint8_t*>(kSkottieData),
+              reinterpret_cast<const uint8_t*>(kSkottieData) +
+                  std::strlen(kSkottieData));
+
+  scoped_refptr<SkottieWrapper> skottie =
+      SkottieWrapper::CreateSerializable(std::move(data));
+
+  ASSERT_TRUE(skottie->is_valid());
+  const SkRect input_rect = SkRect::MakeIWH(400, 300);
+  const float input_t = 0.4f;
+
+  PaintOpBuffer buffer;
+  buffer.push<DrawSkottieOp>(skottie, input_rect, input_t);
+
+  // Serialize
+  TestOptionsProvider options_provider;
+  SimpleBufferSerializer serializer(
+      memory.get(), PaintOpBuffer::kInitialBufferSize,
+      options_provider.image_provider(),
+      options_provider.transfer_cache_helper(),
+      options_provider.client_paint_cache(), options_provider.strike_server(),
+      options_provider.color_space(), options_provider.can_use_lcd_text(),
+      options_provider.context_supports_distance_field_text(),
+      options_provider.max_texture_size());
+  serializer.Serialize(&buffer);
+  ASSERT_TRUE(serializer.valid());
+  ASSERT_GT(serializer.written(), 0u);
+
+  // De-Serialize
+  auto deserialized_buffer =
+      PaintOpBuffer::MakeFromMemory(memory.get(), serializer.written(),
+                                    options_provider.deserialize_options());
+  ASSERT_TRUE(deserialized_buffer);
+  PaintOpBuffer::Iterator it(deserialized_buffer.get());
+  ASSERT_TRUE(it);
+  auto* op = *it;
+  ASSERT_TRUE(op->GetType() == PaintOpType::DrawSkottie);
+  auto* skottie_op = static_cast<DrawSkottieOp*>(op);
+  EXPECT_FLOAT_RECT_EQ(skottie_op->dst, input_rect);
+  EXPECT_FLOAT_EQ(skottie_op->t, input_t);
+  EXPECT_EQ(skottie_op->skottie->id(), skottie->id());
+  EXPECT_TRUE(skottie_op->skottie->is_valid());
+
+  // Check that an entry in transfer cache is present for the skottie data.
+  EXPECT_TRUE(options_provider.transfer_cache_helper()->GetEntryInternal(
+      TransferCacheEntryType::kSkottie, skottie->id()));
+}
+
+TEST(PaintOpBufferTest, DrawSkottieOpSerializationFailure) {
+  std::unique_ptr<char, base::AlignedFreeDeleter> memory(
+      static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
+                                            PaintOpBuffer::PaintOpAlign)));
+
+  std::vector<uint8_t> data(std::strlen(kSkottieData));
+  data.assign(reinterpret_cast<const uint8_t*>(kSkottieData),
+              reinterpret_cast<const uint8_t*>(kSkottieData) +
+                  std::strlen(kSkottieData));
+
+  scoped_refptr<SkottieWrapper> skottie =
+      SkottieWrapper::CreateSerializable(std::move(data));
+  ASSERT_TRUE(skottie->is_valid());
+  const SkRect input_rect = SkRect::MakeIWH(400, 300);
+  const float input_t = 0.4f;
+
+  PaintOpBuffer buffer;
+  buffer.push<DrawSkottieOp>(skottie, input_rect, input_t);
+
+  // Serialize
+  TestOptionsProvider options_provider;
+  SimpleBufferSerializer serializer(
+      memory.get(), PaintOpBuffer::kInitialBufferSize,
+      options_provider.image_provider(),
+      options_provider.transfer_cache_helper(),
+      options_provider.client_paint_cache(), options_provider.strike_server(),
+      options_provider.color_space(), options_provider.can_use_lcd_text(),
+      options_provider.context_supports_distance_field_text(),
+      options_provider.max_texture_size());
+  serializer.Serialize(&buffer);
+  ASSERT_TRUE(serializer.valid());
+  ASSERT_GT(serializer.written(), 0u);
+
+  // De-Serialize
+  PaintOp::DeserializeOptions d_options(options_provider.deserialize_options());
+
+  // Deserialization should fail on a non privileged process.
+  d_options.is_privileged = false;
+
+  auto deserialized_buffer = PaintOpBuffer::MakeFromMemory(
+      memory.get(), serializer.written(), d_options);
+  ASSERT_FALSE(deserialized_buffer);
+}
+#endif  // OS_ANDROID
+
 TEST(PaintOpBufferTest, CustomData) {
   // Basic tests: size, move, comparison.
   {
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index 6ae5eca..078b528 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -26,6 +26,10 @@
 #include "third_party/skia/include/core/SkTextBlob.h"
 #include "third_party/skia/src/core/SkRemoteGlyphCache.h"
 
+#ifndef OS_ANDROID
+#include "cc/paint/skottie_transfer_cache_entry.h"
+#endif
+
 namespace cc {
 namespace {
 
@@ -627,6 +631,41 @@
   *yuv_color_space = static_cast<SkYUVColorSpace>(raw_yuv_color_space);
 }
 
+// Android does not use skottie. Remove below section to keep binary size to a
+// minimum.
+#ifndef OS_ANDROID
+void PaintOpReader::Read(scoped_refptr<SkottieWrapper>* skottie) {
+  if (!options_.is_privileged) {
+    valid_ = false;
+    return;
+  }
+
+  uint32_t transfer_cache_entry_id;
+  ReadSimple(&transfer_cache_entry_id);
+  if (!valid_)
+    return;
+  auto* entry =
+      options_.transfer_cache->GetEntryAs<ServiceSkottieTransferCacheEntry>(
+          transfer_cache_entry_id);
+  if (entry) {
+    *skottie = entry->skottie();
+  } else {
+    valid_ = false;
+  }
+
+  size_t bytes_to_skip = 0u;
+  ReadSize(&bytes_to_skip);
+  if (!valid_)
+    return;
+  if (bytes_to_skip > remaining_bytes_) {
+    valid_ = false;
+    return;
+  }
+  memory_ += bytes_to_skip;
+  remaining_bytes_ -= bytes_to_skip;
+}
+#endif  // OS_ANDROID
+
 void PaintOpReader::AlignMemory(size_t alignment) {
   // Due to the math below, alignment must be a power of two.
   DCHECK_GT(alignment, 0u);
diff --git a/cc/paint/paint_op_reader.h b/cc/paint/paint_op_reader.h
index 51aca7d..9de213e 100644
--- a/cc/paint/paint_op_reader.h
+++ b/cc/paint/paint_op_reader.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "base/memory/scoped_refptr.h"
 #include "cc/paint/paint_export.h"
 #include "cc/paint/paint_filter.h"
 #include "cc/paint/paint_op_writer.h"
@@ -15,6 +16,7 @@
 namespace cc {
 
 class PaintShader;
+class SkottieWrapper;
 
 // PaintOpReader takes garbage |memory| and clobbers it with successive
 // read functions.
@@ -69,6 +71,10 @@
   void Read(sk_sp<SkColorSpace>* color_space);
   void Read(SkYUVColorSpace* yuv_color_space);
 
+#ifndef OS_ANDROID
+  void Read(scoped_refptr<SkottieWrapper>* skottie);
+#endif
+
   void Read(SkClipOp* op) {
     uint8_t value = 0u;
     Read(&value);
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc
index 077f5fb..a24b5c9 100644
--- a/cc/paint/paint_op_writer.cc
+++ b/cc/paint/paint_op_writer.cc
@@ -19,6 +19,10 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/skia_util.h"
 
+#ifndef OS_ANDROID
+#include "cc/paint/skottie_transfer_cache_entry.h"
+#endif
+
 namespace cc {
 namespace {
 const size_t kSkiaAlignment = 4u;
@@ -270,6 +274,35 @@
              decoded_draw_image.transfer_cache_entry_needs_mips());
 }
 
+// Android does not use skottie. Remove below section to keep binary size to a
+// minimum.
+#ifndef OS_ANDROID
+void PaintOpWriter::Write(scoped_refptr<SkottieWrapper> skottie) {
+  uint32_t id = skottie->id();
+  Write(id);
+
+  uint64_t* bytes_to_skip = WriteSize(0u);
+  if (!valid_)
+    return;
+
+  bool locked =
+      options_.transfer_cache->LockEntry(TransferCacheEntryType::kSkottie, id);
+
+  // Add a cache entry for the skottie animation.
+  uint64_t bytes_written = 0u;
+  if (!locked) {
+    bytes_written = options_.transfer_cache->CreateEntry(
+        ClientSkottieTransferCacheEntry(skottie), memory_);
+    options_.transfer_cache->AssertLocked(TransferCacheEntryType::kSkottie, id);
+  }
+
+  DCHECK_LE(bytes_written, remaining_bytes_);
+  *bytes_to_skip = bytes_written;
+  memory_ += bytes_written;
+  remaining_bytes_ -= bytes_written;
+}
+#endif  // OS_ANDROID
+
 void PaintOpWriter::WriteImage(uint32_t transfer_cache_entry_id,
                                bool needs_mips) {
   if (transfer_cache_entry_id == kInvalidImageTransferCacheEntryId) {
diff --git a/cc/paint/paint_op_writer.h b/cc/paint/paint_op_writer.h
index af798ea1..187d697 100644
--- a/cc/paint/paint_op_writer.h
+++ b/cc/paint/paint_op_writer.h
@@ -103,6 +103,11 @@
   // image.
   void Write(const DrawImage& draw_image, SkSize* scale_adjustment);
 
+#ifndef OS_ANDROID
+  // Serializes the given |skottie| vector graphic.
+  void Write(scoped_refptr<SkottieWrapper> skottie);
+#endif
+
  private:
   template <typename T>
   void WriteSimple(const T& val);
diff --git a/cc/paint/skottie_transfer_cache_entry.cc b/cc/paint/skottie_transfer_cache_entry.cc
new file mode 100644
index 0000000..7b1f1bd
--- /dev/null
+++ b/cc/paint/skottie_transfer_cache_entry.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 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/skottie_transfer_cache_entry.h"
+
+#include "cc/paint/skottie_wrapper.h"
+
+namespace cc {
+
+ClientSkottieTransferCacheEntry::ClientSkottieTransferCacheEntry(
+    scoped_refptr<SkottieWrapper> skottie)
+    : skottie_(std::move(skottie)) {}
+
+ClientSkottieTransferCacheEntry::~ClientSkottieTransferCacheEntry() = default;
+
+uint32_t ClientSkottieTransferCacheEntry::Id() const {
+  return skottie_->id();
+}
+
+uint32_t ClientSkottieTransferCacheEntry::SerializedSize() const {
+  return skottie_->raw_data().size();
+}
+
+bool ClientSkottieTransferCacheEntry::Serialize(
+    base::span<uint8_t> data) const {
+  DCHECK_GE(data.size(), SerializedSize());
+  memcpy(data.data(), skottie_->raw_data().data(), SerializedSize());
+  return true;
+}
+
+ServiceSkottieTransferCacheEntry::ServiceSkottieTransferCacheEntry() = default;
+ServiceSkottieTransferCacheEntry::~ServiceSkottieTransferCacheEntry() = default;
+
+size_t ServiceSkottieTransferCacheEntry::CachedSize() const {
+  return cached_size_;
+}
+
+bool ServiceSkottieTransferCacheEntry::Deserialize(
+    GrContext* context,
+    base::span<const uint8_t> data) {
+  skottie_ = SkottieWrapper::CreateNonSerializable(data);
+  cached_size_ = data.size();
+  return skottie_->is_valid();
+}
+
+}  // namespace cc
diff --git a/cc/paint/skottie_transfer_cache_entry.h b/cc/paint/skottie_transfer_cache_entry.h
new file mode 100644
index 0000000..c81de74ad
--- /dev/null
+++ b/cc/paint/skottie_transfer_cache_entry.h
@@ -0,0 +1,54 @@
+// Copyright 2020 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_SKOTTIE_TRANSFER_CACHE_ENTRY_H_
+#define CC_PAINT_SKOTTIE_TRANSFER_CACHE_ENTRY_H_
+
+#include "base/containers/span.h"
+#include "base/memory/ref_counted_memory.h"
+#include "cc/paint/transfer_cache_entry.h"
+
+namespace cc {
+
+class SkottieWrapper;
+
+// Client/ServiceSkottieTransferCacheEntry implements a transfer cache entry
+// for transferring skottie data.
+class CC_PAINT_EXPORT ClientSkottieTransferCacheEntry
+    : public ClientTransferCacheEntryBase<TransferCacheEntryType::kSkottie> {
+ public:
+  explicit ClientSkottieTransferCacheEntry(
+      scoped_refptr<SkottieWrapper> skottie);
+  ~ClientSkottieTransferCacheEntry() final;
+
+  uint32_t Id() const final;
+
+  // ClientTransferCacheEntry implementation:
+  uint32_t SerializedSize() const final;
+  bool Serialize(base::span<uint8_t> data) const final;
+
+ private:
+  scoped_refptr<SkottieWrapper> skottie_;
+};
+
+class CC_PAINT_EXPORT ServiceSkottieTransferCacheEntry
+    : public ServiceTransferCacheEntryBase<TransferCacheEntryType::kSkottie> {
+ public:
+  ServiceSkottieTransferCacheEntry();
+  ~ServiceSkottieTransferCacheEntry() final;
+
+  // ServiceTransferCacheEntry implementation:
+  size_t CachedSize() const final;
+  bool Deserialize(GrContext* context, base::span<const uint8_t> data) final;
+
+  const scoped_refptr<SkottieWrapper>& skottie() const { return skottie_; }
+
+ private:
+  scoped_refptr<SkottieWrapper> skottie_;
+  uint32_t cached_size_ = 0;
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_SKOTTIE_TRANSFER_CACHE_ENTRY_H_
diff --git a/cc/paint/skottie_transfer_cache_entry_unittest.cc b/cc/paint/skottie_transfer_cache_entry_unittest.cc
new file mode 100644
index 0000000..812c697
--- /dev/null
+++ b/cc/paint/skottie_transfer_cache_entry_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 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 <memory>
+
+#include "base/containers/span.h"
+#include "base/memory/scoped_refptr.h"
+#include "cc/paint/skottie_transfer_cache_entry.h"
+#include "cc/paint/skottie_wrapper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+// A skottie animation with solid green color for the first 2.5 seconds and then
+// a solid blue color for the next 2.5 seconds.
+constexpr char kData[] =
+    "{"
+    "  \"v\" : \"4.12.0\","
+    "  \"fr\": 30,"
+    "  \"w\" : 400,"
+    "  \"h\" : 200,"
+    "  \"ip\": 0,"
+    "  \"op\": 150,"
+    "  \"assets\": [],"
+
+    "  \"layers\": ["
+    "    {"
+    "      \"ty\": 1,"
+    "      \"sw\": 400,"
+    "      \"sh\": 200,"
+    "      \"sc\": \"#00ff00\","
+    "      \"ip\": 0,"
+    "      \"op\": 75"
+    "    },"
+    "    {"
+    "      \"ty\": 1,"
+    "      \"sw\": 400,"
+    "      \"sh\": 200,"
+    "      \"sc\": \"#0000ff\","
+    "      \"ip\": 76,"
+    "      \"op\": 150"
+    "    }"
+    "  ]"
+    "}";
+
+}  // namespace
+
+TEST(SkottieTransferCacheEntryTest, SerializationDeserialization) {
+  std::vector<uint8_t> a_data(std::strlen(kData));
+  a_data.assign(reinterpret_cast<const uint8_t*>(kData),
+                reinterpret_cast<const uint8_t*>(kData) + std::strlen(kData));
+
+  scoped_refptr<SkottieWrapper> skottie =
+      SkottieWrapper::CreateSerializable(std::move(a_data));
+
+  // Serialize
+  auto client_entry(std::make_unique<ClientSkottieTransferCacheEntry>(skottie));
+  uint32_t size = client_entry->SerializedSize();
+  std::vector<uint8_t> data(size);
+  ASSERT_TRUE(client_entry->Serialize(
+      base::make_span(static_cast<uint8_t*>(data.data()), size)));
+
+  // De-serialize
+  auto entry(std::make_unique<ServiceSkottieTransferCacheEntry>());
+  ASSERT_TRUE(entry->Deserialize(
+      nullptr, base::make_span(static_cast<uint8_t*>(data.data()), size)));
+
+  EXPECT_EQ(entry->skottie()->id(), skottie->id());
+  EXPECT_EQ(entry->skottie()->duration(), skottie->duration());
+  EXPECT_EQ(entry->skottie()->size(), skottie->size());
+}
+
+}  // namespace cc
diff --git a/cc/paint/skottie_wrapper.cc b/cc/paint/skottie_wrapper.cc
index ad5fb74..6656ce5c 100644
--- a/cc/paint/skottie_wrapper.cc
+++ b/cc/paint/skottie_wrapper.cc
@@ -3,27 +3,40 @@
 // found in the LICENSE file.
 
 #include "cc/paint/skottie_wrapper.h"
+#include <vector>
 
-#include "base/memory/ref_counted_memory.h"
+#include "base/hash/hash.h"
 #include "base/trace_event/trace_event.h"
-#include "third_party/skia/include/core/SkStream.h"
 
 namespace cc {
 
-SkottieWrapper::SkottieWrapper(
-    const scoped_refptr<base::RefCountedMemory>& data_stream) {
-  TRACE_EVENT0("cc", "SkottieWrapper Parse");
-  SkMemoryStream sk_stream(data_stream->front(), data_stream->size());
-  animation_ = skottie::Animation::Make(&sk_stream);
-  DCHECK(animation_);
+// static
+scoped_refptr<SkottieWrapper> SkottieWrapper::CreateSerializable(
+    std::vector<uint8_t> data) {
+  return base::WrapRefCounted<SkottieWrapper>(
+      new SkottieWrapper(std::move(data)));
 }
 
-SkottieWrapper::SkottieWrapper(std::unique_ptr<SkMemoryStream> stream)
-    : animation_(skottie::Animation::Make(stream.get())) {
-  DCHECK(animation_);
+// static
+scoped_refptr<SkottieWrapper> SkottieWrapper::CreateNonSerializable(
+    base::span<const uint8_t> data) {
+  return base::WrapRefCounted<SkottieWrapper>(new SkottieWrapper(data));
 }
 
-SkottieWrapper::~SkottieWrapper() {}
+SkottieWrapper::SkottieWrapper(base::span<const uint8_t> data)
+    : animation_(
+          skottie::Animation::Make(reinterpret_cast<const char*>(data.data()),
+                                   data.size())),
+      id_(base::FastHash(data)) {}
+
+SkottieWrapper::SkottieWrapper(std::vector<uint8_t> data)
+    : animation_(
+          skottie::Animation::Make(reinterpret_cast<const char*>(data.data()),
+                                   data.size())),
+      raw_data_(std::move(data)),
+      id_(base::FastHash(raw_data_)) {}
+
+SkottieWrapper::~SkottieWrapper() = default;
 
 void SkottieWrapper::Draw(SkCanvas* canvas, float t, const SkRect& rect) {
   base::AutoLock lock(lock_);
@@ -31,4 +44,9 @@
   animation_->render(canvas, &rect);
 }
 
+base::span<const uint8_t> SkottieWrapper::raw_data() const {
+  DCHECK(raw_data_.size());
+  return base::as_bytes(base::make_span(raw_data_.data(), raw_data_.size()));
+}
+
 }  // namespace cc
diff --git a/cc/paint/skottie_wrapper.h b/cc/paint/skottie_wrapper.h
index 7690059..3eae75a 100644
--- a/cc/paint/skottie_wrapper.h
+++ b/cc/paint/skottie_wrapper.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/containers/span.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
 #include "cc/paint/paint_export.h"
@@ -14,12 +15,6 @@
 #include "third_party/skia/modules/skottie/include/Skottie.h"
 
 class SkCanvas;
-class SkMemoryStream;
-
-namespace base {
-class RefCountedMemory;
-}  // namespace base
-
 namespace cc {
 
 // A wrapper over Skia's Skottie object that can be shared by multiple
@@ -28,13 +23,23 @@
 class CC_PAINT_EXPORT SkottieWrapper
     : public base::RefCountedThreadSafe<SkottieWrapper> {
  public:
-  explicit SkottieWrapper(
-      const scoped_refptr<base::RefCountedMemory>& data_stream);
-  explicit SkottieWrapper(std::unique_ptr<SkMemoryStream> stream);
+  // Creates an instance that can be serialized for IPC. This uses additional
+  // memory to store the raw animation data.
+  static scoped_refptr<SkottieWrapper> CreateSerializable(
+      std::vector<uint8_t> data);
+
+  // Creates a non serializable instance of the class. This uses less memory.
+  static scoped_refptr<SkottieWrapper> CreateNonSerializable(
+      base::span<const uint8_t> data);
+
   SkottieWrapper(const SkottieWrapper&) = delete;
 
   SkottieWrapper& operator=(const SkottieWrapper&) = delete;
 
+  // Returns true if the |animation_| object initialized is a valid skottie
+  // animation.
+  bool is_valid() const { return !!animation_; }
+
   // A thread safe call that will draw an image with bounds |rect| for the
   // frame at normalized time instant |t| onto the |canvas|.
   void Draw(SkCanvas* canvas, float t, const SkRect& rect);
@@ -42,12 +47,28 @@
   float duration() const { return animation_->duration(); }
   SkSize size() const { return animation_->size(); }
 
+  base::span<const uint8_t> raw_data() const;
+  uint32_t id() const { return id_; }
+
  private:
   friend class base::RefCountedThreadSafe<SkottieWrapper>;
+
+  explicit SkottieWrapper(base::span<const uint8_t> data);
+  explicit SkottieWrapper(std::vector<uint8_t> data);
+
   ~SkottieWrapper();
 
   base::Lock lock_;
   sk_sp<skottie::Animation> animation_;
+
+  // The raw byte data is stored for serialization across OOP-R. This is only
+  // valid if serialization was enabled at construction.
+  const std::vector<uint8_t> raw_data_;
+
+  // Unique id generated for a given animation. This will be unique per
+  // animation file. 2 animation objects from the same source file will have the
+  // same value.
+  const uint32_t id_;
 };
 
 }  // namespace cc
diff --git a/cc/paint/transfer_cache_entry.cc b/cc/paint/transfer_cache_entry.cc
index 4d519b6..47d4ae2 100644
--- a/cc/paint/transfer_cache_entry.cc
+++ b/cc/paint/transfer_cache_entry.cc
@@ -11,6 +11,10 @@
 #include "cc/paint/raw_memory_transfer_cache_entry.h"
 #include "cc/paint/shader_transfer_cache_entry.h"
 
+#ifndef OS_ANDROID
+#include "cc/paint/skottie_transfer_cache_entry.h"
+#endif
+
 namespace cc {
 
 std::unique_ptr<ServiceTransferCacheEntry> ServiceTransferCacheEntry::Create(
@@ -24,6 +28,12 @@
       // ServiceShader/TextBlobTransferCache is only created via
       // CreateLocalEntry and is never serialized/deserialized.
       return nullptr;
+    case TransferCacheEntryType::kSkottie:
+#ifndef OS_ANDROID
+      return std::make_unique<ServiceSkottieTransferCacheEntry>();
+#else
+      return nullptr;
+#endif
   }
 
   return nullptr;
@@ -44,6 +54,7 @@
   switch (type) {
     case TransferCacheEntryType::kRawMemory:
     case TransferCacheEntryType::kShader:
+    case TransferCacheEntryType::kSkottie:
       return false;
     case TransferCacheEntryType::kImage:
       return true;
diff --git a/cc/paint/transfer_cache_entry.h b/cc/paint/transfer_cache_entry.h
index ec5f6270..62a0179 100644
--- a/cc/paint/transfer_cache_entry.h
+++ b/cc/paint/transfer_cache_entry.h
@@ -23,8 +23,9 @@
   kRawMemory,
   kImage,
   kShader,
+  kSkottie,
   // Add new entries above this line, make sure to update kLast.
-  kLast = kShader,
+  kLast = kSkottie,
 };
 
 // An interface used on the client to serialize a transfer cache entry
diff --git a/cc/test/skia_common.cc b/cc/test/skia_common.cc
index c47065e..452c584f 100644
--- a/cc/test/skia_common.cc
+++ b/cc/test/skia_common.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 #include <string>
 
+#include "base/containers/span.h"
 #include "base/strings/string_number_conversions.h"
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/draw_image.h"
@@ -221,8 +222,8 @@
                  base::NumberToString(duration_secs * kFps));
   }
 
-  return base::MakeRefCounted<SkottieWrapper>(
-      std::make_unique<SkMemoryStream>(json.c_str(), json.size()));
+  return SkottieWrapper::CreateNonSerializable(
+      base::as_bytes(base::make_span(json)));
 }
 
 PaintImage CreateNonDiscardablePaintImage(const gfx::Size& size) {
diff --git a/ui/aura_extra/skia_vector_resource.cc b/ui/aura_extra/skia_vector_resource.cc
index fcb823b..940a8e0 100644
--- a/ui/aura_extra/skia_vector_resource.cc
+++ b/ui/aura_extra/skia_vector_resource.cc
@@ -56,19 +56,20 @@
 
   auto compressed_raw_data =
       rb.GetRawDataResourceForScale(resource_id, scale_factor_to_load);
-  auto* uncompressed_bytes = new base::RefCountedBytes(
+  std::vector<uint8_t> uncompressed_bytes(
       compression::GetUncompressedSize(compressed_raw_data));
   base::StringPiece uncompressed_str_piece(
-      reinterpret_cast<const char*>(uncompressed_bytes->front()),
-      uncompressed_bytes->size());
+      reinterpret_cast<char*>(uncompressed_bytes.data()),
+      uncompressed_bytes.size());
 
   TRACE_EVENT1("ui", "GetVectorAnimationNamed uncompress and parse",
-               "zip size bytes", uncompressed_bytes->size());
+               "zip size bytes", uncompressed_bytes.size());
   base::TimeTicks start_timestamp = base::TimeTicks::Now();
   CHECK(
       compression::GzipUncompress(compressed_raw_data, uncompressed_str_piece));
 
-  auto skottie = base::MakeRefCounted<cc::SkottieWrapper>(uncompressed_bytes);
+  auto skottie =
+      cc::SkottieWrapper::CreateSerializable(std::move(uncompressed_bytes));
 
   UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
       "UncompressAndParseSkiaVectorAsset",
diff --git a/ui/gfx/skia_vector_animation_unittest.cc b/ui/gfx/skia_vector_animation_unittest.cc
index 2f9d21d..013c069 100644
--- a/ui/gfx/skia_vector_animation_unittest.cc
+++ b/ui/gfx/skia_vector_animation_unittest.cc
@@ -102,8 +102,8 @@
   void SetUp() override {
     canvas_ = std::make_unique<gfx::Canvas>(
         gfx::Size(kAnimationWidth, kAnimationHeight), 1.f, false);
-    skottie_ = base::MakeRefCounted<cc::SkottieWrapper>(
-        std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
+    skottie_ = cc::SkottieWrapper::CreateNonSerializable(
+        base::as_bytes(base::make_span(kData, std::strlen(kData))));
     animation_ = std::make_unique<SkiaVectorAnimation>(skottie_);
   }
 
@@ -200,9 +200,8 @@
 };
 
 TEST_F(SkiaVectorAnimationTest, InitializationAndLoadingData) {
-  auto bytes = base::MakeRefCounted<base::RefCountedBytes>(
-      std::vector<unsigned char>(kData, kData + std::strlen(kData)));
-  skottie_ = base::MakeRefCounted<cc::SkottieWrapper>(bytes.get());
+  skottie_ = cc::SkottieWrapper::CreateNonSerializable(
+      base::as_bytes(base::make_span(kData, std::strlen(kData))));
   animation_ = std::make_unique<SkiaVectorAnimation>(skottie_);
   EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
   EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);
@@ -210,8 +209,8 @@
                   kAnimationDuration);
   EXPECT_TRUE(IsStopped());
 
-  skottie_ = base::MakeRefCounted<cc::SkottieWrapper>(
-      std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
+  skottie_ = cc::SkottieWrapper::CreateNonSerializable(
+      base::as_bytes(base::make_span(kData, std::strlen(kData))));
   animation_ = std::make_unique<SkiaVectorAnimation>(skottie_);
   EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
   EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);