Initial checkin of the new Mozart compositor.

The Mozart compositor is designed to support feed forward rendering of
scene graphs using scene version codes and node combinators to
determine synchronization behaviors.

A unique characteristic of Mozart is its ability to allow clients to
specify how to resolve situations where content is missing or not ready
by supplying alternative choices for what should be rendered.  The goal
is to avoid stalls by allowing more flexibility in specifying how scene
state updates are coordinated.

The scene graph consists of nodes of various types which represent
primitive operations such as filling a rectangle, drawing an image,
or embedding another scene.

The children of each node are composed according to the node's combinator
rule.  The default MERGE combinator rule simply draws all children
sequentially and refuses to draw anying if any of the children are blocked.
Other rules, such as FALLBACK, allow for alternative choices to be
made by the compositor at snapshot time based on what is available.

In the simplest cases, a client might provide substitute content to be
used in case the primary content is not yet ready.  In more elaborate
cases, a client might describe how an older version of the primary content
can be adapted to fit current demands, perhaps scaling or cropping it
as required.

The compositor itself is designed to be relatively unopinionated about
the contents that it is compositing.  Applications publish their scenes
and Mozart composites them, that's it.  High-level functionality such
as maintaining view embedding relationships or input dispatch are
exclusively handled by other components.

Mozart also offers:

- A relatively easy to use client interface.
- Vsync based scheduling.
- Hit testing (only partially implemented in this patch).
- Support for clients which use separate threads for event processing
  and rendering.
- Multi-display support (only partially implemented in this patch).

The rasterizer is currently implemented using Skia and Ganesh and runs
on a separate thread from the main engine.

This initial patch covers most of the essentials but there's plenty
more testing, optimization, and elaboration to do later.

This patch is followed by several others which serve to update the
view manager to use the new compositor, add various helpers,
update examples, and so on.

BUG=
R=abarth@google.com

Review URL: https://codereview.chromium.org/1552963002 .
diff --git a/mojo/dart/packages/mojo_services/BUILD.gn b/mojo/dart/packages/mojo_services/BUILD.gn
index 35caff7..cf558aa 100644
--- a/mojo/dart/packages/mojo_services/BUILD.gn
+++ b/mojo/dart/packages/mojo_services/BUILD.gn
@@ -36,6 +36,14 @@
   "lib/mojo/files/types.mojom.dart",
   "lib/mojo/geocoder.mojom.dart",
   "lib/mojo/geometry.mojom.dart",
+  "lib/mojo/gfx/composition/compositor.mojom.dart",
+  "lib/mojo/gfx/composition/hit_tests.mojom.dart",
+  "lib/mojo/gfx/composition/nodes.mojom.dart",
+  "lib/mojo/gfx/composition/renderers.mojom.dart",
+  "lib/mojo/gfx/composition/resources.mojom.dart",
+  "lib/mojo/gfx/composition/scenes.mojom.dart",
+  "lib/mojo/gfx/composition/scene_token.mojom.dart",
+  "lib/mojo/gfx/composition/scheduling.mojom.dart",
   "lib/mojo/gpu_capabilities.mojom.dart",
   "lib/mojo/gpu.mojom.dart",
   "lib/mojo/host_resolver.mojom.dart",
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
index cc4fbc7..cdf5a0e 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/geometry.mojom.dart
@@ -9,6 +9,8 @@
 import 'package:mojo/bindings.dart' as bindings;
 import 'package:mojo/core.dart' as core;
 
+
+
 class Point extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -48,13 +50,15 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.x = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.y = decoder0.decodeInt32(12);
     }
     return result;
@@ -62,18 +66,16 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeInt32(x, 8);
-
+    
     encoder0.encodeInt32(y, 12);
   }
 
   String toString() {
     return "Point("
-        "x: $x"
-        ", "
-        "y: $y"
-        ")";
+           "x: $x" ", "
+           "y: $y" ")";
   }
 
   Map toJson() {
@@ -84,6 +86,7 @@
   }
 }
 
+
 class PointF extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -123,13 +126,15 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.x = decoder0.decodeFloat(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.y = decoder0.decodeFloat(12);
     }
     return result;
@@ -137,18 +142,16 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeFloat(x, 8);
-
+    
     encoder0.encodeFloat(y, 12);
   }
 
   String toString() {
     return "PointF("
-        "x: $x"
-        ", "
-        "y: $y"
-        ")";
+           "x: $x" ", "
+           "y: $y" ")";
   }
 
   Map toJson() {
@@ -159,6 +162,7 @@
   }
 }
 
+
 class Size extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -198,13 +202,15 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.width = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.height = decoder0.decodeInt32(12);
     }
     return result;
@@ -212,18 +218,16 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeInt32(width, 8);
-
+    
     encoder0.encodeInt32(height, 12);
   }
 
   String toString() {
     return "Size("
-        "width: $width"
-        ", "
-        "height: $height"
-        ")";
+           "width: $width" ", "
+           "height: $height" ")";
   }
 
   Map toJson() {
@@ -234,6 +238,7 @@
   }
 }
 
+
 class Rect extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(24, 0)
@@ -275,19 +280,23 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.x = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.y = decoder0.decodeInt32(12);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.width = decoder0.decodeInt32(16);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.height = decoder0.decodeInt32(20);
     }
     return result;
@@ -295,26 +304,22 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeInt32(x, 8);
-
+    
     encoder0.encodeInt32(y, 12);
-
+    
     encoder0.encodeInt32(width, 16);
-
+    
     encoder0.encodeInt32(height, 20);
   }
 
   String toString() {
     return "Rect("
-        "x: $x"
-        ", "
-        "y: $y"
-        ", "
-        "width: $width"
-        ", "
-        "height: $height"
-        ")";
+           "x: $x" ", "
+           "y: $y" ", "
+           "width: $width" ", "
+           "height: $height" ")";
   }
 
   Map toJson() {
@@ -327,6 +332,7 @@
   }
 }
 
+
 class RectF extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(24, 0)
@@ -368,19 +374,23 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.x = decoder0.decodeFloat(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.y = decoder0.decodeFloat(12);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.width = decoder0.decodeFloat(16);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.height = decoder0.decodeFloat(20);
     }
     return result;
@@ -388,26 +398,22 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeFloat(x, 8);
-
+    
     encoder0.encodeFloat(y, 12);
-
+    
     encoder0.encodeFloat(width, 16);
-
+    
     encoder0.encodeFloat(height, 20);
   }
 
   String toString() {
     return "RectF("
-        "x: $x"
-        ", "
-        "y: $y"
-        ", "
-        "width: $width"
-        ", "
-        "height: $height"
-        ")";
+           "x: $x" ", "
+           "y: $y" ", "
+           "width: $width" ", "
+           "height: $height" ")";
   }
 
   Map toJson() {
@@ -420,6 +426,7 @@
   }
 }
 
+
 class RRect extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(56, 0)
@@ -469,43 +476,55 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
+      
       result.x = decoder0.decodeInt32(8);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.y = decoder0.decodeInt32(12);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.width = decoder0.decodeInt32(16);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.height = decoder0.decodeInt32(20);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.topLeftRadiusX = decoder0.decodeInt32(24);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.topLeftRadiusY = decoder0.decodeInt32(28);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.topRightRadiusX = decoder0.decodeInt32(32);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.topRightRadiusY = decoder0.decodeInt32(36);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.bottomLeftRadiusX = decoder0.decodeInt32(40);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.bottomLeftRadiusY = decoder0.decodeInt32(44);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.bottomRightRadiusX = decoder0.decodeInt32(48);
     }
     if (mainDataHeader.version >= 0) {
+      
       result.bottomRightRadiusY = decoder0.decodeInt32(52);
     }
     return result;
@@ -513,58 +532,46 @@
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeInt32(x, 8);
-
+    
     encoder0.encodeInt32(y, 12);
-
+    
     encoder0.encodeInt32(width, 16);
-
+    
     encoder0.encodeInt32(height, 20);
-
+    
     encoder0.encodeInt32(topLeftRadiusX, 24);
-
+    
     encoder0.encodeInt32(topLeftRadiusY, 28);
-
+    
     encoder0.encodeInt32(topRightRadiusX, 32);
-
+    
     encoder0.encodeInt32(topRightRadiusY, 36);
-
+    
     encoder0.encodeInt32(bottomLeftRadiusX, 40);
-
+    
     encoder0.encodeInt32(bottomLeftRadiusY, 44);
-
+    
     encoder0.encodeInt32(bottomRightRadiusX, 48);
-
+    
     encoder0.encodeInt32(bottomRightRadiusY, 52);
   }
 
   String toString() {
     return "RRect("
-        "x: $x"
-        ", "
-        "y: $y"
-        ", "
-        "width: $width"
-        ", "
-        "height: $height"
-        ", "
-        "topLeftRadiusX: $topLeftRadiusX"
-        ", "
-        "topLeftRadiusY: $topLeftRadiusY"
-        ", "
-        "topRightRadiusX: $topRightRadiusX"
-        ", "
-        "topRightRadiusY: $topRightRadiusY"
-        ", "
-        "bottomLeftRadiusX: $bottomLeftRadiusX"
-        ", "
-        "bottomLeftRadiusY: $bottomLeftRadiusY"
-        ", "
-        "bottomRightRadiusX: $bottomRightRadiusX"
-        ", "
-        "bottomRightRadiusY: $bottomRightRadiusY"
-        ")";
+           "x: $x" ", "
+           "y: $y" ", "
+           "width: $width" ", "
+           "height: $height" ", "
+           "topLeftRadiusX: $topLeftRadiusX" ", "
+           "topLeftRadiusY: $topLeftRadiusY" ", "
+           "topRightRadiusX: $topRightRadiusX" ", "
+           "topRightRadiusY: $topRightRadiusY" ", "
+           "bottomLeftRadiusX: $bottomLeftRadiusX" ", "
+           "bottomLeftRadiusY: $bottomLeftRadiusY" ", "
+           "bottomRightRadiusX: $bottomRightRadiusX" ", "
+           "bottomRightRadiusY: $bottomRightRadiusY" ")";
   }
 
   Map toJson() {
@@ -585,6 +592,7 @@
   }
 }
 
