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_