[go: nahoru, domu]

Reland "Simple frame production"

This is a reland of commit 23a50951556c98fab168260ac37c32006fe546d1

Update OnBeginFrame call in tests to match new signature.
No other changes.

Original change's description:
> Simple frame production
>
> A simple frame production algorithm. Supports:
> * a single root render pass only
> * Layer position, transform
> * SetMaskToBounds for axis-aligned layer
>
> Add AppendQuads for SolidColorLayer as the simplest layer for unit
> testing.
>
> Notable missing features:
> * Non-axis aligned clip and filters (requires non-root pass)
> * Damage tracking
> * Quad occlusion culling
> * Visible rect on quads
>
> Add test harness to unit test frame generation.
>
> Bug: 1408128
> Change-Id: I5508292876ddc66b59b118742dbceacb2c6a7d8e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4245000
> Reviewed-by: Kyle Charbonneau <kylechar@chromium.org>
> Commit-Queue: Bo Liu <boliu@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1106868}

Bug: 1408128
Change-Id: Ie004e2d0542ce99f0a23c5420b844162e6788dd6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4265173
Auto-Submit: Bo Liu <boliu@chromium.org>
Commit-Queue: Bo Liu <boliu@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
Reviewed-by: Kyle Charbonneau <kylechar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1106946}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 48def44ab..98782bb2 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -784,6 +784,7 @@
     "resources/resource_pool_unittest.cc",
     "scheduler/scheduler_state_machine_unittest.cc",
     "scheduler/scheduler_unittest.cc",
+    "slim/slim_layer_tree_compositor_frame_unittest.cc",
     "slim/slim_layer_tree_unittest.cc",
     "slim/slim_layer_unittest.cc",
     "slim/test_frame_sink_impl.cc",
diff --git a/cc/slim/frame_sink_impl.cc b/cc/slim/frame_sink_impl.cc
index c83845b2..3efcf98 100644
--- a/cc/slim/frame_sink_impl.cc
+++ b/cc/slim/frame_sink_impl.cc
@@ -85,7 +85,8 @@
       base::BindOnce(&FrameSinkImpl::OnContextLost, base::Unretained(this)));
   client_receiver_.Bind(std::move(pending_client_receiver_), task_runner_);
 
-  frame_sink_remote_->InitializeCompositorFrameSinkType(
+  frame_sink_ = frame_sink_remote_.get();
+  frame_sink_->InitializeCompositorFrameSinkType(
       viz::mojom::CompositorFrameSinkType::kLayerTree);
 
 #if BUILDFLAG(IS_ANDROID)
@@ -94,7 +95,7 @@
   if (io_thread_id_ != base::kInvalidThreadId) {
     thread_ids.push_back(io_thread_id_);
   }
-  frame_sink_remote_->SetThreadIds(thread_ids);
+  frame_sink_->SetThreadIds(thread_ids);
 #endif
   return true;
 }
@@ -108,7 +109,7 @@
     return;
   }
   needs_begin_frame_ = needs_begin_frame;
