Mozart: Improve internal scene graph representation.

This patch changes the internal scene graph representation into an
immutable data structure which allows for multiple versions to be
maintained simultaneously as well as for the node structure to be
preserved during snapshot operations.

Previously the node structure was traversed once during snapshot
with the intent that all relevant information would be recorded
info an immutable |RenderFrame| object for rasterization and for
hit testing.  However it proved prohibitively expensive to copy
the necessary node structure information into the |RenderFrame|
for hit testing.  So this change does away with all of that.

The compositor now keeps three levels of structural information
to be generated and traversed as required.

1. |SceneDef| which holds a table of immutable |NodeDef| objects
   describing the currently presented state of the scene graph
   and pending updates which have yet to be applied.

2. |SceneContent| which is generated from a |SceneDef| when the
   scene graph is presented.  It consists of an immutable index
   of |NodeDef| objects for a particular version of the scene
   graph as it existed at the time of presentation.  This index
   contains sufficient linkage information to ensure that the
   scene does not contain any internal cross-node cycles and
   that all references resources and nodes are reachable.

3. |Snapshot| which is generated from a collection of |SceneContent|
   objects when the frame is snapshotted.  When producing the
   snapshot, all scene references are resolved and combinator
   rules are evaluated to produce a final description of a single
   frame of graphical content to be rasterized.  The |Snapshot|
   can also be traversed later for hit testing purposes since
   it retains a record of the disposition of each node (whether
   they were blocked).

Altogether, these new representations resolve numerous issues with
the old model such as how we can retain multiple versions of a
scene for as long as required or how we can traverse particular
versions or snapshots of the scene graph to execute queries such
as hit testing.

There are still many optimizations we could apply to the structure
but it's already looking much better overall and there is
significantly less wasted effort during snapshotting when blocked
nodes are discovered.

This also puts us in a nice position to start dealing with
synchronization issues such as applications which publish scenes
referencing textures which have yet to be produced: while walking
the tree we can make a decision of whether to wait or to fallback
on previously published content which may be already be ready with
per-scene granularity.  (To do later.)

(Hit testing itself will be implemented in a later patch.)

BUG=
R=abarth@google.com

Review URL: https://codereview.chromium.org/1749063002 .
diff --git a/mojo/services/gfx/composition/interfaces/nodes.mojom b/mojo/services/gfx/composition/interfaces/nodes.mojom
index 588a8eb..602ec44 100644
--- a/mojo/services/gfx/composition/interfaces/nodes.mojom
+++ b/mojo/services/gfx/composition/interfaces/nodes.mojom
@@ -16,7 +16,9 @@
 // references to embedded scenes.  Nodes are arranged to form a directed
 // acyclic graph of drawing commands.
 //
-// The node graph is processed in pre-order traversal.  Starting from the
+// RENDERING
+//
+// The node graph is renderer in pre-order traversal.  Starting from the
 // root, the compositor applies the transformation, clip, applies the
 // node's operation (if any), then recursively processes the node's children
 // according to the node's combinator rule.
@@ -53,15 +55,51 @@
 // missing content for an earlier version of that content or for a placeholder
 // if not available.
 //
+// INSTANCING
+//
+// The compositor allows nodes to be referenced and reused multiple times
+// within a scene (this is known as instancing).  Instancing makes it easier
+// to take advantage of combinators for interleaving placeholder content
+// when certain nodes are blocked from rendering (see above).  It also allows
+// common elements to be reused if desired.
+//
+// Likewise, the compositor allows scenes to be multiply referenced so that
+// the same content can be presented simultaneously in several places.
+//
+// CYCLES
+//
+// The compositor forbids cycles among nodes within scenes and will
+// reject scene updates which introduce node cycles by closing the client's
+// connection.
+//
+// Likewise, the compositor forbids cycles across scenes and will respond
+// to them by considering any scene within a cycle to be blocked from
+// rendering.
+//
+// For example, if there are scenes A, B, and C linked such that rendering
+// would traverse a path A -> B -> C -> B, the compositor will consider both
+// scenes B and C to be blocked and will apply A's combinator rules as required
+// to resolve the problem at the point where it would have entered the cycle.
+// This may cause A itself to be blocked if there are no applicable |PRUNE|
+// or |FALLBACK| predicated alternatives.
+//
+// This policy protects clients from cross-scene cycles which may have been
+// introduced downstream in the graph without their knowledge or which may
+// occur transiently, so long as they are not within the cycle themselves.
+// It also ensures that cycles are resolved deterministically regardless of
+// where they are encountered during traversal; all scenes within the cycle
+// are suppressed.
+//
 // TIPS
 //
 // 1. Reuse nodes when possible to reduce the size of the graph.  Consider
 //    using LayerNodeOps to flatten common elements to a texture which can
 //    be redrawn efficiently in many places.
 //
-// 2. Insert PRUNE or FALLBACK nodes in places where blocking is likely to
+// 2. Insert |PRUNE| or |FALLBACK| nodes in places where blocking is likely to
 //    occur, such as when embedding scenes produced by other applications.
-//    Provide alternate content where possible.
+//    Provide alternate content where possible to avoid stalling the
+//    rendering pipeline at these points.
 //
 struct Node {
   // The combinator specifies how child nodes are processed.
@@ -101,11 +139,8 @@
 
   // The ids of the children of this node.
   // It is an error to specify a node id that does not refer to a valid
-  // node; the compositor will close the connection when the scene
-  // is published.
-  // If a cycle is introduced then the node will be considered to be blocked
-  // at the point where recursion occurs.  A subsequent rearrangement of
-  // scenes which removes the cycle will unblock the node.
+  // node or which creates a cycle in the graph; the compositor will close
+  // the connection when the scene is published.
   array<uint32>? child_node_ids;
 
   // The drawing operation to apply when processing this node.
@@ -171,9 +206,8 @@
   // It is an error to specify a resource id that does not refer to a scene
   // resource; the compositor will close the connection when the scene
   // is published.
-  // If a cycle is introduced then the node will be considered to be blocked
-  // at the point where recursion occurs.  A subsequent rearrangement of
-  // scenes which removes the cycle will unblock the node.
+  // If a cycle is introduced then the scene will be substituted with
+  // placeholder content by the compositor.
   uint32 scene_resource_id;
 
   // The version of the scene that we would like to reference.
diff --git a/mojo/services/gfx/composition/interfaces/scenes.mojom b/mojo/services/gfx/composition/interfaces/scenes.mojom
index d9c5c69..2e7f631 100644
--- a/mojo/services/gfx/composition/interfaces/scenes.mojom
+++ b/mojo/services/gfx/composition/interfaces/scenes.mojom
@@ -151,8 +151,8 @@
   //
   // Publishing a scene graph applies all pending updates.  It is an error
   // to publish updates which cause the scene state to become inconsistent,
-  // such as by having nodes which refer to non-existent resources; the
-  // connection will be closed.
+  // such as by having nodes which refer to non-existent resources or
+  // introducing cycles in the scene's nodes; the connection will be closed.
   Publish(SceneMetadata? metadata);
 
   // Gets a scheduler for scheduling upcoming scene operations.
diff --git a/services/gfx/compositor/BUILD.gn b/services/gfx/compositor/BUILD.gn
index 3e88cbd..0f44b0a 100644
--- a/services/gfx/compositor/BUILD.gn
+++ b/services/gfx/compositor/BUILD.gn
@@ -26,16 +26,18 @@
     "graph/node_def.h",
     "graph/resource_def.cc",
     "graph/resource_def.h",
+    "graph/scene_content.cc",
+    "graph/scene_content.h",
     "graph/scene_def.cc",
     "graph/scene_def.h",
+    "graph/scene_label.cc",
+    "graph/scene_label.h",
     "graph/snapshot.cc",
     "graph/snapshot.h",
     "render/render_frame.cc",
     "render/render_frame.h",
     "render/render_image.cc",
     "render/render_image.h",
-    "render/render_layer.cc",
-    "render/render_layer.h",
     "renderer_impl.cc",
     "renderer_impl.h",
     "renderer_state.cc",
diff --git a/services/gfx/compositor/compositor_engine.cc b/services/gfx/compositor/compositor_engine.cc
index 448d689..3e82c41 100644
--- a/services/gfx/compositor/compositor_engine.cc
+++ b/services/gfx/compositor/compositor_engine.cc
@@ -264,23 +264,16 @@
   DCHECK(point);
   DVLOG(1) << "HitTest: renderer=" << renderer_state << ", point=" << point;
 
-  mojo::gfx::composition::HitTestResultPtr result;
-  if (renderer_state->frame()) {
-    result =
-        renderer_state->frame()->HitTest(SkPoint::Make(point->x, point->y));
-  }
-  if (!result) {
-    result = mojo::gfx::composition::HitTestResult::New();
-    result->hits.resize(0u);
-  }
-
+  // TODO(jeffbrown): hit tests on scenes
+  auto result = mojo::gfx::composition::HitTestResult::New();
   callback.Run(result.Pass());
 }
 
