Index: services/gfx/compositor/compositor_engine.cc |
diff --git a/services/gfx/compositor/compositor_engine.cc b/services/gfx/compositor/compositor_engine.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3a9bb102ad2ca0117edd83278ce8a6902ab787bc |
--- /dev/null |
+++ b/services/gfx/compositor/compositor_engine.cc |
@@ -0,0 +1,444 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "services/gfx/compositor/compositor_engine.h" |
+ |
+#include <algorithm> |
+#include <sstream> |
+#include <utility> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/time/time.h" |
+#include "mojo/services/gfx/composition/cpp/logging.h" |
+#include "services/gfx/compositor/backend/gpu_output.h" |
+#include "services/gfx/compositor/graph/snapshot.h" |
+#include "services/gfx/compositor/render/render_frame.h" |
+#include "services/gfx/compositor/renderer_impl.h" |
+#include "services/gfx/compositor/scene_impl.h" |
+ |
+namespace compositor { |
+ |
+// TODO(jeffbrown): Determine and document a more appropriate size limit |
+// for viewports somewhere. May be limited by the renderer output. |
+static const int32_t kMaxViewportWidth = 65536; |
+static const int32_t kMaxViewportHeight = 65536; |
+ |
+CompositorEngine::CompositorEngine() : weak_factory_(this) {} |
+ |
+CompositorEngine::~CompositorEngine() {} |
+ |
+mojo::gfx::composition::SceneTokenPtr CompositorEngine::CreateScene( |
+ mojo::InterfaceRequest<mojo::gfx::composition::Scene> scene_request, |
+ const mojo::String& label) { |
+ auto scene_token = mojo::gfx::composition::SceneToken::New(); |
+ scene_token->value = next_scene_token_value_++; |
+ CHECK(scene_token->value); |
+ CHECK(!FindScene(scene_token->value)); |
+ |
+ // Create the state and bind implementation to it. |
+ std::string sanitized_label = |
+ label.get().substr(0, mojo::gfx::composition::kLabelMaxLength); |
+ SceneState* scene_state = new SceneState(scene_token.Pass(), sanitized_label); |
+ SceneImpl* scene_impl = |
+ new SceneImpl(this, scene_state, scene_request.Pass()); |
+ scene_state->set_scene_impl(scene_impl); |
+ base::Closure error_handler = |
+ base::Bind(&CompositorEngine::OnSceneConnectionError, |
+ base::Unretained(this), scene_state); |
+ scene_impl->set_connection_error_handler(error_handler); |
+ |
+ // Add to registry. |
+ scenes_by_token_.insert({scene_state->scene_token()->value, scene_state}); |
+ DVLOG(1) << "CreateScene: scene=" << scene_state; |
+ return scene_state->scene_token()->Clone(); |
+} |
+ |
+void CompositorEngine::OnSceneConnectionError(SceneState* scene_state) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(1) << "OnSceneConnectionError: scene=" << scene_state; |
+ |
+ DestroyScene(scene_state); |
+} |
+ |
+void CompositorEngine::DestroyScene(SceneState* scene_state) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(1) << "DestroyScene: scene=" << scene_state; |
+ |
+ // Unlink from other scenes. |
+ for (auto& pair : scenes_by_token_) { |
+ SceneState* other_scene_state = pair.second; |
+ other_scene_state->scene_def()->UnlinkReferencedScene( |
+ scene_state->scene_def(), |
+ base::Bind(&CompositorEngine::SendResourceUnavailable, |
+ base::Unretained(this), |
+ base::Unretained(other_scene_state))); |
+ } |
+ |
+ // Destroy any renderers using this scene. |
+ for (auto& renderer : renderers_) { |
+ if (renderer->root_scene() == scene_state) { |
+ LOG(ERROR) << "Destroying renderer whose root scene has become " |
+ "unavailable: renderer=" |
+ << renderer; |
+ DestroyRenderer(renderer); |
+ } |
+ } |
+ |
+ // Destroy. |
+ InvalidateScene(scene_state); |
+ |
+ // Remove from registry. |
+ scenes_by_token_.erase(scene_state->scene_token()->value); |
+ delete scene_state; |
+} |
+ |
+void CompositorEngine::CreateRenderer( |
+ mojo::ContextProviderPtr context_provider, |
+ mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request, |
+ const mojo::String& label) { |
+ DCHECK(context_provider); |
+ uint32_t renderer_id = next_renderer_id_++; |
+ |
+ // Create the state and bind implementation to it. |
+ std::string sanitized_label = |
+ label.get().substr(0, mojo::gfx::composition::kLabelMaxLength); |
abarth
2016/01/10 22:55:35
This pattern recurs a bunch. Maybe factor it out
jeffbrown
2016/01/16 03:28:31
Done.
|
+ RendererState* renderer_state = |
+ new RendererState(renderer_id, sanitized_label); |
+ RendererImpl* renderer_impl = |
+ new RendererImpl(this, renderer_state, renderer_request.Pass()); |
+ renderer_state->set_renderer_impl(renderer_impl); |
+ renderer_impl->set_connection_error_handler( |
+ base::Bind(&CompositorEngine::OnRendererConnectionError, |
+ base::Unretained(this), renderer_state)); |
+ |
+ // Create the renderer. |
+ SchedulerCallbacks scheduler_callbacks( |
+ base::Bind(&CompositorEngine::OnOutputUpdateRequest, |
+ weak_factory_.GetWeakPtr(), renderer_state->GetWeakPtr()), |
+ base::Bind(&CompositorEngine::OnOutputSnapshotRequest, |
+ weak_factory_.GetWeakPtr(), renderer_state->GetWeakPtr())); |
+ std::unique_ptr<Output> output(new GpuOutput( |
+ context_provider.Pass(), scheduler_callbacks, |
+ base::Bind(&CompositorEngine::OnOutputError, weak_factory_.GetWeakPtr(), |
+ renderer_state->GetWeakPtr()))); |
+ renderer_state->set_output(std::move(output)); |
+ |
+ // Add to registry. |
+ renderers_.push_back(renderer_state); |
+ DVLOG(1) << "CreateRenderer: " << renderer_state; |
+} |
+ |
+void CompositorEngine::OnRendererConnectionError( |
+ RendererState* renderer_state) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DVLOG(1) << "OnRendererConnectionError: renderer=" << renderer_state; |
+ |
+ DestroyRenderer(renderer_state); |
+} |
+ |
+void CompositorEngine::DestroyRenderer(RendererState* renderer_state) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DVLOG(1) << "DestroyRenderer: renderer=" << renderer_state; |
+ |
+ // Remove from registry. |
+ renderers_.erase( |
+ std::find(renderers_.begin(), renderers_.end(), renderer_state)); |
+ delete renderer_state; |
+} |
+ |
+void CompositorEngine::SetListener( |
+ SceneState* scene_state, |
+ mojo::gfx::composition::SceneListenerPtr listener) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(1) << "SetSceneListener: scene=" << scene_state; |
+ |
+ scene_state->set_scene_listener(listener.Pass()); |
+} |
+ |
+void CompositorEngine::Update(SceneState* scene_state, |
+ mojo::gfx::composition::SceneUpdatePtr update) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(1) << "Update: scene=" << scene_state << ", update=" << update; |
+ |
+ scene_state->scene_def()->EnqueueUpdate(update.Pass()); |
+} |
+ |
+void CompositorEngine::Publish( |
+ SceneState* scene_state, |
+ mojo::gfx::composition::SceneMetadataPtr metadata) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(1) << "Publish: scene=" << scene_state << ", metadata=" << metadata; |
+ |
+ if (!metadata) |
+ metadata = mojo::gfx::composition::SceneMetadata::New(); |
+ int64_t presentation_time = metadata->presentation_time; |
+ scene_state->scene_def()->EnqueuePublish(metadata.Pass()); |
+ |
+ // Implicitly schedule fresh snapshots. |
+ InvalidateScene(scene_state); |
+ |
+ // Ensure that the scene will be presented eventually, even if it is |
+ // not associated with any renderer. Note that this is only a backstop |
+ // in case the scene does not get presented sooner as part of snapshotting |
+ // a renderer. |
+ MojoTimeTicks now = MojoGetTimeTicksNow(); |
+ DCHECK(now >= 0); |
+ if (presentation_time <= now) { |
+ SceneDef::Disposition disposition = PresentScene(scene_state, now); |
+ if (disposition == SceneDef::Disposition::kFailed) |
+ DestroyScene(scene_state); |
+ } else { |
+ base::MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, base::Bind(&CompositorEngine::OnPresentScene, |
+ weak_factory_.GetWeakPtr(), |
+ scene_state->GetWeakPtr(), presentation_time), |
+ base::TimeDelta::FromMicroseconds(presentation_time - now)); |
abarth
2016/01/10 22:55:35
Do we need to schedule this for earlier so that it
jeffbrown
2016/01/16 03:28:32
It actually doesn't matter here. This only happen
|
+ } |
+} |
+ |
+void CompositorEngine::ScheduleFrame(SceneState* scene_state, |
+ const SceneFrameCallback& callback) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ |
+ scene_state->AddSceneFrameCallback(callback); |
+ |
+ // TODO(jeffbrown): Be more selective and do this work only for scenes |
+ // which are strongly associated with the renderer so it doesn't receive |
+ // conflicting timing signals coming from multiple renderers. |
+ for (auto& renderer : renderers_) { |
+ ScheduleFrameForRenderer(renderer, SchedulingMode::kUpdateAndSnapshot); |
+ } |
+} |
+ |
+void CompositorEngine::SetRootScene( |
+ RendererState* renderer_state, |
+ mojo::gfx::composition::SceneTokenPtr scene_token, |
+ uint32 scene_version, |
+ mojo::RectPtr viewport) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DCHECK(scene_token); |
+ DCHECK(viewport); |
+ DVLOG(1) << "SetRootScene: renderer=" << renderer_state |
+ << ", scene_token=" << scene_token |
+ << ", scene_version=" << scene_version << ", viewport=" << viewport; |
+ |
+ if (viewport->width < 1 || viewport->width > kMaxViewportWidth || |
+ viewport->height < 1 || viewport->height > kMaxViewportHeight) { |
abarth
2016/01/10 22:55:35
nit: I would have tested <= 0
jeffbrown
2016/01/16 03:28:31
Done.
|
+ LOG(ERROR) << "Invalid viewport size: " << viewport; |
+ DestroyRenderer(renderer_state); |
+ return; |
+ } |
+ |
+ // Find the scene. |
+ SceneState* scene_state = FindScene(scene_token->value); |
+ if (!scene_state) { |
+ LOG(ERROR) << "Could not set the renderer's root scene, scene not found: " |
+ "scene_token=" |
+ << scene_token; |
+ DestroyRenderer(renderer_state); |
+ return; |
+ } |
+ |
+ // Update the root. |
+ if (renderer_state->SetRootScene(scene_state, scene_version, *viewport)) { |
+ ScheduleFrameForRenderer(renderer_state, SchedulingMode::kSnapshot); |
+ } |
+} |
+ |
+void CompositorEngine::HitTest( |
+ RendererState* renderer_state, |
+ mojo::PointPtr point, |
+ const mojo::gfx::composition::HitTester::HitTestCallback& callback) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DCHECK(point); |
+ DVLOG(1) << "HitTest: renderer=" << renderer_state << ", point=" << point; |
+ |
+ if (renderer_state->frame()) { |
+ callback.Run( |
+ renderer_state->frame()->HitTest(SkPoint::Make(point->x, point->y))); |
+ } else { |
+ callback.Run(nullptr); |
abarth
2016/01/10 22:55:35
It's odd that there are two "failed" results here:
jeffbrown
2016/01/16 03:28:32
Good point.
|
+ } |
+} |
+ |
+SceneDef* CompositorEngine::ResolveSceneReference( |
+ mojo::gfx::composition::SceneToken* scene_token) { |
+ SceneState* scene_state = FindScene(scene_token->value); |
+ return scene_state ? scene_state->scene_def() : nullptr; |
+} |
+ |
+void CompositorEngine::SendResourceUnavailable(SceneState* scene_state, |
+ uint32_t resource_id) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(2) << "SendResourceUnavailable: resource_id=" << resource_id; |
+ |
+ // TODO: Detect ANRs |
abarth
2016/01/10 22:55:35
ANRs?
jeffbrown
2016/01/16 03:28:31
"Application Not Responding"
|
+ if (scene_state->scene_listener()) |
abarth
2016/01/10 22:55:35
Multiline ifs require { }
jeffbrown
2016/01/16 03:28:32
Done.
|
+ scene_state->scene_listener()->OnResourceUnavailable( |
+ resource_id, base::Bind(&base::DoNothing)); |
+} |
+ |
+SceneState* CompositorEngine::FindScene(uint32_t scene_token) { |
+ auto it = scenes_by_token_.find(scene_token); |
+ return it != scenes_by_token_.end() ? it->second : nullptr; |
+} |
+ |
+void CompositorEngine::InvalidateScene(SceneState* scene_state) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(2) << "InvalidateScene: scene=" << scene_state; |
+ |
+ for (auto& renderer : renderers_) { |
+ if (renderer->snapshot() && |
+ renderer->snapshot()->InvalidateScene(scene_state->scene_def())) { |
+ ScheduleFrameForRenderer(renderer, SchedulingMode::kSnapshot); |
+ } |
+ } |
+} |
+ |
+SceneDef::Disposition CompositorEngine::PresentScene( |
+ SceneState* scene_state, |
+ int64_t presentation_time) { |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ DVLOG(2) << "PresentScene: scene=" << scene_state; |
+ |
+ std::ostringstream errs; |
+ SceneDef::Disposition disposition = scene_state->scene_def()->Present( |
+ presentation_time, base::Bind(&CompositorEngine::ResolveSceneReference, |
+ base::Unretained(this)), |
+ base::Bind(&CompositorEngine::SendResourceUnavailable, |
+ base::Unretained(this), base::Unretained(scene_state)), |
+ errs); |
+ if (disposition == SceneDef::Disposition::kFailed) { |
+ LOG(ERROR) << "Scene published invalid updates: scene=" << scene_state; |
+ LOG(ERROR) << errs.str(); |
+ // Caller is responsible for destroying the scene. |
+ } |
+ return disposition; |
+} |
+ |
+void CompositorEngine::PresentRenderer(RendererState* renderer_state, |
+ int64_t presentation_time) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DVLOG(2) << "PresentRenderer: renderer_state=" << renderer_state; |
+ |
+ // TODO(jeffbrown): Be more selective and do this work only for scenes |
+ // associated with the renderer that actually have pending updates. |
+ std::vector<SceneState*> dead_scenes; |
+ for (auto& pair : scenes_by_token_) { |
+ SceneState* scene_state = pair.second; |
+ SceneDef::Disposition disposition = |
+ PresentScene(scene_state, presentation_time); |
+ if (disposition == SceneDef::Disposition::kFailed) |
+ dead_scenes.push_back(scene_state); |
+ } |
+ for (SceneState* scene_state : dead_scenes) |
+ DestroyScene(scene_state); |
abarth
2016/01/10 22:55:35
Should we DCHECK that scene_state isn't in scenes_
jeffbrown
2016/01/16 03:28:32
You mean after DestroyScene returns? I don't thin
|
+} |
+ |
+void CompositorEngine::SnapshotRenderer( |
+ RendererState* renderer_state, |
+ const mojo::gfx::composition::FrameInfo& frame_info) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ DVLOG(2) << "SnapshotRenderer: renderer_state=" << renderer_state; |
+ |
+ if (VLOG_IS_ON(2)) { |
+ std::ostringstream block_log; |
+ SnapshotRendererInner(renderer_state, frame_info, &block_log); |
+ if (!renderer_state->valid()) { |
+ DVLOG(2) << "Rendering completely blocked: " << block_log.str(); |
+ } else if (!block_log.str().empty()) { |
+ DVLOG(2) << "Rendering partially blocked: " << block_log.str(); |
+ } else { |
+ DVLOG(2) << "Rendering unblocked"; |
+ } |
+ } else { |
+ SnapshotRendererInner(renderer_state, frame_info, nullptr); |
+ } |
+} |
+ |
+void CompositorEngine::SnapshotRendererInner( |
+ RendererState* renderer_state, |
+ const mojo::gfx::composition::FrameInfo& frame_info, |
+ std::ostream* block_log) { |
+ if (!renderer_state->root_scene()) { |
+ if (block_log) |
+ *block_log << "No root scene" << std::endl; |
+ renderer_state->SetSnapshot(nullptr); |
+ return; |
+ } |
+ |
+ SnapshotBuilder builder(block_log); |
+ renderer_state->SetSnapshot( |
+ builder.Build(renderer_state->root_scene()->scene_def(), |
+ renderer_state->root_scene_viewport(), frame_info)); |
+ |
+ if (renderer_state->valid()) |
+ renderer_state->output()->SubmitFrame(renderer_state->frame()); |
+} |
+ |
+void CompositorEngine::ScheduleFrameForRenderer( |
+ RendererState* renderer_state, |
+ SchedulingMode scheduling_mode) { |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ renderer_state->output()->scheduler()->ScheduleFrame(scheduling_mode); |
+} |
+ |
+void CompositorEngine::OnOutputError( |
+ const base::WeakPtr<RendererState>& renderer_state_weak) { |
+ RendererState* renderer_state = renderer_state_weak.get(); |
+ if (!renderer_state) |
+ return; |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ |
+ LOG(ERROR) << "Renderer encountered a fatal error: renderer=" |
+ << renderer_state; |
+ |
+ DestroyRenderer(renderer_state); |
+} |
+ |
+void CompositorEngine::OnOutputUpdateRequest( |
+ const base::WeakPtr<RendererState>& renderer_state_weak, |
+ const mojo::gfx::composition::FrameInfo& frame_info) { |
+ RendererState* renderer_state = renderer_state_weak.get(); |
+ if (!renderer_state) |
+ return; |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ |
+ // TODO(jeffbrown): Be more selective and do this work only for scenes |
+ // associated with the renderer. |
+ for (auto& pair : scenes_by_token_) { |
+ pair.second->DispatchSceneFrameCallbacks(frame_info); |
+ } |
+} |
+ |
+void CompositorEngine::OnOutputSnapshotRequest( |
+ const base::WeakPtr<RendererState>& renderer_state_weak, |
+ const mojo::gfx::composition::FrameInfo& frame_info) { |
+ RendererState* renderer_state = renderer_state_weak.get(); |
+ if (!renderer_state) |
+ return; |
+ DCHECK(IsRendererStateRegisteredDebug(renderer_state)); |
+ |
+ PresentRenderer(renderer_state, frame_info.presentation_time); |
+ SnapshotRenderer(renderer_state, frame_info); |
+} |
+ |
+void CompositorEngine::OnPresentScene( |
+ const base::WeakPtr<SceneState>& scene_state_weak, |
+ int64_t presentation_time) { |
+ SceneState* scene_state = scene_state_weak.get(); |
+ if (!scene_state) |
+ return; |
+ DCHECK(IsSceneStateRegisteredDebug(scene_state)); |
+ |
+ SceneDef::Disposition disposition = |
+ PresentScene(scene_state, presentation_time); |
+ if (disposition == SceneDef::Disposition::kFailed) |
+ DestroyScene(scene_state); |
+ else if (disposition == SceneDef::Disposition::kSucceeded) |
+ InvalidateScene(scene_state); |
+} |
+ |
+} // namespace compositor |