-  frame_sink_remote_->SetNeedsBeginFrame(needs_begin_frame);
+  frame_sink_->SetNeedsBeginFrame(needs_begin_frame);
 }
 
 void FrameSinkImpl::UploadUIResource(cc::UIResourceId resource_id,
@@ -225,7 +226,7 @@
   }
 
   if (!local_surface_id_.is_valid()) {
-    frame_sink_remote_->DidNotProduceFrame(
+    frame_sink_->DidNotProduceFrame(
         viz::BeginFrameAck(begin_frame_args, false));
     return;
   }
@@ -235,11 +236,19 @@
   viz::HitTestRegionList hit_test_region_list;
   if (!client_->BeginFrame(begin_frame_args, frame, viz_resource_ids,
                            hit_test_region_list)) {
-    frame_sink_remote_->DidNotProduceFrame(
+    frame_sink_->DidNotProduceFrame(
         viz::BeginFrameAck(begin_frame_args, false));
     return;
   }
 
+  if (local_surface_id_ == last_submitted_local_surface_id_) {
+    DCHECK_EQ(last_submitted_device_scale_factor_, frame.device_scale_factor());
+    DCHECK_EQ(last_submitted_size_in_pixels_.height(),
+              frame.size_in_pixels().height());
+    DCHECK_EQ(last_submitted_size_in_pixels_.width(),
+              frame.size_in_pixels().width());
+  }
+
   resource_provider_.PrepareSendToParent(std::move(viz_resource_ids).extract(),
                                          &frame.resource_list,
                                          context_provider_.get());
@@ -254,7 +263,7 @@
 
   {
     TRACE_EVENT0("cc", "SubmitCompositorFrame");
-    frame_sink_remote_->SubmitCompositorFrame(
+    frame_sink_->SubmitCompositorFrame(
         local_surface_id_, std::move(frame),
         send_new_hit_test_region_list ? hit_test_region_list_ : absl::nullopt,
         0);
diff --git a/cc/slim/frame_sink_impl.h b/cc/slim/frame_sink_impl.h
index 61db3293..4d7c5ff 100644
--- a/cc/slim/frame_sink_impl.h
+++ b/cc/slim/frame_sink_impl.h
@@ -122,6 +122,8 @@
       pending_client_receiver_;
 
   mojo::AssociatedRemote<viz::mojom::CompositorFrameSink> frame_sink_remote_;
+  // Separate from AssociatedRemote above for testing.
+  viz::mojom::CompositorFrameSink* frame_sink_ = nullptr;
   mojo::Receiver<viz::mojom::CompositorFrameSinkClient> client_receiver_{this};
   scoped_refptr<viz::ContextProvider> context_provider_;
   raw_ptr<FrameSinkImplClient> client_ = nullptr;
@@ -133,6 +135,10 @@
   absl::optional<viz::HitTestRegionList> hit_test_region_list_;
   base::PlatformThreadId io_thread_id_;
 
+  viz::LocalSurfaceId last_submitted_local_surface_id_;
+  float last_submitted_device_scale_factor_ = 1.f;
+  gfx::Size last_submitted_size_in_pixels_;
+
   bool needs_begin_frame_ = false;
 };
 
diff --git a/cc/slim/layer.cc b/cc/slim/layer.cc
index 4ccb227..e226867 100644
--- a/cc/slim/layer.cc
+++ b/cc/slim/layer.cc
@@ -11,7 +11,6 @@
 #include "base/atomic_sequence_num.h"
 #include "base/check.h"
 #include "base/containers/cxx20_erase_vector.h"
-#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "cc/layers/layer.h"
 #include "cc/paint/filter_operation.h"
@@ -20,7 +19,6 @@
 #include "cc/slim/layer_tree.h"
 #include "cc/slim/layer_tree_impl.h"
 #include "components/viz/common/quads/shared_quad_state.h"
-#include "components/viz/common/quads/solid_color_draw_quad.h"
 
 namespace cc::slim {
 
@@ -58,7 +56,7 @@
 
 Layer::Layer(scoped_refptr<cc::Layer> cc_layer)
     : cc_layer_(std::move(cc_layer)),
-      id_(g_next_id.GetNext()),
+      id_(g_next_id.GetNext() + 1),
       is_drawable_(false),
       contents_opaque_(false),
       draws_content_(false),
@@ -375,6 +373,23 @@
   return is_drawable_;
 }
 
+gfx::Transform Layer::ComputeTransformToParent() {
+  // Layer transform is:
+  // position x transform_origin x transform x -transform_origin
+  gfx::Transform transform =
+      gfx::Transform::MakeTranslation(position_.x(), position_.y());
+  transform.Translate3d(transform_origin_.x(), transform_origin_.y(),
+                        transform_origin_.z());
+  transform.PreConcat(transform_);
+  transform.Translate3d(-transform_origin_.x(), -transform_origin_.y(),
+                        -transform_origin_.z());
+  return transform;
+}
+
+void Layer::AppendQuads(viz::CompositorRenderPass& render_pass,
+                        const gfx::Transform& transform,
+                        const gfx::Rect* clip) {}
+
 void Layer::NotifyTreeChanged() {
   if (cc_layer()) {
     return;
@@ -391,4 +406,23 @@
   }
 }
 
+viz::SharedQuadState* Layer::CreateAndAppendSharedQuadState(
+    viz::CompositorRenderPass& render_pass,
+    const gfx::Transform& transform,
+    const gfx::Rect* clip) {
+  viz::SharedQuadState* quad_state =
+      render_pass.CreateAndAppendSharedQuadState();
+  const gfx::Rect rect{bounds()};
+  absl::optional<gfx::Rect> clip_opt;
+  if (clip) {
+    clip_opt = *clip;
+  }
+  // TODO(crbug.com/1408128): Set visible_layer_rect properly.
+  quad_state->SetAll(transform, /*layer_rect=*/rect,
+                     /*visible_layer_rect=*/rect, gfx::MaskFilterInfo(),
+                     std::move(clip_opt), contents_opaque(), opacity(),
+                     SkBlendMode::kSrcOver, 0);
+  return quad_state;
+}
+
 }  // namespace cc::slim
diff --git a/cc/slim/layer.h b/cc/slim/layer.h
index e0c64bc..cd9b1774 100644
--- a/cc/slim/layer.h
+++ b/cc/slim/layer.h
@@ -23,10 +23,16 @@
 class Layer;
 }
 
+namespace viz {
+class CompositorRenderPass;
+class SharedQuadState;
+}  // namespace viz
+
 namespace cc::slim {
 
 class LayerTree;
 class LayerTreeCcWrapper;
+class LayerTreeImpl;
 
 // Base class for composited layers. Special layer types are derived from
 // this class. Each layer is an independent unit in the compositor, be that
@@ -178,15 +184,24 @@
 
  protected:
   friend class LayerTreeCcWrapper;
+  friend class LayerTreeImpl;
 
   explicit Layer(scoped_refptr<cc::Layer> cc_layer);
   virtual ~Layer();
 
   // Called by LayerTree.
+  gfx::Transform ComputeTransformToParent();
   virtual bool HasDrawableContent() const;
+  virtual void AppendQuads(viz::CompositorRenderPass& render_pass,
+                           const gfx::Transform& transform,
+                           const gfx::Rect* clip);
 
   void NotifyTreeChanged();
   void NotifyPropertyChanged();
+  virtual viz::SharedQuadState* CreateAndAppendSharedQuadState(
+      viz::CompositorRenderPass& render_pass,
+      const gfx::Transform& transform,
+      const gfx::Rect* clip);
 
   const scoped_refptr<cc::Layer> cc_layer_;
 
diff --git a/cc/slim/layer_tree_impl.cc b/cc/slim/layer_tree_impl.cc
index b0c3821d..6a849b2 100644
--- a/cc/slim/layer_tree_impl.cc
+++ b/cc/slim/layer_tree_impl.cc
@@ -6,15 +6,24 @@
 
 #include <algorithm>
 #include <memory>
+#include <vector>
 
 #include "base/auto_reset.h"
+#include "base/containers/adapters.h"
+#include "base/trace_event/trace_event.h"
 #include "cc/slim/frame_sink_impl.h"
 #include "cc/slim/layer.h"
 #include "cc/slim/layer_tree_client.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/hit_test/hit_test_region_list.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/quads/compositor_frame_metadata.h"
+#include "components/viz/common/quads/compositor_render_pass.h"
+#include "components/viz/common/quads/draw_quad.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/transform.h"
 
 namespace cc::slim {
 
@@ -76,7 +85,7 @@
 }
 
 void LayerTreeImpl::set_display_transform_hint(gfx::OverlayTransform hint) {
-  // TODO(crbug.com/1408128): Implement.
+  display_transform_hint_ = hint;
 }
 
 void LayerTreeImpl::RequestCopyOfOutput(
@@ -182,9 +191,16 @@
   // changes made by client `BeginFrame` are about to be drawn, so there is no
   // need for another frame.
   needs_draw_ = false;
-  // TODO(crbug.com/1408128): Implement frame production here.
+
+  if (!root_) {
+    UpdateNeedsBeginFrame();
+    return false;
+  }
+
+  GenerateCompositorFrame(args, out_frame, out_resource_ids,
+                          out_hit_test_region_list);
   UpdateNeedsBeginFrame();
-  return false;
+  return true;
 }
 
 void LayerTreeImpl::DidReceiveCompositorFrameAck() {
@@ -262,4 +278,88 @@
   return client_needs_one_begin_frame_ || needs_draw_;
 }
 
+void LayerTreeImpl::GenerateCompositorFrame(
+    const viz::BeginFrameArgs& args,
+    viz::CompositorFrame& out_frame,
+    base::flat_set<viz::ResourceId>& out_resource_ids,
+    viz::HitTestRegionList& out_hit_test_region_list) {
+  // TODO(crbug.com/1408128): Only has a very simple and basic compositor frame
+  // generation. Some missing features include:
+  // * Support multiple render passes (non-axis aligned clip, filters)
+  // * Damage tracking
+  // * Occlusion culling
+  // * Visible rect (ie clip) on quads
+  // * Surface embedding fields (referenced surfaces, activation dependency,
+  //   deadline)
+  TRACE_EVENT0("cc", "slim::LayerTreeImpl::ProduceFrame");
+  auto render_pass = viz::CompositorRenderPass::Create();
+  render_pass->SetNew(viz::CompositorRenderPassId(root_->id()),
+                      /*output_rect=*/device_viewport_rect_,
+                      /*damage_rect=*/device_viewport_rect_,
+                      /*transform_to_root_target=*/gfx::Transform());
+
+  out_frame.metadata.frame_token = ++next_frame_token_;
+  out_frame.metadata.begin_frame_ack =
+      viz::BeginFrameAck(args, /*has_damage=*/true);
+  out_frame.metadata.device_scale_factor = device_scale_factor_;
+  out_frame.metadata.root_background_color = background_color_;
+  out_frame.metadata.referenced_surfaces = std::vector<viz::SurfaceRange>(
+      referenced_surfaces_.begin(), referenced_surfaces_.end());
+  out_frame.metadata.top_controls_visible_height = top_controls_visible_height_;
+  top_controls_visible_height_.reset();
+  out_frame.metadata.display_transform_hint = display_transform_hint_;
+
+  Draw(*root_, *render_pass, /*transform_to_target=*/gfx::Transform(),
+       /*clip_from_parent=*/nullptr);
+
+  out_frame.render_pass_list.push_back(std::move(render_pass));
+
+  for (const auto& pass : out_frame.render_pass_list) {
+    for (const auto* quad : pass->quad_list) {
+      for (viz::ResourceId resource_id : quad->resources) {
+        out_resource_ids.insert(resource_id);
+      }
+    }
+  }
+}
+
+void LayerTreeImpl::Draw(Layer& layer,
+                         viz::CompositorRenderPass& parent_pass,
+                         const gfx::Transform& transform_to_target,
+                         const gfx::Rect* clip_from_parent) {
+  if (layer.hide_layer_and_subtree()) {
+    return;
+  }
+
+  gfx::Transform transform_to_parent = layer.ComputeTransformToParent();
+
+  // New transform is: parent transform x layer transform.
+  gfx::Transform new_transform_to_target = transform_to_target;
+  new_transform_to_target.PreConcat(transform_to_parent);
+
+  bool use_new_clip = false;
+  gfx::Rect new_clip;
+  // Drop non-axis aligned clip instead of using new render pass.
+  // TODO(crbug.com/1408128): Clip in layer space (visible rect) for clip
+  // that is not an exact integer.
+  if (layer.masks_to_bounds() &&
+      new_transform_to_target.Preserves2dAxisAlignment()) {
+    new_clip.set_size(layer.bounds());
+    new_clip = new_transform_to_target.MapRect(new_clip);
+    if (clip_from_parent) {
+      new_clip.Intersect(*clip_from_parent);
+    }
+    use_new_clip = true;
+  }
+  const gfx::Rect* clip = use_new_clip ? &new_clip : clip_from_parent;
+
+  for (auto& child : base::Reversed(layer.children())) {
+    Draw(*child, parent_pass, new_transform_to_target, clip);
+  }
+
+  if (!layer.bounds().IsEmpty() && layer.HasDrawableContent()) {
+    layer.AppendQuads(parent_pass, new_transform_to_target, clip);
+  }
+}
+
 }  // namespace cc::slim
diff --git a/cc/slim/layer_tree_impl.h b/cc/slim/layer_tree_impl.h
index 8c3809e..58e40a6 100644
--- a/cc/slim/layer_tree_impl.h
+++ b/cc/slim/layer_tree_impl.h
@@ -32,6 +32,10 @@
 class UIResourceManager;
 }  // namespace cc
 
