| // 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/engine.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/zx/time.h> |
| |
| #include <set> |
| #include <string> |
| #include <unordered_set> |
| |
| #include <trace/event.h> |
| |
| #include "garnet/lib/ui/gfx/engine/frame_scheduler.h" |
| #include "garnet/lib/ui/gfx/engine/frame_timings.h" |
| #include "garnet/lib/ui/gfx/engine/hardware_layer_assignment.h" |
| #include "garnet/lib/ui/gfx/engine/session.h" |
| #include "garnet/lib/ui/gfx/engine/session_handler.h" |
| #include "garnet/lib/ui/gfx/id.h" |
| #include "garnet/lib/ui/gfx/resources/compositor/compositor.h" |
| #include "garnet/lib/ui/gfx/resources/dump_visitor.h" |
| #include "garnet/lib/ui/gfx/resources/nodes/traversal.h" |
| #include "garnet/lib/ui/scenic/session.h" |
| #include "src/ui/lib/escher/renderer/batch_gpu_uploader.h" |
| #include "src/ui/lib/escher/util/fuchsia_utils.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| Engine::Engine(const std::shared_ptr<FrameScheduler>& frame_scheduler, Sysmem* sysmem, |
| DisplayManager* display_manager, escher::EscherWeakPtr weak_escher, |
| inspect_deprecated::Node inspect_node) |
| : sysmem_(sysmem), |
| display_manager_(display_manager), |
| escher_(std::move(weak_escher)), |
| engine_renderer_(std::make_unique<EngineRenderer>( |
| escher_, escher_->device()->caps().GetMatchingDepthStencilFormat( |
| {vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}))), |
| image_factory_(std::make_unique<escher::ImageFactoryAdapter>(escher()->gpu_allocator(), |
| escher()->resource_recycler())), |
| rounded_rect_factory_(std::make_unique<escher::RoundedRectFactory>(escher_)), |
| release_fence_signaller_( |
| std::make_unique<escher::ReleaseFenceSignaller>(escher()->command_buffer_sequencer())), |
| frame_scheduler_(frame_scheduler), |
| inspect_node_(std::move(inspect_node)), |
| weak_factory_(this) { |
| FXL_DCHECK(escher_); |
| |
| InitializeInspectObjects(); |
| } |
| |
| Engine::Engine(const std::shared_ptr<FrameScheduler>& frame_scheduler, Sysmem* sysmem, |
| DisplayManager* display_manager, |
| std::unique_ptr<escher::ReleaseFenceSignaller> release_fence_signaller, |
| escher::EscherWeakPtr weak_escher) |
| : sysmem_(sysmem), |
| display_manager_(display_manager), |
| escher_(std::move(weak_escher)), |
| release_fence_signaller_(std::move(release_fence_signaller)), |
| frame_scheduler_(frame_scheduler), |
| weak_factory_(this) { |
| InitializeInspectObjects(); |
| } |
| |
| void Engine::InitializeInspectObjects() { |
| inspect_scene_dump_ = inspect_node_.CreateLazyStringProperty("scene_dump", [this] { |
| if (scene_graph_.compositors().empty()) { |
| return std::string("(no compositors)"); |
| } |
| std::ostringstream output; |
| output << std::endl; |
| for (auto& c : scene_graph_.compositors()) { |
| output << "========== BEGIN COMPOSITOR DUMP ======================" << std::endl; |
| DumpVisitor visitor(DumpVisitor::VisitorContext(output, nullptr)); |
| c->Accept(&visitor); |
| output << "============ END COMPOSITOR DUMP ======================"; |
| } |
| return output.str(); |
| }); |
| } |
| |
| // Helper for RenderFrame(). Generate a mapping between a Compositor's Layer |
| // resources and the hardware layers they should be displayed on. |
| // TODO(SCN-1088): there should be a separate mechanism that is responsible |
| // for inspecting the compositor's resource tree and optimizing the assignment |
| // of rendered content to hardware display layers. |
| std::optional<HardwareLayerAssignment> GetHardwareLayerAssignment(const Compositor& compositor) { |
| // TODO(SCN-1098): this is a placeholder; currently only a single hardware |
| // layer is supported, and we don't know its ID (it is hidden within the |
| // DisplayManager implementation), so we just say 0. |
| std::vector<Layer*> layers = compositor.GetDrawableLayers(); |
| if (layers.empty() || !compositor.swapchain()) { |
| return {}; |
| } |
| return { |
| {.items = {{ |
| .hardware_layer_id = 0, |
| .layers = std::move(layers), |
| }}, |
| .swapchain = compositor.swapchain()}, |
| }; |
| } |
| |
| bool Engine::RenderFrame(const FrameTimingsPtr& timings, zx::time presentation_time) { |
| uint64_t frame_number = timings->frame_number(); |
| |
| // NOTE: this name is important for benchmarking. Do not remove or modify it |
| // without also updating the "process_gfx_trace.go" script. |
| TRACE_DURATION("gfx", "RenderFrame", "frame_number", frame_number, "time", |
| presentation_time.get()); |
| |
| TRACE_FLOW_BEGIN("gfx", "scenic_frame", frame_number); |
| |
| UpdateAndDeliverMetrics(presentation_time); |
| |
| // TODO(SCN-1089): the FrameTimings are passed to the Compositor's swapchain |
| // to notify when the frame is finished rendering, presented, dropped, etc. |
| // This doesn't make any sense if there are multiple compositors. |
| FXL_DCHECK(scene_graph_.compositors().size() <= 1); |
| |
| std::vector<HardwareLayerAssignment> hlas; |
| for (auto& compositor : scene_graph_.compositors()) { |
| if (auto hla = GetHardwareLayerAssignment(*compositor)) { |
| hlas.push_back(std::move(hla.value())); |
| |
| // Verbose logging of the entire Compositor resource tree. |
| if (FXL_VLOG_IS_ON(3)) { |
| std::ostringstream output; |
| DumpVisitor visitor(DumpVisitor::VisitorContext(output, nullptr)); |
| compositor->Accept(&visitor); |
| FXL_VLOG(3) << "Compositor dump\n" << output.str(); |
| } |
| } else { |
| // Nothing to be drawn; either the Compositor has no layers to draw or |
| // it has no valid Swapchain. The latter will be true if Escher/Vulkan |
| // is unavailable for whatever reason. |
| } |
| } |
| if (hlas.empty()) { |
| // No compositor has any renderable content. |
| return false; |
| } |
| |
| escher::FramePtr frame = escher()->NewFrame("Scenic Compositor", frame_number); |
| |
| bool success = true; |
| for (size_t i = 0; i < hlas.size(); ++i) { |
| const bool is_last_hla = (i == hlas.size() - 1); |
| HardwareLayerAssignment& hla = hlas[i]; |
| |
| success &= hla.swapchain->DrawAndPresentFrame( |
| timings, hla, |
| [is_last_hla, &frame, escher{escher_}, engine_renderer{engine_renderer_.get()}]( |
| zx::time target_presentation_time, const escher::ImagePtr& output_image, |
| const HardwareLayerAssignment::Item hla_item, |
| const escher::SemaphorePtr& acquire_semaphore, |
| const escher::SemaphorePtr& frame_done_semaphore) { |
| output_image->SetWaitSemaphore(acquire_semaphore); |
| engine_renderer->RenderLayers(frame, target_presentation_time, output_image, |
| hla_item.layers); |
| |
| // Create a flow event that ends in the magma system driver. |
| zx::event semaphore_event = GetEventForSemaphore(escher->device(), frame_done_semaphore); |
| zx_info_handle_basic_t info; |
| zx_status_t status = |
| semaphore_event.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| TRACE_FLOW_BEGIN("gfx", "semaphore", info.koid); |
| |
| if (!is_last_hla) { |
| frame->SubmitPartialFrame(frame_done_semaphore); |
| } else { |
| frame->EndFrame(frame_done_semaphore, nullptr); |
| } |
| }); |
| } |
| if (!success) { |
| // TODO(SCN-1089): what is the proper behavior when some swapchains |
| // are displayed and others aren't? This isn't currently an issue because |
| // there is only one Compositor; see above. |
| FXL_DCHECK(hlas.size() == 1); |
| return false; |
| } |
| |
| CleanupEscher(); |
| return true; |
| } |
| |
| void Engine::UpdateAndDeliverMetrics(zx::time presentation_time) { |
| // NOTE: this name is important for benchmarking. Do not remove or modify it |
| // without also updating the "process_gfx_trace.go" script. |
| TRACE_DURATION("gfx", "UpdateAndDeliverMetrics", "time", presentation_time.get()); |
| |
| // Gather all of the scene which might need to be updated. |
| std::set<Scene*> scenes; |
| for (auto compositor : scene_graph_.compositors()) { |
| compositor->CollectScenes(&scenes); |
| } |
| if (scenes.empty()) |
| return; |
| |
| // TODO(SCN-216): Traversing the whole graph just to compute this is pretty |
| // inefficient. We should optimize this. |
| fuchsia::ui::gfx::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(SCN-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 (auto event_reporter = node->event_reporter()) { |
| fuchsia::ui::gfx::Event event; |
| event.set_metrics(::fuchsia::ui::gfx::MetricsEvent()); |
| event.metrics().node_id = node->id(); |
| event.metrics().metrics = node->reported_metrics(); |
| event_reporter->EnqueueEvent(std::move(event)); |
| } |
| } |
| } |
| |
| // TODO(mikejurka): move this to appropriate util file |
| bool MetricsEquals(const fuchsia::ui::gfx::Metrics& a, const fuchsia::ui::gfx::Metrics& b) { |
| return a.scale_x == b.scale_x && a.scale_y == b.scale_y && a.scale_z == b.scale_z; |
| } |
| |
| void Engine::UpdateMetrics(Node* node, const fuchsia::ui::gfx::Metrics& parent_metrics, |
| std::vector<Node*>* updated_nodes) { |
| fuchsia::ui::gfx::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() & fuchsia::ui::gfx::kMetricsEventMask) && |
| !MetricsEquals(node->reported_metrics(), 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. |
| // |
| // NOTE: If this value changes, you should also change the corresponding |
| // kCleanupDelay inside timestamp_profiler.h. |
| const zx::duration kCleanupDelay = zx::msec(1); |
| |
| escher_cleanup_scheduled_ = true; |
| async::PostDelayedTask( |
| async_get_default_dispatcher(), |
| [weak = weak_factory_.GetWeakPtr()] { |
| if (weak) { |
| // Recursively reschedule if cleanup is incomplete. |
| weak->escher_cleanup_scheduled_ = false; |
| weak->CleanupEscher(); |
| } |
| }, |
| kCleanupDelay); |
| } |
| } |
| |
| void Engine::DumpScenes(std::ostream& output, |
| std::unordered_set<GlobalId, GlobalId::Hash>* visited_resources) const { |
| FXL_DCHECK(visited_resources); |
| |
| // Dump all Compositors and all transitively-reachable Resources. |
| // Remember the set of visited resources; the next step will be to dump the |
| // unreachable resources. |
| output << "Compositors: \n"; |
| for (auto compositor : scene_graph_.compositors()) { |
| DumpVisitor visitor(DumpVisitor::VisitorContext(output, visited_resources)); |
| |
| compositor->Accept(&visitor); |
| output << "\n===\n\n"; |
| } |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |