| 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..8f6990826187d562a0d647e7a76255a37dfe6ba7
|
| --- /dev/null
|
| +++ b/services/gfx/compositor/compositor_engine.cc
|
| @@ -0,0 +1,459 @@
|
| +// 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/formatting.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 {
|
| +namespace {
|
| +// TODO(jeffbrown): Determine and document a more appropriate size limit
|
| +// for viewports somewhere. May be limited by the renderer output.
|
| +const int32_t kMaxViewportWidth = 65536;
|
| +const int32_t kMaxViewportHeight = 65536;
|
| +
|
| +std::string SanitizeLabel(const mojo::String& label) {
|
| + return label.get().substr(0, mojo::gfx::composition::kLabelMaxLength);
|
| +}
|
| +} // namespace
|
| +
|
| +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.
|
| + SceneState* scene_state =
|
| + new SceneState(scene_token.Pass(), SanitizeLabel(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.
|
| + RendererState* renderer_state =
|
| + new RendererState(renderer_id, SanitizeLabel(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. Note that scenes which are actually visible will be
|
| + // snapshotted by the renderer when it comes time to draw the next frame,
|
| + // so this special case is only designed to help with scenes that are
|
| + // not visible to ensure that we will still apply pending updates which
|
| + // might have side-effects on the client's state (such as closing the
|
| + // connection due to an error or releasing resources).
|
| + 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));
|
| + }
|
| +}
|
| +
|
| +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,
|
| + Scheduler::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 <= 0 || viewport->width > kMaxViewportWidth ||
|
| + viewport->height <= 0 || viewport->height > kMaxViewportHeight) {
|
| + 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,
|
| + Scheduler::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;
|
| +
|
| + mojo::gfx::composition::HitTestResultPtr result;
|
| + if (renderer_state->frame()) {
|
| + result =
|
| + renderer_state->frame()->HitTest(SkPoint::Make(point->x, point->y));
|
| + }
|
| + if (!result) {
|
| + result = mojo::gfx::composition::HitTestResult::New();
|
| + result->hits.resize(0u);
|
| + }
|
| +
|
| + callback.Run(result.Pass());
|
| +}
|
| +
|
| +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
|
| + if (scene_state->scene_listener()) {
|
| + 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, Scheduler::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);
|
| +}
|
| +
|
| +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,
|
| + Scheduler::SchedulingMode scheduling_mode) {
|
| + DCHECK(IsRendererStateRegisteredDebug(renderer_state));
|
| + renderer_state->output()->GetScheduler()->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
|
|
|