+namespace viz {
+class CompositorRenderPass;
+}  // namespace viz
+
 namespace cc::slim {
 
 class FrameSinkImpl;
@@ -104,6 +108,15 @@
   // submitted in a CompositorFrame.
   void SetNeedsDraw();
   bool NeedsBeginFrames() const;
+  void GenerateCompositorFrame(
+      const viz::BeginFrameArgs& args,
+      viz::CompositorFrame& out_frame,
+      base::flat_set<viz::ResourceId>& out_resource_ids,
+      viz::HitTestRegionList& out_hit_test_region_list);
+  void Draw(Layer& layer,
+            viz::CompositorRenderPass& render_pass,
+            const gfx::Transform& transform_to_target,
+            const gfx::Rect* clip_from_parent);
 
   const raw_ptr<LayerTreeClient> client_;
   scoped_refptr<Layer> root_;
@@ -132,6 +145,8 @@
   SkColor4f background_color_ = SkColors::kWhite;
   absl::optional<float> top_controls_visible_height_;
   base::flat_set<viz::SurfaceRange> referenced_surfaces_;
+  viz::FrameTokenGenerator next_frame_token_;
+  gfx::OverlayTransform display_transform_hint_ = gfx::OVERLAY_TRANSFORM_NONE;
 
   base::WeakPtrFactory<LayerTreeImpl> weak_factory_{this};
 };
