| // 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/bin/app.h" |
| |
| #include <fuchsia/hardware/display/cpp/fidl.h> |
| #include <fuchsia/vulkan/loader/cpp/fidl.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| |
| #include "rapidjson/document.h" |
| #include "src/graphics/display/lib/coordinator-getter/client.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/functional/cancelable_callback.h" |
| #include "src/ui/lib/escher/vk/pipeline_builder.h" |
| #include "src/ui/scenic/lib/display/color_converter.h" |
| #include "src/ui/scenic/lib/display/display_manager.h" |
| #include "src/ui/scenic/lib/display/display_power_manager.h" |
| #include "src/ui/scenic/lib/flatland/engine/engine.h" |
| #include "src/ui/scenic/lib/flatland/engine/engine_types.h" |
| #include "src/ui/scenic/lib/flatland/renderer/cpu_renderer.h" |
| #include "src/ui/scenic/lib/flatland/renderer/null_renderer.h" |
| #include "src/ui/scenic/lib/flatland/renderer/vk_renderer.h" |
| #include "src/ui/scenic/lib/scheduling/frame_metrics_registry.cb.h" |
| #include "src/ui/scenic/lib/scheduling/windowed_frame_predictor.h" |
| #include "src/ui/scenic/lib/screen_capture/screen_capture.h" |
| #include "src/ui/scenic/lib/screen_capture/screen_capture_buffer_collection_importer.h" |
| #include "src/ui/scenic/lib/screenshot/screenshot_manager.h" |
| #include "src/ui/scenic/lib/utils/escher_provider.h" |
| #include "src/ui/scenic/lib/utils/helpers.h" |
| #include "src/ui/scenic/lib/utils/metrics_impl.h" |
| #include "src/ui/scenic/lib/utils/range_inclusive.h" |
| #include "src/ui/scenic/lib/view_tree/snapshot_dump.h" |
| #include "src/ui/scenic/scenic_structured_config.h" |
| |
| namespace { |
| |
| using scenic_impl::RendererType; |
| |
| // App installs the loader manifest FS at this path so it can use |
| // fsl::DeviceWatcher on it. |
| static const char* kDependencyPath = "/gpu-manifest-fs"; |
| |
| static constexpr zx::duration kShutdownTimeout = zx::sec(1); |
| |
| // NOTE: If this value changes, you should also change the corresponding kCleanupDelay inside |
| // escher/profiling/timestamp_profiler.cc. |
| static constexpr zx::duration kEscherCleanupRetryInterval{1'000'000}; // 1 millisecond |
| |
| std::optional<fuchsia::hardware::display::types::DisplayId> GetDisplayId( |
| const scenic_structured_config::Config& values) { |
| if (values.i_can_haz_display_id() < 0) { |
| return std::nullopt; |
| } |
| return std::make_optional<fuchsia::hardware::display::types::DisplayId>( |
| {.value = static_cast<uint64_t>(values.i_can_haz_display_id())}); |
| } |
| |
| std::optional<uint64_t> GetDisplayMode(const scenic_structured_config::Config& values) { |
| if (values.i_can_haz_display_mode() < 0) { |
| return std::nullopt; |
| } |
| return values.i_can_haz_display_mode(); |
| } |
| |
| utils::RangeInclusive<int> CreateRangeFromStructuredConfigValues(int left, int right) { |
| if (left >= 0 && right >= 0) { |
| ZX_DEBUG_ASSERT(left <= right); |
| return utils::RangeInclusive<int>(left, right); |
| } |
| if (left >= 0) { |
| return utils::RangeInclusive<int>(left, utils::PositiveInfinity{}); |
| } |
| if (right >= 0) { |
| return utils::RangeInclusive<int>(utils::NegativeInfinity{}, right); |
| } |
| return utils::RangeInclusive<int>(); |
| } |
| |
| scenic_impl::display::DisplayModeConstraints GetDisplayModeConstraints( |
| const scenic_structured_config::Config& values) { |
| return { |
| .width_px_range = |
| CreateRangeFromStructuredConfigValues(values.min_display_horizontal_resolution_px(), |
| values.max_display_horizontal_resolution_px()), |
| .height_px_range = CreateRangeFromStructuredConfigValues( |
| values.min_display_vertical_resolution_px(), values.max_display_vertical_resolution_px()), |
| .refresh_rate_millihertz_range = |
| CreateRangeFromStructuredConfigValues(values.min_display_refresh_rate_millihertz(), |
| values.max_display_refresh_rate_millihertz()), |
| }; |
| } |
| |
| std::string ToString(RendererType type) { |
| switch (type) { |
| case RendererType::CPU_RENDERER: |
| return "cpu"; |
| case RendererType::NULL_RENDERER: |
| return "null"; |
| case RendererType::VULKAN: |
| return "vulkan"; |
| } |
| } |
| |
| RendererType GetRendererType(const scenic_structured_config::Config& values) { |
| if (ToString(RendererType::CPU_RENDERER).compare(values.renderer()) == 0) |
| return RendererType::CPU_RENDERER; |
| if (ToString(RendererType::NULL_RENDERER).compare(values.renderer()) == 0) |
| return RendererType::NULL_RENDERER; |
| if (ToString(RendererType::VULKAN).compare(values.renderer()) == 0) |
| return RendererType::VULKAN; |
| FX_LOGS(WARNING) << "Unknown renderer type: " << values.renderer() << ". Falling back to vulkan"; |
| return RendererType::VULKAN; |
| } |
| |
| uint64_t GetDisplayRotation(scenic_structured_config::Config values) { |
| uint64_t rotation = values.display_rotation(); |
| if (rotation >= 0) { |
| FX_CHECK(rotation < 360) << "Rotation should be less than 360 degrees."; |
| return rotation; |
| } |
| FX_LOGS(WARNING) << "Invalid value for display_rotation. Falling back to the default value 0."; |
| return 0; |
| } |
| |
| // Gets Scenic's structured config values and logs them. |
| scenic_structured_config::Config GetConfig() { |
| // Retrieve structured configuration |
| auto values = scenic_structured_config::Config::TakeFromStartupHandle(); |
| |
| FX_LOGS(INFO) << "Scenic renderer: " << ToString(GetRendererType(values)); |
| FX_LOGS(INFO) << "Scenic min_predicted_frame_duration(us): " |
| << values.frame_scheduler_min_predicted_frame_duration_in_us(); |
| FX_LOGS(INFO) << "Scenic pointer auto focus: " << values.pointer_auto_focus(); |
| FX_LOGS(INFO) << "display_composition: " << values.display_composition(); |
| FX_LOGS(INFO) << "Scenic i_can_haz_display_id: " |
| << GetDisplayId(values) |
| .value_or(fuchsia::hardware::display::types::DisplayId{ |
| .value = fuchsia::hardware::display::types::INVALID_DISP_ID}) |
| .value; |
| FX_LOGS(INFO) << "Scenic i_can_haz_display_mode: " << GetDisplayMode(values).value_or(0); |
| FX_LOGS(INFO) << "Scenic display_rotation: " << GetDisplayRotation(values); |
| |
| return values; |
| } |
| |
| #ifdef NDEBUG |
| // TODO(https://fxbug.dev/42125470): Scenic sometimes gets stuck for consecutive 60 seconds. |
| // Here we set up a Watchdog polling Scenic status every 15 seconds. |
| constexpr uint32_t kWatchdogWarningIntervalMs = 15000u; |
| // On some devices, the time to start up Scenic may exceed 15 seconds. |
| // In that case we should only send a warning, and we should only crash |
| // Scenic if the main thread is blocked for longer time. |
| constexpr uint32_t kWatchdogTimeoutMs = 45000u; |
| #else // !defined(NDEBUG) |
| // We set a higher warning interval and timeout length for debug builds, |
| // since these builds could be slower than the default release ones. |
| constexpr uint32_t kWatchdogWarningIntervalMs = 30000u; |
| constexpr uint32_t kWatchdogTimeoutMs = 90000u; |
| #endif // NDEBUG |
| |
| // Interval at which we log that Scenic is waiting for Vulkan or display. |
| static constexpr zx::duration kWaitWarningInterval = zx::sec(5); |
| |
| void PostDelayedTaskUntilCancelled(fit::closure cb, zx::duration delay, bool first_run = true) { |
| if (!cb) |
| return; |
| if (!first_run) |
| cb(); |
| async::PostDelayedTask( |
| async_get_default_dispatcher(), |
| [cb = std::move(cb), delay]() mutable { |
| PostDelayedTaskUntilCancelled(std::move(cb), delay, false); |
| }, |
| delay); |
| } |
| |
| } // namespace |
| |
| namespace scenic_impl { |
| |
| DisplayInfoDelegate::DisplayInfoDelegate(std::shared_ptr<display::Display> display_) |
| : display_(display_) { |
| FX_CHECK(display_); |
| } |
| |
| void DisplayInfoDelegate::GetDisplayInfo( |
| fuchsia::ui::scenic::Scenic::GetDisplayInfoCallback callback) { |
| auto info = ::fuchsia::ui::gfx::DisplayInfo(); |
| info.width_in_px = display_->width_in_px(); |
| info.height_in_px = display_->height_in_px(); |
| |
| callback(std::move(info)); |
| } |
| |
| fuchsia::math::SizeU DisplayInfoDelegate::GetDisplayDimensions() { |
| return {display_->width_in_px(), display_->height_in_px()}; |
| } |
| |
| void DisplayInfoDelegate::GetDisplayOwnershipEvent( |
| fuchsia::ui::scenic::Scenic::GetDisplayOwnershipEventCallback callback) { |
| // These constants are defined as raw hex in the FIDL file, so we confirm here that they are the |
| // same values as the expected constants in the ZX headers. |
| static_assert(fuchsia::ui::scenic::displayNotOwnedSignal == ZX_USER_SIGNAL_0, "Bad constant"); |
| static_assert(fuchsia::ui::scenic::displayOwnedSignal == ZX_USER_SIGNAL_1, "Bad constant"); |
| |
| zx::event dup; |
| if (display_->ownership_event().duplicate(ZX_RIGHTS_BASIC, &dup) != ZX_OK) { |
| FX_LOGS(ERROR) << "Display ownership event duplication error."; |
| callback(zx::event()); |
| } else { |
| callback(std::move(dup)); |
| } |
| } |
| |
| App::App(std::unique_ptr<sys::ComponentContext> app_context, inspect::Node inspect_node, |
| fpromise::promise<::display::CoordinatorClientEnd, zx_status_t> dc_handles_promise, |
| fit::closure quit_callback) |
| : executor_(async_get_default_dispatcher()), |
| app_context_(std::move(app_context)), |
| config_values_(GetConfig()), |
| // TODO(https://fxbug.dev/42117030): subsystems requiring graceful shutdown *on a loop* should |
| // register themselves. It is preferable to cleanly shutdown using destructors only, if |
| // possible. |
| shutdown_manager_( |
| ShutdownManager::New(async_get_default_dispatcher(), std::move(quit_callback))), |
| metrics_logger_( |
| async_get_default_dispatcher(), |
| fidl::ClientEnd<fuchsia_io::Directory>(component::OpenServiceRoot()->TakeChannel())), |
| inspect_node_(std::move(inspect_node)), |
| frame_scheduler_( |
| std::make_unique<scheduling::WindowedFramePredictor>( |
| zx::usec(config_values_.frame_scheduler_min_predicted_frame_duration_in_us()), |
| scheduling::DefaultFrameScheduler::kInitialRenderDuration, |
| scheduling::DefaultFrameScheduler::kInitialUpdateDuration), |
| inspect_node_.CreateChild("FrameScheduler"), &metrics_logger_), |
| renderer_type_(GetRendererType(config_values_)), |
| scenic_(app_context_.get()), |
| uber_struct_system_(std::make_shared<flatland::UberStructSystem>()), |
| link_system_( |
| std::make_shared<flatland::LinkSystem>(uber_struct_system_->GetNextInstanceId())), |
| flatland_presenter_(std::make_shared<flatland::FlatlandPresenterImpl>( |
| async_get_default_dispatcher(), frame_scheduler_)), |
| color_converter_( |
| app_context_.get(), |
| /*set_color_conversion_values*/ |
| display::SetColorConversionFunc([this](const auto& coefficients, const auto& preoffsets, |
| const auto& postoffsets) { |
| FX_DCHECK(flatland_compositor_); |
| flatland_compositor_->SetColorConversionValues(coefficients, preoffsets, postoffsets); |
| }), |
| /*set_minimum_rgb*/ |
| display::SetMinimumRgbFunc([this](const uint8_t minimum_rgb) { |
| FX_DCHECK(flatland_compositor_); |
| return flatland_compositor_->SetMinimumRgb(minimum_rgb); |
| })), |
| geometry_provider_(), |
| observer_registry_(geometry_provider_), |
| scoped_observer_registry_(geometry_provider_), |
| watchdog_("Scenic main thread", kWatchdogWarningIntervalMs, kWatchdogTimeoutMs, |
| async_get_default_dispatcher()) { |
| fpromise::bridge<escher::EscherUniquePtr> escher_bridge; |
| fpromise::bridge<std::shared_ptr<display::Display>> display_bridge; |
| |
| auto vulkan_loader = app_context_->svc()->Connect<fuchsia::vulkan::loader::Loader>(); |
| fidl::InterfaceHandle<fuchsia::io::Directory> dir; |
| vulkan_loader->ConnectToManifestFs(fuchsia::vulkan::loader::ConnectToManifestOptions{}, |
| dir.NewRequest().TakeChannel()); |
| |
| fdio_ns_t* ns; |
| FX_CHECK(fdio_ns_get_installed(&ns) == ZX_OK); |
| FX_CHECK(fdio_ns_bind(ns, kDependencyPath, dir.TakeChannel().release()) == ZX_OK); |
| |
| // Publish all protocols that are ready. |
| view_ref_installed_impl_.Publish(app_context_.get()); |
| observer_registry_.Publish(app_context_.get()); |
| scoped_observer_registry_.Publish(app_context_.get()); |
| focus_manager_.Publish(*app_context_); |
| |
| auto vulkan_wait_log = std::make_unique<fxl::CancelableClosure>( |
| [] { FX_LOGS(WARNING) << "SCENIC IS WAITING FOR VULKAN TO BE AVAILABLE..."; }); |
| PostDelayedTaskUntilCancelled(vulkan_wait_log->callback(), kWaitWarningInterval); |
| |
| if (renderer_type_ == RendererType::VULKAN) { |
| // Wait for a Vulkan ICD to become advertised before trying to launch escher. |
| FX_DCHECK(!device_watcher_); |
| device_watcher_ = fsl::DeviceWatcher::Create( |
| kDependencyPath, [this, vulkan_loader = std::move(vulkan_loader), |
| completer = std::move(escher_bridge.completer), |
| vulkan_wait_log = std::move(vulkan_wait_log)]( |
| const fidl::ClientEnd<fuchsia_io::Directory>& dir, |
| const std::string& filename) mutable { |
| auto escher = utils::CreateEscher(app_context_.get()); |
| if (!escher) { |
| FX_LOGS(WARNING) << "Escher creation failed."; |
| // This should almost never happen, but might if the device was removed quickly after it |
| // was added or if the Vulkan driver doesn't actually work on this hardware. Retry when |
| // a new device is added. |
| return; |
| } |
| completer.complete_ok(std::move(escher)); |
| device_watcher_.reset(); |
| }); |
| FX_DCHECK(device_watcher_); |
| } else { |
| // Immediately complete promise if we aren't using vulkan renderer. |
| escher_bridge.completer.complete_ok(nullptr); |
| } |
| |
| auto display_wait_log = std::make_unique<fxl::CancelableClosure>( |
| [] { FX_LOGS(WARNING) << "SCENIC IS WAITING FOR DISPLAY TO BE AVAILABLE..."; }); |
| PostDelayedTaskUntilCancelled(display_wait_log->callback(), kWaitWarningInterval); |
| |
| // Instantiate DisplayManager and schedule a task to inject the display coordinator into it, once |
| // it becomes available. |
| display_manager_.emplace(GetDisplayId(config_values_), GetDisplayMode(config_values_), |
| GetDisplayModeConstraints(config_values_), |
| [this, completer = std::move(display_bridge.completer), |
| display_wait_log = std::move(display_wait_log)]() mutable { |
| completer.complete_ok(display_manager_->default_display_shared()); |
| }); |
| executor_.schedule_task(dc_handles_promise.then( |
| [this](fpromise::result<::display::CoordinatorClientEnd, zx_status_t>& handles) { |
| FX_CHECK(handles.is_ok()) << "Failed to get display coordinator:" |
| << zx_status_get_string(handles.error()); |
| display_manager_->BindDefaultDisplayCoordinator(std::move(handles.value())); |
| })); |
| |
| // Schedule a task to finish initialization once all promises have been completed. |
| // This closure is placed on |executor_|, which is owned by App, so it is safe to use |this|. |
| { |
| auto p = |
| fpromise::join_promises(escher_bridge.consumer.promise(), display_bridge.consumer.promise()) |
| .and_then( |
| [this](std::tuple<fpromise::result<escher::EscherUniquePtr>, |
| fpromise::result<std::shared_ptr<display::Display>>>& results) { |
| InitializeServices(std::move(std::get<0>(results).value()), |
| std::move(std::get<1>(results).value())); |
| // Should be run after all outgoing services are published. |
| app_context_->outgoing()->ServeFromStartupInfo(); |
| }); |
| |
| executor_.schedule_task(std::move(p)); |
| } |
| } |
| |
| void App::InitializeServices(escher::EscherUniquePtr escher, |
| std::shared_ptr<display::Display> display) { |
| TRACE_DURATION("gfx", "App::InitializeServices"); |
| |
| if (!display) { |
| FX_LOGS(ERROR) << "No default display, Graphics system exiting"; |
| shutdown_manager_->Shutdown(kShutdownTimeout); |
| return; |
| } |
| |
| if (renderer_type_ == RendererType::VULKAN) { |
| if (!escher || !escher->device()) { |
| FX_LOGS(ERROR) << "No Vulkan on device, Graphics system exiting."; |
| shutdown_manager_->Shutdown(kShutdownTimeout); |
| return; |
| } |
| |
| escher_ = std::move(escher); |
| escher_cleanup_ = std::make_shared<utils::CleanupUntilDone>(kEscherCleanupRetryInterval, |
| [escher = escher_->GetWeakPtr()]() { |
| if (!escher) { |
| // Escher is destroyed, so there |
| // is no cleanup to be done. |
| return true; |
| } |
| return escher->Cleanup(); |
| }); |
| } |
| |
| InitializeGraphics(display); |
| InitializeInput(); |
| InitializeHeartbeat(*display); |
| } |
| |
| App::~App() { |
| fdio_ns_t* ns; |
| FX_CHECK(fdio_ns_get_installed(&ns) == ZX_OK); |
| FX_CHECK(fdio_ns_unbind(ns, kDependencyPath) == ZX_OK); |
| } |
| |
| void App::InitializeGraphics(std::shared_ptr<display::Display> display) { |
| TRACE_DURATION("gfx", "App::InitializeGraphics"); |
| FX_LOGS(INFO) << "App::InitializeGraphics() " << display->width_in_px() << "x" |
| << display->height_in_px() << "px " << display->width_in_mm() << "x" |
| << display->height_in_mm() << "mm"; |
| |
| // Replace Escher's default pipeline builder with one which will log to Cobalt upon each |
| // unexpected lazy pipeline creation. This allows us to detect when this slips through our |
| // testing and occurs in the wild. In order to detect problems ASAP during development, debug |
| // builds CHECK instead of logging to Cobalt. |
| if (renderer_type_ == RendererType::VULKAN) { |
| auto pipeline_builder = std::make_unique<escher::PipelineBuilder>(escher_->vk_device()); |
| pipeline_builder->set_log_pipeline_creation_callback( |
| [metrics_logger = &metrics_logger_](const vk::GraphicsPipelineCreateInfo* graphics_info, |
| const vk::ComputePipelineCreateInfo* compute_info) { |
| // TODO(https://fxbug.dev/42126999): pre-warm compute pipelines in addition to graphics |
| // pipelines. |
| if (compute_info) { |
| FX_LOGS(WARNING) << "Unexpected lazy creation of Vulkan compute pipeline."; |
| return; |
| } |
| |
| #if !defined(NDEBUG) |
| FX_CHECK(false) // debug builds should crash for early detection |
| #else |
| FX_LOGS(WARNING) // release builds should log to Cobalt, see below. |
| #endif |
| << "Unexpected lazy creation of Vulkan pipeline."; |
| |
| metrics_logger->LogRareEvent( |
| cobalt_registry::ScenicRareEventMigratedMetricDimensionEvent::LazyPipelineCreation); |
| }); |
| escher_->set_pipeline_builder(std::move(pipeline_builder)); |
| } |
| |
| { |
| singleton_display_service_.emplace(display); |
| singleton_display_service_->AddPublicService(scenic_.app_context()->outgoing().get()); |
| display_info_delegate_.emplace(display); |
| } |
| |
| std::shared_ptr<flatland::Renderer> flatland_renderer; |
| switch (renderer_type_) { |
| case RendererType::CPU_RENDERER: |
| flatland_renderer = std::make_shared<flatland::CpuRenderer>(); |
| break; |
| case RendererType::NULL_RENDERER: |
| flatland_renderer = std::make_shared<flatland::NullRenderer>(); |
| break; |
| case RendererType::VULKAN: |
| flatland_renderer = std::make_shared<flatland::VkRenderer>(escher_->GetWeakPtr()); |
| break; |
| } |
| // TODO(https://fxbug.dev/42158284): flatland::VkRenderer hardcodes the framebuffer pixel format. |
| // Eventually we won't, instead choosing one from the list of acceptable formats advertised by |
| // each plugged-in display. This will raise the issue of where to do pipeline cache warming: it |
| // will be too early to do it here, since we're not yet aware of any displays nor the formats they |
| // support. It will probably be OK to warm the cache when a new display is plugged in, because |
| // users don't expect plugging in a display to be completely jank-free. |
| |
| flatland_renderer->WarmPipelineCache(); |
| |
| // TODO(https://fxbug.dev/42073146) Support camera image in shader pre-warmup. |
| // Disabling this line allows any shaders that weren't warmed up to be lazily created later. |
| // flatland_renderer->set_disable_lazy_pipeline_creation(true); |
| |
| // Flatland compositor must be made first; it is needed by the manager and the engine. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[flatland_display_compositor]"); |
| |
| flatland_compositor_ = std::make_shared<flatland::DisplayCompositor>( |
| async_get_default_dispatcher(), display_manager_->default_display_coordinator(), |
| flatland_renderer, utils::CreateSysmemAllocatorSyncPtr("flatland::DisplayCompositor"), |
| config_values_.display_composition(), /*max_display_layers=*/1); |
| } |
| |
| // Flatland manager depends on compositor, and is required by engine. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[flatland_manager]"); |
| |
| std::vector<std::shared_ptr<allocation::BufferCollectionImporter>> importers{ |
| flatland_compositor_}; |
| |
| flatland_manager_ = std::make_shared<flatland::FlatlandManager>( |
| async_get_default_dispatcher(), flatland_presenter_, uber_struct_system_, link_system_, |
| display, std::move(importers), |
| /*register_view_focuser*/ |
| [this](fidl::InterfaceRequest<fuchsia::ui::views::Focuser> focuser, |
| zx_koid_t view_ref_koid) { |
| focus_manager_.RegisterViewFocuser(view_ref_koid, std::move(focuser)); |
| }, |
| /*register_view_ref_focused*/ |
| [this](fidl::InterfaceRequest<fuchsia::ui::views::ViewRefFocused> vrf, |
| zx_koid_t view_ref_koid) { |
| focus_manager_.RegisterViewRefFocused(view_ref_koid, std::move(vrf)); |
| }, |
| /*register_touch_source*/ |
| [this](fidl::InterfaceRequest<fuchsia::ui::pointer::TouchSource> touch_source, |
| zx_koid_t view_ref_koid) { |
| input_->RegisterTouchSource(std::move(touch_source), view_ref_koid); |
| }, |
| /*register_mouse_source*/ |
| [this](fidl::InterfaceRequest<fuchsia::ui::pointer::MouseSource> mouse_source, |
| zx_koid_t view_ref_koid) { |
| input_->RegisterMouseSource(std::move(mouse_source), view_ref_koid); |
| }); |
| |
| // TODO(https://fxbug.dev/42146099): these should be moved into FlatlandManager. |
| { |
| // Note: can't use `fit::bind_member()` here, because `CreateFlatland()` returns non-void. |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::composition::Flatland>)> handler = |
| [flatland_manager = flatland_manager_.get()]( |
| fidl::InterfaceRequest<fuchsia::ui::composition::Flatland> request) { |
| flatland_manager->CreateFlatland(std::move(request)); |
| }; |
| FX_CHECK(app_context_->outgoing()->AddPublicService(std::move(handler)) == ZX_OK); |
| } |
| { |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::composition::FlatlandDisplay>)> |
| handler = fit::bind_member(flatland_manager_.get(), |
| &flatland::FlatlandManager::CreateFlatlandDisplay); |
| FX_CHECK(app_context_->outgoing()->AddPublicService(std::move(handler)) == ZX_OK); |
| } |
| } |
| |
| const auto screen_capture_buffer_collection_importer = |
| std::make_shared<screen_capture::ScreenCaptureBufferCollectionImporter>( |
| utils::CreateSysmemAllocatorSyncPtr("ScreenCaptureBufferCollectionImporter"), |
| flatland_renderer); |
| |
| // Allocator service needs Flatland DisplayCompositor to act as a BufferCollectionImporter. |
| { |
| std::vector<std::shared_ptr<allocation::BufferCollectionImporter>> screen_capture_importers; |
| screen_capture_importers.push_back(screen_capture_buffer_collection_importer); |
| |
| std::vector<std::shared_ptr<allocation::BufferCollectionImporter>> default_importers; |
| default_importers.push_back(flatland_compositor_); |
| |
| allocator_ = std::make_shared<allocation::Allocator>( |
| app_context_.get(), default_importers, screen_capture_importers, |
| utils::CreateSysmemAllocatorSyncPtr("ScenicAllocator")); |
| } |
| |
| // Flatland engine requires FlatlandManager and DisplayCompositor to be constructed first. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[flatland_engine]"); |
| |
| flatland_engine_ = std::make_shared<flatland::Engine>( |
| flatland_compositor_, flatland_presenter_, uber_struct_system_, link_system_, |
| inspect_node_.CreateChild("FlatlandEngine"), [this] { |
| FX_DCHECK(flatland_manager_); |
| const auto display = flatland_manager_->GetPrimaryFlatlandDisplayForRendering(); |
| return display ? std::optional<flatland::TransformHandle>(display->root_transform()) |
| : std::nullopt; |
| }); |
| } |
| |
| // Make ScreenCaptureManager. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[screen_capture_manager]"); |
| |
| std::vector<std::shared_ptr<allocation::BufferCollectionImporter>> screen_capture_importers; |
| screen_capture_importers.push_back(screen_capture_buffer_collection_importer); |
| |
| // Capture flatland_manager since the primary display may not have been initialized yet. |
| screen_capture_manager_.emplace(flatland_engine_, flatland_renderer, flatland_manager_, |
| std::move(screen_capture_importers)); |
| |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::composition::ScreenCapture>)> handler = |
| fit::bind_member(&screen_capture_manager_.value(), |
| &screen_capture::ScreenCaptureManager::CreateClient); |
| FX_CHECK(app_context_->outgoing()->AddPublicService(std::move(handler)) == ZX_OK); |
| } |
| |
| // Make ScreenCapture2Manager. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[screen_capture2_manager]"); |
| |
| // Capture flatland_manager since the primary display may not have been initialized yet. |
| screen_capture2_manager_.emplace( |
| flatland_renderer, screen_capture_buffer_collection_importer, [this]() { |
| FX_DCHECK(flatland_manager_); |
| FX_DCHECK(flatland_engine_); |
| |
| auto display = flatland_manager_->GetPrimaryFlatlandDisplayForRendering(); |
| if (!display) { |
| FX_LOGS(WARNING) |
| << "No FlatlandDisplay attached at root. Returning an empty screenshot."; |
| return flatland::Renderables(); |
| } |
| |
| return flatland_engine_->GetRenderables(*display); |
| }); |
| |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::composition::internal::ScreenCapture>)> |
| handler = fit::bind_member(&screen_capture2_manager_.value(), |
| &screen_capture2::ScreenCapture2Manager::CreateClient); |
| FX_CHECK(app_context_->outgoing()->AddPublicService(std::move(handler)) == ZX_OK); |
| } |
| |
| // Make ScreenshotManager for the client-friendly screenshot protocol. |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[screenshot_manager]"); |
| |
| std::vector<std::shared_ptr<allocation::BufferCollectionImporter>> screen_capture_importers; |
| screen_capture_importers.push_back(screen_capture_buffer_collection_importer); |
| |
| // Capture flatland_manager since the primary display may not have been initialized yet. |
| screenshot_manager_.emplace( |
| allocator_, flatland_renderer, |
| [this]() { |
| FX_DCHECK(flatland_manager_); |
| FX_DCHECK(flatland_engine_); |
| |
| auto display = flatland_manager_->GetPrimaryFlatlandDisplayForRendering(); |
| if (!display) { |
| FX_LOGS(WARNING) |
| << "No FlatlandDisplay attached at root. Returning an empty screenshot."; |
| return flatland::Renderables(); |
| } |
| |
| return flatland_engine_->GetRenderables(*display); |
| }, |
| std::move(screen_capture_importers), display_info_delegate_->GetDisplayDimensions(), |
| GetDisplayRotation(config_values_)); |
| |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::composition::Screenshot>)> handler = |
| fit::bind_member(&screenshot_manager_.value(), |
| &screenshot::ScreenshotManager::CreateBinding); |
| FX_CHECK(app_context_->outgoing()->AddPublicService(std::move(handler)) == ZX_OK); |
| } |
| |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[display_power]"); |
| display_power_manager_.emplace(display_manager_.value()); |
| FX_CHECK(app_context_->outgoing()->AddPublicService(display_power_manager_->GetHandler()) == |
| ZX_OK); |
| } |
| } |
| |
| void App::InitializeInput() { |
| TRACE_DURATION("gfx", "App::InitializeInput"); |
| input_.emplace(app_context_.get(), inspect_node_, |
| /*request_focus*/ |
| [this, use_auto_focus = config_values_.pointer_auto_focus()](zx_koid_t koid) { |
| if (!use_auto_focus) |
| return; |
| |
| const auto& focus_chain = focus_manager_.focus_chain(); |
| if (!focus_chain.empty()) { |
| const zx_koid_t requestor = focus_chain[0]; |
| const zx_koid_t request = koid != ZX_KOID_INVALID ? koid : requestor; |
| focus_manager_.RequestFocus(requestor, request); |
| } |
| }); |
| } |
| |
| void App::InitializeHeartbeat(display::Display& display) { |
| TRACE_DURATION("gfx", "App::InitializeHeartbeat"); |
| { // Initialize ViewTreeSnapshotter |
| |
| // These callbacks are be called once per frame (at the end of OnCpuWorkDone()) and the results |
| // used to build the ViewTreeSnapshot. |
| // We create one per compositor. |
| std::vector<view_tree::SubtreeSnapshotGenerator> subtrees_generator_callbacks; |
| subtrees_generator_callbacks.emplace_back([this] { |
| if (auto display = flatland_manager_->GetPrimaryFlatlandDisplayForRendering()) { |
| return flatland_engine_->GenerateViewTreeSnapshot(display->root_transform()); |
| } else { |
| return view_tree::SubtreeSnapshot{}; // Empty snapshot. |
| } |
| }); |
| |
| // All subscriber callbacks get called with the new snapshot every time one is generated (once |
| // per frame). |
| std::vector<view_tree::ViewTreeSnapshotter::Subscriber> subscribers; |
| subscribers.push_back( |
| {.on_new_view_tree = |
| [this](auto snapshot) { input_->OnNewViewTreeSnapshot(std::move(snapshot)); }, |
| .dispatcher = async_get_default_dispatcher()}); |
| |
| subscribers.push_back( |
| {.on_new_view_tree = |
| [this](auto snapshot) { focus_manager_.OnNewViewTreeSnapshot(std::move(snapshot)); }, |
| .dispatcher = async_get_default_dispatcher()}); |
| |
| subscribers.push_back({.on_new_view_tree = |
| [this](auto snapshot) { |
| view_ref_installed_impl_.OnNewViewTreeSnapshot( |
| std::move(snapshot)); |
| }, |
| .dispatcher = async_get_default_dispatcher()}); |
| |
| subscribers.push_back({.on_new_view_tree = |
| [this](auto snapshot) { |
| geometry_provider_.OnNewViewTreeSnapshot(std::move(snapshot)); |
| }, |
| .dispatcher = async_get_default_dispatcher()}); |
| |
| if (enable_snapshot_dump_) { |
| subscribers.push_back({.on_new_view_tree = |
| [](auto snapshot) { |
| view_tree::SnapshotDump::OnNewViewTreeSnapshot( |
| std::move(snapshot)); |
| }, |
| .dispatcher = async_get_default_dispatcher()}); |
| } |
| |
| view_tree_snapshotter_.emplace(std::move(subtrees_generator_callbacks), std::move(subscribers)); |
| } |
| |
| // Set up what to do each time a FrameScheduler event fires. |
| frame_scheduler_.Initialize( |
| display.vsync_timing(), |
| /*update_sessions*/ |
| [this](auto& sessions_to_update, auto trace_id, auto fences_from_previous_presents) { |
| TRACE_DURATION("gfx", "App update_sessions"); |
| |
| // Flatland doesn't pass release fences into the FrameScheduler. Instead, they are stored |
| // in the FlatlandPresenter and pulled out by the flatland::Engine during rendering. |
| FX_CHECK(fences_from_previous_presents.empty()) |
| << "Flatland fences should not be handled by FrameScheduler."; |
| |
| flatland_manager_->UpdateInstances(sessions_to_update); |
| flatland_presenter_->AccumulateReleaseFences(sessions_to_update); |
| }, |
| /*on_cpu_work_done*/ |
| [this] { |
| TRACE_DURATION("gfx", "App on_cpu_work_done"); |
| flatland_manager_->SendHintsToStartRendering(); |
| screen_capture2_manager_->RenderPendingScreenCaptures(); |
| view_tree_snapshotter_->UpdateSnapshot(); |
| // Always defer the first cleanup attempt, because the first try is almost guaranteed to |
| // fail, and checking the status of a `VkFence` is fairly expensive. |
| if (escher_cleanup_) |
| escher_cleanup_->Cleanup(/*ok_to_run_immediately=*/false); |
| }, |
| /*on_frame_presented*/ |
| [this](auto latched_times, auto present_times) { |
| TRACE_DURATION("gfx", "App on_frame_presented"); |
| flatland_manager_->OnFramePresented(latched_times, present_times); |
| }, |
| /*render_scheduled_frame*/ |
| [this](auto frame_number, auto presentation_time, auto callback) { |
| TRACE_DURATION("gfx", "App render_scheduled_frame"); |
| FX_CHECK(flatland_frame_count_ + skipped_frame_count_ == frame_number - 1); |
| if (auto display = flatland_manager_->GetPrimaryFlatlandDisplayForRendering()) { |
| flatland_engine_->RenderScheduledFrame(frame_number, presentation_time, *display, |
| std::move(callback)); |
| ++flatland_frame_count_; |
| } else { |
| FX_LOGS(INFO) << "No FlatlandDisplay; skipping render scheduled frame."; |
| skipped_frame_count_++; |
| flatland_engine_->SkipRender(std::move(callback)); |
| } |
| }); |
| } |
| |
| } // namespace scenic_impl |