-SceneDef* CompositorEngine::ResolveSceneReference(
-    mojo::gfx::composition::SceneToken* scene_token) {
-  SceneState* scene_state = FindScene(scene_token->value);
-  return scene_state ? scene_state->scene_def() : nullptr;
+base::WeakPtr<SceneDef> CompositorEngine::ResolveSceneReference(
+    const mojo::gfx::composition::SceneToken& scene_token) {
+  SceneState* scene_state = FindScene(scene_token.value);
+  return scene_state ? scene_state->scene_def()->GetWeakPtr()
+                     : base::WeakPtr<SceneDef>();
 }
 
 void CompositorEngine::SendResourceUnavailable(SceneState* scene_state,
@@ -361,7 +354,7 @@
   if (VLOG_IS_ON(2)) {
     std::ostringstream block_log;
     SnapshotRendererInner(renderer_state, frame_info, &block_log);
-    if (!renderer_state->valid()) {
+    if (!renderer_state->frame()) {
       DVLOG(2) << "Rendering completely blocked: " << block_log.str();
     } else if (!block_log.str().empty()) {
       DVLOG(2) << "Rendering partially blocked: " << block_log.str();
@@ -389,7 +382,7 @@
       builder.Build(renderer_state->root_scene()->scene_def(),
                     renderer_state->root_scene_viewport(), frame_info));
 
-  if (renderer_state->valid())
+  if (renderer_state->frame())
     renderer_state->output()->SubmitFrame(renderer_state->frame());
 }
 
diff --git a/services/gfx/compositor/compositor_engine.h b/services/gfx/compositor/compositor_engine.h
index 0d06369..678523c 100644
--- a/services/gfx/compositor/compositor_engine.h
+++ b/services/gfx/compositor/compositor_engine.h
@@ -105,8 +105,8 @@
   void OnPresentScene(const base::WeakPtr<SceneState>& scene_state_weak,
                       int64_t presentation_time);
 
-  SceneDef* ResolveSceneReference(
-      mojo::gfx::composition::SceneToken* scene_token);
+  base::WeakPtr<SceneDef> ResolveSceneReference(
+      const mojo::gfx::composition::SceneToken& scene_token);
   void SendResourceUnavailable(SceneState* scene_state, uint32_t resource_id);
 
   SceneState* FindScene(uint32_t scene_token);
diff --git a/services/gfx/compositor/graph/node_def.cc b/services/gfx/compositor/graph/node_def.cc
index 9a6fae2..c743e5d 100644
--- a/services/gfx/compositor/graph/node_def.cc
+++ b/services/gfx/compositor/graph/node_def.cc
@@ -4,14 +4,19 @@
 
 #include "services/gfx/compositor/graph/node_def.h"
 
+#include <ostream>
+
 #include "base/logging.h"
-#include "base/strings/stringprintf.h"
 #include "mojo/services/gfx/composition/cpp/formatting.h"
 #include "mojo/skia/type_converters.h"
+#include "services/gfx/compositor/graph/scene_content.h"
 #include "services/gfx/compositor/graph/scene_def.h"
 #include "services/gfx/compositor/graph/snapshot.h"
 #include "services/gfx/compositor/render/render_image.h"
-#include "services/gfx/compositor/render/render_layer.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
 
 namespace compositor {
 namespace {
@@ -19,306 +24,388 @@
   return SkColorSetARGBInline(color.alpha, color.red, color.green, color.blue);
 }
 
-SkPaint MakePaintForBlend(const mojo::gfx::composition::Blend& blend) {
-  SkPaint result;
-  result.setAlpha(blend.alpha);
-  return result;
+void SetPaintForBlend(SkPaint* paint, mojo::gfx::composition::Blend* blend) {
+  DCHECK(paint);
+  if (blend)
+    paint->setAlpha(blend->alpha);
 }
 }  // namespace
 
 NodeDef::NodeDef(uint32_t node_id,
                  mojo::TransformPtr content_transform,
                  mojo::RectPtr content_clip,
-                 uint32_t hit_id,
                  Combinator combinator,
-                 const std::vector<uint32_t>& child_node_ids,
-                 NodeOp* op)
+                 const std::vector<uint32_t>& child_node_ids)
     : node_id_(node_id),
       content_transform_(content_transform.Pass()),
       content_clip_(content_clip.Pass()),
-      hit_id_(hit_id),
       combinator_(combinator),
-      child_node_ids_(child_node_ids),
-      op_(op) {}
+      child_node_ids_(child_node_ids) {}
 
 NodeDef::~NodeDef() {}
 
-bool NodeDef::Validate(SceneDef* scene, std::ostream& err) {
-  child_nodes_.clear();
-  for (uint32_t child_node_id : child_node_ids_) {
-    NodeDef* child_node = scene->FindNode(child_node_id);
-    if (!child_node) {
-      err << "Node refers to unknown child: " << FormattedLabel(scene)
-          << ", child_node_id=" << child_node_id;
+std::string NodeDef::FormattedLabel(const SceneContent* content) const {
+  return content->FormattedLabelForNode(node_id_);
+}
+
+bool NodeDef::RecordContent(SceneContentBuilder* builder) const {
+  DCHECK(builder);
+
+  for (const auto& child_node_id : child_node_ids_) {
+    if (!builder->RequireNode(child_node_id, node_id_))
       return false;
-    }
-    child_nodes_.push_back(child_node);
   }
-
-  return !op_ || op_->Validate(scene, this, err);
-}
-
-bool NodeDef::Snapshot(SnapshotBuilder* snapshot_builder,
-                       RenderLayerBuilder* layer_builder,
-                       SceneDef* scene) {
-  DCHECK(snapshot_builder);
-  DCHECK(layer_builder);
-  DCHECK(scene);
-
-  // Detect cycles.
-  if (visited_) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Node blocked due to recursive cycle: " << FormattedLabel(scene)
-          << std::endl;
-    }
-    return false;
-  }
-
-  // Snapshot the contents of the node.
-  visited_ = true;
-  bool success = SnapshotInner(snapshot_builder, layer_builder, scene);
-  visited_ = false;
-  return success;
-}
-
-bool NodeDef::SnapshotInner(SnapshotBuilder* snapshot_builder,
-                            RenderLayerBuilder* layer_builder,
-                            SceneDef* scene) {
-  // TODO(jeffbrown): Frequently referenced and reused nodes, especially
-  // layer nodes, may benefit from caching.
-  layer_builder->PushNode(node_id_, hit_id_);
-  if (content_transform_)
-    layer_builder->ApplyTransform(content_transform_.To<SkMatrix>());
-  if (content_clip_)
-    layer_builder->ApplyClip(content_clip_->To<SkRect>());
-
-  bool success =
-      op_ ? op_->Snapshot(snapshot_builder, layer_builder, scene, this)
-          : SnapshotChildren(snapshot_builder, layer_builder, scene);
-  if (!success)
-    return false;
-
-  layer_builder->PopNode();
   return true;
 }
 
-bool NodeDef::SnapshotChildren(SnapshotBuilder* snapshot_builder,
-                               RenderLayerBuilder* layer_builder,
-                               SceneDef* scene) {
-  DCHECK(snapshot_builder);
-  DCHECK(layer_builder);
-  DCHECK(scene);
+Snapshot::Disposition NodeDef::RecordSnapshot(const SceneContent* content,
+                                              SnapshotBuilder* builder) const {
+  DCHECK(content);
+  DCHECK(builder);
 
   switch (combinator_) {
     // MERGE: All or nothing.
     case Combinator::MERGE: {
-      for (NodeDef* child_node : child_nodes_) {
-        if (!child_node->Snapshot(snapshot_builder, layer_builder, scene)) {
-          if (snapshot_builder->block_log()) {
-            *snapshot_builder->block_log()
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        Snapshot::Disposition disposition =
+            builder->SnapshotNode(child_node, content);
+        if (disposition == Snapshot::Disposition::kCycle)
+          return disposition;
+        if (disposition == Snapshot::Disposition::kBlocked) {
+          if (builder->block_log()) {
+            *builder->block_log()
                 << "Node with MERGE combinator blocked since "
                    "one of its children is blocked: "
-                << FormattedLabel(scene) << ", blocked child "
-                << child_node->FormattedLabel(scene) << std::endl;
+                << FormattedLabel(content) << ", blocked child "
+                << child_node->FormattedLabel(content) << std::endl;
           }
-          return false;  // blocked
+          return disposition;
         }
       }
-      return true;
+      return Snapshot::Disposition::kSuccess;
     }
 
     // PRUNE: Silently discard blocked children.
     case Combinator::PRUNE: {
-      for (NodeDef* child_node : child_nodes_) {
-        RenderLayerBuilder child_layer_builder;
-        if (child_node->Snapshot(snapshot_builder, &child_layer_builder,
-                                 scene)) {
-          layer_builder->DrawLayer(child_layer_builder.Build());
-        }
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        Snapshot::Disposition disposition =
+            builder->SnapshotNode(child_node, content);
+        if (disposition == Snapshot::Disposition::kCycle)
+          return disposition;
       }
-      return true;
+      return Snapshot::Disposition::kSuccess;
     }
 
     // FALLBACK: Keep only the first unblocked child.
     case Combinator::FALLBACK: {
-      if (child_nodes_.empty())
-        return true;
-      for (NodeDef* child_node : child_nodes_) {
-        RenderLayerBuilder child_layer_builder;
-        if (child_node->Snapshot(snapshot_builder, &child_layer_builder,
-                                 scene)) {
-          layer_builder->DrawLayer(child_layer_builder.Build());
-          return true;
-        }
+      if (child_node_ids_.empty())
+        return Snapshot::Disposition::kSuccess;
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        Snapshot::Disposition disposition =
+            builder->SnapshotNode(child_node, content);
+        if (disposition != Snapshot::Disposition::kBlocked)
+          return disposition;
       }
-      if (snapshot_builder->block_log()) {
-        *snapshot_builder->block_log()
-            << "Node with FALLBACK combinator blocked since "
-               "all of its children are blocked: "
-            << FormattedLabel(scene) << std::endl;
+      if (builder->block_log()) {
+        *builder->block_log() << "Node with FALLBACK combinator blocked since "
+                                 "all of its children are blocked: "
+                              << FormattedLabel(content) << std::endl;
       }
-      return false;  // blocked
+      return Snapshot::Disposition::kBlocked;
     }
 
     default: {
-      if (snapshot_builder->block_log()) {
-        *snapshot_builder->block_log()
-            << "Unrecognized combinator: " << FormattedLabel(scene)
+      if (builder->block_log()) {
+        *builder->block_log()
+            << "Unrecognized combinator: " << FormattedLabel(content)
             << std::endl;
       }
-      return false;
+      return Snapshot::Disposition::kBlocked;
     }
   }
 }
 
-std::string NodeDef::FormattedLabel(SceneDef* scene) {
-  return base::StringPrintf("%s[%d]", scene->FormattedLabel().c_str(),
-                            node_id_);
+template <typename Func>
+void NodeDef::TraverseSnapshottedChildren(const SceneContent* content,
+                                          const Snapshot* snapshot,
+                                          const Func& func) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+
+  switch (combinator_) {
+    // MERGE: All or nothing.
+    case Combinator::MERGE: {
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        DCHECK(!snapshot->IsBlocked(child_node));
+        if (!func(child_node))
+          return;
+      }
+      return;
+    }
+
+    // PRUNE: Silently discard blocked children.
+    case Combinator::PRUNE: {
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        if (!snapshot->IsBlocked(child_node) && !func(child_node))
+          return;
+      }
+      return;
+    }
+
+    // FALLBACK: Keep only the first unblocked child.
+    case Combinator::FALLBACK: {
+      if (child_node_ids_.empty())
+        return;
+      for (uint32_t child_node_id : child_node_ids_) {
+        const NodeDef* child_node = content->GetNode(child_node_id);
+        DCHECK(child_node);
+        if (!snapshot->IsBlocked(child_node)) {
+          func(child_node);  // don't care about the result because we
+          return;            // always stop after the first one
+        }
+      }
+      NOTREACHED();
+      return;
+    }
+
+    default: {
+      NOTREACHED();
+      return;
+    }
+  }
 }
 
-bool NodeOp::Validate(SceneDef* scene, NodeDef* node, std::ostream& err) {
-  return true;
+void NodeDef::RecordPicture(const SceneContent* content,
+                            const Snapshot* snapshot,
+                            SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
+
+  const bool must_save = content_transform_ || content_clip_;
+  if (must_save) {
+    canvas->save();
+    if (content_transform_)
+      canvas->concat(content_transform_.To<SkMatrix>());
+    if (content_clip_)
+      canvas->clipRect(content_clip_->To<SkRect>());
+  }
+
+  RecordPictureInner(content, snapshot, canvas);
+
+  if (must_save)
+    canvas->restore();
 }
 
-RectNodeOp::RectNodeOp(const mojo::Rect& content_rect,
-                       const mojo::gfx::composition::Color& color)
-    : content_rect_(content_rect), color_(color) {}
+void NodeDef::RecordPictureInner(const SceneContent* content,
+                                 const Snapshot* snapshot,
+                                 SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
 
-RectNodeOp::~RectNodeOp() {}
+  TraverseSnapshottedChildren(
+      content, snapshot,
+      [this, content, snapshot, canvas](const NodeDef* child_node) -> bool {
+        child_node->RecordPicture(content, snapshot, canvas);
+        return true;
+      });
+}
 
-bool RectNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
-                          RenderLayerBuilder* layer_builder,
-                          SceneDef* scene,
-                          NodeDef* node) {
+RectNodeDef::RectNodeDef(uint32_t node_id,
+                         mojo::TransformPtr content_transform,
+                         mojo::RectPtr content_clip,
+                         Combinator combinator,
+                         const std::vector<uint32_t>& child_node_ids,
+                         const mojo::Rect& content_rect,
+                         const mojo::gfx::composition::Color& color)
+    : NodeDef(node_id,
+              content_transform.Pass(),
+              content_clip.Pass(),
+              combinator,
+              child_node_ids),
+      content_rect_(content_rect),
+      color_(color) {}
+
+RectNodeDef::~RectNodeDef() {}
+
+void RectNodeDef::RecordPictureInner(const SceneContent* content,
+                                     const Snapshot* snapshot,
+                                     SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
+
   SkPaint paint;
   paint.setColor(MakeSkColor(color_));
-  layer_builder->DrawRect(content_rect_.To<SkRect>(), paint);
+  canvas->drawRect(content_rect_.To<SkRect>(), paint);
 
-  return node->SnapshotChildren(snapshot_builder, layer_builder, scene);
+  NodeDef::RecordPictureInner(content, snapshot, canvas);
 }
 
-ImageNodeOp::ImageNodeOp(const mojo::Rect& content_rect,
-                         mojo::RectPtr image_rect,
-                         uint32 image_resource_id,
-                         mojo::gfx::composition::BlendPtr blend)
-    : content_rect_(content_rect),
+ImageNodeDef::ImageNodeDef(uint32_t node_id,
+                           mojo::TransformPtr content_transform,
+                           mojo::RectPtr content_clip,
+                           Combinator combinator,
+                           const std::vector<uint32_t>& child_node_ids,
+                           const mojo::Rect& content_rect,
+                           mojo::RectPtr image_rect,
+                           uint32 image_resource_id,
+                           mojo::gfx::composition::BlendPtr blend)
+    : NodeDef(node_id,
+              content_transform.Pass(),
+              content_clip.Pass(),
+              combinator,
+              child_node_ids),
+      content_rect_(content_rect),
       image_rect_(image_rect.Pass()),
       image_resource_id_(image_resource_id),
       blend_(blend.Pass()) {}
 
-ImageNodeOp::~ImageNodeOp() {}
+ImageNodeDef::~ImageNodeDef() {}
 
-bool ImageNodeOp::Validate(SceneDef* scene, NodeDef* node, std::ostream& err) {
-  image_resource_ = scene->FindImageResource(image_resource_id_);
-  if (!image_resource_) {
-    err << "Node refers to unknown or invalid image resource: "
-        << node->FormattedLabel(scene)
-        << ", image_resource_id=" << image_resource_id_;
-    return false;
-  }
-  return true;
+bool ImageNodeDef::RecordContent(SceneContentBuilder* builder) const {
+  DCHECK(builder);
+
+  return NodeDef::RecordContent(builder) &&
+         builder->RequireResource(image_resource_id_, ResourceDef::Type::kImage,
+                                  node_id());
 }
 
-bool ImageNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
-                           RenderLayerBuilder* layer_builder,
-                           SceneDef* scene,
-                           NodeDef* node) {
-  DCHECK(image_resource_);
+void ImageNodeDef::RecordPictureInner(const SceneContent* content,
+                                      const Snapshot* snapshot,
+                                      SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
 
-  if (!image_resource_->image()) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Node blocked due to its referenced image "
-             "resource being unavailable: "
-          << node->FormattedLabel(scene) << std::endl;
-    }
-    return false;
-  }
+  auto image_resource = static_cast<const ImageResourceDef*>(
+      content->GetResource(image_resource_id_, ResourceDef::Type::kImage));
+  DCHECK(image_resource);
 
-  layer_builder->DrawImage(
-      image_resource_->image(), content_rect_.To<SkRect>(),
-      image_rect_ ? image_rect_->To<SkRect>()
-                  : SkRect::MakeWH(image_resource_->image()->width(),
-                                   image_resource_->image()->height()),
-      blend_ ? MakePaintForBlend(*blend_) : SkPaint());
+  SkPaint paint;
+  SetPaintForBlend(&paint, blend_.get());
 
-  return node->SnapshotChildren(snapshot_builder, layer_builder, scene);
+  canvas->drawImageRect(image_resource->image()->image().get(),
+                        image_rect_
+                            ? image_rect_->To<SkRect>()
+                            : SkRect::MakeWH(image_resource->image()->width(),
+                                             image_resource->image()->height()),
+                        content_rect_.To<SkRect>(), &paint);
+
+  NodeDef::RecordPictureInner(content, snapshot, canvas);
 }
 
-SceneNodeOp::SceneNodeOp(uint32_t scene_resource_id, uint32_t scene_version)
-    : scene_resource_id_(scene_resource_id), scene_version_(scene_version) {}
+SceneNodeDef::SceneNodeDef(uint32_t node_id,
+                           mojo::TransformPtr content_transform,
+                           mojo::RectPtr content_clip,
+                           Combinator combinator,
+                           const std::vector<uint32_t>& child_node_ids,
+                           uint32_t scene_resource_id,
+                           uint32_t scene_version)
+    : NodeDef(node_id,
+              content_transform.Pass(),
+              content_clip.Pass(),
+              combinator,
+              child_node_ids),
+      scene_resource_id_(scene_resource_id),
+      scene_version_(scene_version) {}
 
-SceneNodeOp::~SceneNodeOp() {}
+SceneNodeDef::~SceneNodeDef() {}
 
-bool SceneNodeOp::Validate(SceneDef* scene, NodeDef* node, std::ostream& err) {
-  scene_resource_ = scene->FindSceneResource(scene_resource_id_);
-  if (!scene_resource_) {
-    err << "Node refers to unknown or invalid scene resource: "
-        << node->FormattedLabel(scene)
-        << ", scene_resource_id=" << scene_resource_id_;
-    return false;
-  }
-  return true;
+bool SceneNodeDef::RecordContent(SceneContentBuilder* builder) const {
+  DCHECK(builder);
+
+  return NodeDef::RecordContent(builder) &&
+         builder->RequireResource(scene_resource_id_, ResourceDef::Type::kScene,
+                                  node_id());
 }
 
-bool SceneNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
-                           RenderLayerBuilder* layer_builder,
-                           SceneDef* scene,
-                           NodeDef* node) {
-  DCHECK(scene_resource_);
+Snapshot::Disposition SceneNodeDef::RecordSnapshot(
+    const SceneContent* content,
+    SnapshotBuilder* builder) const {
+  DCHECK(content);
+  DCHECK(builder);
 
-  SceneDef* referenced_scene = scene_resource_->referenced_scene();
+  auto scene_resource = static_cast<const SceneResourceDef*>(
+      content->GetResource(scene_resource_id_, ResourceDef::Type::kScene));
+  DCHECK(scene_resource);
+
+  SceneDef* referenced_scene = scene_resource->referenced_scene().get();
   if (!referenced_scene) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Node blocked due to its referenced scene "
-             "resource being unavailable: "
-          << node->FormattedLabel(scene) << std::endl;
+    if (builder->block_log()) {
+      *builder->block_log()
+          << "Scene node blocked because its referenced scene is unavailable: "
+          << FormattedLabel(content) << std::endl;
     }
-    return false;
+    return Snapshot::Disposition::kBlocked;
   }
 
-  uint32_t actual_version = referenced_scene->version();
-  if (scene_version_ != mojo::gfx::composition::kSceneVersionNone &&
-      actual_version != mojo::gfx::composition::kSceneVersionNone &&
-      scene_version_ != actual_version) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Node blocked due to its referenced scene "
-             "resource not having the desired version: "
-          << node->FormattedLabel(scene)
-          << ", requested_version=" << scene_version_
-          << ", actual_version=" << actual_version << std::endl;
-    }
-    return false;
-  }
-
-  if (!referenced_scene->Snapshot(snapshot_builder, layer_builder))
-    return false;
-
-  return node->SnapshotChildren(snapshot_builder, layer_builder, scene);
+  Snapshot::Disposition disposition =
+      builder->SnapshotScene(referenced_scene, scene_version_, this, content);
+  if (disposition != Snapshot::Disposition::kSuccess)
+    return disposition;
+  return NodeDef::RecordSnapshot(content, builder);
 }
 
-LayerNodeOp::LayerNodeOp(const mojo::Size& size,
-                         mojo::gfx::composition::BlendPtr blend)
-    : size_(size), blend_(blend.Pass()) {}
+void SceneNodeDef::RecordPictureInner(const SceneContent* content,
+                                      const Snapshot* snapshot,
+                                      SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
 
-LayerNodeOp::~LayerNodeOp() {}
+  const SceneContent* resolved_content =
+      snapshot->GetResolvedSceneContent(this);
+  DCHECK(resolved_content);
 
-bool LayerNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
-                           RenderLayerBuilder* layer_builder,
-                           SceneDef* scene,
-                           NodeDef* node) {
-  SkRect content_rect = SkRect::MakeWH(size_.width, size_.height);
-  RenderLayerBuilder children_layer_builder(&content_rect);
-  if (!node->SnapshotChildren(snapshot_builder, &children_layer_builder, scene))
-    return false;
+  const NodeDef* root_node = resolved_content->GetRootNodeIfExists();
+  DCHECK(root_node);  // must have a root otherwise would have been blocked
+  root_node->RecordPicture(resolved_content, snapshot, canvas);
 
-  layer_builder->DrawSavedLayer(
-      children_layer_builder.Build(), content_rect,
-      blend_ ? MakePaintForBlend(*blend_) : SkPaint());
-  return true;
+  NodeDef::RecordPictureInner(content, snapshot, canvas);
+}
+
+LayerNodeDef::LayerNodeDef(uint32_t node_id,
+                           mojo::TransformPtr content_transform,
+                           mojo::RectPtr content_clip,
+                           Combinator combinator,
+                           const std::vector<uint32_t>& child_node_ids,
+                           const mojo::Size& size,
+                           mojo::gfx::composition::BlendPtr blend)
+    : NodeDef(node_id,
+              content_transform.Pass(),
+              content_clip.Pass(),
+              combinator,
+              child_node_ids),
+      size_(size),
+      blend_(blend.Pass()) {}
+
+LayerNodeDef::~LayerNodeDef() {}
+
+void LayerNodeDef::RecordPictureInner(const SceneContent* content,
+                                      const Snapshot* snapshot,
+                                      SkCanvas* canvas) const {
+  DCHECK(content);
+  DCHECK(snapshot);
+  DCHECK(canvas);
+
+  SkPaint paint;
+  SetPaintForBlend(&paint, blend_.get());
+
+  canvas->saveLayer(SkRect::MakeWH(size_.width, size_.height), &paint);
+  NodeDef::RecordPictureInner(content, snapshot, canvas);
+  canvas->restore();
 }
 
 }  // namespace compositor
diff --git a/services/gfx/compositor/graph/node_def.h b/services/gfx/compositor/graph/node_def.h
index beff45b..5e84d1a 100644
--- a/services/gfx/compositor/graph/node_def.h
+++ b/services/gfx/compositor/graph/node_def.h
@@ -6,159 +6,153 @@
 #define SERVICES_GFX_COMPOSITOR_GRAPH_NODE_DEF_H_
 
 #include <iosfwd>
-#include <memory>
-#include <string>
 #include <vector>
 
-#include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
 #include "mojo/services/gfx/composition/interfaces/nodes.mojom.h"
-#include "services/gfx/compositor/graph/resource_def.h"
+#include "services/gfx/compositor/graph/snapshot.h"
+
+class SkCanvas;
 
 namespace compositor {
 
-class RenderImage;
-class RenderLayerBuilder;
+class SceneContent;
+class SceneContentBuilder;
 class SceneDef;
-class SnapshotBuilder;
-class NodeOp;
 
-// Scene graph node definition.
-class NodeDef {
+// Represents a scene graph node.
+//
+// The base class mainly acts as a container for other nodes and does not
+// draw any content of its own.
+//
+// Instances of this class are immutable and reference counted so they may
+// be shared by multiple versions of the same scene.
+class NodeDef : public base::RefCounted<NodeDef> {
  public:
   using Combinator = mojo::gfx::composition::Node::Combinator;
 
   NodeDef(uint32_t node_id,
           mojo::TransformPtr content_transform,
           mojo::RectPtr content_clip,
-          uint32_t hit_id,
           Combinator combinator,
-          const std::vector<uint32_t>& child_node_ids,
-          NodeOp* op);
-  ~NodeDef();
+          const std::vector<uint32_t>& child_node_ids);
 
-  uint32_t node_id() { return node_id_; }
-  const mojo::Transform* content_transform() {
+  uint32_t node_id() const { return node_id_; }
+  const mojo::Transform* content_transform() const {
     return content_transform_.get();
   }
-  const mojo::Rect* content_clip() { return content_clip_.get(); }
-  uint32_t hit_id() { return hit_id_; }
-  Combinator combinator() { return combinator_; }
-  const std::vector<uint32_t>& child_node_ids() { return child_node_ids_; }
-  NodeOp* op() { return op_.get(); }
+  const mojo::Rect* content_clip() const { return content_clip_.get(); }
+  Combinator combinator() const { return combinator_; }
+  const std::vector<uint32_t>& child_node_ids() const {
+    return child_node_ids_;
+  }
 
-  std::string FormattedLabel(SceneDef* scene);
+  // Gets a descriptive label.
+  std::string FormattedLabel(const SceneContent* content) const;
 
-  // Updated by |Validate()|.
-  const std::vector<NodeDef*>& child_nodes() { return child_nodes_; }
+  // Called by the scene content builder to traverse the node's dependencies
+  // recursively and ensure they are included in the scene's local content.
+  // Returns true if successful, false if the node contains linkage errors.
+  virtual bool RecordContent(SceneContentBuilder* builder) const;
 
-  // Validates and prepares the object for rendering.
-  // Returns true if successful, false if errors were reported.
-  bool Validate(SceneDef* scene, std::ostream& err);
+  // Called by the snapshot builder to traverse the node's dependencies
+  // recursively follow links into other scenes, evaluate whether the
+  // node can be rendered, and record which path was taken for the purposes
+  // of satisfying combinators.
+  virtual Snapshot::Disposition RecordSnapshot(const SceneContent* content,
+                                               SnapshotBuilder* builder) const;
 
-  // Generates a snapshot of the node into the specified builder.
-  // Returns true if successful, false if the node is blocked from rendering.
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder,
-                SceneDef* scene);
+  // Called to record drawing commands from a snapshot.
+  void RecordPicture(const SceneContent* content,
+                     const Snapshot* snapshot,
+                     SkCanvas* canvas) const;
 
-  // Generates a snapshot of the node's children into the specified builder.
-  // Returns true if successful, false if the children are blocked from
-  // rendering.
-  bool SnapshotChildren(SnapshotBuilder* snapshot_builder,
-                        RenderLayerBuilder* layer_builder,
-                        SceneDef* scene);
+ protected:
+  friend class base::RefCounted<NodeDef>;
+  virtual ~NodeDef();
+
+  // Applies a unary function to the children selected by the node's
+  // combinator rule during a snapshot.
+  // Stops when |Func| returns false.
+  // |Func| should have the signature |bool func(const NodeDef*)|.
+  template <typename Func>
+  void TraverseSnapshottedChildren(const SceneContent* content,
+                                   const Snapshot* snapshot,
+                                   const Func& func) const;
+
+  virtual void RecordPictureInner(const SceneContent* content,
+                                  const Snapshot* snapshot,
+                                  SkCanvas* canvas) const;
 
  private:
-  bool SnapshotInner(SnapshotBuilder* snapshot_builder,
-                     RenderLayerBuilder* layer_builder,
-                     SceneDef* scene);
-
-  uint32_t node_id_;
+  uint32_t const node_id_;
   mojo::TransformPtr const content_transform_;
   mojo::RectPtr const content_clip_;
-  uint32_t const hit_id_;
   Combinator const combinator_;
   std::vector<uint32_t> const child_node_ids_;
-  std::unique_ptr<NodeOp> const op_;
-
-  std::vector<NodeDef*> child_nodes_;
-
-  // Used to detect cycles during a snapshot operation.
-  // This is safe because the object will only be used by a single thread.
-  bool visited_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(NodeDef);
 };
 
-// Abstract scene graph node operation.
-class NodeOp {
+// Represents a rectangle node.
+//
+// Draws a solid color filled rectangle node underneath its children.
+class RectNodeDef : public NodeDef {
  public:
-  NodeOp() = default;
-  virtual ~NodeOp() = default;
+  RectNodeDef(uint32_t node_id,
+              mojo::TransformPtr content_transform,
+              mojo::RectPtr content_clip,
+              Combinator combinator,
+              const std::vector<uint32_t>& child_node_ids,
+              const mojo::Rect& content_rect,
+              const mojo::gfx::composition::Color& color);
 
-  // Validates and prepares the object for rendering.
-  // Returns true if successful, false if errors were reported.
-  virtual bool Validate(SceneDef* scene, NodeDef* node, std::ostream& err);
+  const mojo::Rect& content_rect() const { return content_rect_; }
+  const mojo::gfx::composition::Color& color() const { return color_; }
 
-  // Generates a snapshot of the node operation into the specified builder.
-  // This method is responsible for calling |SnapshotChildren| to process
-  // the children of the node.  Returns true if successful, false if the node
-  // is blocked from rendering.
-  virtual bool Snapshot(SnapshotBuilder* snapshot_builder,
-                        RenderLayerBuilder* layer_builder,
-                        SceneDef* scene,
-                        NodeDef* node) = 0;
+ protected:
+  ~RectNodeDef() override;
+
+  void RecordPictureInner(const SceneContent* content,
+                          const Snapshot* snapshot,
+                          SkCanvas* canvas) const override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(NodeOp);
+  mojo::Rect const content_rect_;
+  mojo::gfx::composition::Color const color_;
+
+  DISALLOW_COPY_AND_ASSIGN(RectNodeDef);
 };
 
-// A solid color filled rectangle node definition.
-class RectNodeOp : public NodeOp {
+// Represents an image node.
+//
+// Draws an image filled rectangle underneath its children.
+class ImageNodeDef : public NodeDef {
  public:
-  RectNodeOp(const mojo::Rect& content_rect,
-             const mojo::gfx::composition::Color& color);
-  ~RectNodeOp() override;
+  ImageNodeDef(uint32_t node_id,
+               mojo::TransformPtr content_transform,
+               mojo::RectPtr content_clip,
+               Combinator combinator,
+               const std::vector<uint32_t>& child_node_ids,
+               const mojo::Rect& content_rect,
+               mojo::RectPtr image_rect,
+               uint32 image_resource_id,
+               mojo::gfx::composition::BlendPtr blend);
 
-  const mojo::Rect& content_rect() { return content_rect_; }
-  const mojo::gfx::composition::Color& color() { return color_; }
+  const mojo::Rect& content_rect() const { return content_rect_; }
+  const mojo::Rect* image_rect() const { return image_rect_.get(); }
+  uint32_t image_resource_id() const { return image_resource_id_; }
+  const mojo::gfx::composition::Blend* blend() const { return blend_.get(); }
 
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder,
-                SceneDef* scene,
-                NodeDef* node) override;
+  bool RecordContent(SceneContentBuilder* builder) const override;
 
- private:
-  mojo::Rect content_rect_;
-  mojo::gfx::composition::Color color_;
+ protected:
+  ~ImageNodeDef() override;
 
-  DISALLOW_COPY_AND_ASSIGN(RectNodeOp);
-};
-
-// An image filled rectangle node definition.
-class ImageNodeOp : public NodeOp {
- public:
-  ImageNodeOp(const mojo::Rect& content_rect,
-              mojo::RectPtr image_rect,
-              uint32 image_resource_id,
-              mojo::gfx::composition::BlendPtr blend);
-  ~ImageNodeOp() override;
-
-  const mojo::Rect& content_rect() { return content_rect_; }
-  mojo::Rect* image_rect() { return image_rect_.get(); }
-  uint32_t image_resource_id() { return image_resource_id_; }
-  mojo::gfx::composition::Blend* blend() { return blend_.get(); }
-
-  // Updated by |Validate()|.
-  ImageResourceDef* image_resource() { return image_resource_; }
-
-  bool Validate(SceneDef* scene, NodeDef* node, std::ostream& err) override;
-
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder,
-                SceneDef* scene,
-                NodeDef* node) override;
+  void RecordPictureInner(const SceneContent* content,
+                          const Snapshot* snapshot,
+                          SkCanvas* canvas) const override;
 
  private:
   mojo::Rect const content_rect_;
@@ -166,58 +160,72 @@
   uint32_t const image_resource_id_;
   mojo::gfx::composition::BlendPtr const blend_;
 
-  ImageResourceDef* image_resource_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(ImageNodeOp);
+  DISALLOW_COPY_AND_ASSIGN(ImageNodeDef);
 };
 
-// An embedded scene node definition.
-class SceneNodeOp : public NodeOp {
+// Represents a scene node.
+//
+// Draws an embedded scene underneath its children.
+class SceneNodeDef : public NodeDef {
  public:
-  SceneNodeOp(uint32_t scene_resource_id, uint32_t scene_version);
-  ~SceneNodeOp() override;
+  SceneNodeDef(uint32_t node_id,
+               mojo::TransformPtr content_transform,
+               mojo::RectPtr content_clip,
+               Combinator combinator,
+               const std::vector<uint32_t>& child_node_ids,
+               uint32_t scene_resource_id,
+               uint32_t scene_version);
 
-  uint32_t scene_resource_id() { return scene_resource_id_; }
-  uint32_t scene_version() { return scene_version_; }
+  uint32_t scene_resource_id() const { return scene_resource_id_; }
+  uint32_t scene_version() const { return scene_version_; }
 
-  // Updated by |Validate()|.
-  SceneResourceDef* scene_resource() { return scene_resource_; }
+  bool RecordContent(SceneContentBuilder* builder) const override;
 
-  bool Validate(SceneDef* scene, NodeDef* node, std::ostream& err) override;
+  Snapshot::Disposition RecordSnapshot(const SceneContent* content,
+                                       SnapshotBuilder* builder) const override;
 
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder,
-                SceneDef* scene,
-                NodeDef* node) override;
+ protected:
+  ~SceneNodeDef() override;
+
+  void RecordPictureInner(const SceneContent* content,
+                          const Snapshot* snapshot,
+                          SkCanvas* canvas) const override;
 
  private:
   uint32_t const scene_resource_id_;
   uint32_t const scene_version_;
 
-  SceneResourceDef* scene_resource_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(SceneNodeOp);
+  DISALLOW_COPY_AND_ASSIGN(SceneNodeDef);
 };
 
-// A composited layer node definition.
-class LayerNodeOp : public NodeOp {
+// Represents a layer node.
+//
+// Composites its children to a layer and applies a blending operation.
+class LayerNodeDef : public NodeDef {
  public:
-  LayerNodeOp(const mojo::Size& size, mojo::gfx::composition::BlendPtr blend);
-  ~LayerNodeOp() override;
+  LayerNodeDef(uint32_t node_id,
+               mojo::TransformPtr content_transform,
+               mojo::RectPtr content_clip,
+               Combinator combinator,
+               const std::vector<uint32_t>& child_node_ids,
+               const mojo::Size& size,
+               mojo::gfx::composition::BlendPtr blend);
 
-  const mojo::Size& size() { return size_; }
-  mojo::gfx::composition::Blend* blend() { return blend_.get(); }
+  const mojo::Size& size() const { return size_; }
+  const mojo::gfx::composition::Blend* blend() const { return blend_.get(); }
 
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder,
-                SceneDef* scene,
-                NodeDef* node) override;
+ protected:
+  ~LayerNodeDef() override;
+
+  void RecordPictureInner(const SceneContent* content,
+                          const Snapshot* snapshot,
+                          SkCanvas* canvas) const override;
 
  private:
   mojo::Size const size_;
   mojo::gfx::composition::BlendPtr const blend_;
 
-  DISALLOW_COPY_AND_ASSIGN(LayerNodeOp);
+  DISALLOW_COPY_AND_ASSIGN(LayerNodeDef);
 };
 
 }  // namespace compositor
diff --git a/services/gfx/compositor/graph/resource_def.cc b/services/gfx/compositor/graph/resource_def.cc
index f837f1e..2ebf957 100644
--- a/services/gfx/compositor/graph/resource_def.cc
+++ b/services/gfx/compositor/graph/resource_def.cc
@@ -8,11 +8,21 @@
 
 namespace compositor {
 
-SceneResourceDef::SceneResourceDef(SceneDef* referenced_scene)
-    : referenced_scene_(referenced_scene) {}
+ResourceDef::ResourceDef() {}
+
+ResourceDef::~ResourceDef() {}
+
+SceneResourceDef::SceneResourceDef(
+    const mojo::gfx::composition::SceneToken& scene_token,
+    const base::WeakPtr<SceneDef>& referenced_scene)
+    : scene_token_(scene_token), referenced_scene_(referenced_scene) {}
 
 SceneResourceDef::~SceneResourceDef() {}
 
+scoped_refptr<const SceneResourceDef> SceneResourceDef::Unlink() const {
+  return new SceneResourceDef(scene_token_, base::WeakPtr<SceneDef>());
+}
+
 ResourceDef::Type SceneResourceDef::type() const {
   return Type::kScene;
 }
diff --git a/services/gfx/compositor/graph/resource_def.h b/services/gfx/compositor/graph/resource_def.h
index 9f3d8c8..e29d3cf 100644
--- a/services/gfx/compositor/graph/resource_def.h
+++ b/services/gfx/compositor/graph/resource_def.h
@@ -8,6 +8,8 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "mojo/services/gfx/composition/interfaces/resources.mojom.h"
 #include "services/gfx/compositor/render/render_image.h"
 
@@ -16,39 +18,53 @@
 class SceneDef;
 
 // Abstract scene graph resource definition.
-class ResourceDef {
+//
+// Instances of this class are immutable and reference counted so they may
+// be shared by multiple versions of the same scene.
+class ResourceDef : public base::RefCounted<ResourceDef> {
  public:
   enum class Type { kScene, kImage };
 
-  ResourceDef() = default;
-  virtual ~ResourceDef() = default;
+  ResourceDef();
 
   // Gets the resource type.
   virtual Type type() const = 0;
 
+ protected:
+  friend class base::RefCounted<ResourceDef>;
+  virtual ~ResourceDef();
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceDef);
 };
 
 // Reference to another scene expressed as a resource definition.
-// The pointer may be null if the referenced scene has become unavailable.
 class SceneResourceDef : public ResourceDef {
  public:
-  explicit SceneResourceDef(SceneDef* referenced_scene);
-  ~SceneResourceDef() override;
+  explicit SceneResourceDef(
+      const mojo::gfx::composition::SceneToken& scene_token,
+      const base::WeakPtr<SceneDef>& referenced_scene);
 
   Type type() const override;
 
-  // The referenced scene, may be null if unavailable.
-  SceneDef* referenced_scene() { return referenced_scene_; }
+  const mojo::gfx::composition::SceneToken& scene_token() const {
+    return scene_token_;
+  }
 
-  // Clears the referenced scene.
-  // This is called by |SceneDef::UnlinkReferencedScene| when the
-  // referenced scene is no longer available.
-  void clear_referenced_scene() { referenced_scene_ = nullptr; }
+  // The referenced scene, may be null if the scene is unavailable.
+  const base::WeakPtr<SceneDef>& referenced_scene() const {
+    return referenced_scene_;
+  }
+
+  // Returns a copy of the resource without its referenced scene.
+  scoped_refptr<const SceneResourceDef> Unlink() const;
+
+ protected:
+  ~SceneResourceDef() override;
 
  private:
-  SceneDef* referenced_scene_;
+  mojo::gfx::composition::SceneToken scene_token_;
+  base::WeakPtr<SceneDef> referenced_scene_;
 
   DISALLOW_COPY_AND_ASSIGN(SceneResourceDef);
 };
@@ -57,12 +73,14 @@
 class ImageResourceDef : public ResourceDef {
  public:
   explicit ImageResourceDef(const std::shared_ptr<RenderImage>& image);
-  ~ImageResourceDef() override;
 
   Type type() const override;
 
-  // The referenced image, may be null if unavailable.
-  const std::shared_ptr<RenderImage>& image() { return image_; }
+  // The referenced image, never null.
+  const std::shared_ptr<RenderImage>& image() const { return image_; }
+
+ protected:
+  ~ImageResourceDef() override;
 
  private:
   std::shared_ptr<RenderImage> image_;
diff --git a/services/gfx/compositor/graph/scene_content.cc b/services/gfx/compositor/graph/scene_content.cc
new file mode 100644
index 0000000..ad4fcf4
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_content.cc
@@ -0,0 +1,142 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/gfx/compositor/graph/scene_content.h"
+
+#include <ostream>
+
+#include "base/logging.h"
+#include "services/gfx/compositor/graph/scene_def.h"
+
+namespace compositor {
+
+SceneContent::SceneContent(const SceneLabel& label,
+                           uint32_t version,
+                           size_t max_resources,
+                           size_t max_nodes)
+    : label_(label),
+      version_(version),
+      resources_(max_resources),
+      nodes_(max_nodes) {}
+
+SceneContent::~SceneContent() {}
+
+const ResourceDef* SceneContent::GetResource(
+    uint32_t resource_id,
+    ResourceDef::Type resource_type) const {
+  auto it = resources_.find(resource_id);
+  DCHECK(it != resources_.end());
+  DCHECK(it->second->type() == resource_type);
+  return it->second.get();
+}
+
+const NodeDef* SceneContent::GetNode(uint32_t node_id) const {
+  auto it = nodes_.find(node_id);
+  DCHECK(it != nodes_.end());
+  return it->second.get();
+}
+
+const NodeDef* SceneContent::GetRootNodeIfExists() const {
+  auto it = nodes_.find(mojo::gfx::composition::kSceneRootNodeId);
+  return it != nodes_.end() ? it->second.get() : nullptr;
+}
+
+SceneContentBuilder::SceneContentBuilder(const SceneDef* scene,
+                                         uint32_t version,
+                                         std::ostream& err,
+                                         size_t max_resources,
+                                         size_t max_nodes)
+    : content_(
+          new SceneContent(scene->label(), version, max_resources, max_nodes)),
+      scene_(scene),
+      err_(err) {
+  DCHECK(scene);
+}
+
+SceneContentBuilder::~SceneContentBuilder() {}
+
+const ResourceDef* SceneContentBuilder::RequireResource(
+    uint32_t resource_id,
+    ResourceDef::Type resource_type,
+    uint32_t referrer_node_id) {
+  DCHECK(content_);
+
+  auto it = content_->resources_.find(resource_id);
+  if (it != content_->resources_.end())
+    return it->second.get();
+
+  const ResourceDef* resource = scene_->FindResource(resource_id);
+  if (!resource) {
+    err_ << "Missing resource " << resource_id << " referenced from node "
+         << content_->FormattedLabelForNode(referrer_node_id);
+    return nullptr;
+  }
+
+  if (resource->type() != resource_type) {
+    err_ << "Resource " << resource_id << " referenced from node "
+         << content_->FormattedLabelForNode(referrer_node_id)
+         << " has incorrect type for its intended usage";
+    return nullptr;
+  }
+
+  content_->resources_.emplace(std::make_pair(resource_id, resource));
+  return resource;
+}
+
+const NodeDef* SceneContentBuilder::RequireNode(uint32_t node_id,
+                                                uint32_t referrer_node_id) {
+  DCHECK(content_);
+
+  auto it = content_->nodes_.find(node_id);
+  if (it != content_->nodes_.end()) {
+    if (it->second)
+      return it->second.get();
+    err_ << "Cycle detected at node " << node_id << " referenced from node "
+         << content_->FormattedLabelForNode(referrer_node_id);
+    return nullptr;
+  }
+
+  const NodeDef* node = scene_->FindNode(node_id);
+  if (!node) {
+    err_ << "Missing node " << node_id << " referenced from node "
+         << content_->FormattedLabelForNode(referrer_node_id);
+    return nullptr;
+  }
+
+  return AddNode(node) ? node : nullptr;
+}
+
+bool SceneContentBuilder::AddNode(const NodeDef* node) {
+  DCHECK(content_);
+  DCHECK(node);
+
+  // Reserve a spot in the table to mark the node recording in progress.
+  DCHECK(content_->nodes_.size() < content_->nodes_.bucket_count());
+  auto storage = content_->nodes_.emplace(node->node_id(), nullptr);
+  DCHECK(storage.second);
+
+  // Record the node's content.
+  // This performs a depth first search of the node.  If it succeeds, we
+  // will know that this part of the graph has no cycles.  Note that this
+  // function may recurse back into |AddNode| to add additional nodes.
+  if (!node->RecordContent(this))
+    return false;
+
+  // Store the node in the table.
+  // It is safe to use the interator returned by emplace even though additional
+  // nodes may have been added since the map's bucket count was initialized
+  // at creation time to the total number of nodes so it should never be
+  // rehashed during this traversal.
+  storage.first->second = node;
+  return true;
+}
+
+scoped_refptr<const SceneContent> SceneContentBuilder::Build() {
+  DCHECK(content_);
+
+  const NodeDef* root = scene_->FindRootNode();
+  return !root || AddNode(root) ? std::move(content_) : nullptr;
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/scene_content.h b/services/gfx/compositor/graph/scene_content.h
new file mode 100644
index 0000000..f3ccb5a
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_content.h
@@ -0,0 +1,117 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_CONTENT_H_
+#define SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_CONTENT_H_
+
+#include <iosfwd>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "services/gfx/compositor/graph/node_def.h"
+#include "services/gfx/compositor/graph/resource_def.h"
+#include "services/gfx/compositor/graph/scene_label.h"
+
+namespace compositor {
+
+class SceneContentBuilder;
+class SceneDef;
+
+// Represents the content of a particular published version of a scene.
+//
+// Holds a resource and node table which describes the content of a
+// scene as it was when a particular version was published.  Only the
+// internal state of the scene is described; links to other scenes are
+// not resolved at this level.
+//
+// Once fully constructed, instances of this class are immutable and
+// reference counted so they may be bound to scene references in other scenes.
+//
+// TODO(jeffbrown): To improve efficiency, we could replace the hash tables
+// with a vector of internally linked graph edges.  This is relatively easy
+// since the traversal order is well-known and we could even build some kind
+// of hierarchical iterator to walk the graph starting from the root.
+class SceneContent : public base::RefCounted<SceneContent> {
+ public:
+  // Gets the scene label.
+  const SceneLabel& label() const { return label_; }
+  std::string FormattedLabel() const {
+    return label_.FormattedLabelForVersion(version_);
+  }
+  std::string FormattedLabelForNode(uint32_t node_id) const {
+    return label_.FormattedLabelForNode(version_, node_id);
+  }
+
+  // Gets the version of the scene represented by this object.
+  uint32_t version() const { return version_; }
+
+  // Gets the requested resource, never null because it must be present.
+  const ResourceDef* GetResource(uint32_t resource_id,
+                                 ResourceDef::Type resource_type) const;
+
+  // Gets the requested node, never null because it must be present.
+  const NodeDef* GetNode(uint32_t node_id) const;
+
+  // Gets the root node if it exists, otherwise returns nullptr.
+  const NodeDef* GetRootNodeIfExists() const;
+
+ private:
+  friend class base::RefCounted<SceneContent>;
+  friend class SceneContentBuilder;
+  SceneContent(const SceneLabel& label,
+               uint32_t version,
+               size_t max_resources,
+               size_t max_nodes);
+  ~SceneContent();
+
+  const SceneLabel label_;
+  const uint32_t version_;
+  std::unordered_map<uint32_t, scoped_refptr<const ResourceDef>> resources_;
+  std::unordered_map<uint32_t, scoped_refptr<const NodeDef>> nodes_;
+
+  DISALLOW_COPY_AND_ASSIGN(SceneContent);
+};
+
+// Builds a table of all of the nodes and resources that make up the
+// content of a particular version of a scene.
+class SceneContentBuilder {
+ public:
+  SceneContentBuilder(const SceneDef* scene,
+                      uint32_t version,
+                      std::ostream& err,
+                      size_t max_resources,
+                      size_t max_nodes);
+  ~SceneContentBuilder();
+
+  // Stream for reporting validation error messages.
+  std::ostream& err() { return err_; }
+
+  // Ensures the requested resource is part of the retained scene graph and
+  // returns a reference to it, or nullptr if an error occurred.
+  const ResourceDef* RequireResource(uint32_t resource_id,
+                                     ResourceDef::Type resource_type,
+                                     uint32_t referrer_node_id);
+
+  // Ensures the requested node is part of the retained scene graph and
+  // returns a reference to it, or nullptr if an error occurred.
+  const NodeDef* RequireNode(uint32_t node_id, uint32_t referrer_node_id);
+
+  // Builds the content graph.
+  // Returns nullptr if an error occurred.
+  scoped_refptr<const SceneContent> Build();
+
+ private:
+  bool AddNode(const NodeDef* node);
+
+  scoped_refptr<SceneContent> content_;
+  const SceneDef* scene_;
+  std::ostream& err_;
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_CONTENT_H_
diff --git a/services/gfx/compositor/graph/scene_def.cc b/services/gfx/compositor/graph/scene_def.cc
index a23c108..649c43e 100644
--- a/services/gfx/compositor/graph/scene_def.cc
+++ b/services/gfx/compositor/graph/scene_def.cc
@@ -5,16 +5,14 @@
 #include "services/gfx/compositor/graph/scene_def.h"
 
 #include <ostream>
-#include <utility>
 
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "mojo/services/gfx/composition/cpp/formatting.h"
-#include "services/gfx/compositor/graph/snapshot.h"
+#include "services/gfx/compositor/graph/scene_content.h"
 #include "services/gfx/compositor/render/render_image.h"
-#include "services/gfx/compositor/render/render_layer.h"
 
 namespace compositor {
 
@@ -31,11 +29,8 @@
 }
 }  // namespace
 
-SceneDef::SceneDef(mojo::gfx::composition::SceneTokenPtr scene_token,
-                   const std::string& label)
-    : scene_token_(scene_token.Pass()), label_(label) {
-  DCHECK(scene_token_);
-}
+SceneDef::SceneDef(const SceneLabel& label)
+    : label_(label), weak_factory_(this) {}
 
 SceneDef::~SceneDef() {}
 
@@ -68,15 +63,8 @@
     end--;
   }
 
-  // Prepare to apply all publications up to this point.
-  uint32_t version = pending_publications_[end - 1]->metadata->version;
-  if (version_ != version) {
-    version_ = version;
-    formatted_label_cache_.clear();
-  }
-  Invalidate();
-
-  // Apply all updates sequentially.
+  // Apply all updates sequentially up to this point.
+  version_ = pending_publications_[end - 1]->metadata->version;
   for (size_t index = 0; index < end; ++index) {
     for (auto& update : pending_publications_[index]->updates) {
       if (!ApplyUpdate(update.Pass(), resolver, unavailable_sender, err))
@@ -88,10 +76,12 @@
   pending_publications_.erase(pending_publications_.begin(),
                               pending_publications_.begin() + end);
 
-  // Ensure the scene is in a valid state.
-  if (!Validate(err))
-    return Disposition::kFailed;
-  return Disposition::kSucceeded;
+  // Rebuild the scene content, gathering all reachable nodes and resources
+  // and verifying that everything is correctly linked.
+  SceneContentBuilder builder(this, version_, err, resources_.size(),
+                              nodes_.size());
+  content_ = builder.Build();
+  return content_ ? Disposition::kSucceeded : Disposition::kFailed;
 }
 
 bool SceneDef::ApplyUpdate(mojo::gfx::composition::SceneUpdatePtr update,
@@ -100,6 +90,12 @@
                            std::ostream& err) {
   DCHECK(update);
 
+  // TODO(jeffbrown): We may be able to reuse some content from previous
+  // versions even when the client removes and recreates resources or nodes.
+  // To reduce unnecessary churn, consider keeping track of items which have
+  // been removed or are being replaced then checking to see whether they
+  // really changed.
+
   // Update resources.
   if (update->clear_resources) {
     resources_.clear();
@@ -109,11 +105,11 @@
     uint32_t resource_id = it.GetKey();
     mojo::gfx::composition::ResourcePtr& resource_decl = it.GetValue();
     if (resource_decl) {
-      ResourceDef* resource = CreateResource(resource_id, resource_decl.Pass(),
-                                             resolver, unavailable_sender, err);
+      scoped_refptr<const ResourceDef> resource = CreateResource(
+          resource_id, resource_decl.Pass(), resolver, unavailable_sender, err);
       if (!resource)
         return false;
-      resources_[resource_id].reset(resource);
+      resources_[resource_id] = std::move(resource);
     } else {
       resources_.erase(resource_id);
     }
@@ -127,10 +123,11 @@
     uint32_t node_id = it.GetKey();
     mojo::gfx::composition::NodePtr& node_decl = it.GetValue();
     if (node_decl) {
-      NodeDef* node = CreateNode(node_id, node_decl.Pass(), err);
+      scoped_refptr<const NodeDef> node =
+          CreateNode(node_id, node_decl.Pass(), err);
       if (!node)
         return false;
-      nodes_[node_id].reset(node);
+      nodes_[node_id] = std::move(node);
     } else {
       nodes_.erase(node_id);
     }
@@ -138,22 +135,6 @@
   return true;
 }
 
-bool SceneDef::Validate(std::ostream& err) {
-  // Validate all nodes.
-  // TODO(jeffbrown): Figure out how to do this incrementally if it gets
-  // too expensive to process all nodes each time.
-  root_node_ = nullptr;
-  for (auto& pair : nodes_) {
-    uint32_t node_id = pair.first;
-    NodeDef* node = pair.second.get();
-    if (!node->Validate(this, err))
-      return false;
-    if (node_id == mojo::gfx::composition::kSceneRootNodeId)
-      root_node_ = node;
-  }
-  return true;
-}
-
 bool SceneDef::UnlinkReferencedScene(
     SceneDef* scene,
     const SceneUnavailableSender& unavailable_sender) {
@@ -162,11 +143,11 @@
   bool changed = false;
   for (auto& pair : resources_) {
     if (pair.second->type() == ResourceDef::Type::kScene) {
-      auto scene_resource = static_cast<SceneResourceDef*>(pair.second.get());
-      if (scene_resource->referenced_scene() == scene) {
-        scene_resource->clear_referenced_scene();
-        Invalidate();
+      auto scene_resource =
+          static_cast<const SceneResourceDef*>(pair.second.get());
+      if (scene_resource->referenced_scene().get() == scene) {
         changed = true;
+        pair.second = scene_resource->Unlink();
         unavailable_sender.Run(pair.first);
       }
     }
@@ -174,86 +155,7 @@
   return changed;
 }
 
-bool SceneDef::Snapshot(SnapshotBuilder* snapshot_builder,
-                        RenderLayerBuilder* layer_builder) {
-  DCHECK(snapshot_builder);
-  DCHECK(layer_builder);
-
-  // Detect cycles.
-  if (visited_) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Scene blocked due to recursive cycle: " << FormattedLabel()
-          << std::endl;
-    }
-    return false;
-  }
-
-  // Snapshot the contents of the scene.
-  visited_ = true;
-  bool success = SnapshotInner(snapshot_builder, layer_builder);
-  visited_ = false;
-  return success;
-}
-
-bool SceneDef::SnapshotInner(SnapshotBuilder* snapshot_builder,
-                             RenderLayerBuilder* layer_builder) {
-  // Note the dependency even if blocked.
-  snapshot_builder->AddSceneDependency(this);
-
-  // Ensure we have a root node.
-  if (!root_node_) {
-    if (snapshot_builder->block_log()) {
-      *snapshot_builder->block_log()
-          << "Scene blocked due because it has no root node: "
-          << FormattedLabel() << std::endl;
-    }
-    return false;
-  }
-
-  // Snapshot and draw the layer.
-  std::shared_ptr<RenderLayer> scene_layer = SnapshotLayer(snapshot_builder);
-  if (!scene_layer)
-    return false;
-  layer_builder->DrawLayer(scene_layer);
-  return true;
-}
-
-std::shared_ptr<RenderLayer> SceneDef::SnapshotLayer(
-    SnapshotBuilder* snapshot_builder) {
-  if (cached_layer_)
-    return cached_layer_;
-
-  RenderLayerBuilder scene_layer_builder;
-  scene_layer_builder.PushScene(scene_token_->value, version_);
-  if (!root_node_->Snapshot(snapshot_builder, &scene_layer_builder, this))
-    return nullptr;
-  scene_layer_builder.PopScene();
-
-  // TODO(jeffbrown): Implement caching even when the scene has dependencies.
-  // There are some subtleties to be dealt with to ensure that caches
-  // are properly invalidated and that we don't accidentally cache layers which
-  // bake in decisions which counteract the intended cycle detection and
-  // avoidance behavior.  Basically just need better bookkeeping.
-  std::shared_ptr<RenderLayer> scene_layer = scene_layer_builder.Build();
-  if (!HasSceneResources())
-    cached_layer_ = scene_layer;
-  return scene_layer;
-}
-
-void SceneDef::Invalidate() {
-  cached_layer_.reset();
-}
-
-bool SceneDef::HasSceneResources() {
-  for (auto& pair : resources_) {
-    if (pair.second->type() == ResourceDef::Type::kScene)
-      return true;
-  }
-  return false;
-}
-
-ResourceDef* SceneDef::CreateResource(
+scoped_refptr<const ResourceDef> SceneDef::CreateResource(
     uint32_t resource_id,
     mojo::gfx::composition::ResourcePtr resource_decl,
     const SceneResolver& resolver,
@@ -264,12 +166,13 @@
   if (resource_decl->is_scene()) {
     auto& scene_resource_decl = resource_decl->get_scene();
     DCHECK(scene_resource_decl->scene_token);
-    SceneDef* referenced_scene =
-        resolver.Run(scene_resource_decl->scene_token.get());
-    if (!referenced_scene) {
+
+    const mojo::gfx::composition::SceneToken& scene_token =
+        *scene_resource_decl->scene_token;
+    base::WeakPtr<SceneDef> referenced_scene = resolver.Run(scene_token);
+    if (!referenced_scene)
       unavailable_sender.Run(resource_id);
-    }
-    return new SceneResourceDef(referenced_scene);
+    return new SceneResourceDef(scene_token, referenced_scene);
   }
 
   if (resource_decl->is_mailbox_texture()) {
@@ -277,8 +180,9 @@
     DCHECK(mailbox_texture_resource_decl->mailbox_name.size() ==
            GL_MAILBOX_SIZE_CHROMIUM);
     DCHECK(mailbox_texture_resource_decl->size);
-    int32_t width = mailbox_texture_resource_decl->size->width;
-    int32_t height = mailbox_texture_resource_decl->size->height;
+
+    const int32_t width = mailbox_texture_resource_decl->size->width;
+    const int32_t height = mailbox_texture_resource_decl->size->height;
     if (width < 1 || width > kMaxTextureWidth || height < 1 ||
         height > kMaxTextureHeight) {
       err << "MailboxTexture resource has invalid size: "
@@ -286,10 +190,12 @@
           << ", height=" << height;
       return nullptr;
     }
+    const GLbyte* const mailbox_name = reinterpret_cast<GLbyte*>(
+        mailbox_texture_resource_decl->mailbox_name.data());
+    const GLuint sync_point = mailbox_texture_resource_decl->sync_point;
+
     std::shared_ptr<RenderImage> image = RenderImage::CreateFromMailboxTexture(
-        reinterpret_cast<GLbyte*>(
-            mailbox_texture_resource_decl->mailbox_name.data()),
-        mailbox_texture_resource_decl->sync_point, width, height,
+        mailbox_name, sync_point, width, height,
         base::MessageLoop::current()->task_runner(),
         base::Bind(
             &ReleaseMailboxTexture,
@@ -307,86 +213,97 @@
   return nullptr;
 }
 
-NodeDef* SceneDef::CreateNode(uint32_t node_id,
-                              mojo::gfx::composition::NodePtr node_decl,
-                              std::ostream& err) {
+scoped_refptr<const NodeDef> SceneDef::CreateNode(
+    uint32_t node_id,
+    mojo::gfx::composition::NodePtr node_decl,
+    std::ostream& err) {
   DCHECK(node_decl);
 
-  NodeOp* op = nullptr;
-  if (node_decl->op) {
-    op = CreateNodeOp(node_id, node_decl->op.Pass(), err);
-    if (!op)
-      return nullptr;
+  mojo::TransformPtr content_transform = node_decl->content_transform.Pass();
+  mojo::RectPtr content_clip = node_decl->content_clip.Pass();
+  const mojo::gfx::composition::Node::Combinator combinator =
+      node_decl->combinator;
+  const std::vector<uint32_t>& child_node_ids =
+      node_decl->child_node_ids.storage();
+
+  if (!node_decl->op) {
+    return new NodeDef(node_id, content_transform.Pass(), content_clip.Pass(),
+                       combinator, child_node_ids);
   }
 
-  return new NodeDef(node_id, node_decl->content_transform.Pass(),
-                     node_decl->content_clip.Pass(), node_decl->hit_id,
-                     node_decl->combinator, node_decl->child_node_ids.storage(),
-                     op);
-}
+  if (node_decl->op->is_rect()) {
+    auto& rect_node_decl = node_decl->op->get_rect();
+    DCHECK(rect_node_decl->content_rect);
+    DCHECK(rect_node_decl->color);
 
-NodeOp* SceneDef::CreateNodeOp(uint32_t node_id,
-                               mojo::gfx::composition::NodeOpPtr node_op_decl,
-                               std::ostream& err) {
-  DCHECK(node_op_decl);
-
-  if (node_op_decl->is_rect()) {
-    auto& rect_node_op_decl = node_op_decl->get_rect();
-    DCHECK(rect_node_op_decl->content_rect);
-    DCHECK(rect_node_op_decl->color);
-    return new RectNodeOp(*rect_node_op_decl->content_rect,
-                          *rect_node_op_decl->color);
+    const mojo::Rect& content_rect = *rect_node_decl->content_rect;
+    const mojo::gfx::composition::Color& color = *rect_node_decl->color;
+    return new RectNodeDef(node_id, content_transform.Pass(),
+                           content_clip.Pass(), combinator, child_node_ids,
+                           content_rect, color);
   }
 
-  if (node_op_decl->is_image()) {
-    auto& image_node_op_decl = node_op_decl->get_image();
-    DCHECK(image_node_op_decl->content_rect);
-    return new ImageNodeOp(*image_node_op_decl->content_rect,
-                           image_node_op_decl->image_rect.Pass(),
-                           image_node_op_decl->image_resource_id,
-                           image_node_op_decl->blend.Pass());
+  if (node_decl->op->is_image()) {
+    auto& image_node_decl = node_decl->op->get_image();
+    DCHECK(image_node_decl->content_rect);
+
+    const mojo::Rect& content_rect = *image_node_decl->content_rect;
+    mojo::RectPtr image_rect = image_node_decl->image_rect.Pass();
+    const uint32 image_resource_id = image_node_decl->image_resource_id;
+    mojo::gfx::composition::BlendPtr blend = image_node_decl->blend.Pass();
+    return new ImageNodeDef(node_id, content_transform.Pass(),
+                            content_clip.Pass(), combinator, child_node_ids,
+                            content_rect, image_rect.Pass(), image_resource_id,
+                            blend.Pass());
   }
 
-  if (node_op_decl->is_scene()) {
-    auto& scene_node_op_decl = node_op_decl->get_scene();
-    return new SceneNodeOp(scene_node_op_decl->scene_resource_id,
-                           scene_node_op_decl->scene_version);
+  if (node_decl->op->is_scene()) {
+    auto& scene_node_decl = node_decl->op->get_scene();
+
+    const uint32_t scene_resource_id = scene_node_decl->scene_resource_id;
+    const uint32_t scene_version = scene_node_decl->scene_version;
+    return new SceneNodeDef(node_id, content_transform.Pass(),
+                            content_clip.Pass(), combinator, child_node_ids,
+                            scene_resource_id, scene_version);
   }
 
-  if (node_op_decl->is_layer()) {
-    auto& layer_node_op_decl = node_op_decl->get_layer();
-    DCHECK(layer_node_op_decl->layer_size);
-    return new LayerNodeOp(*layer_node_op_decl->layer_size,
-                           layer_node_op_decl->blend.Pass());
+  if (node_decl->op->is_layer()) {
+    auto& layer_node_decl = node_decl->op->get_layer();
+    DCHECK(layer_node_decl->layer_size);
+
+    const mojo::Size& layer_size = *layer_node_decl->layer_size;
+    mojo::gfx::composition::BlendPtr blend = layer_node_decl->blend.Pass();
+    return new LayerNodeDef(node_id, content_transform.Pass(),
+                            content_clip.Pass(), combinator, child_node_ids,
+                            layer_size, blend.Pass());
   }
 
   err << "Unsupported node op type: node_id=" << node_id
-      << ", node_op=" << node_op_decl;
+      << ", node_op=" << node_decl->op;
   return nullptr;
 }
 
-NodeDef* SceneDef::FindNode(uint32_t node_id) {
+const NodeDef* SceneDef::FindNode(uint32_t node_id) const {
   auto it = nodes_.find(node_id);
   return it != nodes_.end() ? it->second.get() : nullptr;
 }
 
-ResourceDef* SceneDef::FindResource(uint32_t resource_id,
-                                    ResourceDef::Type resource_type) {
+const ResourceDef* SceneDef::FindResource(uint32_t resource_id) const {
   auto it = resources_.find(resource_id);
-  return it != resources_.end() && it->second->type() == resource_type
-             ? it->second.get()
-             : nullptr;
+  return it != resources_.end() ? it->second.get() : nullptr;
 }
 
-std::string SceneDef::FormattedLabel() {
-  if (formatted_label_cache_.empty()) {
-    formatted_label_cache_ =
-        label_.empty()
-            ? base::StringPrintf("<%d/%d>", scene_token_->value, version_)
-            : base::StringPrintf("<%d:%s/%d>", scene_token_->value,
-                                 label_.c_str(), version_);
-  }
-  return formatted_label_cache_;
+const SceneContent* SceneDef::FindContent(uint32_t version) const {
+  if (!content_)
+    return nullptr;
+
+  // TODO(jeffbrown): Consider briefly caching older versions to allow them
+  // to be used to provide alternate content for node combinators.
+  if (version != mojo::gfx::composition::kSceneVersionNone &&
+      version != content_->version() &&
+      content_->version() != mojo::gfx::composition::kSceneVersionNone)
+    return nullptr;
+  return content_.get();
 }
 
 SceneDef::Publication::Publication(
diff --git a/services/gfx/compositor/graph/scene_def.h b/services/gfx/compositor/graph/scene_def.h
index 973e22a..052b903 100644
--- a/services/gfx/compositor/graph/scene_def.h
+++ b/services/gfx/compositor/graph/scene_def.h
@@ -6,32 +6,38 @@
 #define SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_DEF_H_
 
 #include <iosfwd>
-#include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
 #include "services/gfx/compositor/graph/node_def.h"
+#include "services/gfx/compositor/graph/resource_def.h"
+#include "services/gfx/compositor/graph/scene_label.h"
 
 namespace compositor {
 
-class RenderLayer;
-class RenderLayerBuilder;
+class SceneContent;
 class SceneDef;
 class SnapshotBuilder;
 
 // Resolves a scene token to a scene definition.
-using SceneResolver =
-    base::Callback<SceneDef*(mojo::gfx::composition::SceneToken*)>;
+using SceneResolver = base::Callback<base::WeakPtr<SceneDef>(
+    const mojo::gfx::composition::SceneToken&)>;
 
 // Sends a scene unavailable message with the specified resource id.
 using SceneUnavailableSender = base::Callback<void(uint32_t)>;
 
 // Scene definition.
 // Contains all of the resources and nodes of a published scene.
+//
+// TODO(jeffbrown): Consider renaming to |Scene| since there is now an
+// asymmetry between the stateful nature of this class compared with
+// |NodeDef| and |ResourceDef| which are immutable but similarly named.
 class SceneDef {
  public:
   // Outcome of a call to |Present|.
@@ -41,21 +47,19 @@
     kFailed,
   };
 
-  SceneDef(mojo::gfx::composition::SceneTokenPtr scene_token,
-           const std::string& label);
+  SceneDef(const SceneLabel& label);
   ~SceneDef();
 
-  // Gets the token used to refer to this scene globally.
-  // Caller does not obtain ownership of the token.
-  mojo::gfx::composition::SceneToken* scene_token() {
-    return scene_token_.get();
+  base::WeakPtr<SceneDef> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
+
+  // Gets the scene label.
+  const SceneLabel& label() const { return label_; }
+  std::string FormattedLabel() const {
+    return label_.FormattedLabelForVersion(version_);
   }
 
   // Gets the currently published scene graph version.
-  uint32_t version() { return version_; }
-
-  // Gets the root node, or nullptr if none.
-  NodeDef* root_node() { return root_node_; }
+  uint32_t version() const { return version_; }
 
   // Enqueues a pending update event to the scene graph.
   void EnqueueUpdate(mojo::gfx::composition::SceneUpdatePtr update);
@@ -81,30 +85,17 @@
   bool UnlinkReferencedScene(SceneDef* scene,
                              const SceneUnavailableSender& unavailable_sender);
 
-  // Generates a snapshot of the scene.
-  // Returns true if successful, false if the scene is blocked from rendering.
-  bool Snapshot(SnapshotBuilder* snapshot_builder,
-                RenderLayerBuilder* layer_builder);
-
-  // Finds the resource with the specified id.
-  // Returns nullptr if not found.
-  ResourceDef* FindResource(uint32_t resource_id,
-                            ResourceDef::Type resource_type);
-  SceneResourceDef* FindSceneResource(uint32_t scene_resource_id) {
-    return static_cast<SceneResourceDef*>(
-        FindResource(scene_resource_id, ResourceDef::Type::kScene));
-  }
-  ImageResourceDef* FindImageResource(uint32_t image_resource_id) {
-    return static_cast<ImageResourceDef*>(
-        FindResource(image_resource_id, ResourceDef::Type::kImage));
+  // Finds resources or nodes in the current version, returns nullptr if absent.
+  const ResourceDef* FindResource(uint32_t resource_id) const;
+  const NodeDef* FindNode(uint32_t node_id) const;
+  const NodeDef* FindRootNode() const {
+    return FindNode(mojo::gfx::composition::kSceneRootNodeId);
   }
 
-  // Finds the node with the specified id.
-  // Returns nullptr if not found.
-  NodeDef* FindNode(uint32_t node_id);
-
-  const std::string& label() { return label_; }
-  std::string FormattedLabel();
+  // Finds the most recently presented content of the specified version,
+  // returns nullptr if absent.
+  // If the version is |kSceneVersionNone| returns the current version.
+  const SceneContent* FindContent(uint32_t version) const;
 
  private:
   struct Publication {
@@ -126,42 +117,30 @@
                    const SceneResolver& resolver,
                    const SceneUnavailableSender& unavailable_sender,
                    std::ostream& err);
-  bool Validate(std::ostream& err);
 
-  bool SnapshotInner(SnapshotBuilder* snapshot_builder,
-                     RenderLayerBuilder* layer_builder);
-  std::shared_ptr<RenderLayer> SnapshotLayer(SnapshotBuilder* snapshot_builder);
-  void Invalidate();
-  bool HasSceneResources();
+  scoped_refptr<const ResourceDef> CreateResource(
+      uint32_t resource_id,
+      mojo::gfx::composition::ResourcePtr resource_decl,
+      const SceneResolver& resolver,
+      const SceneUnavailableSender& unavailable_sender,
+      std::ostream& err);
+  scoped_refptr<const NodeDef> CreateNode(
+      uint32_t node_id,
+      mojo::gfx::composition::NodePtr node_decl,
+      std::ostream& err);
 
-  ResourceDef* CreateResource(uint32_t resource_id,
-                              mojo::gfx::composition::ResourcePtr resource_decl,
-                              const SceneResolver& resolver,
-                              const SceneUnavailableSender& unavailable_sender,
-                              std::ostream& err);
-  NodeDef* CreateNode(uint32_t node_id,
-                      mojo::gfx::composition::NodePtr node_decl,
-                      std::ostream& err);
-  NodeOp* CreateNodeOp(uint32_t node_id,
-                       mojo::gfx::composition::NodeOpPtr node_op_decl,
-                       std::ostream& err);
+  const SceneLabel label_;
 
-  mojo::gfx::composition::SceneTokenPtr scene_token_;
-  const std::string label_;
-  std::string formatted_label_cache_;
-
-  uint32_t version_ = mojo::gfx::composition::kSceneVersionNone;
   std::vector<mojo::gfx::composition::SceneUpdatePtr> pending_updates_;
   std::vector<std::unique_ptr<Publication>> pending_publications_;
-  std::unordered_map<uint32_t, std::unique_ptr<ResourceDef>> resources_;
-  std::unordered_map<uint32_t, std::unique_ptr<NodeDef>> nodes_;
-  NodeDef* root_node_ = nullptr;
 
-  std::shared_ptr<RenderLayer> cached_layer_;
+  uint32_t version_ = mojo::gfx::composition::kSceneVersionNone;
+  std::unordered_map<uint32_t, scoped_refptr<const ResourceDef>> resources_;
+  std::unordered_map<uint32_t, scoped_refptr<const NodeDef>> nodes_;
 
-  // Used to detect cycles during a snapshot operation.
-  // This is safe because the object will only be used by a single thread.
-  bool visited_ = false;
+  scoped_refptr<const SceneContent> content_;
+
+  base::WeakPtrFactory<SceneDef> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SceneDef);
 };
diff --git a/services/gfx/compositor/graph/scene_label.cc b/services/gfx/compositor/graph/scene_label.cc
new file mode 100644
index 0000000..e4dfec0
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_label.cc
@@ -0,0 +1,38 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/gfx/compositor/graph/scene_label.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace compositor {
+
+SceneLabel::SceneLabel(uint32_t token, const std::string& label)
+    : token_(token), label_(label) {}
+
+SceneLabel::SceneLabel(const SceneLabel& other)
+    : token_(other.token_), label_(other.label_) {}
+
+SceneLabel::~SceneLabel() {}
+
+std::string SceneLabel::FormattedLabel() const {
+  return label_.empty() ? base::StringPrintf("<%d>", token_)
+                        : base::StringPrintf("<%d:%s>", token_, label_.c_str());
+}
+
+std::string SceneLabel::FormattedLabelForVersion(uint32_t version) const {
+  return label_.empty() ? base::StringPrintf("<%d/%d>", token_, version)
+                        : base::StringPrintf("<%d:%s/%d>", token_,
+                                             label_.c_str(), version);
+}
+
+std::string SceneLabel::FormattedLabelForNode(uint32_t version,
+                                              uint32_t node_id) const {
+  return label_.empty()
+             ? base::StringPrintf("<%d/%d>[%d]", token_, version, node_id)
+             : base::StringPrintf("<%d:%s/%d>[%d]", token_, label_.c_str(),
+                                  version, node_id);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/scene_label.h b/services/gfx/compositor/graph/scene_label.h
new file mode 100644
index 0000000..bc474c5
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_label.h
@@ -0,0 +1,37 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_LABEL_H_
+#define SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_LABEL_H_
+
+#include <string>
+
+namespace compositor {
+
+// Convenience class for formatting descriptive labels for diagnostics.
+class SceneLabel {
+ public:
+  SceneLabel(uint32_t token, const std::string& label);
+  SceneLabel(const SceneLabel& other);
+  ~SceneLabel();
+
+  // Gets the scene token.
+  uint32_t token() const { return token_; }
+
+  // Gets the user-supplied label of the scene.
+  const std::string& label() const { return label_; }
+
+  // Gets a descriptive label including optional version and node information.
+  std::string FormattedLabel() const;
+  std::string FormattedLabelForVersion(uint32_t version) const;
+  std::string FormattedLabelForNode(uint32_t version, uint32_t node_id) const;
+
+ private:
+  uint32_t const token_;
+  std::string const label_;
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_LABEL_H_
diff --git a/services/gfx/compositor/graph/snapshot.cc b/services/gfx/compositor/graph/snapshot.cc
index 935fa20..ab4a447 100644
--- a/services/gfx/compositor/graph/snapshot.cc
+++ b/services/gfx/compositor/graph/snapshot.cc
@@ -5,9 +5,10 @@
 #include "services/gfx/compositor/graph/snapshot.h"
 
 #include "base/logging.h"
+#include "services/gfx/compositor/graph/scene_content.h"
 #include "services/gfx/compositor/graph/scene_def.h"
 #include "services/gfx/compositor/render/render_frame.h"
-#include "services/gfx/compositor/render/render_layer.h"
+#include "third_party/skia/include/core/SkPictureRecorder.h"
 #include "third_party/skia/include/core/SkRect.h"
 
 namespace compositor {
@@ -20,19 +21,42 @@
   if (valid_) {
     valid_ = false;
     dependencies_.clear();
-    frame_.reset();
+    ClearContent();
     return true;
   }
   return false;
 }
 
-bool Snapshot::InvalidateScene(SceneDef* scene_def) {
+bool Snapshot::InvalidateScene(const SceneDef* scene_def) {
   DCHECK(scene_def);
+  return valid_ &&
+         dependencies_.find(scene_def->label().token()) !=
+             dependencies_.end() &&
+         Invalidate();
+}
 
-  if (valid_ && dependencies_.find(scene_def) != dependencies_.end()) {
-    return Invalidate();
-  }
-  return false;
+void Snapshot::ClearContent() {
+  root_scene_content_ = nullptr;
+  resolved_scene_contents_.clear();
+  node_dispositions_.clear();
+  frame_.reset();
+}
+
+bool Snapshot::IsBlocked(const NodeDef* node) const {
+  DCHECK(valid_);
+  auto it = node_dispositions_.find(node);
+  DCHECK(it != node_dispositions_.end());
+  DCHECK(it->second == Disposition::kSuccess ||
+         it->second == Disposition::kBlocked);
+  return it->second == Disposition::kBlocked;
+}
+
+const SceneContent* Snapshot::GetResolvedSceneContent(
+    const SceneNodeDef* scene_node) const {
+  DCHECK(valid_);
+  auto it = resolved_scene_contents_.find(scene_node);
+  DCHECK(it != resolved_scene_contents_.end());
+  return it->second.get();
 }
 
 SnapshotBuilder::SnapshotBuilder(std::ostream* block_log)
@@ -40,26 +64,164 @@
 
 SnapshotBuilder::~SnapshotBuilder() {}
 
-void SnapshotBuilder::AddSceneDependency(SceneDef* scene) {
+Snapshot::Disposition SnapshotBuilder::SnapshotNode(
+    const NodeDef* node,
+    const SceneContent* content) {
   DCHECK(snapshot_);
-  snapshot_->dependencies_.insert(scene);
+  DCHECK(node);
+  DCHECK(content);
+  DCHECK(node != content->GetRootNodeIfExists());
+
+  auto it = snapshot_->node_dispositions_.find(node);
+  if (it != snapshot_->node_dispositions_.end())
+    return it->second;
+
+  Snapshot::Disposition disposition = node->RecordSnapshot(content, this);
+  snapshot_->node_dispositions_[node] = disposition;
+  return disposition;
+}
+
+Snapshot::Disposition SnapshotBuilder::SnapshotRootAndDetectCycles(
+    const NodeDef* node,
+    const SceneContent* content) {
+  DCHECK(snapshot_);
+  DCHECK(node);
+  DCHECK(content);
+  DCHECK(node == content->GetRootNodeIfExists());
+
+  auto storage = snapshot_->node_dispositions_.emplace(
+      node, Snapshot::Disposition::kCycle);
+  if (!storage.second) {
+    if (storage.first->second == Snapshot::Disposition::kCycle)
+      cycle_ = content;  // start unwinding, remember where to stop
+    return storage.first->second;
+  }
+
+  Snapshot::Disposition disposition = node->RecordSnapshot(content, this);
+  if (disposition == Snapshot::Disposition::kSuccess) {
+    snapshot_->node_dispositions_[node] = disposition;
+    return disposition;
+  }
+
+  // We cannot reuse the iterator in |storage.first| because it may have
+  // been invalidated by the call to |RecordSnapshot| due to rehashing so
+  // we must look up the node again just in case.
+  snapshot_->node_dispositions_[node] = Snapshot::Disposition::kBlocked;
+
+  if (disposition == Snapshot::Disposition::kCycle) {
+    DCHECK(cycle_);
+    if (block_log_) {
+      *block_log_ << "Scene blocked because it is part of a cycle: "
+                  << content->FormattedLabel() << std::endl;
+    }
+    if (cycle_ == content) {
+      cycle_ = nullptr;  // found the ouroboros tail, stop unwinding
+      disposition = Snapshot::Disposition::kBlocked;
+    }
+  }
+  return disposition;
+}
+Snapshot::Disposition SnapshotBuilder::SnapshotScene(
+    const SceneDef* scene,
+    uint32_t version,
+    const SceneNodeDef* referrer_node,
+    const SceneContent* referrer_content) {
+  DCHECK(snapshot_);
+  DCHECK(scene);
+  DCHECK(referrer_node);
+  DCHECK(referrer_content);
+
+  // This function should only ever be called once when snapshotting the
+  // referring |SceneNodeDef| at which point the result will be memoized
+  // by |SnapshotNode| as usual so reentrance should not occur.
+  DCHECK(snapshot_->resolved_scene_contents_.find(referrer_node) ==
+         snapshot_->resolved_scene_contents_.end());
+
+  snapshot_->dependencies_.insert(scene->label().token());
+
+  const SceneContent* content = scene->FindContent(version);
+  if (!content) {
+    if (block_log_) {
+      *block_log_ << "Scene node blocked because its referenced scene is not "
+                     "available with the requested version: "
+                  << referrer_node->FormattedLabel(referrer_content)
+                  << ", scene " << scene->label().FormattedLabel()
+                  << ", requested version " << version << ", current version "
+                  << scene->version() << std::endl;
+    }
+    return Snapshot::Disposition::kBlocked;
+  }
+
+  const NodeDef* root = content->GetRootNodeIfExists();
+  if (!root) {
+    if (block_log_) {
+      *block_log_ << "Scene node blocked because its referenced scene has no "
+                     "root node: "
+                  << referrer_node->FormattedLabel(referrer_content)
+                  << ", scene " << content->FormattedLabel() << std::endl;
+    }
+    return Snapshot::Disposition::kBlocked;
+  }
+
+  snapshot_->resolved_scene_contents_[referrer_node] = content;
+  return SnapshotRootAndDetectCycles(root, content);
+}
+
+Snapshot::Disposition SnapshotBuilder::SnapshotRenderer(const SceneDef* scene) {
+  DCHECK(!snapshot_->root_scene_content_);
+
+  snapshot_->dependencies_.insert(scene->label().token());
+
+  const SceneContent* content =
+      scene->FindContent(mojo::gfx::composition::kSceneVersionNone);
+  if (!content) {
+    if (block_log_) {
+      *block_log_ << "Rendering blocked because the root scene has no content: "
+                  << scene->label().FormattedLabel() << std::endl;
+    }
+    return Snapshot::Disposition::kBlocked;
+  }
+
+  const NodeDef* root = content->GetRootNodeIfExists();
+  if (!root) {
+    if (block_log_) {
+      *block_log_ << "Rendering blocked the root scene has no root node: "
+                  << content->FormattedLabel() << std::endl;
+    }
+    return Snapshot::Disposition::kBlocked;
+  }
+
+  snapshot_->root_scene_content_ = content;
+  return SnapshotRootAndDetectCycles(root, content);
 }
 
 std::unique_ptr<Snapshot> SnapshotBuilder::Build(
-    SceneDef* root_scene,
+    const SceneDef* root_scene,
     const mojo::Rect& viewport,
     const mojo::gfx::composition::FrameInfo& frame_info) {
   DCHECK(snapshot_);
   DCHECK(root_scene);
 
-  SkRect sk_viewport =
-      SkRect::MakeXYWH(viewport.x, viewport.y, viewport.width, viewport.height);
-  RenderLayerBuilder layer_builder(&sk_viewport);
-  if (root_scene->Snapshot(this, &layer_builder)) {
+  Snapshot::Disposition disposition = SnapshotRenderer(root_scene);
+  DCHECK(!cycle_);  // must have properly unwound any cycles by now
+
+  if (disposition == Snapshot::Disposition::kSuccess) {
+    SkRect sk_viewport = SkRect::MakeXYWH(viewport.x, viewport.y,
+                                          viewport.width, viewport.height);
+    SkPictureRecorder recorder;
+    recorder.beginRecording(sk_viewport);
+
+    const NodeDef* root_node =
+        snapshot_->root_scene_content_->GetRootNodeIfExists();
+    DCHECK(root_node);  // otherwise would have failed to snapshot
+    root_node->RecordPicture(snapshot_->root_scene_content_.get(),
+                             snapshot_.get(), recorder.getRecordingCanvas());
+
     snapshot_->frame_ =
-        RenderFrame::Create(layer_builder.Build(), sk_viewport, frame_info);
+        RenderFrame::Create(skia::AdoptRef(recorder.endRecordingAsPicture()),
+                            sk_viewport, frame_info);
   } else {
-    snapshot_->valid_ = false;
+    snapshot_->ClearContent();
   }
   return std::move(snapshot_);
 }
diff --git a/services/gfx/compositor/graph/snapshot.h b/services/gfx/compositor/graph/snapshot.h
index 21c17b7..d2d448a 100644
--- a/services/gfx/compositor/graph/snapshot.h
+++ b/services/gfx/compositor/graph/snapshot.h
@@ -7,15 +7,20 @@
 
 #include <iosfwd>
 #include <memory>
+#include <unordered_map>
 #include <unordered_set>
 
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
 #include "mojo/services/geometry/interfaces/geometry.mojom.h"
 #include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
 
 namespace compositor {
 
+class NodeDef;
 class SceneDef;
+class SceneContent;
+class SceneNodeDef;
 class RenderFrame;
 
 // Describes a single frame snapshot of the scene graph, sufficient for
@@ -33,9 +38,16 @@
 //
 // Snapshot objects are not thread-safe since they have direct references to
 // the scene graph definition.  However, the snapshot's frame is thread-safe
-// and is intended to be shared by other components.
+// and is intended to be sent to the backend rasterizer.
 class Snapshot {
  public:
+  // Describes the result of a snapshot operation.
+  enum class Disposition {
+    kSuccess,  // The snapshot was successful.
+    kBlocked,  // The node was blocked from rendering.
+    kCycle,    // The node was blocked due to a cycle, must unwind fully.
+  };
+
   ~Snapshot();
 
   // Returns true if the snapshot is valid.
@@ -61,28 +73,53 @@
   //
   // Returns true if the snapshot became invalid as a result of this operation,
   // or false if it was already invalid.
-  bool InvalidateScene(SceneDef* scene_def);
+  bool InvalidateScene(const SceneDef* scene_def);
+
+  // Returns true if the specified node was blocked from rendering.
+  // Only meaningful while the snapshot is valid.
+  bool IsBlocked(const NodeDef* node) const;
+
+  // Gets the scene content which was resolved by following a scene node link.
+  // Only meaningful while the snapshot is valid.
+  const SceneContent* GetResolvedSceneContent(
+      const SceneNodeDef* scene_node) const;
 
  private:
   friend class SnapshotBuilder;
 
   Snapshot();
 
-  std::unordered_set<SceneDef*> dependencies_;
+  void ClearContent();
+
+  // Just the set of dependent scene tokens.  Used for invalidation.
+  std::unordered_set<uint32_t> dependencies_;
+
+  // The root scene in the graph.
+  // This reference together with |resolved_scenes| retains all of the
+  // nodes used by the snapshot so that we can use bare pointers for nodes
+  // and avoid excess reference counting overhead in other data structures.
+  scoped_refptr<const SceneContent> root_scene_content_;
+
+  // Map of scenes which were resolved from scene nodes.
+  std::unordered_map<const SceneNodeDef*, scoped_refptr<const SceneContent>>
+      resolved_scene_contents_;
+
+  // Node states, true if snapshotted successfully, false if blocked.
+  std::unordered_map<const NodeDef*, Disposition> node_dispositions_;
+
+  // A frame ready to be rendered.
   std::shared_ptr<RenderFrame> frame_;
+
+  // True if the snapshot is still valid.
   bool valid_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(Snapshot);
 };
 
-// Builder for snapshots.
+// Builds a table of all of the state which will be required for rendering
+// a scene graph.
 class SnapshotBuilder {
  public:
-  // Creates a snapshot builder.
-  //
-  // |block_log|, if not null, the snapshotter will append information to
-  // this stream describing the parts of the scene graph for which
-  // composition was blocked.
   explicit SnapshotBuilder(std::ostream* block_log);
   ~SnapshotBuilder();
 
@@ -90,18 +127,38 @@
   // describing the parts of the scene graph for which composition was blocked.
   std::ostream* block_log() { return block_log_; }
 
-  // Adds a scene dependency to the snapshot.
-  void AddSceneDependency(SceneDef* scene);
+  // Snapshots the requested node.
+  Snapshot::Disposition SnapshotNode(const NodeDef* node,
+                                     const SceneContent* content);
+
+  // Snapshots the requested scene.
+  Snapshot::Disposition SnapshotScene(const SceneDef* scene,
+                                      uint32_t version,
+                                      const SceneNodeDef* referrer_node,
+                                      const SceneContent* referrer_content);
 
   // Builds a snapshot rooted at the specified scene.
   std::unique_ptr<Snapshot> Build(
-      SceneDef* root_scene,
+      const SceneDef* root_scene,
       const mojo::Rect& viewport,
       const mojo::gfx::composition::FrameInfo& frame_info);
 
  private:
+  // Snapshots the root scene of a renderer.
+  // This is just like |SnapshotScene| but the errors are reported a little
+  // differently since there is no referrer node.
+  Snapshot::Disposition SnapshotRenderer(const SceneDef* scene);
+
+  // Snapshots the root node of a scene and detects cycles.
+  // This is just like |SnapshotNode| but performs cycle detection which
+  // isn't otherwise needed.
+  Snapshot::Disposition SnapshotRootAndDetectCycles(
+      const NodeDef* node,
+      const SceneContent* content);
+
   std::ostream* const block_log_;
   std::unique_ptr<Snapshot> snapshot_;
+  const SceneContent* cycle_ = nullptr;  // point where a cycle was detected
 
   DISALLOW_COPY_AND_ASSIGN(SnapshotBuilder);
 };
diff --git a/services/gfx/compositor/render/render_frame.cc b/services/gfx/compositor/render/render_frame.cc
index 39bae02..c026c56 100644
--- a/services/gfx/compositor/render/render_frame.cc
+++ b/services/gfx/compositor/render/render_frame.cc
@@ -5,36 +5,27 @@
 #include "services/gfx/compositor/render/render_frame.h"
 
 #include "base/logging.h"
-#include "services/gfx/compositor/render/render_layer.h"
 #include "skia/ext/refptr.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkPicture.h"
-#include "third_party/skia/include/core/SkPoint.h"
 
 namespace compositor {
 
-RenderFrame::RenderFrame(const std::shared_ptr<RenderLayer>& root_layer,
+RenderFrame::RenderFrame(const skia::RefPtr<SkPicture>& picture,
                          const SkRect& viewport,
                          const mojo::gfx::composition::FrameInfo& frame_info)
-    : root_layer_(root_layer), viewport_(viewport), frame_info_(frame_info) {
-  DCHECK(root_layer_);
+    : picture_(picture), viewport_(viewport), frame_info_(frame_info) {
+  DCHECK(picture_);
 }
 
 RenderFrame::~RenderFrame() {}
 
 void RenderFrame::Paint(SkCanvas* canvas) const {
+  DCHECK(canvas);
+
   // TODO: Consider using GrDrawContext instead of SkCanvas.
   canvas->clear(SK_ColorBLACK);
-  canvas->drawPicture(root_layer_->picture().get());
-  canvas->flush();
-}
-
-mojo::gfx::composition::HitTestResultPtr RenderFrame::HitTest(
-    const SkPoint& point) const {
-  // TODO: implement me
-  auto result = mojo::gfx::composition::HitTestResult::New();
-  result->hits.resize(0u);
-  return result.Pass();
+  canvas->drawPicture(picture_.get());
 }
 
 }  // namespace compositor
diff --git a/services/gfx/compositor/render/render_frame.h b/services/gfx/compositor/render/render_frame.h
index 21cdb77..f9cf838 100644
--- a/services/gfx/compositor/render/render_frame.h
+++ b/services/gfx/compositor/render/render_frame.h
@@ -8,40 +8,35 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "mojo/services/gfx/composition/interfaces/hit_tests.mojom.h"
 #include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+#include "skia/ext/refptr.h"
 #include "third_party/skia/include/core/SkRect.h"
 
 class SkCanvas;
-struct SkPoint;
+class SkPicture;
 
 namespace compositor {
 
-class RenderLayer;
-
-// Describes a sequence of drawing commands to be evaluated as a single pass.
-// Frames are introduced into the render frame at points where blending must
-// occur or where portions of a scene graph may be drawn once into a temporary
-// buffer and used many times.
+// Describes a frame to be rendered.
 //
 // Render objects are thread-safe, immutable, and reference counted via
 // std::shared_ptr.  They have no direct references to the scene graph.
 class RenderFrame {
  public:
-  RenderFrame(const std::shared_ptr<RenderLayer>& root_layer,
+  RenderFrame(const skia::RefPtr<SkPicture>& picture,
               const SkRect& viewport,
               const mojo::gfx::composition::FrameInfo& frame_info);
   ~RenderFrame();
 
   static std::shared_ptr<RenderFrame> Create(
-      const std::shared_ptr<RenderLayer>& root_layer,
+      const skia::RefPtr<SkPicture>& picture,
       const SkRect& viewport,
       const mojo::gfx::composition::FrameInfo& frame_info) {
-    return std::make_shared<RenderFrame>(root_layer, viewport, frame_info);
+    return std::make_shared<RenderFrame>(picture, viewport, frame_info);
   }
 
-  // Gets the root layer of the frame.
-  const std::shared_ptr<RenderLayer>& root_layer() const { return root_layer_; }
+  // Gets the underlying picture to rasterize.
+  const skia::RefPtr<SkPicture>& picture() const { return picture_; }
 
   // Gets the frame's viewport.
   const SkRect& viewport() const { return viewport_; }
@@ -54,13 +49,10 @@
   // Paints the frame to a canvas.
   void Paint(SkCanvas* canvas) const;
 
-  // Performs a hit test on the content of the frame.
-  mojo::gfx::composition::HitTestResultPtr HitTest(const SkPoint& point) const;
-
  private:
   friend class RenderFrameBuilder;
 
-  std::shared_ptr<RenderLayer> root_layer_;
+  skia::RefPtr<SkPicture> picture_;
   SkRect viewport_;
   mojo::gfx::composition::FrameInfo frame_info_;
 
diff --git a/services/gfx/compositor/render/render_image.h b/services/gfx/compositor/render/render_image.h
index 7cdbf16..f90e579 100644
--- a/services/gfx/compositor/render/render_image.h
+++ b/services/gfx/compositor/render/render_image.h
@@ -51,7 +51,7 @@
   uint32_t width() const { return image_->width(); }
   uint32_t height() const { return image_->height(); }
 
-  // Gets the underlying image to rasterize.
+  // Gets the underlying image to rasterize, never null.
   const skia::RefPtr<SkImage>& image() const { return image_; }
 
  private:
diff --git a/services/gfx/compositor/render/render_layer.cc b/services/gfx/compositor/render/render_layer.cc
deleted file mode 100644
index d652d7b..0000000
--- a/services/gfx/compositor/render/render_layer.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/gfx/compositor/render/render_layer.h"
-
-#include "base/logging.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkPicture.h"
-#include "third_party/skia/include/core/SkPictureRecorder.h"
-
-namespace compositor {
-
-RenderLayer::RenderLayer(const skia::RefPtr<SkPicture>& picture)
-    : picture_(picture) {}
-
-RenderLayer::~RenderLayer() {}
-
-RenderLayerBuilder::RenderLayerBuilder() : RenderLayerBuilder(nullptr) {}
-
-RenderLayerBuilder::RenderLayerBuilder(const SkRect* cull_rect) {
-  recorder_.beginRecording(cull_rect ? *cull_rect : SkRect::MakeLargest());
-  canvas_ = recorder_.getRecordingCanvas();
-}
-
-RenderLayerBuilder::~RenderLayerBuilder() {}
-
-void RenderLayerBuilder::PushScene(uint32_t scene_token, uint32_t version) {
-  DCHECK(canvas_);
-  // TODO: remember scene information for hit testing
-}
-
-void RenderLayerBuilder::PopScene() {
-  DCHECK(canvas_);
-}
-
-void RenderLayerBuilder::PushNode(uint32_t node_id, uint32_t hit_id) {
-  DCHECK(canvas_);
-  // TODO: remember scene information for hit testing
-  // TODO: optimize save() / restore() commands
-  canvas_->save();
-}
-
-void RenderLayerBuilder::PopNode() {
-  DCHECK(canvas_);
-  // TODO: optimize save() / restore() commands
-  canvas_->restore();
-}
-
-void RenderLayerBuilder::ApplyTransform(const SkMatrix& content_transform) {
-  DCHECK(canvas_);
-  canvas_->concat(content_transform);
-}
-
-void RenderLayerBuilder::ApplyClip(const SkRect& content_clip) {
-  DCHECK(canvas_);
-  canvas_->clipRect(content_clip);
-}
-
-void RenderLayerBuilder::DrawRect(const SkRect& content_rect,
-                                  const SkPaint& paint) {
-  DCHECK(canvas_);
-  canvas_->drawRect(content_rect, paint);
-}
-
-void RenderLayerBuilder::DrawImage(const std::shared_ptr<RenderImage>& image,
-                                   const SkRect& content_rect,
-                                   const SkRect& image_rect,
-                                   const SkPaint& paint) {
-  DCHECK(image);
-  DCHECK(canvas_);
-  canvas_->drawImageRect(image->image().get(), image_rect, content_rect,
-                         &paint);
-}
-
-void RenderLayerBuilder::DrawLayer(const std::shared_ptr<RenderLayer>& layer) {
-  DCHECK(canvas_);
-  canvas_->drawPicture(layer->picture().get());
-}
-
-void RenderLayerBuilder::DrawSavedLayer(
-    const std::shared_ptr<RenderLayer>& layer,
-    const SkRect& content_rect,
-    const SkPaint& paint) {
-  DCHECK(layer);
-  DCHECK(canvas_);
-  canvas_->saveLayer(content_rect, &paint);
-  canvas_->drawPicture(layer->picture().get());
-  canvas_->restore();
-}
-
-std::shared_ptr<RenderLayer> RenderLayerBuilder::Build() {
-  DCHECK(canvas_);
-  canvas_ = nullptr;
-  return std::make_shared<RenderLayer>(
-      skia::AdoptRef(recorder_.endRecordingAsPicture()));
-}
-
-}  // namespace compositor
diff --git a/services/gfx/compositor/render/render_layer.h b/services/gfx/compositor/render/render_layer.h
deleted file mode 100644
index 34e614c..0000000
--- a/services/gfx/compositor/render/render_layer.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_GFX_COMPOSITOR_RENDER_RENDER_LAYER_H_
-#define SERVICES_GFX_COMPOSITOR_RENDER_RENDER_LAYER_H_
-
-#include <memory>
-#include <vector>
-
-#include "base/macros.h"
-#include "services/gfx/compositor/render/render_image.h"
-#include "skia/ext/refptr.h"
-#include "third_party/skia/include/core/SkMatrix.h"
-#include "third_party/skia/include/core/SkPaint.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "third_party/skia/include/core/SkPictureRecorder.h"
-#include "third_party/skia/include/core/SkRRect.h"
-#include "third_party/skia/include/core/SkRect.h"
-
-class SkCanvas;
-class SkPicture;
-
-namespace compositor {
-
-class RenderCommand;
-
-// Describes a sequence of drawing commands to be evaluated as a single pass.
-// Layers are introduced into the render frame at points where blending must
-// occur or where portions of a scene graph may be drawn once into a temporary
-// buffer and used many times.
-//
-// Render objects are thread-safe, immutable, and reference counted via
-// std::shared_ptr.  They have no direct references to the scene graph.
-class RenderLayer {
- public:
-  ~RenderLayer();
-  explicit RenderLayer(const skia::RefPtr<SkPicture>& picture);
-
-  // Gets the underlying picture to rasterize.
-  const skia::RefPtr<SkPicture>& picture() const { return picture_; }
-
- private:
-  friend class RenderLayerBuilder;
-
-  // TODO: store auxiliary information required for hit testing
-  skia::RefPtr<SkPicture> picture_;
-
-  DISALLOW_COPY_AND_ASSIGN(RenderLayer);
-};
-
-// Builder for render layers.
-class RenderLayerBuilder {
- public:
-  // Creates a layer builder with an optional |cull_rect| to cull recorded
-  // geometry information.
-  RenderLayerBuilder();
-  explicit RenderLayerBuilder(const SkRect* cull_rect);
-  ~RenderLayerBuilder();
-
-  // Pushes information about a scene onto the stack.
-  void PushScene(uint32_t scene_token, uint32_t version);
-
-  // Pops a scene off the stack.
-  void PopScene();
-
-  // Pushes information about a node onto the stack.
-  void PushNode(uint32_t node_id, uint32_t hit_id);
-
-  // Pops a node off the stack.
-  void PopNode();
-
-  // Applies a transformation matrix to the node.
-  void ApplyTransform(const SkMatrix& content_transform);
-
-  // Applies a clip to the node.
-  void ApplyClip(const SkRect& content_clip);
-
-  // Inserts a command to draw a rectangle.
-  void DrawRect(const SkRect& content_rect, const SkPaint& paint);
-
-  // Inserts a command to draw an image.
-  void DrawImage(const std::shared_ptr<RenderImage>& image,
-                 const SkRect& content_rect,
-                 const SkRect& image_rect,
-                 const SkPaint& paint);
-
-  // Inserts a command to draw an in-place layer.
-  void DrawLayer(const std::shared_ptr<RenderLayer>& layer);
-
-  // Inserts a command to draw a saved layer.
-  void DrawSavedLayer(const std::shared_ptr<RenderLayer>& layer,
-                      const SkRect& content_rect,
-                      const SkPaint& paint);
-
-  // Returns the layer which was built.
-  std::shared_ptr<RenderLayer> Build();
-
- private:
-  SkPictureRecorder recorder_;
-  SkCanvas* canvas_;
-
-  DISALLOW_COPY_AND_ASSIGN(RenderLayerBuilder);
-};
-
-}  // namespace compositor
-
-#endif  // SERVICES_GFX_COMPOSITOR_RENDER_RENDER_LAYER_H_
diff --git a/services/gfx/compositor/scene_state.cc b/services/gfx/compositor/scene_state.cc
index c3b4575..319d4ed 100644
--- a/services/gfx/compositor/scene_state.cc
+++ b/services/gfx/compositor/scene_state.cc
@@ -8,7 +8,9 @@
 
 SceneState::SceneState(mojo::gfx::composition::SceneTokenPtr scene_token,
                        const std::string& label)
-    : scene_def_(scene_token.Pass(), label), weak_factory_(this) {}
+    : scene_token_(scene_token.Pass()),
+      scene_def_(SceneLabel(scene_token_->value, label)),
+      weak_factory_(this) {}
 
 SceneState::~SceneState() {
   // The scene implementation and all of its bindings must be destroyed
@@ -32,7 +34,7 @@
 std::ostream& operator<<(std::ostream& os, SceneState* scene_state) {
   if (!scene_state)
     return os << "null";
-  return os << scene_state->FormattedLabel();
+  return os << scene_state->scene_def()->FormattedLabel();
 }
 
 }  // namespace compositor
diff --git a/services/gfx/compositor/scene_state.h b/services/gfx/compositor/scene_state.h
index 3b6743f..47526e3 100644
--- a/services/gfx/compositor/scene_state.h
+++ b/services/gfx/compositor/scene_state.h
@@ -34,8 +34,8 @@
 
   // Gets the token used to refer to this scene globally.
   // Caller does not obtain ownership of the token.
-  mojo::gfx::composition::SceneToken* scene_token() {
-    return scene_def_.scene_token();
+  const mojo::gfx::composition::SceneToken* scene_token() {
+    return scene_token_.get();
   }
 
   // Gets or sets the scene listener interface.
@@ -58,10 +58,8 @@
   void DispatchSceneFrameCallbacks(
       const mojo::gfx::composition::FrameInfo& frame_info);
 
-  const std::string& label() { return scene_def_.label(); }
-  std::string FormattedLabel() { return scene_def_.FormattedLabel(); }
-
  private:
+  mojo::gfx::composition::SceneTokenPtr scene_token_;
   std::unique_ptr<mojo::gfx::composition::Scene> scene_impl_;
   mojo::gfx::composition::SceneListenerPtr scene_listener_;
   std::vector<SceneFrameCallback> pending_frame_callbacks_;