blob: 1e0f11865ba689298ba567b64d20a248780e1c47 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "garnet/lib/ui/scenic/engine/engine.h"
#include <set>
#include <trace/event.h>
#include "garnet/lib/ui/mozart/session.h"
#include "garnet/lib/ui/scenic/engine/frame_scheduler.h"
#include "garnet/lib/ui/scenic/engine/frame_timings.h"
#include "garnet/lib/ui/scenic/engine/session.h"
#include "garnet/lib/ui/scenic/engine/session_handler.h"
#include "garnet/lib/ui/scenic/resources/compositor/compositor.h"
#include "garnet/lib/ui/scenic/resources/dump_visitor.h"
#include "garnet/lib/ui/scenic/resources/nodes/traversal.h"
#include "garnet/lib/ui/scenic/swapchain/display_swapchain.h"
#include "garnet/lib/ui/scenic/swapchain/vulkan_display_swapchain.h"
#include "lib/escher/renderer/paper_renderer.h"
#include "lib/escher/renderer/shadow_map_renderer.h"
#include "lib/fxl/functional/make_copyable.h"
namespace scene_manager {
Engine::Engine(DisplayManager* display_manager, escher::Escher* escher)
: display_manager_(display_manager),
escher_(escher),
paper_renderer_(escher::PaperRenderer::New(escher)),
shadow_renderer_(
escher::ShadowMapRenderer::New(escher,
paper_renderer_->model_data(),
paper_renderer_->model_renderer())),
image_factory_(std::make_unique<escher::SimpleImageFactory>(
escher->resource_recycler(),
escher->gpu_allocator())),
rounded_rect_factory_(
std::make_unique<escher::RoundedRectFactory>(escher)),
release_fence_signaller_(std::make_unique<escher::ReleaseFenceSignaller>(
escher->command_buffer_sequencer())),
session_count_(0),
weak_factory_(this) {
FXL_DCHECK(display_manager_);
FXL_DCHECK(escher_);
InitializeFrameScheduler();
paper_renderer_->set_sort_by_pipeline(false);
}
Engine::Engine(
DisplayManager* display_manager,
std::unique_ptr<escher::ReleaseFenceSignaller> release_fence_signaller,
escher::Escher* escher = nullptr)
: display_manager_(display_manager),
escher_(escher),
release_fence_signaller_(std::move(release_fence_signaller)),
weak_factory_(this) {
FXL_DCHECK(display_manager_);
InitializeFrameScheduler();
}
Engine::~Engine() = default;
void Engine::InitializeFrameScheduler() {
if (display_manager_->default_display()) {
frame_scheduler_ =
std::make_unique<FrameScheduler>(display_manager_->default_display());
frame_scheduler_->set_delegate(this);
}
}
void Engine::ScheduleSessionUpdate(uint64_t presentation_time,
fxl::RefPtr<Session> session) {
if (session->is_valid()) {
updatable_sessions_.insert({presentation_time, std::move(session)});
ScheduleUpdate(presentation_time);
}
}
void Engine::ScheduleUpdate(uint64_t presentation_time) {
if (frame_scheduler_) {
frame_scheduler_->RequestFrame(presentation_time);
} else {
// Apply update immediately. This is done for tests.
FXL_LOG(WARNING)
<< "No FrameScheduler available; applying update immediately";
RenderFrame(FrameTimingsPtr(), presentation_time, 0);
}
}
std::unique_ptr<mz::CommandDispatcher> Engine::CreateCommandDispatcher(
mz::CommandDispatcherContext context) {
SessionId session_id = next_session_id_++;
mz::Session* session = context.session();
auto handler =
CreateSessionHandler(std::move(context), session_id, session, session);
sessions_.insert({session_id, handler.get()});
++session_count_;
return handler;
}
std::unique_ptr<Swapchain> Engine::CreateDisplaySwapchain(Display* display) {
FXL_DCHECK(!display->is_claimed());
#if defined(SCENE_MANAGER_VULKAN_SWAPCHAIN)
return std::make_unique<VulkanDisplaySwapchain>(display, event_timestamper(),
escher());
#else
return std::make_unique<DisplaySwapchain>(display, event_timestamper(),
escher());
#endif
}
std::unique_ptr<SessionHandler> Engine::CreateSessionHandler(
mz::CommandDispatcherContext context,
SessionId session_id,
mz::EventReporter* event_reporter,
mz::ErrorReporter* error_reporter) {
return std::make_unique<SessionHandler>(std::move(context), this, session_id,
event_reporter, error_reporter);
}
SessionHandler* Engine::FindSession(SessionId id) {
auto it = sessions_.find(id);
if (it != sessions_.end()) {
return it->second;
}
return nullptr;
}
void Engine::TearDownSession(SessionId id) {
auto it = sessions_.find(id);
FXL_DCHECK(it != sessions_.end());
if (it != sessions_.end()) {
SessionHandler* handler = std::move(it->second);
sessions_.erase(it);
FXL_DCHECK(session_count_ > 0);
--session_count_;
// Don't destroy handler immediately, since it may be the one calling
// TearDownSession().
fsl::MessageLoop::GetCurrent()->task_runner()->PostTask(
[handler] { handler->TearDown(); });
}
}
bool Engine::RenderFrame(const FrameTimingsPtr& timings,
uint64_t presentation_time,
uint64_t presentation_interval) {
TRACE_DURATION("gfx", "RenderFrame", "frame_number", timings->frame_number(),
"time", presentation_time, "interval", presentation_interval);
if (!ApplyScheduledSessionUpdates(presentation_time, presentation_interval))
return false;
UpdateAndDeliverMetrics(presentation_time);
bool frame_drawn = false;
for (auto& compositor : compositors_) {
frame_drawn |= compositor->DrawFrame(timings, paper_renderer_.get(),
shadow_renderer_.get());
}
// Technically, we should be able to do this only when frame_drawn == true.
// But the cost is negligible, so do it always.
CleanupEscher();
return frame_drawn;
}
bool Engine::ApplyScheduledSessionUpdates(uint64_t presentation_time,
uint64_t presentation_interval) {
TRACE_DURATION("gfx", "ApplyScheduledSessionUpdates", "time",
presentation_time, "interval", presentation_interval);
bool needs_render = false;
while (!updatable_sessions_.empty()) {
auto top = updatable_sessions_.begin();
if (top->first > presentation_time)
break;
auto session = std::move(top->second);
updatable_sessions_.erase(top);
if (session) {
needs_render |= session->ApplyScheduledUpdates(presentation_time,
presentation_interval);
} else {
// Corresponds to a call to ScheduleUpdate(), which always triggers a
// render.
needs_render = true;
}
}
return needs_render;
}
void Engine::AddCompositor(Compositor* compositor) {
FXL_DCHECK(compositor);
FXL_DCHECK(compositor->session()->engine() == this);
bool success = compositors_.insert(compositor).second;
FXL_DCHECK(success);
}
void Engine::RemoveCompositor(Compositor* compositor) {
FXL_DCHECK(compositor);
FXL_DCHECK(compositor->session()->engine() == this);
size_t count = compositors_.erase(compositor);
FXL_DCHECK(count == 1);
}
Compositor* Engine::GetFirstCompositor() const {
FXL_DCHECK(!compositors_.empty());
return compositors_.empty() ? nullptr : *compositors_.begin();
}
void Engine::UpdateAndDeliverMetrics(uint64_t presentation_time) {
TRACE_DURATION("gfx", "UpdateAndDeliverMetrics", "time", presentation_time);
// Gather all of the scene which might need to be updated.
std::set<Scene*> scenes;
for (auto compositor : compositors_) {
compositor->CollectScenes(&scenes);
}
if (scenes.empty())
return;
// TODO(MZ-216): Traversing the whole graph just to compute this is pretty
// inefficient. We should optimize this.
scenic::Metrics metrics;
metrics.scale_x = 1.f;
metrics.scale_y = 1.f;
metrics.scale_z = 1.f;
std::vector<Node*> updated_nodes;
for (auto scene : scenes) {
UpdateMetrics(scene, metrics, &updated_nodes);
}
// TODO(MZ-216): Deliver events to sessions in batches.
// We probably want delivery to happen somewhere else which can also
// handle delivery of other kinds of events. We should probably also
// have some kind of backpointer from a session to its handler.
for (auto node : updated_nodes) {
if (node->session()) {
auto event = scenic::Event::New();
event->set_metrics(scenic::MetricsEvent::New());
event->get_metrics()->node_id = node->id();
event->get_metrics()->metrics = node->reported_metrics().Clone();
node->session()->EnqueueEvent(std::move(event));
}
}
}
void Engine::UpdateMetrics(Node* node,
const scenic::Metrics& parent_metrics,
std::vector<Node*>* updated_nodes) {
scenic::Metrics local_metrics;
local_metrics.scale_x = parent_metrics.scale_x * node->scale().x;
local_metrics.scale_y = parent_metrics.scale_y * node->scale().y;
local_metrics.scale_z = parent_metrics.scale_z * node->scale().z;
if ((node->event_mask() & scenic::kMetricsEventMask) &&
!node->reported_metrics().Equals(local_metrics)) {
node->set_reported_metrics(local_metrics);
updated_nodes->push_back(node);
}
ForEachDirectDescendantFrontToBack(
*node, [this, &local_metrics, updated_nodes](Node* node) {
UpdateMetrics(node, local_metrics, updated_nodes);
});
}
void Engine::CleanupEscher() {
// Either there is already a cleanup scheduled (meaning that this was already
// called recently), or there is no Escher because we're running tests.
if (!escher_ || escher_cleanup_scheduled_) {
return;
}
// Only trace when there is the possibility of doing work.
TRACE_DURATION("gfx", "Engine::CleanupEscher");
if (!escher_->Cleanup()) {
// Wait long enough to give GPU work a chance to finish.
const fxl::TimeDelta kCleanupDelay = fxl::TimeDelta::FromMilliseconds(1);
escher_cleanup_scheduled_ = true;
fsl::MessageLoop::GetCurrent()->task_runner()->PostDelayedTask(
[weak = weak_factory_.GetWeakPtr()] {
if (weak) {
// Recursively reschedule if cleanup is incomplete.
weak->escher_cleanup_scheduled_ = false;
weak->CleanupEscher();
}
},
kCleanupDelay);
}
}
std::string Engine::DumpScenes() const {
std::ostringstream output;
DumpVisitor visitor(output);
bool first = true;
for (auto compositor : compositors_) {
if (first)
first = false;
else
output << std::endl << "===" << std::endl << std::endl;
compositor->Accept(&visitor);
}
return output.str();
}
} // namespace scene_manager