Add a multi-threaded rendering example.

Add a new example to test multi-threaded rendering support to ensure
that the Mozart APIs are not overly thread-hostile.

The Noodles app draws Lissajous figures.  The main thread handles
events and submits SkPictures to the rasterizer thread to be
drawn and composited.

Writing this example provided useful feedback into the design of the
UI helper library and the Scene interface itself, particularly the
separation of event handling from content publishing.

BUG=
R=abarth@google.com

Review URL: https://codereview.chromium.org/1558813002 .
diff --git a/examples/BUILD.gn b/examples/BUILD.gn
index 8b33176..05c5a7c 100644
--- a/examples/BUILD.gn
+++ b/examples/BUILD.gn
@@ -36,6 +36,7 @@
 
   deps = [
     ":portable_examples",
+    "//examples/ui/noodles",
     "//examples/ui/png_viewer",
     "//examples/ui/shapes",
     "//examples/ui/spinning_cube",
diff --git a/examples/ui/noodles/BUILD.gn b/examples/ui/noodles/BUILD.gn
new file mode 100644
index 0000000..75b87a1
--- /dev/null
+++ b/examples/ui/noodles/BUILD.gn
@@ -0,0 +1,42 @@
+# 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")
+
+mojo_native_application("noodles") {
+  output_name = "noodles_view"
+
+  sources = [
+    "frame.cc",
+    "frame.h",
+    "main.cc",
+    "noodles_app.cc",
+    "noodles_app.h",
+    "noodles_view.cc",
+    "noodles_view.h",
+    "rasterizer.cc",
+    "rasterizer.h",
+  ]
+
+  deps = [
+    "//base",
+    "//mojo/application",
+    "//mojo/common",
+    "//mojo/environment:chromium",
+    "//mojo/gpu",
+    "//mojo/public/c/gpu",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/environment",
+    "//mojo/public/cpp/system",
+    "//mojo/public/interfaces/application",
+    "//mojo/services/geometry/interfaces",
+    "//mojo/services/gfx/composition/interfaces",
+    "//mojo/services/ui/views/interfaces",
+    "//mojo/skia",
+    "//mojo/ui",
+    "//mojo/ui:ganesh",
+    "//mojo/ui:gl",
+    "//skia",
+  ]
+}
diff --git a/examples/ui/noodles/README.md b/examples/ui/noodles/README.md
new file mode 100644
index 0000000..e0911b9
--- /dev/null
+++ b/examples/ui/noodles/README.md
@@ -0,0 +1,11 @@
+# Multithreaded rendering example.
+
+This directory contains a simple application which rasterizes frames on
+a separate thread from the one it uses to handle view events and to produce
+each frame's content.
+
+For the purposes of this example, the content consists of a Lissajous figure.
+
+## USAGE
+
+  out/Debug/mojo_shell "mojo:launcher mojo:noodles_view"
diff --git a/examples/ui/noodles/frame.cc b/examples/ui/noodles/frame.cc
new file mode 100644
index 0000000..ca4faec
--- /dev/null
+++ b/examples/ui/noodles/frame.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 "examples/ui/noodles/frame.h"
+
+#include "base/logging.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPicture.h"
+
+namespace examples {
+
+Frame::Frame(const mojo::Size& size,
+             skia::RefPtr<SkPicture> picture,
+             mojo::gfx::composition::SceneMetadataPtr scene_metadata)
+    : size_(size), picture_(picture), scene_metadata_(scene_metadata.Pass()) {
+  DCHECK(picture_);
+}
+
+Frame::~Frame() {}
+
+void Frame::Paint(SkCanvas* canvas) {
+  DCHECK(canvas);
+
+  canvas->clear(SK_ColorBLACK);
+  canvas->drawPicture(picture_.get());
+  canvas->flush();
+}
+
+}  // namespace examples
diff --git a/examples/ui/noodles/frame.h b/examples/ui/noodles/frame.h
new file mode 100644
index 0000000..6c3858d
--- /dev/null
+++ b/examples/ui/noodles/frame.h
@@ -0,0 +1,46 @@
+// 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 EXAMPLES_UI_NOODLES_FRAME_H_
+#define EXAMPLES_UI_NOODLES_FRAME_H_
+
+#include "base/macros.h"
+#include "mojo/services/geometry/interfaces/geometry.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "skia/ext/refptr.h"
+
+class SkCanvas;
+class SkPicture;
+
+namespace examples {
+
+// A frame of content to be rasterized.
+// Instances of this object are created by the view's thread and sent to
+// the rasterizer's thread to be drawn.
+class Frame {
+ public:
+  Frame(const mojo::Size& size,
+        skia::RefPtr<SkPicture> picture,
+        mojo::gfx::composition::SceneMetadataPtr scene_metadata);
+  ~Frame();
+
+  const mojo::Size& size() { return size_; }
+
+  mojo::gfx::composition::SceneMetadataPtr TakeSceneMetadata() {
+    return scene_metadata_.Pass();
+  }
+
+  void Paint(SkCanvas* canvas);
+
+ private:
+  mojo::Size size_;
+  skia::RefPtr<SkPicture> picture_;
+  mojo::gfx::composition::SceneMetadataPtr scene_metadata_;
+
+  DISALLOW_COPY_AND_ASSIGN(Frame);
+};
+
+}  // namespace examples
+
+#endif  // EXAMPLES_UI_NOODLES_FRAME_H_
diff --git a/examples/ui/noodles/main.cc b/examples/ui/noodles/main.cc
new file mode 100644
index 0000000..6aff78e
--- /dev/null
+++ b/examples/ui/noodles/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 "examples/ui/noodles/noodles_app.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+
+MojoResult MojoMain(MojoHandle application_request) {
+  mojo::ApplicationRunnerChromium runner(new examples::NoodlesApp);
+  return runner.Run(application_request);
+}
diff --git a/examples/ui/noodles/noodles_app.cc b/examples/ui/noodles/noodles_app.cc
new file mode 100644
index 0000000..31a3c4c
--- /dev/null
+++ b/examples/ui/noodles/noodles_app.cc
@@ -0,0 +1,24 @@
+// 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 "examples/ui/noodles/noodles_app.h"
+
+#include "examples/ui/noodles/noodles_view.h"
+
+namespace examples {
+
+NoodlesApp::NoodlesApp() {}
+
+NoodlesApp::~NoodlesApp() {}
+
+bool NoodlesApp::CreateView(
+    const std::string& connection_url,
+    mojo::InterfaceRequest<mojo::ServiceProvider> services,
+    mojo::ServiceProviderPtr exposed_services,
+    const mojo::ui::ViewProvider::CreateViewCallback& callback) {
+  new NoodlesView(app_impl(), callback);
+  return true;
+}
+
+}  // namespace examples
diff --git a/examples/ui/noodles/noodles_app.h b/examples/ui/noodles/noodles_app.h
new file mode 100644
index 0000000..221cd8e
--- /dev/null
+++ b/examples/ui/noodles/noodles_app.h
@@ -0,0 +1,29 @@
+// 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 EXAMPLES_UI_NOODLES_NOODLES_APP_H_
+#define EXAMPLES_UI_NOODLES_NOODLES_APP_H_
+
+#include "mojo/ui/view_provider_app.h"
+
+namespace examples {
+
+class NoodlesApp : public mojo::ui::ViewProviderApp {
+ public:
+  NoodlesApp();
+  ~NoodlesApp() override;
+
+  bool CreateView(
+      const std::string& connection_url,
+      mojo::InterfaceRequest<mojo::ServiceProvider> services,
+      mojo::ServiceProviderPtr exposed_services,
+      const mojo::ui::ViewProvider::CreateViewCallback& callback) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoodlesApp);
+};
+
+}  // namespace examples
+
+#endif  // EXAMPLES_UI_NOODLES_NOODLES_APP_H_
diff --git a/examples/ui/noodles/noodles_view.cc b/examples/ui/noodles/noodles_view.cc
new file mode 100644
index 0000000..5fa00f2
--- /dev/null
+++ b/examples/ui/noodles/noodles_view.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 "examples/ui/noodles/noodles_view.h"
+
+#include <math.h>
+
+#include <cstdlib>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "examples/ui/noodles/frame.h"
+#include "examples/ui/noodles/rasterizer.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkPicture.h"
+#include "third_party/skia/include/core/SkPictureRecorder.h"
+
+namespace examples {
+
+namespace {
+constexpr double kSecondsBetweenChanges = 10.0;
+
+template <typename T>
+void Drop(scoped_ptr<T> ptr) {}
+
+scoped_ptr<base::MessagePump> CreateMessagePumpMojo() {
+  return base::MessageLoop::CreateMessagePumpForType(
+      base::MessageLoop::TYPE_DEFAULT);
+}
+
+void Lissajous(SkPath* path, double ax, double ay, int wx, int wy, double p) {
+  uint32_t segments = ceil(fabs(ax) + fabs(ay)) / 2u + 1u;
+  for (uint32_t i = 0; i < segments; ++i) {
+    double t = M_PI * 2.0 * i / segments;
+    double x = ax * sin(t * wx);
+    double y = ay * sin(t * wy + p);
+    if (i == 0u)
+      path->moveTo(x, y);
+    else
+      path->lineTo(x, y);
+  }
+  path->close();
+}
+}  // namespace
+
+NoodlesView::NoodlesView(
+    mojo::ApplicationImpl* app_impl,
+    const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback)
+    : BaseView(app_impl, "Noodles", create_view_callback),
+      choreographer_(scene(), this),
+      frame_queue_(std::make_shared<FrameQueue>()),
+      rasterizer_delegate_(
+          make_scoped_ptr(new RasterizerDelegate(frame_queue_))) {
+  base::Thread::Options options;
+  options.message_pump_factory = base::Bind(&CreateMessagePumpMojo);
+
+  rasterizer_thread_.reset(new base::Thread("noodles_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(app_impl->CreateApplicationConnector()),
+                 base::Passed(TakeScene().PassInterface())));
+}
+
+NoodlesView::~NoodlesView() {
+  // Ensure destruction happens on the correct thread.
+  rasterizer_task_runner_->PostTask(
+      FROM_HERE, base::Bind(&Drop<RasterizerDelegate>,
+                            base::Passed(&rasterizer_delegate_)));
+}
+
+void NoodlesView::OnLayout(mojo::ui::ViewLayoutParamsPtr layout_params,
+                           mojo::Array<uint32_t> children_needing_layout,
+                           const OnLayoutCallback& callback) {
+  size_.width = layout_params->constraints->max_width;
+  size_.height = layout_params->constraints->max_height;
+
+  // Submit the new layout information.
+  auto info = mojo::ui::ViewLayoutResult::New();
+  info->size = size_.Clone();
+  callback.Run(info.Pass());
+
+  choreographer_.ScheduleDraw();
+}
+
+void NoodlesView::OnDraw(const mojo::gfx::composition::FrameInfo& frame_info,
+                         const base::TimeDelta& time_delta) {
+  choreographer_.ScheduleDraw();
+
+  // Update the animation.
+  alpha_ += time_delta.InSecondsF();
+
+  // Create and post a new frame to the renderer.
+  auto metadata = mojo::gfx::composition::SceneMetadata::New();
+  metadata->presentation_time = frame_info.presentation_time;
+  std::unique_ptr<Frame> frame(
+      new Frame(size_, CreatePicture(), metadata.Pass()));
+  if (frame_queue_->PutFrame(std::move(frame))) {
+    rasterizer_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&RasterizerDelegate::PublishNextFrame,
+                              base::Unretained(rasterizer_delegate_.get())));
+  }
+}
+
+skia::RefPtr<SkPicture> NoodlesView::CreatePicture() {
+  constexpr int count = 4;
+  constexpr int padding = 1;
+
+  if (alpha_ > kSecondsBetweenChanges) {
+    alpha_ = 0.0;
+    wx_ = rand() % 9 + 1;
+    wy_ = rand() % 9 + 1;
+  }
+
+  SkPictureRecorder recorder;
+  SkCanvas* canvas = recorder.beginRecording(size_.width, size_.height);
+
+  double cx = size_.width * 0.5;
+  double cy = size_.height * 0.5;
+  canvas->translate(cx, cy);
+
+  double phase = alpha_;
+  for (int i = 0; i < count; i++, phase += 0.1) {
+    SkPaint paint;
+    SkScalar hsv[3] = {fmod(phase * 120, 360), 1, 1};
+    paint.setColor(SkHSVToColor(hsv));
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setAntiAlias(true);
+
+    SkPath path;
+    Lissajous(&path, cx - padding, cy - padding, wx_, wy_, phase);
+    canvas->drawPath(path, paint);
+  }
+
+  return skia::AdoptRef(recorder.endRecordingAsPicture());
+}
+
+NoodlesView::FrameQueue::FrameQueue() {}
+
+NoodlesView::FrameQueue::~FrameQueue() {}
+
+bool NoodlesView::FrameQueue::PutFrame(std::unique_ptr<Frame> frame) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  bool was_empty = !next_frame_.get();
+  next_frame_.swap(frame);
+  return was_empty;
+}
+
+std::unique_ptr<Frame> NoodlesView::FrameQueue::TakeFrame() {
+  std::lock_guard<std::mutex> lock(mutex_);
+  return std::move(next_frame_);
+}
+
+NoodlesView::RasterizerDelegate::RasterizerDelegate(
+    const std::shared_ptr<FrameQueue>& frame_queue)
+    : frame_queue_(frame_queue) {
+  DCHECK(frame_queue_);
+}
+
+NoodlesView::RasterizerDelegate::~RasterizerDelegate() {}
+
+void NoodlesView::RasterizerDelegate::CreateRasterizer(
+    mojo::InterfacePtrInfo<mojo::ApplicationConnector> connector_info,
+    mojo::InterfacePtrInfo<mojo::gfx::composition::Scene> scene_info) {
+  rasterizer_.reset(
+      new Rasterizer(mojo::MakeProxy(connector_info.Pass()).Pass(),
+                     mojo::MakeProxy(scene_info.Pass()).Pass()));
+}
+
+void NoodlesView::RasterizerDelegate::PublishNextFrame() {
+  std::unique_ptr<Frame> frame(frame_queue_->TakeFrame());
+  DCHECK(frame);
+  rasterizer_->PublishFrame(std::move(frame));
+}
+
+}  // namespace examples
diff --git a/examples/ui/noodles/noodles_view.h b/examples/ui/noodles/noodles_view.h
new file mode 100644
index 0000000..4606c29
--- /dev/null
+++ b/examples/ui/noodles/noodles_view.h
@@ -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.
+
+#ifndef EXAMPLES_UI_NOODLES_NOODLES_VIEW_H_
+#define EXAMPLES_UI_NOODLES_NOODLES_VIEW_H_
+
+#include <memory>
+#include <mutex>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/ui/base_view.h"
+#include "mojo/ui/choreographer.h"
+#include "skia/ext/refptr.h"
+
+class SkPicture;
+
+namespace examples {
+
+class Frame;
+class Rasterizer;
+
+class NoodlesView : public mojo::ui::BaseView,
+                    public mojo::ui::ChoreographerDelegate {
+ public:
+  NoodlesView(
+      mojo::ApplicationImpl* app_impl,
+      const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback);
+
+  ~NoodlesView() 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(std::unique_ptr<Frame> frame);
+
+    // Takes a pending frame from the queue.
+    std::unique_ptr<Frame> TakeFrame();
+
+   private:
+    std::mutex mutex_;
+    std::unique_ptr<Frame> 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::ApplicationConnector> connector_info,
+        mojo::InterfacePtrInfo<mojo::gfx::composition::Scene> scene_info);
+
+    void PublishNextFrame();
+
+   private:
+    std::shared_ptr<FrameQueue> frame_queue_;
+    std::unique_ptr<Rasterizer> rasterizer_;
+
+    DISALLOW_COPY_AND_ASSIGN(RasterizerDelegate);
+  };
+
+  // |BaseView|:
+  void OnLayout(mojo::ui::ViewLayoutParamsPtr layout_params,
+                mojo::Array<uint32_t> children_needing_layout,
+                const OnLayoutCallback& callback) override;
+
+  // |ChoreographerDelegate|:
+  void OnDraw(const mojo::gfx::composition::FrameInfo& frame_info,
+              const base::TimeDelta& time_delta) override;
+
+  void UpdateFrame();
+  skia::RefPtr<SkPicture> CreatePicture();
+
+  mojo::ui::Choreographer choreographer_;
+
+  std::shared_ptr<FrameQueue> frame_queue_;
+
+  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_;
+
+  mojo::Size size_;
+  double alpha_ = 0.0;
+  int wx_ = 2;
+  int wy_ = 3;
+
+  DISALLOW_COPY_AND_ASSIGN(NoodlesView);
+};
+
+}  // namespace examples
+
+#endif  // EXAMPLES_UI_NOODLES_NOODLES_VIEW_H_
diff --git a/examples/ui/noodles/rasterizer.cc b/examples/ui/noodles/rasterizer.cc
new file mode 100644
index 0000000..5fa495d
--- /dev/null
+++ b/examples/ui/noodles/rasterizer.cc
@@ -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.
+
+#include "examples/ui/noodles/rasterizer.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "examples/ui/noodles/frame.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkSurface.h"
+
+namespace examples {
+
+constexpr uint32_t kContentImageResourceId = 1;
+constexpr uint32_t kRootNodeId = mojo::gfx::composition::kSceneRootNodeId;
+
+Rasterizer::Rasterizer(mojo::ApplicationConnectorPtr connector,
+                       mojo::gfx::composition::ScenePtr scene)
+    : gl_context_owner_(connector.get()),
+      ganesh_context_(gl_context_owner_.context()),
+      ganesh_renderer_(&ganesh_context_),
+      scene_(scene.Pass()) {}
+
+Rasterizer::~Rasterizer() {}
+
+void Rasterizer::PublishFrame(std::unique_ptr<Frame> frame) {
+  DCHECK(frame);
+
+  mojo::Rect bounds;
+  bounds.width = frame->size().width;
+  bounds.height = frame->size().height;
+
+  auto update = mojo::gfx::composition::SceneUpdate::New();
+  mojo::gfx::composition::ResourcePtr content_resource =
+      ganesh_renderer_.DrawCanvas(
+          frame->size(),
+          base::Bind(&Frame::Paint, base::Unretained(frame.get())));
+  DCHECK(content_resource);
+  update->resources.insert(kContentImageResourceId, content_resource.Pass());
+
+  auto root_node = mojo::gfx::composition::Node::New();
+  root_node->op = mojo::gfx::composition::NodeOp::New();
+  root_node->op->set_image(mojo::gfx::composition::ImageNodeOp::New());
+  root_node->op->get_image()->content_rect = bounds.Clone();
+  root_node->op->get_image()->image_resource_id = kContentImageResourceId;
+  update->nodes.insert(kRootNodeId, root_node.Pass());
+
+  scene_->Update(update.Pass());
+  scene_->Publish(frame->TakeSceneMetadata());
+}
+
+}  // namespace examples
diff --git a/examples/ui/noodles/rasterizer.h b/examples/ui/noodles/rasterizer.h
new file mode 100644
index 0000000..040c985
--- /dev/null
+++ b/examples/ui/noodles/rasterizer.h
@@ -0,0 +1,44 @@
+// 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 EXAMPLES_UI_NOODLES_RASTERIZER_H_
+#define EXAMPLES_UI_NOODLES_RASTERIZER_H_
+
+#include <memory>
+
+#include "mojo/gpu/gl_context.h"
+#include "mojo/gpu/gl_context_owner.h"
+#include "mojo/public/interfaces/application/application_connector.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "mojo/skia/ganesh_context.h"
+#include "mojo/ui/ganesh_renderer.h"
+
+namespace examples {
+
+class Frame;
+
+// Ganesh-based rasterizer which runs on a separate thread from the view.
+// Calls into this object, including its creation, must be posted to the
+// correct message loop by the view.
+class Rasterizer {
+ public:
+  Rasterizer(mojo::ApplicationConnectorPtr connector,
+             mojo::gfx::composition::ScenePtr scene);
+
+  ~Rasterizer();
+
+  void PublishFrame(std::unique_ptr<Frame> frame);
+
+ private:
+  mojo::GLContextOwner gl_context_owner_;
+  mojo::skia::GaneshContext ganesh_context_;
+  mojo::ui::GaneshRenderer ganesh_renderer_;
+  mojo::gfx::composition::ScenePtr scene_;
+
+  DISALLOW_COPY_AND_ASSIGN(Rasterizer);
+};
+
+}  // namespace examples
+
+#endif  // EXAMPLES_UI_NOODLES_RASTERIZER_H_