Add helpers for creating UI components.
These helpers make it easier to use Mozart in C++ and greatly
reduce the amount of boilerplate involved in writing simple
applications.
View implementations:
- BaseView: A base implementation of the View interface.
- GLView: A View with an associated GLRenderer which takes care
of allocating, binding, and recycling textures.
- GaneshView: A View with an associated GaneshRenderer which takes
care of setting up a GaneshContext and drawing to canvas.
View providers:
- ViewProviderApp: Skeleton of a simple app which offers the
ViewProvider interface and vends Views on demand.
- ContextViewerApp: Skeleton of a simple app which offers the
ContentHandler interface and vends ViewProviders on demand.
Helpers:
- Choreographer: Coordinates the scheduling of drawing operations
on behalf of a View and compensates for lag.
- InputHandler: Binds an InputListener on behalf of a View.
BUG=
R=abarth@google.com, viettrungluu@chromium.org
Review URL: https://codereview.chromium.org/1556803002 .
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index 915c5b4..a5ef8c7 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -71,6 +71,7 @@
"//mojo/services/files/cpp:files_impl_apptests",
"//mojo/services/log/cpp:log_client_apptests",
"//mojo/tools:message_generator",
+ "//mojo/ui:unittests",
]
if (mojo_use_prebuilt_network_service) {
diff --git a/mojo/common/strong_binding_set.h b/mojo/common/strong_binding_set.h
index 95467d8..99b25ea 100644
--- a/mojo/common/strong_binding_set.h
+++ b/mojo/common/strong_binding_set.h
@@ -44,6 +44,16 @@
});
}
+ // Removes all bindings for the specified interface implementation.
+ // The implementation object is not destroyed.
+ void RemoveBindings(Interface* impl) {
+ bindings_.erase(
+ std::remove_if(bindings_.begin(), bindings_.end(),
+ [impl](const std::unique_ptr<Binding<Interface>>& b) {
+ return (b->impl() == impl);
+ }));
+ }
+
// Closes all bindings and deletes their associated interfaces.
void CloseAllBindings() {
for (auto it = bindings_.begin(); it != bindings_.end(); ++it) {
diff --git a/mojo/ui/BUILD.gn b/mojo/ui/BUILD.gn
new file mode 100644
index 0000000..a6c7b5a
--- /dev/null
+++ b/mojo/ui/BUILD.gn
@@ -0,0 +1,112 @@
+# 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")
+
+source_set("ui") {
+ sources = [
+ "base_view.cc",
+ "base_view.h",
+ "choreographer.cc",
+ "choreographer.h",
+ "input_handler.cc",
+ "input_handler.h",
+ "view_provider_app.cc",
+ "view_provider_app.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/gfx/composition/interfaces",
+ "//mojo/services/ui/input/interfaces",
+ "//mojo/services/ui/views/interfaces",
+ ]
+}
+
+source_set("content") {
+ sources = [
+ "content_viewer_app.cc",
+ "content_viewer_app.h",
+ ]
+
+ deps = [
+ ":ui",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/content_handler/interfaces",
+ "//mojo/services/ui/views/interfaces",
+ ]
+}
+
+source_set("gl") {
+ sources = [
+ "gl_renderer.cc",
+ "gl_renderer.h",
+ "gl_view.cc",
+ "gl_view.h",
+ ]
+
+ deps = [
+ ":ui",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/gpu",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/gfx/composition/interfaces",
+ "//mojo/services/ui/views/interfaces",
+ ]
+}
+
+source_set("ganesh") {
+ sources = [
+ "ganesh_renderer.cc",
+ "ganesh_renderer.h",
+ "ganesh_view.cc",
+ "ganesh_view.h",
+ ]
+
+ deps = [
+ ":gl",
+ ":ui",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/gpu",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/gfx/composition/interfaces",
+ "//mojo/services/ui/views/interfaces",
+ "//mojo/skia",
+ "//skia",
+ ]
+}
+
+mojo_native_application("unittests") {
+ output_name = "ui_unittests"
+
+ testonly = true
+
+ sources = [
+ "gl_renderer_unittest.cc",
+ ]
+
+ deps = [
+ ":gl",
+ ":ui",
+ "//base",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//mojo/gpu",
+ "//mojo/public/cpp/bindings:callback",
+ "//mojo/services/geometry/interfaces",
+ "//mojo/services/gfx/composition/interfaces",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/ui/base_view.cc b/mojo/ui/base_view.cc
new file mode 100644
index 0000000..b58faf4
--- /dev/null
+++ b/mojo/ui/base_view.cc
@@ -0,0 +1,36 @@
+// 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/ui/base_view.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace ui {
+
+BaseView::BaseView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback)
+ : app_impl_(app_impl), view_binding_(this) {
+ DCHECK(app_impl_);
+ app_impl_->ConnectToService("mojo:view_manager_service", &view_manager_);
+
+ mojo::ui::ViewPtr view;
+ view_binding_.Bind(mojo::GetProxy(&view));
+ view_manager_->RegisterView(view.Pass(), mojo::GetProxy(&view_host_), label,
+ create_view_callback);
+ view_host_->CreateScene(mojo::GetProxy(&scene_));
+ view_host_->GetServiceProvider(mojo::GetProxy(&view_service_provider_));
+}
+
+BaseView::~BaseView() {}
+
+void BaseView::OnChildUnavailable(uint32_t child_key,
+ const OnChildUnavailableCallback& callback) {
+ callback.Run();
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/base_view.h b/mojo/ui/base_view.h
new file mode 100644
index 0000000..757dcb9
--- /dev/null
+++ b/mojo/ui/base_view.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 MOJO_UI_BASE_VIEW_H_
+#define MOJO_UI_BASE_VIEW_H_
+
+#include <string>
+
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "mojo/services/ui/views/interfaces/view_manager.mojom.h"
+#include "mojo/services/ui/views/interfaces/view_provider.mojom.h"
+#include "mojo/services/ui/views/interfaces/views.mojom.h"
+
+namespace mojo {
+namespace ui {
+
+// Abstract base implementation of the View interface for simple applications.
+// Subclasses must handle layout and provide content for the scene by
+// implementing the methods of the |View| mojom interface.
+//
+// It is not necessary to use this class to implement all Views.
+// This class is merely intended to make the simple apps easier to write.
+class BaseView : public mojo::ui::View {
+ public:
+ // TODO(jeffbrown): Consider switching this over to an ApplicationConnector
+ // but having ApplicationImpl is handy for simple examples.
+ BaseView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback);
+
+ ~BaseView() override;
+
+ // Gets the application implementation object provided at creation time.
+ mojo::ApplicationImpl* app_impl() { return app_impl_; }
+
+ // Gets the view manager.
+ mojo::ui::ViewManager* view_manager() { return view_manager_.get(); }
+
+ // Gets the view host for the view.
+ mojo::ui::ViewHost* view_host() { return view_host_.get(); }
+
+ // Gets the service provider for the view.
+ mojo::ServiceProvider* view_service_provider() {
+ return view_service_provider_.get();
+ }
+
+ // Gets the scene for the view.
+ // Returns nullptr if the |TakeScene| was called.
+ mojo::gfx::composition::Scene* scene() { return scene_.get(); }
+
+ // Takes the scene from the view.
+ // This is useful if the scene will be rendered by a separate component.
+ mojo::gfx::composition::ScenePtr TakeScene() { return scene_.Pass(); }
+
+ // |View|:
+ void OnChildUnavailable(uint32_t child_key,
+ const OnChildUnavailableCallback& callback) override;
+
+ private:
+ mojo::ApplicationImpl* app_impl_;
+
+ mojo::StrongBinding<mojo::ui::View> view_binding_;
+ mojo::ui::ViewManagerPtr view_manager_;
+ mojo::ui::ViewHostPtr view_host_;
+ mojo::ServiceProviderPtr view_service_provider_;
+ mojo::gfx::composition::ScenePtr scene_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(BaseView);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_BASE_VIEW_H_
diff --git a/mojo/ui/choreographer.cc b/mojo/ui/choreographer.cc
new file mode 100644
index 0000000..7ea295b
--- /dev/null
+++ b/mojo/ui/choreographer.cc
@@ -0,0 +1,119 @@
+// 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/ui/choreographer.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "mojo/public/cpp/system/functions.h"
+
+namespace mojo {
+namespace ui {
+
+Choreographer::Choreographer(mojo::gfx::composition::Scene* scene,
+ ChoreographerDelegate* delegate)
+ : delegate_(delegate) {
+ DCHECK(delegate_);
+ scene->GetScheduler(mojo::GetProxy(&scene_scheduler_));
+}
+
+Choreographer::Choreographer(
+ mojo::gfx::composition::SceneSchedulerPtr scene_scheduler,
+ ChoreographerDelegate* delegate)
+ : scene_scheduler_(scene_scheduler.Pass()), delegate_(delegate) {
+ DCHECK(scene_scheduler_);
+ DCHECK(delegate_);
+}
+
+Choreographer::~Choreographer() {}
+
+void Choreographer::ScheduleDraw() {
+ if (!draw_scheduled_) {
+ draw_scheduled_ = true;
+ ScheduleFrame();
+ }
+}
+
+void Choreographer::ScheduleFrame() {
+ if (!frame_scheduled_) {
+ frame_scheduled_ = true;
+ scene_scheduler_->ScheduleFrame(
+ base::Bind(&Choreographer::DoFrame, base::Unretained(this)));
+ }
+}
+
+void Choreographer::DoFrame(mojo::gfx::composition::FrameInfoPtr frame_info) {
+ DCHECK(frame_info);
+ DCHECK(frame_scheduled_);
+ frame_scheduled_ = false;
+
+ if (draw_scheduled_) {
+ draw_scheduled_ = false;
+
+ // To reduce latency and jank, anticipate the next frame to be drawn by
+ // scheduling it early.
+ //
+ // TODO(jeffbrown): Reenable this once issue #604 is fixed. Unfortunately
+ // this exacerbates starvation issues in the Mojo message pump.
+ // ScheduleFrame();
+
+ // Ensure frame info is sane since it comes from another service.
+ // TODO(jeffbrown): Would be better to report an error to the client
+ // who can shut things down if needed.
+ MojoTimeTicks now = MojoGetTimeTicksNow();
+ if (frame_info->frame_time > now) {
+ LOG(WARNING) << "Frame time is in the future: frame_time="
+ << frame_info->frame_time << ", now=" << now;
+ frame_info->frame_time = now;
+ }
+ if (frame_info->frame_deadline < frame_info->frame_time) {
+ LOG(WARNING)
+ << "Frame deadline is earlier than frame time: frame_deadline="
+ << frame_info->frame_deadline
+ << ", frame_time=" << frame_info->frame_time << ", now=" << now;
+ frame_info->frame_deadline = frame_info->frame_time;
+ }
+ if (frame_info->presentation_time < frame_info->frame_deadline) {
+ LOG(WARNING) << "Presentation time is earlier than frame deadline: "
+ "presentation_time="
+ << frame_info->presentation_time
+ << ", frame_deadline=" << frame_info->frame_deadline
+ << ", now=" << now;
+ frame_info->presentation_time = frame_info->frame_deadline;
+ }
+
+ // Compensate for significant lag by adjusting the frame time if needed
+ // to step past skipped frames.
+ uint64_t lag = now - frame_info->frame_time;
+ if (frame_info->frame_interval > 0u && lag > frame_info->frame_interval) {
+ uint64_t offset = lag % frame_info->frame_interval;
+ uint64_t adjustment = now - offset - frame_info->frame_time;
+ frame_info->frame_time = now - offset;
+ frame_info->frame_deadline += adjustment;
+ frame_info->presentation_time += adjustment;
+
+ // Jank warning.
+ // TODO(jeffbrown): Suppress this once we're happy with things.
+ LOG(WARNING) << "Missed " << frame_info->frame_interval
+ << " us frame deadline by " << lag << " us, skipping "
+ << (lag / frame_info->frame_interval) << " frames";
+ }
+
+ // Ensure frame time isn't going backwards, just in case the compositor's
+ // timing is seriously broken.
+ base::TimeDelta time_delta;
+ if (last_frame_info_) {
+ DCHECK(frame_info->frame_time >= last_frame_info_->frame_time);
+ time_delta = base::TimeDelta::FromMicroseconds(
+ frame_info->frame_time - last_frame_info_->frame_time);
+ }
+
+ // Invoke the callback.
+ last_frame_info_ = frame_info.Pass();
+ delegate_->OnDraw(*last_frame_info_, time_delta);
+ }
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/choreographer.h b/mojo/ui/choreographer.h
new file mode 100644
index 0000000..3708cc2
--- /dev/null
+++ b/mojo/ui/choreographer.h
@@ -0,0 +1,98 @@
+// 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_UI_CHOREOGRAPHER_H_
+#define MOJO_UI_CHOREOGRAPHER_H_
+
+#include "base/time/time.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace mojo {
+namespace ui {
+
+class ChoreographerDelegate;
+
+// Coordinates drawing a frame of a scene.
+//
+// This class is intended to be included as a member of a View that wants to
+// schedule drawing using the following pattern.
+//
+// class MyView : public mojo::ui::BaseView,
+// public mojo::ui::ChoreographerDelegate {
+// public:
+// MyView(mojo::ApplicationImpl* app_impl,
+// const mojo::ui::ViewProvider::CreateViewCallback&
+// create_view_callback)
+// : BaseView(app_impl, "MyView", create_view_callback),
+// choreographer_(scene_scheduler(), this) {}
+// ~MyView() override {}
+//
+// private:
+// // |ChoreographerDelegate|:
+// void OnDraw(const mojo::gfx::composition::FrameInfo& frame_info) override;
+//
+// mojo::ui::Choreographer choreographer_;
+//
+// MOJO_DISALLOW_COPY_AND_ASSIGN(MyView);
+// };
+class Choreographer {
+ public:
+ Choreographer(mojo::gfx::composition::Scene* scene,
+ ChoreographerDelegate* delegate);
+ Choreographer(mojo::gfx::composition::SceneSchedulerPtr scene_scheduler,
+ ChoreographerDelegate* delegate);
+ ~Choreographer();
+
+ // Gets the scene scheduler.
+ mojo::gfx::composition::SceneScheduler* scene_scheduler() {
+ return scene_scheduler_.get();
+ }
+
+ // Gets the most recent frame info, or null if none.
+ mojo::gfx::composition::FrameInfo* last_frame_info() {
+ return last_frame_info_.get();
+ }
+
+ // Schedules a call to the delegate's |OnDraw| using the scene scheduler.
+ void ScheduleDraw();
+
+ private:
+ mojo::gfx::composition::SceneSchedulerPtr scene_scheduler_;
+ ChoreographerDelegate* delegate_;
+ mojo::gfx::composition::FrameInfoPtr last_frame_info_;
+
+ void ScheduleFrame();
+ void DoFrame(mojo::gfx::composition::FrameInfoPtr frame_info);
+
+ bool draw_scheduled_ = false;
+ bool frame_scheduled_ = false;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Choreographer);
+};
+
+// An abstract class that the view may subclass to handle choreographer events.
+class ChoreographerDelegate {
+ public:
+ ChoreographerDelegate() = default;
+ virtual ~ChoreographerDelegate() = default;
+
+ // Called when it is time to draw the next frame and provides timing
+ // information for the frame.
+ //
+ // The |frame_info| provides information about the frame timing.
+ // The |time_delta| is the time interval since the last draw occurred,
+ // guaranteed to be non-negative. Always zero for the first draw.
+ virtual void OnDraw(const mojo::gfx::composition::FrameInfo& frame_info,
+ const base::TimeDelta& time_delta) = 0;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ChoreographerDelegate);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_CHOREOGRAPHER_H_
diff --git a/mojo/ui/content_viewer_app.cc b/mojo/ui/content_viewer_app.cc
new file mode 100644
index 0000000..6993082
--- /dev/null
+++ b/mojo/ui/content_viewer_app.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 "mojo/ui/content_viewer_app.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+
+namespace mojo {
+namespace ui {
+
+class ContentViewerApp::DelegatingContentHandler : public mojo::ContentHandler {
+ public:
+ DelegatingContentHandler(ContentViewerApp* app,
+ const std::string& content_handler_url)
+ : app_(app), content_handler_url_(content_handler_url) {}
+
+ ~DelegatingContentHandler() override {}
+
+ private:
+ // |ContentHandler|:
+ void StartApplication(
+ mojo::InterfaceRequest<mojo::Application> application_request,
+ mojo::URLResponsePtr response) override {
+ app_->StartViewer(content_handler_url_, application_request.Pass(),
+ response.Pass());
+ }
+
+ ContentViewerApp* app_;
+ std::string content_handler_url_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DelegatingContentHandler);
+};
+
+ContentViewerApp::ContentViewerApp() {}
+
+ContentViewerApp::~ContentViewerApp() {}
+
+void ContentViewerApp::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);
+}
+
+bool ContentViewerApp::ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) {
+ connection->AddService<mojo::ContentHandler>(this);
+ return true;
+}
+
+void ContentViewerApp::Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::ContentHandler> request) {
+ bindings_.AddBinding(
+ new DelegatingContentHandler(this, connection->GetConnectionURL()),
+ request.Pass());
+}
+
+void ContentViewerApp::StartViewer(
+ const std::string& content_handler_url,
+ mojo::InterfaceRequest<mojo::Application> application_request,
+ mojo::URLResponsePtr response) {
+ ViewProviderApp* app = LoadContent(content_handler_url, response.Pass());
+ if (app)
+ new mojo::ApplicationImpl(app, application_request.Pass());
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/content_viewer_app.h b/mojo/ui/content_viewer_app.h
new file mode 100644
index 0000000..a45ebf9
--- /dev/null
+++ b/mojo/ui/content_viewer_app.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 MOJO_UI_CONTENT_VIEWER_APP_H_
+#define MOJO_UI_CONTENT_VIEWER_APP_H_
+
+#include "mojo/common/strong_binding_set.h"
+#include "mojo/services/content_handler/interfaces/content_handler.mojom.h"
+#include "mojo/ui/view_provider_app.h"
+
+namespace mojo {
+namespace ui {
+
+// A simple ContentHandler application implementation for rendering
+// content as Views. Subclasses must provide a function to create
+// the view provider application on demand.
+//
+// TODO(jeffbrown): Support creating the view provider application in a
+// separate thread if desired (often not the case). This is one reason
+// we are not using the ContentHandlerFactory here.
+class ContentViewerApp : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::ContentHandler> {
+ public:
+ ContentViewerApp();
+ ~ContentViewerApp() override;
+
+ mojo::ApplicationImpl* app_impl() { return app_impl_; }
+
+ // |ApplicationDelegate|:
+ void Initialize(mojo::ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // Called to create the view provider application to view the content.
+ //
+ // This method may be called multiple times to load different content
+ // into separate view providers. The view provider implementation should
+ // cache the loaded content in case it is asked to create multiple instances
+ // of the view since the response can only be consumed once.
+ //
+ // The |content_handler_url| is the connection URL of the content handler
+ // request.
+ // The |response| carries the data retrieved by the content handler.
+ //
+ // Returns the view provider application delegate to view the content,
+ // or nullptr if the content could not be loaded.
+ virtual ViewProviderApp* LoadContent(const std::string& content_handler_url,
+ mojo::URLResponsePtr response) = 0;
+
+ private:
+ class DelegatingContentHandler;
+
+ // |InterfaceFactory<ContentHandler>|:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::ContentHandler> request) override;
+
+ void StartViewer(
+ const std::string& content_handler_url,
+ mojo::InterfaceRequest<mojo::Application> application_request,
+ mojo::URLResponsePtr response);
+
+ mojo::ApplicationImpl* app_impl_ = nullptr;
+ mojo::StrongBindingSet<mojo::ContentHandler> bindings_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ContentViewerApp);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_CONTENT_VIEWER_APP_H_
diff --git a/mojo/ui/ganesh_renderer.cc b/mojo/ui/ganesh_renderer.cc
new file mode 100644
index 0000000..a1f47b3
--- /dev/null
+++ b/mojo/ui/ganesh_renderer.cc
@@ -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.
+
+#include "mojo/ui/ganesh_renderer.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "mojo/gpu/gl_texture.h"
+#include "mojo/skia/ganesh_context.h"
+#include "mojo/skia/ganesh_texture_surface.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkSurface.h"
+
+namespace mojo {
+namespace ui {
+
+GaneshRenderer::GaneshRenderer(mojo::skia::GaneshContext* ganesh_context)
+ : ganesh_context_(ganesh_context),
+ gl_renderer_(ganesh_context_->gl_context()) {
+ DCHECK(ganesh_context_);
+}
+
+GaneshRenderer::~GaneshRenderer() {}
+
+mojo::gfx::composition::ResourcePtr GaneshRenderer::DrawSurface(
+ const mojo::Size& size,
+ const DrawSurfaceCallback& callback) {
+ std::unique_ptr<mojo::GLTexture> texture = gl_renderer_.GetTexture(size);
+ DCHECK(texture);
+
+ {
+ mojo::skia::GaneshContext::Scope scope(ganesh_context_);
+ mojo::skia::GaneshTextureSurface texture_surface(scope, std::move(texture));
+
+ callback.Run(texture_surface.surface());
+
+ texture = texture_surface.TakeTexture();
+ }
+
+ return gl_renderer_.BindTextureResource(std::move(texture));
+}
+
+static void RunCanvasCallback(
+ const mojo::ui::GaneshRenderer::DrawCanvasCallback& callback,
+ SkSurface* surface) {
+ callback.Run(surface->getCanvas());
+}
+
+mojo::gfx::composition::ResourcePtr GaneshRenderer::DrawCanvas(
+ const mojo::Size& size,
+ const DrawCanvasCallback& callback) {
+ return DrawSurface(size, base::Bind(&RunCanvasCallback, callback));
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/ganesh_renderer.h b/mojo/ui/ganesh_renderer.h
new file mode 100644
index 0000000..5190ecd
--- /dev/null
+++ b/mojo/ui/ganesh_renderer.h
@@ -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.
+
+#ifndef MOJO_UI_GANESH_RENDERER_H_
+#define MOJO_UI_GANESH_RENDERER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/services/gfx/composition/interfaces/resources.mojom.h"
+#include "mojo/ui/gl_renderer.h"
+
+class SkCanvas;
+class SkSurface;
+
+namespace mojo {
+namespace skia {
+class GaneshContext;
+} // namespace skia
+
+namespace ui {
+
+// Provides support for rendering Skia commands into surfaces backed by
+// a pool of textures and producing scene resources for them.
+class GaneshRenderer {
+ public:
+ // Called with an active GaneshContext and a Skia surface or canvas
+ // to draw into the framebuffer.
+ using DrawSurfaceCallback = base::Callback<void(SkSurface* surface)>;
+ using DrawCanvasCallback = base::Callback<void(SkCanvas* canvas)>;
+
+ // Creates a Ganesh backed renderer.
+ // Does not take ownership of |ganesh_context|; it must outlive the renderer.
+ explicit GaneshRenderer(mojo::skia::GaneshContext* ganesh_context);
+ ~GaneshRenderer();
+
+ // Gets the Ganesh context.
+ mojo::skia::GaneshContext* ganesh_context() { return ganesh_context_; }
+
+ // Allocates a GL texture, binds it to a Ganesh surface or canvas,
+ // invokes the provided function, then returns the resulting resource.
+ mojo::gfx::composition::ResourcePtr DrawSurface(
+ const mojo::Size& size,
+ const DrawSurfaceCallback& callback);
+ mojo::gfx::composition::ResourcePtr DrawCanvas(
+ const mojo::Size& size,
+ const DrawCanvasCallback& callback);
+
+ private:
+ mojo::skia::GaneshContext* ganesh_context_;
+ mojo::ui::GLRenderer gl_renderer_;
+
+ DISALLOW_COPY_AND_ASSIGN(GaneshRenderer);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_GANESH_RENDERER_H_
diff --git a/mojo/ui/ganesh_view.cc b/mojo/ui/ganesh_view.cc
new file mode 100644
index 0000000..c700380
--- /dev/null
+++ b/mojo/ui/ganesh_view.cc
@@ -0,0 +1,27 @@
+// 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.
+
+#include "mojo/ui/ganesh_view.h"
+
+#include "base/logging.h"
+#include "mojo/skia/ganesh_texture_surface.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+
+namespace mojo {
+namespace ui {
+
+GaneshView::GaneshView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback)
+ : BaseView(app_impl, label, create_view_callback),
+ gl_context_owner_(mojo::MakeProxy(app_impl->CreateApplicationConnector())
+ .get()),
+ ganesh_context_(gl_context()),
+ ganesh_renderer_(&ganesh_context_) {}
+
+GaneshView::~GaneshView() {}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/ganesh_view.h b/mojo/ui/ganesh_view.h
new file mode 100644
index 0000000..7cf29e9
--- /dev/null
+++ b/mojo/ui/ganesh_view.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef MOJO_UI_GANESH_VIEW_H_
+#define MOJO_UI_GANESH_VIEW_H_
+
+#include "mojo/gpu/gl_context.h"
+#include "mojo/gpu/gl_context_owner.h"
+#include "mojo/skia/ganesh_context.h"
+#include "mojo/ui/base_view.h"
+#include "mojo/ui/ganesh_renderer.h"
+
+class SkSurface;
+
+namespace mojo {
+namespace ui {
+
+// Abstract base implementation of the View interface for simple applications
+// which use Ganesh for rendering. Subclasses must handle layout and provide
+// content for the scene.
+class GaneshView : public BaseView {
+ public:
+ GaneshView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback);
+
+ ~GaneshView() override;
+
+ // Gets the GL context, or null if none.
+ const base::WeakPtr<mojo::GLContext>& gl_context() const {
+ return gl_context_owner_.context();
+ }
+
+ // Gets the Ganesh context.
+ mojo::skia::GaneshContext* ganesh_context() { return &ganesh_context_; }
+
+ // Gets the Ganesh renderer.
+ mojo::ui::GaneshRenderer* ganesh_renderer() { return &ganesh_renderer_; }
+
+ private:
+ mojo::GLContextOwner gl_context_owner_;
+ mojo::skia::GaneshContext ganesh_context_;
+ mojo::ui::GaneshRenderer ganesh_renderer_;
+
+ DISALLOW_COPY_AND_ASSIGN(GaneshView);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_GANESH_VIEW_H_
diff --git a/mojo/ui/gl_renderer.cc b/mojo/ui/gl_renderer.cc
new file mode 100644
index 0000000..3adf507
--- /dev/null
+++ b/mojo/ui/gl_renderer.cc
@@ -0,0 +1,161 @@
+// 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/ui/gl_renderer.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+#include <GLES2/gl2.h>
+#include <GLES2/gl2extmojo.h>
+
+#include "mojo/gpu/gl_context.h"
+#include "mojo/gpu/gl_texture.h"
+
+namespace mojo {
+namespace ui {
+
+GLRenderer::GLRenderer(base::WeakPtr<mojo::GLContext> gl_context,
+ uint32_t max_recycled_textures)
+ : gl_context_(gl_context),
+ max_recycled_textures_(max_recycled_textures),
+ weak_factory_(this) {}
+
+GLRenderer::~GLRenderer() {}
+
+std::unique_ptr<mojo::GLTexture> GLRenderer::GetTexture(
+ const mojo::Size& requested_size) {
+ if (!gl_context_) {
+ recycled_textures_.clear();
+ return nullptr;
+ }
+
+ while (!recycled_textures_.empty()) {
+ GLRecycledTextureInfo texture_info(std::move(recycled_textures_.front()));
+ recycled_textures_.pop_front();
+ if (texture_info.first->size().Equals(requested_size)) {
+ gl_context_->MakeCurrent();
+ glWaitSyncPointCHROMIUM(texture_info.second);
+ return std::move(texture_info.first);
+ }
+ }
+
+ return std::unique_ptr<GLTexture>(new GLTexture(gl_context_, requested_size));
+}
+
+mojo::gfx::composition::ResourcePtr GLRenderer::BindTextureResource(
+ std::unique_ptr<GLTexture> texture) {
+ if (!gl_context_)
+ return nullptr;
+
+ // Produce the texture.
+ gl_context_->MakeCurrent();
+ glBindTexture(GL_TEXTURE_2D, texture->texture_id());
+ GLbyte mailbox[GL_MAILBOX_SIZE_CHROMIUM];
+ glGenMailboxCHROMIUM(mailbox);
+ glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ GLuint sync_point = glInsertSyncPointCHROMIUM();
+
+ // Populate the resource description.
+ auto resource = mojo::gfx::composition::Resource::New();
+ resource->set_mailbox_texture(
+ mojo::gfx::composition::MailboxTextureResource::New());
+ resource->get_mailbox_texture()->mailbox_name.resize(sizeof(mailbox));
+ memcpy(resource->get_mailbox_texture()->mailbox_name.data(), mailbox,
+ sizeof(mailbox));
+ resource->get_mailbox_texture()->sync_point = sync_point;
+ resource->get_mailbox_texture()->size = texture->size().Clone();
+ resource->get_mailbox_texture()->callback =
+ (new GLTextureReleaser(
+ weak_factory_.GetWeakPtr(),
+ GLRecycledTextureInfo(std::move(texture), sync_point)))
+ ->StrongBind()
+ .Pass();
+
+ bound_textures_++;
+ DVLOG(2) << "bind: bound_textures=" << bound_textures_;
+ return resource;
+}
+
+mojo::gfx::composition::ResourcePtr GLRenderer::DrawGL(
+ const mojo::Size& size,
+ bool with_depth,
+ const DrawGLCallback& callback) {
+ std::unique_ptr<mojo::GLTexture> texture = GetTexture(size);
+ DCHECK(texture);
+
+ GLuint fbo = 0u;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture->texture_id(), 0);
+
+ GLuint depth_buffer = 0u;
+ if (with_depth) {
+ glGenRenderbuffers(1, &depth_buffer);
+ glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width,
+ size.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, depth_buffer);
+ }
+
+ DCHECK_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
+ glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+ glViewport(0, 0, size.width, size.height);
+ callback.Run();
+
+ if (with_depth)
+ glDeleteRenderbuffers(1, &depth_buffer);
+ glDeleteFramebuffers(1, &fbo);
+
+ return BindTextureResource(std::move(texture));
+}
+
+void GLRenderer::ReleaseTexture(GLRecycledTextureInfo texture_info,
+ bool recyclable) {
+ DCHECK(bound_textures_);
+ bound_textures_--;
+ if (recyclable && recycled_textures_.size() < max_recycled_textures_) {
+ recycled_textures_.emplace_back(std::move(texture_info));
+ }
+ DVLOG(2) << "release: bound_textures=" << bound_textures_
+ << ", recycled_textures=" << recycled_textures_.size();
+}
+
+GLRenderer::GLTextureReleaser::GLTextureReleaser(
+ const base::WeakPtr<GLRenderer>& provider,
+ GLRecycledTextureInfo info)
+ : provider_(provider), texture_info_(std::move(info)), binding_(this) {}
+
+GLRenderer::GLTextureReleaser::~GLTextureReleaser() {
+ // It's possible for the object to be destroyed due to a connection
+ // error on the callback pipe. When this happens we don't want to
+ // recycle the texture since we have too little knowledge about its
+ // state to confirm that it will be safe to do so.
+ Release(false /*recyclable*/);
+}
+
+mojo::gfx::composition::MailboxTextureCallbackPtr
+GLRenderer::GLTextureReleaser::StrongBind() {
+ mojo::gfx::composition::MailboxTextureCallbackPtr callback;
+ binding_.Bind(mojo::GetProxy(&callback));
+ return callback;
+}
+
+void GLRenderer::GLTextureReleaser::OnMailboxTextureReleased() {
+ Release(true /*recyclable*/);
+}
+
+void GLRenderer::GLTextureReleaser::Release(bool recyclable) {
+ if (provider_) {
+ provider_->ReleaseTexture(std::move(texture_info_), recyclable);
+ provider_.reset();
+ }
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/gl_renderer.h b/mojo/ui/gl_renderer.h
new file mode 100644
index 0000000..a21c799
--- /dev/null
+++ b/mojo/ui/gl_renderer.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 MOJO_UI_GL_RENDERER_H_
+#define MOJO_UI_GL_RENDERER_H_
+
+#include <deque>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/gfx/composition/interfaces/resources.mojom.h"
+
+namespace mojo {
+
+class GLContext;
+class GLTexture;
+class Size;
+
+namespace ui {
+
+// Provides support for rendering GL commands into a pool of textures
+// and producing scene resources for them.
+class GLRenderer {
+ public:
+ // Called with an active GL context to draw into the framebuffer.
+ using DrawGLCallback = base::Closure;
+
+ GLRenderer(base::WeakPtr<mojo::GLContext> gl_context,
+ uint32_t max_recycled_textures = 3u);
+ ~GLRenderer();
+
+ // Gets the GL context, or null if none.
+ const base::WeakPtr<mojo::GLContext>& gl_context() const {
+ return gl_context_;
+ }
+
+ // Obtains a texture of the specified size.
+ // Returns a nullptr if the GLContext was destroyed.
+ std::unique_ptr<mojo::GLTexture> GetTexture(const mojo::Size& requested_size);
+
+ // Takes ownership of the specified texture, issues GL commands to
+ // produce a mailbox texture, and returns its resource pointer.
+ // The caller should add the resource to its scene.
+ // Returns a nullptr if the GLContext was destroyed.
+ mojo::gfx::composition::ResourcePtr BindTextureResource(
+ std::unique_ptr<GLTexture> texture);
+
+ // Allocates a GL texture, binds it to a framebuffer, invokes the
+ // provided function, then returns the resulting resource.
+ // If |with_depth| is true, provides a depth buffer attachment.
+ mojo::gfx::composition::ResourcePtr DrawGL(const mojo::Size& size,
+ bool with_depth,
+ const DrawGLCallback& callback);
+
+ private:
+ using GLRecycledTextureInfo =
+ std::pair<std::unique_ptr<mojo::GLTexture>, uint32_t>;
+
+ // TODO(jeffbrown): Avoid creating new callbacks each time, perhaps by
+ // migrating to image pipes.
+ class GLTextureReleaser : mojo::gfx::composition::MailboxTextureCallback {
+ public:
+ GLTextureReleaser(const base::WeakPtr<GLRenderer>& provider,
+ GLRecycledTextureInfo info);
+ ~GLTextureReleaser() override;
+
+ mojo::gfx::composition::MailboxTextureCallbackPtr StrongBind();
+
+ private:
+ void OnMailboxTextureReleased() override;
+ void Release(bool recyclable);
+
+ base::WeakPtr<GLRenderer> provider_;
+ GLRecycledTextureInfo texture_info_;
+ mojo::StrongBinding<mojo::gfx::composition::MailboxTextureCallback>
+ binding_;
+ };
+
+ void ReleaseTexture(GLRecycledTextureInfo texture_info, bool recyclable);
+
+ const base::WeakPtr<mojo::GLContext> gl_context_;
+ const uint32_t max_recycled_textures_;
+
+ std::deque<GLRecycledTextureInfo> recycled_textures_;
+ uint32_t bound_textures_ = 0u;
+
+ base::WeakPtrFactory<mojo::ui::GLRenderer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GLRenderer);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_GL_RENDERER_H_
diff --git a/mojo/ui/gl_renderer_unittest.cc b/mojo/ui/gl_renderer_unittest.cc
new file mode 100644
index 0000000..c9ffabd
--- /dev/null
+++ b/mojo/ui/gl_renderer_unittest.cc
@@ -0,0 +1,209 @@
+// 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 "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/gpu/gl_context.h"
+#include "mojo/gpu/gl_texture.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/services/geometry/interfaces/geometry.mojom.h"
+#include "mojo/services/gfx/composition/interfaces/resources.mojom.h"
+#include "mojo/ui/gl_renderer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+static const base::TimeDelta kDefaultMessageDelay =
+ base::TimeDelta::FromMilliseconds(20);
+
+class GLRendererTest : public mojo::test::ApplicationTestBase {
+ public:
+ GLRendererTest() : weak_factory_(this) {}
+ ~GLRendererTest() override {}
+
+ void SetUp() override {
+ mojo::test::ApplicationTestBase::SetUp();
+ gl_context_ = mojo::GLContext::CreateOffscreen(
+ mojo::MakeProxy(application_impl()->CreateApplicationConnector())
+ .get());
+ quit_message_loop_callback_ = base::Bind(
+ &GLRendererTest::QuitMessageLoopCallback, weak_factory_.GetWeakPtr());
+ }
+
+ void QuitMessageLoopCallback() { base::MessageLoop::current()->Quit(); }
+
+ void KickMessageLoop() {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, quit_message_loop_callback_, kDefaultMessageDelay);
+ base::MessageLoop::current()->Run();
+ }
+
+ protected:
+ base::WeakPtr<mojo::GLContext> gl_context_;
+ base::Closure quit_message_loop_callback_;
+ base::WeakPtrFactory<GLRendererTest> weak_factory_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GLRendererTest);
+};
+
+TEST_F(GLRendererTest, GetTextureOnce) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture = renderer.GetTexture(size);
+ EXPECT_NE(texture.get(), nullptr);
+
+ mojo::gfx::composition::ResourcePtr resource =
+ renderer.BindTextureResource(std::move(texture));
+ EXPECT_NE(resource.get(), nullptr);
+ EXPECT_NE(resource->get_mailbox_texture().get(), nullptr);
+ EXPECT_FALSE(resource->get_mailbox_texture()->mailbox_name.is_null());
+ EXPECT_TRUE(resource->get_mailbox_texture()->size->Equals(size));
+ EXPECT_NE(resource->get_mailbox_texture()->sync_point, 0u);
+ EXPECT_NE(resource->get_mailbox_texture()->callback.get(), nullptr);
+}
+
+TEST_F(GLRendererTest, GetTextureTwiceSameSize) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture1 = renderer.GetTexture(size);
+ EXPECT_NE(texture1.get(), nullptr);
+
+ std::unique_ptr<mojo::GLTexture> texture2 = renderer.GetTexture(size);
+ EXPECT_NE(texture2.get(), nullptr);
+
+ EXPECT_NE(texture2.get(), texture1.get());
+ EXPECT_NE(texture2->texture_id(), texture1->texture_id());
+
+ mojo::gfx::composition::ResourcePtr resource1 =
+ renderer.BindTextureResource(std::move(texture1));
+ EXPECT_NE(resource1.get(), nullptr);
+ EXPECT_NE(resource1->get_mailbox_texture().get(), nullptr);
+ EXPECT_FALSE(resource1->get_mailbox_texture()->mailbox_name.is_null());
+ EXPECT_TRUE(resource1->get_mailbox_texture()->size->Equals(size));
+ EXPECT_NE(resource1->get_mailbox_texture()->sync_point, 0u);
+ EXPECT_NE(resource1->get_mailbox_texture()->callback.get(), nullptr);
+
+ mojo::gfx::composition::ResourcePtr resource2 =
+ renderer.BindTextureResource(std::move(texture2));
+ EXPECT_NE(resource2.get(), nullptr);
+ EXPECT_NE(resource2->get_mailbox_texture().get(), nullptr);
+ EXPECT_FALSE(resource2->get_mailbox_texture()->mailbox_name.is_null());
+ EXPECT_TRUE(resource2->get_mailbox_texture()->size->Equals(size));
+ EXPECT_NE(resource2->get_mailbox_texture()->sync_point, 0u);
+ EXPECT_NE(resource2->get_mailbox_texture()->callback.get(), nullptr);
+
+ EXPECT_NE(resource2->get_mailbox_texture()->sync_point,
+ resource1->get_mailbox_texture()->sync_point);
+}
+
+TEST_F(GLRendererTest, GetTextureAfterRecycleSameSize) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture1 = renderer.GetTexture(size);
+ EXPECT_NE(texture1.get(), nullptr);
+ mojo::GLTexture* original_texture = texture1.get();
+
+ // Return the texture.
+ mojo::gfx::composition::ResourcePtr resource1 =
+ renderer.BindTextureResource(std::move(texture1));
+ EXPECT_NE(resource1.get(), nullptr);
+ resource1->get_mailbox_texture()->callback->OnMailboxTextureReleased();
+
+ KickMessageLoop();
+
+ // Get a texture of the same size, it should be the same one as before.
+ std::unique_ptr<mojo::GLTexture> texture2 = renderer.GetTexture(size);
+ EXPECT_EQ(texture2.get(), original_texture);
+}
+
+TEST_F(GLRendererTest, GetTextureAfterRecycleDifferentSize) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size1;
+ size1.width = 100;
+ size1.height = 100;
+ std::unique_ptr<mojo::GLTexture> texture1 = renderer.GetTexture(size1);
+ EXPECT_NE(texture1.get(), nullptr);
+ EXPECT_TRUE(texture1->size().Equals(size1));
+
+ // Return the texture.
+ mojo::gfx::composition::ResourcePtr resource1 =
+ renderer.BindTextureResource(std::move(texture1));
+ EXPECT_NE(resource1.get(), nullptr);
+ resource1->get_mailbox_texture()->callback->OnMailboxTextureReleased();
+
+ KickMessageLoop();
+
+ // Get a texture of the a different size, it should be a new one
+ // with the new size.
+ mojo::Size size2;
+ size2.width = size1.width - 1;
+ size2.height = size1.height - 1;
+ std::unique_ptr<mojo::GLTexture> texture2 = renderer.GetTexture(size2);
+ EXPECT_NE(texture2.get(), nullptr);
+ EXPECT_TRUE(texture2->size().Equals(size2));
+}
+
+TEST_F(GLRendererTest, GetTextureReleasedGlContext) {
+ gl_context_->Destroy();
+
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture = renderer.GetTexture(size);
+ EXPECT_EQ(texture.get(), nullptr);
+}
+
+TEST_F(GLRendererTest, BindTextureResourceReleasedGlContext) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture = renderer.GetTexture(size);
+ EXPECT_NE(texture.get(), nullptr);
+
+ gl_context_->Destroy();
+
+ mojo::gfx::composition::ResourcePtr resource =
+ renderer.BindTextureResource(std::move(texture));
+ EXPECT_EQ(resource.get(), nullptr);
+}
+
+TEST_F(GLRendererTest, RecycledAfterReleasedGlContext) {
+ mojo::ui::GLRenderer renderer(gl_context_);
+ mojo::Size size;
+ size.width = 100;
+ size.height = 100;
+
+ std::unique_ptr<mojo::GLTexture> texture1 = renderer.GetTexture(size);
+ EXPECT_NE(texture1.get(), nullptr);
+
+ mojo::gfx::composition::ResourcePtr resource1 =
+ renderer.BindTextureResource(std::move(texture1));
+ EXPECT_NE(resource1.get(), nullptr);
+
+ gl_context_->Destroy();
+ resource1->get_mailbox_texture()->callback->OnMailboxTextureReleased();
+
+ KickMessageLoop();
+
+ // Get a texture of the same size, should be null due to the released context.
+ std::unique_ptr<mojo::GLTexture> texture2 = renderer.GetTexture(size);
+ EXPECT_EQ(texture2.get(), nullptr);
+}
+
+} // namespace
diff --git a/mojo/ui/gl_view.cc b/mojo/ui/gl_view.cc
new file mode 100644
index 0000000..3e7fea3
--- /dev/null
+++ b/mojo/ui/gl_view.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 "mojo/ui/gl_view.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace ui {
+
+GLView::GLView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback)
+ : BaseView(app_impl, label, create_view_callback),
+ gl_context_owner_(mojo::MakeProxy(app_impl->CreateApplicationConnector())
+ .get()),
+ gl_renderer_(gl_context_owner_.context()) {}
+
+GLView::~GLView() {}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/gl_view.h b/mojo/ui/gl_view.h
new file mode 100644
index 0000000..1f9796c
--- /dev/null
+++ b/mojo/ui/gl_view.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 MOJO_UI_GL_VIEW_H_
+#define MOJO_UI_GL_VIEW_H_
+
+#include "mojo/gpu/gl_context.h"
+#include "mojo/gpu/gl_context_owner.h"
+#include "mojo/ui/base_view.h"
+#include "mojo/ui/gl_renderer.h"
+
+namespace mojo {
+namespace ui {
+
+// Abstract base implementation of the View interface for simple applications
+// which use GL for rendering. Subclasses must handle layout and provide
+// content for the scene.
+class GLView : public BaseView {
+ public:
+ GLView(
+ mojo::ApplicationImpl* app_impl,
+ const std::string& label,
+ const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback);
+
+ ~GLView() override;
+
+ // Gets the GL context, or null if none.
+ const base::WeakPtr<mojo::GLContext>& gl_context() const {
+ return gl_context_owner_.context();
+ }
+
+ // Gets the GL renderer.
+ mojo::ui::GLRenderer* gl_renderer() { return &gl_renderer_; }
+
+ private:
+ mojo::GLContextOwner gl_context_owner_;
+ mojo::ui::GLRenderer gl_renderer_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(GLView);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_GL_VIEW_H_
diff --git a/mojo/ui/input_handler.cc b/mojo/ui/input_handler.cc
new file mode 100644
index 0000000..7eb06f8
--- /dev/null
+++ b/mojo/ui/input_handler.cc
@@ -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.
+
+#include "mojo/ui/input_handler.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/application/connect.h"
+
+namespace mojo {
+namespace ui {
+
+InputHandler::InputHandler(mojo::ServiceProvider* service_provider,
+ mojo::ui::InputListener* listener)
+ : listener_binding_(listener) {
+ DCHECK(service_provider);
+ DCHECK(listener);
+
+ mojo::ConnectToService(service_provider, &connection_);
+
+ mojo::ui::InputListenerPtr listener_ptr;
+ listener_binding_.Bind(mojo::GetProxy(&listener_ptr));
+ connection_->SetListener(listener_ptr.Pass());
+}
+
+InputHandler::~InputHandler() {}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/input_handler.h b/mojo/ui/input_handler.h
new file mode 100644
index 0000000..5073584
--- /dev/null
+++ b/mojo/ui/input_handler.h
@@ -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.
+
+#ifndef MOJO_UI_INPUT_HANDLER_H_
+#define MOJO_UI_INPUT_HANDLER_H_
+
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/ui/input/interfaces/input_connection.mojom.h"
+
+namespace mojo {
+namespace ui {
+
+// Holds an |InputConnection| and sets its |InputListener|.
+//
+// This class is intended to be included as a member of a View that wants to
+// receive input using the following pattern.
+//
+// class MyView : public mojo::ui::BaseView, public mojo::ui::InputListener {
+// public:
+// MyView(mojo::ApplicationImpl* app_impl,
+// const mojo::ui::ViewProvider::CreateViewCallback&
+// create_view_callback)
+// : BaseView(app_impl, "MyView", create_view_callback),
+// input_handler_(view_service_provider(), this) {}
+// ~MyView() override {}
+//
+// private:
+// // |InputListener|:
+// void OnEvent(mojo::EventPtr event,
+// const OnEventCallback& callback) override;
+//
+// mojo::ui::InputHandler input_handler_;
+//
+// MOJO_DISALLOW_COPY_AND_ASSIGN(MyView);
+// };
+class InputHandler {
+ public:
+ // Creates an input connection associated with the specified view host.
+ InputHandler(mojo::ServiceProvider* service_provider,
+ mojo::ui::InputListener* listener);
+ ~InputHandler();
+
+ // Gets the input connection.
+ mojo::ui::InputConnection* connection() { return connection_.get(); }
+
+ private:
+ mojo::Binding<mojo::ui::InputListener> listener_binding_;
+ mojo::ui::InputConnectionPtr connection_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(InputHandler);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_INPUT_HANDLER_H_
diff --git a/mojo/ui/view_provider_app.cc b/mojo/ui/view_provider_app.cc
new file mode 100644
index 0000000..40d5a5b
--- /dev/null
+++ b/mojo/ui/view_provider_app.cc
@@ -0,0 +1,79 @@
+// 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/ui/view_provider_app.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+
+namespace mojo {
+namespace ui {
+
+class ViewProviderApp::DelegatingViewProvider : public mojo::ui::ViewProvider {
+ public:
+ DelegatingViewProvider(ViewProviderApp* app,
+ const std::string& view_provider_url)
+ : app_(app), view_provider_url_(view_provider_url) {}
+
+ ~DelegatingViewProvider() override {}
+
+ private:
+ // |ViewProvider|:
+ void CreateView(
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services,
+ const mojo::ui::ViewProvider::CreateViewCallback& callback) override {
+ app_->CreateView(this, view_provider_url_, services.Pass(),
+ exposed_services.Pass(), callback);
+ }
+
+ ViewProviderApp* app_;
+ std::string view_provider_url_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DelegatingViewProvider);
+};
+
+ViewProviderApp::ViewProviderApp() {}
+
+ViewProviderApp::~ViewProviderApp() {}
+
+void ViewProviderApp::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);
+}
+
+bool ViewProviderApp::ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) {
+ connection->AddService<mojo::ui::ViewProvider>(this);
+ return true;
+}
+
+void ViewProviderApp::Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::ui::ViewProvider> request) {
+ bindings_.AddBinding(
+ new DelegatingViewProvider(this, connection->GetConnectionURL()),
+ request.Pass());
+}
+
+void ViewProviderApp::CreateView(
+ DelegatingViewProvider* provider,
+ const std::string& view_provider_url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services,
+ const mojo::ui::ViewProvider::CreateViewCallback& callback) {
+ if (!CreateView(view_provider_url, services.Pass(), exposed_services.Pass(),
+ callback)) {
+ bindings_.RemoveBindings(provider);
+ delete provider;
+ }
+}
+
+} // namespace ui
+} // namespace mojo
diff --git a/mojo/ui/view_provider_app.h b/mojo/ui/view_provider_app.h
new file mode 100644
index 0000000..07668c6
--- /dev/null
+++ b/mojo/ui/view_provider_app.h
@@ -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.
+
+#ifndef MOJO_UI_VIEW_PROVIDER_APP_H_
+#define MOJO_UI_VIEW_PROVIDER_APP_H_
+
+#include <string>
+
+#include "mojo/common/strong_binding_set.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 "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/ui/views/interfaces/view_provider.mojom.h"
+
+namespace mojo {
+namespace ui {
+
+// Abstract implementation of a simple application that offers a ViewProvider.
+// Subclasses must provide a function to create the necessary Views.
+//
+// It is not necessary to use this class to implement all ViewProviders.
+// This class is merely intended to make the simple apps easier to write.
+class ViewProviderApp : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::ui::ViewProvider> {
+ public:
+ ViewProviderApp();
+ ~ViewProviderApp() override;
+
+ mojo::ApplicationImpl* app_impl() { return app_impl_; }
+
+ // |ApplicationDelegate|:
+ void Initialize(mojo::ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // Called by the ViewProvider to create a view.
+ // This method may be called multiple times in the case where the
+ // view provider is asked to create multiple view instances.
+ //
+ // The |view_provider_url| is the connection URL of the view provider request.
+ //
+ // Returns true if successful, false if the view could not be created.
+ virtual bool CreateView(
+ const std::string& view_provider_url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services,
+ const mojo::ui::ViewProvider::CreateViewCallback& callback) = 0;
+
+ private:
+ class DelegatingViewProvider;
+
+ // |InterfaceFactory<mojo::ui::ViewProvider>|:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::ui::ViewProvider> request) override;
+
+ void CreateView(DelegatingViewProvider* provider,
+ const std::string& view_provider_url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services,
+ const mojo::ui::ViewProvider::CreateViewCallback& callback);
+
+ mojo::ApplicationImpl* app_impl_ = nullptr;
+ mojo::StrongBindingSet<mojo::ui::ViewProvider> bindings_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ViewProviderApp);
+};
+
+} // namespace ui
+} // namespace mojo
+
+#endif // MOJO_UI_VIEW_PROVIDER_APP_H_