Chromium Code Reviews| 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 |