diff --git a/cc/slim/slim_layer_tree_compositor_frame_unittest.cc b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
new file mode 100644
index 0000000..8fe76a85
--- /dev/null
+++ b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
@@ -0,0 +1,310 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/weak_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
+#include "base/unguessable_token.h"
+#include "cc/slim/features.h"
+#include "cc/slim/layer.h"
+#include "cc/slim/solid_color_layer.h"
+#include "cc/slim/test_frame_sink_impl.h"
+#include "cc/slim/test_layer_tree_client.h"
+#include "cc/slim/test_layer_tree_impl.h"
+#include "components/viz/common/frame_sinks/begin_frame_args.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/test/draw_quad_matchers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace cc::slim {
+
+namespace {
+
+using testing::AllOf;
+using testing::ElementsAre;
+
+class SlimLayerTreeCompositorFrameTest : public testing::Test {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kSlimCompositor);
+    layer_tree_ = std::make_unique<TestLayerTreeImpl>(&client_);
+    layer_tree_->SetVisible(true);
+
+    auto frame_sink = TestFrameSinkImpl::Create();
+    frame_sink_ = frame_sink->GetWeakPtr();
+    layer_tree_->SetFrameSink(std::move(frame_sink));
+
+    viewport_ = gfx::Rect(100, 100);
+    base::UnguessableToken token = base::UnguessableToken::Create();
+    local_surface_id_ = viz::LocalSurfaceId(1u, 2u, token);
+    EXPECT_TRUE(local_surface_id_.is_valid());
+    layer_tree_->SetViewportRectAndScale(
+        viewport_, /*device_scale_factor=*/1.0f, local_surface_id_);
+  }
+
+  void IncrementLocalSurfaceId() {
+    DCHECK(local_surface_id_.is_valid());
+    local_surface_id_ =
+        viz::LocalSurfaceId(local_surface_id_.parent_sequence_number(),
+                            local_surface_id_.child_sequence_number() + 1,
+                            local_surface_id_.embed_token());
+    DCHECK(local_surface_id_.is_valid());
+  }
+
+  viz::CompositorFrame ProduceFrame() {
+    layer_tree_->SetNeedsRedraw();
+    EXPECT_TRUE(layer_tree_->NeedsBeginFrames());
+    base::TimeTicks frame_time = base::TimeTicks::Now();
+    base::TimeDelta interval = viz::BeginFrameArgs::DefaultInterval();
+    viz::BeginFrameArgs begin_frame_args = viz::BeginFrameArgs::Create(
+        BEGINFRAME_FROM_HERE,
+        /*source_id=*/1, ++sequence_id_, frame_time, frame_time + interval,
+        interval, viz::BeginFrameArgs::NORMAL);
+    frame_sink_->OnBeginFrame(begin_frame_args, {}, /*frame_ack=*/false, {});
+    viz::CompositorFrame frame = frame_sink_->TakeLastFrame();
+    frame_sink_->DidReceiveCompositorFrameAck({});
+    return frame;
+  }
+
+  scoped_refptr<SolidColorLayer> CreateSolidColorLayer(const gfx::Size& bounds,
+                                                       SkColor4f color) {
+    auto solid_color_layer = SolidColorLayer::Create();
+    solid_color_layer->SetBounds(bounds);
+    solid_color_layer->SetBackgroundColor(color);
+    solid_color_layer->SetIsDrawable(true);
+    return solid_color_layer;
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestLayerTreeClient client_;
+  std::unique_ptr<TestLayerTreeImpl> layer_tree_;
+  base::WeakPtr<TestFrameSinkImpl> frame_sink_;
+
+  uint64_t sequence_id_ = 0;
+
+  gfx::Rect viewport_;
+  viz::LocalSurfaceId local_surface_id_;
+};
+
+TEST_F(SlimLayerTreeCompositorFrameTest, CompositorFrameMetadataBasics) {
+  auto solid_color_layer =
+      CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(solid_color_layer);
+
+  // TODO(crbug.com/1408128): Add tests for features once implemented:
+  // * reference_surfaces
+  // * activation_dependencies
+  // * deadline
+  uint32_t first_frame_token = 0u;
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    viz::CompositorFrameMetadata& metadata = frame.metadata;
+    EXPECT_NE(0u, metadata.frame_token);
+    first_frame_token = metadata.frame_token;
+    EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
+    EXPECT_EQ(1.0f, metadata.device_scale_factor);
+    EXPECT_EQ(SkColors::kWhite, metadata.root_background_color);
+    EXPECT_EQ(gfx::OVERLAY_TRANSFORM_NONE, metadata.display_transform_hint);
+    EXPECT_EQ(absl::nullopt, metadata.top_controls_visible_height);
+  }
+
+  IncrementLocalSurfaceId();
+  layer_tree_->SetViewportRectAndScale(viewport_, /*device_scale_factor=*/2.0f,
+                                       local_surface_id_);
+  layer_tree_->set_background_color(SkColors::kBlue);
+  layer_tree_->set_display_transform_hint(gfx::OVERLAY_TRANSFORM_ROTATE_90);
+  layer_tree_->UpdateTopControlsVisibleHeight(5.0f);
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    viz::CompositorFrameMetadata& metadata = frame.metadata;
+    EXPECT_NE(0u, metadata.frame_token);
+    EXPECT_NE(first_frame_token, metadata.frame_token);
+    EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
+    EXPECT_EQ(2.0f, metadata.device_scale_factor);
+    EXPECT_EQ(SkColors::kBlue, metadata.root_background_color);
+    EXPECT_EQ(gfx::OVERLAY_TRANSFORM_ROTATE_90,
+              metadata.display_transform_hint);
+    EXPECT_EQ(5.0f, metadata.top_controls_visible_height);
+  }
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, OneSolidColorQuad) {
+  auto solid_color_layer =
+      CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(solid_color_layer);
+
+  viz::CompositorFrame frame = ProduceFrame();
+
+  ASSERT_EQ(frame.render_pass_list.size(), 1u);
+  auto& pass = frame.render_pass_list.back();
+  EXPECT_EQ(pass->output_rect, viewport_);
+  EXPECT_EQ(pass->damage_rect, viewport_);
+  EXPECT_EQ(pass->transform_to_root_target, gfx::Transform());
+
+  ASSERT_THAT(
+      pass->quad_list,
+      ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
+                        viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
+                        viz::HasTransform(gfx::Transform()))));
+  auto* quad = pass->quad_list.back();
+  auto* shared_quad_state = quad->shared_quad_state;
+
+  EXPECT_EQ(shared_quad_state->quad_layer_rect, viewport_);
+  EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, viewport_);
+  EXPECT_EQ(shared_quad_state->clip_rect, absl::nullopt);
+  EXPECT_EQ(shared_quad_state->are_contents_opaque, true);
+  EXPECT_EQ(shared_quad_state->opacity, 1.0f);
+  EXPECT_EQ(shared_quad_state->blend_mode, SkBlendMode::kSrcOver);
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, LayerTransform) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  auto child = CreateSolidColorLayer(gfx::Size(10, 20), SkColors::kGreen);
+  root_layer->AddChild(child);
+
+  auto check_child_quad = [&](gfx::Rect expected_rect_in_root) {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
+                                  viz::HasRect(gfx::Rect(10, 20)),
+                                  viz::HasVisibleRect(gfx::Rect(10, 20))),
+                            AllOf(viz::IsSolidColorQuad(SkColors::kGray),
+                                  viz::HasRect(viewport_),
+                                  viz::HasVisibleRect(viewport_))));
+
+    auto* quad = pass->quad_list.front();
+    auto* shared_quad_state = quad->shared_quad_state;
+
+    EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(10, 20));
+    EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(10, 20));
+
+    gfx::Rect rect_in_root =
+        shared_quad_state->quad_to_target_transform.MapRect(quad->rect);
+    EXPECT_EQ(expected_rect_in_root, rect_in_root);
+  };
+
+  child->SetPosition(gfx::PointF(30.0f, 30.0f));
+  check_child_quad(gfx::Rect(30, 30, 10, 20));
+
+  child->SetTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f));
+  check_child_quad(gfx::Rect(40, 40, 10, 20));
+
+  // Rotate about top left corner.
+  child->SetTransform(gfx::Transform::Make90degRotation());
+  check_child_quad(gfx::Rect(10, 30, 20, 10));
+
+  // Rotate about the center.
+  child->SetTransformOrigin(gfx::Point3F(5.0f, 10.0f, 0.0f));
+  check_child_quad(gfx::Rect(25, 35, 20, 10));
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, ChildOrder) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  scoped_refptr<SolidColorLayer> children[] = {
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kBlue),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kGreen),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kMagenta),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kRed),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kYellow)};
+
+  // Build tree such that quads appear in child order.
+  // Quads are appended post order depth first, in reverse child order.
+  // root <- child4 <- child3
+  //                <- child2
+  //      <- child1 <- child0
+  root_layer->AddChild(children[4]);
+  root_layer->AddChild(children[1]);
+  children[4]->AddChild(children[3]);
+  children[4]->AddChild(children[2]);
+  children[1]->AddChild(children[0]);
+
+  // Add offsets so they do not cover each other.
+  children[3]->SetPosition(gfx::PointF(10.0f, 10.0f));
+  children[2]->SetPosition(gfx::PointF(20.0f, 20.0f));
+  children[1]->SetPosition(gfx::PointF(30.0f, 30.0f));
+  children[0]->SetPosition(gfx::PointF(10.0f, 10.0f));
+
+  gfx::Point expected_origins[] = {
+      gfx::Point(40.0f, 40.0f), gfx::Point(30.0f, 30.0f),
+      gfx::Point(20.0f, 20.0f), gfx::Point(10.0f, 10.0f),
+      gfx::Point(00.0f, 00.0f)};
+
+  viz::CompositorFrame frame = ProduceFrame();
+  ASSERT_EQ(frame.render_pass_list.size(), 1u);
+  auto& pass = frame.render_pass_list.back();
+  ASSERT_THAT(pass->quad_list,
+              ElementsAre(viz::IsSolidColorQuad(SkColors::kBlue),
+                          viz::IsSolidColorQuad(SkColors::kGreen),
+                          viz::IsSolidColorQuad(SkColors::kMagenta),
+                          viz::IsSolidColorQuad(SkColors::kRed),
+                          viz::IsSolidColorQuad(SkColors::kYellow),
+                          viz::IsSolidColorQuad(SkColors::kGray)));
+
+  for (size_t i = 0; i < std::size(expected_origins); ++i) {
+    auto* quad = pass->quad_list.ElementAt(i);
+    EXPECT_EQ(quad->shared_quad_state->quad_to_target_transform.MapPoint(
+                  gfx::Point()),
+              expected_origins[i]);
+  }
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, AxisAlignedClip) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  auto clip_layer = Layer::Create();
+  clip_layer->SetBounds(gfx::Size(10, 20));
+  clip_layer->SetMasksToBounds(true);
+
+  auto draw_layer = CreateSolidColorLayer(gfx::Size(30, 30), SkColors::kRed);
+
+  root_layer->AddChild(clip_layer);
+  clip_layer->AddChild(draw_layer);
+
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
+                            viz::IsSolidColorQuad(SkColors::kGray)));
+
+    auto* quad = pass->quad_list.front();
+    ASSERT_TRUE(quad->shared_quad_state->clip_rect);
+    EXPECT_EQ(quad->shared_quad_state->clip_rect.value(), gfx::Rect(10, 20));
+  }
+
+  clip_layer->SetPosition(gfx::PointF(5, 5));
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
+                            viz::IsSolidColorQuad(SkColors::kGray)));
+
+    auto* quad = pass->quad_list.front();
+    ASSERT_TRUE(quad->shared_quad_state->clip_rect);
+    // Clip is in target space.
+    EXPECT_EQ(quad->shared_quad_state->clip_rect.value(),
+              gfx::Rect(5, 5, 10, 20));
+  }
+}
+
+}  // namespace
+
+}  // namespace cc::slim
diff --git a/cc/slim/solid_color_layer.cc b/cc/slim/solid_color_layer.cc
index dc4e6f5..ece3586 100644
--- a/cc/slim/solid_color_layer.cc
+++ b/cc/slim/solid_color_layer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "cc/layers/solid_color_layer.h"
+#include "cc/slim/features.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 
@@ -15,7 +16,9 @@
 // static
 scoped_refptr<SolidColorLayer> SolidColorLayer::Create() {
   scoped_refptr<cc::SolidColorLayer> cc_layer;
-  cc_layer = cc::SolidColorLayer::Create();
+  if (!features::IsSlimCompositorEnabled()) {
+    cc_layer = cc::SolidColorLayer::Create();
+  }
   return base::AdoptRef(new SolidColorLayer(std::move(cc_layer)));
 }
 
@@ -29,7 +32,24 @@
 }
 
 void SolidColorLayer::SetBackgroundColor(SkColor4f color) {
-  cc_layer()->SetBackgroundColor(color);
+  if (cc_layer()) {
+    cc_layer()->SetBackgroundColor(color);
+    return;
+  }
+  SetContentsOpaque(color.isOpaque());
+  Layer::SetBackgroundColor(color);
+}
+
+void SolidColorLayer::AppendQuads(viz::CompositorRenderPass& render_pass,
+                                  const gfx::Transform& transform,
+                                  const gfx::Rect* clip) {
+  viz::SharedQuadState* quad_state =
+      CreateAndAppendSharedQuadState(render_pass, transform, clip);
+  viz::SolidColorDrawQuad* quad =
+      render_pass.CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
+  quad->SetNew(quad_state, quad_state->quad_layer_rect,
+               quad_state->visible_quad_layer_rect, background_color(),
+               /*anti_aliasing_off=*/true);
 }
 
 }  // namespace cc::slim
diff --git a/cc/slim/solid_color_layer.h b/cc/slim/solid_color_layer.h
index c3c91b5..abbc41d 100644
--- a/cc/slim/solid_color_layer.h
+++ b/cc/slim/solid_color_layer.h
@@ -26,6 +26,10 @@
   explicit SolidColorLayer(scoped_refptr<cc::SolidColorLayer> cc_layer);
   ~SolidColorLayer() override;
 
