blob: eade11f7aaee30bdaf721687acbd11b03adfa7e2 [file] [log] [blame]
// 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 "base/trace_event/trace_event.h"
#include "mojo/services/gfx/composition/cpp/formatting.h"
#include "mojo/skia/type_converters.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_.emplace(scene_state->scene_token().value, scene_state);
universe_.AddScene(scene_state->scene_def()->label());
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;
// Notify other scenes which may depend on this one.
for (auto& pair : scenes_by_token_) {
SceneState* other_scene_state = pair.second;
other_scene_state->scene_def()->NotifySceneUnavailable(
scene_state->scene_token(),
base::Bind(&CompositorEngine::SendResourceUnavailable,
base::Unretained(this),
base::Unretained(other_scene_state)));
}
// Destroy any renderers using this scene.
std::vector<RendererState*> renderers_to_destroy;
for (auto& renderer : renderers_) {
if (renderer->root_scene() == scene_state) {
renderers_to_destroy.emplace_back(renderer);
}
}
for (auto& renderer : renderers_to_destroy) {
LOG(ERROR) << "Destroying renderer whose root scene has become "
"unavailable: renderer="
<< renderer;
DestroyRenderer(renderer);
}
// Consider all dependent rendering to be invalidated.
universe_.RemoveScene(scene_state->scene_token());
InvalidateScene(scene_state);
// Remove from registry.
scenes_by_token_.erase(scene_state->scene_token().value);
delete scene_state;
}
void CompositorEngine::CreateRenderer(
mojo::InterfaceHandle<mojo::ContextProvider> context_provider,
mojo::InterfaceRequest<mojo::gfx::composition::Renderer> renderer_request,
const mojo::String& label) {
DCHECK(context_provider);
uint32_t renderer_id = next_renderer_id_++;
CHECK(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 FrameCallback& callback) {
DCHECK(IsSceneStateRegisteredDebug(scene_state));
DVLOG(1) << "ScheduleFrame: scene=" << scene_state;
if (!scene_state->frame_dispatcher().AddCallback(callback))
return;
// 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::ClearRootScene(RendererState* renderer_state) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(1) << "ClearRootScene: renderer=" << renderer_state;
// Update the root.
if (renderer_state->ClearRootScene()) {
ScheduleFrameForRenderer(renderer_state,
Scheduler::SchedulingMode::kSnapshot);
}
}
void CompositorEngine::ScheduleFrame(RendererState* renderer_state,
const FrameCallback& callback) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(1) << "ScheduleFrame: renderer=" << renderer_state;
if (!renderer_state->frame_dispatcher().AddCallback(callback))
return;
ScheduleFrameForRenderer(renderer_state,
Scheduler::SchedulingMode::kUpdateAndSnapshot);
}
void CompositorEngine::HitTest(
RendererState* renderer_state,
mojo::PointFPtr point,
const mojo::gfx::composition::HitTester::HitTestCallback& callback) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DCHECK(point);
DVLOG(1) << "HitTest: renderer=" << renderer_state << ", point=" << point;
auto result = mojo::gfx::composition::HitTestResult::New();
if (renderer_state->visible_snapshot()) {
DCHECK(!renderer_state->visible_snapshot()->is_blocked());
renderer_state->visible_snapshot()->HitTest(*point, result.get());
}
callback.Run(result.Pass());
}
bool CompositorEngine::ResolveSceneReference(
const mojo::gfx::composition::SceneToken& scene_token) {
return FindScene(scene_token.value) != 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->current_snapshot() &&
renderer->current_snapshot()->HasDependency(
scene_state->scene_token())) {
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, &universe_,
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::ComposeRenderer(
RendererState* renderer_state,
const mojo::gfx::composition::FrameInfo& frame_info) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(2) << "ComposeRenderer: renderer_state=" << renderer_state;
TRACE_EVENT1("gfx", "CompositorEngine::ComposeRenderer", "renderer",
renderer_state->FormattedLabel());
int64_t composition_time = MojoGetTimeTicksNow();
PresentRenderer(renderer_state, frame_info.presentation_time);
SnapshotRenderer(renderer_state);
PaintRenderer(renderer_state, frame_info, composition_time);
}
void CompositorEngine::PresentRenderer(RendererState* renderer_state,
int64_t presentation_time) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(2) << "PresentRenderer: renderer_state=" << renderer_state;
TRACE_EVENT1("gfx", "CompositorEngine::PresentRenderer", "renderer",
renderer_state->FormattedLabel());
// 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) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(2) << "SnapshotRenderer: renderer_state=" << renderer_state;
TRACE_EVENT1("gfx", "CompositorEngine::SnapshotRenderer", "renderer",
renderer_state->FormattedLabel());
if (VLOG_IS_ON(2)) {
std::ostringstream block_log;
SnapshotRendererInner(renderer_state, &block_log);
if (!renderer_state->current_snapshot() ||
renderer_state->current_snapshot()->is_blocked()) {
DVLOG(2) << "Rendering completely blocked:" << std::endl
<< block_log.str();
} else if (!block_log.str().empty()) {
DVLOG(2) << "Rendering partially blocked:" << std::endl
<< block_log.str();
} else {
DVLOG(2) << "Rendering unblocked";
}
} else {
SnapshotRendererInner(renderer_state, nullptr);
}
}
void CompositorEngine::SnapshotRendererInner(RendererState* renderer_state,
std::ostream* block_log) {
if (!renderer_state->root_scene()) {
if (block_log)
*block_log << "No root scene" << std::endl;
renderer_state->SetSnapshot(nullptr);
return;
}
renderer_state->SetSnapshot(
universe_.SnapshotScene(renderer_state->root_scene()->scene_token(),
renderer_state->root_scene_version(), block_log));
}
void CompositorEngine::PaintRenderer(
RendererState* renderer_state,
const mojo::gfx::composition::FrameInfo& frame_info,
int64_t composition_time) {
DCHECK(IsRendererStateRegisteredDebug(renderer_state));
DVLOG(2) << "PaintRenderer: renderer_state=" << renderer_state;
TRACE_EVENT1("gfx", "CompositorEngine::PaintRenderer", "renderer",
renderer_state->FormattedLabel());
RenderFrame::Metadata frame_metadata(frame_info, composition_time);
if (renderer_state->visible_snapshot()) {
// The renderer has snapshotted content; paint and submit it.
DCHECK(!renderer_state->visible_snapshot()->is_blocked());
renderer_state->output()->SubmitFrame(
renderer_state->visible_snapshot()->Paint(
frame_metadata, renderer_state->root_scene_viewport()));
} else {
// The renderer does not have any content; submit an empty (black) frame.
SkIRect viewport = renderer_state->root_scene_viewport().To<SkIRect>();
if (!viewport.isEmpty()) {
renderer_state->output()->SubmitFrame(
new RenderFrame(frame_metadata, viewport));
}
}
}
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));
renderer_state->frame_dispatcher().DispatchCallbacks(frame_info);
// TODO(jeffbrown): Be more selective and do this work only for scenes
// associated with the renderer.
for (auto& pair : scenes_by_token_) {
pair.second->frame_dispatcher().DispatchCallbacks(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));
ComposeRenderer(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