+
 class Transform extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -623,26 +631,25 @@
       }
     } else if (mainDataHeader.size < kVersions.last.size) {
       throw new bindings.MojoCodecError(
-          'Message newer than the last known version cannot be shorter than '
-          'required by the last known version.');
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
     }
     if (mainDataHeader.version >= 0) {
-      result.matrix =
-          decoder0.decodeFloatArray(8, bindings.kNothingNullable, 16);
+      
+      result.matrix = decoder0.decodeFloatArray(8, bindings.kNothingNullable, 16);
     }
     return result;
   }
 
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-
+    
     encoder0.encodeFloatArray(matrix, 8, bindings.kNothingNullable, 16);
   }
 
   String toString() {
     return "Transform("
-        "matrix: $matrix"
-        ")";
+           "matrix: $matrix" ")";
   }
 
   Map toJson() {
@@ -651,3 +658,5 @@
     return map;
   }
 }
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/compositor.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/compositor.mojom.dart
new file mode 100644
index 0000000..832d45d
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/compositor.mojom.dart
@@ -0,0 +1,472 @@
+// Copyright 2014 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.
+
+library compositor_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
+import 'package:mojo_services/mojo/context_provider.mojom.dart' as context_provider_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scenes.mojom.dart' as scenes_mojom;
+import 'package:mojo_services/mojo/gfx/composition/renderers.mojom.dart' as renderers_mojom;
+const int kLabelMaxLength = 32;
+
+
+
+class _CompositorCreateSceneParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(24, 0)
+  ];
+  Object scene = null;
+  String label = null;
+
+  _CompositorCreateSceneParams() : super(kVersions.last.size);
+
+  static _CompositorCreateSceneParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _CompositorCreateSceneParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _CompositorCreateSceneParams result = new _CompositorCreateSceneParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.scene = decoder0.decodeInterfaceRequest(8, false, scenes_mojom.SceneStub.newFromEndpoint);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.label = decoder0.decodeString(16, true);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInterfaceRequest(scene, 8, false);
+    
+    encoder0.encodeString(label, 16, true);
+  }
+
+  String toString() {
+    return "_CompositorCreateSceneParams("
+           "scene: $scene" ", "
+           "label: $label" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class CompositorCreateSceneResponseParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  scene_token_mojom.SceneToken sceneToken = null;
+
+  CompositorCreateSceneResponseParams() : super(kVersions.last.size);
+
+  static CompositorCreateSceneResponseParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static CompositorCreateSceneResponseParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    CompositorCreateSceneResponseParams result = new CompositorCreateSceneResponseParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.sceneToken = scene_token_mojom.SceneToken.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(sceneToken, 8, false);
+  }
+
+  String toString() {
+    return "CompositorCreateSceneResponseParams("
+           "sceneToken: $sceneToken" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["sceneToken"] = sceneToken;
+    return map;
+  }
+}
+
+
+class _CompositorCreateRendererParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(32, 0)
+  ];
+  Object contextProvider = null;
+  Object renderer = null;
+  String label = null;
+
+  _CompositorCreateRendererParams() : super(kVersions.last.size);
+
+  static _CompositorCreateRendererParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _CompositorCreateRendererParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _CompositorCreateRendererParams result = new _CompositorCreateRendererParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.contextProvider = decoder0.decodeServiceInterface(8, false, context_provider_mojom.ContextProviderProxy.newFromEndpoint);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.renderer = decoder0.decodeInterfaceRequest(16, false, renderers_mojom.RendererStub.newFromEndpoint);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.label = decoder0.decodeString(24, true);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInterface(contextProvider, 8, false);
+    
+    encoder0.encodeInterfaceRequest(renderer, 16, false);
+    
+    encoder0.encodeString(label, 24, true);
+  }
+
+  String toString() {
+    return "_CompositorCreateRendererParams("
+           "contextProvider: $contextProvider" ", "
+           "renderer: $renderer" ", "
+           "label: $label" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+const int _Compositor_createSceneName = 0;
+const int _Compositor_createRendererName = 1;
+
+abstract class Compositor {
+  static const String serviceName = "mojo::gfx::composition::Compositor";
+  dynamic createScene(Object scene,String label,[Function responseFactory = null]);
+  void createRenderer(Object contextProvider, Object renderer, String label);
+}
+
+
+class _CompositorProxyImpl extends bindings.Proxy {
+  _CompositorProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _CompositorProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _CompositorProxyImpl.unbound() : super.unbound();
+
+  static _CompositorProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _CompositorProxyImpl"));
+    return new _CompositorProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      case _Compositor_createSceneName:
+        var r = CompositorCreateSceneResponseParams.deserialize(
+            message.payload);
+        if (!message.header.hasRequestId) {
+          proxyError("Expected a message with a valid request Id.");
+          return;
+        }
+        Completer c = completerMap[message.header.requestId];
+        if (c == null) {
+          proxyError(
+              "Message had unknown request Id: ${message.header.requestId}");
+          return;
+        }
+        completerMap.remove(message.header.requestId);
+        if (c.isCompleted) {
+          proxyError("Response completer already completed");
+          return;
+        }
+        c.complete(r);
+        break;
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_CompositorProxyImpl($superString)";
+  }
+}
+
+
+class _CompositorProxyCalls implements Compositor {
+  _CompositorProxyImpl _proxyImpl;
+
+  _CompositorProxyCalls(this._proxyImpl);
+    dynamic createScene(Object scene,String label,[Function responseFactory = null]) {
+      var params = new _CompositorCreateSceneParams();
+      params.scene = scene;
+      params.label = label;
+      return _proxyImpl.sendMessageWithRequestId(
+          params,
+          _Compositor_createSceneName,
+          -1,
+          bindings.MessageHeader.kMessageExpectsResponse);
+    }
+    void createRenderer(Object contextProvider, Object renderer, String label) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _CompositorCreateRendererParams();
+      params.contextProvider = contextProvider;
+      params.renderer = renderer;
+      params.label = label;
+      _proxyImpl.sendMessage(params, _Compositor_createRendererName);
+    }
+}
+
+
+class CompositorProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  Compositor ptr;
+
+  CompositorProxy(_CompositorProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _CompositorProxyCalls(proxyImpl);
+
+  CompositorProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _CompositorProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _CompositorProxyCalls(impl);
+  }
+
+  CompositorProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _CompositorProxyImpl.fromHandle(handle) {
+    ptr = new _CompositorProxyCalls(impl);
+  }
+
+  CompositorProxy.unbound() :
+      impl = new _CompositorProxyImpl.unbound() {
+    ptr = new _CompositorProxyCalls(impl);
+  }
+
+  factory CompositorProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    CompositorProxy p = new CompositorProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static CompositorProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For CompositorProxy"));
+    return new CompositorProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => Compositor.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "CompositorProxy($impl)";
+  }
+}
+
+
+class CompositorStub extends bindings.Stub {
+  Compositor _impl = null;
+
+  CompositorStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  CompositorStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  CompositorStub.unbound() : super.unbound();
+
+  static CompositorStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For CompositorStub"));
+    return new CompositorStub.fromEndpoint(endpoint);
+  }
+
+
+  CompositorCreateSceneResponseParams _CompositorCreateSceneResponseParamsFactory(scene_token_mojom.SceneToken sceneToken) {
+    var mojo_factory_result = new CompositorCreateSceneResponseParams();
+    mojo_factory_result.sceneToken = sceneToken;
+    return mojo_factory_result;
+  }
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _Compositor_createSceneName:
+        var params = _CompositorCreateSceneParams.deserialize(
+            message.payload);
+        var response = _impl.createScene(params.scene,params.label,_CompositorCreateSceneResponseParamsFactory);
+        if (response is Future) {
+          return response.then((response) {
+            if (response != null) {
+              return buildResponseWithId(
+                  response,
+                  _Compositor_createSceneName,
+                  message.header.requestId,
+                  bindings.MessageHeader.kMessageIsResponse);
+            }
+          });
+        } else if (response != null) {
+          return buildResponseWithId(
+              response,
+              _Compositor_createSceneName,
+              message.header.requestId,
+              bindings.MessageHeader.kMessageIsResponse);
+        }
+        break;
+      case _Compositor_createRendererName:
+        var params = _CompositorCreateRendererParams.deserialize(
+            message.payload);
+        _impl.createRenderer(params.contextProvider, params.renderer, params.label);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  Compositor get impl => _impl;
+  set impl(Compositor d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "CompositorStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/hit_tests.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/hit_tests.mojom.dart
new file mode 100644
index 0000000..c08228b
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/hit_tests.mojom.dart
@@ -0,0 +1,551 @@
+// Copyright 2014 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.
+
+library hit_tests_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+const int kHitIdNone = 0;
+
+
+
+class Hit extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(40, 0)
+  ];
+  scene_token_mojom.SceneToken sceneToken = null;
+  int sceneVersion = 0;
+  int nodeId = 0;
+  int hitId = 0;
+  geometry_mojom.Point intersection = null;
+
+  Hit() : super(kVersions.last.size);
+
+  static Hit deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static Hit decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    Hit result = new Hit();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.sceneToken = scene_token_mojom.SceneToken.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.sceneVersion = decoder0.decodeUint32(16);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.nodeId = decoder0.decodeUint32(20);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.hitId = decoder0.decodeUint32(24);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(32, false);
+      result.intersection = geometry_mojom.Point.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(sceneToken, 8, false);
+    
+    encoder0.encodeUint32(sceneVersion, 16);
+    
+    encoder0.encodeUint32(nodeId, 20);
+    
+    encoder0.encodeUint32(hitId, 24);
+    
+    encoder0.encodeStruct(intersection, 32, false);
+  }
+
+  String toString() {
+    return "Hit("
+           "sceneToken: $sceneToken" ", "
+           "sceneVersion: $sceneVersion" ", "
+           "nodeId: $nodeId" ", "
+           "hitId: $hitId" ", "
+           "intersection: $intersection" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["sceneToken"] = sceneToken;
+    map["sceneVersion"] = sceneVersion;
+    map["nodeId"] = nodeId;
+    map["hitId"] = hitId;
+    map["intersection"] = intersection;
+    return map;
+  }
+}
+
+
+class HitTestResult extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  List<Hit> hits = null;
+
+  HitTestResult() : super(kVersions.last.size);
+
+  static HitTestResult deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static HitTestResult decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    HitTestResult result = new HitTestResult();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      {
+        var si1 = decoder1.decodeDataHeaderForPointerArray(bindings.kUnspecifiedArrayLength);
+        result.hits = new List<Hit>(si1.numElements);
+        for (int i1 = 0; i1 < si1.numElements; ++i1) {
+          
+          var decoder2 = decoder1.decodePointer(bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i1, false);
+          result.hits[i1] = Hit.decode(decoder2);
+        }
+      }
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    if (hits == null) {
+      encoder0.encodeNullPointer(8, false);
+    } else {
+      var encoder1 = encoder0.encodePointerArray(hits.length, 8, bindings.kUnspecifiedArrayLength);
+      for (int i0 = 0; i0 < hits.length; ++i0) {
+        
+        encoder1.encodeStruct(hits[i0], bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i0, false);
+      }
+    }
+  }
+
+  String toString() {
+    return "HitTestResult("
+           "hits: $hits" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["hits"] = hits;
+    return map;
+  }
+}
+
+
+class _HitTesterHitTestParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  geometry_mojom.Point point = null;
+
+  _HitTesterHitTestParams() : super(kVersions.last.size);
+
+  static _HitTesterHitTestParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _HitTesterHitTestParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _HitTesterHitTestParams result = new _HitTesterHitTestParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.point = geometry_mojom.Point.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(point, 8, false);
+  }
+
+  String toString() {
+    return "_HitTesterHitTestParams("
+           "point: $point" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["point"] = point;
+    return map;
+  }
+}
+
+
+class HitTesterHitTestResponseParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  HitTestResult result = null;
+
+  HitTesterHitTestResponseParams() : super(kVersions.last.size);
+
+  static HitTesterHitTestResponseParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static HitTesterHitTestResponseParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    HitTesterHitTestResponseParams result = new HitTesterHitTestResponseParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.result = HitTestResult.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(result, 8, false);
+  }
+
+  String toString() {
+    return "HitTesterHitTestResponseParams("
+           "result: $result" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["result"] = result;
+    return map;
+  }
+}
+
+const int _HitTester_hitTestName = 0;
+
+abstract class HitTester {
+  static const String serviceName = null;
+  dynamic hitTest(geometry_mojom.Point point,[Function responseFactory = null]);
+}
+
+
+class _HitTesterProxyImpl extends bindings.Proxy {
+  _HitTesterProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _HitTesterProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _HitTesterProxyImpl.unbound() : super.unbound();
+
+  static _HitTesterProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _HitTesterProxyImpl"));
+    return new _HitTesterProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      case _HitTester_hitTestName:
+        var r = HitTesterHitTestResponseParams.deserialize(
+            message.payload);
+        if (!message.header.hasRequestId) {
+          proxyError("Expected a message with a valid request Id.");
+          return;
+        }
+        Completer c = completerMap[message.header.requestId];
+        if (c == null) {
+          proxyError(
+              "Message had unknown request Id: ${message.header.requestId}");
+          return;
+        }
+        completerMap.remove(message.header.requestId);
+        if (c.isCompleted) {
+          proxyError("Response completer already completed");
+          return;
+        }
+        c.complete(r);
+        break;
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_HitTesterProxyImpl($superString)";
+  }
+}
+
+
+class _HitTesterProxyCalls implements HitTester {
+  _HitTesterProxyImpl _proxyImpl;
+
+  _HitTesterProxyCalls(this._proxyImpl);
+    dynamic hitTest(geometry_mojom.Point point,[Function responseFactory = null]) {
+      var params = new _HitTesterHitTestParams();
+      params.point = point;
+      return _proxyImpl.sendMessageWithRequestId(
+          params,
+          _HitTester_hitTestName,
+          -1,
+          bindings.MessageHeader.kMessageExpectsResponse);
+    }
+}
+
+
+class HitTesterProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  HitTester ptr;
+
+  HitTesterProxy(_HitTesterProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _HitTesterProxyCalls(proxyImpl);
+
+  HitTesterProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _HitTesterProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _HitTesterProxyCalls(impl);
+  }
+
+  HitTesterProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _HitTesterProxyImpl.fromHandle(handle) {
+    ptr = new _HitTesterProxyCalls(impl);
+  }
+
+  HitTesterProxy.unbound() :
+      impl = new _HitTesterProxyImpl.unbound() {
+    ptr = new _HitTesterProxyCalls(impl);
+  }
+
+  factory HitTesterProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    HitTesterProxy p = new HitTesterProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static HitTesterProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For HitTesterProxy"));
+    return new HitTesterProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => HitTester.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "HitTesterProxy($impl)";
+  }
+}
+
+
+class HitTesterStub extends bindings.Stub {
+  HitTester _impl = null;
+
+  HitTesterStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  HitTesterStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  HitTesterStub.unbound() : super.unbound();
+
+  static HitTesterStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For HitTesterStub"));
+    return new HitTesterStub.fromEndpoint(endpoint);
+  }
+
+
+  HitTesterHitTestResponseParams _HitTesterHitTestResponseParamsFactory(HitTestResult result) {
+    var mojo_factory_result = new HitTesterHitTestResponseParams();
+    mojo_factory_result.result = result;
+    return mojo_factory_result;
+  }
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _HitTester_hitTestName:
+        var params = _HitTesterHitTestParams.deserialize(
+            message.payload);
+        var response = _impl.hitTest(params.point,_HitTesterHitTestResponseParamsFactory);
+        if (response is Future) {
+          return response.then((response) {
+            if (response != null) {
+              return buildResponseWithId(
+                  response,
+                  _HitTester_hitTestName,
+                  message.header.requestId,
+                  bindings.MessageHeader.kMessageIsResponse);
+            }
+          });
+        } else if (response != null) {
+          return buildResponseWithId(
+              response,
+              _HitTester_hitTestName,
+              message.header.requestId,
+              bindings.MessageHeader.kMessageIsResponse);
+        }
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  HitTester get impl => _impl;
+  set impl(HitTester d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "HitTesterStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/nodes.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/nodes.mojom.dart
new file mode 100644
index 0000000..0e49fe0
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/nodes.mojom.dart
@@ -0,0 +1,839 @@
+// Copyright 2014 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.
+
+library nodes_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
+import 'package:mojo_services/mojo/gfx/composition/hit_tests.mojom.dart' as hit_tests_mojom;
+
+
+
+class NodeCombinator extends bindings.MojoEnum {
+  static const NodeCombinator merge = const NodeCombinator._(0);
+  static const NodeCombinator prune = const NodeCombinator._(1);
+  static const NodeCombinator fallback = const NodeCombinator._(2);
+
+  const NodeCombinator._(int v) : super(v);
+
+  static const Map<String, NodeCombinator> valuesMap = const {
+    "merge": merge,
+    "prune": prune,
+    "fallback": fallback,
+  };
+  static const List<NodeCombinator> values = const [
+    merge,
+    prune,
+    fallback,
+  ];
+
+  static NodeCombinator valueOf(String name) => valuesMap[name];
+
+  factory NodeCombinator(int v) {
+    switch (v) {
+      case 0:
+        return merge;
+      case 1:
+        return prune;
+      case 2:
+        return fallback;
+      default:
+        return null;
+    }
+  }
+
+  static NodeCombinator decode(bindings.Decoder decoder0, int offset) {
+    int v = decoder0.decodeUint32(offset);
+    NodeCombinator result = new NodeCombinator(v);
+    if (result == null) {
+      throw new bindings.MojoCodecError(
+          'Bad value $v for enum NodeCombinator.');
+    }
+    return result;
+  }
+
+  String toString() {
+    switch(this) {
+      case merge:
+        return 'NodeCombinator.merge';
+      case prune:
+        return 'NodeCombinator.prune';
+      case fallback:
+        return 'NodeCombinator.fallback';
+    }
+  }
+
+  int toJson() => mojoEnumValue;
+}
+
+class Node extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(56, 0)
+  ];
+  geometry_mojom.Transform contentTransform = null;
+  geometry_mojom.Rect contentClip = null;
+  int hitId = 0;
+  NodeCombinator combinator = new NodeCombinator(0);
+  List<int> childNodeIds = null;
+  NodeOp op = null;
+
+  Node() : super(kVersions.last.size);
+
+  static Node deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static Node decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    Node result = new Node();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, true);
+      result.contentTransform = geometry_mojom.Transform.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(16, true);
+      result.contentClip = geometry_mojom.Rect.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.hitId = decoder0.decodeUint32(24);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+        result.combinator = NodeCombinator.decode(decoder0, 28);
+        if (result.combinator == null) {
+          throw new bindings.MojoCodecError(
+            'Trying to decode null union for non-nullable NodeCombinator.');
+        }
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.childNodeIds = decoder0.decodeUint32Array(32, bindings.kArrayNullable, bindings.kUnspecifiedArrayLength);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+        result.op = NodeOp.decode(decoder0, 40);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(contentTransform, 8, true);
+    
+    encoder0.encodeStruct(contentClip, 16, true);
+    
+    encoder0.encodeUint32(hitId, 24);
+    
+    encoder0.encodeEnum(combinator, 28);
+    
+    encoder0.encodeUint32Array(childNodeIds, 32, bindings.kArrayNullable, bindings.kUnspecifiedArrayLength);
+    
+    encoder0.encodeUnion(op, 40, true);
+  }
+
+  String toString() {
+    return "Node("
+           "contentTransform: $contentTransform" ", "
+           "contentClip: $contentClip" ", "
+           "hitId: $hitId" ", "
+           "combinator: $combinator" ", "
+           "childNodeIds: $childNodeIds" ", "
+           "op: $op" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["contentTransform"] = contentTransform;
+    map["contentClip"] = contentClip;
+    map["hitId"] = hitId;
+    map["combinator"] = combinator;
+    map["childNodeIds"] = childNodeIds;
+    map["op"] = op;
+    return map;
+  }
+}
+
+
+class RectNodeOp extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(24, 0)
+  ];
+  geometry_mojom.Rect contentRect = null;
+  Color color = null;
+
+  RectNodeOp() : super(kVersions.last.size);
+
+  static RectNodeOp deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static RectNodeOp decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    RectNodeOp result = new RectNodeOp();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.contentRect = geometry_mojom.Rect.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(16, false);
+      result.color = Color.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(contentRect, 8, false);
+    
+    encoder0.encodeStruct(color, 16, false);
+  }
+
+  String toString() {
+    return "RectNodeOp("
+           "contentRect: $contentRect" ", "
+           "color: $color" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["contentRect"] = contentRect;
+    map["color"] = color;
+    return map;
+  }
+}
+
+
+class ImageNodeOp extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(40, 0)
+  ];
+  geometry_mojom.Rect contentRect = null;
+  geometry_mojom.Rect imageRect = null;
+  int imageResourceId = 0;
+  Blend blend = null;
+
+  ImageNodeOp() : super(kVersions.last.size);
+
+  static ImageNodeOp deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static ImageNodeOp decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    ImageNodeOp result = new ImageNodeOp();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.contentRect = geometry_mojom.Rect.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(16, true);
+      result.imageRect = geometry_mojom.Rect.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.imageResourceId = decoder0.decodeUint32(24);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(32, true);
+      result.blend = Blend.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(contentRect, 8, false);
+    
+    encoder0.encodeStruct(imageRect, 16, true);
+    
+    encoder0.encodeUint32(imageResourceId, 24);
+    
+    encoder0.encodeStruct(blend, 32, true);
+  }
+
+  String toString() {
+    return "ImageNodeOp("
+           "contentRect: $contentRect" ", "
+           "imageRect: $imageRect" ", "
+           "imageResourceId: $imageResourceId" ", "
+           "blend: $blend" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["contentRect"] = contentRect;
+    map["imageRect"] = imageRect;
+    map["imageResourceId"] = imageResourceId;
+    map["blend"] = blend;
+    return map;
+  }
+}
+
+
+class SceneNodeOp extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  int sceneResourceId = 0;
+  int sceneVersion = 0;
+
+  SceneNodeOp() : super(kVersions.last.size);
+
+  static SceneNodeOp deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneNodeOp decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneNodeOp result = new SceneNodeOp();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.sceneResourceId = decoder0.decodeUint32(8);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.sceneVersion = decoder0.decodeUint32(12);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint32(sceneResourceId, 8);
+    
+    encoder0.encodeUint32(sceneVersion, 12);
+  }
+
+  String toString() {
+    return "SceneNodeOp("
+           "sceneResourceId: $sceneResourceId" ", "
+           "sceneVersion: $sceneVersion" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["sceneResourceId"] = sceneResourceId;
+    map["sceneVersion"] = sceneVersion;
+    return map;
+  }
+}
+
+
+class LayerNodeOp extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(24, 0)
+  ];
+  geometry_mojom.Size layerSize = null;
+  Blend blend = null;
+
+  LayerNodeOp() : super(kVersions.last.size);
+
+  static LayerNodeOp deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static LayerNodeOp decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    LayerNodeOp result = new LayerNodeOp();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.layerSize = geometry_mojom.Size.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(16, true);
+      result.blend = Blend.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(layerSize, 8, false);
+    
+    encoder0.encodeStruct(blend, 16, true);
+  }
+
+  String toString() {
+    return "LayerNodeOp("
+           "layerSize: $layerSize" ", "
+           "blend: $blend" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["layerSize"] = layerSize;
+    map["blend"] = blend;
+    return map;
+  }
+}
+
+
+class Color extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  int red = 0;
+  int green = 0;
+  int blue = 0;
+  int alpha = 0;
+
+  Color() : super(kVersions.last.size);
+
+  static Color deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static Color decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    Color result = new Color();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.red = decoder0.decodeUint8(8);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.green = decoder0.decodeUint8(9);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.blue = decoder0.decodeUint8(10);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.alpha = decoder0.decodeUint8(11);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint8(red, 8);
+    
+    encoder0.encodeUint8(green, 9);
+    
+    encoder0.encodeUint8(blue, 10);
+    
+    encoder0.encodeUint8(alpha, 11);
+  }
+
+  String toString() {
+    return "Color("
+           "red: $red" ", "
+           "green: $green" ", "
+           "blue: $blue" ", "
+           "alpha: $alpha" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["red"] = red;
+    map["green"] = green;
+    map["blue"] = blue;
+    map["alpha"] = alpha;
+    return map;
+  }
+}
+
+
+class Blend extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  int alpha = 255;
+
+  Blend() : super(kVersions.last.size);
+
+  static Blend deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static Blend decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    Blend result = new Blend();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.alpha = decoder0.decodeUint8(8);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint8(alpha, 8);
+  }
+
+  String toString() {
+    return "Blend("
+           "alpha: $alpha" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["alpha"] = alpha;
+    return map;
+  }
+}
+
+
+
+enum NodeOpTag {
+  rect,
+  image,
+  scene,
+  layer,
+  unknown
+}
+
+class NodeOp extends bindings.Union {
+  static final _tag_to_int = const {
+    NodeOpTag.rect: 0,
+    NodeOpTag.image: 1,
+    NodeOpTag.scene: 2,
+    NodeOpTag.layer: 3,
+  };
+
+  static final _int_to_tag = const {
+    0: NodeOpTag.rect,
+    1: NodeOpTag.image,
+    2: NodeOpTag.scene,
+    3: NodeOpTag.layer,
+  };
+
+  var _data;
+  NodeOpTag _tag = NodeOpTag.unknown;
+
+  NodeOpTag get tag => _tag;
+  RectNodeOp get rect {
+    if (_tag != NodeOpTag.rect) {
+      throw new bindings.UnsetUnionTagError(_tag, NodeOpTag.rect);
+    }
+    return _data;
+  }
+
+  set rect(RectNodeOp value) {
+    _tag = NodeOpTag.rect;
+    _data = value;
+  }
+  ImageNodeOp get image {
+    if (_tag != NodeOpTag.image) {
+      throw new bindings.UnsetUnionTagError(_tag, NodeOpTag.image);
+    }
+    return _data;
+  }
+
+  set image(ImageNodeOp value) {
+    _tag = NodeOpTag.image;
+    _data = value;
+  }
+  SceneNodeOp get scene {
+    if (_tag != NodeOpTag.scene) {
+      throw new bindings.UnsetUnionTagError(_tag, NodeOpTag.scene);
+    }
+    return _data;
+  }
+
+  set scene(SceneNodeOp value) {
+    _tag = NodeOpTag.scene;
+    _data = value;
+  }
+  LayerNodeOp get layer {
+    if (_tag != NodeOpTag.layer) {
+      throw new bindings.UnsetUnionTagError(_tag, NodeOpTag.layer);
+    }
+    return _data;
+  }
+
+  set layer(LayerNodeOp value) {
+    _tag = NodeOpTag.layer;
+    _data = value;
+  }
+
+  static NodeOp decode(bindings.Decoder decoder0, int offset) {
+    int size = decoder0.decodeUint32(offset);
+    if (size == 0) {
+      return null;
+    }
+    NodeOp result = new NodeOp();
+
+    // TODO(azani): Handle unknown union member.
+    NodeOpTag tag = _int_to_tag[decoder0.decodeUint32(offset + 4)];
+    switch (tag) {
+      case NodeOpTag.rect:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.rect = RectNodeOp.decode(decoder1);
+        break;
+      case NodeOpTag.image:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.image = ImageNodeOp.decode(decoder1);
+        break;
+      case NodeOpTag.scene:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.scene = SceneNodeOp.decode(decoder1);
+        break;
+      case NodeOpTag.layer:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.layer = LayerNodeOp.decode(decoder1);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Bad union tag: $tag");
+    }
+
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder0, int offset) {
+    // TODO(azani): Error when trying to encode an unknown member.
+    encoder0.encodeUint32(16, offset);
+    encoder0.encodeUint32(_tag_to_int[_tag], offset + 4);
+    switch (_tag) {
+      case NodeOpTag.rect:
+        
+        encoder0.encodeStruct(rect, offset + 8, false);
+        break;
+      case NodeOpTag.image:
+        
+        encoder0.encodeStruct(image, offset + 8, false);
+        break;
+      case NodeOpTag.scene:
+        
+        encoder0.encodeStruct(scene, offset + 8, false);
+        break;
+      case NodeOpTag.layer:
+        
+        encoder0.encodeStruct(layer, offset + 8, false);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Bad union tag: $_tag");
+    }
+  }
+
+  String toString() {
+    String result = "NodeOp(";
+    switch (_tag) {
+      case NodeOpTag.rect:
+        result += "rect";
+        break;
+      case NodeOpTag.image:
+        result += "image";
+        break;
+      case NodeOpTag.scene:
+        result += "scene";
+        break;
+      case NodeOpTag.layer:
+        result += "layer";
+        break;
+      default:
+        result += "unknown";
+    }
+    result += ": $_data)";
+    return result;
+  }
+}
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart
new file mode 100644
index 0000000..9c8d520
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart
@@ -0,0 +1,355 @@
+// Copyright 2014 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.
+
+library renderers_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
+import 'package:mojo_services/mojo/gfx/composition/hit_tests.mojom.dart' as hit_tests_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+
+
+
+class _RendererSetRootSceneParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(32, 0)
+  ];
+  scene_token_mojom.SceneToken sceneToken = null;
+  int sceneVersion = 0;
+  geometry_mojom.Rect viewport = null;
+
+  _RendererSetRootSceneParams() : super(kVersions.last.size);
+
+  static _RendererSetRootSceneParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _RendererSetRootSceneParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _RendererSetRootSceneParams result = new _RendererSetRootSceneParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.sceneToken = scene_token_mojom.SceneToken.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.sceneVersion = decoder0.decodeUint32(16);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(24, false);
+      result.viewport = geometry_mojom.Rect.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(sceneToken, 8, false);
+    
+    encoder0.encodeUint32(sceneVersion, 16);
+    
+    encoder0.encodeStruct(viewport, 24, false);
+  }
+
+  String toString() {
+    return "_RendererSetRootSceneParams("
+           "sceneToken: $sceneToken" ", "
+           "sceneVersion: $sceneVersion" ", "
+           "viewport: $viewport" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["sceneToken"] = sceneToken;
+    map["sceneVersion"] = sceneVersion;
+    map["viewport"] = viewport;
+    return map;
+  }
+}
+
+
+class _RendererGetHitTesterParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  Object hitTester = null;
+
+  _RendererGetHitTesterParams() : super(kVersions.last.size);
+
+  static _RendererGetHitTesterParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _RendererGetHitTesterParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _RendererGetHitTesterParams result = new _RendererGetHitTesterParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.hitTester = decoder0.decodeInterfaceRequest(8, false, hit_tests_mojom.HitTesterStub.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInterfaceRequest(hitTester, 8, false);
+  }
+
+  String toString() {
+    return "_RendererGetHitTesterParams("
+           "hitTester: $hitTester" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+const int _Renderer_setRootSceneName = 0;
+const int _Renderer_getHitTesterName = 1;
+
+abstract class Renderer {
+  static const String serviceName = null;
+  void setRootScene(scene_token_mojom.SceneToken sceneToken, int sceneVersion, geometry_mojom.Rect viewport);
+  void getHitTester(Object hitTester);
+}
+
+
+class _RendererProxyImpl extends bindings.Proxy {
+  _RendererProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _RendererProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _RendererProxyImpl.unbound() : super.unbound();
+
+  static _RendererProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _RendererProxyImpl"));
+    return new _RendererProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_RendererProxyImpl($superString)";
+  }
+}
+
+
+class _RendererProxyCalls implements Renderer {
+  _RendererProxyImpl _proxyImpl;
+
+  _RendererProxyCalls(this._proxyImpl);
+    void setRootScene(scene_token_mojom.SceneToken sceneToken, int sceneVersion, geometry_mojom.Rect viewport) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _RendererSetRootSceneParams();
+      params.sceneToken = sceneToken;
+      params.sceneVersion = sceneVersion;
+      params.viewport = viewport;
+      _proxyImpl.sendMessage(params, _Renderer_setRootSceneName);
+    }
+    void getHitTester(Object hitTester) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _RendererGetHitTesterParams();
+      params.hitTester = hitTester;
+      _proxyImpl.sendMessage(params, _Renderer_getHitTesterName);
+    }
+}
+
+
+class RendererProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  Renderer ptr;
+
+  RendererProxy(_RendererProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _RendererProxyCalls(proxyImpl);
+
+  RendererProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _RendererProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _RendererProxyCalls(impl);
+  }
+
+  RendererProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _RendererProxyImpl.fromHandle(handle) {
+    ptr = new _RendererProxyCalls(impl);
+  }
+
+  RendererProxy.unbound() :
+      impl = new _RendererProxyImpl.unbound() {
+    ptr = new _RendererProxyCalls(impl);
+  }
+
+  factory RendererProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    RendererProxy p = new RendererProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static RendererProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For RendererProxy"));
+    return new RendererProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => Renderer.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "RendererProxy($impl)";
+  }
+}
+
+
+class RendererStub extends bindings.Stub {
+  Renderer _impl = null;
+
+  RendererStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  RendererStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  RendererStub.unbound() : super.unbound();
+
+  static RendererStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For RendererStub"));
+    return new RendererStub.fromEndpoint(endpoint);
+  }
+
+
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _Renderer_setRootSceneName:
+        var params = _RendererSetRootSceneParams.deserialize(
+            message.payload);
+        _impl.setRootScene(params.sceneToken, params.sceneVersion, params.viewport);
+        break;
+      case _Renderer_getHitTesterName:
+        var params = _RendererGetHitTesterParams.deserialize(
+            message.payload);
+        _impl.getHitTester(params.hitTester);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  Renderer get impl => _impl;
+  set impl(Renderer d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "RendererStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/resources.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/resources.mojom.dart
new file mode 100644
index 0000000..ad2bba4
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/resources.mojom.dart
@@ -0,0 +1,506 @@
+// Copyright 2014 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.
+
+library resources_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+
+
+
+class SceneResource extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  scene_token_mojom.SceneToken sceneToken = null;
+
+  SceneResource() : super(kVersions.last.size);
+
+  static SceneResource deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneResource decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneResource result = new SceneResource();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.sceneToken = scene_token_mojom.SceneToken.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(sceneToken, 8, false);
+  }
+
+  String toString() {
+    return "SceneResource("
+           "sceneToken: $sceneToken" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["sceneToken"] = sceneToken;
+    return map;
+  }
+}
+
+
+class MailboxTextureResource extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(40, 0)
+  ];
+  List<int> mailboxName = null;
+  int syncPoint = 0;
+  geometry_mojom.Size size = null;
+  Object callback = null;
+
+  MailboxTextureResource() : super(kVersions.last.size);
+
+  static MailboxTextureResource deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static MailboxTextureResource decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    MailboxTextureResource result = new MailboxTextureResource();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.mailboxName = decoder0.decodeUint8Array(8, bindings.kNothingNullable, 64);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.syncPoint = decoder0.decodeUint32(16);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(24, false);
+      result.size = geometry_mojom.Size.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.callback = decoder0.decodeServiceInterface(32, false, MailboxTextureCallbackProxy.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint8Array(mailboxName, 8, bindings.kNothingNullable, 64);
+    
+    encoder0.encodeUint32(syncPoint, 16);
+    
+    encoder0.encodeStruct(size, 24, false);
+    
+    encoder0.encodeInterface(callback, 32, false);
+  }
+
+  String toString() {
+    return "MailboxTextureResource("
+           "mailboxName: $mailboxName" ", "
+           "syncPoint: $syncPoint" ", "
+           "size: $size" ", "
+           "callback: $callback" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class _MailboxTextureCallbackOnMailboxTextureReleasedParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(8, 0)
+  ];
+
+  _MailboxTextureCallbackOnMailboxTextureReleasedParams() : super(kVersions.last.size);
+
+  static _MailboxTextureCallbackOnMailboxTextureReleasedParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _MailboxTextureCallbackOnMailboxTextureReleasedParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _MailboxTextureCallbackOnMailboxTextureReleasedParams result = new _MailboxTextureCallbackOnMailboxTextureReleasedParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    encoder.getStructEncoderAtOffset(kVersions.last);
+  }
+
+  String toString() {
+    return "_MailboxTextureCallbackOnMailboxTextureReleasedParams("")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    return map;
+  }
+}
+
+
+
+enum ResourceTag {
+  scene,
+  mailboxTexture,
+  unknown
+}
+
+class Resource extends bindings.Union {
+  static final _tag_to_int = const {
+    ResourceTag.scene: 0,
+    ResourceTag.mailboxTexture: 1,
+  };
+
+  static final _int_to_tag = const {
+    0: ResourceTag.scene,
+    1: ResourceTag.mailboxTexture,
+  };
+
+  var _data;
+  ResourceTag _tag = ResourceTag.unknown;
+
+  ResourceTag get tag => _tag;
+  SceneResource get scene {
+    if (_tag != ResourceTag.scene) {
+      throw new bindings.UnsetUnionTagError(_tag, ResourceTag.scene);
+    }
+    return _data;
+  }
+
+  set scene(SceneResource value) {
+    _tag = ResourceTag.scene;
+    _data = value;
+  }
+  MailboxTextureResource get mailboxTexture {
+    if (_tag != ResourceTag.mailboxTexture) {
+      throw new bindings.UnsetUnionTagError(_tag, ResourceTag.mailboxTexture);
+    }
+    return _data;
+  }
+
+  set mailboxTexture(MailboxTextureResource value) {
+    _tag = ResourceTag.mailboxTexture;
+    _data = value;
+  }
+
+  static Resource decode(bindings.Decoder decoder0, int offset) {
+    int size = decoder0.decodeUint32(offset);
+    if (size == 0) {
+      return null;
+    }
+    Resource result = new Resource();
+
+    // TODO(azani): Handle unknown union member.
+    ResourceTag tag = _int_to_tag[decoder0.decodeUint32(offset + 4)];
+    switch (tag) {
+      case ResourceTag.scene:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.scene = SceneResource.decode(decoder1);
+        break;
+      case ResourceTag.mailboxTexture:
+        
+        var decoder1 = decoder0.decodePointer(offset + 8, false);
+        result.mailboxTexture = MailboxTextureResource.decode(decoder1);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Bad union tag: $tag");
+    }
+
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder0, int offset) {
+    // TODO(azani): Error when trying to encode an unknown member.
+    encoder0.encodeUint32(16, offset);
+    encoder0.encodeUint32(_tag_to_int[_tag], offset + 4);
+    switch (_tag) {
+      case ResourceTag.scene:
+        
+        encoder0.encodeStruct(scene, offset + 8, false);
+        break;
+      case ResourceTag.mailboxTexture:
+        
+        encoder0.encodeStruct(mailboxTexture, offset + 8, false);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Bad union tag: $_tag");
+    }
+  }
+
+  String toString() {
+    String result = "Resource(";
+    switch (_tag) {
+      case ResourceTag.scene:
+        result += "scene";
+        break;
+      case ResourceTag.mailboxTexture:
+        result += "mailboxTexture";
+        break;
+      default:
+        result += "unknown";
+    }
+    result += ": $_data)";
+    return result;
+  }
+}
+const int _MailboxTextureCallback_onMailboxTextureReleasedName = 0;
+
+abstract class MailboxTextureCallback {
+  static const String serviceName = null;
+  void onMailboxTextureReleased();
+}
+
+
+class _MailboxTextureCallbackProxyImpl extends bindings.Proxy {
+  _MailboxTextureCallbackProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _MailboxTextureCallbackProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _MailboxTextureCallbackProxyImpl.unbound() : super.unbound();
+
+  static _MailboxTextureCallbackProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _MailboxTextureCallbackProxyImpl"));
+    return new _MailboxTextureCallbackProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_MailboxTextureCallbackProxyImpl($superString)";
+  }
+}
+
+
+class _MailboxTextureCallbackProxyCalls implements MailboxTextureCallback {
+  _MailboxTextureCallbackProxyImpl _proxyImpl;
+
+  _MailboxTextureCallbackProxyCalls(this._proxyImpl);
+    void onMailboxTextureReleased() {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _MailboxTextureCallbackOnMailboxTextureReleasedParams();
+      _proxyImpl.sendMessage(params, _MailboxTextureCallback_onMailboxTextureReleasedName);
+    }
+}
+
+
+class MailboxTextureCallbackProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  MailboxTextureCallback ptr;
+
+  MailboxTextureCallbackProxy(_MailboxTextureCallbackProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _MailboxTextureCallbackProxyCalls(proxyImpl);
+
+  MailboxTextureCallbackProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _MailboxTextureCallbackProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _MailboxTextureCallbackProxyCalls(impl);
+  }
+
+  MailboxTextureCallbackProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _MailboxTextureCallbackProxyImpl.fromHandle(handle) {
+    ptr = new _MailboxTextureCallbackProxyCalls(impl);
+  }
+
+  MailboxTextureCallbackProxy.unbound() :
+      impl = new _MailboxTextureCallbackProxyImpl.unbound() {
+    ptr = new _MailboxTextureCallbackProxyCalls(impl);
+  }
+
+  factory MailboxTextureCallbackProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    MailboxTextureCallbackProxy p = new MailboxTextureCallbackProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static MailboxTextureCallbackProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For MailboxTextureCallbackProxy"));
+    return new MailboxTextureCallbackProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => MailboxTextureCallback.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "MailboxTextureCallbackProxy($impl)";
+  }
+}
+
+
+class MailboxTextureCallbackStub extends bindings.Stub {
+  MailboxTextureCallback _impl = null;
+
+  MailboxTextureCallbackStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  MailboxTextureCallbackStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  MailboxTextureCallbackStub.unbound() : super.unbound();
+
+  static MailboxTextureCallbackStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For MailboxTextureCallbackStub"));
+    return new MailboxTextureCallbackStub.fromEndpoint(endpoint);
+  }
+
+
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _MailboxTextureCallback_onMailboxTextureReleasedName:
+        var params = _MailboxTextureCallbackOnMailboxTextureReleasedParams.deserialize(
+            message.payload);
+        _impl.onMailboxTextureReleased();
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  MailboxTextureCallback get impl => _impl;
+  set impl(MailboxTextureCallback d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "MailboxTextureCallbackStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scene_token.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scene_token.mojom.dart
new file mode 100644
index 0000000..d2619f5
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scene_token.mojom.dart
@@ -0,0 +1,80 @@
+// Copyright 2014 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.
+
+library scene_token_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+
+
+
+class SceneToken extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  int value = 0;
+
+  SceneToken() : super(kVersions.last.size);
+
+  static SceneToken deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneToken decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneToken result = new SceneToken();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.value = decoder0.decodeUint32(8);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint32(value, 8);
+  }
+
+  String toString() {
+    return "SceneToken("
+           "value: $value" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["value"] = value;
+    return map;
+  }
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart
new file mode 100644
index 0000000..30e582e
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart
@@ -0,0 +1,1087 @@
+// Copyright 2014 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.
+
+library scenes_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo_services/mojo/gfx/composition/nodes.mojom.dart' as nodes_mojom;
+import 'package:mojo_services/mojo/gfx/composition/resources.mojom.dart' as resources_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scheduling.mojom.dart' as scheduling_mojom;
+const int kSceneRootNodeId = 0;
+const int kSceneVersionNone = 0;
+
+
+
+class SceneUpdate extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(32, 0)
+  ];
+  bool clearResources = false;
+  bool clearNodes = false;
+  Map<int, resources_mojom.Resource> resources = null;
+  Map<int, nodes_mojom.Node> nodes = null;
+
+  SceneUpdate() : super(kVersions.last.size);
+
+  static SceneUpdate deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneUpdate decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneUpdate result = new SceneUpdate();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.clearResources = decoder0.decodeBool(8, 0);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.clearNodes = decoder0.decodeBool(8, 1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(16, true);
+      if (decoder1 == null) {
+        result.resources = null;
+      } else {
+        decoder1.decodeDataHeaderForMap();
+        List<int> keys0;
+        List<resources_mojom.Resource> values0;
+        {
+          
+          keys0 = decoder1.decodeUint32Array(bindings.ArrayDataHeader.kHeaderSize, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+        }
+        {
+          
+          var decoder2 = decoder1.decodePointer(bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize, false);
+          {
+            var si2 = decoder2.decodeDataHeaderForUnionArray(keys0.length);
+            values0 = new List<resources_mojom.Resource>(si2.numElements);
+            for (int i2 = 0; i2 < si2.numElements; ++i2) {
+              
+                values0[i2] = resources_mojom.Resource.decode(decoder2, bindings.ArrayDataHeader.kHeaderSize + bindings.kUnionSize * i2);
+            }
+          }
+        }
+        result.resources = new Map<int, resources_mojom.Resource>.fromIterables(
+            keys0, values0);
+      }
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(24, true);
+      if (decoder1 == null) {
+        result.nodes = null;
+      } else {
+        decoder1.decodeDataHeaderForMap();
+        List<int> keys0;
+        List<nodes_mojom.Node> values0;
+        {
+          
+          keys0 = decoder1.decodeUint32Array(bindings.ArrayDataHeader.kHeaderSize, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+        }
+        {
+          
+          var decoder2 = decoder1.decodePointer(bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize, false);
+          {
+            var si2 = decoder2.decodeDataHeaderForPointerArray(keys0.length);
+            values0 = new List<nodes_mojom.Node>(si2.numElements);
+            for (int i2 = 0; i2 < si2.numElements; ++i2) {
+              
+              var decoder3 = decoder2.decodePointer(bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i2, true);
+              values0[i2] = nodes_mojom.Node.decode(decoder3);
+            }
+          }
+        }
+        result.nodes = new Map<int, nodes_mojom.Node>.fromIterables(
+            keys0, values0);
+      }
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeBool(clearResources, 8, 0);
+    
+    encoder0.encodeBool(clearNodes, 8, 1);
+    
+    if (resources == null) {
+      encoder0.encodeNullPointer(16, true);
+    } else {
+      var encoder1 = encoder0.encoderForMap(16);
+      int size0 = resources.length;
+      var keys0 = resources.keys.toList();
+      var values0 = resources.values.toList();
+      
+      encoder1.encodeUint32Array(keys0, bindings.ArrayDataHeader.kHeaderSize, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+      
+      {
+        var encoder2 = encoder1.encodeUnionArray(values0.length, bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize, bindings.kUnspecifiedArrayLength);
+        for (int i1 = 0; i1 < values0.length; ++i1) {
+          
+          encoder2.encodeUnion(values0[i1], bindings.ArrayDataHeader.kHeaderSize + bindings.kUnionSize * i1, true);
+        }
+      }
+    }
+    
+    if (nodes == null) {
+      encoder0.encodeNullPointer(24, true);
+    } else {
+      var encoder1 = encoder0.encoderForMap(24);
+      int size0 = nodes.length;
+      var keys0 = nodes.keys.toList();
+      var values0 = nodes.values.toList();
+      
+      encoder1.encodeUint32Array(keys0, bindings.ArrayDataHeader.kHeaderSize, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+      
+      {
+        var encoder2 = encoder1.encodePointerArray(values0.length, bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize, bindings.kUnspecifiedArrayLength);
+        for (int i1 = 0; i1 < values0.length; ++i1) {
+          
+          encoder2.encodeStruct(values0[i1], bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i1, true);
+        }
+      }
+    }
+  }
+
+  String toString() {
+    return "SceneUpdate("
+           "clearResources: $clearResources" ", "
+           "clearNodes: $clearNodes" ", "
+           "resources: $resources" ", "
+           "nodes: $nodes" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class SceneMetadata extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(24, 0)
+  ];
+  int version = 0;
+  int presentationTime = 0;
+
+  SceneMetadata() : super(kVersions.last.size);
+
+  static SceneMetadata deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneMetadata decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneMetadata result = new SceneMetadata();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.version = decoder0.decodeUint32(8);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.presentationTime = decoder0.decodeInt64(16);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint32(version, 8);
+    
+    encoder0.encodeInt64(presentationTime, 16);
+  }
+
+  String toString() {
+    return "SceneMetadata("
+           "version: $version" ", "
+           "presentationTime: $presentationTime" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["version"] = version;
+    map["presentationTime"] = presentationTime;
+    return map;
+  }
+}
+
+
+class _SceneSetListenerParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  Object listener = null;
+
+  _SceneSetListenerParams() : super(kVersions.last.size);
+
+  static _SceneSetListenerParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _SceneSetListenerParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _SceneSetListenerParams result = new _SceneSetListenerParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.listener = decoder0.decodeServiceInterface(8, true, SceneListenerProxy.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInterface(listener, 8, true);
+  }
+
+  String toString() {
+    return "_SceneSetListenerParams("
+           "listener: $listener" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class _SceneUpdateParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  SceneUpdate update = null;
+
+  _SceneUpdateParams() : super(kVersions.last.size);
+
+  static _SceneUpdateParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _SceneUpdateParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _SceneUpdateParams result = new _SceneUpdateParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.update = SceneUpdate.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(update, 8, false);
+  }
+
+  String toString() {
+    return "_SceneUpdateParams("
+           "update: $update" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class _ScenePublishParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  SceneMetadata metadata = null;
+
+  _ScenePublishParams() : super(kVersions.last.size);
+
+  static _ScenePublishParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _ScenePublishParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _ScenePublishParams result = new _ScenePublishParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, true);
+      result.metadata = SceneMetadata.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(metadata, 8, true);
+  }
+
+  String toString() {
+    return "_ScenePublishParams("
+           "metadata: $metadata" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["metadata"] = metadata;
+    return map;
+  }
+}
+
+
+class _SceneGetSchedulerParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  Object scheduler = null;
+
+  _SceneGetSchedulerParams() : super(kVersions.last.size);
+
+  static _SceneGetSchedulerParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _SceneGetSchedulerParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _SceneGetSchedulerParams result = new _SceneGetSchedulerParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.scheduler = decoder0.decodeInterfaceRequest(8, false, scheduling_mojom.SceneSchedulerStub.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInterfaceRequest(scheduler, 8, false);
+  }
+
+  String toString() {
+    return "_SceneGetSchedulerParams("
+           "scheduler: $scheduler" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
+class _SceneListenerOnResourceUnavailableParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  int resourceId = 0;
+
+  _SceneListenerOnResourceUnavailableParams() : super(kVersions.last.size);
+
+  static _SceneListenerOnResourceUnavailableParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _SceneListenerOnResourceUnavailableParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _SceneListenerOnResourceUnavailableParams result = new _SceneListenerOnResourceUnavailableParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.resourceId = decoder0.decodeUint32(8);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeUint32(resourceId, 8);
+  }
+
+  String toString() {
+    return "_SceneListenerOnResourceUnavailableParams("
+           "resourceId: $resourceId" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["resourceId"] = resourceId;
+    return map;
+  }
+}
+
+
+class SceneListenerOnResourceUnavailableResponseParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(8, 0)
+  ];
+
+  SceneListenerOnResourceUnavailableResponseParams() : super(kVersions.last.size);
+
+  static SceneListenerOnResourceUnavailableResponseParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneListenerOnResourceUnavailableResponseParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneListenerOnResourceUnavailableResponseParams result = new SceneListenerOnResourceUnavailableResponseParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    encoder.getStructEncoderAtOffset(kVersions.last);
+  }
+
+  String toString() {
+    return "SceneListenerOnResourceUnavailableResponseParams("")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    return map;
+  }
+}
+
+const int _Scene_setListenerName = 0;
+const int _Scene_updateName = 1;
+const int _Scene_publishName = 2;
+const int _Scene_getSchedulerName = 3;
+
+abstract class Scene {
+  static const String serviceName = null;
+  void setListener(Object listener);
+  void update(SceneUpdate update);
+  void publish(SceneMetadata metadata);
+  void getScheduler(Object scheduler);
+}
+
+
+class _SceneProxyImpl extends bindings.Proxy {
+  _SceneProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _SceneProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _SceneProxyImpl.unbound() : super.unbound();
+
+  static _SceneProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _SceneProxyImpl"));
+    return new _SceneProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_SceneProxyImpl($superString)";
+  }
+}
+
+
+class _SceneProxyCalls implements Scene {
+  _SceneProxyImpl _proxyImpl;
+
+  _SceneProxyCalls(this._proxyImpl);
+    void setListener(Object listener) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _SceneSetListenerParams();
+      params.listener = listener;
+      _proxyImpl.sendMessage(params, _Scene_setListenerName);
+    }
+    void update(SceneUpdate update) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _SceneUpdateParams();
+      params.update = update;
+      _proxyImpl.sendMessage(params, _Scene_updateName);
+    }
+    void publish(SceneMetadata metadata) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _ScenePublishParams();
+      params.metadata = metadata;
+      _proxyImpl.sendMessage(params, _Scene_publishName);
+    }
+    void getScheduler(Object scheduler) {
+      if (!_proxyImpl.isBound) {
+        _proxyImpl.proxyError("The Proxy is closed.");
+        return;
+      }
+      var params = new _SceneGetSchedulerParams();
+      params.scheduler = scheduler;
+      _proxyImpl.sendMessage(params, _Scene_getSchedulerName);
+    }
+}
+
+
+class SceneProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  Scene ptr;
+
+  SceneProxy(_SceneProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _SceneProxyCalls(proxyImpl);
+
+  SceneProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _SceneProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _SceneProxyCalls(impl);
+  }
+
+  SceneProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _SceneProxyImpl.fromHandle(handle) {
+    ptr = new _SceneProxyCalls(impl);
+  }
+
+  SceneProxy.unbound() :
+      impl = new _SceneProxyImpl.unbound() {
+    ptr = new _SceneProxyCalls(impl);
+  }
+
+  factory SceneProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    SceneProxy p = new SceneProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static SceneProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneProxy"));
+    return new SceneProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => Scene.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "SceneProxy($impl)";
+  }
+}
+
+
+class SceneStub extends bindings.Stub {
+  Scene _impl = null;
+
+  SceneStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  SceneStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  SceneStub.unbound() : super.unbound();
+
+  static SceneStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneStub"));
+    return new SceneStub.fromEndpoint(endpoint);
+  }
+
+
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _Scene_setListenerName:
+        var params = _SceneSetListenerParams.deserialize(
+            message.payload);
+        _impl.setListener(params.listener);
+        break;
+      case _Scene_updateName:
+        var params = _SceneUpdateParams.deserialize(
+            message.payload);
+        _impl.update(params.update);
+        break;
+      case _Scene_publishName:
+        var params = _ScenePublishParams.deserialize(
+            message.payload);
+        _impl.publish(params.metadata);
+        break;
+      case _Scene_getSchedulerName:
+        var params = _SceneGetSchedulerParams.deserialize(
+            message.payload);
+        _impl.getScheduler(params.scheduler);
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  Scene get impl => _impl;
+  set impl(Scene d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "SceneStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+const int _SceneListener_onResourceUnavailableName = 0;
+
+abstract class SceneListener {
+  static const String serviceName = null;
+  dynamic onResourceUnavailable(int resourceId,[Function responseFactory = null]);
+}
+
+
+class _SceneListenerProxyImpl extends bindings.Proxy {
+  _SceneListenerProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _SceneListenerProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _SceneListenerProxyImpl.unbound() : super.unbound();
+
+  static _SceneListenerProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _SceneListenerProxyImpl"));
+    return new _SceneListenerProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      case _SceneListener_onResourceUnavailableName:
+        var r = SceneListenerOnResourceUnavailableResponseParams.deserialize(
+            message.payload);
+        if (!message.header.hasRequestId) {
+          proxyError("Expected a message with a valid request Id.");
+          return;
+        }
+        Completer c = completerMap[message.header.requestId];
+        if (c == null) {
+          proxyError(
+              "Message had unknown request Id: ${message.header.requestId}");
+          return;
+        }
+        completerMap.remove(message.header.requestId);
+        if (c.isCompleted) {
+          proxyError("Response completer already completed");
+          return;
+        }
+        c.complete(r);
+        break;
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_SceneListenerProxyImpl($superString)";
+  }
+}
+
+
+class _SceneListenerProxyCalls implements SceneListener {
+  _SceneListenerProxyImpl _proxyImpl;
+
+  _SceneListenerProxyCalls(this._proxyImpl);
+    dynamic onResourceUnavailable(int resourceId,[Function responseFactory = null]) {
+      var params = new _SceneListenerOnResourceUnavailableParams();
+      params.resourceId = resourceId;
+      return _proxyImpl.sendMessageWithRequestId(
+          params,
+          _SceneListener_onResourceUnavailableName,
+          -1,
+          bindings.MessageHeader.kMessageExpectsResponse);
+    }
+}
+
+
+class SceneListenerProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  SceneListener ptr;
+
+  SceneListenerProxy(_SceneListenerProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _SceneListenerProxyCalls(proxyImpl);
+
+  SceneListenerProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _SceneListenerProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _SceneListenerProxyCalls(impl);
+  }
+
+  SceneListenerProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _SceneListenerProxyImpl.fromHandle(handle) {
+    ptr = new _SceneListenerProxyCalls(impl);
+  }
+
+  SceneListenerProxy.unbound() :
+      impl = new _SceneListenerProxyImpl.unbound() {
+    ptr = new _SceneListenerProxyCalls(impl);
+  }
+
+  factory SceneListenerProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    SceneListenerProxy p = new SceneListenerProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static SceneListenerProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneListenerProxy"));
+    return new SceneListenerProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => SceneListener.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "SceneListenerProxy($impl)";
+  }
+}
+
+
+class SceneListenerStub extends bindings.Stub {
+  SceneListener _impl = null;
+
+  SceneListenerStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  SceneListenerStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  SceneListenerStub.unbound() : super.unbound();
+
+  static SceneListenerStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneListenerStub"));
+    return new SceneListenerStub.fromEndpoint(endpoint);
+  }
+
+
+  SceneListenerOnResourceUnavailableResponseParams _SceneListenerOnResourceUnavailableResponseParamsFactory() {
+    var mojo_factory_result = new SceneListenerOnResourceUnavailableResponseParams();
+    return mojo_factory_result;
+  }
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _SceneListener_onResourceUnavailableName:
+        var params = _SceneListenerOnResourceUnavailableParams.deserialize(
+            message.payload);
+        var response = _impl.onResourceUnavailable(params.resourceId,_SceneListenerOnResourceUnavailableResponseParamsFactory);
+        if (response is Future) {
+          return response.then((response) {
+            if (response != null) {
+              return buildResponseWithId(
+                  response,
+                  _SceneListener_onResourceUnavailableName,
+                  message.header.requestId,
+                  bindings.MessageHeader.kMessageIsResponse);
+            }
+          });
+        } else if (response != null) {
+          return buildResponseWithId(
+              response,
+              _SceneListener_onResourceUnavailableName,
+              message.header.requestId,
+              bindings.MessageHeader.kMessageIsResponse);
+        }
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  SceneListener get impl => _impl;
+  set impl(SceneListener d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "SceneListenerStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart
new file mode 100644
index 0000000..28cecc9
--- /dev/null
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart
@@ -0,0 +1,442 @@
+// Copyright 2014 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.
+
+library scheduling_mojom;
+
+import 'dart:async';
+
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+
+
+
+class FrameInfo extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(40, 0)
+  ];
+  int frameTime = 0;
+  int frameInterval = 0;
+  int frameDeadline = 0;
+  int presentationTime = 0;
+
+  FrameInfo() : super(kVersions.last.size);
+
+  static FrameInfo deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static FrameInfo decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    FrameInfo result = new FrameInfo();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.frameTime = decoder0.decodeInt64(8);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.frameInterval = decoder0.decodeUint64(16);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.frameDeadline = decoder0.decodeInt64(24);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.presentationTime = decoder0.decodeInt64(32);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeInt64(frameTime, 8);
+    
+    encoder0.encodeUint64(frameInterval, 16);
+    
+    encoder0.encodeInt64(frameDeadline, 24);
+    
+    encoder0.encodeInt64(presentationTime, 32);
+  }
+
+  String toString() {
+    return "FrameInfo("
+           "frameTime: $frameTime" ", "
+           "frameInterval: $frameInterval" ", "
+           "frameDeadline: $frameDeadline" ", "
+           "presentationTime: $presentationTime" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["frameTime"] = frameTime;
+    map["frameInterval"] = frameInterval;
+    map["frameDeadline"] = frameDeadline;
+    map["presentationTime"] = presentationTime;
+    return map;
+  }
+}
+
+
+class _SceneSchedulerScheduleFrameParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(8, 0)
+  ];
+
+  _SceneSchedulerScheduleFrameParams() : super(kVersions.last.size);
+
+  static _SceneSchedulerScheduleFrameParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _SceneSchedulerScheduleFrameParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _SceneSchedulerScheduleFrameParams result = new _SceneSchedulerScheduleFrameParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    encoder.getStructEncoderAtOffset(kVersions.last);
+  }
+
+  String toString() {
+    return "_SceneSchedulerScheduleFrameParams("")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    return map;
+  }
+}
+
+
+class SceneSchedulerScheduleFrameResponseParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  FrameInfo frameInfo = null;
+
+  SceneSchedulerScheduleFrameResponseParams() : super(kVersions.last.size);
+
+  static SceneSchedulerScheduleFrameResponseParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static SceneSchedulerScheduleFrameResponseParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    SceneSchedulerScheduleFrameResponseParams result = new SceneSchedulerScheduleFrameResponseParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.frameInfo = FrameInfo.decode(decoder1);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(frameInfo, 8, false);
+  }
+
+  String toString() {
+    return "SceneSchedulerScheduleFrameResponseParams("
+           "frameInfo: $frameInfo" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["frameInfo"] = frameInfo;
+    return map;
+  }
+}
+
+const int _SceneScheduler_scheduleFrameName = 0;
+
+abstract class SceneScheduler {
+  static const String serviceName = null;
+  dynamic scheduleFrame([Function responseFactory = null]);
+}
+
+
+class _SceneSchedulerProxyImpl extends bindings.Proxy {
+  _SceneSchedulerProxyImpl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
+
+  _SceneSchedulerProxyImpl.fromHandle(core.MojoHandle handle) :
+      super.fromHandle(handle);
+
+  _SceneSchedulerProxyImpl.unbound() : super.unbound();
+
+  static _SceneSchedulerProxyImpl newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For _SceneSchedulerProxyImpl"));
+    return new _SceneSchedulerProxyImpl.fromEndpoint(endpoint);
+  }
+
+  void handleResponse(bindings.ServiceMessage message) {
+    switch (message.header.type) {
+      case _SceneScheduler_scheduleFrameName:
+        var r = SceneSchedulerScheduleFrameResponseParams.deserialize(
+            message.payload);
+        if (!message.header.hasRequestId) {
+          proxyError("Expected a message with a valid request Id.");
+          return;
+        }
+        Completer c = completerMap[message.header.requestId];
+        if (c == null) {
+          proxyError(
+              "Message had unknown request Id: ${message.header.requestId}");
+          return;
+        }
+        completerMap.remove(message.header.requestId);
+        if (c.isCompleted) {
+          proxyError("Response completer already completed");
+          return;
+        }
+        c.complete(r);
+        break;
+      default:
+        proxyError("Unexpected message type: ${message.header.type}");
+        close(immediate: true);
+        break;
+    }
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "_SceneSchedulerProxyImpl($superString)";
+  }
+}
+
+
+class _SceneSchedulerProxyCalls implements SceneScheduler {
+  _SceneSchedulerProxyImpl _proxyImpl;
+
+  _SceneSchedulerProxyCalls(this._proxyImpl);
+    dynamic scheduleFrame([Function responseFactory = null]) {
+      var params = new _SceneSchedulerScheduleFrameParams();
+      return _proxyImpl.sendMessageWithRequestId(
+          params,
+          _SceneScheduler_scheduleFrameName,
+          -1,
+          bindings.MessageHeader.kMessageExpectsResponse);
+    }
+}
+
+
+class SceneSchedulerProxy implements bindings.ProxyBase {
+  final bindings.Proxy impl;
+  SceneScheduler ptr;
+
+  SceneSchedulerProxy(_SceneSchedulerProxyImpl proxyImpl) :
+      impl = proxyImpl,
+      ptr = new _SceneSchedulerProxyCalls(proxyImpl);
+
+  SceneSchedulerProxy.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) :
+      impl = new _SceneSchedulerProxyImpl.fromEndpoint(endpoint) {
+    ptr = new _SceneSchedulerProxyCalls(impl);
+  }
+
+  SceneSchedulerProxy.fromHandle(core.MojoHandle handle) :
+      impl = new _SceneSchedulerProxyImpl.fromHandle(handle) {
+    ptr = new _SceneSchedulerProxyCalls(impl);
+  }
+
+  SceneSchedulerProxy.unbound() :
+      impl = new _SceneSchedulerProxyImpl.unbound() {
+    ptr = new _SceneSchedulerProxyCalls(impl);
+  }
+
+  factory SceneSchedulerProxy.connectToService(
+      bindings.ServiceConnector s, String url, [String serviceName]) {
+    SceneSchedulerProxy p = new SceneSchedulerProxy.unbound();
+    s.connectToService(url, p, serviceName);
+    return p;
+  }
+
+  static SceneSchedulerProxy newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneSchedulerProxy"));
+    return new SceneSchedulerProxy.fromEndpoint(endpoint);
+  }
+
+  String get serviceName => SceneScheduler.serviceName;
+
+  Future close({bool immediate: false}) => impl.close(immediate: immediate);
+
+  Future responseOrError(Future f) => impl.responseOrError(f);
+
+  Future get errorFuture => impl.errorFuture;
+
+  int get version => impl.version;
+
+  Future<int> queryVersion() => impl.queryVersion();
+
+  void requireVersion(int requiredVersion) {
+    impl.requireVersion(requiredVersion);
+  }
+
+  String toString() {
+    return "SceneSchedulerProxy($impl)";
+  }
+}
+
+
+class SceneSchedulerStub extends bindings.Stub {
+  SceneScheduler _impl = null;
+
+  SceneSchedulerStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [this._impl])
+      : super.fromEndpoint(endpoint);
+
+  SceneSchedulerStub.fromHandle(core.MojoHandle handle, [this._impl])
+      : super.fromHandle(handle);
+
+  SceneSchedulerStub.unbound() : super.unbound();
+
+  static SceneSchedulerStub newFromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint) {
+    assert(endpoint.setDescription("For SceneSchedulerStub"));
+    return new SceneSchedulerStub.fromEndpoint(endpoint);
+  }
+
+
+  SceneSchedulerScheduleFrameResponseParams _SceneSchedulerScheduleFrameResponseParamsFactory(FrameInfo frameInfo) {
+    var mojo_factory_result = new SceneSchedulerScheduleFrameResponseParams();
+    mojo_factory_result.frameInfo = frameInfo;
+    return mojo_factory_result;
+  }
+
+  dynamic handleMessage(bindings.ServiceMessage message) {
+    if (bindings.ControlMessageHandler.isControlMessage(message)) {
+      return bindings.ControlMessageHandler.handleMessage(this,
+                                                          0,
+                                                          message);
+    }
+    assert(_impl != null);
+    switch (message.header.type) {
+      case _SceneScheduler_scheduleFrameName:
+        var params = _SceneSchedulerScheduleFrameParams.deserialize(
+            message.payload);
+        var response = _impl.scheduleFrame(_SceneSchedulerScheduleFrameResponseParamsFactory);
+        if (response is Future) {
+          return response.then((response) {
+            if (response != null) {
+              return buildResponseWithId(
+                  response,
+                  _SceneScheduler_scheduleFrameName,
+                  message.header.requestId,
+                  bindings.MessageHeader.kMessageIsResponse);
+            }
+          });
+        } else if (response != null) {
+          return buildResponseWithId(
+              response,
+              _SceneScheduler_scheduleFrameName,
+              message.header.requestId,
+              bindings.MessageHeader.kMessageIsResponse);
+        }
+        break;
+      default:
+        throw new bindings.MojoCodecError("Unexpected message name");
+        break;
+    }
+    return null;
+  }
+
+  SceneScheduler get impl => _impl;
+  set impl(SceneScheduler d) {
+    assert(_impl == null);
+    _impl = d;
+  }
+
+  String toString() {
+    var superString = super.toString();
+    return "SceneSchedulerStub($superString)";
+  }
+
+  int get version => 0;
+}
+
+
diff --git a/mojo/services/gfx/composition/cpp/BUILD.gn b/mojo/services/gfx/composition/cpp/BUILD.gn
new file mode 100644
index 0000000..8be2931
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/BUILD.gn
@@ -0,0 +1,20 @@
+# 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.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/mojo_sdk.gni")
+
+mojo_sdk_source_set("cpp") {
+  restrict_external_deps = false
+  public_configs = [ "../../../public/build/config:mojo_services" ]
+  sources = [
+    "formatting.cc",
+    "formatting.h",
+  ]
+
+  deps = [
+    "../../../geometry/cpp",
+    "../interfaces",
+  ]
+}
diff --git a/mojo/services/gfx/composition/cpp/formatting.cc b/mojo/services/gfx/composition/cpp/formatting.cc
new file mode 100644
index 0000000..6aefebe
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/formatting.cc
@@ -0,0 +1,184 @@
+// 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 "mojo/services/gfx/composition/cpp/formatting.h"
+
+#include <ostream>
+
+namespace mojo {
+namespace gfx {
+namespace composition {
+
+class Delimiter {
+ public:
+  Delimiter(std::ostream& os) : os_(os) {}
+
+  std::ostream& Append() {
+    if (need_comma_)
+      os_ << ", ";
+    else
+      need_comma_ = true;
+    return os_;
+  }
+
+ private:
+  std::ostream& os_;
+  bool need_comma_ = false;
+};
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneToken& value) {
+  return os << "{value=" << value.value << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneUpdate& value) {
+  os << "{";
+  Delimiter d(os);
+  if (value.clear_resources) {
+    d.Append() << "clear_resources=true";
+  }
+  if (value.clear_nodes) {
+    d.Append() << "clear_nodes=true";
+  }
+  if (value.resources) {
+    d.Append() << "resources=" << value.resources;
+  }
+  if (value.nodes) {
+    d.Append() << "nodes=" << value.nodes;
+  }
+  os << "}";
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneMetadata& value) {
+  return os << "{version=" << value.version
+            << ", presentation_time=" << value.presentation_time << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Resource& value) {
+  os << "{";
+  if (value.is_scene()) {
+    os << "scene=" << value.get_scene();
+  } else if (value.is_mailbox_texture()) {
+    os << "mailbox_texture=" << value.get_mailbox_texture();
+  } else {
+    os << "???";
+  }
+  return os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneResource& value) {
+  return os << "{scene_token=" << value.scene_token << "}";
+}
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const mojo::gfx::composition::MailboxTextureResource& value) {
+  return os << "{sync_point=" << value.sync_point << ", size=" << value.size
+            << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Node& value) {
+  os << "{";
+  Delimiter d(os);
+  if (value.content_transform)
+    d.Append() << "content_transform=" << value.content_transform;
+  if (value.content_clip)
+    d.Append() << "content_clip=" << value.content_clip;
+  if (value.hit_id != mojo::gfx::composition::kHitIdNone)
+    d.Append() << "hit_id=" << value.hit_id;
+  if (value.op)
+    d.Append() << "op=" << value.op;
+  d.Append() << "combinator=" << &value.combinator;
+  if (value.child_node_ids)
+    d.Append() << "child_node_ids=" << value.child_node_ids;
+  return os << "}";
+}
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const mojo::gfx::composition::Node::Combinator* value) {
+  switch (*value) {
+    case mojo::gfx::composition::Node::Combinator::MERGE:
+      return os << "MERGE";
+    case mojo::gfx::composition::Node::Combinator::PRUNE:
+      return os << "PRUNE";
+    case mojo::gfx::composition::Node::Combinator::FALLBACK:
+      return os << "FALLBACK";
+    default:
+      return os << "???";
+  }
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::NodeOp& value) {
+  os << "{";
+  if (value.is_rect()) {
+    os << "rect=" << value.get_rect();
+  } else if (value.is_image()) {
+    os << "image=" << value.get_image();
+  } else if (value.is_scene()) {
+    os << "scene=" << value.get_scene();
+  } else if (value.is_layer()) {
+    os << "layer=" << value.get_layer();
+  } else {
+    os << "???";
+  }
+  return os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::RectNodeOp& value) {
+  return os << "{content_rect=" << value.content_rect
+            << ", color=" << value.color << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::ImageNodeOp& value) {
+  return os << "{content_rect=" << value.content_rect
+            << ", image_rect=" << value.image_rect
+            << ", image_resource_id=" << value.image_resource_id
+            << ", blend=" << value.blend << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneNodeOp& value) {
+  return os << "{scene_resource_id=" << value.scene_resource_id
+            << ", scene_version=" << value.scene_version << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::LayerNodeOp& value) {
+  return os << "{layer_size=" << value.layer_size << ", blend=" << value.blend
+            << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Color& value) {
+  return os << "{red=" << static_cast<int>(value.red)
+            << ", green=" << static_cast<int>(value.green)
+            << ", blue=" << static_cast<int>(value.blue)
+            << ", alpha=" << static_cast<int>(value.alpha) << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Blend& value) {
+  return os << "{alpha=" << static_cast<int>(value.alpha) << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::FrameInfo& value) {
+  return os << "{frame_time=" << value.frame_time
+            << ", frame_interval=" << value.frame_interval
+            << ", frame_deadline=" << value.frame_deadline << "}";
+}
+
+}  // namespace composition
+}  // namespace gfx
+}  // namespace mojo
diff --git a/mojo/services/gfx/composition/cpp/formatting.h b/mojo/services/gfx/composition/cpp/formatting.h
new file mode 100644
index 0000000..0f4df77
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/formatting.h
@@ -0,0 +1,64 @@
+// 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 MOJO_SERVICES_GFX_COMPOSITION_CPP_FORMATTING_H_
+#define MOJO_SERVICES_GFX_COMPOSITION_CPP_FORMATTING_H_
+
+#include "mojo/public/cpp/bindings/formatting.h"
+#include "mojo/services/geometry/cpp/formatting.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace mojo {
+namespace gfx {
+namespace composition {
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneToken& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneUpdate& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneMetadata& value);
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Resource& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneResource& value);
+std::ostream& operator<<(
+    std::ostream& os,
+    const mojo::gfx::composition::MailboxTextureResource& value);
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Node& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::NodeOp& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::RectNodeOp& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::ImageNodeOp& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::SceneNodeOp& value);
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::LayerNodeOp& value);
+
+// FIXME(jeffbrown): Passing as a pointer to disambiguate with the operator<<
+// generated by mojom itself for this enum, which unfortunately doesn't print
+// names.  We should improve mojom then get rid of this overload.
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Node::Combinator* value);
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Color& value);
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::Blend& value);
+
+std::ostream& operator<<(std::ostream& os,
+                         const mojo::gfx::composition::FrameInfo& value);
+
+}  // namespace composition
+}  // namespace gfx
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_GFX_COMPOSITION_CPP_FORMATTING_H_
diff --git a/mojo/services/gfx/composition/interfaces/BUILD.gn b/mojo/services/gfx/composition/interfaces/BUILD.gn
new file mode 100644
index 0000000..3fae5c7
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/BUILD.gn
@@ -0,0 +1,26 @@
+# 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.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+  sources = [
+    "compositor.mojom",
+    "hit_tests.mojom",
+    "nodes.mojom",
+    "renderers.mojom",
+    "resources.mojom",
+    "scene_token.mojom",
+    "scenes.mojom",
+    "scheduling.mojom",
+  ]
+
+  import_dirs = [ get_path_info("../../../", "abspath") ]
+
+  deps = [
+    "../../../geometry/interfaces",
+    "../../../gpu/interfaces",
+  ]
+}
diff --git a/mojo/services/gfx/composition/interfaces/compositor.mojom b/mojo/services/gfx/composition/interfaces/compositor.mojom
new file mode 100644
index 0000000..fb1bd69
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/compositor.mojom
@@ -0,0 +1,57 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/geometry/interfaces/geometry.mojom";
+import "mojo/services/gpu/interfaces/context_provider.mojom";
+import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+import "mojo/services/gfx/composition/interfaces/scenes.mojom";
+import "mojo/services/gfx/composition/interfaces/renderers.mojom";
+
+// Maximum length for a scene or renderer label.
+const uint32 kLabelMaxLength = 32;
+
+// The compositor manages scenes and scene graph renderers.
+//
+// Applications create scenes to describe graphical content they would like
+// to render, including references to other scenes they would like to compose.
+//
+// The system creates a renderer to bind a scene graph to a particular display.
+//
+// Refer to |Scene| and |Renderer| for more information about these objects.
+[ServiceName="mojo::gfx::composition::Compositor"]
+interface Compositor {
+  // Creates a scene.
+  //
+  // The |scene| is used to supply content for the scene.  The scene pipe
+  // is private to the scene and should not be shared with anyone else.
+  //
+  // The |scene_token| is used as a transferable reference which can be passed
+  // to owners of other scenes to allow them to embed this scene as a
+  // resource.  The compositor itself does not describe how this interaction
+  // should take place, only that the token may eventually be used to
+  // construct a |SceneResource|.
+  //
+  // The |label| is an optional name to associate with the view for
+  // diagnostic purposes.  The label will be truncated if it is longer
+  // than |kLabelMaxLength|.
+  //
+  // To unregister the scene, simply close the |scene| message pipe.
+  CreateScene(Scene& scene, string? label) => (SceneToken scene_token);
+
+  // Creates a scene graph renderer.
+  //
+  // The |context_provider| provides the GL Context to which the content
+  // should be rendered.  This will typically be a display.
+  //
+  // The |label| is an optional name to associate with the renderer for
+  // diagnostic purposes.  The label will be truncated if it is longer
+  // than |kLabelMaxLength|.
+  //
+  // To destroy the renderer, simply close the |renderer| message pipe.
+  CreateRenderer(mojo.ContextProvider context_provider,
+                 Renderer& renderer, string? label);
+};
diff --git a/mojo/services/gfx/composition/interfaces/hit_tests.mojom b/mojo/services/gfx/composition/interfaces/hit_tests.mojom
new file mode 100644
index 0000000..d18d868
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/hit_tests.mojom
@@ -0,0 +1,49 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/geometry/interfaces/geometry.mojom";
+import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+
+// Indicates that hit testing is not needed for a given node.
+const uint32 kHitIdNone = 0;
+
+// Provides information about the point of intersection of a hit test with
+// a node in a scene graph.
+struct Hit {
+  // The scene token of the scene which was hit.
+  SceneToken scene_token;
+
+  // The version of the scene which was consulted as part of evaluating the
+  // hit test.
+  uint32 scene_version;
+
+  // The node id of the node which was hit.
+  uint32 node_id;
+
+  // The hit test id of the node which was hit.
+  uint32 hit_id;
+
+  // The coordinates of the hit within the node's content space.
+  mojo.Point intersection;
+};
+
+// The result of a hit test operation.
+struct HitTestResult {
+  // A sorted list of hits in dispatch order from the top-most hit node
+  // to the nodes underneath it and containing it, or an empty array if none.
+  // Omits nodes for which the hit_id was |kHitIdNone|.
+  array<Hit> hits;
+};
+
+// A hit testing service for scene graphs.
+interface HitTester {
+  // Performs a hit test on the specified point.
+  //
+  // TODO(jeffbrown): Specify a timestamp to allow for hit-tests of geometry
+  // as it appeared in the recent past.
+  HitTest(mojo.Point point) => (HitTestResult result);
+};
diff --git a/mojo/services/gfx/composition/interfaces/nodes.mojom b/mojo/services/gfx/composition/interfaces/nodes.mojom
new file mode 100644
index 0000000..dc773d7
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/nodes.mojom
@@ -0,0 +1,217 @@
+// 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.
+
+// Primitives express the geometry of the scene, such as quads and references
+// to embedded scenes.  Primitives are arranged hierarchically as nodes,
+// each with an associated transformation matrix.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/geometry/interfaces/geometry.mojom";
+import "mojo/services/gfx/composition/interfaces/hit_tests.mojom";
+
+// Nodes express the geometry and content of the scene, such as images and
+// 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
+// root, the compositor applies the transformation, clip, recursively
+// processes the node's children according to the node's combinator rule,
+// then applies the node's own operation.
+//
+// BLOCKED NODES
+//
+// Due to the asynchronous nature of the system, it may happen that some
+// nodes cannot be processed immediately at drawing time because they require
+// access to certain resources which are not available, such as a specific
+// version of a scene which has yet to be produced by some other application.
+//
+// When a node cannot be drawn due to an unsatisfied dependency, it is
+// said to be "blocked".  Blocked nodes prevent rendering of the entire
+// subgraph below them.
+//
+// NODE COMBINATORS
+//
+// Node combinator rules describe what should happen when a node which is
+// otherwise unblocked has one or more blocked children.
+//
+// With the |MERGE| combinator, the children of a node are all drawn in
+// sequence if none of them are blocked, otherwise the node itself is
+// blocked.  This is the default.
+//
+// With the |PRUNE| combinator, the children of a node are all drawn in
+// sequence while skipping over any of them which are blocked.  Blocked
+// children will not appear in the output.
+//
+// With the |FALLBACK| combinator, the first unblocked child of a node is
+// drawn and the remaining nodes are ignored.  If the node has children
+// and all of them are blocked then the node itself is blocked.
+//
+// Combinators make it possible to express complex rules such as substituting
+// missing content for an earlier version of that content or for a placeholder
+// if not available.
+//
+// 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
+//    occur, such as when embedding scenes produced by other applications.
+//    Provide alternate content where possible.
+//
+struct Node {
+  // The combinator specifies how child nodes are processed.
+  enum Combinator {
+    // All children are drawn in sequence, blocking if any are blocked.
+    MERGE,
+    // All children are drawn in sequence, skipping any that are blocked.
+    PRUNE,
+    // The first unblocked node is drawn, blocking if there are children
+    // and all of them are blocked.
+    FALLBACK,
+  };
+
+  // The forward transformation from the node's content space to its
+  // containing node's content space.  If null, an identity transformation
+  // is assumed.
+  //
+  // For example, if you want to translate the content of the node so that
+  // it is drawn at X = 100 relative to its containing node's origin, simply
+  // set a transformation matrix with the X translation component equal to 100.
+  // Take care not to specify the inverse transform by mistake.
+  mojo.Transform? content_transform;
+
+  // The clip rectangle to apply to this node's content and to its children
+  // in content space in addition to any clipping performed by the container.
+  // If null, the node does not apply any clipping of its own.
+  mojo.Rect? content_clip;
+
+  // The hit test id to report if anything within the node is hit.
+  // Use |kHitIdNone| if the node should not be hit tested.
+  //
+  // TODO(jeffbrown): This scheme is probably too naive.
+  uint32 hit_id = kHitIdNone;
+
+  // The Combinator to apply when processing the children of this node.
+  Combinator combinator = Combinator.MERGE;
+
+  // 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.
+  array<uint32>? child_node_ids;
+
+  // The drawing operation to apply when processing this node.
+  // If null, no drawing operation occurs at this node.
+  NodeOp? op;
+};
+
+// A drawing operation to apply when processing the node.
+union NodeOp {
+  RectNodeOp rect;
+  ImageNodeOp image;
+  SceneNodeOp scene;
+  LayerNodeOp layer;
+  // TODO(jeffbrown): Color filters.
+};
+
+// Fills a rectangle with a solid color.
+struct RectNodeOp {
+  // The rectangle to fill in content space.
+  mojo.Rect content_rect;
+
+  // The rectangle's color.
+  Color color;
+};
+
+// Draws an image at the specified location.
+//
+// The node containing this operation will be blocked if the image resource
+// is not ready for use at draw time.
+struct ImageNodeOp {
+  // The rectangle in which to draw the image in content space.
+  mojo.Rect content_rect;
+
+  // The portion of the image to draw.
+  // If null, draws the entire image.
+  mojo.Rect? image_rect;
+
+  // The resource id of a valid |MailboxTextureResource| to draw.
+  // It is an error to specify a resource id that does not refer to an image
+  // resource; the compositor will close the connection when the scene
+  // is published.
+  uint32 image_resource_id;
+
+  // The blending parameters.  If null, uses the default values specified
+  // in the |Blend| structure declaration.
+  Blend? blend;
+};
+
+// Draws a scene.
+//
+// A scene operation embeds another scene at this point in the scene graph.
+// It has essentially the same effect as drawing the root node of the
+// referenced scene and drawing it as if it were a child of this node.
+//
+// The node containing this operation will be blocked if the specified
+// version of the scene is not ready for use at draw time or if it too
+// is blocked.
+//
+// It is often useful to wrap this node with a |LayerNodeOp| when blending
+// the scene with other content.
+struct SceneNodeOp {
+  // The resource id of a valid |SceneResource| to link into the scene.
+  // 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.
+  uint32 scene_resource_id;
+
+  // The version of the scene that we would like to reference.
+  // Use |kSceneVersionNone| to request the most recently published
+  // version of the scene if synchronization is unimportant.
+  uint32 scene_version = 0; // kSceneVersionNone
+};
+
+// Draws a layer.
+//
+// Conceptually, this operation has the effect of drawing the children of
+// the node to a temporary buffer of the specified size which is then
+// composited in place like an image.  This is useful for ensuring
+// correct blending of layered content.
+struct LayerNodeOp {
+  // The size of the layer to create.
+  mojo.Size layer_size;
+
+  // The blending parameters.  If null, uses the default values specified
+  // in the |Blend| structure declaration.
+  Blend? blend;
+};
+
+// Specifies a color to draw.
+// TODO(jeffbrown): This is silly but unambiguous for prototyping.
+// Make it less silly.
+struct Color {
+  uint8 red;
+  uint8 green;
+  uint8 blue;
+  uint8 alpha;
+};
+
+// Specifies how blending should take place.
+struct Blend {
+  // The opacity for composition in a range from 0 (fully transparent)
+  // to 255 (fully opaque).
+  uint8 alpha = 255;
+
+  // TODO(jeffbrown): Blend modes and texture filtering.
+};
diff --git a/mojo/services/gfx/composition/interfaces/renderers.mojom b/mojo/services/gfx/composition/interfaces/renderers.mojom
new file mode 100644
index 0000000..3d9eb47
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/renderers.mojom
@@ -0,0 +1,38 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/geometry/interfaces/geometry.mojom";
+import "mojo/services/gfx/composition/interfaces/hit_tests.mojom";
+import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+
+// The renderer is a service which renders a scene graph to a display.
+//
+// Use |Compositor.CreateRenderer()| to create a renderer.
+interface Renderer {
+  // Sets the scene to be displayed by the renderer.
+  //
+  // The |size| specifies the size of the output.
+  //
+  // The |scene_token| specifies the scene which will be drawn as the
+  // root of the scene graph.
+  //
+  // The |scene_version| specifies the version of the scene to render,
+  // Use |kSceneVersionNone| to request the most recently published
+  // version of the scene if synchronization is unimportant.
+  //
+  // The |viewport| specifies the portion of the scene to render.
+  //
+  // It is an error to create a renderer with an invalid |scene_token|
+  // or |size|; the connection will be closed.
+  // TODO: make this an event instead
+  SetRootScene(SceneToken scene_token, uint32 scene_version,
+    mojo.Rect viewport);
+
+  // Provides an interface which can be used to perform hit tests on the
+  // contents of the renderer's scene graph.
+  GetHitTester(HitTester& hit_tester);
+};
diff --git a/mojo/services/gfx/composition/interfaces/resources.mojom b/mojo/services/gfx/composition/interfaces/resources.mojom
new file mode 100644
index 0000000..f2f9a52
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/resources.mojom
@@ -0,0 +1,62 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/geometry/interfaces/geometry.mojom";
+import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+
+// Resources are references to foreign objects which are being imported into
+// the scene graph, such as images and other scenes.  Each resource must be
+// given a locally unique identifier when it is bound to the scene.
+union Resource {
+  SceneResource scene;
+  MailboxTextureResource mailbox_texture;
+};
+
+// Scene resource declaration.
+//
+// Binds a resource id to a scene given its scene token.
+//
+// If the scene referenced by the token becomes unavailable, the
+// |Scene.OnResourceUnavailable| event will be sent.
+struct SceneResource {
+  // The scene token of the referenced scene.
+  SceneToken scene_token;
+};
+
+// Mailbox texture resource declaration.
+//
+// Binds a resource id to a texture transferred via a GL mailbox.
+// Assumes the format is RGBA_8888 and the target is GL_TEXTURE_2D.
+//
+// If the texture referenced by the token becomes unavailable, the
+// |Scene.OnResourceUnavailable| event will be sent.
+//
+// TODO(jeffbrown): Replace this with Image and ImagePipe.
+struct MailboxTextureResource {
+  // The name of the mailbox, as produced by glGenMailboxCHROMIUM().
+  array<uint8, 64> mailbox_name;
+
+  // The sync point which indicates completion of texture rendering, as
+  // produced by glInsertSyncPointCHROMIUM().
+  uint32 sync_point;
+
+  // The size of the texture in pixels.
+  mojo.Size size;
+
+  // The callback interface to be notified when it is safe to release or
+  // recycle the underlying texture storage once the resource has been
+  // removed from the scene.
+  MailboxTextureCallback callback;
+};
+
+// Release callback for MailboxTextureResources.
+interface MailboxTextureCallback {
+  // Called some time after a mailbox texture is removed from the scene
+  // to indicate that it is now safe to release or recycle the underlying
+  // texture storage.
+  OnMailboxTextureReleased();
+};
diff --git a/mojo/services/gfx/composition/interfaces/scene_token.mojom b/mojo/services/gfx/composition/interfaces/scene_token.mojom
new file mode 100644
index 0000000..0213f6b
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/scene_token.mojom
@@ -0,0 +1,20 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+// A scene token is an opaque transferable reference to a scene.
+//
+// Each scene is associated with a unique scene token at creation time.
+// This token can be used to create references from one scene to another
+// or to create a renderer for a scene graph.
+//
+// Scene tokens should be kept secret.
+//
+// TODO(jeffbrown): This implementation is a temporary placeholder until
+// we extend Mojo to provide a way to create tokens which cannot be forged.
+struct SceneToken {
+  uint32 value;
+};
diff --git a/mojo/services/gfx/composition/interfaces/scenes.mojom b/mojo/services/gfx/composition/interfaces/scenes.mojom
new file mode 100644
index 0000000..759ee72
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/scenes.mojom
@@ -0,0 +1,213 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+import "mojo/services/gfx/composition/interfaces/nodes.mojom";
+import "mojo/services/gfx/composition/interfaces/resources.mojom";
+import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+import "mojo/services/gfx/composition/interfaces/scheduling.mojom";
+
+// The node id which represents the root of a scene.
+//
+// Every scene should have a root node which uses this id unless the scene
+// has no content at all.
+const uint32 kSceneRootNodeId = 0;
+
+// The scene version used as a placeholder when the version doesn't matter.
+//
+// This version code can be used by a consumer of a scene to express that
+// it would like to consume the latest version of the scene.
+//
+// This version code can also be used by a scene itself if it has not
+// received any other specific indications as to the version it should
+// report when publishing its content.
+const uint32 kSceneVersionNone = 0;
+
+// A scene component maintains a description of some graphical content which
+// an application would like to render.  This description consists of
+// resources and nodes.
+//
+// Use |Compositor.RegisterScene()| to register a scene.  The application
+// uses the |Scene| interface to manage the scene's content and implements
+// the |SceneListener| interface to handle events.
+//
+// CONTENT
+//
+// Resources are references to foreign objects which are being imported into
+// the scene graph, such as images and other scenes.  Each resource must be
+// given a locally unique identifier when it is bound to the scene.
+//
+// Nodes express the geometry and content of the scene, such as images and
+// references to embedded scenes.  Nodes are arranged to form a directed
+// acyclic graph of drawing commands.
+//
+// The root node of the scene has the special id of |kSceneRootNodeId|.
+//
+// ATOMICITY
+//
+// To ensure atomicity of updates, the evolution of a scene occurs in two
+// phases: update and publish.  Incremental changes to the scene made during
+// the update phase are not observed until published.
+//
+// - Update phase: To construct the next state of a scene, the scene owner
+//   issues any number of scene update requests to add, update, or remove
+//   resources and nodes.
+//
+// - Publish phase: When the next state of a scene is fully constructed, the
+//   scene owner issues a request to publish its changes to the scene and
+//   assigns it a version identifier.  The version identifer may be used
+//   in scene references to coordinate updates across multiple scenes which
+//   must be synchronized.
+//
+// Once the scene has been published, the scene owner may begin to construct
+// the next state without disrupting the state just published.
+//
+// SYNCHRONIZATION
+//
+// To ensure synchronization of updates across scenes, some additional work
+// is needed.
+//
+// For example, consider the case of a scene X which embeds two other scenes
+// A and B.  If X changes size, it may need to resize A and B as well to
+// fit properly within its layout.
+//
+// To do so, X sends the owners of A and B each a message indicating the
+// new size it desires along with new scene version identifiers V(A) and
+// V(B) which the owners of A and B must use when they publish the new scene
+// state which incorporates the requested size change.
+//
+// Meanwhile, X produces a new scene of its own taking into account its
+// new size.  As part of constructing this new scene, it binds to the V(A)
+// and V(B) versions of A and B to express its dependence on A and B
+// having been updated to the new size.
+//
+// Propagating version information top-down through the scene graph in
+// this manner allows for state changes to be synchronized in a purely
+// feed-forward manner.
+//
+// CONCURRENCY
+//
+// In the above synchronization example, version numbers were used to
+// couple the state of one scene with others that it depends on.
+// What should happen if these dependencies are not satisfied promptly?
+//
+// Suppose X and A quickly produce new scenes with the new size but
+// B is taking longer than anticipated to do so.  We have a few choices:
+//
+// - Stall: The compositor could continue to show the old state of X, A, and B
+//   until such time as all three have published their new versions.
+//
+// - Recover: The compositor could realize that X and A are already up to date
+//   but B is lagging and choose to apply a transformation to B's old content
+//   cover for the lag, such as by scaling, clipping, masking, or discarding
+//   B's old content.
+//
+// - Speculate: The compositor could allow X to provide alternate descriptions
+//   of the scene in the case where A or B are not keeping up so that it
+//   can more finely control the recovery policy.
+//
+// To maximize concurrency, we adopt the latter approach.
+interface Scene {
+  // Sets the listener for receiving events from the scene.
+  //
+  // If |listener| is null, then the previously set listener is removed.
+  SetListener(SceneListener? listener);
+
+  // Applies a batch of incremental updates to the contents of the scene.
+  //
+  // Incremental changes can be split across any number of updates but it is
+  // more efficient to process them in batches.  Updates have no visible
+  // effect until the scene owner calls |Publish()| to publish them.
+  //
+  // Updates are buffered and applied when |Publish()| is called.  This means
+  // it is safe to issue an update which adds a node that refers to a resource
+  // which is not added until a subsequent update as long as the scene is
+  // not published in between those updates.
+  Update(SceneUpdate update);
+
+  // Publishes the scene and tags it with the specified version.
+  //
+  // All pending incremental updates are applied atomically when the scene
+  // is published and the new state is labeled with the version indicated
+  // in the associated metadata.
+  //
+  // The |metadata| describes how the published scene should be presented.
+  // If null, this method behaves as if a |SceneMetadata| object initialized
+  // with default values had been supplied.
+  //
+  // 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.
+  Publish(SceneMetadata? metadata);
+
+  // Gets a scheduler for scheduling upcoming scene operations.
+  GetScheduler(SceneScheduler& scheduler);
+};
+
+// An interface applications may implement to receive events from a scene.
+interface SceneListener {
+  // Called when a resource has become unavailable.
+  //
+  // Any nodes which are using the resource will be considered blocked
+  // until the resource is replaced or the nodes using it are removed.
+  OnResourceUnavailable(uint32 resource_id) => ();
+};
+
+// A batch of incremental changes to be applied to a scene.
+struct SceneUpdate {
+  // If true, clears all resources before processing the update.
+  bool clear_resources = false;
+
+  // If true, clears all nodes before processing the update.
+  bool clear_nodes = false;
+
+  // Resources to be added, updated, or removed, indexed by resource id.
+  // The value replaces the definition of the resource with the given id.
+  // If the value is null, then the resource is removed from the scene.
+  map<uint32, Resource?>? resources;
+
+  // Nodes to be added, updated, or removed, indexed by node id.
+  // The value replaces the definition of the node with the given id.
+  // If the value is null, then the node is removed from the scene.
+  //
+  // The root node must have an id of 0.
+  map<uint32, Node?>? nodes;
+};
+
+// Describes how a published scene should be presented.
+struct SceneMetadata {
+  // The scene's new version number.
+  //
+  // Version numbers are usually associated with requests to change the
+  // state of the scene.  For example, the embedder of this scene may send
+  // it a request to resize its content or change its color and provide a
+  // new version number that this scene should apply when it publishes the
+  // first update which addresses this request.
+  //
+  // In general, the version number will be provided to the owner of the
+  // scene by some other party which is in change of synchronizing updates
+  // across multiple scenes including this one.  The other party will
+  // frequently be the owner of some other scene which embeds this one.
+  //
+  // In certain cases there might not be a version number provided for
+  // synchronization.  When this happens, set |version| to |kSceneVersionNone|.
+  //
+  // Note that version numbers are unordered; there is no need to increment
+  // them monotonically.  Values may also be reused at will.
+  uint32 version = 0; // kSceneVersionNone
+
+  // A timestamp indicating approximately when the published contents of the
+  // scene are to be shown on the display output assuming everything is
+  // fully rendered and submitted by the appropriate deadline.
+  //
+  // Expressed in microseconds in the |MojoTimeTicks| timebase.
+  //
+  // A value of 0 means "as soon as possible".
+  //
+  // See also: |FrameInfo.presentation_time|.
+  int64 presentation_time = 0;
+};
diff --git a/mojo/services/gfx/composition/interfaces/scheduling.mojom b/mojo/services/gfx/composition/interfaces/scheduling.mojom
new file mode 100644
index 0000000..4399a91
--- /dev/null
+++ b/mojo/services/gfx/composition/interfaces/scheduling.mojom
@@ -0,0 +1,89 @@
+// 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.
+
+[DartPackage="mojo_services"]
+module mojo.gfx.composition;
+
+// Provides support for scheduling drawing and composition operations
+// for a particular scene.
+//
+// Instances of this interface must be obtained from a |Scene|.  This interface
+// is modeled separately so as to allow applications to use different threads
+// for scheduling work as opposed to publishing scene updates.
+interface SceneScheduler {
+  // Asks the compositor to invoke the callback when it is a good time to
+  // draw the next frame.
+  //
+  // The rate at which the callback is invoked may depend on how the scene
+  // has been embedded.  The scene will only receive frame callbacks while
+  // it is attached to a scene graph which the compositor has been asked
+  // to renderer since timing information ultimately derives from the
+  // renderer.  If the same scene is being rendered to multiple destinations
+  // with different timing requirements, the compositor will perform rate
+  // adaptation as required behind the scenes.
+  //
+  // The returned |frame_info| provides information about the frame to
+  // be drawn.
+  //
+  // TODO(jeffbrown): Consider whether we should have the callback be invoked
+  // immediately rather than on schedule, letting the client set its own
+  // timers.  Advantage may be a reduction in latency due to the elimination
+  // of an IPC on a timing critical path.  Disadvantage may be an increase
+  // in the number of context switches and some extra client side bookkeeping.
+  // Note that although clients could predict future frame times based on the
+  // reported information, it's still better for them to schedule each frame
+  // individually so they stay in sync with one another and quickly catch
+  // up to any changes in timing.  This also gives the compositor more
+  // opportunity to plan for the upcoming frame, perhaps even boost clocks
+  // in anticipation.
+  ScheduleFrame() => (FrameInfo frame_info);
+};
+
+// Provides timestamp information about a frame which has been scheduled
+// to be drawn.
+struct FrameInfo {
+  // A timestamp indicating when the work of updating the frame was scheduled
+  // to begin which may be used to coordinate animations that require a
+  // monotonic timing reference for synchronization, even across scenes.
+  //
+  // This value monotonically increases with each frame, never repeats, and
+  // is guaranteed to represent a time in the past.  It will always be less
+  // than or equal to the value returned by |MojoGetTimeTicksNow()| when the
+  // frame info is received as part of a |ScheduleFrame()| callback.
+  //
+  // Expressed in microseconds in the |MojoTimeTicks| timebase.
+  // TODO(jeffbrown): Time ticks may need finer resolution guarantees.
+  int64 frame_time;
+
+  // The anticipated time interval between the currently scheduled frame
+  // and the next one.
+  //
+  // The interval is approximate and may change at any time.  There is no
+  // guarantee that the next frame will occur precisely on the specified
+  // interval.
+  //
+  // Expressed in microseconds.
+  uint64 frame_interval;
+
+  // A timestamp indicating when scene updates must be published and ready
+  // to be composited in order for the changes to be reflected in this frame.
+  //
+  // This value is guaranteed to be no less than |frame_time| but it is
+  // not necessarily monotonic; it may briefly go backwards if the frame
+  // interval or pipeline depth changes between frames.
+  //
+  // Expressed in microseconds in the |MojoTimeTicks| timebase.
+  int64 frame_deadline;
+
+  // A timestamp indicating approximately when the contents of the frame
+  // will be shown on the display output assuming everything is fully rendered
+  // and submitted by the indicated |frame_deadline|.
+  //
+  // This value is guaranteed to be no less than |frame_deadline| but it is
+  // not necessarily monotonic; it may briefly go backwards if the frame
+  // interval or pipeline depth changes between frames.
+  //
+  // Expressed in microseconds in the |MojoTimeTicks| timebase.
+  int64 presentation_time;
+};
diff --git a/mojo/services/mojo_services.gni b/mojo/services/mojo_services.gni
index 37ce7b5..5b04f49 100644
--- a/mojo/services/mojo_services.gni
+++ b/mojo/services/mojo_services.gni
@@ -19,6 +19,7 @@
   "//mojo/services/device_info/interfaces",
   "//mojo/services/files/interfaces",
   "//mojo/services/geometry/interfaces",
+  "//mojo/services/gfx/composition/interfaces",
   "//mojo/services/gpu/interfaces",
   "//mojo/services/http_server/interfaces",
   "//mojo/services/icu_data/interfaces",
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 5e59c14..11cfefb 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -53,6 +53,7 @@
     deps += [
       "//services/device_info",
       "//services/files",
+      "//services/gfx",
       "//services/native_viewport",
       "//services/ui",
       "//services/url_response_disk_cache",
@@ -75,6 +76,7 @@
     "//services/asset_bundle:apptests",
     "//services/authenticating_url_loader_interceptor:apptests",
     "//services/clipboard:apptests",
+    "//services/gfx/compositor:apptests",
     "//services/http_server:apptests",
     "//services/native_support:apptests",
     "//services/prediction:apptests",
diff --git a/services/gfx/BUILD.gn b/services/gfx/BUILD.gn
new file mode 100644
index 0000000..3903d6b
--- /dev/null
+++ b/services/gfx/BUILD.gn
@@ -0,0 +1,9 @@
+# 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.
+
+group("gfx") {
+  deps = [
+    "//services/gfx/compositor",
+  ]
+}
diff --git a/services/gfx/README.md b/services/gfx/README.md
new file mode 100644
index 0000000..fddafe7
--- /dev/null
+++ b/services/gfx/README.md
@@ -0,0 +1,4 @@
+# Mozart Graphics System
+
+This directory contains Mozart Graphics System, an implementation of a
+graphical compositor system named after the classical composer.
diff --git a/services/gfx/compositor/BUILD.gn b/services/gfx/compositor/BUILD.gn
new file mode 100644
index 0000000..3e88cbd
--- /dev/null
+++ b/services/gfx/compositor/BUILD.gn
@@ -0,0 +1,96 @@
+# 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.
+
+import("//mojo/public/mojo_application.gni")
+import("//testing/test.gni")
+
+source_set("common") {
+  sources = [
+    "backend/gpu_output.cc",
+    "backend/gpu_output.h",
+    "backend/gpu_rasterizer.cc",
+    "backend/gpu_rasterizer.h",
+    "backend/output.h",
+    "backend/scheduler.cc",
+    "backend/scheduler.h",
+    "backend/vsync_scheduler.cc",
+    "backend/vsync_scheduler.h",
+    "compositor_app.cc",
+    "compositor_app.h",
+    "compositor_engine.cc",
+    "compositor_engine.h",
+    "compositor_impl.cc",
+    "compositor_impl.h",
+    "graph/node_def.cc",
+    "graph/node_def.h",
+    "graph/resource_def.cc",
+    "graph/resource_def.h",
+    "graph/scene_def.cc",
+    "graph/scene_def.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",
+    "renderer_state.h",
+    "scene_impl.cc",
+    "scene_impl.h",
+    "scene_state.cc",
+    "scene_state.h",
+  ]
+
+  public_deps = [
+    "//base",
+    "//mojo/application",
+    "//mojo/common",
+    "//mojo/common:tracing_impl",
+    "//mojo/converters/geometry",
+    "//mojo/gpu",
+    "//mojo/public/c/gpu",
+    "//mojo/public/cpp/bindings:bindings",
+    "//mojo/services/geometry/cpp",
+    "//mojo/services/geometry/interfaces",
+    "//mojo/services/gfx/composition/cpp",
+    "//mojo/services/gfx/composition/interfaces",
+    "//mojo/services/gpu/interfaces",
+    "//mojo/skia",
+    "//skia",
+  ]
+}
+
+mojo_native_application("compositor") {
+  output_name = "compositor_service"
+
+  sources = [
+    "main.cc",
+  ]
+
+  deps = [
+    ":common",
+    "//mojo/environment:chromium",
+  ]
+}
+
+mojo_native_application("apptests") {
+  output_name = "compositor_apptests"
+
+  testonly = true
+
+  sources = [
+    "backend/vsync_scheduler_unittest.cc",
+  ]
+
+  deps = [
+    ":common",
+    "//base/test:test_support",
+    "//mojo/application:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/services/gfx/compositor/README.md b/services/gfx/compositor/README.md
new file mode 100644
index 0000000..751299d
--- /dev/null
+++ b/services/gfx/compositor/README.md
@@ -0,0 +1,9 @@
+# Mozart Compositor
+
+This directory contains an implementation of the Compositor interface.
+It provides a graphical compositor system for use by other applications.
+
+It doesn't make sense to run this application stand-alone since it
+doesn't have any UI of its own to display.  Instead, use the Mozart
+Launcher or some other application to launch and embed the UI of some
+other application using the view manager.
diff --git a/services/gfx/compositor/backend/gpu_output.cc b/services/gfx/compositor/backend/gpu_output.cc
new file mode 100644
index 0000000..1a757f3
--- /dev/null
+++ b/services/gfx/compositor/backend/gpu_output.cc
@@ -0,0 +1,109 @@
+// 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/backend/gpu_output.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "services/gfx/compositor/backend/gpu_rasterizer.h"
+
+namespace compositor {
+
+template <typename T>
+static void Drop(scoped_ptr<T> ptr) {}
+
+static scoped_ptr<base::MessagePump> CreateMessagePumpMojo() {
+  return base::MessageLoop::CreateMessagePumpForType(
+      base::MessageLoop::TYPE_DEFAULT);
+}
+
+GpuOutput::GpuOutput(mojo::ContextProviderPtr context_provider,
+                     const SchedulerCallbacks& scheduler_callbacks,
+                     const base::Closure& error_callback)
+    : frame_queue_(std::make_shared<FrameQueue>()),
+      scheduler_(std::make_shared<VsyncScheduler>(base::MessageLoop::current()
+                                                      ->task_runner(),
+                                                  scheduler_callbacks)),
+      rasterizer_delegate_(
+          make_scoped_ptr(new RasterizerDelegate(frame_queue_))) {
+  DCHECK(context_provider);
+
+  base::Thread::Options options;
+  options.message_pump_factory = base::Bind(&CreateMessagePumpMojo);
+
+  rasterizer_thread_.reset(new base::Thread("gpu_rasterizer"));
+  rasterizer_thread_->StartWithOptions(options);
+  rasterizer_task_runner_ = rasterizer_thread_->message_loop()->task_runner();
+
+  rasterizer_task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&RasterizerDelegate::CreateRasterizer,
+                 base::Unretained(rasterizer_delegate_.get()),
+                 base::Passed(context_provider.PassInterface()), scheduler_,
+                 base::MessageLoop::current()->task_runner(), error_callback));
+}
+
+GpuOutput::~GpuOutput() {
+  // Ensure destruction happens on the correct thread.
+  rasterizer_task_runner_->PostTask(
+      FROM_HERE, base::Bind(&Drop<RasterizerDelegate>,
+                            base::Passed(&rasterizer_delegate_)));
+}
+
+Scheduler* GpuOutput::GetScheduler() {
+  return scheduler_.get();
+}
+
+void GpuOutput::SubmitFrame(const std::shared_ptr<RenderFrame>& frame) {
+  if (frame_queue_->PutFrame(frame)) {
+    rasterizer_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&RasterizerDelegate::SubmitNextFrame,
+                              base::Unretained(rasterizer_delegate_.get())));
+  }
+}
+
+GpuOutput::FrameQueue::FrameQueue() {}
+
+GpuOutput::FrameQueue::~FrameQueue() {}
+
+bool GpuOutput::FrameQueue::PutFrame(
+    const std::shared_ptr<RenderFrame>& frame) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  bool was_empty = !next_frame_.get();
+  next_frame_ = frame;
+  return was_empty;
+}
+
+std::shared_ptr<RenderFrame> GpuOutput::FrameQueue::TakeFrame() {
+  std::lock_guard<std::mutex> lock(mutex_);
+  return std::move(next_frame_);
+}
+
+GpuOutput::RasterizerDelegate::RasterizerDelegate(
+    const std::shared_ptr<FrameQueue>& frame_queue)
+    : frame_queue_(frame_queue) {
+  DCHECK(frame_queue_);
+}
+
+GpuOutput::RasterizerDelegate::~RasterizerDelegate() {}
+
+void GpuOutput::RasterizerDelegate::CreateRasterizer(
+    mojo::InterfacePtrInfo<mojo::ContextProvider> context_provider_info,
+    const std::shared_ptr<VsyncScheduler>& scheduler,
+    const scoped_refptr<base::TaskRunner>& task_runner,
+    const base::Closure& error_callback) {
+  rasterizer_.reset(
+      new GpuRasterizer(mojo::MakeProxy(context_provider_info.Pass()).Pass(),
+                        scheduler, task_runner, error_callback));
+}
+
+void GpuOutput::RasterizerDelegate::SubmitNextFrame() {
+  std::shared_ptr<RenderFrame> frame(frame_queue_->TakeFrame());
+  DCHECK(frame);
+  rasterizer_->SubmitFrame(frame);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/backend/gpu_output.h b/services/gfx/compositor/backend/gpu_output.h
new file mode 100644
index 0000000..678a3fa
--- /dev/null
+++ b/services/gfx/compositor/backend/gpu_output.h
@@ -0,0 +1,92 @@
+// 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_BACKEND_GPU_OUTPUT_H_
+#define SERVICES_GFX_COMPOSITOR_BACKEND_GPU_OUTPUT_H_
+
+#include <memory>
+#include <mutex>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_runner.h"
+#include "base/threading/thread.h"
+#include "mojo/services/gpu/interfaces/context_provider.mojom.h"
+#include "services/gfx/compositor/backend/output.h"
+#include "services/gfx/compositor/backend/vsync_scheduler.h"
+
+namespace compositor {
+
+class GpuRasterizer;
+
+// Renderer backed by a ContextProvider.
+class GpuOutput : public Output {
+ public:
+  GpuOutput(mojo::ContextProviderPtr context_provider,
+            const SchedulerCallbacks& scheduler_callbacks,
+            const base::Closure& error_callback);
+  ~GpuOutput() override;
+
+  Scheduler* GetScheduler() override;
+  void SubmitFrame(const std::shared_ptr<RenderFrame>& frame) override;
+
+ private:
+  // Frame queue, held by a std::shared_ptr.
+  // This object acts as a shared fifo between both threads.
+  class FrameQueue {
+   public:
+    FrameQueue();
+    ~FrameQueue();
+
+    // Puts a pending frame into the queue, drops existing frames if needed.
+    // Returns true if the queue was previously empty.
+    bool PutFrame(const std::shared_ptr<RenderFrame>& frame);
+
+    // Takes a pending frame from the queue.
+    std::shared_ptr<RenderFrame> TakeFrame();
+
+   private:
+    std::mutex mutex_;
+    std::shared_ptr<RenderFrame> next_frame_;  // guarded by |mutex_|
+
+    DISALLOW_COPY_AND_ASSIGN(FrameQueue);
+  };
+
+  // Wrapper around state which is only accessible by the rasterizer thread.
+  class RasterizerDelegate {
+   public:
+    explicit RasterizerDelegate(const std::shared_ptr<FrameQueue>& frame_queue);
+    ~RasterizerDelegate();
+
+    void CreateRasterizer(
+        mojo::InterfacePtrInfo<mojo::ContextProvider> context_provider_info,
+        const std::shared_ptr<VsyncScheduler>& scheduler,
+        const scoped_refptr<base::TaskRunner>& task_runner,
+        const base::Closure& error_callback);
+
+    void SubmitNextFrame();
+
+   private:
+    std::shared_ptr<FrameQueue> frame_queue_;
+    std::unique_ptr<GpuRasterizer> rasterizer_;
+
+    DISALLOW_COPY_AND_ASSIGN(RasterizerDelegate);
+  };
+
+  std::shared_ptr<FrameQueue> frame_queue_;
+  std::shared_ptr<VsyncScheduler> scheduler_;
+  scoped_ptr<RasterizerDelegate> rasterizer_delegate_;  // can't use unique_ptr
+                                                        // here due to
+                                                        // base::Bind (sadness)
+  std::unique_ptr<base::Thread> rasterizer_thread_;
+  scoped_refptr<base::SingleThreadTaskRunner> rasterizer_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(GpuOutput);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_BACKEND_GPU_OUTPUT_H_
diff --git a/services/gfx/compositor/backend/gpu_rasterizer.cc b/services/gfx/compositor/backend/gpu_rasterizer.cc
new file mode 100644
index 0000000..bdd6aa3
--- /dev/null
+++ b/services/gfx/compositor/backend/gpu_rasterizer.cc
@@ -0,0 +1,171 @@
+// 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/backend/gpu_rasterizer.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2extmojo.h>
+#include <MGL/mgl.h>
+#include <MGL/mgl_onscreen.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "services/gfx/compositor/backend/vsync_scheduler.h"
+#include "services/gfx/compositor/render/render_frame.h"
+
+namespace compositor {
+
+GpuRasterizer::GpuRasterizer(mojo::ContextProviderPtr context_provider,
+                             const std::shared_ptr<VsyncScheduler>& scheduler,
+                             const scoped_refptr<base::TaskRunner>& task_runner,
+                             const base::Closure& error_callback)
+    : context_provider_(context_provider.Pass()),
+      scheduler_(scheduler),
+      task_runner_(task_runner),
+      error_callback_(error_callback),
+      viewport_parameter_listener_binding_(this) {
+  DCHECK(context_provider_);
+  context_provider_.set_connection_error_handler(
+      base::Bind(&GpuRasterizer::OnContextProviderConnectionError,
+                 base::Unretained(this)));
+  CreateContext();
+}
+
+GpuRasterizer::~GpuRasterizer() {
+  DestroyContext();
+}
+
+void GpuRasterizer::CreateContext() {
+  mojo::ViewportParameterListenerPtr viewport_parameter_listener;
+  viewport_parameter_listener_binding_.Bind(
+      GetProxy(&viewport_parameter_listener));
+  context_provider_->Create(
+      viewport_parameter_listener.Pass(),
+      base::Bind(&GpuRasterizer::InitContext, base::Unretained(this)));
+}
+
+void GpuRasterizer::InitContext(mojo::CommandBufferPtr command_buffer) {
+  DCHECK(!gl_context_);
+  DCHECK(!ganesh_context_);
+  DCHECK(!ganesh_surface_);
+
+  if (!command_buffer) {
+    LOG(ERROR) << "Could not create GL context.";
+    PostErrorCallback();
+    return;
+  }
+
+  gl_context_ = mojo::GLContext::CreateFromCommandBuffer(command_buffer.Pass());
+  gl_context_->AddObserver(this);
+  ganesh_context_.reset(new mojo::skia::GaneshContext(gl_context_));
+
+  if (current_frame_)
+    Draw();
+}
+
+void GpuRasterizer::DestroyContext() {
+  if (gl_context_) {
+    scheduler_->Stop();
+
+    // Release objects that belong to special scopes.
+    {
+      mojo::skia::GaneshContext::Scope ganesh_scope(ganesh_context_.get());
+      ganesh_surface_.reset();
+    }
+
+    // Release the ganesh context before the GL context.
+    ganesh_context_.reset();
+
+    // Now release the GL context.
+    gl_context_->Destroy();
+    gl_context_.reset();
+  }
+}
+
+void GpuRasterizer::OnContextProviderConnectionError() {
+  LOG(ERROR) << "Context provider connection lost.";
+  PostErrorCallback();
+}
+
+void GpuRasterizer::OnContextLost() {
+  LOG(WARNING) << "GL context lost, recreating it.";
+  DestroyContext();
+  CreateContext();
+}
+
+void GpuRasterizer::OnVSyncParametersUpdated(int64_t timebase,
+                                             int64_t interval) {
+  DVLOG(1) << "Vsync parameters: timebase=" << timebase
+           << ", interval=" << interval;
+
+  if (!gl_context_)
+    return;
+
+  // TODO(jeffbrown): This shouldn't be hardcoded.
+  // Need to do some real tuning and possibly determine values adaptively.
+  int64_t update_phase = -interval;
+  int64_t snapshot_phase = -interval / 8;
+  int64_t presentation_phase = interval;
+  if (!scheduler_->Start(timebase, interval, update_phase, snapshot_phase,
+                         presentation_phase)) {
+    LOG(ERROR) << "Received invalid vsync parameters: timebase=" << timebase
+               << ", interval=" << interval;
+    PostErrorCallback();
+  }
+}
+
+void GpuRasterizer::SubmitFrame(const std::shared_ptr<RenderFrame>& frame) {
+  DCHECK(frame);
+
+  if (current_frame_ == frame)
+    return;
+
+  current_frame_ = frame;
+  if (gl_context_)
+    Draw();
+}
+
+void GpuRasterizer::Draw() {
+  DCHECK(gl_context_);
+  DCHECK(ganesh_context_);
+  DCHECK(current_frame_);
+
+  gl_context_->MakeCurrent();
+
+  // Update the viewport.
+  const SkRect& viewport = current_frame_->viewport();
+  bool stale_surface = false;
+  if (!ganesh_surface_ ||
+      ganesh_surface_->surface()->width() != viewport.width() ||
+      ganesh_surface_->surface()->height() != viewport.height()) {
+    glViewport(viewport.x(), viewport.y(), viewport.width(), viewport.height());
+    glResizeCHROMIUM(viewport.width(), viewport.height(), 1.0f);
+    stale_surface = true;
+  }
+
+  // Paint the frame.
+  {
+    mojo::skia::GaneshContext::Scope ganesh_scope(ganesh_context_.get());
+
+    if (stale_surface)
+      ganesh_surface_.reset(
+          new mojo::skia::GaneshFramebufferSurface(ganesh_scope));
+
+    current_frame_->Paint(ganesh_surface_->canvas());
+  }
+
+  // Swap buffers.
+  MGLSwapBuffers();
+}
+
+void GpuRasterizer::PostErrorCallback() {
+  task_runner_->PostTask(FROM_HERE, error_callback_);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/backend/gpu_rasterizer.h b/services/gfx/compositor/backend/gpu_rasterizer.h
new file mode 100644
index 0000000..65eb5bb
--- /dev/null
+++ b/services/gfx/compositor/backend/gpu_rasterizer.h
@@ -0,0 +1,75 @@
+// 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_BACKEND_GPU_RASTERIZER_H_
+#define SERVICES_GFX_COMPOSITOR_BACKEND_GPU_RASTERIZER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task_runner.h"
+#include "mojo/gpu/gl_context.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/functions.h"
+#include "mojo/services/gpu/interfaces/context_provider.mojom.h"
+#include "mojo/skia/ganesh_context.h"
+#include "mojo/skia/ganesh_framebuffer_surface.h"
+
+namespace compositor {
+
+class RenderFrame;
+class VsyncScheduler;
+
+// Ganesh-based rasterizer which runs on a separate thread from the compositor.
+// Calls into this object, including its creation, must be posted to the
+// correct message loop by the output.
+class GpuRasterizer : public mojo::ViewportParameterListener,
+                      public mojo::GLContext::Observer {
+ public:
+  GpuRasterizer(mojo::ContextProviderPtr context_provider,
+                const std::shared_ptr<VsyncScheduler>& scheduler,
+                const scoped_refptr<base::TaskRunner>& task_runner,
+                const base::Closure& error_callback);
+  ~GpuRasterizer() override;
+
+  void SubmitFrame(const std::shared_ptr<RenderFrame>& frame);
+
+ private:
+  // |ViewportParameterListener|:
+  void OnVSyncParametersUpdated(int64_t timebase, int64_t interval) override;
+
+  // |GLContext::Observer|:
+  void OnContextLost() override;
+
+  void CreateContext();
+  void InitContext(mojo::CommandBufferPtr command_buffer);
+  void DestroyContext();
+  void OnContextProviderConnectionError();
+
+  void Draw();
+
+  void PostErrorCallback();
+
+  mojo::ContextProviderPtr context_provider_;
+  std::shared_ptr<VsyncScheduler> scheduler_;
+  scoped_refptr<base::TaskRunner> task_runner_;
+  base::Closure error_callback_;
+
+  base::WeakPtr<mojo::GLContext> gl_context_;
+  std::unique_ptr<mojo::skia::GaneshContext> ganesh_context_;
+  std::unique_ptr<mojo::skia::GaneshFramebufferSurface> ganesh_surface_;
+
+  std::shared_ptr<RenderFrame> current_frame_;
+
+  mojo::Binding<ViewportParameterListener> viewport_parameter_listener_binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(GpuRasterizer);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_BACKEND_GPU_RASTERIZER_H_
diff --git a/services/gfx/compositor/backend/output.h b/services/gfx/compositor/backend/output.h
new file mode 100644
index 0000000..8891d54
--- /dev/null
+++ b/services/gfx/compositor/backend/output.h
@@ -0,0 +1,40 @@
+// 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_BACKEND_OUTPUT_H_
+#define SERVICES_GFX_COMPOSITOR_BACKEND_OUTPUT_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace compositor {
+
+class RenderFrame;
+class Scheduler;
+
+// Renders snapshotted frames of the scene graph to a display output.
+//
+// The output object is created on the compositor's main thread and frames
+// are submitted to it from there.  Behind the scenes, the implementation of
+// Output may use some number of worker threads.  How this is accomplished
+// is left up to the implementation of the Output to decide.
+class Output {
+ public:
+  Output() = default;
+  virtual ~Output() = default;
+
+  // Gets the output's frame scheduler.
+  virtual Scheduler* GetScheduler() = 0;
+
+  // Submits a frame to be rendered to the display.
+  virtual void SubmitFrame(const std::shared_ptr<RenderFrame>& frame) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Output);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_BACKEND_OUTPUT_H_
diff --git a/services/gfx/compositor/backend/scheduler.cc b/services/gfx/compositor/backend/scheduler.cc
new file mode 100644
index 0000000..ac98d51
--- /dev/null
+++ b/services/gfx/compositor/backend/scheduler.cc
@@ -0,0 +1,15 @@
+// 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/backend/scheduler.h"
+
+namespace compositor {
+
+SchedulerCallbacks::SchedulerCallbacks(const FrameCallback& update_callback,
+                                       const FrameCallback& snapshot_callback)
+    : update_callback(update_callback), snapshot_callback(snapshot_callback) {}
+
+SchedulerCallbacks::~SchedulerCallbacks() {}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/backend/scheduler.h b/services/gfx/compositor/backend/scheduler.h
new file mode 100644
index 0000000..ce8d3d9
--- /dev/null
+++ b/services/gfx/compositor/backend/scheduler.h
@@ -0,0 +1,89 @@
+// 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_BACKEND_SCHEDULER_H_
+#define SERVICES_GFX_COMPOSITOR_BACKEND_SCHEDULER_H_
+
+#include <limits>
+#include <mutex>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace compositor {
+
+// A frame scheduler is responsible for deciding when to perform each
+// phase of composition.
+//
+// During the "update" phase, the compositor signals each application that
+// that it should start producing the next frame of content.
+//
+// During the "snapshot" phase, the compositor gathers all pending scene
+// graph updates and produces a new frame for rendering.  Rendering begins
+// immediately after the snapshot is taken.
+//
+// An instance of the |Scheduler| interface is exposed by each |Output|
+// so as to express the timing requirements of the output.
+class Scheduler {
+ public:
+  // Determines the behavior of |ScheduleFrame()|.
+  enum class SchedulingMode {
+    // Schedules a snapshot, at minimum.
+    kSnapshot,
+
+    // Schedules an update followed by a snapshot, at minimum.
+    kUpdateAndSnapshot,
+  };
+
+  Scheduler() = default;
+  virtual ~Scheduler() = default;
+
+  // Schedules work for a frame.
+  //
+  // This function ensures that every update is followed by a snapshot
+  // unless scheduling is suspended in the meantime.
+  //
+  // When |scheduling_mode| is |kSnapshot|, if there is time between now
+  // and the snapshot during which an update can be performed, then an
+  // update will also be scheduled before the requested snapshot.
+  //
+  // When |scheduling_mode| is |kUpdateAndSnapshot|, if there is time
+  // between now and the update during which a snapshot can be performed,
+  // then a snapshot will also be scheduled before the requested update
+  // and the next snapshot.
+  //
+  // This design is intended to minimize latency by anticipating that
+  // snapshots will be needed after updates and by scheduling updates in
+  // advance if it is known that a snapshot will be needed on the next frame.
+  virtual void ScheduleFrame(SchedulingMode scheduling_mode) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Scheduler);
+};
+
+// Scheduling callbacks.
+//
+// These callbacks are provided to the |Output| in order to receive the
+// events produced by the output's associated |Scheduler|.
+struct SchedulerCallbacks {
+  using FrameCallback =
+      base::Callback<void(const mojo::gfx::composition::FrameInfo&)>;
+
+  SchedulerCallbacks(const FrameCallback& update_callback,
+                     const FrameCallback& snapshot_callback);
+  ~SchedulerCallbacks();
+
+  // Called when it's time for applications to/ update the contents of
+  // their scenes.
+  const FrameCallback update_callback;
+
+  // Called when it's time for the compositor to snapshot and submit
+  // the next frame.
+  const FrameCallback snapshot_callback;
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_BACKEND_SCHEDULER_H_
diff --git a/services/gfx/compositor/backend/vsync_scheduler.cc b/services/gfx/compositor/backend/vsync_scheduler.cc
new file mode 100644
index 0000000..71be4f3
--- /dev/null
+++ b/services/gfx/compositor/backend/vsync_scheduler.cc
@@ -0,0 +1,273 @@
+// 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/backend/vsync_scheduler.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace compositor {
+
+constexpr int64_t VsyncScheduler::kMinVsyncInterval;
+constexpr int64_t VsyncScheduler::kMaxVsyncInterval;
+
+VsyncScheduler::VsyncScheduler(
+    const scoped_refptr<base::TaskRunner>& task_runner,
+    const SchedulerCallbacks& callbacks)
+    : VsyncScheduler(task_runner, callbacks, base::Bind(&MojoGetTimeTicksNow)) {
+}
+
+VsyncScheduler::VsyncScheduler(
+    const scoped_refptr<base::TaskRunner>& task_runner,
+    const SchedulerCallbacks& callbacks,
+    const Clock& clock)
+    : state_(std::make_shared<State>(task_runner, callbacks, clock)) {}
+
+VsyncScheduler::~VsyncScheduler() {}
+
+void VsyncScheduler::ScheduleFrame(SchedulingMode scheduling_mode) {
+  state_->ScheduleFrame(scheduling_mode);
+}
+
+VsyncScheduler::State::State(const scoped_refptr<base::TaskRunner>& task_runner,
+                             const SchedulerCallbacks& callbacks,
+                             const Clock& clock)
+    : task_runner_(task_runner), callbacks_(callbacks), clock_(clock) {}
+
+VsyncScheduler::State::~State() {}
+
+bool VsyncScheduler::State::Start(int64_t vsync_timebase,
+                                  int64_t vsync_interval,
+                                  int64_t update_phase,
+                                  int64_t snapshot_phase,
+                                  int64_t presentation_phase) {
+  // Be slightly paranoid.  Timing glitches are hard to find and the
+  // vsync parameters will typically come from other services.
+  // Ensure vsync timing is anchored on actual observations from the past.
+  MojoTimeTicks now = GetTimeTicksNow();
+  if (vsync_timebase > now) {
+    DVLOG(1) << "Vsync timebase is in the future: vsync_timebase="
+             << vsync_timebase << ", now=" << now;
+    return false;
+  }
+  if (vsync_interval < kMinVsyncInterval ||
+      vsync_interval > kMaxVsyncInterval) {
+    DVLOG(1) << "Vsync interval is invalid: vsync_interval=" << vsync_interval
+             << ", min=" << kMinVsyncInterval << ", max=" << kMaxVsyncInterval;
+    return false;
+  }
+  if (snapshot_phase < update_phase ||
+      snapshot_phase > update_phase + vsync_interval ||
+      presentation_phase < snapshot_phase) {
+    // Updating and snapshotting must happen within the same frame interval
+    // to avoid having multiple updates in progress simultanteously (which
+    // doesn't make much sense if we're already compute bound).
+    DVLOG(1) << "Vsync scheduling phases are invalid: update_phase="
+             << update_phase << ", snapshot_phase=" << snapshot_phase
+             << ", presentation_phase=" << presentation_phase;
+    return false;
+  }
+
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+
+    // Suppress spurious updates.
+    if (running_ && vsync_timebase_ == vsync_timebase &&
+        vsync_interval_ == vsync_interval && update_phase_ == update_phase &&
+        snapshot_phase_ == snapshot_phase &&
+        presentation_phase_ == presentation_phase)
+      return true;
+
+    // Get running with these new parameters.
+    // Note that |last_delivered_update_time_| is preserved.
+    running_ = true;
+    generation_++;  // cancels pending undelivered callbacks
+    vsync_timebase_ = vsync_timebase;
+    vsync_interval_ = vsync_interval;
+    update_phase_ = update_phase;
+    snapshot_phase_ = snapshot_phase;
+    presentation_phase_ = presentation_phase;
+    need_update_ = true;
+    pending_dispatch_ = false;
+    ScheduleLocked(now);
+    return true;
+  }
+}
+
+void VsyncScheduler::State::Stop() {
+  std::lock_guard<std::mutex> lock(mutex_);
+  running_ = false;
+}
+
+void VsyncScheduler::State::ScheduleFrame(SchedulingMode scheduling_mode) {
+  MojoTimeTicks now = GetTimeTicksNow();
+
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+    if (running_) {
+      if (scheduling_mode == SchedulingMode::kUpdateAndSnapshot)
+        need_update_ = true;
+      ScheduleLocked(now);
+    }
+  }
+}
+
+void VsyncScheduler::State::ScheduleLocked(MojoTimeTicks now) {
+  DCHECK(running_);
+  DCHECK(now >= vsync_timebase_);
+  DVLOG(2) << "schedule: now=" << now << ", need_update_=" << need_update_
+           << ", last_delivered_update_time_=" << last_delivered_update_time_;
+  if (pending_dispatch_)
+    return;
+
+  // Determine the time of the earliest achievable frame snapshot in
+  // the near future.
+  int64_t snapshot_timebase = vsync_timebase_ + snapshot_phase_;
+  uint64_t snapshot_offset = (now - snapshot_timebase) % vsync_interval_;
+  int64_t snapshot_time = now - snapshot_offset + vsync_interval_;
+  DCHECK(snapshot_time >= now);
+
+  // Determine when the update that produced this snapshot must have begun.
+  // This time may be in the past.
+  int64_t update_time = snapshot_time - snapshot_phase_ + update_phase_;
+  DCHECK(update_time <= snapshot_time);
+
+  // When changing vsync parameters, it's possible for the next update time
+  // to regress.  Prevent applications from observing that.
+  if (update_time <= last_delivered_update_time_) {
+    int64_t frames =
+        (last_delivered_update_time_ - update_time) / vsync_interval_ + 1;
+    int64_t adjustment = frames * vsync_interval_;
+    update_time += adjustment;
+    snapshot_time += adjustment;
+  }
+
+  // Schedule dispatching at that time.
+  if (update_time >= now) {
+    PostDispatchLocked(now, update_time, Action::kUpdate, update_time);
+  } else {
+    PostDispatchLocked(now, snapshot_time, Action::kEarlySnapshot, update_time);
+  }
+
+  pending_dispatch_ = true;
+}
+
+void VsyncScheduler::State::PostDispatchLocked(int64_t now,
+                                               int64_t delivery_time,
+                                               Action action,
+                                               int64_t update_time) {
+  DVLOG(2) << "post: now=" << now << ", delivery_time=" << delivery_time
+           << ", action=" << static_cast<int>(action)
+           << ", update_time=" << update_time;
+
+  task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&VsyncScheduler::State::DispatchThunk, shared_from_this(),
+                 generation_, action, update_time),
+      base::TimeDelta::FromMicroseconds(
+          std::max(delivery_time - now, static_cast<int64_t>(0))));
+}
+
+void VsyncScheduler::State::DispatchThunk(
+    const std::weak_ptr<State>& state_weak,
+    int32_t generation,
+    Action action,
+    int64_t update_time) {
+  std::shared_ptr<State> state = state_weak.lock();
+  if (state)
+    state->Dispatch(generation, action, update_time);
+}
+
+void VsyncScheduler::State::Dispatch(int32_t generation,
+                                     Action action,
+                                     int64_t update_time) {
+  MojoTimeTicks now = GetTimeTicksNow();
+  DCHECK(update_time <= now);
+
+  // Time may have passed since the callback was originally scheduled and
+  // it's possible that we completely missed the deadline we were aiming for.
+  // Reevaluate the schedule and jump ahead if necessary.
+  mojo::gfx::composition::FrameInfo frame_info;
+  {
+    std::lock_guard<std::mutex> lock(mutex_);
+    if (!running_ || generation_ != generation)
+      return;
+
+    DCHECK(pending_dispatch_);
+
+    // Check whether we missed any deadlines.
+    bool missed_deadline = false;
+    if (action == Action::kUpdate) {
+      int64_t update_deadline = update_time - update_phase_ + snapshot_phase_;
+      if (now > update_deadline) {
+        LOG(WARNING) << "Compositor missed update deadline by "
+                     << (now - update_deadline) << " us";
+        missed_deadline = true;
+      }
+    } else {
+      int64_t snapshot_deadline = update_time + vsync_interval_;
+      if (now > snapshot_deadline) {
+        LOG(WARNING) << "Compositor missed snapshot deadline by "
+                     << (now - snapshot_deadline) << " us";
+        missed_deadline = true;
+      }
+    }
+    if (missed_deadline) {
+      uint64_t offset = (now - update_time) % vsync_interval_;
+      update_time = now - offset;
+      DCHECK(update_time > now - vsync_interval_ && update_time <= now);
+    }
+
+    DVLOG(2) << "dispatch: now=" << now
+             << ", action=" << static_cast<int>(action)
+             << ", update_time=" << update_time;
+
+    // Schedule the corresponding snapshot for the update.
+    if (action == Action::kUpdate) {
+      int64_t snapshot_time = update_time - update_phase_ + snapshot_phase_;
+      PostDispatchLocked(now, snapshot_time, Action::kLateSnapshot,
+                         update_time);
+      need_update_ = false;
+    } else if (need_update_) {
+      int64_t next_update_time = update_time + vsync_interval_;
+      PostDispatchLocked(now, next_update_time, Action::kUpdate,
+                         next_update_time);
+
+      // If we missed the deadline on an early snapshot, then just skip it
+      // and wait for the following update instead.
+      if (action == Action::kEarlySnapshot && missed_deadline) {
+        DVLOG(2) << "skip early snapshot";
+        return;
+      }
+    } else {
+      pending_dispatch_ = false;
+    }
+
+    SetFrameInfoLocked(&frame_info, update_time);
+    last_delivered_update_time_ = update_time;
+  }
+
+  if (action == Action::kUpdate) {
+    callbacks_.update_callback.Run(frame_info);
+  } else {
+    callbacks_.snapshot_callback.Run(frame_info);
+  }
+}
+
+void VsyncScheduler::State::SetFrameInfoLocked(
+    mojo::gfx::composition::FrameInfo* frame_info,
+    int64_t update_time) {
+  DCHECK(frame_info);
+  frame_info->frame_time = update_time;
+  frame_info->frame_interval = vsync_interval_;
+  frame_info->frame_deadline = update_time - update_phase_ + snapshot_phase_;
+  frame_info->presentation_time =
+      update_time - update_phase_ + presentation_phase_;
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/backend/vsync_scheduler.h b/services/gfx/compositor/backend/vsync_scheduler.h
new file mode 100644
index 0000000..46fced8
--- /dev/null
+++ b/services/gfx/compositor/backend/vsync_scheduler.h
@@ -0,0 +1,162 @@
+// 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_BACKEND_VSYNC_SCHEDULER_H_
+#define SERVICES_GFX_COMPOSITOR_BACKEND_VSYNC_SCHEDULER_H_
+
+#include <limits>
+#include <memory>
+#include <mutex>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "services/gfx/compositor/backend/scheduler.h"
+
+namespace compositor {
+
+// Schedules work to coincide with vsync intervals.
+//
+// This object is thread-safe and it intended to be used to allow one thread
+// to be scheduling work for itself while another thread concurrently updates
+// timing parameters.
+class VsyncScheduler : public Scheduler {
+ public:
+  // Limits on allowable parameters.  (Exposed for testing.)
+  static constexpr int64_t kMinVsyncInterval = 1000;     // 1000 Hz
+  static constexpr int64_t kMaxVsyncInterval = 1000000;  // 1 Hz
+
+  // Time reference.  Should be MojoTimeTicksNow() except during testing.
+  using Clock = base::Callback<MojoTimeTicks()>;
+
+  VsyncScheduler(const scoped_refptr<base::TaskRunner>& task_runner,
+                 const SchedulerCallbacks& callbacks);
+  VsyncScheduler(const scoped_refptr<base::TaskRunner>& task_runner,
+                 const SchedulerCallbacks& callbacks,
+                 const Clock& clock);
+  ~VsyncScheduler() override;
+
+  // Starts scheduling work and sets the scheduling parameters.
+  //
+  // |vsync_timebase| is a value in the |MojoTimeTicks| timebase which
+  // specifies when a recent vsync occurred and is used to determine the phase.
+  //
+  // |vsync_interval| is the number of microseconds between vsyncs which
+  // also determines the |FrameInfo.frame_interval| value to deliver to
+  // applications.
+  //
+  // |update_phase| specifies an offset relative to vsync for determining
+  // when updates are scheduled and the |FrameInfo.frame_time| to deliver
+  // to applications.
+  //
+  // |snapshot_phase| specifies an offset relative to vsync for
+  // determining when snapshots are scheduled and the |FrameInfo.frame_deadline|
+  // to deliver to applications.  Must be greater than or equal to
+  // |update_phase|.
+  //
+  // |presentation_phase| specifies an offset relative to vsync for
+  // determining when frames are shown on the display output and the
+  // |FrameInfo.presentation_time| to deliver to applications.  Must be
+  // greater than or equal to |snapshot_phase|.
+  //
+  // The notion of 'vsync' is somewhat abstract here.  It's just a reference
+  // pulse but we usually interpret it as a deadline for preparing the next
+  // frame and submitting it to the display hardware.
+  //
+  // The phases can be positive or negative but negative offsets from vsync
+  // may be easier to interpret when computing deadlines.  To avoid
+  // overflows, the values chosen for the phases should be close to 0.
+  //
+  // This function schedules an update and snapshot if not already scheduled.
+  //
+  // Returns true if the schedule was started successfully, false if the
+  // parameters are invalid.
+  bool Start(int64_t vsync_timebase,
+             int64_t vsync_interval,
+             int64_t update_phase,
+             int64_t snapshot_phase,
+             int64_t presentation_phase) {
+    return state_->Start(vsync_timebase, vsync_interval, update_phase,
+                         snapshot_phase, presentation_phase);
+  }
+
+  // Stops scheduling work.
+  //
+  // Previously scheduled callbacks may still be delivered.
+  void Stop() { state_->Stop(); }
+
+  // |Scheduler|:
+  void ScheduleFrame(SchedulingMode scheduling_mode) override;
+
+ private:
+  // Internal state.  Held by a shared_ptr so that callbacks running on
+  // other threads can reference it using a weak_ptr.
+  class State : public std::enable_shared_from_this<State> {
+   public:
+    State(const scoped_refptr<base::TaskRunner>& task_runner,
+          const SchedulerCallbacks& callbacks,
+          const Clock& clock);
+    ~State();
+
+    MojoTimeTicks GetTimeTicksNow() { return clock_.Run(); }
+
+    bool Start(int64_t vsync_timebase,
+               int64_t vsync_interval,
+               int64_t update_phase,
+               int64_t snapshot_phase,
+               int64_t presentation_phase);
+    void Stop();
+    void ScheduleFrame(SchedulingMode scheduling_mode);
+
+   private:
+    enum class Action {
+      kUpdate,
+      kEarlySnapshot,
+      kLateSnapshot,
+    };
+
+    static void DispatchThunk(const std::weak_ptr<State>& state_weak,
+                              int32_t generation,
+                              Action action,
+                              int64_t update_time);
+
+    void ScheduleLocked(MojoTimeTicks now);
+    void PostDispatchLocked(int64_t now,
+                            int64_t delivery_time,
+                            Action action,
+                            int64_t update_time);
+
+    void Dispatch(int32_t generation, Action action, int64_t update_time);
+    void SetFrameInfoLocked(mojo::gfx::composition::FrameInfo* frame_info,
+                            int64_t update_time);
+
+    const scoped_refptr<base::TaskRunner> task_runner_;
+    const SchedulerCallbacks callbacks_;
+    const Clock clock_;
+
+    // Parameters and state guarded by |mutex_|.
+    std::mutex mutex_;
+    bool running_ = false;
+    int32_t generation_ = 0;
+    int64_t vsync_timebase_ = 0;
+    int64_t vsync_interval_ = 0;
+    int64_t update_phase_ = 0;
+    int64_t snapshot_phase_ = 0;
+    int64_t presentation_phase_ = 0;
+    bool need_update_ = false;
+    bool pending_dispatch_ = false;
+    int64_t last_delivered_update_time_ = std::numeric_limits<int64_t>::min();
+
+    DISALLOW_COPY_AND_ASSIGN(State);
+  };
+
+  const std::shared_ptr<State> state_;
+
+  DISALLOW_COPY_AND_ASSIGN(VsyncScheduler);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_BACKEND_VSYNC_SCHEDULER_H_
diff --git a/services/gfx/compositor/backend/vsync_scheduler_unittest.cc b/services/gfx/compositor/backend/vsync_scheduler_unittest.cc
new file mode 100644
index 0000000..654eed1
--- /dev/null
+++ b/services/gfx/compositor/backend/vsync_scheduler_unittest.cc
@@ -0,0 +1,367 @@
+// 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/backend/vsync_scheduler.h"
+
+#include <queue>
+
+#include "base/bind.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/time/tick_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace compositor {
+
+namespace {
+constexpr int64_t kVsyncTimebase = -5000;
+constexpr int64_t kVsyncInterval = 10000;
+constexpr int64_t kUpdatePhase = -9000;
+constexpr int64_t kSnapshotPhase = -1000;
+constexpr int64_t kPresentationPhase = 2000;
+}  // namespace
+
+class VsyncSchedulerTest : public testing::Test {
+ protected:
+  void SetUp() override { Reset(); }
+
+  void TearDown() override {
+    task_runner_->FastForwardUntilNoTasksRemain();
+    EXPECT_TRUE(expected_callbacks_.empty());
+  }
+
+  void ExpectUpdateCallback(int64_t frame_time,
+                            uint64_t frame_interval,
+                            int64_t frame_deadline,
+                            int64_t presentation_time) {
+    expected_callbacks_.emplace(CallbackType::kUpdate, frame_time, frame_time,
+                                frame_interval, frame_deadline,
+                                presentation_time);
+  }
+
+  void ExpectSnapshotCallback(int64_t frame_time,
+                              uint64_t frame_interval,
+                              int64_t frame_deadline,
+                              int64_t presentation_time) {
+    expected_callbacks_.emplace(CallbackType::kSnapshot, frame_deadline,
+                                frame_time, frame_interval, frame_deadline,
+                                presentation_time);
+  }
+
+  MojoTimeTicks GetTimeTicksNow() {
+    return task_runner_->NowTicks().ToInternalValue();
+  }
+
+  void Reset() {
+    task_runner_ = new base::TestMockTimeTaskRunner();
+    SchedulerCallbacks callbacks(
+        base::Bind(&VsyncSchedulerTest::OnUpdate, base::Unretained(this)),
+        base::Bind(&VsyncSchedulerTest::OnSnapshot, base::Unretained(this)));
+    scheduler_.reset(
+        new VsyncScheduler(task_runner_, callbacks,
+                           base::Bind(&VsyncSchedulerTest::GetTimeTicksNow,
+                                      base::Unretained(this))));
+
+    std::queue<ExpectedCallback> victim;
+    expected_callbacks_.swap(victim);
+  }
+
+  void FastForwardTo(int64_t time) {
+    DCHECK(time >= GetTimeTicksNow());
+    task_runner_->FastForwardBy(
+        base::TimeDelta::FromMicroseconds(time - GetTimeTicksNow()));
+  }
+
+  std::unique_ptr<VsyncScheduler> scheduler_;
+
+ private:
+  enum class CallbackType {
+    kUpdate,
+    kSnapshot,
+  };
+
+  struct ExpectedCallback {
+    ExpectedCallback(CallbackType type,
+                     int64_t delivery_time,
+                     int64_t frame_time,
+                     uint64_t frame_interval,
+                     int64_t frame_deadline,
+                     int64_t presentation_time)
+        : type(type),
+          delivery_time(delivery_time),
+          frame_time(frame_time),
+          frame_interval(frame_interval),
+          frame_deadline(frame_deadline),
+          presentation_time(presentation_time) {}
+
+    CallbackType type;
+    int64_t delivery_time;
+    int64_t frame_time;
+    uint64_t frame_interval;
+    int64_t frame_deadline;
+    int64_t presentation_time;
+  };
+
+  void OnUpdate(const mojo::gfx::composition::FrameInfo& frame_info) {
+    VerifyCallback(CallbackType::kUpdate, frame_info);
+  }
+
+  void OnSnapshot(const mojo::gfx::composition::FrameInfo& frame_info) {
+    VerifyCallback(CallbackType::kSnapshot, frame_info);
+  }
+
+  void VerifyCallback(CallbackType type,
+                      const mojo::gfx::composition::FrameInfo& frame_info) {
+    EXPECT_FALSE(expected_callbacks_.empty());
+    if (!expected_callbacks_.empty()) {
+      const ExpectedCallback& c = expected_callbacks_.front();
+      EXPECT_EQ(static_cast<int>(c.type), static_cast<int>(type));
+      EXPECT_EQ(c.delivery_time, GetTimeTicksNow());
+      EXPECT_EQ(c.frame_time, frame_info.frame_time);
+      EXPECT_EQ(c.frame_interval, frame_info.frame_interval);
+      EXPECT_EQ(c.frame_deadline, frame_info.frame_deadline);
+      EXPECT_EQ(c.presentation_time, frame_info.presentation_time);
+      expected_callbacks_.pop();
+    }
+  }
+
+  scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
+  std::queue<ExpectedCallback> expected_callbacks_;
+};
+
+TEST_F(VsyncSchedulerTest, StartValidatesArguments) {
+  // Vsync timebase is in the past.
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+  Reset();
+
+  // Vsync timebase is now.  (current time == 0)
+  EXPECT_TRUE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase, kSnapshotPhase,
+                                kPresentationPhase));
+  Reset();
+
+  // Vsync timebase in the future.  (current time == 0)
+  EXPECT_FALSE(scheduler_->Start(1, kVsyncInterval, kUpdatePhase,
+                                 kSnapshotPhase, kPresentationPhase));
+
+  // Vsync interval too small.
+  EXPECT_FALSE(
+      scheduler_->Start(0, VsyncScheduler::kMinVsyncInterval - 1, 0, 0, 0));
+
+  // Vsync interval at minimum.
+  EXPECT_TRUE(scheduler_->Start(0, VsyncScheduler::kMinVsyncInterval, 0, 0, 0));
+  Reset();
+
+  // Vsync interval at maximum.
+  EXPECT_TRUE(scheduler_->Start(0, VsyncScheduler::kMaxVsyncInterval, 0, 0, 0));
+  Reset();
+
+  // Vsync interval too large.
+  EXPECT_FALSE(
+      scheduler_->Start(0, VsyncScheduler::kMaxVsyncInterval + 1, 0, 0, 0));
+
+  // Snapshot phase earlier than update phase.
+  EXPECT_FALSE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase,
+                                 kUpdatePhase - 1, kPresentationPhase));
+
+  // Snapshot phase more than one frame behind update phase.
+  EXPECT_FALSE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase,
+                                 kUpdatePhase + kVsyncInterval + 1,
+                                 kPresentationPhase));
+
+  // Presentation phase earlier than snapshot phase.
+  EXPECT_FALSE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase,
+                                 kSnapshotPhase, kSnapshotPhase - 1));
+
+  // Minimum and maximum update vs. snapshot phase delta.
+  EXPECT_TRUE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase, kUpdatePhase,
+                                kUpdatePhase));
+  Reset();
+  EXPECT_TRUE(scheduler_->Start(0, kVsyncInterval, kUpdatePhase,
+                                kUpdatePhase + kVsyncInterval,
+                                kUpdatePhase + kVsyncInterval));
+  Reset();
+}
+
+TEST_F(VsyncSchedulerTest, ScheduleRedundantSnapshot) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Shortly after the first update, schedule another snapshot.
+  // Nothing happens because a snapshot is still due at 14000.
+  FastForwardTo(8000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kSnapshot);
+}
+
+TEST_F(VsyncSchedulerTest, ScheduleRedundantUpdate) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Before the first update, schedule another update.
+  // Nothing happens because an update is still due at 6000.
+  FastForwardTo(5000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+}
+
+TEST_F(VsyncSchedulerTest, ScheduleRequiredSnapshot) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Shortly after the last snapshot, schedule another snapshot.
+  FastForwardTo(15000);
+  ExpectUpdateCallback(16000, kVsyncInterval, 24000, 27000);
+  ExpectSnapshotCallback(16000, kVsyncInterval, 24000, 27000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kSnapshot);
+
+  // Exactly at the moment of the next snapshot, schedule another snapshot.
+  FastForwardTo(24000);
+  ExpectUpdateCallback(26000, kVsyncInterval, 34000, 37000);
+  ExpectSnapshotCallback(26000, kVsyncInterval, 34000, 37000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kSnapshot);
+
+  // A long time thereafter, with no time to update, schedule another snapshot.
+  FastForwardTo(53000);
+  ExpectSnapshotCallback(46000, kVsyncInterval, 54000, 57000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kSnapshot);
+
+  // A long time thereafter, with time to update, schedule another snapshot.
+  FastForwardTo(75000);
+  ExpectUpdateCallback(76000, kVsyncInterval, 84000, 87000);
+  ExpectSnapshotCallback(76000, kVsyncInterval, 84000, 87000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kSnapshot);
+}
+
+TEST_F(VsyncSchedulerTest, ScheduleRequiredUpdate) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Shortly after the first update, schedule another update.
+  FastForwardTo(8000);
+  ExpectUpdateCallback(16000, kVsyncInterval, 24000, 27000);
+  ExpectSnapshotCallback(16000, kVsyncInterval, 24000, 27000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // Exactly at the moment of the next update, schedule another update.
+  FastForwardTo(16000);
+  ExpectUpdateCallback(26000, kVsyncInterval, 34000, 37000);
+  ExpectSnapshotCallback(26000, kVsyncInterval, 34000, 37000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // A long time thereafter, with no time to snapshot, schedule another update.
+  FastForwardTo(55000);
+  ExpectUpdateCallback(56000, kVsyncInterval, 64000, 67000);
+  ExpectSnapshotCallback(56000, kVsyncInterval, 64000, 67000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // A long time thereafter, with time to snapshot, schedule another update.
+  FastForwardTo(83000);
+  ExpectSnapshotCallback(76000, kVsyncInterval, 84000, 87000);
+  ExpectUpdateCallback(86000, kVsyncInterval, 94000, 97000);
+  ExpectSnapshotCallback(86000, kVsyncInterval, 94000, 97000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+}
+
+TEST_F(VsyncSchedulerTest, StartAndStop) {
+  // Scheduling frames before start does nothing.
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // Starting the scheduler automatically schedules an update.
+  FastForwardTo(15000);
+  ExpectUpdateCallback(16000, kVsyncInterval, 24000, 27000);
+  ExpectSnapshotCallback(16000, kVsyncInterval, 24000, 27000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Stopping the scheduler suspends further updates.
+  FastForwardTo(24000);
+  scheduler_->Stop();
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // Restarting scheduling resumes updates.
+  FastForwardTo(53000);
+  ExpectSnapshotCallback(46000, kVsyncInterval, 54000, 57000);
+  ExpectUpdateCallback(56000, kVsyncInterval, 64000, 67000);
+  ExpectSnapshotCallback(56000, kVsyncInterval, 64000, 67000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Stopping the scheduler cancels undelivered updates.
+  FastForwardTo(63000);
+  // canceled: ExpectUpdateCallback(66000, kVsyncInterval, 74000, 77000);
+  // canceled: ExpectSnapshotCallback(66000, kVsyncInterval, 74000, 77000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+  FastForwardTo(65000);
+  scheduler_->Stop();
+}
+
+TEST_F(VsyncSchedulerTest, RedundantStart) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // Doing it again has no added effect.
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // A long time thereafter, schedule another update.
+  FastForwardTo(55000);
+  ExpectUpdateCallback(56000, kVsyncInterval, 64000, 67000);
+  ExpectSnapshotCallback(56000, kVsyncInterval, 64000, 67000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+}
+
+TEST_F(VsyncSchedulerTest, StartWithNewParameters) {
+  // Start immediately schedules work.
+  ExpectSnapshotCallback(-4000, kVsyncInterval, 4000, 7000);
+  ExpectUpdateCallback(6000, kVsyncInterval, 14000, 17000);
+  ExpectSnapshotCallback(6000, kVsyncInterval, 14000, 17000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+
+  // After the snapshot is delivered, change parameters.
+  FastForwardTo(14000);
+  ExpectUpdateCallback(17000, kVsyncInterval * 2, 33000, 39000);
+  ExpectSnapshotCallback(17000, kVsyncInterval * 2, 33000, 39000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval * 2,
+                                kUpdatePhase * 2, kSnapshotPhase * 2,
+                                kPresentationPhase * 2));
+
+  // Schedule another update with these parameters.
+  FastForwardTo(18000);
+  ExpectUpdateCallback(37000, kVsyncInterval * 2, 53000, 59000);
+  // canceled: ExpectSnapshotCallback(37000, kVsyncInterval * 2, 53000, 59000);
+  scheduler_->ScheduleFrame(Scheduler::SchedulingMode::kUpdateAndSnapshot);
+
+  // At the moment when the update is delivered, change parameters again.
+  // We're too late to cancel the prior update but we do cancel the prior
+  // snapshot and we'll follow it up with another update with the new
+  // parameters.
+  FastForwardTo(37000);
+  ExpectUpdateCallback(46000, kVsyncInterval, 54000, 57000);
+  ExpectSnapshotCallback(46000, kVsyncInterval, 54000, 57000);
+  EXPECT_TRUE(scheduler_->Start(kVsyncTimebase, kVsyncInterval, kUpdatePhase,
+                                kSnapshotPhase, kPresentationPhase));
+}
+
+// TODO(jeffbrown): Add tests for cases where the compositor has fallen behind.
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/compositor_app.cc b/services/gfx/compositor/compositor_app.cc
new file mode 100644
index 0000000..5fd8a9a
--- /dev/null
+++ b/services/gfx/compositor/compositor_app.cc
@@ -0,0 +1,50 @@
+// 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/compositor_app.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "services/gfx/compositor/compositor_impl.h"
+
+namespace compositor {
+
+CompositorApp::CompositorApp() : app_impl_(nullptr) {}
+
+CompositorApp::~CompositorApp() {}
+
+void CompositorApp::Initialize(mojo::ApplicationImpl* app_impl) {
+  app_impl_ = app_impl;
+
+  auto command_line = base::CommandLine::ForCurrentProcess();
+  command_line->InitFromArgv(app_impl_->args());
+  logging::LoggingSettings settings;
+  settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  logging::InitLogging(settings);
+
+  tracing_.Initialize(app_impl_);
+
+  engine_.reset(new CompositorEngine());
+}
+
+bool CompositorApp::ConfigureIncomingConnection(
+    mojo::ApplicationConnection* connection) {
+  connection->AddService<mojo::gfx::composition::Compositor>(this);
+  return true;
+}
+
+void CompositorApp::Create(
+    mojo::ApplicationConnection* connection,
+    mojo::InterfaceRequest<mojo::gfx::composition::Compositor> request) {
+  compositor_bindings_.AddBinding(new CompositorImpl(engine_.get()),
+                                  request.Pass());
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/compositor_app.h b/services/gfx/compositor/compositor_app.h
new file mode 100644
index 0000000..c80c5d7
--- /dev/null
+++ b/services/gfx/compositor/compositor_app.h
@@ -0,0 +1,51 @@
+// 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_COMPOSITOR_APP_H_
+#define SERVICES_GFX_COMPOSITOR_COMPOSITOR_APP_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/common/strong_binding_set.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
+#include "services/gfx/compositor/compositor_engine.h"
+
+namespace compositor {
+
+// Compositor application entry point.
+class CompositorApp
+    : public mojo::ApplicationDelegate,
+      public mojo::InterfaceFactory<mojo::gfx::composition::Compositor> {
+ public:
+  CompositorApp();
+  ~CompositorApp() override;
+
+ private:
+  // |ApplicationDelegate|:
+  void Initialize(mojo::ApplicationImpl* app_impl) override;
+  bool ConfigureIncomingConnection(
+      mojo::ApplicationConnection* connection) override;
+
+  // |InterfaceFactory<Compositor>|:
+  void Create(mojo::ApplicationConnection* connection,
+              mojo::InterfaceRequest<mojo::gfx::composition::Compositor>
+                  request) override;
+
+  mojo::ApplicationImpl* app_impl_;
+  mojo::TracingImpl tracing_;
+
+  mojo::StrongBindingSet<mojo::gfx::composition::Compositor>
+      compositor_bindings_;
+  std::unique_ptr<CompositorEngine> engine_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompositorApp);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_COMPOSITOR_APP_H_
diff --git a/services/gfx/compositor/compositor_engine.cc b/services/gfx/compositor/compositor_engine.cc
new file mode 100644
index 0000000..8f69908
--- /dev/null
+++ b/services/gfx/compositor/compositor_engine.cc
@@ -0,0 +1,459 @@
+// 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/compositor_engine.h"
+
+#include <algorithm>
+#include <sstream>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/time/time.h"
+#include "mojo/services/gfx/composition/cpp/formatting.h"
+#include "services/gfx/compositor/backend/gpu_output.h"
+#include "services/gfx/compositor/graph/snapshot.h"
+#include "services/gfx/compositor/render/render_frame.h"
+#include "services/gfx/compositor/renderer_impl.h"
+#include "services/gfx/compositor/scene_impl.h"
+
+namespace compositor {
+namespace {
+// TODO(jeffbrown): Determine and document a more appropriate size limit
+// for viewports somewhere.  May be limited by the renderer output.
+const int32_t kMaxViewportWidth = 65536;
+const int32_t kMaxViewportHeight = 65536;
+
+std::string SanitizeLabel(const mojo::String& label) {
+  return label.get().substr(0, mojo::gfx::composition::kLabelMaxLength);
+}
+}  // namespace
+
+CompositorEngine::CompositorEngine() : weak_factory_(this) {}
+
+CompositorEngine::~CompositorEngine() {}
+
+mojo::gfx::composition::SceneTokenPtr CompositorEngine::CreateScene(
+    mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request,
+    const mojo::String& label) {
+  auto scene_token = mojo::gfx::composition::SceneToken::New();
+  scene_token->value = next_scene_token_value_++;
+  CHECK(scene_token->value);
+  CHECK(!FindScene(scene_token->value));
+
+  // Create the state and bind implementation to it.
+  SceneState* scene_state =
+      new SceneState(scene_token.Pass(), SanitizeLabel(label));
+  SceneImpl* scene_impl =
+      new SceneImpl(this, scene_state, scene_request.Pass());
+  scene_state->set_scene_impl(scene_impl);
+  base::Closure error_handler =
+      base::Bind(&CompositorEngine::OnSceneConnectionError,
+                 base::Unretained(this), scene_state);
+  scene_impl->set_connection_error_handler(error_handler);
+
+  // Add to registry.
+  scenes_by_token_.insert({scene_state->scene_token()->value, scene_state});
+  DVLOG(1) << "CreateScene: scene=" << scene_state;
+  return scene_state->scene_token()->Clone();
+}
+
+void CompositorEngine::OnSceneConnectionError(SceneState* scene_state) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "OnSceneConnectionError: scene=" << scene_state;
+
+  DestroyScene(scene_state);
+}
+
+void CompositorEngine::DestroyScene(SceneState* scene_state) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "DestroyScene: scene=" << scene_state;
+
+  // Unlink from other scenes.
+  for (auto& pair : scenes_by_token_) {
+    SceneState* other_scene_state = pair.second;
+    other_scene_state->scene_def()->UnlinkReferencedScene(
+        scene_state->scene_def(),
+        base::Bind(&CompositorEngine::SendResourceUnavailable,
+                   base::Unretained(this),
+                   base::Unretained(other_scene_state)));
+  }
+
+  // Destroy any renderers using this scene.
+  for (auto& renderer : renderers_) {
+    if (renderer->root_scene() == scene_state) {
+      LOG(ERROR) << "Destroying renderer whose root scene has become "
+                    "unavailable: renderer="
+                 << renderer;
+      DestroyRenderer(renderer);
+    }
+  }
+
+  // Destroy.
+  InvalidateScene(scene_state);
+
+  // Remove from registry.
+  scenes_by_token_.erase(scene_state->scene_token()->value);
+  delete scene_state;
+}
+
+void CompositorEngine::CreateRenderer(
+    mojo::ContextProviderPtr context_provider,
+    mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request,
+    const mojo::String& label) {
+  DCHECK(context_provider);
+  uint32_t renderer_id = next_renderer_id_++;
+
+  // Create the state and bind implementation to it.
+  RendererState* renderer_state =
+      new RendererState(renderer_id, SanitizeLabel(label));
+  RendererImpl* renderer_impl =
+      new RendererImpl(this, renderer_state, renderer_request.Pass());
+  renderer_state->set_renderer_impl(renderer_impl);
+  renderer_impl->set_connection_error_handler(
+      base::Bind(&CompositorEngine::OnRendererConnectionError,
+                 base::Unretained(this), renderer_state));
+
+  // Create the renderer.
+  SchedulerCallbacks scheduler_callbacks(
+      base::Bind(&CompositorEngine::OnOutputUpdateRequest,
+                 weak_factory_.GetWeakPtr(), renderer_state->GetWeakPtr()),
+      base::Bind(&CompositorEngine::OnOutputSnapshotRequest,
+                 weak_factory_.GetWeakPtr(), renderer_state->GetWeakPtr()));
+  std::unique_ptr<Output> output(new GpuOutput(
+      context_provider.Pass(), scheduler_callbacks,
+      base::Bind(&CompositorEngine::OnOutputError, weak_factory_.GetWeakPtr(),
+                 renderer_state->GetWeakPtr())));
+  renderer_state->set_output(std::move(output));
+
+  // Add to registry.
+  renderers_.push_back(renderer_state);
+  DVLOG(1) << "CreateRenderer: " << renderer_state;
+}
+
+void CompositorEngine::OnRendererConnectionError(
+    RendererState* renderer_state) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DVLOG(1) << "OnRendererConnectionError: renderer=" << renderer_state;
+
+  DestroyRenderer(renderer_state);
+}
+
+void CompositorEngine::DestroyRenderer(RendererState* renderer_state) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DVLOG(1) << "DestroyRenderer: renderer=" << renderer_state;
+
+  // Remove from registry.
+  renderers_.erase(
+      std::find(renderers_.begin(), renderers_.end(), renderer_state));
+  delete renderer_state;
+}
+
+void CompositorEngine::SetListener(
+    SceneState* scene_state,
+    mojo::gfx::composition::SceneListenerPtr listener) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "SetSceneListener: scene=" << scene_state;
+
+  scene_state->set_scene_listener(listener.Pass());
+}
+
+void CompositorEngine::Update(SceneState* scene_state,
+                              mojo::gfx::composition::SceneUpdatePtr update) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "Update: scene=" << scene_state << ", update=" << update;
+
+  scene_state->scene_def()->EnqueueUpdate(update.Pass());
+}
+
+void CompositorEngine::Publish(
+    SceneState* scene_state,
+    mojo::gfx::composition::SceneMetadataPtr metadata) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "Publish: scene=" << scene_state << ", metadata=" << metadata;
+
+  if (!metadata)
+    metadata = mojo::gfx::composition::SceneMetadata::New();
+  int64_t presentation_time = metadata->presentation_time;
+  scene_state->scene_def()->EnqueuePublish(metadata.Pass());
+
+  // Implicitly schedule fresh snapshots.
+  InvalidateScene(scene_state);
+
+  // Ensure that the scene will be presented eventually, even if it is
+  // not associated with any renderer.  Note that this is only a backstop
+  // in case the scene does not get presented sooner as part of snapshotting
+  // a renderer.  Note that scenes which are actually visible will be
+  // snapshotted by the renderer when it comes time to draw the next frame,
+  // so this special case is only designed to help with scenes that are
+  // not visible to ensure that we will still apply pending updates which
+  // might have side-effects on the client's state (such as closing the
+  // connection due to an error or releasing resources).
+  MojoTimeTicks now = MojoGetTimeTicksNow();
+  DCHECK(now >= 0);
+  if (presentation_time <= now) {
+    SceneDef::Disposition disposition = PresentScene(scene_state, now);
+    if (disposition == SceneDef::Disposition::kFailed)
+      DestroyScene(scene_state);
+  } else {
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, base::Bind(&CompositorEngine::OnPresentScene,
+                              weak_factory_.GetWeakPtr(),
+                              scene_state->GetWeakPtr(), presentation_time),
+        base::TimeDelta::FromMicroseconds(presentation_time - now));
+  }
+}
+
+void CompositorEngine::ScheduleFrame(SceneState* scene_state,
+                                     const SceneFrameCallback& callback) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+
+  scene_state->AddSceneFrameCallback(callback);
+
+  // TODO(jeffbrown): Be more selective and do this work only for scenes
+  // which are strongly associated with the renderer so it doesn't receive
+  // conflicting timing signals coming from multiple renderers.
+  for (auto& renderer : renderers_) {
+    ScheduleFrameForRenderer(renderer,
+                             Scheduler::SchedulingMode::kUpdateAndSnapshot);
+  }
+}
+
+void CompositorEngine::SetRootScene(
+    RendererState* renderer_state,
+    mojo::gfx::composition::SceneTokenPtr scene_token,
+    uint32 scene_version,
+    mojo::RectPtr viewport) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DCHECK(scene_token);
+  DCHECK(viewport);
+  DVLOG(1) << "SetRootScene: renderer=" << renderer_state
+           << ", scene_token=" << scene_token
+           << ", scene_version=" << scene_version << ", viewport=" << viewport;
+
+  if (viewport->width <= 0 || viewport->width > kMaxViewportWidth ||
+      viewport->height <= 0 || viewport->height > kMaxViewportHeight) {
+    LOG(ERROR) << "Invalid viewport size: " << viewport;
+    DestroyRenderer(renderer_state);
+    return;
+  }
+
+  // Find the scene.
+  SceneState* scene_state = FindScene(scene_token->value);
+  if (!scene_state) {
+    LOG(ERROR) << "Could not set the renderer's root scene, scene not found: "
+                  "scene_token="
+               << scene_token;
+    DestroyRenderer(renderer_state);
+    return;
+  }
+
+  // Update the root.
+  if (renderer_state->SetRootScene(scene_state, scene_version, *viewport)) {
+    ScheduleFrameForRenderer(renderer_state,
+                             Scheduler::SchedulingMode::kSnapshot);
+  }
+}
+
+void CompositorEngine::HitTest(
+    RendererState* renderer_state,
+    mojo::PointPtr point,
+    const mojo::gfx::composition::HitTester::HitTestCallback& callback) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  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);
+  }
+
+  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;
+}
+
+void CompositorEngine::SendResourceUnavailable(SceneState* scene_state,
+                                               uint32_t resource_id) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(2) << "SendResourceUnavailable: resource_id=" << resource_id;
+
+  // TODO: Detect ANRs
+  if (scene_state->scene_listener()) {
+    scene_state->scene_listener()->OnResourceUnavailable(
+        resource_id, base::Bind(&base::DoNothing));
+  }
+}
+
+SceneState* CompositorEngine::FindScene(uint32_t scene_token) {
+  auto it = scenes_by_token_.find(scene_token);
+  return it != scenes_by_token_.end() ? it->second : nullptr;
+}
+
+void CompositorEngine::InvalidateScene(SceneState* scene_state) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(2) << "InvalidateScene: scene=" << scene_state;
+
+  for (auto& renderer : renderers_) {
+    if (renderer->snapshot() &&
+        renderer->snapshot()->InvalidateScene(scene_state->scene_def())) {
+      ScheduleFrameForRenderer(renderer, Scheduler::SchedulingMode::kSnapshot);
+    }
+  }
+}
+
+SceneDef::Disposition CompositorEngine::PresentScene(
+    SceneState* scene_state,
+    int64_t presentation_time) {
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(2) << "PresentScene: scene=" << scene_state;
+
+  std::ostringstream errs;
+  SceneDef::Disposition disposition = scene_state->scene_def()->Present(
+      presentation_time, base::Bind(&CompositorEngine::ResolveSceneReference,
+                                    base::Unretained(this)),
+      base::Bind(&CompositorEngine::SendResourceUnavailable,
+                 base::Unretained(this), base::Unretained(scene_state)),
+      errs);
+  if (disposition == SceneDef::Disposition::kFailed) {
+    LOG(ERROR) << "Scene published invalid updates: scene=" << scene_state;
+    LOG(ERROR) << errs.str();
+    // Caller is responsible for destroying the scene.
+  }
+  return disposition;
+}
+
+void CompositorEngine::PresentRenderer(RendererState* renderer_state,
+                                       int64_t presentation_time) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DVLOG(2) << "PresentRenderer: renderer_state=" << renderer_state;
+
+  // TODO(jeffbrown): Be more selective and do this work only for scenes
+  // associated with the renderer that actually have pending updates.
+  std::vector<SceneState*> dead_scenes;
+  for (auto& pair : scenes_by_token_) {
+    SceneState* scene_state = pair.second;
+    SceneDef::Disposition disposition =
+        PresentScene(scene_state, presentation_time);
+    if (disposition == SceneDef::Disposition::kFailed)
+      dead_scenes.push_back(scene_state);
+  }
+  for (SceneState* scene_state : dead_scenes)
+    DestroyScene(scene_state);
+}
+
+void CompositorEngine::SnapshotRenderer(
+    RendererState* renderer_state,
+    const mojo::gfx::composition::FrameInfo& frame_info) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DVLOG(2) << "SnapshotRenderer: renderer_state=" << renderer_state;
+
+  if (VLOG_IS_ON(2)) {
+    std::ostringstream block_log;
+    SnapshotRendererInner(renderer_state, frame_info, &block_log);
+    if (!renderer_state->valid()) {
+      DVLOG(2) << "Rendering completely blocked: " << block_log.str();
+    } else if (!block_log.str().empty()) {
+      DVLOG(2) << "Rendering partially blocked: " << block_log.str();
+    } else {
+      DVLOG(2) << "Rendering unblocked";
+    }
+  } else {
+    SnapshotRendererInner(renderer_state, frame_info, nullptr);
+  }
+}
+
+void CompositorEngine::SnapshotRendererInner(
+    RendererState* renderer_state,
+    const mojo::gfx::composition::FrameInfo& frame_info,
+    std::ostream* block_log) {
+  if (!renderer_state->root_scene()) {
+    if (block_log)
+      *block_log << "No root scene" << std::endl;
+    renderer_state->SetSnapshot(nullptr);
+    return;
+  }
+
+  SnapshotBuilder builder(block_log);
+  renderer_state->SetSnapshot(
+      builder.Build(renderer_state->root_scene()->scene_def(),
+                    renderer_state->root_scene_viewport(), frame_info));
+
+  if (renderer_state->valid())
+    renderer_state->output()->SubmitFrame(renderer_state->frame());
+}
+
+void CompositorEngine::ScheduleFrameForRenderer(
+    RendererState* renderer_state,
+    Scheduler::SchedulingMode scheduling_mode) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  renderer_state->output()->GetScheduler()->ScheduleFrame(scheduling_mode);
+}
+
+void CompositorEngine::OnOutputError(
+    const base::WeakPtr<RendererState>& renderer_state_weak) {
+  RendererState* renderer_state = renderer_state_weak.get();
+  if (!renderer_state)
+    return;
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+
+  LOG(ERROR) << "Renderer encountered a fatal error: renderer="
+             << renderer_state;
+
+  DestroyRenderer(renderer_state);
+}
+
+void CompositorEngine::OnOutputUpdateRequest(
+    const base::WeakPtr<RendererState>& renderer_state_weak,
+    const mojo::gfx::composition::FrameInfo& frame_info) {
+  RendererState* renderer_state = renderer_state_weak.get();
+  if (!renderer_state)
+    return;
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+
+  // TODO(jeffbrown): Be more selective and do this work only for scenes
+  // associated with the renderer.
+  for (auto& pair : scenes_by_token_) {
+    pair.second->DispatchSceneFrameCallbacks(frame_info);
+  }
+}
+
+void CompositorEngine::OnOutputSnapshotRequest(
+    const base::WeakPtr<RendererState>& renderer_state_weak,
+    const mojo::gfx::composition::FrameInfo& frame_info) {
+  RendererState* renderer_state = renderer_state_weak.get();
+  if (!renderer_state)
+    return;
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+
+  PresentRenderer(renderer_state, frame_info.presentation_time);
+  SnapshotRenderer(renderer_state, frame_info);
+}
+
+void CompositorEngine::OnPresentScene(
+    const base::WeakPtr<SceneState>& scene_state_weak,
+    int64_t presentation_time) {
+  SceneState* scene_state = scene_state_weak.get();
+  if (!scene_state)
+    return;
+  DCHECK(IsSceneStateRegisteredDebug(scene_state));
+
+  SceneDef::Disposition disposition =
+      PresentScene(scene_state, presentation_time);
+  if (disposition == SceneDef::Disposition::kFailed)
+    DestroyScene(scene_state);
+  else if (disposition == SceneDef::Disposition::kSucceeded)
+    InvalidateScene(scene_state);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/compositor_engine.h b/services/gfx/compositor/compositor_engine.h
new file mode 100644
index 0000000..c9d459d
--- /dev/null
+++ b/services/gfx/compositor/compositor_engine.h
@@ -0,0 +1,137 @@
+// 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_COMPOSITOR_H_
+#define SERVICES_GFX_COMPOSITOR_COMPOSITOR_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
+#include "services/gfx/compositor/backend/scheduler.h"
+#include "services/gfx/compositor/renderer_state.h"
+#include "services/gfx/compositor/scene_state.h"
+
+namespace compositor {
+
+// Core of the compositor.
+// All SceneState and RendererState objects are owned by the engine.
+class CompositorEngine {
+ public:
+  explicit CompositorEngine();
+  ~CompositorEngine();
+
+  // COMPOSITOR REQUESTS
+
+  // Registers a scene.
+  mojo::gfx::composition::SceneTokenPtr CreateScene(
+      mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request,
+      const mojo::String& label);
+
+  // Creates a scene graph renderer.
+  void CreateRenderer(
+      mojo::ContextProviderPtr context_provider,
+      mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request,
+      const mojo::String& label);
+
+  // SCENE REQUESTS
+
+  // Sets the scene listener.
+  void SetListener(SceneState* scene_state,
+                   mojo::gfx::composition::SceneListenerPtr listener);
+
+  // Updates a scene.
+  // Destroys |scene_state| if an error occurs.
+  void Update(SceneState* scene_state,
+              mojo::gfx::composition::SceneUpdatePtr update);
+
+  // Publishes a scene.
+  // Destroys |scene_state| if an error occurs.
+  void Publish(SceneState* scene_state,
+               mojo::gfx::composition::SceneMetadataPtr metadata);
+
+  // Schedules a frame callback.
+  void ScheduleFrame(SceneState* scene_state,
+                     const SceneFrameCallback& callback);
+
+  // RENDERER REQUESTS
+
+  // Sets the root scene.
+  // Destroys |renderer_state| if an error occurs.
+  void SetRootScene(RendererState* renderer_state,
+                    mojo::gfx::composition::SceneTokenPtr scene_token,
+                    uint32 scene_version,
+                    mojo::RectPtr viewport);
+
+  // Performs a hit test.
+  void HitTest(
+      RendererState* renderer_state,
+      mojo::PointPtr point,
+      const mojo::gfx::composition::HitTester::HitTestCallback& callback);
+
+ private:
+  void OnSceneConnectionError(SceneState* scene_state);
+  void DestroyScene(SceneState* scene_state);
+
+  void OnRendererConnectionError(RendererState* renderer_state);
+  void DestroyRenderer(RendererState* renderer_state);
+
+  void InvalidateScene(SceneState* scene_state);
+  SceneDef::Disposition PresentScene(SceneState* scene_state,
+                                     int64_t presentation_time);
+
+  void PresentRenderer(RendererState* renderer_state,
+                       int64_t presentation_time);
+  void SnapshotRenderer(RendererState* renderer_state,
+                        const mojo::gfx::composition::FrameInfo& frame_info);
+  void SnapshotRendererInner(
+      RendererState* renderer_state,
+      const mojo::gfx::composition::FrameInfo& frame_info,
+      std::ostream* block_log);
+  void ScheduleFrameForRenderer(RendererState* renderer_state,
+                                Scheduler::SchedulingMode scheduling_mode);
+
+  void OnOutputError(const base::WeakPtr<RendererState>& renderer_state_weak);
+  void OnOutputUpdateRequest(
+      const base::WeakPtr<RendererState>& renderer_state_weak,
+      const mojo::gfx::composition::FrameInfo& frame_info);
+  void OnOutputSnapshotRequest(
+      const base::WeakPtr<RendererState>& renderer_state_weak,
+      const mojo::gfx::composition::FrameInfo& frame_info);
+  void OnPresentScene(const base::WeakPtr<SceneState>& scene_state_weak,
+                      int64_t presentation_time);
+
+  SceneDef* ResolveSceneReference(
+      mojo::gfx::composition::SceneToken* scene_token);
+  void SendResourceUnavailable(SceneState* scene_state, uint32_t resource_id);
+
+  SceneState* FindScene(uint32_t scene_token);
+
+  bool IsSceneStateRegisteredDebug(SceneState* scene_state) {
+    return scene_state && FindScene(scene_state->scene_token()->value);
+  }
+  bool IsRendererStateRegisteredDebug(RendererState* renderer_state) {
+    return renderer_state &&
+           std::any_of(renderers_.begin(), renderers_.end(),
+                       [renderer_state](RendererState* other) {
+                         return renderer_state == other;
+                       });
+  }
+
+  uint32_t next_scene_token_value_ = 1u;
+  uint32_t next_renderer_id_ = 1u;
+  std::unordered_map<uint32_t, SceneState*> scenes_by_token_;
+  std::vector<RendererState*> renderers_;
+
+  base::WeakPtrFactory<CompositorEngine> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompositorEngine);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_COMPOSITOR_H_
diff --git a/services/gfx/compositor/compositor_impl.cc b/services/gfx/compositor/compositor_impl.cc
new file mode 100644
index 0000000..5d14003
--- /dev/null
+++ b/services/gfx/compositor/compositor_impl.cc
@@ -0,0 +1,32 @@
+// 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/compositor_impl.h"
+
+#include "services/gfx/compositor/scene_impl.h"
+
+namespace compositor {
+
+CompositorImpl::CompositorImpl(CompositorEngine* engine) : engine_(engine) {}
+
+CompositorImpl::~CompositorImpl() {}
+
+void CompositorImpl::CreateScene(
+    mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request,
+    const mojo::String& label,
+    const CreateSceneCallback& callback) {
+  mojo::gfx::composition::SceneTokenPtr scene_token =
+      engine_->CreateScene(scene_request.Pass(), label);
+  callback.Run(scene_token.Pass());
+}
+
+void CompositorImpl::CreateRenderer(
+    mojo::ContextProviderPtr context_provider,
+    mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request,
+    const mojo::String& label) {
+  engine_->CreateRenderer(context_provider.Pass(), renderer_request.Pass(),
+                          label);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/compositor_impl.h b/services/gfx/compositor/compositor_impl.h
new file mode 100644
index 0000000..78e35ad
--- /dev/null
+++ b/services/gfx/compositor/compositor_impl.h
@@ -0,0 +1,39 @@
+// 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_COMPOSITOR_IMPL_H_
+#define SERVICES_GFX_COMPOSITOR_COMPOSITOR_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/common/strong_binding_set.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
+#include "services/gfx/compositor/compositor_engine.h"
+
+namespace compositor {
+
+// Compositor interface implementation.
+class CompositorImpl : public mojo::gfx::composition::Compositor {
+ public:
+  explicit CompositorImpl(CompositorEngine* engine);
+  ~CompositorImpl() override;
+
+ private:
+  // |Compositor|:
+  void CreateScene(
+      mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request,
+      const mojo::String& label,
+      const CreateSceneCallback& callback) override;
+  void CreateRenderer(
+      mojo::ContextProviderPtr context_provider,
+      mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request,
+      const mojo::String& label) override;
+
+  CompositorEngine* engine_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompositorImpl);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_COMPOSITOR_IMPL_H_
diff --git a/services/gfx/compositor/graph/node_def.cc b/services/gfx/compositor/graph/node_def.cc
new file mode 100644
index 0000000..3287301
--- /dev/null
+++ b/services/gfx/compositor/graph/node_def.cc
@@ -0,0 +1,328 @@
+// 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/graph/node_def.h"
+
+#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_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"
+
+namespace compositor {
+namespace {
+SkColor MakeSkColor(const mojo::gfx::composition::Color& color) {
+  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;
+}
+}  // 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)
+    : 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) {}
+
+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;
+      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);
+
+  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()
+                << "Node with MERGE combinator blocked since "
+                   "one of its children is blocked: "
+                << FormattedLabel(scene) << ", blocked child "
+                << child_node->FormattedLabel(scene) << std::endl;
+          }
+          return false;  // blocked
+        }
+      }
+      return true;
+    }
+
+    // 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());
+        }
+      }
+      return true;
+    }
+
+    // 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 (snapshot_builder->block_log()) {
+        *snapshot_builder->block_log()
+            << "Node with FALLBACK combinator blocked since "
+               "all of its children are blocked: "
+            << FormattedLabel(scene) << std::endl;
+      }
+      return false;  // blocked
+    }
+
+    default: {
+      if (snapshot_builder->block_log()) {
+        *snapshot_builder->block_log()
+            << "Unrecognized combinator: " << FormattedLabel(scene)
+            << std::endl;
+      }
+      return false;
+    }
+  }
+}
+
+std::string NodeDef::FormattedLabel(SceneDef* scene) {
+  return base::StringPrintf("%s[%d]", scene->FormattedLabel().c_str(),
+                            node_id_);
+}
+
+bool NodeOp::Validate(SceneDef* scene, NodeDef* node, std::ostream& err) {
+  return true;
+}
+
+RectNodeOp::RectNodeOp(const mojo::Rect& content_rect,
+                       const mojo::gfx::composition::Color& color)
+    : content_rect_(content_rect), color_(color) {}
+
+RectNodeOp::~RectNodeOp() {}
+
+bool RectNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
+                          RenderLayerBuilder* layer_builder,
+                          SceneDef* scene,
+                          NodeDef* node) {
+  if (!node->SnapshotChildren(snapshot_builder, layer_builder, scene))
+    return false;
+
+  SkPaint paint;
+  paint.setColor(MakeSkColor(color_));
+  layer_builder->DrawRect(content_rect_.To<SkRect>(), paint);
+  return true;
+}
+
+ImageNodeOp::ImageNodeOp(const mojo::Rect& content_rect,
+                         mojo::RectPtr image_rect,
+                         uint32 image_resource_id,
+                         mojo::gfx::composition::BlendPtr blend)
+    : content_rect_(content_rect),
+      image_rect_(image_rect.Pass()),
+      image_resource_id_(image_resource_id),
+      blend_(blend.Pass()) {}
+
+ImageNodeOp::~ImageNodeOp() {}
+
+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 ImageNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
+                           RenderLayerBuilder* layer_builder,
+                           SceneDef* scene,
+                           NodeDef* node) {
+  DCHECK(image_resource_);
+
+  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;
+  }
+
+  if (!node->SnapshotChildren(snapshot_builder, layer_builder, scene))
+    return false;
+
+  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());
+  return true;
+}
+
+SceneNodeOp::SceneNodeOp(uint32_t scene_resource_id, uint32_t scene_version)
+    : scene_resource_id_(scene_resource_id), scene_version_(scene_version) {}
+
+SceneNodeOp::~SceneNodeOp() {}
+
+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 SceneNodeOp::Snapshot(SnapshotBuilder* snapshot_builder,
+                           RenderLayerBuilder* layer_builder,
+                           SceneDef* scene,
+                           NodeDef* node) {
+  DCHECK(scene_resource_);
+
+  SceneDef* referenced_scene = scene_resource_->referenced_scene();
+  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;
+    }
+    return false;
+  }
+
+  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 (!node->SnapshotChildren(snapshot_builder, layer_builder, scene))
+    return false;
+
+  return referenced_scene->Snapshot(snapshot_builder, layer_builder);
+}
+
+LayerNodeOp::LayerNodeOp(const mojo::Size& size,
+                         mojo::gfx::composition::BlendPtr blend)
+    : size_(size), blend_(blend.Pass()) {}
+
+LayerNodeOp::~LayerNodeOp() {}
+
+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;
+
+  layer_builder->DrawSavedLayer(
+      children_layer_builder.Build(), content_rect,
+      blend_ ? MakePaintForBlend(*blend_) : SkPaint());
+  return true;
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/node_def.h b/services/gfx/compositor/graph/node_def.h
new file mode 100644
index 0000000..beff45b
--- /dev/null
+++ b/services/gfx/compositor/graph/node_def.h
@@ -0,0 +1,225 @@
+// 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_GRAPH_NODE_DEF_H_
+#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 "mojo/services/gfx/composition/interfaces/nodes.mojom.h"
+#include "services/gfx/compositor/graph/resource_def.h"
+
+namespace compositor {
+
+class RenderImage;
+class RenderLayerBuilder;
+class SceneDef;
+class SnapshotBuilder;
+class NodeOp;
+
+// Scene graph node definition.
+class 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();
+
+  uint32_t node_id() { return node_id_; }
+  const mojo::Transform* content_transform() {
+    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(); }
+
+  std::string FormattedLabel(SceneDef* scene);
+
+  // Updated by |Validate()|.
+  const std::vector<NodeDef*>& child_nodes() { return child_nodes_; }
+
+  // Validates and prepares the object for rendering.
+  // Returns true if successful, false if errors were reported.
+  bool Validate(SceneDef* scene, std::ostream& err);
+
+  // 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);
+
+  // 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);
+
+ private:
+  bool SnapshotInner(SnapshotBuilder* snapshot_builder,
+                     RenderLayerBuilder* layer_builder,
+                     SceneDef* scene);
+
+  uint32_t 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 {
+ public:
+  NodeOp() = default;
+  virtual ~NodeOp() = default;
+
+  // 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);
+
+  // 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;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NodeOp);
+};
+
+// A solid color filled rectangle node definition.
+class RectNodeOp : public NodeOp {
+ public:
+  RectNodeOp(const mojo::Rect& content_rect,
+             const mojo::gfx::composition::Color& color);
+  ~RectNodeOp() override;
+
+  const mojo::Rect& content_rect() { return content_rect_; }
+  const mojo::gfx::composition::Color& color() { return color_; }
+
+  bool Snapshot(SnapshotBuilder* snapshot_builder,
+                RenderLayerBuilder* layer_builder,
+                SceneDef* scene,
+                NodeDef* node) override;
+
+ private:
+  mojo::Rect content_rect_;
+  mojo::gfx::composition::Color color_;
+
+  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;
+
+ private:
+  mojo::Rect const content_rect_;
+  mojo::RectPtr const image_rect_;
+  uint32_t const image_resource_id_;
+  mojo::gfx::composition::BlendPtr const blend_;
+
+  ImageResourceDef* image_resource_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageNodeOp);
+};
+
+// An embedded scene node definition.
+class SceneNodeOp : public NodeOp {
+ public:
+  SceneNodeOp(uint32_t scene_resource_id, uint32_t scene_version);
+  ~SceneNodeOp() override;
+
+  uint32_t scene_resource_id() { return scene_resource_id_; }
+  uint32_t scene_version() { return scene_version_; }
+
+  // Updated by |Validate()|.
+  SceneResourceDef* scene_resource() { return scene_resource_; }
+
+  bool Validate(SceneDef* scene, NodeDef* node, std::ostream& err) override;
+
+  bool Snapshot(SnapshotBuilder* snapshot_builder,
+                RenderLayerBuilder* layer_builder,
+                SceneDef* scene,
+                NodeDef* node) override;
+
+ private:
+  uint32_t const scene_resource_id_;
+  uint32_t const scene_version_;
+
+  SceneResourceDef* scene_resource_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(SceneNodeOp);
+};
+
+// A composited layer node definition.
+class LayerNodeOp : public NodeOp {
+ public:
+  LayerNodeOp(const mojo::Size& size, mojo::gfx::composition::BlendPtr blend);
+  ~LayerNodeOp() override;
+
+  const mojo::Size& size() { return size_; }
+  mojo::gfx::composition::Blend* blend() { return blend_.get(); }
+
+  bool Snapshot(SnapshotBuilder* snapshot_builder,
+                RenderLayerBuilder* layer_builder,
+                SceneDef* scene,
+                NodeDef* node) override;
+
+ private:
+  mojo::Size const size_;
+  mojo::gfx::composition::BlendPtr const blend_;
+
+  DISALLOW_COPY_AND_ASSIGN(LayerNodeOp);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_NODE_DEF_H_
diff --git a/services/gfx/compositor/graph/resource_def.cc b/services/gfx/compositor/graph/resource_def.cc
new file mode 100644
index 0000000..f837f1e
--- /dev/null
+++ b/services/gfx/compositor/graph/resource_def.cc
@@ -0,0 +1,31 @@
+// 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/graph/resource_def.h"
+
+#include "base/logging.h"
+
+namespace compositor {
+
+SceneResourceDef::SceneResourceDef(SceneDef* referenced_scene)
+    : referenced_scene_(referenced_scene) {}
+
+SceneResourceDef::~SceneResourceDef() {}
+
+ResourceDef::Type SceneResourceDef::type() const {
+  return Type::kScene;
+}
+
+ImageResourceDef::ImageResourceDef(const std::shared_ptr<RenderImage>& image)
+    : image_(image) {
+  DCHECK(image);
+}
+
+ImageResourceDef::~ImageResourceDef() {}
+
+ResourceDef::Type ImageResourceDef::type() const {
+  return Type::kImage;
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/resource_def.h b/services/gfx/compositor/graph/resource_def.h
new file mode 100644
index 0000000..9f3d8c8
--- /dev/null
+++ b/services/gfx/compositor/graph/resource_def.h
@@ -0,0 +1,75 @@
+// 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_GRAPH_RESOURCE_DEF_H_
+#define SERVICES_GFX_COMPOSITOR_GRAPH_RESOURCE_DEF_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "mojo/services/gfx/composition/interfaces/resources.mojom.h"
+#include "services/gfx/compositor/render/render_image.h"
+
+namespace compositor {
+
+class SceneDef;
+
+// Abstract scene graph resource definition.
+class ResourceDef {
+ public:
+  enum class Type { kScene, kImage };
+
+  ResourceDef() = default;
+  virtual ~ResourceDef() = default;
+
+  // Gets the resource type.
+  virtual Type type() const = 0;
+
+ 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;
+
+  Type type() const override;
+
+  // The referenced scene, may be null if unavailable.
+  SceneDef* referenced_scene() { return referenced_scene_; }
+
+  // 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; }
+
+ private:
+  SceneDef* referenced_scene_;
+
+  DISALLOW_COPY_AND_ASSIGN(SceneResourceDef);
+};
+
+// Reference to an image expressed as a resource definition.
+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_; }
+
+ private:
+  std::shared_ptr<RenderImage> image_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageResourceDef);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_RESOURCE_DEF_H_
diff --git a/services/gfx/compositor/graph/scene_def.cc b/services/gfx/compositor/graph/scene_def.cc
new file mode 100644
index 0000000..ae77273
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_def.cc
@@ -0,0 +1,397 @@
+// 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/graph/scene_def.h"
+
+#include <ostream>
+
+#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/render/render_image.h"
+#include "services/gfx/compositor/render/render_layer.h"
+
+namespace compositor {
+
+namespace {
+// TODO(jeffbrown): Determine and document a more appropriate size limit
+// for transferred images as part of the image pipe abstraction instead.
+const int32_t kMaxTextureWidth = 65536;
+const int32_t kMaxTextureHeight = 65536;
+
+void ReleaseMailboxTexture(
+    mojo::gfx::composition::MailboxTextureCallbackPtr callback) {
+  if (callback)
+    callback->OnMailboxTextureReleased();
+}
+}  // 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() {}
+
+void SceneDef::EnqueueUpdate(mojo::gfx::composition::SceneUpdatePtr update) {
+  DCHECK(update);
+  pending_updates_.push_back(update.Pass());
+}
+
+void SceneDef::EnqueuePublish(
+    mojo::gfx::composition::SceneMetadataPtr metadata) {
+  DCHECK(metadata);
+  pending_publications_.emplace_back(new Publication(metadata.Pass()));
+  pending_updates_.swap(pending_publications_.back()->updates);
+}
+
+SceneDef::Disposition SceneDef::Present(
+    int64_t presentation_time,
+    const SceneResolver& resolver,
+    const SceneUnavailableSender& unavailable_sender,
+    std::ostream& err) {
+  // Walk backwards through the pending publications to find the index
+  // just beyond the last one which is due to be presented at or before the
+  // presentation time.
+  size_t end = pending_publications_.size();
+  for (;;) {
+    if (!end)
+      return Disposition::kUnchanged;
+    if (pending_publications_[end - 1]->is_due(presentation_time))
+      break;  // found last presentable publication
+    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.
+  for (size_t index = 0; index < end; ++index) {
+    for (auto& update : pending_publications_[index]->updates) {
+      if (!ApplyUpdate(update.Pass(), resolver, unavailable_sender, err))
+        return Disposition::kFailed;
+    }
+  }
+
+  // Dequeue the publications we processed.
+  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;
+}
+
+bool SceneDef::ApplyUpdate(mojo::gfx::composition::SceneUpdatePtr update,
+                           const SceneResolver& resolver,
+                           const SceneUnavailableSender& unavailable_sender,
+                           std::ostream& err) {
+  DCHECK(update);
+
+  // Update resources.
+  if (update->clear_resources) {
+    resources_.clear();
+  }
+  for (auto it = update->resources.begin(); it != update->resources.end();
+       ++it) {
+    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);
+      if (!resource)
+        return false;
+      resources_[resource_id].reset(resource);
+    } else {
+      resources_.erase(resource_id);
+    }
+  }
+
+  // Update nodes.
+  if (update->clear_nodes) {
+    nodes_.clear();
+  }
+  for (auto it = update->nodes.begin(); it != update->nodes.end(); ++it) {
+    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);
+      if (!node)
+        return false;
+      nodes_[node_id].reset(node);
+    } else {
+      nodes_.erase(node_id);
+    }
+  }
+  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) {
+  DCHECK(scene);
+
+  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();
+        changed = true;
+        unavailable_sender.Run(pair.first);
+      }
+    }
+  }
+  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(
+    uint32_t resource_id,
+    mojo::gfx::composition::ResourcePtr resource_decl,
+    const SceneResolver& resolver,
+    const SceneUnavailableSender& unavailable_sender,
+    std::ostream& err) {
+  DCHECK(resource_decl);
+
+  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) {
+      unavailable_sender.Run(resource_id);
+    }
+    return new SceneResourceDef(referenced_scene);
+  }
+
+  if (resource_decl->is_mailbox_texture()) {
+    auto& mailbox_texture_resource_decl = resource_decl->get_mailbox_texture();
+    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;
+    if (width < 1 || width > kMaxTextureWidth || height < 1 ||
+        height > kMaxTextureHeight) {
+      err << "MailboxTexture resource has invalid size: "
+          << "resource_id=" << resource_id << ", width=" << width
+          << ", height=" << height;
+      return nullptr;
+    }
+    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,
+        base::MessageLoop::current()->task_runner(),
+        base::Bind(
+            &ReleaseMailboxTexture,
+            base::Passed(mailbox_texture_resource_decl->callback.Pass())));
+    if (!image) {
+      err << "Could not create MailboxTexture";
+      return nullptr;
+    }
+    return new ImageResourceDef(image);
+  }
+
+  err << "Unsupported resource type: resource_id=" << resource_id;
+  return nullptr;
+}
+
+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;
+  }
+
+  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);
+}
+
+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);
+  }
+
+  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_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_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());
+  }
+
+  err << "Unsupported node op type: node_id=" << node_id
+      << ", node_op=" << node_op_decl;
+  return nullptr;
+}
+
+NodeDef* SceneDef::FindNode(uint32_t node_id) {
+  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) {
+  auto it = resources_.find(resource_id);
+  return it != resources_.end() && it->second->type() == resource_type
+             ? 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_;
+}
+
+SceneDef::Publication::Publication(
+    mojo::gfx::composition::SceneMetadataPtr metadata)
+    : metadata(metadata.Pass()) {
+  DCHECK(this->metadata);
+}
+
+SceneDef::Publication::~Publication() {}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/scene_def.h b/services/gfx/compositor/graph/scene_def.h
new file mode 100644
index 0000000..973e22a
--- /dev/null
+++ b/services/gfx/compositor/graph/scene_def.h
@@ -0,0 +1,171 @@
+// 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_GRAPH_SCENE_DEF_H_
+#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 "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "services/gfx/compositor/graph/node_def.h"
+
+namespace compositor {
+
+class RenderLayer;
+class RenderLayerBuilder;
+class SceneDef;
+class SnapshotBuilder;
+
+// Resolves a scene token to a scene definition.
+using SceneResolver =
+    base::Callback<SceneDef*(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.
+class SceneDef {
+ public:
+  // Outcome of a call to |Present|.
+  enum class Disposition {
+    kUnchanged,
+    kSucceeded,
+    kFailed,
+  };
+
+  SceneDef(mojo::gfx::composition::SceneTokenPtr scene_token,
+           const std::string& 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();
+  }
+
+  // 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_; }
+
+  // Enqueues a pending update event to the scene graph.
+  void EnqueueUpdate(mojo::gfx::composition::SceneUpdatePtr update);
+
+  // Enqueues a pending publish event to the scene graph.
+  // The changes are not applied until |ApplyChanges| is called.
+  void EnqueuePublish(mojo::gfx::composition::SceneMetadataPtr metadata);
+
+  // Applies published updates to the scene up to the point indicated by
+  // |presentation_time|.
+  //
+  // Returns a value which indicates whether the updates succeded.
+  // If the result is |kFailed|, the scene graph was left in an unusable
+  // and inconsistent state and must be destroyed.
+  Disposition Present(int64_t presentation_time,
+                      const SceneResolver& resolver,
+                      const SceneUnavailableSender& unavailable_sender,
+                      std::ostream& err);
+
+  // Unlinks references to another scene which has been unregistered.
+  // Causes |OnResourceUnavailable()| to be delivered to the scene for all
+  // invalidated scene resources.  Returns true if any changes were made.
+  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 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();
+
+ private:
+  struct Publication {
+    Publication(mojo::gfx::composition::SceneMetadataPtr metadata);
+    ~Publication();
+
+    bool is_due(int64_t presentation_time) const {
+      return metadata->presentation_time <= presentation_time;
+    }
+
+    mojo::gfx::composition::SceneMetadataPtr metadata;
+    std::vector<mojo::gfx::composition::SceneUpdatePtr> updates;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Publication);
+  };
+
+  bool ApplyUpdate(mojo::gfx::composition::SceneUpdatePtr update,
+                   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();
+
+  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);
+
+  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_;
+
+  // 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(SceneDef);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_SCENE_DEF_H_
diff --git a/services/gfx/compositor/graph/snapshot.cc b/services/gfx/compositor/graph/snapshot.cc
new file mode 100644
index 0000000..935fa20
--- /dev/null
+++ b/services/gfx/compositor/graph/snapshot.cc
@@ -0,0 +1,67 @@
+// 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/graph/snapshot.h"
+
+#include "base/logging.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/SkRect.h"
+
+namespace compositor {
+
+Snapshot::Snapshot() {}
+
+Snapshot::~Snapshot() {}
+
+bool Snapshot::Invalidate() {
+  if (valid_) {
+    valid_ = false;
+    dependencies_.clear();
+    frame_.reset();
+    return true;
+  }
+  return false;
+}
+
+bool Snapshot::InvalidateScene(SceneDef* scene_def) {
+  DCHECK(scene_def);
+
+  if (valid_ && dependencies_.find(scene_def) != dependencies_.end()) {
+    return Invalidate();
+  }
+  return false;
+}
+
+SnapshotBuilder::SnapshotBuilder(std::ostream* block_log)
+    : block_log_(block_log), snapshot_(new Snapshot()) {}
+
+SnapshotBuilder::~SnapshotBuilder() {}
+
+void SnapshotBuilder::AddSceneDependency(SceneDef* scene) {
+  DCHECK(snapshot_);
+  snapshot_->dependencies_.insert(scene);
+}
+
+std::unique_ptr<Snapshot> SnapshotBuilder::Build(
+    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_->frame_ =
+        RenderFrame::Create(layer_builder.Build(), sk_viewport, frame_info);
+  } else {
+    snapshot_->valid_ = false;
+  }
+  return std::move(snapshot_);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/graph/snapshot.h b/services/gfx/compositor/graph/snapshot.h
new file mode 100644
index 0000000..21c17b7
--- /dev/null
+++ b/services/gfx/compositor/graph/snapshot.h
@@ -0,0 +1,111 @@
+// 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_GRAPH_SNAPSHOT_H_
+#define SERVICES_GFX_COMPOSITOR_GRAPH_SNAPSHOT_H_
+
+#include <iosfwd>
+#include <memory>
+#include <unordered_set>
+
+#include "base/macros.h"
+#include "mojo/services/geometry/interfaces/geometry.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace compositor {
+
+class SceneDef;
+class RenderFrame;
+
+// Describes a single frame snapshot of the scene graph, sufficient for
+// rendering and hit testing.  When the snapshot is made, all predicated and
+// blocked scene nodes are evaluated to produce a final description of
+// the frame along with its dependencies.
+//
+// The snapshot holds a list of dependencies for the scenes whose state was
+// originally used to produce it.  The snapshot must be invalidated whenever
+// any of these scenes change.  Note that the snapshot will contain a list
+// of dependencies even in the case where a frame could not be produced,
+// in which case the dependencies express the set of scenes which, if updated,
+// might allow composition to be unblocked and make progress on a subsequent
+// frame.
+//
+// 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.
+class Snapshot {
+ public:
+  ~Snapshot();
+
+  // Returns true if the snapshot is valid.
+  bool valid() const { return valid_; }
+
+  // Gets the frame produced from this snapshot, or null if none.
+  //
+  // This is always null if |valid()| is false but it may be null even
+  // when |valid()| is true if composition was blocked and unable to produce
+  // a frame during the snapshot operation.
+  const std::shared_ptr<RenderFrame>& frame() const { return frame_; }
+
+  // Unconditionally marks the snapshot as invalid.
+  //
+  // Returns true if the snapshot became invalid as a result of this operation,
+  // or false if it was already invalid.
+  bool Invalidate();
+
+  // Invalidates the snapshot if it has a dependency on the specified scene.
+  // When this occurs, the entire list of dependencies is flushed (we no longer
+  // need them) in case the scene in question or its contents are about to
+  // be destroyed.
+  //
+  // 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);
+
+ private:
+  friend class SnapshotBuilder;
+
+  Snapshot();
+
+  std::unordered_set<SceneDef*> dependencies_;
+  std::shared_ptr<RenderFrame> frame_;
+  bool valid_ = true;
+
+  DISALLOW_COPY_AND_ASSIGN(Snapshot);
+};
+
+// Builder for snapshots.
+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();
+
+  // If not null, the snapshotter will append information to this stream
+  // 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);
+
+  // Builds a snapshot rooted at the specified scene.
+  std::unique_ptr<Snapshot> Build(
+      SceneDef* root_scene,
+      const mojo::Rect& viewport,
+      const mojo::gfx::composition::FrameInfo& frame_info);
+
+ private:
+  std::ostream* const block_log_;
+  std::unique_ptr<Snapshot> snapshot_;
+
+  DISALLOW_COPY_AND_ASSIGN(SnapshotBuilder);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_GRAPH_SNAPSHOT_H_
diff --git a/services/gfx/compositor/main.cc b/services/gfx/compositor/main.cc
new file mode 100644
index 0000000..e03e9ca
--- /dev/null
+++ b/services/gfx/compositor/main.cc
@@ -0,0 +1,12 @@
+// 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 "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "services/gfx/compositor/compositor_app.h"
+
+MojoResult MojoMain(MojoHandle application_request) {
+  mojo::ApplicationRunnerChromium runner(new compositor::CompositorApp);
+  return runner.Run(application_request);
+}
diff --git a/services/gfx/compositor/render/render_frame.cc b/services/gfx/compositor/render/render_frame.cc
new file mode 100644
index 0000000..39bae02
--- /dev/null
+++ b/services/gfx/compositor/render/render_frame.cc
@@ -0,0 +1,40 @@
+// 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_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,
+                         const SkRect& viewport,
+                         const mojo::gfx::composition::FrameInfo& frame_info)
+    : root_layer_(root_layer), viewport_(viewport), frame_info_(frame_info) {
+  DCHECK(root_layer_);
+}
+
+RenderFrame::~RenderFrame() {}
+
+void RenderFrame::Paint(SkCanvas* canvas) const {
+  // 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();
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/render/render_frame.h b/services/gfx/compositor/render/render_frame.h
new file mode 100644
index 0000000..21cdb77
--- /dev/null
+++ b/services/gfx/compositor/render/render_frame.h
@@ -0,0 +1,72 @@
+// 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_FRAME_H_
+#define SERVICES_GFX_COMPOSITOR_RENDER_RENDER_FRAME_H_
+
+#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 "third_party/skia/include/core/SkRect.h"
+
+class SkCanvas;
+struct SkPoint;
+
+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.
+//
+// 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,
+              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 SkRect& viewport,
+      const mojo::gfx::composition::FrameInfo& frame_info) {
+    return std::make_shared<RenderFrame>(root_layer, viewport, frame_info);
+  }
+
+  // Gets the root layer of the frame.
+  const std::shared_ptr<RenderLayer>& root_layer() const { return root_layer_; }
+
+  // Gets the frame's viewport.
+  const SkRect& viewport() const { return viewport_; }
+
+  // Gets information about the frame to be rendered.
+  const mojo::gfx::composition::FrameInfo& frame_info() const {
+    return frame_info_;
+  }
+
+  // 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_;
+  SkRect viewport_;
+  mojo::gfx::composition::FrameInfo frame_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(RenderFrame);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_RENDER_RENDER_FRAME_H_
diff --git a/services/gfx/compositor/render/render_image.cc b/services/gfx/compositor/render/render_image.cc
new file mode 100644
index 0000000..a62a289
--- /dev/null
+++ b/services/gfx/compositor/render/render_image.cc
@@ -0,0 +1,74 @@
+// 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_image.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "mojo/skia/ganesh_image_factory.h"
+#include "third_party/skia/include/core/SkImage.h"
+
+namespace compositor {
+
+class RenderImage::Releaser {
+ public:
+  Releaser(const scoped_refptr<base::TaskRunner>& task_runner,
+           const base::Closure& release_task)
+      : task_runner_(task_runner), release_task_(release_task) {
+    DCHECK(task_runner_);
+  }
+
+  ~Releaser() { task_runner_->PostTask(FROM_HERE, release_task_); }
+
+ private:
+  scoped_refptr<base::TaskRunner> const task_runner_;
+  base::Closure const release_task_;
+};
+
+class RenderImage::Generator : public mojo::skia::MailboxTextureImageGenerator {
+ public:
+  Generator(const std::shared_ptr<Releaser>& releaser,
+            const GLbyte mailbox_name[GL_MAILBOX_SIZE_CHROMIUM],
+            GLuint sync_point,
+            uint32_t width,
+            uint32_t height)
+      : MailboxTextureImageGenerator(mailbox_name, sync_point, width, height),
+        releaser_(releaser) {
+    DCHECK(releaser_);
+  }
+
+  ~Generator() override {}
+
+ private:
+  std::shared_ptr<Releaser> releaser_;
+};
+
+RenderImage::RenderImage(const skia::RefPtr<SkImage>& image,
+                         const std::shared_ptr<Releaser>& releaser)
+    : image_(image), releaser_(releaser) {
+  DCHECK(image_);
+  DCHECK(releaser_);
+}
+
+RenderImage::~RenderImage() {}
+
+std::shared_ptr<RenderImage> RenderImage::CreateFromMailboxTexture(
+    const GLbyte mailbox_name[GL_MAILBOX_SIZE_CHROMIUM],
+    GLuint sync_point,
+    uint32_t width,
+    uint32_t height,
+    const scoped_refptr<base::TaskRunner>& task_runner,
+    const base::Closure& release_task) {
+  std::shared_ptr<Releaser> releaser =
+      std::make_shared<Releaser>(task_runner, release_task);
+  skia::RefPtr<SkImage> image = skia::AdoptRef(SkImage::NewFromGenerator(
+      new Generator(releaser, mailbox_name, sync_point, width, height)));
+  if (!image)
+    return nullptr;
+
+  return std::make_shared<RenderImage>(image, releaser);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/render/render_image.h b/services/gfx/compositor/render/render_image.h
new file mode 100644
index 0000000..7cdbf16
--- /dev/null
+++ b/services/gfx/compositor/render/render_image.h
@@ -0,0 +1,66 @@
+// 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_IMAGE_H_
+#define SERVICES_GFX_COMPOSITOR_RENDER_RENDER_IMAGE_H_
+
+#include <memory>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2extmojo.h>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkImage.h"
+
+namespace compositor {
+
+// Describes an image which can be rendered by the compositor.
+//
+// Render objects are thread-safe, immutable, and reference counted via
+// std::shared_ptr.  They have no direct references to the scene graph.
+//
+// TODO(jeffbrown): Generalize this beyond mailbox textures.
+class RenderImage {
+  class Releaser;
+  class Generator;
+
+ public:
+  RenderImage(const skia::RefPtr<SkImage>& image,
+              const std::shared_ptr<Releaser>& releaser);
+  ~RenderImage();
+
+  // Creates a new image backed by a mailbox texture.
+  // If |sync_point| is non-zero, inserts a sync point into the command stream
+  // before the image is first drawn.
+  // When the last reference is released, the associated release task is
+  // posted to the task runner.  Returns nullptr if the mailbox texture
+  // is invalid.
+  static std::shared_ptr<RenderImage> CreateFromMailboxTexture(
+      const GLbyte mailbox_name[GL_MAILBOX_SIZE_CHROMIUM],
+      GLuint sync_point,
+      uint32_t width,
+      uint32_t height,
+      const scoped_refptr<base::TaskRunner>& task_runner,
+      const base::Closure& release_task);
+
+  uint32_t width() const { return image_->width(); }
+  uint32_t height() const { return image_->height(); }
+
+  // Gets the underlying image to rasterize.
+  const skia::RefPtr<SkImage>& image() const { return image_; }
+
+ private:
+  skia::RefPtr<SkImage> image_;
+  std::shared_ptr<Releaser> releaser_;
+
+  DISALLOW_COPY_AND_ASSIGN(RenderImage);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_RENDER_RENDER_IMAGE_H_
diff --git a/services/gfx/compositor/render/render_layer.cc b/services/gfx/compositor/render/render_layer.cc
new file mode 100644
index 0000000..d652d7b
--- /dev/null
+++ b/services/gfx/compositor/render/render_layer.cc
@@ -0,0 +1,99 @@
+// 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
new file mode 100644
index 0000000..34e614c
--- /dev/null
+++ b/services/gfx/compositor/render/render_layer.h
@@ -0,0 +1,108 @@
+// 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/renderer_impl.cc b/services/gfx/compositor/renderer_impl.cc
new file mode 100644
index 0000000..7c730b0
--- /dev/null
+++ b/services/gfx/compositor/renderer_impl.cc
@@ -0,0 +1,41 @@
+// 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/renderer_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+
+namespace compositor {
+
+RendererImpl::RendererImpl(
+    CompositorEngine* engine,
+    RendererState* state,
+    mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request)
+    : engine_(engine),
+      state_(state),
+      renderer_binding_(this, renderer_request.Pass()) {}
+
+RendererImpl::~RendererImpl() {}
+
+void RendererImpl::GetHitTester(
+    mojo::InterfaceRequest<mojo::gfx::composition::HitTester>
+        hit_tester_request) {
+  hit_tester_bindings.AddBinding(this, hit_tester_request.Pass());
+}
+
+void RendererImpl::SetRootScene(
+    mojo::gfx::composition::SceneTokenPtr scene_token,
+    uint32 scene_version,
+    mojo::RectPtr viewport) {
+  engine_->SetRootScene(state_, scene_token.Pass(), scene_version,
+                        viewport.Pass());
+}
+
+void RendererImpl::HitTest(mojo::PointPtr point,
+                           const HitTestCallback& callback) {
+  engine_->HitTest(state_, point.Pass(), callback);
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/renderer_impl.h b/services/gfx/compositor/renderer_impl.h
new file mode 100644
index 0000000..d9fc09e
--- /dev/null
+++ b/services/gfx/compositor/renderer_impl.h
@@ -0,0 +1,54 @@
+// 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_RENDERER_IMPL_H_
+#define SERVICES_GFX_COMPOSITOR_RENDERER_IMPL_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/common/binding_set.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/services/gfx/composition/interfaces/renderers.mojom.h"
+#include "services/gfx/compositor/compositor_engine.h"
+#include "services/gfx/compositor/renderer_state.h"
+
+namespace compositor {
+
+// Renderer interface implementation.
+// This object is owned by its associated RendererState.
+class RendererImpl : public mojo::gfx::composition::Renderer,
+                     public mojo::gfx::composition::HitTester {
+ public:
+  RendererImpl(CompositorEngine* engine,
+               RendererState* state,
+               mojo::InterfaceRequest<mojo::gfx::composition::Renderer>
+                   renderer_request);
+  ~RendererImpl() override;
+
+  void set_connection_error_handler(const base::Closure& handler) {
+    renderer_binding_.set_connection_error_handler(handler);
+  }
+
+ private:
+  // |Renderer|:
+  void SetRootScene(mojo::gfx::composition::SceneTokenPtr scene_token,
+                    uint32 scene_version,
+                    mojo::RectPtr viewport) override;
+  void GetHitTester(mojo::InterfaceRequest<mojo::gfx::composition::HitTester>
+                        hit_tester_request) override;
+
+  // |HitTester|:
+  void HitTest(mojo::PointPtr point, const HitTestCallback& callback) override;
+
+  CompositorEngine* const engine_;
+  RendererState* const state_;
+  mojo::Binding<mojo::gfx::composition::Renderer> renderer_binding_;
+  mojo::BindingSet<mojo::gfx::composition::HitTester> hit_tester_bindings;
+
+  DISALLOW_COPY_AND_ASSIGN(RendererImpl);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_RENDERER_IMPL_H_
diff --git a/services/gfx/compositor/renderer_state.cc b/services/gfx/compositor/renderer_state.cc
new file mode 100644
index 0000000..28ba687
--- /dev/null
+++ b/services/gfx/compositor/renderer_state.cc
@@ -0,0 +1,59 @@
+// 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/renderer_state.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace compositor {
+
+RendererState::RendererState(uint32_t id, const std::string& label)
+    : id_(id), label_(label), weak_factory_(this) {}
+
+RendererState::~RendererState() {}
+
+bool RendererState::SetRootScene(SceneState* scene,
+                                 uint32_t version,
+                                 const mojo::Rect& viewport) {
+  DCHECK(scene);
+  DCHECK(version);
+
+  if (root_scene_ != scene || root_scene_version_ != version ||
+      !root_scene_viewport_.Equals(viewport)) {
+    root_scene_ = scene;
+    root_scene_version_ = version;
+    root_scene_viewport_ = viewport;
+    SetSnapshot(nullptr);
+    return true;
+  }
+  return false;
+}
+
+bool RendererState::SetSnapshot(std::unique_ptr<Snapshot> snapshot) {
+  snapshot_ = std::move(snapshot);
+  if (snapshot_ && snapshot_->valid()) {
+    frame_ = snapshot_->frame();
+    DCHECK(frame_);
+    return true;
+  }
+  return false;
+}
+
+std::string RendererState::FormattedLabel() {
+  if (formatted_label_cache_.empty()) {
+    formatted_label_cache_ =
+        label_.empty() ? base::StringPrintf("<%d>", id_)
+                       : base::StringPrintf("<%d:%s>", id_, label_.c_str());
+  }
+  return formatted_label_cache_;
+}
+
+std::ostream& operator<<(std::ostream& os, RendererState* renderer_state) {
+  if (!renderer_state)
+    return os << "null";
+  return os << renderer_state->FormattedLabel();
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/renderer_state.h b/services/gfx/compositor/renderer_state.h
new file mode 100644
index 0000000..54e688b
--- /dev/null
+++ b/services/gfx/compositor/renderer_state.h
@@ -0,0 +1,99 @@
+// 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_RENDERER_STATE_H_
+#define SERVICES_GFX_COMPOSITOR_RENDERER_STATE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/gfx/composition/cpp/formatting.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
+#include "services/gfx/compositor/backend/output.h"
+#include "services/gfx/compositor/graph/snapshot.h"
+#include "services/gfx/compositor/scene_state.h"
+
+namespace compositor {
+
+class Snapshot;
+
+// Describes the state of a particular renderer.
+// This object is owned by the CompositorEngine that created it.
+class RendererState {
+ public:
+  RendererState(uint32_t id, const std::string& label);
+  ~RendererState();
+
+  base::WeakPtr<RendererState> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+  // Sets the associated renderer implementation and takes ownership of it.
+  void set_renderer_impl(mojo::gfx::composition::Renderer* impl) {
+    renderer_impl_.reset(impl);
+  }
+
+  // The underlying backend output.
+  void set_output(std::unique_ptr<Output> output) {
+    output_ = std::move(output);
+  }
+  Output* output() { return output_.get(); }
+
+  // Gets the root scene, may be null if none set yet.
+  SceneState* root_scene() { return root_scene_; }
+  uint32_t root_scene_version() { return root_scene_version_; }
+  const mojo::Rect& root_scene_viewport() { return root_scene_viewport_; }
+
+  // Sets the root scene and clears the current frame and cached dependencies.
+  // If different, invalidates the snapshot and returns true.
+  bool SetRootScene(SceneState* scene,
+                    uint32_t version,
+                    const mojo::Rect& viewport);
+
+  // The currently composited frame, may be null if none.
+  const std::shared_ptr<RenderFrame>& frame() { return frame_; }
+
+  // The current scene graph snapshot, may be null if none.
+  const std::unique_ptr<Snapshot>& snapshot() { return snapshot_; }
+
+  // Returns true if the renderer has a snapshot and it is valid.
+  // This implies that |frame()| is also non-null.
+  bool valid() { return snapshot_ && snapshot_->valid(); }
+
+  // Sets the snapshot, or null if none.
+  // If the snapshot is valid, updates |frame()| to point to the snapshot's
+  // new frame, otherwise leaves it alone.
+  // Returns true if the snapshot is valid.
+  bool SetSnapshot(std::unique_ptr<Snapshot> snapshot);
+
+  const std::string& label() { return label_; }
+  std::string FormattedLabel();
+
+ private:
+  std::unique_ptr<Output> output_;
+  const uint32_t id_;
+  const std::string label_;
+  std::string formatted_label_cache_;
+
+  std::unique_ptr<mojo::gfx::composition::Renderer> renderer_impl_;
+
+  SceneState* root_scene_ = nullptr;
+  uint32_t root_scene_version_ = mojo::gfx::composition::kSceneVersionNone;
+  mojo::Rect root_scene_viewport_;
+
+  std::shared_ptr<RenderFrame> frame_;
+  std::unique_ptr<Snapshot> snapshot_;
+
+  base::WeakPtrFactory<RendererState> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RendererState);
+};
+
+std::ostream& operator<<(std::ostream& os, RendererState* renderer_state);
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_RENDERER_STATE_H_
diff --git a/services/gfx/compositor/scene_impl.cc b/services/gfx/compositor/scene_impl.cc
new file mode 100644
index 0000000..c02486e
--- /dev/null
+++ b/services/gfx/compositor/scene_impl.cc
@@ -0,0 +1,51 @@
+// 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/scene_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+
+namespace compositor {
+namespace {
+void RunScheduleFrameCallback(const SceneImpl::ScheduleFrameCallback& callback,
+                              mojo::gfx::composition::FrameInfoPtr info) {
+  callback.Run(info.Pass());
+}
+}  // namespace
+
+SceneImpl::SceneImpl(
+    CompositorEngine* engine,
+    SceneState* state,
+    mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request)
+    : engine_(engine),
+      state_(state),
+      scene_binding_(this, scene_request.Pass()) {}
+
+SceneImpl::~SceneImpl() {}
+
+void SceneImpl::SetListener(mojo::gfx::composition::SceneListenerPtr listener) {
+  engine_->SetListener(state_, listener.Pass());
+}
+
+void SceneImpl::Update(mojo::gfx::composition::SceneUpdatePtr update) {
+  engine_->Update(state_, update.Pass());
+}
+
+void SceneImpl::Publish(mojo::gfx::composition::SceneMetadataPtr metadata) {
+  engine_->Publish(state_, metadata.Pass());
+}
+
+void SceneImpl::GetScheduler(
+    mojo::InterfaceRequest<mojo::gfx::composition::SceneScheduler>
+        scheduler_request) {
+  scheduler_bindings_.AddBinding(this, scheduler_request.Pass());
+}
+
+void SceneImpl::ScheduleFrame(const ScheduleFrameCallback& callback) {
+  engine_->ScheduleFrame(state_,
+                         base::Bind(&RunScheduleFrameCallback, callback));
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/scene_impl.h b/services/gfx/compositor/scene_impl.h
new file mode 100644
index 0000000..a7def23
--- /dev/null
+++ b/services/gfx/compositor/scene_impl.h
@@ -0,0 +1,55 @@
+// 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_SCENE_IMPL_H_
+#define SERVICES_GFX_COMPOSITOR_SCENE_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/common/binding_set.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+#include "services/gfx/compositor/compositor_engine.h"
+#include "services/gfx/compositor/scene_state.h"
+
+namespace compositor {
+
+// Scene interface implementation.
+// This object is owned by its associated SceneState.
+class SceneImpl : public mojo::gfx::composition::Scene,
+                  public mojo::gfx::composition::SceneScheduler {
+ public:
+  SceneImpl(
+      CompositorEngine* engine,
+      SceneState* state,
+      mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request);
+  ~SceneImpl() override;
+
+  void set_connection_error_handler(const base::Closure& handler) {
+    scene_binding_.set_connection_error_handler(handler);
+  }
+
+ private:
+  // |Scene|:
+  void SetListener(mojo::gfx::composition::SceneListenerPtr listener) override;
+  void Update(mojo::gfx::composition::SceneUpdatePtr update) override;
+  void Publish(mojo::gfx::composition::SceneMetadataPtr metadata) override;
+  void GetScheduler(
+      mojo::InterfaceRequest<mojo::gfx::composition::SceneScheduler>
+          scheduler_request) override;
+
+  // |SceneScheduler|:
+  void ScheduleFrame(const ScheduleFrameCallback& callback) override;
+
+  CompositorEngine* const engine_;
+  SceneState* const state_;
+  mojo::Binding<mojo::gfx::composition::Scene> scene_binding_;
+  mojo::BindingSet<mojo::gfx::composition::SceneScheduler> scheduler_bindings_;
+
+  DISALLOW_COPY_AND_ASSIGN(SceneImpl);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_SCENE_IMPL_H_
diff --git a/services/gfx/compositor/scene_state.cc b/services/gfx/compositor/scene_state.cc
new file mode 100644
index 0000000..c3b4575
--- /dev/null
+++ b/services/gfx/compositor/scene_state.cc
@@ -0,0 +1,38 @@
+// 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/scene_state.h"
+
+namespace compositor {
+
+SceneState::SceneState(mojo::gfx::composition::SceneTokenPtr scene_token,
+                       const std::string& label)
+    : scene_def_(scene_token.Pass(), label), weak_factory_(this) {}
+
+SceneState::~SceneState() {
+  // The scene implementation and all of its bindings must be destroyed
+  // before any pending callbacks are dropped on the floor.
+  scene_impl_.reset();
+  pending_frame_callbacks_.clear();
+}
+
+void SceneState::AddSceneFrameCallback(const SceneFrameCallback& callback) {
+  pending_frame_callbacks_.push_back(callback);
+}
+
+void SceneState::DispatchSceneFrameCallbacks(
+    const mojo::gfx::composition::FrameInfo& frame_info) {
+  for (auto& callback : pending_frame_callbacks_) {
+    callback.Run(frame_info.Clone());
+  }
+  pending_frame_callbacks_.clear();
+}
+
+std::ostream& operator<<(std::ostream& os, SceneState* scene_state) {
+  if (!scene_state)
+    return os << "null";
+  return os << scene_state->FormattedLabel();
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/scene_state.h b/services/gfx/compositor/scene_state.h
new file mode 100644
index 0000000..3b6743f
--- /dev/null
+++ b/services/gfx/compositor/scene_state.h
@@ -0,0 +1,80 @@
+// 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_SCENE_STATE_H_
+#define SERVICES_GFX_COMPOSITOR_SCENE_STATE_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/gfx/composition/cpp/formatting.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "services/gfx/compositor/graph/scene_def.h"
+
+namespace compositor {
+
+using SceneFrameCallback =
+    base::Callback<void(mojo::gfx::composition::FrameInfoPtr)>;
+
+// Describes the state of a particular scene.
+// This object is owned by the CompositorEngine that created it.
+class SceneState {
+ public:
+  SceneState(mojo::gfx::composition::SceneTokenPtr scene_token,
+             const std::string& label);
+  ~SceneState();
+
+  base::WeakPtr<SceneState> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
+
+  // 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();
+  }
+
+  // Gets or sets the scene listener interface.
+  mojo::gfx::composition::SceneListener* scene_listener() {
+    return scene_listener_.get();
+  }
+  void set_scene_listener(mojo::gfx::composition::SceneListenerPtr listener) {
+    scene_listener_ = listener.Pass();
+  }
+
+  // Sets the associated scene implementation and takes ownership of it.
+  void set_scene_impl(mojo::gfx::composition::Scene* impl) {
+    scene_impl_.reset(impl);
+  }
+
+  // Gets the underlying scene definition, never null.
+  SceneDef* scene_def() { return &scene_def_; }
+
+  void AddSceneFrameCallback(const SceneFrameCallback& callback);
+  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:
+  std::unique_ptr<mojo::gfx::composition::Scene> scene_impl_;
+  mojo::gfx::composition::SceneListenerPtr scene_listener_;
+  std::vector<SceneFrameCallback> pending_frame_callbacks_;
+
+  SceneDef scene_def_;
+
+  base::WeakPtrFactory<SceneState> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SceneState);
+};
+
+std::ostream& operator<<(std::ostream& os, SceneState* scene_state);
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_SCENE_STATE_H_