| // 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/gfx/engine/session.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <trace/event.h> |
| |
| #include "garnet/lib/ui/gfx/engine/gfx_command_applier.h" |
| #include "garnet/lib/ui/gfx/engine/hit_tester.h" |
| #include "garnet/lib/ui/gfx/engine/session_handler.h" |
| #include "garnet/lib/ui/gfx/resources/compositor/layer_stack.h" |
| #include "garnet/lib/ui/gfx/resources/image_pipe.h" |
| #include "garnet/lib/ui/gfx/swapchain/swapchain_factory.h" |
| #include "garnet/lib/ui/gfx/util/time.h" |
| #include "garnet/lib/ui/gfx/util/unwrap.h" |
| #include "garnet/lib/ui/gfx/util/wrap.h" |
| #include "garnet/lib/ui/scenic/util/print_command.h" |
| #include "lib/escher/hmd/pose_buffer.h" |
| #include "lib/escher/renderer/batch_gpu_uploader.h" |
| #include "lib/escher/shape/mesh.h" |
| #include "lib/escher/shape/rounded_rect_factory.h" |
| #include "lib/escher/util/type_utils.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| namespace { |
| |
| // Converts the provided vector of Hits into a fidl array of HitPtrs. |
| fidl::VectorPtr<::fuchsia::ui::gfx::Hit> WrapHits( |
| const std::vector<Hit>& hits) { |
| fidl::VectorPtr<::fuchsia::ui::gfx::Hit> wrapped_hits; |
| wrapped_hits.resize(hits.size()); |
| for (size_t i = 0; i < hits.size(); ++i) { |
| const Hit& hit = hits[i]; |
| ::fuchsia::ui::gfx::Hit wrapped_hit; |
| wrapped_hit.tag_value = hit.tag_value; |
| wrapped_hit.ray_origin = Wrap(hit.ray.origin); |
| wrapped_hit.ray_direction = Wrap(hit.ray.direction); |
| wrapped_hit.inverse_transform = Wrap(hit.inverse_transform); |
| wrapped_hit.distance = hit.distance; |
| wrapped_hits->at(i) = std::move(wrapped_hit); |
| } |
| return wrapped_hits; |
| } |
| } // anonymous namespace |
| |
| Session::Session(SessionId id, SessionContext session_context, |
| EventReporter* event_reporter, ErrorReporter* error_reporter) |
| : id_(id), |
| error_reporter_(error_reporter), |
| event_reporter_(event_reporter), |
| session_context_(std::move(session_context)), |
| resource_context_({session_context_.vk_device, |
| session_context_.escher != nullptr |
| ? session_context_.escher->device()->caps() |
| : escher::VulkanDeviceQueues::Caps(), |
| session_context_.imported_memory_type_index, |
| session_context_.escher_resource_recycler, |
| session_context_.escher_image_factory}), |
| resources_(error_reporter), |
| weak_factory_(this) { |
| FXL_DCHECK(error_reporter); |
| } |
| |
| Session::~Session() { |
| resources_.Clear(); |
| scheduled_image_pipe_updates_ = {}; |
| |
| // We assume the channel for the associated gfx::Session is closed by |
| // SessionHandler before this point, since |scheduled_updates_| contains |
| // pending callbacks to gfx::Session::Present(). If the channel was not closed |
| // we would have to invoke those callbacks before destroying them. |
| scheduled_updates_ = {}; |
| fences_to_release_on_next_update_.clear(); |
| |
| if (resource_count_ != 0) { |
| auto exported_count = |
| session_context_.resource_linker->NumExportsForSession(this); |
| FXL_CHECK(resource_count_ == 0) |
| << "Session::~Session(): Not all resources have been collected. " |
| "Exported resources: " |
| << exported_count |
| << ", total outstanding resources: " << resource_count_; |
| } |
| error_reporter_ = nullptr; |
| |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| ErrorReporter* Session::error_reporter() const { |
| return error_reporter_ ? error_reporter_ : ErrorReporter::Default(); |
| } |
| |
| EventReporter* Session::event_reporter() const { return event_reporter_; } |
| |
| bool Session::ScheduleUpdate( |
| uint64_t requested_presentation_time, |
| std::vector<::fuchsia::ui::gfx::Command> commands, |
| ::std::vector<zx::event> acquire_fences, |
| ::std::vector<zx::event> release_events, |
| fuchsia::ui::scenic::Session::PresentCallback callback) { |
| uint64_t last_scheduled_presentation_time = |
| last_applied_update_presentation_time_; |
| if (!scheduled_updates_.empty()) { |
| last_scheduled_presentation_time = |
| std::max(last_scheduled_presentation_time, |
| scheduled_updates_.back().presentation_time); |
| } |
| |
| if (requested_presentation_time < last_scheduled_presentation_time) { |
| error_reporter_->ERROR() |
| << "scenic_impl::gfx::Session: Present called with out-of-order " |
| "presentation time. " |
| << "requested presentation time=" << requested_presentation_time |
| << ", last scheduled presentation time=" |
| << last_scheduled_presentation_time << "."; |
| return false; |
| } |
| |
| // If we're not running headless, warn if the requested presesentation time |
| // is not reasonable. |
| if (session_context_.frame_scheduler && |
| session_context_.display_manager->default_display()) { |
| std::pair<zx_time_t, zx_time_t> target_times = |
| session_context_.frame_scheduler |
| ->ComputeTargetPresentationAndWakeupTimes( |
| requested_presentation_time); |
| uint64_t target_presentation_time = target_times.first; |
| zx_time_t vsync_interval = |
| session_context_.display_manager->default_display()->GetVsyncInterval(); |
| // TODO(SCN-723): Re-enable warning when requested_presentation_time == 0 |
| // after Flutter engine is fixed. |
| if (requested_presentation_time != 0 && |
| target_presentation_time - vsync_interval > |
| requested_presentation_time) { |
| // Present called with too early of a presentation time. |
| TRACE_INSTANT("gfx", "Session requested too early presentation time", |
| TRACE_SCOPE_PROCESS, "session_id", id(), |
| "requested presentation time", requested_presentation_time, |
| "target presentation time", target_presentation_time); |
| } |
| } |
| |
| auto acquire_fence_set = |
| std::make_unique<escher::FenceSetListener>(std::move(acquire_fences)); |
| // TODO(SCN-1201): Consider calling ScheduleUpdateForSession immediately if |
| // acquire_fence_set is already ready (which is the case if there are |
| // zero acquire fences). |
| |
| acquire_fence_set->WaitReadyAsync( |
| [weak = GetWeakPtr(), requested_presentation_time] { |
| if (weak) { |
| weak->session_context_.session_manager->ScheduleUpdateForSession( |
| weak->session_context_.update_scheduler, |
| requested_presentation_time, std::move(weak)); |
| } |
| }); |
| |
| scheduled_updates_.push( |
| Update{requested_presentation_time, std::move(commands), |
| std::move(acquire_fence_set), std::move(release_events), |
| std::move(callback)}); |
| |
| return true; |
| } |
| |
| void Session::ScheduleImagePipeUpdate(uint64_t presentation_time, |
| ImagePipePtr image_pipe) { |
| FXL_DCHECK(image_pipe); |
| scheduled_image_pipe_updates_.push( |
| {presentation_time, std::move(image_pipe)}); |
| |
| session_context_.session_manager->ScheduleUpdateForSession( |
| session_context_.update_scheduler, presentation_time, GetWeakPtr()); |
| } |
| |
| Session::ApplyUpdateResult Session::ApplyScheduledUpdates( |
| CommandContext* command_context, uint64_t presentation_time, |
| uint64_t presentation_interval) { |
| TRACE_DURATION("gfx", "Session::ApplyScheduledUpdates", "session_id", id_, |
| "session_debug_name", debug_name_, "time", presentation_time, |
| "interval", presentation_interval); |
| |
| ApplyUpdateResult update_results{false, false}; |
| |
| if (presentation_time < last_presentation_time_) { |
| error_reporter_->ERROR() |
| << "scenic_impl::gfx::Session: ApplyScheduledUpdates called with " |
| "presentation_time=" |
| << presentation_time << ", which is less than last_presentation_time_=" |
| << last_presentation_time_ << "."; |
| update_results.success = false; |
| return update_results; |
| } |
| |
| while (!scheduled_updates_.empty() && |
| scheduled_updates_.front().presentation_time <= presentation_time) { |
| if (!scheduled_updates_.front().acquire_fences->ready()) { |
| TRACE_INSTANT("gfx", "Session missed frame", TRACE_SCOPE_PROCESS, |
| "session_id", id(), "session_debug_name", debug_name_, |
| "target presentation time (usecs)", |
| presentation_time / 1000, |
| "session target presentation time (usecs)", |
| scheduled_updates_.front().presentation_time / 1000); |
| break; |
| } |
| if (ApplyUpdate(command_context, |
| std::move(scheduled_updates_.front().commands))) { |
| update_results.needs_render = true; |
| auto info = fuchsia::images::PresentationInfo(); |
| info.presentation_time = presentation_time; |
| info.presentation_interval = presentation_interval; |
| scheduled_updates_.front().present_callback(std::move(info)); |
| |
| FXL_DCHECK(last_applied_update_presentation_time_ <= |
| scheduled_updates_.front().presentation_time); |
| last_applied_update_presentation_time_ = |
| scheduled_updates_.front().presentation_time; |
| |
| for (size_t i = 0; i < fences_to_release_on_next_update_.size(); ++i) { |
| session_context_.release_fence_signaller->AddCPUReleaseFence( |
| std::move(fences_to_release_on_next_update_.at(i))); |
| } |
| fences_to_release_on_next_update_ = |
| std::move(scheduled_updates_.front().release_fences); |
| |
| scheduled_updates_.pop(); |
| |
| // TODO(SCN-1202): gather statistics about how close the actual |
| // presentation_time was to the requested time. |
| } else { |
| // An error was encountered while applying the update. |
| FXL_LOG(WARNING) << "scenic_impl::gfx::Session::ApplyScheduledUpdates(): " |
| "An error was encountered while applying the update. " |
| "Initiating teardown."; |
| update_results.success = false; |
| scheduled_updates_ = {}; |
| |
| // Tearing down a session will very probably result in changes to |
| // the global scene-graph. |
| update_results.needs_render = true; |
| return update_results; |
| } |
| } |
| |
| // TODO(SCN-1219): Unify with other session updates. |
| std::unordered_map<ResourceId, ImagePipePtr> image_pipe_updates_to_upload; |
| while (!scheduled_image_pipe_updates_.empty() && |
| scheduled_image_pipe_updates_.top().presentation_time <= |
| presentation_time) { |
| if (scheduled_image_pipe_updates_.top().image_pipe) { |
| bool image_updated = |
| scheduled_image_pipe_updates_.top().image_pipe->Update( |
| session_context_.release_fence_signaller, presentation_time, |
| presentation_interval); |
| // Only upload images that were updated and are currently dirty, and only |
| // do one upload per ImagePipe. |
| if (image_updated) { |
| image_pipe_updates_to_upload.try_emplace( |
| scheduled_image_pipe_updates_.top().image_pipe->id(), |
| std::move(scheduled_image_pipe_updates_.top().image_pipe)); |
| } |
| } |
| scheduled_image_pipe_updates_.pop(); |
| } |
| |
| // Stage GPU uploads for the latest dirty image on each updated ImagePipe. |
| for (const auto& entry : image_pipe_updates_to_upload) { |
| ImagePipePtr image_pipe = entry.second; |
| image_pipe->UpdateEscherImage(command_context->batch_gpu_uploader()); |
| // Image was updated so the image in the scene is dirty. |
| update_results.needs_render = true; |
| } |
| image_pipe_updates_to_upload.clear(); |
| |
| update_results.success = true; |
| return update_results; |
| } |
| |
| void Session::EnqueueEvent(::fuchsia::ui::gfx::Event event) { |
| event_reporter_->EnqueueEvent(std::move(event)); |
| } |
| |
| void Session::EnqueueEvent(::fuchsia::ui::input::InputEvent event) { |
| event_reporter_->EnqueueEvent(std::move(event)); |
| } |
| |
| bool Session::ApplyUpdate(CommandContext* command_context, |
| std::vector<::fuchsia::ui::gfx::Command> commands) { |
| TRACE_DURATION("gfx", "Session::ApplyUpdate"); |
| for (auto& command : commands) { |
| if (!ApplyCommand(command_context, std::move(command))) { |
| using ::operator<<; // From print_commands.h |
| error_reporter_->ERROR() << "scenic_impl::gfx::Session::ApplyCommand() " |
| "failed to apply Command: " |
| << command; |
| return false; |
| } |
| } |
| return true; |
| // TODO: acquire_fences and release_fences should be added to a list that is |
| // consumed by the FrameScheduler. |
| } |
| |
| void Session::HitTest(uint32_t node_id, ::fuchsia::ui::gfx::vec3 ray_origin, |
| ::fuchsia::ui::gfx::vec3 ray_direction, |
| fuchsia::ui::scenic::Session::HitTestCallback callback) { |
| if (auto node = resources_.FindResource<Node>(node_id)) { |
| SessionHitTester hit_tester(node->session()); |
| std::vector<Hit> hits = hit_tester.HitTest( |
| node.get(), escher::ray4{escher::vec4(Unwrap(ray_origin), 1.f), |
| escher::vec4(Unwrap(ray_direction), 0.f)}); |
| callback(WrapHits(hits)); |
| } else { |
| // TODO(SCN-162): Currently the test fails if the node isn't presented yet. |
| // Perhaps we should given clients more control over which state of |
| // the scene graph will be consulted for hit testing purposes. |
| error_reporter_->WARN() |
| << "Cannot perform hit test because node " << node_id |
| << " does not exist in the currently presented content."; |
| callback(nullptr); |
| } |
| } |
| |
| void Session::HitTestDeviceRay( |
| ::fuchsia::ui::gfx::vec3 ray_origin, ::fuchsia::ui::gfx::vec3 ray_direction, |
| fuchsia::ui::scenic::Session::HitTestCallback callback) { |
| escher::ray4 ray = |
| escher::ray4{{Unwrap(ray_origin), 1.f}, {Unwrap(ray_direction), 0.f}}; |
| |
| // The layer stack expects the input to the hit test to be in unscaled device |
| // coordinates. |
| SessionHitTester hit_tester(this); |
| // TODO(SCN-1170): get rid of SceneGraph::first_compositor(). |
| std::vector<Hit> layer_stack_hits = |
| session_context_.scene_graph->first_compositor()->layer_stack()->HitTest( |
| ray, &hit_tester); |
| |
| callback(WrapHits(layer_stack_hits)); |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |