Revert "fuchsia: Remove dead code / break dependencies (#19396)" (#20302) (#20327)

This reverts commit 12a37478de2cc9aff7f8fc72bc3a47f5f02e083c.

Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc
index 2a51590..795946e 100644
--- a/flow/layers/child_scene_layer.cc
+++ b/flow/layers/child_scene_layer.cc
@@ -4,6 +4,8 @@
 
 #include "flutter/flow/layers/child_scene_layer.h"
 
+#include "flutter/flow/view_holder.h"
+
 namespace flutter {
 
 ChildSceneLayer::ChildSceneLayer(zx_koid_t layer_id,
@@ -17,9 +19,11 @@
 
 void ChildSceneLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
   TRACE_EVENT0("flutter", "ChildSceneLayer::Preroll");
+  set_needs_system_composite(true);
+
+  CheckForChildLayerBelow(context);
 
   context->child_scene_layer_exists_below = true;
-  CheckForChildLayerBelow(context);
 
   // An alpha "hole punch" is required if the frame behind us is not opaque.
   if (!context->is_opaque) {
@@ -45,7 +49,15 @@
 void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) {
   TRACE_EVENT0("flutter", "ChildSceneLayer::UpdateScene");
   FML_DCHECK(needs_system_composite());
-  context.UpdateView(layer_id_, offset_, size_, hit_testable_);
+
+  Layer::UpdateScene(context);
+
+  auto* view_holder = ViewHolder::FromId(layer_id_);
+  FML_DCHECK(view_holder);
+
+  view_holder->UpdateScene(context, offset_, size_,
+                           SkScalarRoundToInt(context.alphaf() * 255),
+                           hit_testable_);
 }
 
 }  // namespace flutter
diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc
index 825826b..d8bf8ed 100644
--- a/flow/layers/container_layer.cc
+++ b/flow/layers/container_layer.cc
@@ -4,8 +4,6 @@
 
 #include "flutter/flow/layers/container_layer.h"
 
-#include <optional>
-
 namespace flutter {
 
 ContainerLayer::ContainerLayer() {}
@@ -32,9 +30,6 @@
                                      const SkMatrix& child_matrix,
                                      SkRect* child_paint_bounds) {
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
-  // If there is embedded Fuchsia content in the scene (a ChildSceneLayer),
-  // Layers that appear above the embedded content will be turned into their own
-  // Scenic layers.
   child_layer_exists_below_ = context->child_scene_layer_exists_below;
   context->child_scene_layer_exists_below = false;
 #endif
@@ -103,20 +98,63 @@
 }
 
 void ContainerLayer::UpdateSceneChildren(SceneUpdateContext& context) {
+  auto update_scene_layers = [&] {
+    // Paint all of the layers which need to be drawn into the container.
+    // These may be flattened down to a containing Scenic Frame.
+    for (auto& layer : layers_) {
+      if (layer->needs_system_composite()) {
+        layer->UpdateScene(context);
+      }
+    }
+  };
+
   FML_DCHECK(needs_system_composite());
 
-  std::optional<SceneUpdateContext::Frame> frame;
+  // If there is embedded Fuchsia content in the scene (a ChildSceneLayer),
+  // PhysicalShapeLayers that appear above the embedded content will be turned
+  // into their own Scenic layers.
   if (child_layer_exists_below_) {
-    frame.emplace(
-        context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT,
-        SkScalarRoundToInt(context.alphaf() * 255), "flutter::ContainerLayer");
-    frame->AddPaintLayer(this);
-  }
+    float global_scenic_elevation =
+        context.GetGlobalElevationForNextScenicLayer();
+    float local_scenic_elevation =
+        global_scenic_elevation - context.scenic_elevation();
+    float z_translation = -local_scenic_elevation;
 
-  for (auto& layer : layers_) {
-    if (layer->needs_system_composite()) {
-      layer->UpdateScene(context);
+    // Retained rendering: speedup by reusing a retained entity node if
+    // possible. When an entity node is reused, no paint layer is added to the
+    // frame so we won't call PhysicalShapeLayer::Paint.
+    LayerRasterCacheKey key(unique_id(), context.Matrix());
+    if (context.HasRetainedNode(key)) {
+      TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit");
+      scenic::EntityNode* retained_node = context.GetRetainedNode(key);
+      FML_DCHECK(context.top_entity());
+      FML_DCHECK(retained_node->session() == context.session());
+
+      // Re-adjust the elevation.
+      retained_node->SetTranslation(0.f, 0.f, z_translation);
+
+      context.top_entity()->entity_node().AddChild(*retained_node);
+      return;
     }
+
+    TRACE_EVENT_INSTANT0("flutter", "cache miss, creating");
+    // If we can't find an existing retained surface, create one.
+    SceneUpdateContext::Frame frame(
+        context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT,
+        SkScalarRoundToInt(context.alphaf() * 255),
+        "flutter::PhysicalShapeLayer", z_translation, this);
+
+    frame.AddPaintLayer(this);
+
+    // Node: UpdateSceneChildren needs to be called here so that |frame| is
+    // still in scope (and therefore alive) while UpdateSceneChildren is being
+    // called.
+    float scenic_elevation = context.scenic_elevation();
+    context.set_scenic_elevation(scenic_elevation + local_scenic_elevation);
+    update_scene_layers();
+    context.set_scenic_elevation(scenic_elevation);
+  } else {
+    update_scene_layers();
   }
 }
 
diff --git a/flow/layers/fuchsia_layer_unittests.cc b/flow/layers/fuchsia_layer_unittests.cc
index fcc17c0..b1e7d2b 100644
--- a/flow/layers/fuchsia_layer_unittests.cc
+++ b/flow/layers/fuchsia_layer_unittests.cc
@@ -238,17 +238,57 @@
   fuchsia::ui::scenic::SessionListenerPtr listener_;
 };
 
-class MockSessionWrapper : public flutter::SessionWrapper {
+class MockSurfaceProducerSurface
+    : public SceneUpdateContext::SurfaceProducerSurface {
  public:
-  MockSessionWrapper(fuchsia::ui::scenic::SessionPtr session_ptr)
-      : session_(std::move(session_ptr)) {}
-  ~MockSessionWrapper() override = default;
+  MockSurfaceProducerSurface(scenic::Session* session, const SkISize& size)
+      : image_(session, 0, 0, {}), size_(size) {}
 
-  scenic::Session* get() override { return &session_; }
-  void Present() override { session_.Flush(); }
+  size_t AdvanceAndGetAge() override { return 0; }
+
+  bool FlushSessionAcquireAndReleaseEvents() override { return false; }
+
+  bool IsValid() const override { return false; }
+
+  SkISize GetSize() const override { return size_; }
+
+  void SignalWritesFinished(
+      const std::function<void(void)>& on_writes_committed) override {}
+
+  scenic::Image* GetImage() override { return &image_; };
+
+  sk_sp<SkSurface> GetSkiaSurface() const override { return nullptr; };
 
  private:
-  scenic::Session session_;
+  scenic::Image image_;
+  SkISize size_;
+};
+
+class MockSurfaceProducer : public SceneUpdateContext::SurfaceProducer {
+ public:
+  MockSurfaceProducer(scenic::Session* session) : session_(session) {}
+  std::unique_ptr<SceneUpdateContext::SurfaceProducerSurface> ProduceSurface(
+      const SkISize& size,
+      const LayerRasterCacheKey& layer_key,
+      std::unique_ptr<scenic::EntityNode> entity_node) override {
+    return std::make_unique<MockSurfaceProducerSurface>(session_, size);
+  }
+
+  // Query a retained entity node (owned by a retained surface) for retained
+  // rendering.
+  bool HasRetainedNode(const LayerRasterCacheKey& key) const override {
+    return false;
+  }
+
+  scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) override {
+    return nullptr;
+  }
+
+  void SubmitSurface(std::unique_ptr<SceneUpdateContext::SurfaceProducerSurface>
+                         surface) override {}
+
+ private:
+  scenic::Session* session_;
 };
 
 struct TestContext {
@@ -257,11 +297,12 @@
   fml::RefPtr<fml::TaskRunner> task_runner;
 
   // Session.
-  fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> listener_request;
   MockSession mock_session;
-  std::unique_ptr<MockSessionWrapper> mock_session_wrapper;
+  fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> listener_request;
+  std::unique_ptr<scenic::Session> session;
 
   // SceneUpdateContext.
+  std::unique_ptr<MockSurfaceProducer> mock_surface_producer;
   std::unique_ptr<SceneUpdateContext> scene_update_context;
 
   // PrerollContext.
@@ -283,13 +324,15 @@
   fuchsia::ui::scenic::SessionListenerPtr listener;
   context->listener_request = listener.NewRequest();
   context->mock_session.Bind(session_ptr.NewRequest(), std::move(listener));
-  context->mock_session_wrapper =
-      std::make_unique<MockSessionWrapper>(std::move(session_ptr));
+  context->session = std::make_unique<scenic::Session>(std::move(session_ptr));
 
   // Init SceneUpdateContext.
+  context->mock_surface_producer =
+      std::make_unique<MockSurfaceProducer>(context->session.get());
   context->scene_update_context = std::make_unique<SceneUpdateContext>(
-      "fuchsia_layer_unittest", fuchsia::ui::views::ViewToken(),
-      scenic::ViewRefPair::New(), *(context->mock_session_wrapper));
+      context->session.get(), context->mock_surface_producer.get());
+  context->scene_update_context->set_metrics(
+      fidl::MakeOptional(fuchsia::ui::gfx::Metrics{1.f, 1.f, 1.f}));
 
   // Init PrerollContext.
   context->preroll_context = std::unique_ptr<PrerollContext>(new PrerollContext{
@@ -305,6 +348,7 @@
       context->unused_texture_registry,  // texture registry (not
                                          // supported)
       false,                             // checkerboard_offscreen_layers
+      100.f,                             // maximum depth allowed for rendering
       1.f                                // ratio between logical and physical
   });
 
@@ -558,7 +602,7 @@
   // against the list above.
   root->UpdateScene(*(test_context->scene_update_context));
 
-  test_context->mock_session_wrapper->Present();
+  test_context->session->Flush();
 
   // Run loop until idle, so that the Session receives and processes
   // its method calls.
@@ -740,7 +784,7 @@
   // commands against the list above.
   root->UpdateScene(*(test_context->scene_update_context));
 
-  test_context->mock_session_wrapper->Present();
+  test_context->session->Flush();
 
   // Run loop until idle, so that the Session receives and processes
   // its method calls.
diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc
index a242f97..97da04f 100644
--- a/flow/layers/layer.cc
+++ b/flow/layers/layer.cc
@@ -58,9 +58,6 @@
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
 
 void Layer::CheckForChildLayerBelow(PrerollContext* context) {
-  // If there is embedded Fuchsia content in the scene (a ChildSceneLayer),
-  // PhysicalShapeLayers that appear above the embedded content will be turned
-  // into their own Scenic layers.
   child_layer_exists_below_ = context->child_scene_layer_exists_below;
   if (child_layer_exists_below_) {
     set_needs_system_composite(true);
@@ -68,14 +65,42 @@
 }
 
 void Layer::UpdateScene(SceneUpdateContext& context) {
-  FML_DCHECK(needs_system_composite());
-  FML_DCHECK(child_layer_exists_below_);
+  // If there is embedded Fuchsia content in the scene (a ChildSceneLayer),
+  // PhysicalShapeLayers that appear above the embedded content will be turned
+  // into their own Scenic layers.
+  if (child_layer_exists_below_) {
+    float global_scenic_elevation =
+        context.GetGlobalElevationForNextScenicLayer();
+    float local_scenic_elevation =
+        global_scenic_elevation - context.scenic_elevation();
+    float z_translation = -local_scenic_elevation;
 
-  SceneUpdateContext::Frame frame(
-      context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT,
-      SkScalarRoundToInt(context.alphaf() * 255), "flutter::Layer");
+    // Retained rendering: speedup by reusing a retained entity node if
+    // possible. When an entity node is reused, no paint layer is added to the
+    // frame so we won't call PhysicalShapeLayer::Paint.
+    LayerRasterCacheKey key(unique_id(), context.Matrix());
+    if (context.HasRetainedNode(key)) {
+      TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit");
+      scenic::EntityNode* retained_node = context.GetRetainedNode(key);
+      FML_DCHECK(context.top_entity());
+      FML_DCHECK(retained_node->session() == context.session());
 
-  frame.AddPaintLayer(this);
+      // Re-adjust the elevation.
+      retained_node->SetTranslation(0.f, 0.f, z_translation);
+
+      context.top_entity()->entity_node().AddChild(*retained_node);
+      return;
+    }
+
+    TRACE_EVENT_INSTANT0("flutter", "cache miss, creating");
+    // If we can't find an existing retained surface, create one.
+    SceneUpdateContext::Frame frame(
+        context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT,
+        SkScalarRoundToInt(context.alphaf() * 255),
+        "flutter::PhysicalShapeLayer", z_translation, this);
+
+    frame.AddPaintLayer(this);
+  }
 }
 
 #endif
diff --git a/flow/layers/layer.h b/flow/layers/layer.h
index f973276..b22a322 100644
--- a/flow/layers/layer.h
+++ b/flow/layers/layer.h
@@ -56,10 +56,14 @@
   const Stopwatch& ui_time;
   TextureRegistry& texture_registry;
   const bool checkerboard_offscreen_layers;
-  const float frame_device_pixel_ratio;
+
+  // These allow us to make use of the scene metrics during Preroll.
+  float frame_physical_depth;
+  float frame_device_pixel_ratio;
 
   // These allow us to track properties like elevation, opacity, and the
   // prescence of a platform view during Preroll.
+  float total_elevation = 0.0f;
   bool has_platform_view = false;
   bool is_opaque = true;
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
@@ -124,7 +128,10 @@
     TextureRegistry& texture_registry;
     const RasterCache* raster_cache;
     const bool checkerboard_offscreen_layers;
-    const float frame_device_pixel_ratio;
+
+    // These allow us to make use of the scene metrics during Paint.
+    float frame_physical_depth;
+    float frame_device_pixel_ratio;
   };
 
   // Calls SkCanvas::saveLayer and restores the layer upon destruction. Also
diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc
index 9b247b4..0f598ae 100644
--- a/flow/layers/layer_tree.cc
+++ b/flow/layers/layer_tree.cc
@@ -11,14 +11,15 @@
 
 namespace flutter {
 
-LayerTree::LayerTree(const SkISize& frame_size, float device_pixel_ratio)
+LayerTree::LayerTree(const SkISize& frame_size,
+                     float frame_physical_depth,
+                     float frame_device_pixel_ratio)
     : frame_size_(frame_size),
-      device_pixel_ratio_(device_pixel_ratio),
+      frame_physical_depth_(frame_physical_depth),
+      frame_device_pixel_ratio_(frame_device_pixel_ratio),
       rasterizer_tracing_threshold_(0),
       checkerboard_raster_cache_images_(false),
-      checkerboard_offscreen_layers_(false) {
-  FML_CHECK(device_pixel_ratio_ != 0.0f);
-}
+      checkerboard_offscreen_layers_(false) {}
 
 void LayerTree::RecordBuildTime(fml::TimePoint build_start,
                                 fml::TimePoint target_time) {
@@ -53,21 +54,32 @@
       frame.context().ui_time(),
       frame.context().texture_registry(),
       checkerboard_offscreen_layers_,
-      device_pixel_ratio_};
+      frame_physical_depth_,
+      frame_device_pixel_ratio_};
 
   root_layer_->Preroll(&context, frame.root_surface_transformation());
   return context.surface_needs_readback;
 }
 
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
-void LayerTree::UpdateScene(SceneUpdateContext& context) {
+void LayerTree::UpdateScene(SceneUpdateContext& context,
+                            scenic::ContainerNode& container) {
   TRACE_EVENT0("flutter", "LayerTree::UpdateScene");
 
-  // Reset for a new Scene.
-  context.Reset();
+  // Ensure the context is aware of the view metrics.
+  context.set_dimensions(frame_size_, frame_physical_depth_,
+                         frame_device_pixel_ratio_);
 
-  const float inv_dpr = 1.0f / device_pixel_ratio_;
-  SceneUpdateContext::Transform transform(context, inv_dpr, inv_dpr, 1.0f);
+  const auto& metrics = context.metrics();
+  FML_DCHECK(metrics->scale_x > 0.0f);
+  FML_DCHECK(metrics->scale_y > 0.0f);
+  FML_DCHECK(metrics->scale_z > 0.0f);
+
+  SceneUpdateContext::Transform transform(context,                  // context
+                                          1.0f / metrics->scale_x,  // X
+                                          1.0f / metrics->scale_y,  // Y
+                                          1.0f / metrics->scale_z   // Z
+  );
 
   SceneUpdateContext::Frame frame(
       context,
@@ -80,7 +92,7 @@
   if (root_layer_->needs_painting()) {
     frame.AddPaintLayer(root_layer_.get());
   }
-  context.root_node().AddChild(transform.entity_node());
+  container.AddChild(transform.entity_node());
 }
 #endif
 
@@ -113,7 +125,8 @@
       frame.context().texture_registry(),
       ignore_raster_cache ? nullptr : &frame.context().raster_cache(),
       checkerboard_offscreen_layers_,
-      device_pixel_ratio_};
+      frame_physical_depth_,
+      frame_device_pixel_ratio_};
 
   if (root_layer_->needs_painting()) {
     root_layer_->Paint(context);
@@ -138,18 +151,19 @@
   root_surface_transformation.reset();
 
   PrerollContext preroll_context{
-      nullptr,                  // raster_cache (don't consult the cache)
-      nullptr,                  // gr_context  (used for the raster cache)
-      nullptr,                  // external view embedder
-      unused_stack,             // mutator stack
-      nullptr,                  // SkColorSpace* dst_color_space
-      kGiantRect,               // SkRect cull_rect
-      false,                    // layer reads from surface
-      unused_stopwatch,         // frame time (dont care)
-      unused_stopwatch,         // engine time (dont care)
-      unused_texture_registry,  // texture registry (not supported)
-      false,                    // checkerboard_offscreen_layers
-      device_pixel_ratio_       // ratio between logical and physical
+      nullptr,                   // raster_cache (don't consult the cache)
+      nullptr,                   // gr_context  (used for the raster cache)
+      nullptr,                   // external view embedder
+      unused_stack,              // mutator stack
+      nullptr,                   // SkColorSpace* dst_color_space
+      kGiantRect,                // SkRect cull_rect
+      false,                     // layer reads from surface
+      unused_stopwatch,          // frame time (dont care)
+      unused_stopwatch,          // engine time (dont care)
+      unused_texture_registry,   // texture registry (not supported)
+      false,                     // checkerboard_offscreen_layers
+      frame_physical_depth_,     // maximum depth allowed for rendering
+      frame_device_pixel_ratio_  // ratio between logical and physical
   };
 
   SkISize canvas_size = canvas->getBaseLayerSize();
@@ -161,12 +175,13 @@
       canvas,  // canvas
       nullptr,
       nullptr,
-      unused_stopwatch,         // frame time (dont care)
-      unused_stopwatch,         // engine time (dont care)
-      unused_texture_registry,  // texture registry (not supported)
-      nullptr,                  // raster cache
-      false,                    // checkerboard offscreen layers
-      device_pixel_ratio_       // ratio between logical and physical
+      unused_stopwatch,          // frame time (dont care)
+      unused_stopwatch,          // engine time (dont care)
+      unused_texture_registry,   // texture registry (not supported)
+      nullptr,                   // raster cache
+      false,                     // checkerboard offscreen layers
+      frame_physical_depth_,     // maximum depth allowed for rendering
+      frame_device_pixel_ratio_  // ratio between logical and physical
   };
 
   // Even if we don't have a root layer, we still need to create an empty
diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h
index efee960..733284a 100644
--- a/flow/layers/layer_tree.h
+++ b/flow/layers/layer_tree.h
@@ -20,7 +20,9 @@
 
 class LayerTree {
  public:
-  LayerTree(const SkISize& frame_size, float device_pixel_ratio);
+  LayerTree(const SkISize& frame_size,
+            float frame_physical_depth,
+            float frame_device_pixel_ratio);
 
   // Perform a preroll pass on the tree and return information about
   // the tree that affects rendering this frame.
@@ -33,7 +35,8 @@
                bool ignore_raster_cache = false);
 
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
-  void UpdateScene(SceneUpdateContext& context);
+  void UpdateScene(SceneUpdateContext& context,
+                   scenic::ContainerNode& container);
 #endif
 
   void Paint(CompositorContext::ScopedFrame& frame,
@@ -48,7 +51,8 @@
   }
 
   const SkISize& frame_size() const { return frame_size_; }
-  float device_pixel_ratio() const { return device_pixel_ratio_; }
+  float frame_physical_depth() const { return frame_physical_depth_; }
+  float frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; }
 
   void RecordBuildTime(fml::TimePoint build_start, fml::TimePoint target_time);
   fml::TimePoint build_start() const { return build_start_; }
@@ -75,13 +79,16 @@
     checkerboard_offscreen_layers_ = checkerboard;
   }
 
+  double device_pixel_ratio() const { return frame_device_pixel_ratio_; }
+
  private:
   std::shared_ptr<Layer> root_layer_;
   fml::TimePoint build_start_;
   fml::TimePoint build_finish_;
   fml::TimePoint target_time_;
   SkISize frame_size_ = SkISize::MakeEmpty();  // Physical pixels.
-  const float device_pixel_ratio_;  // Logical / Physical pixels ratio.
+  float frame_physical_depth_;
+  float frame_device_pixel_ratio_ = 1.0f;  // Logical / Physical pixels ratio.
   uint32_t rasterizer_tracing_threshold_;
   bool checkerboard_raster_cache_images_;
   bool checkerboard_offscreen_layers_;
diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc
index 7045497..1215b72 100644
--- a/flow/layers/layer_tree_unittests.cc
+++ b/flow/layers/layer_tree_unittests.cc
@@ -18,7 +18,7 @@
 class LayerTreeTest : public CanvasTest {
  public:
   LayerTreeTest()
-      : layer_tree_(SkISize::Make(64, 64), 1.0f),
+      : layer_tree_(SkISize::Make(64, 64), 100.0f, 1.0f),
         compositor_context_(fml::kDefaultFrameBudget),
         root_transform_(SkMatrix::Translate(1.0f, 1.0f)),
         scoped_frame_(compositor_context_.AcquireFrame(nullptr,
diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h
index ed5f028..73e508f 100644
--- a/flow/layers/opacity_layer.h
+++ b/flow/layers/opacity_layer.h
@@ -38,6 +38,7 @@
  private:
   SkAlpha alpha_;
   SkPoint offset_;
+  SkRRect frameRRect_;
 
   FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer);
 };
diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc
index 7ba2b7c..4f87fb2 100644
--- a/flow/layers/physical_shape_layer.cc
+++ b/flow/layers/physical_shape_layer.cc
@@ -21,7 +21,28 @@
       shadow_color_(shadow_color),
       elevation_(elevation),
       path_(path),
-      clip_behavior_(clip_behavior) {}
+      isRect_(false),
+      clip_behavior_(clip_behavior) {
+  SkRect rect;
+  if (path.isRect(&rect)) {
+    isRect_ = true;
+    frameRRect_ = SkRRect::MakeRect(rect);
+  } else if (path.isRRect(&frameRRect_)) {
+    isRect_ = frameRRect_.isRect();
+  } else if (path.isOval(&rect)) {
+    // isRRect returns false for ovals, so we need to explicitly check isOval
+    // as well.
+    frameRRect_ = SkRRect::MakeOval(rect);
+  } else {
+    // Scenic currently doesn't provide an easy way to create shapes from
+    // arbitrary paths.
+    // For shapes that cannot be represented as a rounded rectangle we
+    // default to use the bounding rectangle.
+    // TODO(amirh): fix this once we have a way to create a Scenic shape from
+    // an SkPath.
+    frameRRect_ = SkRRect::MakeRect(path.getBounds());
+  }
+}
 
 void PhysicalShapeLayer::Preroll(PrerollContext* context,
                                  const SkMatrix& matrix) {
@@ -29,9 +50,14 @@
   Layer::AutoPrerollSaveLayerState save =
       Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
 
+  context->total_elevation += elevation_;
+  total_elevation_ = context->total_elevation;
+
   SkRect child_paint_bounds;
   PrerollChildren(context, matrix, &child_paint_bounds);
 
+  context->total_elevation -= elevation_;
+
   if (elevation_ == 0) {
     set_paint_bounds(path_.getBounds());
   } else {
diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h
index ce49af1..2c04368 100644
--- a/flow/layers/physical_shape_layer.h
+++ b/flow/layers/physical_shape_layer.h
@@ -35,13 +35,16 @@
     return clip_behavior_ == Clip::antiAliasWithSaveLayer;
   }
 
-  float elevation() const { return elevation_; }
+  float total_elevation() const { return total_elevation_; }
 
  private:
   SkColor color_;
   SkColor shadow_color_;
   float elevation_ = 0.0f;
+  float total_elevation_ = 0.0f;
   SkPath path_;
+  bool isRect_;
+  SkRRect frameRRect_;
   Clip clip_behavior_;
 };
 
diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc
index bb5d0ac..7ad0b4e 100644
--- a/flow/layers/physical_shape_layer_unittests.cc
+++ b/flow/layers/physical_shape_layer_unittests.cc
@@ -131,7 +131,7 @@
                                                     initial_elevation, 1.0f));
   EXPECT_TRUE(layer->needs_painting());
   EXPECT_FALSE(layer->needs_system_composite());
-  EXPECT_EQ(layer->elevation(), initial_elevation);
+  EXPECT_EQ(layer->total_elevation(), initial_elevation);
 
   // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
   // their shadows , so we do not use the direct |Paint()| path there.
@@ -162,6 +162,7 @@
   // |
   // layers[1] + 2.0f = 3.0f
   constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f};
+  constexpr float total_elevations[4] = {1.0f, 3.0f, 4.0f, 8.0f};
   SkPath layer_path;
   layer_path.addRect(0, 0, 80, 80).close();
 
@@ -186,6 +187,7 @@
                   1.0f /* pixel_ratio */)));
     EXPECT_TRUE(layers[i]->needs_painting());
     EXPECT_FALSE(layers[i]->needs_system_composite());
+    EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]);
   }
 
   // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc
index 80514b5..0bd6ee7 100644
--- a/flow/layers/platform_view_layer.cc
+++ b/flow/layers/platform_view_layer.cc
@@ -48,9 +48,7 @@
 
 #if defined(LEGACY_FUCHSIA_EMBEDDER)
 void PlatformViewLayer::UpdateScene(SceneUpdateContext& context) {
-  TRACE_EVENT0("flutter", "PlatformViewLayer::UpdateScene");
-  FML_DCHECK(needs_system_composite());
-  context.UpdateView(view_id_, offset_, size_);
+  context.UpdateScene(view_id_, offset_, size_);
 }
 #endif
 
diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc
index 8fe5dd3..d01c219 100644
--- a/flow/layers/transform_layer.cc
+++ b/flow/layers/transform_layer.cc
@@ -4,8 +4,6 @@
 
 #include "flutter/flow/layers/transform_layer.h"
 
-#include <optional>
-
 namespace flutter {
 
 TransformLayer::TransformLayer(const SkMatrix& transform)
@@ -58,12 +56,12 @@
   TRACE_EVENT0("flutter", "TransformLayer::UpdateScene");
   FML_DCHECK(needs_system_composite());
 
-  std::optional<SceneUpdateContext::Transform> transform;
   if (!transform_.isIdentity()) {
-    transform.emplace(context, transform_);
+    SceneUpdateContext::Transform transform(context, transform_);
+    UpdateSceneChildren(context);
+  } else {
+    UpdateSceneChildren(context);
   }
-
-  UpdateSceneChildren(context);
 }
 
 #endif
diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc
index c39a7e7..1e39e37 100644
--- a/flow/raster_cache.cc
+++ b/flow/raster_cache.cc
@@ -169,6 +169,7 @@
             context->texture_registry,
             context->has_platform_view ? nullptr : context->raster_cache,
             context->checkerboard_offscreen_layers,
+            context->frame_physical_depth,
             context->frame_device_pixel_ratio};
         if (layer->needs_painting()) {
           layer->Paint(paintContext);
diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc
index 698a23a..b0628ed 100644
--- a/flow/scene_update_context.cc
+++ b/flow/scene_update_context.cc
@@ -4,7 +4,6 @@
 
 #include "flutter/flow/scene_update_context.h"
 
-#include <lib/ui/scenic/cpp/commands.h>
 #include <lib/ui/scenic/cpp/view_token_pair.h>
 
 #include "flutter/flow/layers/layer.h"
@@ -14,10 +13,10 @@
 #include "include/core/SkColor.h"
 
 namespace flutter {
-namespace {
 
-void SetEntityNodeClipPlanes(scenic::EntityNode& entity_node,
-                             const SkRect& bounds) {
+// Helper function to generate clip planes for a scenic::EntityNode.
+static void SetEntityNodeClipPlanes(scenic::EntityNode& entity_node,
+                                    const SkRect& bounds) {
   const float top = bounds.top();
   const float bottom = bounds.bottom();
   const float left = bounds.left();
@@ -54,69 +53,20 @@
   entity_node.SetClipPlanes(std::move(clip_planes));
 }
 
-void SetMaterialColor(scenic::Material& material,
-                      SkColor color,
-                      SkAlpha opacity) {
-  const SkAlpha color_alpha = static_cast<SkAlpha>(
-      ((float)SkColorGetA(color) * (float)opacity) / 255.0f);
-  material.SetColor(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color),
-                    color_alpha);
+SceneUpdateContext::SceneUpdateContext(scenic::Session* session,
+                                       SurfaceProducer* surface_producer)
+    : session_(session), surface_producer_(surface_producer) {
+  FML_DCHECK(surface_producer_ != nullptr);
 }
 
-}  // namespace
-
-SceneUpdateContext::SceneUpdateContext(std::string debug_label,
-                                       fuchsia::ui::views::ViewToken view_token,
-                                       scenic::ViewRefPair view_ref_pair,
-                                       SessionWrapper& session)
-    : session_(session),
-      root_view_(session_.get(),
-                 std::move(view_token),
-                 std::move(view_ref_pair.control_ref),
-                 std::move(view_ref_pair.view_ref),
-                 debug_label),
-      root_node_(session_.get()) {
-  root_view_.AddChild(root_node_);
-  root_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask);
-
-  session_.Present();
-}
-
-std::vector<SceneUpdateContext::PaintTask> SceneUpdateContext::GetPaintTasks() {
-  std::vector<PaintTask> frame_paint_tasks = std::move(paint_tasks_);
-
-  paint_tasks_.clear();
-
-  return frame_paint_tasks;
-}
-
-void SceneUpdateContext::EnableWireframe(bool enable) {
-  session_.get()->Enqueue(
-      scenic::NewSetEnableDebugViewBoundsCmd(root_view_.id(), enable));
-}
-
-void SceneUpdateContext::Reset() {
-  paint_tasks_.clear();
-  top_entity_ = nullptr;
-  top_scale_x_ = 1.f;
-  top_scale_y_ = 1.f;
-  top_elevation_ = 0.f;
-  next_elevation_ = 0.f;
-  alpha_ = 1.f;
-
-  // We are going to be sending down a fresh node hierarchy every frame. So just
-  // enqueue a detach op on the imported root node.
-  session_.get()->Enqueue(scenic::NewDetachChildrenCmd(root_node_.id()));
-}
-
-void SceneUpdateContext::CreateFrame(scenic::EntityNode& entity_node,
+void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node,
                                      const SkRRect& rrect,
                                      SkColor color,
                                      SkAlpha opacity,
                                      const SkRect& paint_bounds,
-                                     std::vector<Layer*> paint_layers) {
-  if (rrect.isEmpty())
-    return;
+                                     std::vector<Layer*> paint_layers,
+                                     Layer* layer) {
+  FML_DCHECK(!rrect.isEmpty());
 
   // Frames always clip their children.
   SkRect shape_bounds = rrect.getBounds();
@@ -124,8 +74,11 @@
 
   // and possibly for its texture.
   // TODO(SCN-137): Need to be able to express the radii as vectors.
-  scenic::ShapeNode shape_node(session_.get());
-  scenic::Rectangle shape(session_.get(), rrect.width(), rrect.height());
+  scenic::ShapeNode shape_node(session());
+  scenic::Rectangle shape(session_,       // session
+                          rrect.width(),  // width
+                          rrect.height()  // height
+  );
   shape_node.SetShape(shape);
   shape_node.SetTranslation(shape_bounds.width() * 0.5f + shape_bounds.left(),
                             shape_bounds.height() * 0.5f + shape_bounds.top(),
@@ -135,49 +88,155 @@
   if (paint_bounds.isEmpty() || !paint_bounds.intersects(shape_bounds))
     paint_layers.clear();
 
-  scenic::Material material(session_.get());
+  scenic::Material material(session());
   shape_node.SetMaterial(material);
   entity_node.AddChild(shape_node);
 
   // Check whether a solid color will suffice.
-  if (paint_layers.empty() || shape_bounds.isEmpty()) {
+  if (paint_layers.empty()) {
     SetMaterialColor(material, color, opacity);
   } else {
+    // Apply current metrics and transformation scale factors.
+    const float scale_x = ScaleX();
+    const float scale_y = ScaleY();
+
+    // Apply a texture to the whole shape.
+    SetMaterialTextureAndColor(material, color, opacity, scale_x, scale_y,
+                               shape_bounds, std::move(paint_layers), layer,
+                               std::move(entity_node));
+  }
+}
+
+void SceneUpdateContext::SetMaterialTextureAndColor(
+    scenic::Material& material,
+    SkColor color,
+    SkAlpha opacity,
+    SkScalar scale_x,
+    SkScalar scale_y,
+    const SkRect& paint_bounds,
+    std::vector<Layer*> paint_layers,
+    Layer* layer,
+    scenic::EntityNode entity_node) {
+  scenic::Image* image = GenerateImageIfNeeded(
+      color, scale_x, scale_y, paint_bounds, std::move(paint_layers), layer,
+      std::move(entity_node));
+
+  if (image != nullptr) {
     // The final shape's color is material_color * texture_color.  The passed in
     // material color was already used as a background when generating the
     // texture, so set the model color to |SK_ColorWHITE| in order to allow
     // using the texture's color unmodified.
     SetMaterialColor(material, SK_ColorWHITE, opacity);
-
-    // Enqueue a paint task for these layers, to apply a texture to the whole
-    // shape.
-    paint_tasks_.emplace_back(PaintTask{.paint_bounds = paint_bounds,
-                                        .scale_x = top_scale_x_,
-                                        .scale_y = top_scale_y_,
-                                        .background_color = color,
-                                        .material = std::move(material),
-                                        .layers = std::move(paint_layers)});
+    material.SetTexture(*image);
+  } else {
+    // No texture was needed, so apply a solid color to the whole shape.
+    SetMaterialColor(material, color, opacity);
   }
 }
 
-void SceneUpdateContext::UpdateView(int64_t view_id,
-                                    const SkPoint& offset,
-                                    const SkSize& size,
-                                    std::optional<bool> override_hit_testable) {
+void SceneUpdateContext::SetMaterialColor(scenic::Material& material,
+                                          SkColor color,
+                                          SkAlpha opacity) {
+  const SkAlpha color_alpha = static_cast<SkAlpha>(
+      ((float)SkColorGetA(color) * (float)opacity) / 255.0f);
+  material.SetColor(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color),
+                    color_alpha);
+}
+
+scenic::Image* SceneUpdateContext::GenerateImageIfNeeded(
+    SkColor color,
+    SkScalar scale_x,
+    SkScalar scale_y,
+    const SkRect& paint_bounds,
+    std::vector<Layer*> paint_layers,
+    Layer* layer,
+    scenic::EntityNode entity_node) {
+  // Bail if there's nothing to paint.
+  if (paint_layers.empty())
+    return nullptr;
+
+  // Bail if the physical bounds are empty after rounding.
+  SkISize physical_size = SkISize::Make(paint_bounds.width() * scale_x,
+                                        paint_bounds.height() * scale_y);
+  if (physical_size.isEmpty())
+    return nullptr;
+
+  // Acquire a surface from the surface producer and register the paint tasks.
+  std::unique_ptr<SurfaceProducerSurface> surface =
+      surface_producer_->ProduceSurface(
+          physical_size,
+          LayerRasterCacheKey(
+              // Root frame has a nullptr layer
+              layer ? layer->unique_id() : 0, Matrix()),
+          std::make_unique<scenic::EntityNode>(std::move(entity_node)));
+
+  if (!surface) {
+    FML_LOG(ERROR) << "Could not acquire a surface from the surface producer "
+                      "of size: "
+                   << physical_size.width() << "x" << physical_size.height();
+    return nullptr;
+  }
+
+  auto image = surface->GetImage();
+
+  // Enqueue the paint task.
+  paint_tasks_.push_back({.surface = std::move(surface),
+                          .left = paint_bounds.left(),
+                          .top = paint_bounds.top(),
+                          .scale_x = scale_x,
+                          .scale_y = scale_y,
+                          .background_color = color,
+                          .layers = std::move(paint_layers)});
+  return image;
+}
+
+std::vector<
+    std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>
+SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) {
+  TRACE_EVENT0("flutter", "SceneUpdateContext::ExecutePaintTasks");
+  std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces_to_submit;
+  for (auto& task : paint_tasks_) {
+    FML_DCHECK(task.surface);
+    SkCanvas* canvas = task.surface->GetSkiaSurface()->getCanvas();
+    Layer::PaintContext context = {canvas,
+                                   canvas,
+                                   frame.gr_context(),
+                                   nullptr,
+                                   frame.context().raster_time(),
+                                   frame.context().ui_time(),
+                                   frame.context().texture_registry(),
+                                   &frame.context().raster_cache(),
+                                   false,
+                                   frame_physical_depth_,
+                                   frame_device_pixel_ratio_};
+    canvas->restoreToCount(1);
+    canvas->save();
+    canvas->clear(task.background_color);
+    canvas->scale(task.scale_x, task.scale_y);
+    canvas->translate(-task.left, -task.top);
+    for (Layer* layer : task.layers) {
+      layer->Paint(context);
+    }
+    surfaces_to_submit.emplace_back(std::move(task.surface));
+  }
+  paint_tasks_.clear();
+  alpha_ = 1.f;
+  topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers;
+  scenic_elevation_ = 0.f;
+  return surfaces_to_submit;
+}
+
+void SceneUpdateContext::UpdateScene(int64_t view_id,
+                                     const SkPoint& offset,
+                                     const SkSize& size) {
   auto* view_holder = ViewHolder::FromId(view_id);
   FML_DCHECK(view_holder);
 
-  if (size.width() > 0.f && size.height() > 0.f) {
-    view_holder->SetProperties(size.width(), size.height(), 0, 0, 0, 0,
-                               view_holder->focusable());
-  }
-
-  bool hit_testable = override_hit_testable.has_value()
-                          ? *override_hit_testable
-                          : view_holder->hit_testable();
-  view_holder->UpdateScene(session_.get(), top_entity_->embedder_node(), offset,
-                           size, SkScalarRoundToInt(alphaf() * 255),
-                           hit_testable);
+  view_holder->SetProperties(size.width(), size.height(), 0, 0, 0, 0,
+                             view_holder->focusable());
+  view_holder->UpdateScene(*this, offset, size,
+                           SkScalarRoundToInt(alphaf() * 255),
+                           view_holder->hit_testable());
 }
 
 void SceneUpdateContext::CreateView(int64_t view_id,
@@ -201,17 +260,13 @@
 SceneUpdateContext::Entity::Entity(SceneUpdateContext& context)
     : context_(context),
       previous_entity_(context.top_entity_),
-      entity_node_(context.session_.get()) {
+      entity_node_(context.session()) {
+  if (previous_entity_)
+    previous_entity_->embedder_node().AddChild(entity_node_);
   context.top_entity_ = this;
 }
 
 SceneUpdateContext::Entity::~Entity() {
-  if (previous_entity_) {
-    previous_entity_->embedder_node().AddChild(entity_node_);
-  } else {
-    context_.root_node_.AddChild(entity_node_);
-  }
-
   FML_DCHECK(context_.top_entity_ == this);
   context_.top_entity_ = previous_entity_;
 }
@@ -274,20 +329,18 @@
                                  const SkRRect& rrect,
                                  SkColor color,
                                  SkAlpha opacity,
-                                 std::string label)
+                                 std::string label,
+                                 float z_translation,
+                                 Layer* layer)
     : Entity(context),
-      previous_elevation_(context.top_elevation_),
       rrect_(rrect),
       color_(color),
       opacity_(opacity),
-      opacity_node_(context.session_.get()),
-      paint_bounds_(SkRect::MakeEmpty()) {
+      opacity_node_(context.session()),
+      paint_bounds_(SkRect::MakeEmpty()),
+      layer_(layer) {
   entity_node().SetLabel(label);
-  entity_node().SetTranslation(0.f, 0.f,
-                               context.next_elevation_ - previous_elevation_);
-  context.top_elevation_ += kScenicZElevationBetweenLayers;
-  context.next_elevation_ += kScenicZElevationBetweenLayers;
-
+  entity_node().SetTranslation(0.f, 0.f, z_translation);
   entity_node().AddChild(opacity_node_);
   // Scenic currently lacks an API to enable rendering of alpha channel; alpha
   // channels are only rendered if there is a OpacityNode higher in the tree
@@ -297,11 +350,20 @@
 }
 
 SceneUpdateContext::Frame::~Frame() {
-  // Add a part which represents the frame's geometry for clipping purposes
-  context().CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_,
-                        std::move(paint_layers_));
+  // We don't need a shape if the frame is zero size.
+  if (rrect_.isEmpty())
+    return;
 
-  context().top_elevation_ = previous_elevation_;
+  // isEmpty should account for this, but we are adding these experimental
+  // checks to validate if this is the root cause for b/144933519.
+  if (std::isnan(rrect_.width()) || std::isnan(rrect_.height())) {
+    FML_LOG(ERROR) << "Invalid RoundedRectangle";
+    return;
+  }
+
+  // Add a part which represents the frame's geometry for clipping purposes
+  context().CreateFrame(std::move(entity_node()), rrect_, color_, opacity_,
+                        paint_bounds_, std::move(paint_layers_), layer_);
 }
 
 void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) {
diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h
index 9ca6c7d..3b46fb2 100644
--- a/flow/scene_update_context.h
+++ b/flow/scene_update_context.h
@@ -5,19 +5,18 @@
 #ifndef FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_
 #define FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_
 
-#include <fuchsia/ui/views/cpp/fidl.h>
-#include <lib/ui/scenic/cpp/resources.h>
-#include <lib/ui/scenic/cpp/session.h>
-#include <lib/ui/scenic/cpp/view_ref_pair.h>
-
 #include <cfloat>
 #include <memory>
 #include <set>
 #include <vector>
 
+#include "flutter/flow/compositor_context.h"
 #include "flutter/flow/embedded_views.h"
+#include "flutter/flow/raster_cache_key.h"
+#include "flutter/fml/compiler_specific.h"
 #include "flutter/fml/logging.h"
 #include "flutter/fml/macros.h"
+#include "lib/ui/scenic/cpp/resources.h"
 #include "third_party/skia/include/core/SkRect.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
@@ -34,16 +33,50 @@
 // How much layers are separated in Scenic z elevation.
 constexpr float kScenicZElevationBetweenLayers = 10.f;
 
-class SessionWrapper {
- public:
-  virtual ~SessionWrapper() {}
-
-  virtual scenic::Session* get() = 0;
-  virtual void Present() = 0;
-};
-
 class SceneUpdateContext : public flutter::ExternalViewEmbedder {
  public:
+  class SurfaceProducerSurface {
+   public:
+    virtual ~SurfaceProducerSurface() = default;
+
+    virtual size_t AdvanceAndGetAge() = 0;
+
+    virtual bool FlushSessionAcquireAndReleaseEvents() = 0;
+
+    virtual bool IsValid() const = 0;
+
+    virtual SkISize GetSize() const = 0;
+
+    virtual void SignalWritesFinished(
+        const std::function<void(void)>& on_writes_committed) = 0;
+
+    virtual scenic::Image* GetImage() = 0;
+
+    virtual sk_sp<SkSurface> GetSkiaSurface() const = 0;
+  };
+
+  class SurfaceProducer {
+   public:
+    virtual ~SurfaceProducer() = default;
+
+    // The produced surface owns the entity_node and has a layer_key for
+    // retained rendering. The surface will only be retained if the layer_key
+    // has a non-null layer pointer (layer_key.id()).
+    virtual std::unique_ptr<SurfaceProducerSurface> ProduceSurface(
+        const SkISize& size,
+        const LayerRasterCacheKey& layer_key,
+        std::unique_ptr<scenic::EntityNode> entity_node) = 0;
+
+    // Query a retained entity node (owned by a retained surface) for retained
+    // rendering.
+    virtual bool HasRetainedNode(const LayerRasterCacheKey& key) const = 0;
+    virtual scenic::EntityNode* GetRetainedNode(
+        const LayerRasterCacheKey& key) = 0;
+
+    virtual void SubmitSurface(
+        std::unique_ptr<SurfaceProducerSurface> surface) = 0;
+  };
+
   class Entity {
    public:
     Entity(SceneUpdateContext& context);
@@ -83,7 +116,9 @@
           const SkRRect& rrect,
           SkColor color,
           SkAlpha opacity,
-          std::string label);
+          std::string label,
+          float z_translation = 0.0f,
+          Layer* layer = nullptr);
     virtual ~Frame();
 
     scenic::ContainerNode& embedder_node() override { return opacity_node_; }
@@ -91,8 +126,6 @@
     void AddPaintLayer(Layer* layer);
 
    private:
-    const float previous_elevation_;
-
     const SkRRect rrect_;
     SkColor const color_;
     SkAlpha const opacity_;
@@ -100,6 +133,7 @@
     scenic::OpacityNodeHACK opacity_node_;
     std::vector<Layer*> paint_layers_;
     SkRect paint_bounds_;
+    Layer* layer_;
   };
 
   class Clip : public Entity {
@@ -108,35 +142,68 @@
     ~Clip() = default;
   };
 
-  struct PaintTask {
-    SkRect paint_bounds;
-    SkScalar scale_x;
-    SkScalar scale_y;
-    SkColor background_color;
-    scenic::Material material;
-    std::vector<Layer*> layers;
-  };
-
-  SceneUpdateContext(std::string debug_label,
-                     fuchsia::ui::views::ViewToken view_token,
-                     scenic::ViewRefPair view_ref_pair,
-                     SessionWrapper& session);
+  SceneUpdateContext(scenic::Session* session,
+                     SurfaceProducer* surface_producer);
   ~SceneUpdateContext() = default;
 
-  scenic::ContainerNode& root_node() { return root_node_; }
+  scenic::Session* session() { return session_; }
+
+  Entity* top_entity() { return top_entity_; }
+
+  bool has_metrics() const { return !!metrics_; }
+  void set_metrics(fuchsia::ui::gfx::MetricsPtr metrics) {
+    metrics_ = std::move(metrics);
+  }
+  const fuchsia::ui::gfx::MetricsPtr& metrics() const { return metrics_; }
+
+  void set_dimensions(const SkISize& frame_physical_size,
+                      float frame_physical_depth,
+                      float frame_device_pixel_ratio) {
+    frame_physical_size_ = frame_physical_size;
+    frame_physical_depth_ = frame_physical_depth;
+    frame_device_pixel_ratio_ = frame_device_pixel_ratio;
+  }
+  const SkISize& frame_size() const { return frame_physical_size_; }
+  float frame_physical_depth() const { return frame_physical_depth_; }
+  float frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; }
+
+  // TODO(chinmaygarde): This method must submit the surfaces as soon as paint
+  // tasks are done. However, given that there is no support currently for
+  // Vulkan semaphores, we need to submit all the surfaces after an explicit
+  // CPU wait. Once Vulkan semaphores are available, this method must return
+  // void and the implementation must submit surfaces on its own as soon as the
+  // specific canvas operations are done.
+  [[nodiscard]] std::vector<std::unique_ptr<SurfaceProducerSurface>>
+  ExecutePaintTasks(CompositorContext::ScopedFrame& frame);
+
+  float ScaleX() const { return metrics_->scale_x * top_scale_x_; }
+  float ScaleY() const { return metrics_->scale_y * top_scale_y_; }
+
+  // The transformation matrix of the current context. It's used to construct
+  // the LayerRasterCacheKey for a given layer.
+  SkMatrix Matrix() const { return SkMatrix::MakeScale(ScaleX(), ScaleY()); }
+
+  bool HasRetainedNode(const LayerRasterCacheKey& key) const {
+    return surface_producer_->HasRetainedNode(key);
+  }
+  scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) {
+    return surface_producer_->GetRetainedNode(key);
+  }
 
   // The cumulative alpha value based on all the parent OpacityLayers.
   void set_alphaf(float alpha) { alpha_ = alpha; }
   float alphaf() { return alpha_; }
 
-  // Returns all `PaintTask`s generated for the current frame.
-  std::vector<PaintTask> GetPaintTasks();
+  // The global scenic elevation at a given point in the traversal.
+  float scenic_elevation() { return scenic_elevation_; }
 
-  // Enable/disable wireframe rendering around the root view bounds.
-  void EnableWireframe(bool enable);
+  void set_scenic_elevation(float elevation) { scenic_elevation_ = elevation; }
 
-  // Reset state for a new frame.
-  void Reset();
+  float GetGlobalElevationForNextScenicLayer() {
+    float elevation = topmost_global_scenic_elevation_;
+    topmost_global_scenic_elevation_ += kScenicZElevationBetweenLayers;
+    return elevation;
+  }
 
   // |ExternalViewEmbedder|
   SkCanvas* GetRootCanvas() override { return nullptr; }
@@ -167,34 +234,73 @@
   }
 
   void CreateView(int64_t view_id, bool hit_testable, bool focusable);
+
   void DestroyView(int64_t view_id);
-  void UpdateView(int64_t view_id,
-                  const SkPoint& offset,
-                  const SkSize& size,
-                  std::optional<bool> override_hit_testable = std::nullopt);
+
+  void UpdateScene(int64_t view_id, const SkPoint& offset, const SkSize& size);
 
  private:
-  void CreateFrame(scenic::EntityNode& entity_node,
+  struct PaintTask {
+    std::unique_ptr<SurfaceProducerSurface> surface;
+    SkScalar left;
+    SkScalar top;
+    SkScalar scale_x;
+    SkScalar scale_y;
+    SkColor background_color;
+    std::vector<Layer*> layers;
+  };
+
+  // Setup the entity_node as a frame that materialize all the paint_layers. In
+  // most cases, this creates a VulkanSurface (SurfaceProducerSurface) by
+  // calling SetShapeTextureOrColor and GenerageImageIfNeeded. Such surface will
+  // own the associated entity_node. If the layer pointer isn't nullptr, the
+  // surface (and thus the entity_node) will be retained for that layer to
+  // improve the performance.
+  void CreateFrame(scenic::EntityNode entity_node,
                    const SkRRect& rrect,
                    SkColor color,
                    SkAlpha opacity,
                    const SkRect& paint_bounds,
-                   std::vector<Layer*> paint_layers);
-
-  SessionWrapper& session_;
-
-  scenic::View root_view_;
-  scenic::EntityNode root_node_;
-
-  std::vector<PaintTask> paint_tasks_;
+                   std::vector<Layer*> paint_layers,
+                   Layer* layer);
+  void SetMaterialTextureAndColor(scenic::Material& material,
+                                  SkColor color,
+                                  SkAlpha opacity,
+                                  SkScalar scale_x,
+                                  SkScalar scale_y,
+                                  const SkRect& paint_bounds,
+                                  std::vector<Layer*> paint_layers,
+                                  Layer* layer,
+                                  scenic::EntityNode entity_node);
+  void SetMaterialColor(scenic::Material& material,
+                        SkColor color,
+                        SkAlpha opacity);
+  scenic::Image* GenerateImageIfNeeded(SkColor color,
+                                       SkScalar scale_x,
+                                       SkScalar scale_y,
+                                       const SkRect& paint_bounds,
+                                       std::vector<Layer*> paint_layers,
+                                       Layer* layer,
+                                       scenic::EntityNode entity_node);
 
   Entity* top_entity_ = nullptr;
   float top_scale_x_ = 1.f;
   float top_scale_y_ = 1.f;
-  float top_elevation_ = 0.f;
 
-  float next_elevation_ = 0.f;
-  float alpha_ = 1.f;
+  scenic::Session* const session_;
+  SurfaceProducer* const surface_producer_;
+
+  fuchsia::ui::gfx::MetricsPtr metrics_;
+  SkISize frame_physical_size_;
+  float frame_physical_depth_ = 0.0f;
+  float frame_device_pixel_ratio_ =
+      1.0f;  // Ratio between logical and physical pixels.
+
+  float alpha_ = 1.0f;
+  float scenic_elevation_ = 0.f;
+  float topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers;
+
+  std::vector<PaintTask> paint_tasks_;
 
   FML_DISALLOW_COPY_AND_ASSIGN(SceneUpdateContext);
 };
diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h
index d2df8b4..c63057f 100644
--- a/flow/testing/layer_test.h
+++ b/flow/testing/layer_test.h
@@ -47,9 +47,11 @@
             kGiantRect, /* cull_rect */
             false,      /* layer reads from surface */
             raster_time_, ui_time_, texture_registry_,
-            false, /* checkerboard_offscreen_layers */
-            1.0f,  /* frame_device_pixel_ratio */
-            false, /* has_platform_view */
+            false,  /* checkerboard_offscreen_layers */
+            100.0f, /* frame_physical_depth */
+            1.0f,   /* frame_device_pixel_ratio */
+            0.0f,   /* total_elevation */
+            false,  /* has_platform_view */
         }),
         paint_context_({
             TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */
@@ -59,6 +61,7 @@
             raster_time_, ui_time_, texture_registry_,
             nullptr, /* raster_cache */
             false,   /* checkerboard_offscreen_layers */
+            100.0f,  /* frame_physical_depth */
             1.0f,    /* frame_device_pixel_ratio */
         }) {
     use_null_raster_cache();
diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc
index b32bdb2..5fe1b98 100644
--- a/flow/testing/mock_layer.cc
+++ b/flow/testing/mock_layer.cc
@@ -22,6 +22,7 @@
   parent_mutators_ = context->mutators_stack;
   parent_matrix_ = matrix;
   parent_cull_rect_ = context->cull_rect;
+  parent_elevation_ = context->total_elevation;
   parent_has_platform_view_ = context->has_platform_view;
 
   context->has_platform_view = fake_has_platform_view_;
diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h
index b92583f..835c3ee 100644
--- a/flow/testing/mock_layer.h
+++ b/flow/testing/mock_layer.h
@@ -28,6 +28,7 @@
   const MutatorsStack& parent_mutators() { return parent_mutators_; }
   const SkMatrix& parent_matrix() { return parent_matrix_; }
   const SkRect& parent_cull_rect() { return parent_cull_rect_; }
+  float parent_elevation() { return parent_elevation_; }
   bool parent_has_platform_view() { return parent_has_platform_view_; }
 
  private:
@@ -36,6 +37,7 @@
   SkRect parent_cull_rect_ = SkRect::MakeEmpty();
   SkPath fake_paint_path_;
   SkPaint fake_paint_;
+  float parent_elevation_ = 0;
   bool parent_has_platform_view_ = false;
   bool fake_has_platform_view_ = false;
   bool fake_needs_system_composite_ = false;
diff --git a/flow/testing/mock_layer_unittests.cc b/flow/testing/mock_layer_unittests.cc
index ebb837c..0e6e379 100644
--- a/flow/testing/mock_layer_unittests.cc
+++ b/flow/testing/mock_layer_unittests.cc
@@ -39,11 +39,13 @@
   const SkMatrix start_matrix = SkMatrix::Translate(1.0f, 2.0f);
   const SkMatrix scale_matrix = SkMatrix::Scale(0.5f, 0.5f);
   const SkRect cull_rect = SkRect::MakeWH(5.0f, 5.0f);
+  const float parent_elevation = 5.0f;
   const bool parent_has_platform_view = true;
   auto layer = std::make_shared<MockLayer>(path, paint);
 
   preroll_context()->mutators_stack.PushTransform(scale_matrix);
   preroll_context()->cull_rect = cull_rect;
+  preroll_context()->total_elevation = parent_elevation;
   preroll_context()->has_platform_view = parent_has_platform_view;
   layer->Preroll(preroll_context(), start_matrix);
   EXPECT_EQ(preroll_context()->has_platform_view, false);
@@ -53,6 +55,7 @@
   EXPECT_EQ(layer->parent_mutators(), std::vector{Mutator(scale_matrix)});
   EXPECT_EQ(layer->parent_matrix(), start_matrix);
   EXPECT_EQ(layer->parent_cull_rect(), cull_rect);
+  EXPECT_EQ(layer->parent_elevation(), parent_elevation);
   EXPECT_EQ(layer->parent_has_platform_view(), parent_has_platform_view);
 
   layer->Paint(paint_context());
diff --git a/flow/view_holder.cc b/flow/view_holder.cc
index c201182..7fd0050 100644
--- a/flow/view_holder.cc
+++ b/flow/view_holder.cc
@@ -4,8 +4,6 @@
 
 #include "flutter/flow/view_holder.h"
 
-#include <unordered_map>
-
 #include "flutter/fml/thread_local.h"
 
 namespace {
@@ -100,17 +98,18 @@
   FML_DCHECK(pending_view_holder_token_.value);
 }
 
-void ViewHolder::UpdateScene(scenic::Session* session,
-                             scenic::ContainerNode& container_node,
+void ViewHolder::UpdateScene(SceneUpdateContext& context,
                              const SkPoint& offset,
                              const SkSize& size,
                              SkAlpha opacity,
                              bool hit_testable) {
   if (pending_view_holder_token_.value) {
-    entity_node_ = std::make_unique<scenic::EntityNode>(session);
-    opacity_node_ = std::make_unique<scenic::OpacityNodeHACK>(session);
+    entity_node_ = std::make_unique<scenic::EntityNode>(context.session());
+    opacity_node_ =
+        std::make_unique<scenic::OpacityNodeHACK>(context.session());
     view_holder_ = std::make_unique<scenic::ViewHolder>(
-        session, std::move(pending_view_holder_token_), "Flutter SceneHost");
+        context.session(), std::move(pending_view_holder_token_),
+        "Flutter SceneHost");
     opacity_node_->AddChild(*entity_node_);
     opacity_node_->SetLabel("flutter::ViewHolder");
     entity_node_->Attach(*view_holder_);
@@ -126,7 +125,7 @@
   FML_DCHECK(opacity_node_);
   FML_DCHECK(view_holder_);
 
-  container_node.AddChild(*opacity_node_);
+  context.top_entity()->embedder_node().AddChild(*opacity_node_);
   opacity_node_->SetOpacity(opacity / 255.0f);
   entity_node_->SetTranslation(offset.x(), offset.y(), -0.1f);
   entity_node_->SetHitTestBehavior(
diff --git a/flow/view_holder.h b/flow/view_holder.h
index f25b205..bb8ff83 100644
--- a/flow/view_holder.h
+++ b/flow/view_holder.h
@@ -9,17 +9,17 @@
 #include <fuchsia/ui/views/cpp/fidl.h>
 #include <lib/ui/scenic/cpp/id.h>
 #include <lib/ui/scenic/cpp/resources.h>
-#include <lib/ui/scenic/cpp/session.h>
 #include <zircon/types.h>
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/core/SkPoint.h"
+#include "third_party/skia/include/core/SkSize.h"
 
 #include <memory>
 
+#include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/macros.h"
 #include "flutter/fml/memory/ref_counted.h"
 #include "flutter/fml/task_runner.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "third_party/skia/include/core/SkPoint.h"
-#include "third_party/skia/include/core/SkSize.h"
 
 namespace flutter {
 
@@ -54,8 +54,7 @@
 
   // Creates or updates the contained ViewHolder resource using the specified
   // |SceneUpdateContext|.
-  void UpdateScene(scenic::Session* session,
-                   scenic::ContainerNode& container_node,
+  void UpdateScene(SceneUpdateContext& context,
                    const SkPoint& offset,
                    const SkSize& size,
                    SkAlpha opacity,
diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc
index 96eac58..d70033a 100644
--- a/lib/ui/compositing/scene.cc
+++ b/lib/ui/compositing/scene.cc
@@ -50,6 +50,7 @@
   layer_tree_ = std::make_unique<LayerTree>(
       SkISize::Make(viewport_metrics.physical_width,
                     viewport_metrics.physical_height),
+      static_cast<float>(viewport_metrics.physical_depth),
       static_cast<float>(viewport_metrics.device_pixel_ratio));
   layer_tree_->set_root_layer(std::move(rootLayer));
   layer_tree_->set_rasterizer_tracing_threshold(rasterizerTracingThreshold);
diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart
index fa44deb..c010954 100644
--- a/lib/ui/hooks.dart
+++ b/lib/ui/hooks.dart
@@ -14,6 +14,7 @@
   double devicePixelRatio,
   double width,
   double height,
+  double depth,
   double viewPaddingTop,
   double viewPaddingRight,
   double viewPaddingBottom,
@@ -30,6 +31,7 @@
   window
     .._devicePixelRatio = devicePixelRatio
     .._physicalSize = Size(width, height)
+    .._physicalDepth = depth
     .._viewPadding = WindowPadding._(
         top: viewPaddingTop,
         right: viewPaddingRight,
diff --git a/lib/ui/window.dart b/lib/ui/window.dart
index 9b562ba..03fcfb0 100644
--- a/lib/ui/window.dart
+++ b/lib/ui/window.dart
@@ -627,6 +627,20 @@
   Size get physicalSize => _physicalSize;
   Size _physicalSize = Size.zero;
 
+  /// The physical depth is the maximum elevation that the Window allows.
+  ///
+  /// Physical layers drawn at or above this elevation will have their elevation
+  /// clamped to this value. This can happen if the physical layer itself has
+  /// an elevation larger than available depth, or if some ancestor of the layer
+  /// causes it to have a cumulative elevation that is larger than the available
+  /// depth.
+  ///
+  /// The default value is [double.maxFinite], which is used for platforms that
+  /// do not specify a maximum elevation. This property is currently on expected
+  /// to be set to a non-default value on Fuchsia.
+  double get physicalDepth => _physicalDepth;
+  double _physicalDepth = double.maxFinite;
+
   /// The number of physical pixels on each side of the display rectangle into
   /// which the application can render, but over which the operating system
   /// will likely place system UI, such as the keyboard, that fully obscures
diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc
index f642bd1..329cea0 100644
--- a/lib/ui/window/viewport_metrics.cc
+++ b/lib/ui/window/viewport_metrics.cc
@@ -8,8 +8,6 @@
 
 namespace flutter {
 
-ViewportMetrics::ViewportMetrics() = default;
-
 ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio,
                                  double p_physical_width,
                                  double p_physical_height,
@@ -50,6 +48,40 @@
 
 ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio,
                                  double p_physical_width,
+                                 double p_physical_height,
+                                 double p_physical_depth,
+                                 double p_physical_padding_top,
+                                 double p_physical_padding_right,
+                                 double p_physical_padding_bottom,
+                                 double p_physical_padding_left,
+                                 double p_physical_view_inset_front,
+                                 double p_physical_view_inset_back,
+                                 double p_physical_view_inset_top,
+                                 double p_physical_view_inset_right,
+                                 double p_physical_view_inset_bottom,
+                                 double p_physical_view_inset_left)
+    : device_pixel_ratio(p_device_pixel_ratio),
+      physical_width(p_physical_width),
+      physical_height(p_physical_height),
+      physical_depth(p_physical_depth),
+      physical_padding_top(p_physical_padding_top),
+      physical_padding_right(p_physical_padding_right),
+      physical_padding_bottom(p_physical_padding_bottom),
+      physical_padding_left(p_physical_padding_left),
+      physical_view_inset_top(p_physical_view_inset_top),
+      physical_view_inset_right(p_physical_view_inset_right),
+      physical_view_inset_bottom(p_physical_view_inset_bottom),
+      physical_view_inset_left(p_physical_view_inset_left),
+      physical_view_inset_front(p_physical_view_inset_front),
+      physical_view_inset_back(p_physical_view_inset_back) {
+  // Ensure we don't have nonsensical dimensions.
+  FML_DCHECK(physical_width >= 0);
+  FML_DCHECK(physical_height >= 0);
+  FML_DCHECK(device_pixel_ratio > 0);
+}
+
+ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio,
+                                 double p_physical_width,
                                  double p_physical_height)
     : device_pixel_ratio(p_device_pixel_ratio),
       physical_width(p_physical_width),
diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h
index 01081e3..d4a7311 100644
--- a/lib/ui/window/viewport_metrics.h
+++ b/lib/ui/window/viewport_metrics.h
@@ -5,10 +5,21 @@
 #ifndef FLUTTER_LIB_UI_WINDOW_VIEWPORT_METRICS_H_
 #define FLUTTER_LIB_UI_WINDOW_VIEWPORT_METRICS_H_
 
+#include <stdint.h>
+
 namespace flutter {
 
+// This is the value of double.maxFinite from dart:core.
+// Platforms that do not explicitly set a depth will use this value, which
+// avoids the need to special case logic that wants to check the max depth on
+// the Dart side.
+static const double kUnsetDepth = 1.7976931348623157e+308;
+
 struct ViewportMetrics {
-  ViewportMetrics();
+  ViewportMetrics() = default;
+  ViewportMetrics(const ViewportMetrics& other) = default;
+
+  // Create a 2D ViewportMetrics instance.
   ViewportMetrics(double p_device_pixel_ratio,
                   double p_physical_width,
                   double p_physical_height,
@@ -25,6 +36,22 @@
                   double p_physical_system_gesture_inset_bottom,
                   double p_physical_system_gesture_inset_left);
 
+  // Create a ViewportMetrics instance that contains z information.
+  ViewportMetrics(double p_device_pixel_ratio,
+                  double p_physical_width,
+                  double p_physical_height,
+                  double p_physical_depth,
+                  double p_physical_padding_top,
+                  double p_physical_padding_right,
+                  double p_physical_padding_bottom,
+                  double p_physical_padding_left,
+                  double p_physical_view_inset_front,
+                  double p_physical_view_inset_back,
+                  double p_physical_view_inset_top,
+                  double p_physical_view_inset_right,
+                  double p_physical_view_inset_bottom,
+                  double p_physical_view_inset_left);
+
   // Create a ViewportMetrics instance that doesn't include depth, padding, or
   // insets.
   ViewportMetrics(double p_device_pixel_ratio,
@@ -34,6 +61,7 @@
   double device_pixel_ratio = 1.0;
   double physical_width = 0;
   double physical_height = 0;
+  double physical_depth = kUnsetDepth;
   double physical_padding_top = 0;
   double physical_padding_right = 0;
   double physical_padding_bottom = 0;
@@ -42,6 +70,8 @@
   double physical_view_inset_right = 0;
   double physical_view_inset_bottom = 0;
   double physical_view_inset_left = 0;
+  double physical_view_inset_front = kUnsetDepth;
+  double physical_view_inset_back = kUnsetDepth;
   double physical_system_gesture_inset_top = 0;
   double physical_system_gesture_inset_right = 0;
   double physical_system_gesture_inset_bottom = 0;
@@ -51,6 +81,7 @@
 struct LogicalSize {
   double width = 0.0;
   double height = 0.0;
+  double depth = kUnsetDepth;
 };
 
 struct LogicalInset {
@@ -58,11 +89,14 @@
   double top = 0.0;
   double right = 0.0;
   double bottom = 0.0;
+  double front = kUnsetDepth;
+  double back = kUnsetDepth;
 };
 
 struct LogicalMetrics {
   LogicalSize size;
   double scale = 1.0;
+  double scale_z = 1.0;
   LogicalInset padding;
   LogicalInset view_inset;
 };
diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc
index 1779998..d583682 100644
--- a/lib/ui/window/window.cc
+++ b/lib/ui/window/window.cc
@@ -49,6 +49,7 @@
           tonic::ToDart(metrics.device_pixel_ratio),
           tonic::ToDart(metrics.physical_width),
           tonic::ToDart(metrics.physical_height),
+          tonic::ToDart(metrics.physical_depth),
           tonic::ToDart(metrics.physical_padding_top),
           tonic::ToDart(metrics.physical_padding_right),
           tonic::ToDart(metrics.physical_padding_bottom),
diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart
index 5f75265..6463e60 100644
--- a/lib/web_ui/lib/src/engine/window.dart
+++ b/lib/web_ui/lib/src/engine/window.dart
@@ -146,6 +146,9 @@
   /// Overrides the value of [physicalSize] in tests.
   ui.Size? webOnlyDebugPhysicalSizeOverride;
 
+  @override
+  double get physicalDepth => double.maxFinite;
+
   /// Handles the browser history integration to allow users to use the back
   /// button, etc.
   final BrowserHistory _browserHistory = BrowserHistory();
diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart
index ac4d936..fd9e667 100644
--- a/lib/web_ui/lib/src/ui/window.dart
+++ b/lib/web_ui/lib/src/ui/window.dart
@@ -455,6 +455,19 @@
   ///    observe when this value changes.
   Size get physicalSize;
 
+  /// The physical depth is the maximum elevation that the Window allows.
+  ///
+  /// Physical layers drawn at or above this elevation will have their elevation
+  /// clamped to this value. This can happen if the physical layer itself has
+  /// an elevation larger than available depth, or if some ancestor of the layer
+  /// causes it to have a cumulative elevation that is larger than the available
+  /// depth.
+  ///
+  /// The default value is [double.maxFinite], which is used for platforms that
+  /// do not specify a maximum elevation. This property is currently on expected
+  /// to be set to a non-default value on Fuchsia.
+  double get physicalDepth;
+
   /// The number of physical pixels on each side of the display rectangle into
   /// which the application can render, but over which the operating system
   /// will likely place system UI, such as the keyboard, that fully obscures
diff --git a/shell/common/engine.cc b/shell/common/engine.cc
index dc309f7..79f885d 100644
--- a/shell/common/engine.cc
+++ b/shell/common/engine.cc
@@ -276,6 +276,7 @@
   bool dimensions_changed =
       viewport_metrics_.physical_height != metrics.physical_height ||
       viewport_metrics_.physical_width != metrics.physical_width ||
+      viewport_metrics_.physical_depth != metrics.physical_depth ||
       viewport_metrics_.device_pixel_ratio != metrics.device_pixel_ratio;
   viewport_metrics_ = metrics;
   runtime_controller_->SetViewportMetrics(viewport_metrics_);
@@ -461,7 +462,8 @@
 
   // Ensure frame dimensions are sane.
   if (layer_tree->frame_size().isEmpty() ||
-      layer_tree->device_pixel_ratio() <= 0.0f) {
+      layer_tree->frame_physical_depth() <= 0.0f ||
+      layer_tree->frame_device_pixel_ratio() <= 0.0f) {
     return;
   }
 
diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc
index 6846884..075f4ad 100644
--- a/shell/common/shell_test.cc
+++ b/shell/common/shell_test.cc
@@ -129,6 +129,7 @@
         auto layer_tree = std::make_unique<LayerTree>(
             SkISize::Make(viewport_metrics.physical_width,
                           viewport_metrics.physical_height),
+            static_cast<float>(viewport_metrics.physical_depth),
             static_cast<float>(viewport_metrics.device_pixel_ratio));
         SkMatrix identity;
         identity.setIdentity();
diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn
index c8652cc..ba68d1b 100644
--- a/shell/platform/fuchsia/flutter/BUILD.gn
+++ b/shell/platform/fuchsia/flutter/BUILD.gn
@@ -12,6 +12,7 @@
 import("//flutter/tools/fuchsia/fuchsia_archive.gni")
 import("//flutter/tools/fuchsia/fuchsia_libs.gni")
 import("//flutter/vulkan/config.gni")
+import("engine_flutter_runner.gni")
 
 # Fuchsia uses its own custom Surface implementation.
 shell_gpu_configuration("fuchsia_legacy_gpu_configuration") {
@@ -21,158 +22,10 @@
   enable_metal = false
 }
 
-source_set("flutter_runner_sources") {
-  sources = [
-    "accessibility_bridge.cc",
-    "accessibility_bridge.h",
-    "component.cc",
-    "component.h",
-    "compositor_context.cc",
-    "compositor_context.h",
-    "engine.cc",
-    "engine.h",
-    "flutter_runner_product_configuration.cc",
-    "flutter_runner_product_configuration.h",
-    "fuchsia_intl.cc",
-    "fuchsia_intl.h",
-    "isolate_configurator.cc",
-    "isolate_configurator.h",
-    "logging.h",
-    "loop.cc",
-    "loop.h",
-    "platform_view.cc",
-    "platform_view.h",
-    "runner.cc",
-    "runner.h",
-    "session_connection.cc",
-    "session_connection.h",
-    "surface.cc",
-    "surface.h",
-    "task_observers.cc",
-    "task_observers.h",
-    "task_runner_adapter.cc",
-    "task_runner_adapter.h",
-    "thread.cc",
-    "thread.h",
-    "unique_fdio_ns.h",
-    "vsync_recorder.cc",
-    "vsync_recorder.h",
-    "vsync_waiter.cc",
-    "vsync_waiter.h",
-    "vulkan_surface.cc",
-    "vulkan_surface.h",
-    "vulkan_surface_pool.cc",
-    "vulkan_surface_pool.h",
-    "vulkan_surface_producer.cc",
-    "vulkan_surface_producer.h",
-  ]
-
-  # The use of these dependencies is temporary and will be moved behind the
-  # embedder API.
-  flutter_public_deps = [
-    "//flutter/flow:flow_fuchsia_legacy",
-    "//flutter/lib/ui:ui_fuchsia_legacy",
-    "//flutter/runtime:runtime_fuchsia_legacy",
-    "//flutter/shell/common:common_fuchsia_legacy",
-  ]
-  flutter_deps = [
-    ":fuchsia_legacy_gpu_configuration",
-    "//flutter/assets",
-    "//flutter/common",
-    "//flutter/fml",
-    "//flutter/vulkan",
-  ]
-
-  public_deps = [
-                  "$fuchsia_sdk_root/pkg:scenic_cpp",
-                  "$fuchsia_sdk_root/pkg:sys_cpp",
-                  "//flutter/shell/platform/fuchsia/runtime/dart/utils",
-                ] + flutter_public_deps
-
-  deps = [
-           "$fuchsia_sdk_root/fidl:fuchsia.accessibility.semantics",
-           "$fuchsia_sdk_root/fidl:fuchsia.fonts",
-           "$fuchsia_sdk_root/fidl:fuchsia.images",
-           "$fuchsia_sdk_root/fidl:fuchsia.intl",
-           "$fuchsia_sdk_root/fidl:fuchsia.io",
-           "$fuchsia_sdk_root/fidl:fuchsia.sys",
-           "$fuchsia_sdk_root/fidl:fuchsia.ui.app",
-           "$fuchsia_sdk_root/fidl:fuchsia.ui.scenic",
-           "$fuchsia_sdk_root/pkg:async-cpp",
-           "$fuchsia_sdk_root/pkg:async-default",
-           "$fuchsia_sdk_root/pkg:async-loop",
-           "$fuchsia_sdk_root/pkg:async-loop-cpp",
-           "$fuchsia_sdk_root/pkg:fdio",
-           "$fuchsia_sdk_root/pkg:fidl_cpp",
-           "$fuchsia_sdk_root/pkg:syslog",
-           "$fuchsia_sdk_root/pkg:trace",
-           "$fuchsia_sdk_root/pkg:trace-engine",
-           "$fuchsia_sdk_root/pkg:trace-provider-so",
-           "$fuchsia_sdk_root/pkg:vfs_cpp",
-           "$fuchsia_sdk_root/pkg:zx",
-           "//flutter/shell/platform/fuchsia/dart-pkg/fuchsia",
-           "//flutter/shell/platform/fuchsia/dart-pkg/zircon",
-         ] + flutter_deps
-}
-
 # Things that explicitly being excluded:
 # 1. Kernel snapshot framework mode.
 # 2. Profiler symbols.
 
-# Builds a flutter_runner
-#
-# Parameters:
-#
-#  output_name (required):
-#    The name of the resulting binary.
-#
-#  extra_deps (required):
-#    Any additional dependencies.
-#
-#  product (required):
-#    Whether to link against a Product mode Dart VM.
-#
-#  extra_defines (optional):
-#    Any additional preprocessor defines.
-template("flutter_runner") {
-  assert(defined(invoker.output_name), "flutter_runner must define output_name")
-  assert(defined(invoker.extra_deps), "flutter_runner must define extra_deps")
-  assert(defined(invoker.product), "flutter_runner must define product")
-
-  invoker_output_name = invoker.output_name
-  extra_deps = invoker.extra_deps
-
-  extra_defines = []
-  if (defined(invoker.extra_defines)) {
-    extra_defines += invoker.extra_defines
-  }
-
-  executable(target_name) {
-    output_name = invoker_output_name
-
-    defines = extra_defines
-
-    sources = [ "main.cc" ]
-
-    deps = [
-             ":flutter_runner_sources",
-             "$fuchsia_sdk_root/pkg:async-loop-cpp",
-             "$fuchsia_sdk_root/pkg:trace",
-             "$fuchsia_sdk_root/pkg:trace-provider-so",
-           ] + extra_deps
-
-    # The flags below are needed so that Dart's CPU profiler can walk the
-    # C++ stack.
-    cflags = [ "-fno-omit-frame-pointer" ]
-
-    if (!invoker.product) {
-      # This flag is needed so that the call to dladdr() in Dart's native symbol
-      # resolver can report good symbol information for the CPU profiler.
-      ldflags = [ "-rdynamic" ]
-    }
-  }
-}
-
 flutter_runner("jit") {
   output_name = "flutter_jit_runner"
   product = false
@@ -414,36 +267,66 @@
   output_name = "flutter_runner_tests"
 
   sources = [
+    "accessibility_bridge.cc",
+    "accessibility_bridge.h",
     "accessibility_bridge_unittest.cc",
+    "component.cc",
+    "component.h",
     "component_unittest.cc",
     "flutter_runner_fakes.h",
+    "flutter_runner_product_configuration.cc",
+    "flutter_runner_product_configuration.h",
+    "fuchsia_intl.cc",
+    "fuchsia_intl.h",
     "fuchsia_intl_unittest.cc",
+    "logging.h",
+    "loop.cc",
+    "loop.h",
+    "platform_view.cc",
+    "platform_view.h",
     "platform_view_unittest.cc",
+    "runner.cc",
+    "runner.h",
     "runner_unittest.cc",
+    "surface.cc",
+    "surface.h",
+    "task_observers.cc",
+    "task_observers.h",
+    "task_runner_adapter.cc",
+    "task_runner_adapter.h",
     "tests/flutter_runner_product_configuration_unittests.cc",
     "tests/vsync_recorder_unittests.cc",
+    "thread.cc",
+    "thread.h",
+    "vsync_recorder.cc",
+    "vsync_recorder.h",
+    "vsync_waiter.cc",
+    "vsync_waiter.h",
     "vsync_waiter_unittests.cc",
   ]
 
   # This is needed for //third_party/googletest for linking zircon symbols.
   libs = [ "//fuchsia/sdk/$host_os/arch/$target_cpu/sysroot/lib/libzircon.so" ]
 
-  # The use of these dependencies is temporary and will be moved behind the
-  # embedder API.
-  flutter_deps = [
+  deps = [
+    ":aot",
+    ":flutter_runner_fixtures",
+    "//build/fuchsia/fidl:fuchsia.accessibility.semantics",
+    "//build/fuchsia/pkg:async-default",
+    "//build/fuchsia/pkg:async-loop-cpp",
+    "//build/fuchsia/pkg:async-loop-default",
+    "//build/fuchsia/pkg:scenic_cpp",
+    "//build/fuchsia/pkg:sys_cpp_testing",
+    "//flutter/common",
     "//flutter/flow:flow_fuchsia_legacy",
     "//flutter/lib/ui:ui_fuchsia_legacy",
+    "//flutter/runtime:runtime_fuchsia_legacy",
     "//flutter/shell/common:common_fuchsia_legacy",
+    "//flutter/shell/platform/fuchsia/runtime/dart/utils",
+    "//flutter/testing",
     "//third_party/dart/runtime:libdart_jit",
     "//third_party/dart/runtime/platform:libdart_platform_jit",
   ]
-
-  deps = [
-           ":flutter_runner_fixtures",
-           ":flutter_runner_sources",
-           "//build/fuchsia/pkg:sys_cpp_testing",
-           "//flutter/testing",
-         ] + flutter_deps
 }
 
 executable("flutter_runner_tzdata_unittests") {
@@ -451,24 +334,34 @@
 
   output_name = "flutter_runner_tzdata_tests"
 
-  sources = [ "runner_tzdata_unittest.cc" ]
+  sources = [
+    "runner.cc",
+    "runner.h",
+    "runner_tzdata_unittest.cc",
+  ]
 
   # This is needed for //third_party/googletest for linking zircon symbols.
   libs = [ "//fuchsia/sdk/$host_os/arch/$target_cpu/sysroot/lib/libzircon.so" ]
 
-  # The use of these dependencies is temporary and will be moved behind the
-  # embedder API.
-  flutter_deps = [
+  deps = [
+    ":aot",
+    ":flutter_runner_fixtures",
+    "//build/fuchsia/fidl:fuchsia.accessibility.semantics",
+    "//build/fuchsia/pkg:async-loop-cpp",
+    "//build/fuchsia/pkg:async-loop-default",
+    "//build/fuchsia/pkg:scenic_cpp",
+    "//build/fuchsia/pkg:sys_cpp_testing",
+    "//flutter/flow:flow_fuchsia_legacy",
     "//flutter/lib/ui:ui_fuchsia_legacy",
+    "//flutter/runtime:runtime_fuchsia_legacy",
+    "//flutter/shell/common:common_fuchsia_legacy",
+    "//flutter/shell/platform/fuchsia/runtime/dart/utils",
+    "//flutter/testing",
     "//third_party/dart/runtime:libdart_jit",
     "//third_party/dart/runtime/platform:libdart_platform_jit",
+    "//third_party/icu",
+    "//third_party/skia",
   ]
-
-  deps = [
-           ":flutter_runner_fixtures",
-           ":flutter_runner_sources",
-           "//flutter/testing",
-         ] + flutter_deps
 }
 
 executable("flutter_runner_scenic_unittests") {
@@ -476,27 +369,84 @@
 
   output_name = "flutter_runner_scenic_tests"
 
-  sources = [ "tests/session_connection_unittests.cc" ]
+  sources = [
+    "component.cc",
+    "component.h",
+    "compositor_context.cc",
+    "compositor_context.h",
+    "engine.cc",
+    "engine.h",
+    "fuchsia_intl.cc",
+    "fuchsia_intl.h",
+    "isolate_configurator.cc",
+    "isolate_configurator.h",
+    "logging.h",
+    "loop.cc",
+    "loop.h",
+    "platform_view.cc",
+    "platform_view.h",
+    "runner.cc",
+    "runner.h",
+    "session_connection.cc",
+    "session_connection.h",
+    "surface.cc",
+    "surface.h",
+    "task_observers.cc",
+    "task_observers.h",
+    "task_runner_adapter.cc",
+    "task_runner_adapter.h",
+    "tests/session_connection_unittests.cc",
+    "thread.cc",
+    "thread.h",
+    "unique_fdio_ns.h",
+    "vsync_recorder.cc",
+    "vsync_recorder.h",
+    "vsync_waiter.cc",
+    "vsync_waiter.h",
+    "vsync_waiter_unittests.cc",
+    "vulkan_surface.cc",
+    "vulkan_surface.h",
+    "vulkan_surface_pool.cc",
+    "vulkan_surface_pool.h",
+    "vulkan_surface_producer.cc",
+    "vulkan_surface_producer.h",
+  ]
 
   # This is needed for //third_party/googletest for linking zircon symbols.
   libs = [ "//fuchsia/sdk/$host_os/arch/$target_cpu/sysroot/lib/libzircon.so" ]
 
-  # The use of these dependencies is temporary and will be moved behind the
-  # embedder API.
-  flutter_deps = [
+  deps = [
+    ":flutter_runner_fixtures",
+    ":jit",
+    "$fuchsia_sdk_root/fidl:fuchsia.ui.policy",
+    "$fuchsia_sdk_root/pkg:trace-provider-so",
+    "//build/fuchsia/fidl:fuchsia.accessibility.semantics",
+    "//build/fuchsia/pkg:async-default",
+    "//build/fuchsia/pkg:async-loop-cpp",
+    "//build/fuchsia/pkg:async-loop-default",
+    "//build/fuchsia/pkg:scenic_cpp",
+    "//build/fuchsia/pkg:sys_cpp_testing",
+    "//flutter/common",
+    "//flutter/flow:flow_fuchsia_legacy",
     "//flutter/lib/ui:ui_fuchsia_legacy",
+    "//flutter/runtime:runtime_fuchsia_legacy",
+    "//flutter/shell/common:common_fuchsia_legacy",
+    "//flutter/shell/platform/fuchsia/dart-pkg/fuchsia",
+    "//flutter/shell/platform/fuchsia/dart-pkg/zircon",
+    "//flutter/shell/platform/fuchsia/runtime/dart/utils",
+    "//flutter/testing",
+    "//flutter/vulkan",
     "//third_party/dart/runtime:libdart_jit",
     "//third_party/dart/runtime/platform:libdart_platform_jit",
+    "//third_party/icu",
+    "//third_party/skia",
   ]
 
-  deps = [
-           ":flutter_runner_fixtures",
-           ":flutter_runner_sources",
-           "$fuchsia_sdk_root/fidl:fuchsia.ui.policy",
-           "//flutter/testing",
-         ] + flutter_deps
+  public_deps = [ "//third_party/googletest:gtest" ]
 }
 
+# When adding a new dep here, please also ensure the dep is added to
+# testing/fuchsia/run_tests.sh and testing/fuchsia/test_fars
 fuchsia_archive("flutter_runner_tests") {
   testonly = true
 
@@ -843,8 +793,6 @@
   resources += vulkan_icds
 }
 
-# When adding a new dep here, please also ensure the dep is added to
-# testing/fuchsia/run_tests.sh and testing/fuchsia/test_fars
 group("tests") {
   testonly = true
 
diff --git a/shell/platform/fuchsia/flutter/component.cc b/shell/platform/fuchsia/flutter/component.cc
index 0106931..00cd9d3 100644
--- a/shell/platform/fuchsia/flutter/component.cc
+++ b/shell/platform/fuchsia/flutter/component.cc
@@ -365,12 +365,6 @@
   // Controls whether category "skia" trace events are enabled.
   settings_.trace_skia = true;
 
-  settings_.verbose_logging = true;
-
-  settings_.advisory_script_uri = debug_label_;
-
-  settings_.advisory_script_entrypoint = debug_label_;
-
   settings_.icu_data_path = "";
 
   settings_.assets_dir = application_assets_directory_.get();
diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc
index 599ebf7..b0bbfc7 100644
--- a/shell/platform/fuchsia/flutter/compositor_context.cc
+++ b/shell/platform/fuchsia/flutter/compositor_context.cc
@@ -4,8 +4,6 @@
 
 #include "compositor_context.h"
 
-#include <vector>
-
 #include "flutter/flow/layers/layer_tree.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 
@@ -13,38 +11,30 @@
 
 class ScopedFrame final : public flutter::CompositorContext::ScopedFrame {
  public:
-  ScopedFrame(CompositorContext& context,
-              GrContext* gr_context,
-              SkCanvas* canvas,
-              flutter::ExternalViewEmbedder* view_embedder,
+  ScopedFrame(flutter::CompositorContext& context,
               const SkMatrix& root_surface_transformation,
+              flutter::ExternalViewEmbedder* view_embedder,
               bool instrumentation_enabled,
-              bool surface_supports_readback,
-              fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger,
-              SessionConnection& session_connection,
-              VulkanSurfaceProducer& surface_producer,
-              flutter::SceneUpdateContext& scene_update_context)
-      : flutter::CompositorContext::ScopedFrame(context,
-                                                surface_producer.gr_context(),
-                                                canvas,
-                                                view_embedder,
-                                                root_surface_transformation,
-                                                instrumentation_enabled,
-                                                surface_supports_readback,
-                                                raster_thread_merger),
-        session_connection_(session_connection),
-        surface_producer_(surface_producer),
-        scene_update_context_(scene_update_context) {}
+              SessionConnection& session_connection)
+      : flutter::CompositorContext::ScopedFrame(
+            context,
+            session_connection.vulkan_surface_producer()->gr_context(),
+            nullptr,
+            view_embedder,
+            root_surface_transformation,
+            instrumentation_enabled,
+            true,
+            nullptr),
+        session_connection_(session_connection) {}
 
  private:
   SessionConnection& session_connection_;
-  VulkanSurfaceProducer& surface_producer_;
-  flutter::SceneUpdateContext& scene_update_context_;
 
   flutter::RasterStatus Raster(flutter::LayerTree& layer_tree,
                                bool ignore_raster_cache) override {
-    std::vector<flutter::SceneUpdateContext::PaintTask> frame_paint_tasks;
-    std::vector<std::unique_ptr<SurfaceProducerSurface>> frame_surfaces;
+    if (!session_connection_.has_metrics()) {
+      return flutter::RasterStatus::kSuccess;
+    }
 
     {
       // Preroll the Flutter layer tree. This allows Flutter to perform
@@ -57,80 +47,15 @@
       // Traverse the Flutter layer tree so that the necessary session ops to
       // represent the frame are enqueued in the underlying session.
       TRACE_EVENT0("flutter", "UpdateScene");
-      layer_tree.UpdateScene(scene_update_context_);
+      layer_tree.UpdateScene(session_connection_.scene_update_context(),
+                             session_connection_.root_node());
     }
 
     {
-      // Flush all pending session ops: create surfaces and enqueue session
-      // Image ops for the frame's paint tasks, then Present.
+      // Flush all pending session ops.
       TRACE_EVENT0("flutter", "SessionPresent");
-      frame_paint_tasks = scene_update_context_.GetPaintTasks();
-      for (auto& task : frame_paint_tasks) {
-        SkISize physical_size =
-            SkISize::Make(layer_tree.device_pixel_ratio() * task.scale_x *
-                              task.paint_bounds.width(),
-                          layer_tree.device_pixel_ratio() * task.scale_y *
-                              task.paint_bounds.height());
-        if (physical_size.width() == 0 || physical_size.height() == 0) {
-          frame_surfaces.emplace_back(nullptr);
-          continue;
-        }
 
-        std::unique_ptr<SurfaceProducerSurface> surface =
-            surface_producer_.ProduceSurface(physical_size);
-        if (!surface) {
-          FML_LOG(ERROR)
-              << "Could not acquire a surface from the surface producer "
-                 "of size: "
-              << physical_size.width() << "x" << physical_size.height();
-        } else {
-          task.material.SetTexture(*(surface->GetImage()));
-        }
-
-        frame_surfaces.emplace_back(std::move(surface));
-      }
-
-      session_connection_.Present();
-    }
-
-    {
-      // Execute paint tasks in parallel with Scenic's side of the Present, then
-      // signal fences.
-      TRACE_EVENT0("flutter", "ExecutePaintTasks");
-      size_t surface_index = 0;
-      for (auto& task : frame_paint_tasks) {
-        std::unique_ptr<SurfaceProducerSurface>& task_surface =
-            frame_surfaces[surface_index++];
-        if (!task_surface) {
-          continue;
-        }
-
-        SkCanvas* canvas = task_surface->GetSkiaSurface()->getCanvas();
-        flutter::Layer::PaintContext paint_context = {
-            canvas,
-            canvas,
-            gr_context(),
-            nullptr,
-            context().raster_time(),
-            context().ui_time(),
-            context().texture_registry(),
-            &context().raster_cache(),
-            false,
-            layer_tree.device_pixel_ratio()};
-        canvas->restoreToCount(1);
-        canvas->save();
-        canvas->clear(task.background_color);
-        canvas->scale(layer_tree.device_pixel_ratio() * task.scale_x,
-                      layer_tree.device_pixel_ratio() * task.scale_y);
-        canvas->translate(-task.paint_bounds.left(), -task.paint_bounds.top());
-        for (flutter::Layer* layer : task.layers) {
-          layer->Paint(paint_context);
-        }
-      }
-
-      // Tell the surface producer that a present has occurred so it can perform
-      // book-keeping on buffer caches.
-      surface_producer_.OnSurfacesPresented(std::move(frame_surfaces));
+      session_connection_.Present(this);
     }
 
     return flutter::RasterStatus::kSuccess;
@@ -140,14 +65,51 @@
 };
 
 CompositorContext::CompositorContext(
-    SessionConnection& session_connection,
-    VulkanSurfaceProducer& surface_producer,
-    flutter::SceneUpdateContext& scene_update_context)
-    : session_connection_(session_connection),
-      surface_producer_(surface_producer),
-      scene_update_context_(scene_update_context) {}
+    std::string debug_label,
+    fuchsia::ui::views::ViewToken view_token,
+    scenic::ViewRefPair view_ref_pair,
+    fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session,
+    fml::closure session_error_callback,
+    zx_handle_t vsync_event_handle)
+    : debug_label_(std::move(debug_label)),
+      session_connection_(
+          debug_label_,
+          std::move(view_token),
+          std::move(view_ref_pair),
+          std::move(session),
+          session_error_callback,
+          [](auto) {},
+          vsync_event_handle) {}
 
-CompositorContext::~CompositorContext() = default;
+void CompositorContext::OnSessionMetricsDidChange(
+    const fuchsia::ui::gfx::Metrics& metrics) {
+  session_connection_.set_metrics(metrics);
+}
+
+void CompositorContext::OnSessionSizeChangeHint(float width_change_factor,
+                                                float height_change_factor) {
+  session_connection_.OnSessionSizeChangeHint(width_change_factor,
+                                              height_change_factor);
+}
+
+void CompositorContext::OnWireframeEnabled(bool enabled) {
+  session_connection_.set_enable_wireframe(enabled);
+}
+
+void CompositorContext::OnCreateView(int64_t view_id,
+                                     bool hit_testable,
+                                     bool focusable) {
+  session_connection_.scene_update_context().CreateView(view_id, hit_testable,
+                                                        focusable);
+}
+
+void CompositorContext::OnDestroyView(int64_t view_id) {
+  session_connection_.scene_update_context().DestroyView(view_id);
+}
+
+CompositorContext::~CompositorContext() {
+  OnGrContextDestroyed();
+}
 
 std::unique_ptr<flutter::CompositorContext::ScopedFrame>
 CompositorContext::AcquireFrame(
@@ -158,10 +120,16 @@
     bool instrumentation_enabled,
     bool surface_supports_readback,
     fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
+  // TODO: The AcquireFrame interface is too broad and must be refactored to get
+  // rid of the context and canvas arguments as those seem to be only used for
+  // colorspace correctness purposes on the mobile shells.
   return std::make_unique<flutter_runner::ScopedFrame>(
-      *this, gr_context, canvas, view_embedder, root_surface_transformation,
-      instrumentation_enabled, surface_supports_readback, raster_thread_merger,
-      session_connection_, surface_producer_, scene_update_context_);
+      *this,                        //
+      root_surface_transformation,  //
+      view_embedder,
+      instrumentation_enabled,  //
+      session_connection_       //
+  );
 }
 
 }  // namespace flutter_runner
diff --git a/shell/platform/fuchsia/flutter/compositor_context.h b/shell/platform/fuchsia/flutter/compositor_context.h
index 542e5d3..6ad2878 100644
--- a/shell/platform/fuchsia/flutter/compositor_context.h
+++ b/shell/platform/fuchsia/flutter/compositor_context.h
@@ -5,15 +5,15 @@
 #ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPOSITOR_CONTEXT_H_
 #define FLUTTER_SHELL_PLATFORM_FUCHSIA_COMPOSITOR_CONTEXT_H_
 
-#include <memory>
+#include <fuchsia/ui/scenic/cpp/fidl.h>
+#include <fuchsia/ui/views/cpp/fidl.h>
+#include <lib/fit/function.h>
+#include <lib/ui/scenic/cpp/view_ref_pair.h>
 
 #include "flutter/flow/compositor_context.h"
 #include "flutter/flow/embedded_views.h"
-#include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/macros.h"
-
 #include "session_connection.h"
-#include "vulkan_surface_producer.h"
 
 namespace flutter_runner {
 
@@ -21,16 +21,31 @@
 // Fuchsia.
 class CompositorContext final : public flutter::CompositorContext {
  public:
-  CompositorContext(SessionConnection& session_connection,
-                    VulkanSurfaceProducer& surface_producer,
-                    flutter::SceneUpdateContext& scene_update_context);
+  CompositorContext(std::string debug_label,
+                    fuchsia::ui::views::ViewToken view_token,
+                    scenic::ViewRefPair view_ref_pair,
+                    fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session,
+                    fml::closure session_error_callback,
+                    zx_handle_t vsync_event_handle);
 
   ~CompositorContext() override;
 
+  void OnSessionMetricsDidChange(const fuchsia::ui::gfx::Metrics& metrics);
+  void OnSessionSizeChangeHint(float width_change_factor,
+                               float height_change_factor);
+
+  void OnWireframeEnabled(bool enabled);
+  void OnCreateView(int64_t view_id, bool hit_testable, bool focusable);
+  void OnDestroyView(int64_t view_id);
+
+  flutter::ExternalViewEmbedder* GetViewEmbedder() {
+    return &session_connection_.scene_update_context();
+  }
+
  private:
-  SessionConnection& session_connection_;
-  VulkanSurfaceProducer& surface_producer_;
-  flutter::SceneUpdateContext& scene_update_context_;
+  const std::string debug_label_;
+  scenic::ViewRefPair view_ref_pair_;
+  SessionConnection session_connection_;
 
   // |flutter::CompositorContext|
   std::unique_ptr<ScopedFrame> AcquireFrame(
diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc
index 82c66fc..7868a19 100644
--- a/shell/platform/fuchsia/flutter/engine.cc
+++ b/shell/platform/fuchsia/flutter/engine.cc
@@ -7,6 +7,9 @@
 #include <lib/async/cpp/task.h>
 #include <zircon/status.h>
 
+#include <sstream>
+
+#include "compositor_context.h"
 #include "flutter/common/task_runners.h"
 #include "flutter/fml/make_copyable.h"
 #include "flutter/fml/synchronization/waitable_event.h"
@@ -14,21 +17,18 @@
 #include "flutter/runtime/dart_vm_lifecycle.h"
 #include "flutter/shell/common/rasterizer.h"
 #include "flutter/shell/common/run_configuration.h"
-#include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
-
-#include "../runtime/dart/utils/files.h"
-#include "compositor_context.h"
 #include "flutter_runner_product_configuration.h"
 #include "fuchsia_intl.h"
 #include "platform_view.h"
+#include "runtime/dart/utils/files.h"
 #include "task_runner_adapter.h"
+#include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
 #include "thread.h"
 
 namespace flutter_runner {
-namespace {
 
-void UpdateNativeThreadLabelNames(const std::string& label,
-                                  const flutter::TaskRunners& runners) {
+static void UpdateNativeThreadLabelNames(const std::string& label,
+                                         const flutter::TaskRunners& runners) {
   auto set_thread_name = [](fml::RefPtr<fml::TaskRunner> runner,
                             std::string prefix, std::string suffix) {
     if (!runner) {
@@ -44,15 +44,13 @@
   set_thread_name(runners.GetIOTaskRunner(), label, ".io");
 }
 
-fml::RefPtr<flutter::PlatformMessage> MakeLocalizationPlatformMessage(
+static fml::RefPtr<flutter::PlatformMessage> MakeLocalizationPlatformMessage(
     const fuchsia::intl::Profile& intl_profile) {
   return fml::MakeRefCounted<flutter::PlatformMessage>(
       "flutter/localization", MakeLocalizationPlatformMessageData(intl_profile),
       nullptr);
 }
 
-}  // namespace
-
 Engine::Engine(Delegate& delegate,
                std::string thread_label,
                std::shared_ptr<sys::ServiceDirectory> svc,
@@ -66,70 +64,28 @@
                FlutterRunnerProductConfiguration product_config)
     : delegate_(delegate),
       thread_label_(std::move(thread_label)),
+      settings_(std::move(settings)),
       weak_factory_(this) {
   if (zx::event::create(0, &vsync_event_) != ZX_OK) {
     FML_DLOG(ERROR) << "Could not create the vsync event.";
     return;
   }
 
-  // Get the task runners from the managed threads. The current thread will be
-  // used as the "platform" thread.
-  const flutter::TaskRunners task_runners(
-      thread_label_,  // Dart thread labels
-      CreateFMLTaskRunner(async_get_default_dispatcher()),  // platform
-      CreateFMLTaskRunner(threads_[0].dispatcher()),        // raster
-      CreateFMLTaskRunner(threads_[1].dispatcher()),        // ui
-      CreateFMLTaskRunner(threads_[2].dispatcher())         // io
-  );
-  UpdateNativeThreadLabelNames(thread_label_, task_runners);
+  // Launch the threads that will be used to run the shell. These threads will
+  // be joined in the destructor.
+  for (auto& thread : threads_) {
+    thread.reset(new Thread());
+  }
 
-  // Connect to Scenic.
+  // Set up the session connection.
   auto scenic = svc->Connect<fuchsia::ui::scenic::Scenic>();
   fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session;
   fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> session_listener;
   auto session_listener_request = session_listener.NewRequest();
   scenic->CreateSession(session.NewRequest(), session_listener.Bind());
 
-  // Make clones of the `ViewRef` before sending it down to Scenic.
-  fuchsia::ui::views::ViewRef platform_view_ref, isolate_view_ref;
-  view_ref_pair.view_ref.Clone(&platform_view_ref);
-  view_ref_pair.view_ref.Clone(&isolate_view_ref);
-
-  // Session is terminated on the raster thread, but we must terminate ourselves
-  // on the platform thread.
-  //
-  // This handles the fidl error callback when the Session connection is
-  // broken. The SessionListener interface also has an OnError method, which is
-  // invoked on the platform thread (in PlatformView).
-  fml::closure session_error_callback = [dispatcher =
-                                             async_get_default_dispatcher(),
-                                         weak = weak_factory_.GetWeakPtr()]() {
-    async::PostTask(dispatcher, [weak]() {
-      if (weak) {
-        weak->Terminate();
-      }
-    });
-  };
-
-  // Set up the session connection and other Scenic helpers on the raster
-  // thread.
-  task_runners.GetRasterTaskRunner()->PostTask(fml::MakeCopyable(
-      [this, session = std::move(session),
-       session_error_callback = std::move(session_error_callback),
-       view_token = std::move(view_token),
-       view_ref_pair = std::move(view_ref_pair),
-       vsync_handle = vsync_event_.get()]() mutable {
-        session_connection_.emplace(
-            thread_label_, std::move(session),
-            std::move(session_error_callback), [](auto) {}, vsync_handle);
-        surface_producer_.emplace(session_connection_->get());
-        scene_update_context_.emplace(thread_label_, std::move(view_token),
-                                      std::move(view_ref_pair),
-                                      session_connection_.value());
-      }));
-
-  // Grab the parent environment services. The platform view may want to
-  // access some of these services.
+  // Grab the parent environment services. The platform view may want to access
+  // some of these services.
   fuchsia::sys::EnvironmentPtr environment;
   svc->Connect(environment.NewRequest());
   fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>
@@ -137,15 +93,23 @@
   environment->GetServices(parent_environment_service_provider.NewRequest());
   environment.Unbind();
 
-  OnEnableWireframe on_enable_wireframe_callback = std::bind(
-      &Engine::DebugWireframeSettingsChanged, this, std::placeholders::_1);
+  // We need to manually schedule a frame when the session metrics change.
+  OnMetricsUpdate on_session_metrics_change_callback = std::bind(
+      &Engine::OnSessionMetricsDidChange, this, std::placeholders::_1);
 
-  OnCreateView on_create_view_callback =
-      std::bind(&Engine::CreateView, this, std::placeholders::_1,
+  OnSizeChangeHint on_session_size_change_hint_callback =
+      std::bind(&Engine::OnSessionSizeChangeHint, this, std::placeholders::_1,
+                std::placeholders::_2);
+
+  OnEnableWireframe on_enable_wireframe_callback = std::bind(
+      &Engine::OnDebugWireframeSettingsChanged, this, std::placeholders::_1);
+
+  flutter_runner::OnCreateView on_create_view_callback =
+      std::bind(&Engine::OnCreateView, this, std::placeholders::_1,
                 std::placeholders::_2, std::placeholders::_3);
 
-  OnDestroyView on_destroy_view_callback =
-      std::bind(&Engine::DestroyView, this, std::placeholders::_1);
+  flutter_runner::OnDestroyView on_destroy_view_callback =
+      std::bind(&Engine::OnDestroyView, this, std::placeholders::_1);
 
   OnGetViewEmbedder on_get_view_embedder_callback =
       std::bind(&Engine::GetViewEmbedder, this);
@@ -163,6 +127,10 @@
         });
       };
 
+  fuchsia::ui::views::ViewRef platform_view_ref, isolate_view_ref;
+  view_ref_pair.view_ref.Clone(&platform_view_ref);
+  view_ref_pair.view_ref.Clone(&isolate_view_ref);
+
   // Setup the callback that will instantiate the platform view.
   flutter::Shell::CreateCallback<flutter::PlatformView>
       on_create_platform_view = fml::MakeCopyable(
@@ -173,6 +141,10 @@
            session_listener_request = std::move(session_listener_request),
            on_session_listener_error_callback =
                std::move(on_session_listener_error_callback),
+           on_session_metrics_change_callback =
+               std::move(on_session_metrics_change_callback),
+           on_session_size_change_hint_callback =
+               std::move(on_session_size_change_hint_callback),
            on_enable_wireframe_callback =
                std::move(on_enable_wireframe_callback),
            on_create_view_callback = std::move(on_create_view_callback),
@@ -190,6 +162,8 @@
                 std::move(parent_environment_service_provider),  // services
                 std::move(session_listener_request),  // session listener
                 std::move(on_session_listener_error_callback),
+                std::move(on_session_metrics_change_callback),
+                std::move(on_session_size_change_hint_callback),
                 std::move(on_enable_wireframe_callback),
                 std::move(on_create_view_callback),
                 std::move(on_destroy_view_callback),
@@ -198,17 +172,53 @@
                 product_config);
           });
 
+  // Session can be terminated on the raster thread, but we must terminate
+  // ourselves on the platform thread.
+  //
+  // This handles the fidl error callback when the Session connection is
+  // broken. The SessionListener interface also has an OnError method, which is
+  // invoked on the platform thread (in PlatformView).
+  fml::closure on_session_error_callback =
+      [dispatcher = async_get_default_dispatcher(),
+       weak = weak_factory_.GetWeakPtr()]() {
+        async::PostTask(dispatcher, [weak]() {
+          if (weak) {
+            weak->Terminate();
+          }
+        });
+      };
+
+  // Get the task runners from the managed threads. The current thread will be
+  // used as the "platform" thread.
+  const flutter::TaskRunners task_runners(
+      thread_label_,  // Dart thread labels
+      CreateFMLTaskRunner(async_get_default_dispatcher()),  // platform
+      CreateFMLTaskRunner(threads_[0]->dispatcher()),       // raster
+      CreateFMLTaskRunner(threads_[1]->dispatcher()),       // ui
+      CreateFMLTaskRunner(threads_[2]->dispatcher())        // io
+  );
+
   // Setup the callback that will instantiate the rasterizer.
   flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
-      fml::MakeCopyable([this](flutter::Shell& shell) mutable {
-        FML_DCHECK(session_connection_);
-        FML_DCHECK(surface_producer_);
-        FML_DCHECK(scene_update_context_);
-
-        std::unique_ptr<flutter_runner::CompositorContext> compositor_context =
-            std::make_unique<flutter_runner::CompositorContext>(
-                session_connection_.value(), surface_producer_.value(),
-                scene_update_context_.value());
+      fml::MakeCopyable([thread_label = thread_label_,              //
+                         view_token = std::move(view_token),        //
+                         view_ref_pair = std::move(view_ref_pair),  //
+                         session = std::move(session),              //
+                         on_session_error_callback,                 //
+                         vsync_event = vsync_event_.get()           //
+  ](flutter::Shell& shell) mutable {
+        std::unique_ptr<flutter_runner::CompositorContext> compositor_context;
+        {
+          TRACE_DURATION("flutter", "CreateCompositorContext");
+          compositor_context =
+              std::make_unique<flutter_runner::CompositorContext>(
+                  thread_label,           // debug label
+                  std::move(view_token),  // scenic view we attach our tree to
+                  std::move(view_ref_pair),  // scenic view ref/view ref control
+                  std::move(session),        // scenic session
+                  on_session_error_callback,  // session did encounter error
+                  vsync_event);               // vsync event handle
+        }
 
         return std::make_unique<flutter::Rasterizer>(
             /*task_runners=*/shell.GetTaskRunners(),
@@ -216,9 +226,18 @@
             /*is_gpu_disabled_sync_switch=*/shell.GetIsGpuDisabledSyncSwitch());
       });
 
-  settings.root_isolate_create_callback =
+  UpdateNativeThreadLabelNames(thread_label_, task_runners);
+
+  settings_.verbose_logging = true;
+
+  settings_.advisory_script_uri = thread_label_;
+
+  settings_.advisory_script_entrypoint = thread_label_;
+
+  settings_.root_isolate_create_callback =
       std::bind(&Engine::OnMainIsolateStart, this);
-  settings.root_isolate_shutdown_callback =
+
+  settings_.root_isolate_shutdown_callback =
       std::bind([weak = weak_factory_.GetWeakPtr(),
                  runner = task_runners.GetPlatformTaskRunner()]() {
         runner->PostTask([weak = std::move(weak)] {
@@ -228,7 +247,7 @@
         });
       });
 
-  auto vm = flutter::DartVMRef::Create(settings);
+  auto vm = flutter::DartVMRef::Create(settings_);
 
   if (!isolate_snapshot) {
     isolate_snapshot = vm->GetVMData()->GetIsolateSnapshot();
@@ -237,13 +256,13 @@
   {
     TRACE_EVENT0("flutter", "CreateShell");
     shell_ = flutter::Shell::Create(
-        std::move(task_runners),             // host task runners
-        flutter::PlatformData(),             // default window data
-        std::move(settings),                 // shell launch settings
-        std::move(isolate_snapshot),         // isolate snapshot
-        std::move(on_create_platform_view),  // platform view create callback
-        std::move(on_create_rasterizer),     // rasterizer create callback
-        std::move(vm)                        // vm reference
+        task_runners,                 // host task runners
+        flutter::PlatformData(),      // default window data
+        settings_,                    // shell launch settings
+        std::move(isolate_snapshot),  // isolate snapshot
+        on_create_platform_view,      // platform view create callback
+        on_create_rasterizer,         // rasterizer create callback
+        std::move(vm)                 // vm reference
     );
   }
 
@@ -320,7 +339,7 @@
 
   // Launch the engine in the appropriate configuration.
   auto run_configuration = flutter::RunConfiguration::InferFromSettings(
-      shell_->GetSettings(), shell_->GetTaskRunners().GetIOTaskRunner());
+      settings_, task_runners.GetIOTaskRunner());
 
   auto on_run_failure = [weak = weak_factory_.GetWeakPtr()]() {
     // The engine could have been killed by the caller right after the
@@ -357,11 +376,11 @@
 
 Engine::~Engine() {
   shell_.reset();
-  for (auto& thread : threads_) {
-    thread.Quit();
+  for (const auto& thread : threads_) {
+    thread->Quit();
   }
-  for (auto& thread : threads_) {
-    thread.Join();
+  for (const auto& thread : threads_) {
+    thread->Join();
   }
 }
 
@@ -466,41 +485,105 @@
   // collected this object.
 }
 
-void Engine::DebugWireframeSettingsChanged(bool enabled) {
-  if (!shell_ || !scene_update_context_) {
+void Engine::OnSessionMetricsDidChange(
+    const fuchsia::ui::gfx::Metrics& metrics) {
+  if (!shell_) {
     return;
   }
 
   shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
-      [this, enabled]() { scene_update_context_->EnableWireframe(enabled); });
-}
+      [rasterizer = shell_->GetRasterizer(), metrics]() {
+        if (rasterizer) {
+          auto compositor_context =
+              reinterpret_cast<flutter_runner::CompositorContext*>(
+                  rasterizer->compositor_context());
 
-void Engine::CreateView(int64_t view_id, bool hit_testable, bool focusable) {
-  if (!shell_ || !scene_update_context_) {
-    return;
-  }
-
-  shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
-      [this, view_id, hit_testable, focusable]() {
-        scene_update_context_->CreateView(view_id, hit_testable, focusable);
+          compositor_context->OnSessionMetricsDidChange(metrics);
+        }
       });
 }
 
-void Engine::DestroyView(int64_t view_id) {
-  if (!shell_ || !scene_update_context_) {
+void Engine::OnDebugWireframeSettingsChanged(bool enabled) {
+  if (!shell_) {
     return;
   }
 
   shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
-      [this, view_id]() { scene_update_context_->DestroyView(view_id); });
+      [rasterizer = shell_->GetRasterizer(), enabled]() {
+        if (rasterizer) {
+          auto compositor_context =
+              reinterpret_cast<flutter_runner::CompositorContext*>(
+                  rasterizer->compositor_context());
+
+          compositor_context->OnWireframeEnabled(enabled);
+        }
+      });
+}
+
+void Engine::OnCreateView(int64_t view_id, bool hit_testable, bool focusable) {
+  if (!shell_) {
+    return;
+  }
+
+  shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
+      [rasterizer = shell_->GetRasterizer(), view_id, hit_testable,
+       focusable]() {
+        if (rasterizer) {
+          auto compositor_context =
+              reinterpret_cast<flutter_runner::CompositorContext*>(
+                  rasterizer->compositor_context());
+          compositor_context->OnCreateView(view_id, hit_testable, focusable);
+        }
+      });
+}
+
+void Engine::OnDestroyView(int64_t view_id) {
+  if (!shell_) {
+    return;
+  }
+
+  shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
+      [rasterizer = shell_->GetRasterizer(), view_id]() {
+        if (rasterizer) {
+          auto compositor_context =
+              reinterpret_cast<flutter_runner::CompositorContext*>(
+                  rasterizer->compositor_context());
+          compositor_context->OnDestroyView(view_id);
+        }
+      });
 }
 
 flutter::ExternalViewEmbedder* Engine::GetViewEmbedder() {
-  if (!scene_update_context_) {
-    return nullptr;
+  // GetEmbedder should be called only after rasterizer is created.
+  FML_DCHECK(shell_);
+  FML_DCHECK(shell_->GetRasterizer());
+
+  auto rasterizer = shell_->GetRasterizer();
+  auto compositor_context =
+      reinterpret_cast<flutter_runner::CompositorContext*>(
+          rasterizer->compositor_context());
+  flutter::ExternalViewEmbedder* view_embedder =
+      compositor_context->GetViewEmbedder();
+  return view_embedder;
+}
+
+void Engine::OnSessionSizeChangeHint(float width_change_factor,
+                                     float height_change_factor) {
+  if (!shell_) {
+    return;
   }
 
-  return &scene_update_context_.value();
+  shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(
+      [rasterizer = shell_->GetRasterizer(), width_change_factor,
+       height_change_factor]() {
+        if (rasterizer) {
+          auto compositor_context = reinterpret_cast<CompositorContext*>(
+              rasterizer->compositor_context());
+
+          compositor_context->OnSessionSizeChangeHint(width_change_factor,
+                                                      height_change_factor);
+        }
+      });
 }
 
 #if !defined(DART_PRODUCT)
diff --git a/shell/platform/fuchsia/flutter/engine.h b/shell/platform/fuchsia/flutter/engine.h
index fdd7f68..5ed4139 100644
--- a/shell/platform/fuchsia/flutter/engine.h
+++ b/shell/platform/fuchsia/flutter/engine.h
@@ -15,15 +15,11 @@
 #include <lib/zx/event.h>
 
 #include "flutter/flow/embedded_views.h"
-#include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/macros.h"
 #include "flutter/shell/common/shell.h"
-
 #include "flutter_runner_product_configuration.h"
 #include "isolate_configurator.h"
-#include "session_connection.h"
 #include "thread.h"
-#include "vulkan_surface_producer.h"
 
 namespace flutter_runner {
 
@@ -59,22 +55,15 @@
 
  private:
   Delegate& delegate_;
-
   const std::string thread_label_;
-  std::array<Thread, 3> threads_;
-
-  std::optional<SessionConnection> session_connection_;
-  std::optional<VulkanSurfaceProducer> surface_producer_;
-  std::optional<flutter::SceneUpdateContext> scene_update_context_;
-
+  flutter::Settings settings_;
+  std::array<std::unique_ptr<Thread>, 3> threads_;
   std::unique_ptr<IsolateConfigurator> isolate_configurator_;
   std::unique_ptr<flutter::Shell> shell_;
-
-  fuchsia::intl::PropertyProviderPtr intl_property_provider_;
-
   zx::event vsync_event_;
-
   fml::WeakPtrFactory<Engine> weak_factory_;
+  // A stub for the FIDL protocol fuchsia.intl.PropertyProvider.
+  fuchsia::intl::PropertyProviderPtr intl_property_provider_;
 
   void OnMainIsolateStart();
 
@@ -82,9 +71,15 @@
 
   void Terminate();
 
-  void DebugWireframeSettingsChanged(bool enabled);
-  void CreateView(int64_t view_id, bool hit_testable, bool focusable);
-  void DestroyView(int64_t view_id);
+  void OnSessionMetricsDidChange(const fuchsia::ui::gfx::Metrics& metrics);
+  void OnSessionSizeChangeHint(float width_change_factor,
+                               float height_change_factor);
+
+  void OnDebugWireframeSettingsChanged(bool enabled);
+
+  void OnCreateView(int64_t view_id, bool hit_testable, bool focusable);
+
+  void OnDestroyView(int64_t view_id);
 
   flutter::ExternalViewEmbedder* GetViewEmbedder();
 
diff --git a/shell/platform/fuchsia/flutter/engine_flutter_runner.gni b/shell/platform/fuchsia/flutter/engine_flutter_runner.gni
new file mode 100644
index 0000000..630575b
--- /dev/null
+++ b/shell/platform/fuchsia/flutter/engine_flutter_runner.gni
@@ -0,0 +1,150 @@
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_fuchsia)
+
+import("//build/fuchsia/sdk.gni")
+
+# Builds a flutter_runner
+#
+# Parameters:
+#
+#  output_name (required):
+#    The name of the resulting binary.
+#
+#  extra_deps (required):
+#    Any additional dependencies.
+#
+#  product (required):
+#    Whether to link against a Product mode Dart VM.
+#
+#  extra_defines (optional):
+#    Any additional preprocessor defines.
+template("flutter_runner") {
+  assert(defined(invoker.output_name), "flutter_runner must define output_name")
+  assert(defined(invoker.extra_deps), "flutter_runner must define extra_deps")
+  assert(defined(invoker.product), "flutter_runner must define product")
+
+  invoker_output_name = invoker.output_name
+  extra_deps = invoker.extra_deps
+
+  extra_defines = []
+  if (defined(invoker.extra_defines)) {
+    extra_defines += invoker.extra_defines
+  }
+
+  executable(target_name) {
+    output_name = invoker_output_name
+
+    defines = extra_defines
+
+    libs = []
+
+    sources = [
+      "accessibility_bridge.cc",
+      "accessibility_bridge.h",
+      "component.cc",
+      "component.h",
+      "compositor_context.cc",
+      "compositor_context.h",
+      "engine.cc",
+      "engine.h",
+      "flutter_runner_product_configuration.cc",
+      "flutter_runner_product_configuration.h",
+      "fuchsia_intl.cc",
+      "fuchsia_intl.h",
+      "isolate_configurator.cc",
+      "isolate_configurator.h",
+      "logging.h",
+      "loop.cc",
+      "loop.h",
+      "main.cc",
+      "platform_view.cc",
+      "platform_view.h",
+      "runner.cc",
+      "runner.h",
+      "session_connection.cc",
+      "session_connection.h",
+      "surface.cc",
+      "surface.h",
+      "task_observers.cc",
+      "task_observers.h",
+      "task_runner_adapter.cc",
+      "task_runner_adapter.h",
+      "thread.cc",
+      "thread.h",
+      "unique_fdio_ns.h",
+      "vsync_recorder.cc",
+      "vsync_recorder.h",
+      "vsync_waiter.cc",
+      "vsync_waiter.h",
+      "vulkan_surface.cc",
+      "vulkan_surface.h",
+      "vulkan_surface_pool.cc",
+      "vulkan_surface_pool.h",
+      "vulkan_surface_producer.cc",
+      "vulkan_surface_producer.h",
+    ]
+
+    # The use of these dependencies is temporary and will be moved behind the
+    # embedder API.
+    flutter_deps = [
+      "../flutter:fuchsia_legacy_gpu_configuration",
+      "//flutter/assets",
+      "//flutter/common",
+      "//flutter/flow:flow_fuchsia_legacy",
+      "//flutter/fml",
+      "//flutter/lib/ui:ui_fuchsia_legacy",
+      "//flutter/runtime:runtime_fuchsia_legacy",
+      "//flutter/shell/common:common_fuchsia_legacy",
+      "//flutter/vulkan",
+    ]
+
+    _fuchsia_platform = "//flutter/shell/platform/fuchsia"
+
+    # TODO(kaushikiska) evaluate if all of these are needed.
+    fuchsia_deps = [
+      "${_fuchsia_platform}/dart-pkg/fuchsia",
+      "${_fuchsia_platform}/dart-pkg/zircon",
+      "${_fuchsia_platform}/runtime/dart/utils",
+    ]
+
+    deps = [
+             "$fuchsia_sdk_root/fidl:fuchsia.accessibility.semantics",
+             "$fuchsia_sdk_root/fidl:fuchsia.fonts",
+             "$fuchsia_sdk_root/fidl:fuchsia.images",
+             "$fuchsia_sdk_root/fidl:fuchsia.intl",
+             "$fuchsia_sdk_root/fidl:fuchsia.io",
+             "$fuchsia_sdk_root/fidl:fuchsia.sys",
+             "$fuchsia_sdk_root/fidl:fuchsia.ui.app",
+             "$fuchsia_sdk_root/fidl:fuchsia.ui.scenic",
+             "$fuchsia_sdk_root/pkg:async-cpp",
+             "$fuchsia_sdk_root/pkg:async-default",
+             "$fuchsia_sdk_root/pkg:async-loop",
+             "$fuchsia_sdk_root/pkg:async-loop-cpp",
+             "$fuchsia_sdk_root/pkg:fdio",
+             "$fuchsia_sdk_root/pkg:fidl_cpp",
+             "$fuchsia_sdk_root/pkg:scenic_cpp",
+             "$fuchsia_sdk_root/pkg:sys_cpp",
+             "$fuchsia_sdk_root/pkg:syslog",
+             "$fuchsia_sdk_root/pkg:trace",
+             "$fuchsia_sdk_root/pkg:trace-engine",
+             "$fuchsia_sdk_root/pkg:trace-provider-so",
+             "$fuchsia_sdk_root/pkg:vfs_cpp",
+             "$fuchsia_sdk_root/pkg:zx",
+             "//third_party/skia",
+             "//flutter/third_party/tonic",
+           ] + fuchsia_deps + flutter_deps + extra_deps
+
+    # The flags below are needed so that Dart's CPU profiler can walk the
+    # C++ stack.
+    cflags = [ "-fno-omit-frame-pointer" ]
+
+    if (!invoker.product) {
+      # This flag is needed so that the call to dladdr() in Dart's native symbol
+      # resolver can report good symbol information for the CPU profiler.
+      ldflags = [ "-rdynamic" ]
+    }
+  }
+}
diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc
index 81441b4..df2b454 100644
--- a/shell/platform/fuchsia/flutter/platform_view.cc
+++ b/shell/platform/fuchsia/flutter/platform_view.cc
@@ -6,7 +6,6 @@
 
 #include "platform_view.h"
 
-#include <fuchsia/ui/gfx/cpp/fidl.h>
 #include <sstream>
 
 #include "flutter/fml/logging.h"
@@ -23,6 +22,41 @@
 
 namespace flutter_runner {
 
+namespace {
+
+inline fuchsia::ui::gfx::vec3 Add(const fuchsia::ui::gfx::vec3& a,
+                                  const fuchsia::ui::gfx::vec3& b) {
+  return {.x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z};
+}
+
+inline fuchsia::ui::gfx::vec3 Subtract(const fuchsia::ui::gfx::vec3& a,
+                                       const fuchsia::ui::gfx::vec3& b) {
+  return {.x = a.x - b.x, .y = a.y - b.y, .z = a.z - b.z};
+}
+
+inline fuchsia::ui::gfx::BoundingBox InsetBy(
+    const fuchsia::ui::gfx::BoundingBox& box,
+    const fuchsia::ui::gfx::vec3& inset_from_min,
+    const fuchsia::ui::gfx::vec3& inset_from_max) {
+  return {.min = Add(box.min, inset_from_min),
+          .max = Subtract(box.max, inset_from_max)};
+}
+
+inline fuchsia::ui::gfx::BoundingBox ViewPropertiesLayoutBox(
+    const fuchsia::ui::gfx::ViewProperties& view_properties) {
+  return InsetBy(view_properties.bounding_box, view_properties.inset_from_min,
+                 view_properties.inset_from_max);
+}
+
+inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& v,
+                                  float min_val) {
+  return {.x = std::max(v.x, min_val),
+          .y = std::max(v.y, min_val),
+          .z = std::max(v.z, min_val)};
+}
+
+}  // end namespace
+
 static constexpr char kFlutterPlatformChannel[] = "flutter/platform";
 static constexpr char kTextInputChannel[] = "flutter/textinput";
 static constexpr char kKeyEventChannel[] = "flutter/keyevent";
@@ -55,6 +89,8 @@
     fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
         session_listener_request,
     fit::closure session_listener_error_callback,
+    OnMetricsUpdate session_metrics_did_change_callback,
+    OnSizeChangeHint session_size_change_hint_callback,
     OnEnableWireframe wireframe_enabled_callback,
     OnCreateView on_create_view_callback,
     OnDestroyView on_destroy_view_callback,
@@ -67,6 +103,8 @@
       session_listener_binding_(this, std::move(session_listener_request)),
       session_listener_error_callback_(
           std::move(session_listener_error_callback)),
+      metrics_changed_callback_(std::move(session_metrics_did_change_callback)),
+      size_change_hint_callback_(std::move(session_size_change_hint_callback)),
       wireframe_enabled_callback_(std::move(wireframe_enabled_callback)),
       on_create_view_callback_(std::move(on_create_view_callback)),
       on_destroy_view_callback_(std::move(on_destroy_view_callback)),
@@ -114,6 +152,64 @@
                 this, std::placeholders::_1);
 }
 
+void PlatformView::OnPropertiesChanged(
+    const fuchsia::ui::gfx::ViewProperties& view_properties) {
+  fuchsia::ui::gfx::BoundingBox layout_box =
+      ViewPropertiesLayoutBox(view_properties);
+
+  fuchsia::ui::gfx::vec3 logical_size =
+      Max(Subtract(layout_box.max, layout_box.min), 0.f);
+
+  metrics_.size.width = logical_size.x;
+  metrics_.size.height = logical_size.y;
+  metrics_.size.depth = logical_size.z;
+  metrics_.padding.left = view_properties.inset_from_min.x;
+  metrics_.padding.top = view_properties.inset_from_min.y;
+  metrics_.padding.front = view_properties.inset_from_min.z;
+  metrics_.padding.right = view_properties.inset_from_max.x;
+  metrics_.padding.bottom = view_properties.inset_from_max.y;
+  metrics_.padding.back = view_properties.inset_from_max.z;
+
+  FlushViewportMetrics();
+}
+
+// TODO(SCN-975): Re-enable.
+// void PlatformView::ConnectSemanticsProvider(
+//     fuchsia::ui::viewsv1token::ViewToken token) {
+//   semantics_bridge_.SetupEnvironment(
+//       token.value, parent_environment_service_provider_.get());
+// }
+
+void PlatformView::UpdateViewportMetrics(
+    const fuchsia::ui::gfx::Metrics& metrics) {
+  metrics_.scale = metrics.scale_x;
+  metrics_.scale_z = metrics.scale_z;
+
+  FlushViewportMetrics();
+}
+
+void PlatformView::FlushViewportMetrics() {
+  const auto scale = metrics_.scale;
+  const auto scale_z = metrics_.scale_z;
+
+  SetViewportMetrics({
+      scale,                                // device_pixel_ratio
+      metrics_.size.width * scale,          // physical_width
+      metrics_.size.height * scale,         // physical_height
+      metrics_.size.depth * scale_z,        // physical_depth
+      metrics_.padding.top * scale,         // physical_padding_top
+      metrics_.padding.right * scale,       // physical_padding_right
+      metrics_.padding.bottom * scale,      // physical_padding_bottom
+      metrics_.padding.left * scale,        // physical_padding_left
+      metrics_.view_inset.front * scale_z,  // physical_view_inset_front
+      metrics_.view_inset.back * scale_z,   // physical_view_inset_back
+      metrics_.view_inset.top * scale,      // physical_view_inset_top
+      metrics_.view_inset.right * scale,    // physical_view_inset_right
+      metrics_.view_inset.bottom * scale,   // physical_view_inset_bottom
+      metrics_.view_inset.left * scale      // physical_view_inset_left
+  });
+}
+
 // |fuchsia::ui::input::InputMethodEditorClient|
 void PlatformView::DidUpdateState(
     fuchsia::ui::input::TextInputState state,
@@ -206,40 +302,27 @@
 void PlatformView::OnScenicEvent(
     std::vector<fuchsia::ui::scenic::Event> events) {
   TRACE_EVENT0("flutter", "PlatformView::OnScenicEvent");
-  bool should_update_metrics = false;
   for (const auto& event : events) {
     switch (event.Which()) {
       case fuchsia::ui::scenic::Event::Tag::kGfx:
         switch (event.gfx().Which()) {
           case fuchsia::ui::gfx::Event::Tag::kMetrics: {
-            const fuchsia::ui::gfx::Metrics& metrics =
-                event.gfx().metrics().metrics;
-            const float new_view_pixel_ratio = metrics.scale_x;
-
-            // Avoid metrics update when possible -- it is computationally
-            // expensive.
-            if (view_pixel_ratio_ != new_view_pixel_ratio) {
-              view_pixel_ratio_ = new_view_pixel_ratio;
-              should_update_metrics = true;
+            if (!fidl::Equals(event.gfx().metrics().metrics, scenic_metrics_)) {
+              scenic_metrics_ = std::move(event.gfx().metrics().metrics);
+              metrics_changed_callback_(scenic_metrics_);
+              UpdateViewportMetrics(scenic_metrics_);
             }
             break;
           }
+          case fuchsia::ui::gfx::Event::Tag::kSizeChangeHint: {
+            size_change_hint_callback_(
+                event.gfx().size_change_hint().width_change_factor,
+                event.gfx().size_change_hint().height_change_factor);
+            break;
+          }
           case fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: {
-            const fuchsia::ui::gfx::BoundingBox& bounding_box =
-                event.gfx().view_properties_changed().properties.bounding_box;
-            const float new_view_width =
-                std::max(bounding_box.max.x - bounding_box.min.x, 0.0f);
-            const float new_view_height =
-                std::max(bounding_box.max.y - bounding_box.min.y, 0.0f);
-
-            // Avoid metrics update when possible -- it is computationally
-            // expensive.
-            if (view_width_ != new_view_width ||
-                view_height_ != new_view_width) {
-              view_width_ = new_view_width;
-              view_height_ = new_view_height;
-              should_update_metrics = true;
-            }
+            OnPropertiesChanged(
+                std::move(event.gfx().view_properties_changed().properties));
             break;
           }
           case fuchsia::ui::gfx::Event::Tag::kViewConnected:
@@ -289,26 +372,6 @@
       }
     }
   }
-
-  if (should_update_metrics) {
-    SetViewportMetrics({
-        view_pixel_ratio_,                 // device_pixel_ratio
-        view_width_ * view_pixel_ratio_,   // physical_width
-        view_height_ * view_pixel_ratio_,  // physical_height
-        0.0f,                              // physical_padding_top
-        0.0f,                              // physical_padding_right
-        0.0f,                              // physical_padding_bottom
-        0.0f,                              // physical_padding_left
-        0.0f,                              // physical_view_inset_top
-        0.0f,                              // physical_view_inset_right
-        0.0f,                              // physical_view_inset_bottom
-        0.0f,                              // physical_view_inset_left
-        0.0f,  // p_physical_system_gesture_inset_top
-        0.0f,  // p_physical_system_gesture_inset_right
-        0.0f,  // p_physical_system_gesture_inset_bottom
-        0.0f,  // p_physical_system_gesture_inset_left
-    });
-  }
 }
 
 void PlatformView::OnChildViewConnected(scenic::ResourceId view_holder_id) {
@@ -388,9 +451,8 @@
   pointer_data.change = GetChangeFromPointerEventPhase(pointer.phase);
   pointer_data.kind = GetKindFromPointerType(pointer.type);
   pointer_data.device = pointer.pointer_id;
-  // Pointer events are in logical pixels, so scale to physical.
-  pointer_data.physical_x = pointer.x * view_pixel_ratio_;
-  pointer_data.physical_y = pointer.y * view_pixel_ratio_;
+  pointer_data.physical_x = pointer.x * metrics_.scale;
+  pointer_data.physical_y = pointer.y * metrics_.scale;
   // Buttons are single bit values starting with kMousePrimaryButton = 1.
   pointer_data.buttons = static_cast<uint64_t>(pointer.buttons);
 
diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h
index b6dd8e1..45e1e1f 100644
--- a/shell/platform/fuchsia/flutter/platform_view.h
+++ b/shell/platform/fuchsia/flutter/platform_view.h
@@ -5,6 +5,7 @@
 #ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_PLATFORM_VIEW_H_
 #define FLUTTER_SHELL_PLATFORM_FUCHSIA_PLATFORM_VIEW_H_
 
+#include <fuchsia/ui/gfx/cpp/fidl.h>
 #include <fuchsia/ui/input/cpp/fidl.h>
 #include <fuchsia/ui/scenic/cpp/fidl.h>
 #include <lib/fit/function.h>
@@ -13,6 +14,7 @@
 #include <set>
 
 #include "flutter/fml/macros.h"
+#include "flutter/lib/ui/window/viewport_metrics.h"
 #include "flutter/shell/common/platform_view.h"
 #include "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
 #include "flutter_runner_product_configuration.h"
@@ -22,6 +24,9 @@
 
 namespace flutter_runner {
 
+using OnMetricsUpdate = fit::function<void(const fuchsia::ui::gfx::Metrics&)>;
+using OnSizeChangeHint =
+    fit::function<void(float width_change_factor, float height_change_factor)>;
 using OnEnableWireframe = fit::function<void(bool)>;
 using OnCreateView = fit::function<void(int64_t, bool, bool)>;
 using OnDestroyView = fit::function<void(int64_t)>;
@@ -48,15 +53,25 @@
                fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
                    session_listener_request,
                fit::closure on_session_listener_error_callback,
+               OnMetricsUpdate session_metrics_did_change_callback,
+               OnSizeChangeHint session_size_change_hint_callback,
                OnEnableWireframe wireframe_enabled_callback,
                OnCreateView on_create_view_callback,
                OnDestroyView on_destroy_view_callback,
                OnGetViewEmbedder on_get_view_embedder_callback,
                zx_handle_t vsync_event_handle,
                FlutterRunnerProductConfiguration product_config);
+  PlatformView(flutter::PlatformView::Delegate& delegate,
+               std::string debug_label,
+               flutter::TaskRunners task_runners,
+               fidl::InterfaceHandle<fuchsia::sys::ServiceProvider>
+                   parent_environment_service_provider,
+               zx_handle_t vsync_event_handle);
 
   ~PlatformView();
 
+  void UpdateViewportMetrics(const fuchsia::ui::gfx::Metrics& metrics);
+
   // |flutter::PlatformView|
   // |flutter_runner::AccessibilityBridge::Delegate|
   void SetSemanticsEnabled(bool enabled) override;
@@ -77,6 +92,8 @@
 
   fidl::Binding<fuchsia::ui::scenic::SessionListener> session_listener_binding_;
   fit::closure session_listener_error_callback_;
+  OnMetricsUpdate metrics_changed_callback_;
+  OnSizeChangeHint size_change_hint_callback_;
   OnEnableWireframe wireframe_enabled_callback_;
   OnCreateView on_create_view_callback_;
   OnDestroyView on_destroy_view_callback_;
@@ -88,7 +105,8 @@
   fuchsia::ui::input::ImeServicePtr text_sync_service_;
 
   fuchsia::sys::ServiceProviderPtr parent_environment_service_provider_;
-
+  flutter::LogicalMetrics metrics_;
+  fuchsia::ui::gfx::Metrics scenic_metrics_;
   // last_text_state_ is the last state of the text input as reported by the IME
   // or initialized by Flutter. We set it to null if Flutter doesn't want any
   // input, since then there is no text input state at all.
@@ -106,14 +124,16 @@
   std::set<std::string /* channel */> unregistered_channels_;
   zx_handle_t vsync_event_handle_ = 0;
 
-  float view_width_ = 0.0f;        // Width in logical pixels.
-  float view_height_ = 0.0f;       // Height in logical pixels.
-  float view_pixel_ratio_ = 0.0f;  // Logical / physical pixel ratio.
-
   FlutterRunnerProductConfiguration product_config_;
 
   void RegisterPlatformMessageHandlers();
 
+  void FlushViewportMetrics();
+
+  // Called when the view's properties have changed.
+  void OnPropertiesChanged(
+      const fuchsia::ui::gfx::ViewProperties& view_properties);
+
   // |fuchsia::ui::input::InputMethodEditorClient|
   void DidUpdateState(
       fuchsia::ui::input::TextInputState state,
diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc
index 7a1929d..89a5658 100644
--- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc
+++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc
@@ -4,7 +4,7 @@
 
 #include "flutter/shell/platform/fuchsia/flutter/platform_view.h"
 
-#include <fuchsia/ui/views/cpp/fidl.h>
+#include <gtest/gtest.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/default.h>
 #include <lib/async/default.h>
@@ -15,11 +15,11 @@
 #include <memory>
 #include <vector>
 
-#include "flutter/flow/embedded_views.h"
+#include "flutter/flow/scene_update_context.h"
 #include "flutter/lib/ui/window/platform_message.h"
 #include "flutter/lib/ui/window/window.h"
+#include "fuchsia/ui/views/cpp/fidl.h"
 #include "gtest/gtest.h"
-
 #include "task_runner_adapter.h"
 
 namespace flutter_runner_test::flutter_runner_a11y_test {
@@ -41,33 +41,6 @@
   FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewTests);
 };
 
-class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder {
- public:
-  MockExternalViewEmbedder() = default;
-  ~MockExternalViewEmbedder() override = default;
-
-  SkCanvas* GetRootCanvas() override { return nullptr; }
-  std::vector<SkCanvas*> GetCurrentCanvases() override {
-    return std::vector<SkCanvas*>();
-  }
-
-  void CancelFrame() override {}
-  void BeginFrame(
-      SkISize frame_size,
-      GrDirectContext* context,
-      double device_pixel_ratio,
-      fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override {}
-  void SubmitFrame(GrDirectContext* context,
-                   std::unique_ptr<flutter::SurfaceFrame> frame) override {
-    return;
-  }
-
-  void PrerollCompositeEmbeddedView(
-      int view_id,
-      std::unique_ptr<flutter::EmbeddedViewParams> params) override {}
-  SkCanvas* CompositeEmbeddedView(int view_id) override { return nullptr; }
-};
-
 class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
  public:
   // |flutter::PlatformView::Delegate|
@@ -126,6 +99,30 @@
   int32_t semantics_features_ = 0;
 };
 
+class MockSurfaceProducer
+    : public flutter::SceneUpdateContext::SurfaceProducer {
+ public:
+  std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+  ProduceSurface(const SkISize& size,
+                 const flutter::LayerRasterCacheKey& layer_key,
+                 std::unique_ptr<scenic::EntityNode> entity_node) override {
+    return nullptr;
+  }
+
+  bool HasRetainedNode(const flutter::LayerRasterCacheKey& key) const override {
+    return false;
+  }
+
+  scenic::EntityNode* GetRetainedNode(
+      const flutter::LayerRasterCacheKey& key) override {
+    return nullptr;
+  }
+
+  void SubmitSurface(
+      std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+          surface) override {}
+};
+
 TEST_F(PlatformViewTests, ChangesAccessibilitySettings) {
   sys::testing::ServiceDirectoryProvider services_provider(dispatcher());
 
@@ -150,6 +147,8 @@
       nullptr,  // parent_environment_service_provider_handle
       nullptr,  // session_listener_request
       nullptr,  // on_session_listener_error_callback
+      nullptr,  // session_metrics_did_change_callback
+      nullptr,  // session_size_change_hint_callback
       nullptr,  // on_enable_wireframe_callback,
       nullptr,  // on_create_view_callback,
       nullptr,  // on_destroy_view_callback,
@@ -204,6 +203,8 @@
       nullptr,                  // parent_environment_service_provider_handle
       nullptr,                  // session_listener_request
       nullptr,                  // on_session_listener_error_callback
+      nullptr,                  // session_metrics_did_change_callback
+      nullptr,                  // session_size_change_hint_callback
       EnableWireframeCallback,  // on_enable_wireframe_callback,
       nullptr,                  // on_create_view_callback,
       nullptr,                  // on_destroy_view_callback,
@@ -269,6 +270,8 @@
       nullptr,             // parent_environment_service_provider_handle
       nullptr,             // session_listener_request
       nullptr,             // on_session_listener_error_callback
+      nullptr,             // session_metrics_did_change_callback
+      nullptr,             // session_size_change_hint_callback
       nullptr,             // on_enable_wireframe_callback,
       CreateViewCallback,  // on_create_view_callback,
       nullptr,             // on_destroy_view_callback,
@@ -336,6 +339,8 @@
       nullptr,              // parent_environment_service_provider_handle
       nullptr,              // session_listener_request
       nullptr,              // on_session_listener_error_callback
+      nullptr,              // session_metrics_did_change_callback
+      nullptr,              // session_size_change_hint_callback
       nullptr,              // on_enable_wireframe_callback,
       nullptr,              // on_create_view_callback,
       DestroyViewCallback,  // on_destroy_view_callback,
@@ -390,8 +395,11 @@
       );
 
   // Test get view embedder callback function.
-  MockExternalViewEmbedder view_embedder;
-  auto GetViewEmbedderCallback = [&view_embedder]() { return &view_embedder; };
+  MockSurfaceProducer surfaceProducer;
+  flutter::SceneUpdateContext scene_update_context(nullptr, &surfaceProducer);
+  flutter::ExternalViewEmbedder* view_embedder =
+      reinterpret_cast<flutter::ExternalViewEmbedder*>(&scene_update_context);
+  auto GetViewEmbedderCallback = [view_embedder]() { return view_embedder; };
 
   auto platform_view = flutter_runner::PlatformView(
       delegate,                               // delegate
@@ -402,6 +410,8 @@
       nullptr,                  // parent_environment_service_provider_handle
       nullptr,                  // session_listener_request
       nullptr,                  // on_session_listener_error_callback
+      nullptr,                  // session_metrics_did_change_callback
+      nullptr,                  // session_size_change_hint_callback
       nullptr,                  // on_enable_wireframe_callback,
       nullptr,                  // on_create_view_callback,
       nullptr,                  // on_destroy_view_callback,
@@ -416,7 +426,7 @@
 
   RunLoopUntilIdle();
 
-  EXPECT_EQ(&view_embedder, delegate.get_view_embedder());
+  EXPECT_EQ(view_embedder, delegate.get_view_embedder());
 }
 
 }  // namespace flutter_runner_test::flutter_runner_a11y_test
diff --git a/shell/platform/fuchsia/flutter/session_connection.cc b/shell/platform/fuchsia/flutter/session_connection.cc
index c87368a..133dc9a 100644
--- a/shell/platform/fuchsia/flutter/session_connection.cc
+++ b/shell/platform/fuchsia/flutter/session_connection.cc
@@ -5,8 +5,8 @@
 #include "session_connection.h"
 
 #include "flutter/fml/make_copyable.h"
-#include "flutter/fml/trace_event.h"
-
+#include "lib/fidl/cpp/optional.h"
+#include "lib/ui/scenic/cpp/commands.h"
 #include "vsync_recorder.h"
 #include "vsync_waiter.h"
 
@@ -14,11 +14,23 @@
 
 SessionConnection::SessionConnection(
     std::string debug_label,
+    fuchsia::ui::views::ViewToken view_token,
+    scenic::ViewRefPair view_ref_pair,
     fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session,
     fml::closure session_error_callback,
     on_frame_presented_event on_frame_presented_callback,
     zx_handle_t vsync_event_handle)
-    : session_wrapper_(session.Bind(), nullptr),
+    : debug_label_(std::move(debug_label)),
+      session_wrapper_(session.Bind(), nullptr),
+      root_view_(&session_wrapper_,
+                 std::move(view_token),
+                 std::move(view_ref_pair.control_ref),
+                 std::move(view_ref_pair.view_ref),
+                 debug_label),
+      root_node_(&session_wrapper_),
+      surface_producer_(
+          std::make_unique<VulkanSurfaceProducer>(&session_wrapper_)),
+      scene_update_context_(&session_wrapper_, surface_producer_.get()),
       on_frame_presented_callback_(std::move(on_frame_presented_callback)),
       vsync_event_handle_(vsync_event_handle) {
   session_wrapper_.set_error_handler(
@@ -51,7 +63,11 @@
       }  // callback
   );
 
-  session_wrapper_.SetDebugName(debug_label);
+  session_wrapper_.SetDebugName(debug_label_);
+
+  root_view_.AddChild(root_node_);
+  root_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask |
+                          fuchsia::ui::gfx::kSizeChangeHintEventMask);
 
   // Get information to finish initialization and only then allow Present()s.
   session_wrapper_.RequestPresentationTimes(
@@ -75,7 +91,8 @@
 
 SessionConnection::~SessionConnection() = default;
 
-void SessionConnection::Present() {
+void SessionConnection::Present(
+    flutter::CompositorContext::ScopedFrame* frame) {
   TRACE_EVENT0("gfx", "SessionConnection::Present");
 
   TRACE_FLOW_BEGIN("gfx", "SessionConnection::PresentSession",
@@ -97,6 +114,21 @@
     present_session_pending_ = true;
     ToggleSignal(vsync_event_handle_, false);
   }
+
+  if (frame) {
+    // Execute paint tasks and signal fences.
+    auto surfaces_to_submit = scene_update_context_.ExecutePaintTasks(*frame);
+
+    // Tell the surface producer that a present has occurred so it can perform
+    // book-keeping on buffer caches.
+    surface_producer_->OnSurfacesPresented(std::move(surfaces_to_submit));
+  }
+}
+
+void SessionConnection::OnSessionSizeChangeHint(float width_change_factor,
+                                                float height_change_factor) {
+  surface_producer_->OnSessionSizeChangeHint(width_change_factor,
+                                             height_change_factor);
 }
 
 fml::TimePoint SessionConnection::CalculateNextLatchPoint(
@@ -128,6 +160,17 @@
   return minimum_latch_point_to_target;
 }
 
+void SessionConnection::set_enable_wireframe(bool enable) {
+  session_wrapper_.Enqueue(
+      scenic::NewSetEnableDebugViewBoundsCmd(root_view_.id(), enable));
+}
+
+void SessionConnection::EnqueueClearOps() {
+  // We are going to be sending down a fresh node hierarchy every frame. So just
+  // enqueue a detach op on the imported root node.
+  session_wrapper_.Enqueue(scenic::NewDetachChildrenCmd(root_node_.id()));
+}
+
 void SessionConnection::PresentSession() {
   TRACE_EVENT0("gfx", "SessionConnection::PresentSession");
 
@@ -189,6 +232,10 @@
         VsyncRecorder::GetInstance().UpdateNextPresentationInfo(
             std::move(info));
       });
+
+  // Prepare for the next frame. These ops won't be processed till the next
+  // present.
+  EnqueueClearOps();
 }
 
 void SessionConnection::ToggleSignal(zx_handle_t handle, bool set) {
diff --git a/shell/platform/fuchsia/flutter/session_connection.h b/shell/platform/fuchsia/flutter/session_connection.h
index 7630d15..dcd55f1 100644
--- a/shell/platform/fuchsia/flutter/session_connection.h
+++ b/shell/platform/fuchsia/flutter/session_connection.h
@@ -5,14 +5,22 @@
 #ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_SESSION_CONNECTION_H_
 #define FLUTTER_SHELL_PLATFORM_FUCHSIA_SESSION_CONNECTION_H_
 
-#include <fuchsia/scenic/scheduling/cpp/fidl.h>
+#include <fuchsia/ui/gfx/cpp/fidl.h>
 #include <fuchsia/ui/scenic/cpp/fidl.h>
+#include <fuchsia/ui/views/cpp/fidl.h>
 #include <lib/fidl/cpp/interface_handle.h>
+#include <lib/fidl/cpp/optional.h>
+#include <lib/fit/function.h>
+#include <lib/ui/scenic/cpp/resources.h>
 #include <lib/ui/scenic/cpp/session.h>
+#include <lib/ui/scenic/cpp/view_ref_pair.h>
 
+#include "flutter/flow/compositor_context.h"
 #include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/closure.h"
 #include "flutter/fml/macros.h"
+#include "flutter/fml/trace_event.h"
+#include "vulkan_surface_producer.h"
 
 namespace flutter_runner {
 
@@ -21,9 +29,11 @@
 
 // The component residing on the raster thread that is responsible for
 // maintaining the Scenic session connection and presenting node updates.
-class SessionConnection final : public flutter::SessionWrapper {
+class SessionConnection final {
  public:
   SessionConnection(std::string debug_label,
+                    fuchsia::ui::views::ViewToken view_token,
+                    scenic::ViewRefPair view_ref_pair,
                     fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session,
                     fml::closure session_error_callback,
                     on_frame_presented_event on_frame_presented_callback,
@@ -31,8 +41,36 @@
 
   ~SessionConnection();
 
-  scenic::Session* get() override { return &session_wrapper_; }
-  void Present() override;
+  bool has_metrics() const { return scene_update_context_.has_metrics(); }
+
+  const fuchsia::ui::gfx::MetricsPtr& metrics() const {
+    return scene_update_context_.metrics();
+  }
+
+  void set_metrics(const fuchsia::ui::gfx::Metrics& metrics) {
+    fuchsia::ui::gfx::Metrics metrics_copy;
+    metrics.Clone(&metrics_copy);
+    scene_update_context_.set_metrics(
+        fidl::MakeOptional(std::move(metrics_copy)));
+  }
+
+  void set_enable_wireframe(bool enable);
+
+  flutter::SceneUpdateContext& scene_update_context() {
+    return scene_update_context_;
+  }
+
+  scenic::ContainerNode& root_node() { return root_node_; }
+  scenic::View* root_view() { return &root_view_; }
+
+  void Present(flutter::CompositorContext::ScopedFrame* frame);
+
+  void OnSessionSizeChangeHint(float width_change_factor,
+                               float height_change_factor);
+
+  VulkanSurfaceProducer* vulkan_surface_producer() {
+    return surface_producer_.get();
+  }
 
   static fml::TimePoint CalculateNextLatchPoint(
       fml::TimePoint present_requested_time,
@@ -44,8 +82,14 @@
           future_presentation_infos);
 
  private:
+  const std::string debug_label_;
   scenic::Session session_wrapper_;
 
+  scenic::View root_view_;
+  scenic::EntityNode root_node_;
+
+  std::unique_ptr<VulkanSurfaceProducer> surface_producer_;
+  flutter::SceneUpdateContext scene_update_context_;
   on_frame_presented_event on_frame_presented_callback_;
 
   zx_handle_t vsync_event_handle_;
@@ -78,6 +122,8 @@
 
   bool present_session_pending_ = false;
 
+  void EnqueueClearOps();
+
   void PresentSession();
 
   static void ToggleSignal(zx_handle_t handle, bool raise);
diff --git a/shell/platform/fuchsia/flutter/tests/session_connection_unittests.cc b/shell/platform/fuchsia/flutter/tests/session_connection_unittests.cc
index 5a54632..87efa11 100644
--- a/shell/platform/fuchsia/flutter/tests/session_connection_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/session_connection_unittests.cc
@@ -2,16 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <fuchsia/scenic/scheduling/cpp/fidl.h>
-#include <fuchsia/ui/policy/cpp/fidl.h>
-#include <fuchsia/ui/scenic/cpp/fidl.h>
+#include "gtest/gtest.h"
+
 #include <lib/async-loop/default.h>
 #include <lib/sys/cpp/component_context.h>
+#include <lib/ui/scenic/cpp/view_token_pair.h>
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/ui/policy/cpp/fidl.h>
 
 #include "flutter/shell/platform/fuchsia/flutter/logging.h"
 #include "flutter/shell/platform/fuchsia/flutter/runner.h"
 #include "flutter/shell/platform/fuchsia/flutter/session_connection.h"
-#include "gtest/gtest.h"
 
 using namespace flutter_runner;
 
@@ -28,8 +30,12 @@
               loop_.StartThread("SessionConnectionTestThread", &fidl_thread_));
 
     auto session_listener_request = session_listener_.NewRequest();
+    auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
+    view_token_ = std::move(view_token);
 
     scenic_->CreateSession(session_.NewRequest(), session_listener_.Bind());
+    presenter_->PresentOrReplaceView(std::move(view_holder_token), nullptr);
+
     FML_CHECK(zx::event::create(0, &vsync_event_) == ZX_OK);
 
     // Ensure Scenic has had time to wake up before the test logic begins.
@@ -54,6 +60,7 @@
 
   fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session_;
   fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> session_listener_;
+  fuchsia::ui::views::ViewToken view_token_;
   zx::event vsync_event_;
   thrd_t fidl_thread_;
 };
@@ -69,11 +76,12 @@
       };
 
   flutter_runner::SessionConnection session_connection(
-      "debug label", std::move(session_), on_session_error_callback,
+      "debug label", std::move(view_token_), scenic::ViewRefPair::New(),
+      std::move(session_), on_session_error_callback,
       on_frame_presented_callback, vsync_event_.get());
 
   for (int i = 0; i < 200; ++i) {
-    session_connection.Present();
+    session_connection.Present(nullptr);
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
   }
 
@@ -91,11 +99,12 @@
       };
 
   flutter_runner::SessionConnection session_connection(
-      "debug label", std::move(session_), on_session_error_callback,
+      "debug label", std::move(view_token_), scenic::ViewRefPair::New(),
+      std::move(session_), on_session_error_callback,
       on_frame_presented_callback, vsync_event_.get());
 
   for (int i = 0; i < 200; ++i) {
-    session_connection.Present();
+    session_connection.Present(nullptr);
     if (i % 10 == 9) {
       std::this_thread::sleep_for(std::chrono::milliseconds(20));
     }
diff --git a/shell/platform/fuchsia/flutter/vulkan_surface.h b/shell/platform/fuchsia/flutter/vulkan_surface.h
index bff2713..e6b7eb4 100644
--- a/shell/platform/fuchsia/flutter/vulkan_surface.h
+++ b/shell/platform/fuchsia/flutter/vulkan_surface.h
@@ -5,7 +5,6 @@
 #pragma once
 
 #include <lib/async/cpp/wait.h>
-#include <lib/ui/scenic/cpp/resources.h>
 #include <lib/zx/event.h>
 #include <lib/zx/vmo.h>
 
@@ -13,46 +12,17 @@
 #include <memory>
 
 #include "flutter/flow/raster_cache_key.h"
+#include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/macros.h"
 #include "flutter/vulkan/vulkan_command_buffer.h"
 #include "flutter/vulkan/vulkan_handle.h"
 #include "flutter/vulkan/vulkan_proc_table.h"
 #include "flutter/vulkan/vulkan_provider.h"
+#include "lib/ui/scenic/cpp/resources.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
 namespace flutter_runner {
 
-class SurfaceProducerSurface {
- public:
-  virtual ~SurfaceProducerSurface() = default;
-
-  virtual size_t AdvanceAndGetAge() = 0;
-
-  virtual bool FlushSessionAcquireAndReleaseEvents() = 0;
-
-  virtual bool IsValid() const = 0;
-
-  virtual SkISize GetSize() const = 0;
-
-  virtual void SignalWritesFinished(
-      const std::function<void(void)>& on_writes_committed) = 0;
-
-  virtual scenic::Image* GetImage() = 0;
-
-  virtual sk_sp<SkSurface> GetSkiaSurface() const = 0;
-};
-
-class SurfaceProducer {
- public:
-  virtual ~SurfaceProducer() = default;
-
-  virtual std::unique_ptr<SurfaceProducerSurface> ProduceSurface(
-      const SkISize& size) = 0;
-
-  virtual void SubmitSurface(
-      std::unique_ptr<SurfaceProducerSurface> surface) = 0;
-};
-
 // A |VkImage| and its relevant metadata.
 struct VulkanImage {
   VulkanImage() = default;
@@ -74,7 +44,8 @@
                        const SkISize& size,
                        VulkanImage* out_vulkan_image);
 
-class VulkanSurface final : public SurfaceProducerSurface {
+class VulkanSurface final
+    : public flutter::SceneUpdateContext::SurfaceProducerSurface {
  public:
   VulkanSurface(vulkan::VulkanProvider& vulkan_provider,
                 sk_sp<GrDirectContext> context,
@@ -83,16 +54,16 @@
 
   ~VulkanSurface() override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   size_t AdvanceAndGetAge() override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   bool FlushSessionAcquireAndReleaseEvents() override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   bool IsValid() const override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   SkISize GetSize() const override;
 
   // Note: It is safe for the caller to collect the surface in the
@@ -100,10 +71,10 @@
   void SignalWritesFinished(
       const std::function<void(void)>& on_writes_committed) override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   scenic::Image* GetImage() override;
 
-  // |SurfaceProducerSurface|
+  // |flutter::SceneUpdateContext::SurfaceProducerSurface|
   sk_sp<SkSurface> GetSkiaSurface() const override;
 
   const vulkan::VulkanHandle<VkImage>& GetVkImage() {
@@ -148,6 +119,41 @@
   // if the swap was not successful.
   bool BindToImage(sk_sp<GrDirectContext> context, VulkanImage vulkan_image);
 
+  // Flutter may retain a |VulkanSurface| for a |flutter::Layer| subtree to
+  // improve the performance. The |retained_key_| identifies which layer subtree
+  // this |VulkanSurface| is retained for. The key has two parts. One is the
+  // pointer to the root of that layer subtree: |retained_key_.id()|. Another is
+  // the transformation matrix: |retained_key_.matrix()|. We need the matrix
+  // part because a different matrix would invalidate the pixels (raster cache)
+  // in this |VulkanSurface|.
+  const flutter::LayerRasterCacheKey& GetRetainedKey() const {
+    return retained_key_;
+  }
+
+  // For better safety in retained rendering, Flutter uses a retained
+  // |EntityNode| associated with the retained surface instead of using the
+  // retained surface directly. Hence Flutter can't modify the surface during
+  // retained rendering. However, the node itself is modifiable to be able
+  // to adjust its position.
+  scenic::EntityNode* GetRetainedNode() {
+    used_in_retained_rendering_ = true;
+    return retained_node_.get();
+  }
+
+  // Check whether the retained surface (and its associated |EntityNode|) is
+  // used in the current frame or not. If unused, the |VulkanSurfacePool| will
+  // try to recycle the surface. This flag is reset after each frame.
+  bool IsUsedInRetainedRendering() const { return used_in_retained_rendering_; }
+  void ResetIsUsedInRetainedRendering() { used_in_retained_rendering_ = false; }
+
+  // Let this surface own the retained EntityNode associated with it (see
+  // |GetRetainedNode|), and set the retained key (see |GetRetainedKey|).
+  void SetRetainedInfo(const flutter::LayerRasterCacheKey& key,
+                       std::unique_ptr<scenic::EntityNode> node) {
+    retained_key_ = key;
+    retained_node_ = std::move(node);
+  }
+
  private:
   static constexpr int kSizeHistorySize = 4;
 
@@ -196,6 +202,11 @@
   size_t age_ = 0;
   bool valid_ = false;
 
+  flutter::LayerRasterCacheKey retained_key_ = {0, SkMatrix::Scale(1, 1)};
+  std::unique_ptr<scenic::EntityNode> retained_node_ = nullptr;
+
+  std::atomic<bool> used_in_retained_rendering_ = {false};
+
   FML_DISALLOW_COPY_AND_ASSIGN(VulkanSurface);
 };
 
diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc b/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc
index 1b85a95..cb40dd2 100644
--- a/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc
+++ b/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc
@@ -112,7 +112,8 @@
 }
 
 void VulkanSurfacePool::SubmitSurface(
-    std::unique_ptr<SurfaceProducerSurface> p_surface) {
+    std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+        p_surface) {
   TRACE_EVENT0("flutter", "VulkanSurfacePool::SubmitSurface");
 
   // This cast is safe because |VulkanSurface| is the only implementation of
@@ -125,14 +126,43 @@
     return;
   }
 
-  uintptr_t surface_key = reinterpret_cast<uintptr_t>(vulkan_surface.get());
-  auto insert_iterator = pending_surfaces_.insert(std::make_pair(
-      surface_key,               // key
-      std::move(vulkan_surface)  // value
-      ));
-  if (insert_iterator.second) {
-    insert_iterator.first->second->SignalWritesFinished(std::bind(
-        &VulkanSurfacePool::RecyclePendingSurface, this, surface_key));
+  const flutter::LayerRasterCacheKey& retained_key =
+      vulkan_surface->GetRetainedKey();
+
+  // TODO(https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=44141): Re-enable
+  // retained surfaces after we find out why textures are being prematurely
+  // recycled.
+  const bool kUseRetainedSurfaces = false;
+  if (kUseRetainedSurfaces && retained_key.id() != 0) {
+    // Add the surface to |retained_surfaces_| if its retained key has a valid
+    // layer id (|retained_key.id()|).
+    //
+    // We have to add the entry to |retained_surfaces_| map early when it's
+    // still pending (|is_pending| = true). Otherwise (if we add the surface
+    // later when |SignalRetainedReady| is called), Flutter would fail to find
+    // the retained node before the painting is done (which could take multiple
+    // frames). Flutter would then create a new |VulkanSurface| for the layer
+    // upon the failed lookup. The new |VulkanSurface| would invalidate this
+    // surface, and before the new |VulkanSurface| is done painting, another
+    // newer |VulkanSurface| is likely to be created to replace the new
+    // |VulkanSurface|. That would make the retained rendering much less useful
+    // in improving the performance.
+    auto insert_iterator = retained_surfaces_.insert(std::make_pair(
+        retained_key, RetainedSurface({true, std::move(vulkan_surface)})));
+    if (insert_iterator.second) {
+      insert_iterator.first->second.vk_surface->SignalWritesFinished(std::bind(
+          &VulkanSurfacePool::SignalRetainedReady, this, retained_key));
+    }
+  } else {
+    uintptr_t surface_key = reinterpret_cast<uintptr_t>(vulkan_surface.get());
+    auto insert_iterator = pending_surfaces_.insert(std::make_pair(
+        surface_key,               // key
+        std::move(vulkan_surface)  // value
+        ));
+    if (insert_iterator.second) {
+      insert_iterator.first->second->SignalWritesFinished(std::bind(
+          &VulkanSurfacePool::RecyclePendingSurface, this, surface_key));
+    }
   }
 }
 
@@ -183,6 +213,25 @@
   TraceStats();
 }
 
+void VulkanSurfacePool::RecycleRetainedSurface(
+    const flutter::LayerRasterCacheKey& key) {
+  auto it = retained_surfaces_.find(key);
+  if (it == retained_surfaces_.end()) {
+    return;
+  }
+
+  // The surface should not be pending.
+  FML_DCHECK(!it->second.is_pending);
+
+  auto surface_to_recycle = std::move(it->second.vk_surface);
+  retained_surfaces_.erase(it);
+  RecycleSurface(std::move(surface_to_recycle));
+}
+
+void VulkanSurfacePool::SignalRetainedReady(flutter::LayerRasterCacheKey key) {
+  retained_surfaces_[key].is_pending = false;
+}
+
 void VulkanSurfacePool::AgeAndCollectOldBuffers() {
   TRACE_EVENT0("flutter", "VulkanSurfacePool::AgeAndCollectOldBuffers");
 
@@ -219,6 +268,25 @@
     }
   }
 
+  // Recycle retained surfaces that are not used and not pending in this frame.
+  //
+  // It's safe to recycle any retained surfaces that are not pending no matter
+  // whether they're used or not. Hence if there's memory pressure, feel free to
+  // recycle all retained surfaces that are not pending.
+  std::vector<flutter::LayerRasterCacheKey> recycle_keys;
+  for (auto& [key, retained_surface] : retained_surfaces_) {
+    if (retained_surface.is_pending ||
+        retained_surface.vk_surface->IsUsedInRetainedRendering()) {
+      // Reset the flag for the next frame
+      retained_surface.vk_surface->ResetIsUsedInRetainedRendering();
+    } else {
+      recycle_keys.push_back(key);
+    }
+  }
+  for (auto& key : recycle_keys) {
+    RecycleRetainedSurface(key);
+  }
+
   TraceStats();
 }
 
@@ -252,9 +320,15 @@
 void VulkanSurfacePool::TraceStats() {
   // Resources held in cached buffers.
   size_t cached_surfaces_bytes = 0;
+  size_t retained_surfaces_bytes = 0;
+
   for (const auto& surface : available_surfaces_) {
     cached_surfaces_bytes += surface->GetAllocationSize();
   }
+  for (const auto& retained_entry : retained_surfaces_) {
+    retained_surfaces_bytes +=
+        retained_entry.second.vk_surface->GetAllocationSize();
+  }
 
   // Resources held by Skia.
   int skia_resources = 0;
@@ -268,13 +342,13 @@
                 "Created", trace_surfaces_created_,               //
                 "Reused", trace_surfaces_reused_,                 //
                 "PendingInCompositor", pending_surfaces_.size(),  //
-                "Retained", 0,                                    //
+                "Retained", retained_surfaces_.size(),            //
                 "SkiaCacheResources", skia_resources              //
   );
 
   TRACE_COUNTER("flutter", "SurfacePoolBytes", 0u,          //
                 "CachedBytes", cached_surfaces_bytes,       //
-                "RetainedBytes", 0,                         //
+                "RetainedBytes", retained_surfaces_bytes,   //
                 "SkiaCacheBytes", skia_bytes,               //
                 "SkiaCachePurgeable", skia_cache_purgeable  //
   );
diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h
index 0667db8..302be9b 100644
--- a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h
+++ b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h
@@ -28,7 +28,9 @@
 
   std::unique_ptr<VulkanSurface> AcquireSurface(const SkISize& size);
 
-  void SubmitSurface(std::unique_ptr<SurfaceProducerSurface> surface);
+  void SubmitSurface(
+      std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+          surface);
 
   void AgeAndCollectOldBuffers();
 
@@ -36,7 +38,26 @@
   // small as they can be.
   void ShrinkToFit();
 
+  // For |VulkanSurfaceProducer::HasRetainedNode|.
+  bool HasRetainedNode(const flutter::LayerRasterCacheKey& key) const {
+    return retained_surfaces_.find(key) != retained_surfaces_.end();
+  }
+  // For |VulkanSurfaceProducer::GetRetainedNode|.
+  scenic::EntityNode* GetRetainedNode(const flutter::LayerRasterCacheKey& key) {
+    FML_DCHECK(HasRetainedNode(key));
+    return retained_surfaces_[key].vk_surface->GetRetainedNode();
+  }
+
  private:
+  // Struct for retained_surfaces_ map.
+  struct RetainedSurface {
+    // If |is_pending| is true, the |vk_surface| is still under painting
+    // (similar to those in |pending_surfaces_|) so we can't recycle the
+    // |vk_surface| yet.
+    bool is_pending;
+    std::unique_ptr<VulkanSurface> vk_surface;
+  };
+
   vulkan::VulkanProvider& vulkan_provider_;
   sk_sp<GrDirectContext> context_;
   scenic::Session* scenic_session_;
@@ -44,6 +65,9 @@
   std::unordered_map<uintptr_t, std::unique_ptr<VulkanSurface>>
       pending_surfaces_;
 
+  // Retained surfaces keyed by the layer that created and used the surface.
+  flutter::LayerRasterCacheKey::Map<RetainedSurface> retained_surfaces_;
+
   size_t trace_surfaces_created_ = 0;
   size_t trace_surfaces_reused_ = 0;
 
@@ -55,6 +79,13 @@
 
   void RecyclePendingSurface(uintptr_t surface_key);
 
+  // Clear the |is_pending| flag of the retained surface.
+  void SignalRetainedReady(flutter::LayerRasterCacheKey key);
+
+  // Remove the corresponding surface from |retained_surfaces| and recycle it.
+  // The surface must not be pending.
+  void RecycleRetainedSurface(const flutter::LayerRasterCacheKey& key);
+
   void TraceStats();
 
   FML_DISALLOW_COPY_AND_ASSIGN(VulkanSurfacePool);
diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc
index 36eeadd..1e0d03d 100644
--- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc
+++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc
@@ -155,7 +155,9 @@
 }
 
 void VulkanSurfaceProducer::OnSurfacesPresented(
-    std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces) {
+    std::vector<
+        std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>
+        surfaces) {
   TRACE_EVENT0("flutter", "VulkanSurfaceProducer::OnSurfacesPresented");
 
   // Do a single flush for all canvases derived from the context.
@@ -195,12 +197,11 @@
 }
 
 bool VulkanSurfaceProducer::TransitionSurfacesToExternal(
-    const std::vector<std::unique_ptr<SurfaceProducerSurface>>& surfaces) {
+    const std::vector<
+        std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>&
+        surfaces) {
   for (auto& surface : surfaces) {
     auto vk_surface = static_cast<VulkanSurface*>(surface.get());
-    if (!vk_surface) {
-      continue;
-    }
 
     vulkan::VulkanCommandBuffer* command_buffer =
         vk_surface->GetCommandBuffer(logical_device_->GetCommandPool());
@@ -258,15 +259,21 @@
   return true;
 }
 
-std::unique_ptr<SurfaceProducerSurface> VulkanSurfaceProducer::ProduceSurface(
-    const SkISize& size) {
+std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+VulkanSurfaceProducer::ProduceSurface(
+    const SkISize& size,
+    const flutter::LayerRasterCacheKey& layer_key,
+    std::unique_ptr<scenic::EntityNode> entity_node) {
   FML_DCHECK(valid_);
   last_produce_time_ = async::Now(async_get_default_dispatcher());
-  return surface_pool_->AcquireSurface(size);
+  auto surface = surface_pool_->AcquireSurface(size);
+  surface->SetRetainedInfo(layer_key, std::move(entity_node));
+  return surface;
 }
 
 void VulkanSurfaceProducer::SubmitSurface(
-    std::unique_ptr<SurfaceProducerSurface> surface) {
+    std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+        surface) {
   FML_DCHECK(valid_ && surface != nullptr);
   surface_pool_->SubmitSurface(std::move(surface));
 }
diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h
index 2caaf64..403ecd1 100644
--- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h
+++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h
@@ -8,8 +8,8 @@
 #include <lib/async/default.h>
 #include <lib/syslog/global.h>
 
+#include "flutter/flow/scene_update_context.h"
 #include "flutter/fml/macros.h"
-#include "flutter/fml/memory/weak_ptr.h"
 #include "flutter/vulkan/vulkan_application.h"
 #include "flutter/vulkan/vulkan_device.h"
 #include "flutter/vulkan/vulkan_proc_table.h"
@@ -22,8 +22,9 @@
 
 namespace flutter_runner {
 
-class VulkanSurfaceProducer final : public SurfaceProducer,
-                                    public vulkan::VulkanProvider {
+class VulkanSurfaceProducer final
+    : public flutter::SceneUpdateContext::SurfaceProducer,
+      public vulkan::VulkanProvider {
  public:
   VulkanSurfaceProducer(scenic::Session* scenic_session);
 
@@ -31,15 +32,39 @@
 
   bool IsValid() const { return valid_; }
 
-  // |SurfaceProducer|
-  std::unique_ptr<SurfaceProducerSurface> ProduceSurface(
-      const SkISize& size) override;
+  // |flutter::SceneUpdateContext::SurfaceProducer|
+  std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+  ProduceSurface(const SkISize& size,
+                 const flutter::LayerRasterCacheKey& layer_key,
+                 std::unique_ptr<scenic::EntityNode> entity_node) override;
 
-  // |SurfaceProducer|
-  void SubmitSurface(std::unique_ptr<SurfaceProducerSurface> surface) override;
+  // |flutter::SceneUpdateContext::SurfaceProducer|
+  void SubmitSurface(
+      std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>
+          surface) override;
+
+  // |flutter::SceneUpdateContext::HasRetainedNode|
+  bool HasRetainedNode(const flutter::LayerRasterCacheKey& key) const override {
+    return surface_pool_->HasRetainedNode(key);
+  }
+
+  // |flutter::SceneUpdateContext::GetRetainedNode|
+  scenic::EntityNode* GetRetainedNode(
+      const flutter::LayerRasterCacheKey& key) override {
+    return surface_pool_->GetRetainedNode(key);
+  }
 
   void OnSurfacesPresented(
-      std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces);
+      std::vector<
+          std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>
+          surfaces);
+
+  void OnSessionSizeChangeHint(float width_change_factor,
+                               float height_change_factor) {
+    FX_LOGF(INFO, LOG_TAG,
+            "VulkanSurfaceProducer:OnSessionSizeChangeHint %f, %f",
+            width_change_factor, height_change_factor);
+  }
 
   GrDirectContext* gr_context() { return context_.get(); }
 
@@ -51,7 +76,9 @@
   }
 
   bool TransitionSurfacesToExternal(
-      const std::vector<std::unique_ptr<SurfaceProducerSurface>>& surfaces);
+      const std::vector<
+          std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>&
+          surfaces);
 
   // Note: the order here is very important. The proctable must be destroyed
   // last because it contains the function pointers for VkDestroyDevice and
diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart
index 21eda23..186ca92 100644
--- a/testing/dart/window_hooks_integration_test.dart
+++ b/testing/dart/window_hooks_integration_test.dart
@@ -46,6 +46,7 @@
 
   double? oldDPR;
   Size? oldSize;
+  double? oldDepth;
   WindowPadding? oldPadding;
   WindowPadding? oldInsets;
   WindowPadding? oldSystemGestureInsets;
@@ -53,6 +54,7 @@
   void setUp() {
     oldDPR = window.devicePixelRatio;
     oldSize = window.physicalSize;
+    oldDepth = window.physicalDepth;
     oldPadding = window.viewPadding;
     oldInsets = window.viewInsets;
     oldSystemGestureInsets = window.systemGestureInsets;
@@ -74,6 +76,7 @@
       oldDPR!,                         // DPR
       oldSize!.width,                  // width
       oldSize!.height,                 // height
+      oldDepth!,                       // depth
       oldPadding!.top,                 // padding top
       oldPadding!.right,               // padding right
       oldPadding!.bottom,              // padding bottom
@@ -158,6 +161,7 @@
       0.1234, // DPR
       0.0,    // width
       0.0,    // height
+      0.0,    // depth
       0.0,    // padding top
       0.0,    // padding right
       0.0,    // padding bottom
@@ -374,6 +378,7 @@
       1.0,   // DPR
       800.0, // width
       600.0, // height
+      100.0, // depth
       50.0,  // padding top
       0.0,   // padding right
       40.0,  // padding bottom
@@ -391,12 +396,14 @@
     expectEquals(window.viewInsets.bottom, 0.0);
     expectEquals(window.viewPadding.bottom, 40.0);
     expectEquals(window.padding.bottom, 40.0);
+    expectEquals(window.physicalDepth, 100.0);
     expectEquals(window.systemGestureInsets.bottom, 0.0);
 
     _updateWindowMetrics(
       1.0,   // DPR
       800.0, // width
       600.0, // height
+      100.0, // depth
       50.0,  // padding top
       0.0,   // padding right
       40.0,  // padding bottom
@@ -414,6 +421,7 @@
     expectEquals(window.viewInsets.bottom, 400.0);
     expectEquals(window.viewPadding.bottom, 40.0);
     expectEquals(window.padding.bottom, 0.0);
+    expectEquals(window.physicalDepth, 100.0);
     expectEquals(window.systemGestureInsets.bottom, 44.0);
   });
 }