+  void AppendQuads(viz::CompositorRenderPass& render_pass,
+                   const gfx::Transform& transform,
+                   const gfx::Rect* clip) override;
+
   cc::SolidColorLayer* cc_layer() const;
 };
 
diff --git a/cc/slim/test_frame_sink_impl.cc b/cc/slim/test_frame_sink_impl.cc
index 67608ef..e764043 100644
--- a/cc/slim/test_frame_sink_impl.cc
+++ b/cc/slim/test_frame_sink_impl.cc
@@ -6,8 +6,11 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/task/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/test/test_context_provider.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
@@ -16,6 +19,41 @@
 
 namespace cc::slim {
 
+class TestFrameSinkImpl::TestMojoCompositorFrameSink
+    : public viz::mojom::CompositorFrameSink {
+ public:
+  TestMojoCompositorFrameSink() = default;
+  void SetNeedsBeginFrame(bool needs_begin_frame) override {}
+  void SetWantsAnimateOnlyBeginFrames() override {}
+  void SubmitCompositorFrame(
+      const viz::LocalSurfaceId& local_surface_id,
+      viz::CompositorFrame frame,
+      absl::optional<::viz::HitTestRegionList> hit_test_region_list,
+      uint64_t submit_time) override {
+    last_frame_ = std::move(frame);
+  }
+  void SubmitCompositorFrameSync(
+      const viz::LocalSurfaceId& local_surface_id,
+      viz::CompositorFrame frame,
+      absl::optional<::viz::HitTestRegionList> hit_test_region_list,
+      uint64_t submit_time,
+      SubmitCompositorFrameSyncCallback callback) override {}
+  void DidNotProduceFrame(const viz::BeginFrameAck& ack) override {}
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
+                               const gpu::Mailbox& id) override {}
+  void DidDeleteSharedBitmap(const gpu::Mailbox& id) override {}
+  void InitializeCompositorFrameSinkType(
+      viz::mojom::CompositorFrameSinkType type) override {}
+#if BUILDFLAG(IS_ANDROID)
+  void SetThreadIds(const std::vector<int32_t>& thread_ids) override {}
+#endif
+
+  viz::CompositorFrame TakeLastFrame() { return std::move(last_frame_); }
+
+ private:
+  viz::CompositorFrame last_frame_;
+};
+
 // static
 std::unique_ptr<TestFrameSinkImpl> TestFrameSinkImpl::Create() {
   auto task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
@@ -44,13 +82,18 @@
                     std::move(client_receiver),
                     std::move(context_provider),
                     base::kInvalidThreadId),
