blob: 97dfa726dd6268e76e8d47a0fc52d3506a67c03a [file] [log] [blame]
// Copyright 2018 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 "src/ui/scenic/lib/scenic/session.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/async/time.h>
#include <lib/trace/event.h>
namespace scenic_impl {
Session::Session(SessionId id, fidl::InterfaceRequest<fuchsia::ui::scenic::Session> session_request,
fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> listener,
std::function<void()> destroy_session_function)
: id_(id),
listener_(listener.Bind()),
reporter_(std::make_shared<EventAndErrorReporter>(this)),
binding_(this, std::move(session_request)),
destroy_session_func_(std::move(destroy_session_function)),
weak_factory_(this) {
FX_DCHECK(!binding_.channel() || binding_.is_bound());
static_assert(!std::is_move_constructible<Session>::value);
}
Session::~Session() { reporter_->Reset(); }
void Session::set_binding_error_handler(fit::function<void(zx_status_t)> error_handler) {
binding_.set_error_handler(std::move(error_handler));
}
void Session::SetFrameScheduler(
const std::shared_ptr<scheduling::FrameScheduler>& frame_scheduler) {
FX_DCHECK(frame_scheduler_.expired()) << "Error: FrameScheduler already set";
frame_scheduler_ = frame_scheduler;
}
void Session::Enqueue(std::vector<fuchsia::ui::scenic::Command> cmds) {
TRACE_DURATION("gfx", "scenic_impl::Session::Enqueue", "session_id", id(), "num_commands",
cmds.size());
for (auto& cmd : cmds) {
// TODO(fxbug.dev/23932): This dispatch is far from optimal in terms of performance.
// We need to benchmark it to figure out whether it matters.
const System::TypeId type_id = SystemTypeForCmd(cmd);
const auto dispatcher_it = dispatchers_.find(type_id);
if (dispatcher_it == dispatchers_.end()) {
reporter_->EnqueueEvent(std::move(cmd));
} else if (type_id == System::TypeId::kInput) {
// Input handles commands immediately and doesn't care about present calls.
dispatcher_it->second->DispatchCommand(std::move(cmd), /*present_id=*/0);
} else {
commands_pending_present_.emplace_back(std::move(cmd));
}
}
}
void Session::Present(uint64_t presentation_time, std::vector<zx::event> acquire_fences,
std::vector<zx::event> release_fences, PresentCallback callback) {
TRACE_DURATION("gfx", "scenic_impl::Session::Present");
if (std::holds_alternative<std::monostate>(present_helper_)) {
present_helper_.emplace<scheduling::Present1Helper>();
} else if (!std::holds_alternative<scheduling::Present1Helper>(present_helper_)) {
reporter_->ERROR() << "Client cannot use Present() and Present2() in the same Session";
destroy_session_func_();
return;
}
// Logic verifying client requests presents in-order.
const zx::time requested_presentation_time = zx::time(presentation_time);
if (requested_presentation_time < last_scheduled_presentation_time_) {
reporter_->ERROR() << "scenic_impl::Session: Present called with out-of-order "
"presentation time. "
<< "requested presentation time=" << requested_presentation_time
<< ", last scheduled presentation time=" << last_scheduled_presentation_time_
<< ".";
destroy_session_func_();
return;
}
last_scheduled_presentation_time_ = requested_presentation_time;
if (--num_presents_allowed_ < 0) {
reporter_->ERROR() << "Present() called with no more present calls allowed.";
}
TRACE_FLOW_END("gfx", "Session::Present", next_present_trace_id_);
next_present_trace_id_++;
// TODO(fxbug.dev/56290): Handle the missing frame scheduler case.
if (auto scheduler = frame_scheduler_.lock()) {
const scheduling::PresentId present_id =
scheduler->RegisterPresent(id_, std::move(release_fences));
std::get<scheduling::Present1Helper>(present_helper_)
.RegisterPresent(present_id, std::move(callback));
SchedulePresentRequest(present_id, requested_presentation_time, std::move(acquire_fences));
}
}
void Session::Present2(fuchsia::ui::scenic::Present2Args args, Present2Callback callback) {
if (std::holds_alternative<std::monostate>(present_helper_)) {
present_helper_.emplace<scheduling::Present2Helper>(
/*on_frame_presented_event*/
// Safe to capture |this| because the Session is guaranteed to outlive |present_helper_|,
// Session is non-movable and Present2Helper does not fire closures after destruction.
[this](fuchsia::scenic::scheduling::FramePresentedInfo info) {
binding_.events().OnFramePresented(std::move(info));
});
} else if (!std::holds_alternative<scheduling::Present2Helper>(present_helper_)) {
reporter_->ERROR() << "Client cannot use Present() and Present2() in the same Session";
destroy_session_func_();
return;
}
// Kill the Session if they have not set any of the Present2Args fields.
if (!args.has_requested_presentation_time() || !args.has_release_fences() ||
!args.has_acquire_fences() || !args.has_requested_prediction_span()) {
reporter_->ERROR() << "One or more fields not set in Present2Args table";
destroy_session_func_();
return;
}
// Kill the Session if they have no more presents left.
if (--num_presents_allowed_ < 0) {
reporter_->ERROR()
<< "Present2() called with no more present calls allowed. Terminating session.";
destroy_session_func_();
return;
}
// Logic verifying client requests presents in-order.
const zx::time requested_presentation_time = zx::time(args.requested_presentation_time());
if (requested_presentation_time < last_scheduled_presentation_time_) {
reporter_->ERROR() << "scenic_impl::Session: Present called with out-of-order "
"presentation time. "
<< "requested presentation time=" << requested_presentation_time
<< ", last scheduled presentation time=" << last_scheduled_presentation_time_
<< ".";
destroy_session_func_();
return;
}
last_scheduled_presentation_time_ = requested_presentation_time;
// Output requested presentation time in milliseconds.
TRACE_DURATION("gfx", "scenic_impl::Session::Present2", "requested_presentation_time",
requested_presentation_time.get() / 1'000'000);
TRACE_FLOW_END("gfx", "Session::Present", next_present_trace_id_);
next_present_trace_id_++;
// TODO(fxbug.dev/56290): Handle the missing frame scheduler case.
if (auto scheduler = frame_scheduler_.lock()) {
const scheduling::PresentId present_id =
scheduler->RegisterPresent(id_, std::move(*args.mutable_release_fences()));
std::get<scheduling::Present2Helper>(present_helper_)
.RegisterPresent(present_id, /*present_received_time*/ zx::time(
async_now(async_get_default_dispatcher())));
InvokeFuturePresentationTimesCallback(args.requested_prediction_span(), std::move(callback));
SchedulePresentRequest(present_id, requested_presentation_time,
std::move(*args.mutable_acquire_fences()));
}
}
void Session::SchedulePresentRequest(scheduling::PresentId present_id,
zx::time requested_presentation_time,
std::vector<zx::event> acquire_fences) {
TRACE_DURATION("gfx", "fidl::WireRequest<scenic_impl::Sesssion::SchedulePresent>");
TRACE_FLOW_BEGIN("gfx", "wait_for_fences", SESSION_TRACE_ID(id_, present_id));
// Safe to capture |this| because the Session is guaranteed to outlive |fence_queue_|,
// Session is non-movable and FenceQueue does not fire closures after destruction.
fence_queue_->QueueTask(
[this, present_id, requested_presentation_time,
commands = std::move(commands_pending_present_)]() mutable {
if (auto scheduler = frame_scheduler_.lock()) {
TRACE_DURATION("gfx", "scenic_impl::Session::ScheduleNextPresent", "session_id", id_,
"requested_presentation_time",
requested_presentation_time.get() / 1'000'000);
TRACE_FLOW_END("gfx", "wait_for_fences", SESSION_TRACE_ID(id_, present_id));
for (auto& cmd : commands) {
dispatchers_.at(SystemTypeForCmd(cmd))->DispatchCommand(std::move(cmd), present_id);
}
scheduler->ScheduleUpdateForSession(requested_presentation_time, {id_, present_id},
/*squashable=*/true);
} else {
// TODO(fxbug.dev/56290): Handle the missing frame scheduler case.
FX_LOGS(WARNING) << "FrameScheduler is missing.";
}
},
std::move(acquire_fences));
commands_pending_present_.clear();
}
void Session::RequestPresentationTimes(zx_duration_t requested_prediction_span,
RequestPresentationTimesCallback callback) {
TRACE_DURATION("gfx", "scenic_impl::Session::RequestPresentationTimes");
InvokeFuturePresentationTimesCallback(requested_prediction_span, std::move(callback));
}
void Session::InvokeFuturePresentationTimesCallback(zx_duration_t requested_prediction_span,
RequestPresentationTimesCallback callback) {
if (!callback)
return;
// TODO(fxbug.dev/56290): Handle the missing frame scheduler case.
if (auto locked_frame_scheduler = frame_scheduler_.lock()) {
locked_frame_scheduler->GetFuturePresentationInfos(
zx::duration(requested_prediction_span),
[weak = weak_factory_.GetWeakPtr(), callback = std::move(callback)](
std::vector<scheduling::FuturePresentationInfo> presentation_infos) {
if (weak) {
std::vector<fuchsia::scenic::scheduling::PresentationInfo> infos;
for (auto& presentation_info : presentation_infos) {
auto& info = infos.emplace_back();
info.set_latch_point(presentation_info.latch_point.get());
info.set_presentation_time(presentation_info.presentation_time.get());
}
callback({std::move(infos), weak->num_presents_allowed_});
}
});
}
}
void Session::OnPresented(const std::map<scheduling::PresentId, zx::time>& latched_times,
scheduling::PresentTimestamps present_times) {
FX_DCHECK(!latched_times.empty());
num_presents_allowed_ += latched_times.size();
FX_DCHECK(num_presents_allowed_ <= scheduling::FrameScheduler::kMaxPresentsInFlight);
if (auto* present2_helper = std::get_if<scheduling::Present2Helper>(&present_helper_)) {
present2_helper->OnPresented(latched_times, present_times, num_presents_allowed_);
} else if (auto* present1_helper = std::get_if<scheduling::Present1Helper>(&present_helper_)) {
present1_helper->OnPresented(latched_times, present_times);
} else {
// Should never be reached.
FX_CHECK(false);
}
}
void Session::SetCommandDispatchers(
std::unordered_map<System::TypeId, CommandDispatcherUniquePtr> dispatchers) {
FX_DCHECK(dispatchers_.empty()) << "dispatchers should only be set once.";
dispatchers_ = std::move(dispatchers);
}
void Session::SetDebugName(std::string debug_name) {
TRACE_DURATION("gfx", "scenic_impl::Session::SetDebugName", "debug name", debug_name);
for (auto& [type_id, dispatcher] : dispatchers_) {
dispatcher->SetDebugName(debug_name);
}
}
void Session::RegisterBufferCollection(
uint32_t buffer_collection_id,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) {
auto dispatcher = dispatchers_[System::TypeId::kGfx].get();
FX_DCHECK(dispatcher);
auto gfx_session = static_cast<gfx::Session*>(dispatcher);
gfx_session->RegisterBufferCollection(buffer_collection_id, std::move(token));
}
void Session::DeregisterBufferCollection(uint32_t buffer_collection_id) {
auto dispatcher = dispatchers_[System::TypeId::kGfx].get();
FX_DCHECK(dispatcher);
auto gfx_session = static_cast<gfx::Session*>(dispatcher);
gfx_session->DeregisterBufferCollection(buffer_collection_id);
}
Session::EventAndErrorReporter::EventAndErrorReporter(Session* session)
: session_(session), weak_factory_(this) {
FX_DCHECK(session_);
}
void Session::EventAndErrorReporter::Reset() { session_ = nullptr; }
void Session::EventAndErrorReporter::PostFlushTask() {
FX_DCHECK(session_);
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::PostFlushTask");
// If this is the first EnqueueEvent() since the last FlushEvent(), post a
// task to ensure that FlushEvents() is called.
if (buffered_events_.empty()) {
async::PostTask(async_get_default_dispatcher(), [weak = weak_factory_.GetWeakPtr()] {
if (!weak)
return;
weak->FilterRedundantGfxEvents();
weak->FlushEvents();
});
}
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::gfx::Event event) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type",
"gfx::Event");
PostFlushTask();
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_gfx(std::move(event));
buffered_events_.push_back(std::move(scenic_event));
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::scenic::Command unhandled_command) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type",
"UnhandledCommand");
PostFlushTask();
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_unhandled(std::move(unhandled_command));
buffered_events_.push_back(std::move(scenic_event));
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::input::InputEvent event) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type",
"input::InputEvent");
// Send input event immediately.
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_input(std::move(event));
FilterRedundantGfxEvents();
buffered_events_.push_back(std::move(scenic_event));
FlushEvents();
}
void Session::EventAndErrorReporter::FilterRedundantGfxEvents() {
if (buffered_events_.empty())
return;
struct EventCounts {
uint32_t view_attached_to_scene = 0;
uint32_t view_detached_from_scene = 0;
};
std::map</*view_id=*/uint32_t, EventCounts> event_counts;
for (const auto& event : buffered_events_) {
if (event.is_gfx()) {
switch (event.gfx().Which()) {
case fuchsia::ui::gfx::Event::kViewAttachedToScene:
event_counts[event.gfx().view_attached_to_scene().view_id].view_attached_to_scene++;
break;
case fuchsia::ui::gfx::Event::kViewDetachedFromScene:
event_counts[event.gfx().view_detached_from_scene().view_id].view_detached_from_scene++;
break;
default:
break;
}
}
}
if (event_counts.empty())
return;
auto is_view_event = [](uint32_t view_id, const fuchsia::ui::scenic::Event& event) {
return event.is_gfx() && ((event.gfx().is_view_detached_from_scene() &&
(view_id == event.gfx().view_detached_from_scene().view_id)) ||
(event.gfx().is_view_attached_to_scene() &&
(view_id == event.gfx().view_attached_to_scene().view_id)));
};
for (auto [view_id, event_count] : event_counts) {
auto matching_view_event = std::bind(is_view_event, view_id, std::placeholders::_1);
// We expect that multiple attach or detach events aren't fired in a row. Then, remove all
// attach/detach events if we have balanced counts. Otherwise, remove all except last.
if (event_count.view_attached_to_scene == event_count.view_detached_from_scene) {
buffered_events_.erase(
std::remove_if(buffered_events_.begin(), buffered_events_.end(), matching_view_event),
buffered_events_.end());
} else if (event_count.view_attached_to_scene && event_count.view_detached_from_scene) {
auto last_event =
std::find_if(buffered_events_.rbegin(), buffered_events_.rend(), matching_view_event);
buffered_events_.erase(
buffered_events_.rend().base(),
std::remove_if(std::next(last_event), buffered_events_.rend(), matching_view_event)
.base());
}
}
}
void Session::EventAndErrorReporter::FlushEvents() {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::FlushEvents");
if (!buffered_events_.empty()) {
if (session_->listener_) {
session_->listener_->OnScenicEvent(std::move(buffered_events_));
} else if (event_callback_) {
// Only use the callback if there is no listener. It is difficult to do
// better because we std::move the argument into OnScenicEvent().
for (auto& evt : buffered_events_) {
event_callback_(std::move(evt));
}
}
buffered_events_.clear();
}
}
void Session::EventAndErrorReporter::ReportError(syslog::LogSeverity severity,
std::string error_string) {
// TODO(fxbug.dev/24465): Come up with a better solution to avoid children
// calling into us during destruction.
if (!session_) {
FX_LOGS(ERROR) << "Reporting Scenic Session error after session destroyed: " << error_string;
return;
}
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::ReportError");
switch (severity) {
case syslog::LOG_INFO:
FX_LOGS(INFO) << error_string;
return;
case syslog::LOG_WARNING:
FX_LOGS(WARNING) << error_string;
return;
case syslog::LOG_ERROR:
FX_LOGS(WARNING) << "Scenic session error (session_id: " << session_->id()
<< "): " << error_string;
if (error_callback_) {
error_callback_(error_string);
}
if (session_->listener_) {
session_->listener_->OnScenicError(std::move(error_string));
}
return;
case syslog::LOG_FATAL:
FX_LOGS(FATAL) << error_string;
return;
default:
// Invalid severity.
FX_DCHECK(false);
}
}
} // namespace scenic_impl