-      pending_sink_receiver_(std::move(sink_receiver)) {}
+      mojo_sink_(std::make_unique<TestMojoCompositorFrameSink>()) {}
 
 TestFrameSinkImpl::~TestFrameSinkImpl() = default;
 
+viz::CompositorFrame TestFrameSinkImpl::TakeLastFrame() {
+  return mojo_sink_->TakeLastFrame();
+}
+
 bool TestFrameSinkImpl::BindToClient(FrameSinkImplClient* client) {
   DCHECK(!bind_to_client_called_);
   client_ = client;
+  frame_sink_ = mojo_sink_.get();
   bind_to_client_called_ = true;
   return bind_to_client_result_;
 }
diff --git a/cc/slim/test_frame_sink_impl.h b/cc/slim/test_frame_sink_impl.h
index a1a5b725..e74ac32 100644
--- a/cc/slim/test_frame_sink_impl.h
+++ b/cc/slim/test_frame_sink_impl.h
@@ -24,6 +24,7 @@
     DCHECK(!bind_to_client_called_);
     bind_to_client_result_ = result;
   }
+  viz::CompositorFrame TakeLastFrame();
   bool bind_to_client_called() const { return bind_to_client_called_; }
   bool needs_begin_frames() const { return needs_begin_frames_; }
 
@@ -32,6 +33,7 @@
   void SetNeedsBeginFrame(bool needs_begin_frame) override;
 
  private:
+  class TestMojoCompositorFrameSink;
   TestFrameSinkImpl(
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink>
@@ -42,8 +44,7 @@
       mojo::PendingAssociatedReceiver<viz::mojom::CompositorFrameSink>
           sink_receiver);
 
-  mojo::PendingAssociatedReceiver<viz::mojom::CompositorFrameSink>
-      pending_sink_receiver_;
+  std::unique_ptr<TestMojoCompositorFrameSink> mojo_sink_;
 
   bool bind_to_client_called_ = false;
   bool bind_to_client_result_ = true;