| // 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 <lib/fit/bridge.h> |
| #include <lib/fit/function.h> |
| #include <lib/fit/single_threaded_executor.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #ifdef SCENIC_ENABLE_GFX_SUBSYSTEM |
| #include "src/ui/scenic/lib/gfx/gfx_system.h" |
| #endif |
| |
| #ifdef SCENIC_ENABLE_INPUT_SUBSYSTEM |
| #include "src/ui/scenic/lib/input/input_system.h" |
| #endif |
| |
| #include "rapidjson/document.h" |
| #include "src/lib/cobalt/cpp/cobalt_logger.h" |
| #include "src/lib/files/file.h" |
| #include "src/ui/lib/escher/vk/pipeline_builder.h" |
| #include "src/ui/scenic/lib/gfx/api/internal_snapshot_impl.h" |
| #include "src/ui/scenic/lib/scheduling/default_frame_scheduler.h" |
| #include "src/ui/scenic/lib/scheduling/frame_metrics_registry.cb.h" |
| #include "src/ui/scenic/lib/scheduling/windowed_frame_predictor.h" |
| |
| namespace { |
| |
| // Wait for /dev/class/display-controller on x86 as that's sufficient for Intel GPU driver and |
| // supports AEMU and swiftshader, which don't depend on devices in /dev/class/gpu. |
| // |
| // TODO(fxbug.dev/23795): Scenic should not be aware of these type of dependencies. |
| #if defined(__x86_64__) |
| static const std::string kDependencyDir = "/dev/class/display-controller"; |
| #else |
| static const std::string kDependencyDir = "/dev/class/gpu"; |
| #endif |
| |
| zx::duration GetMinimumPredictedFrameDuration() { |
| std::string frame_scheduler_min_predicted_frame_duration; |
| int frame_scheduler_min_predicted_frame_duration_in_us = 0; |
| if (files::ReadFileToString("/config/data/scenic_config", |
| &frame_scheduler_min_predicted_frame_duration)) { |
| rapidjson::Document document; |
| document.Parse(frame_scheduler_min_predicted_frame_duration); |
| |
| if (document.HasMember("frame_scheduler_min_predicted_frame_duration_in_us")) { |
| auto& val = document["frame_scheduler_min_predicted_frame_duration_in_us"]; |
| FX_CHECK(val.IsInt()) << "min_preducted_frame_duration must be an integer"; |
| frame_scheduler_min_predicted_frame_duration_in_us = val.GetInt(); |
| FX_CHECK(frame_scheduler_min_predicted_frame_duration_in_us >= 0); |
| } |
| |
| FX_LOGS(INFO) << "Scenic min_predicted_frame_duration(us): " |
| << frame_scheduler_min_predicted_frame_duration_in_us; |
| } |
| return frame_scheduler_min_predicted_frame_duration_in_us > 0 |
| ? zx::usec(frame_scheduler_min_predicted_frame_duration_in_us) |
| : scheduling::DefaultFrameScheduler::kMinPredictedFrameDuration; |
| } |
| |
| } // 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)); |
| } |
| |
| 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, |
| fit::promise<ui_display::DisplayControllerHandles> dc_handles_promise, |
| fit::closure quit_callback) |
| : executor_(async_get_default_dispatcher()), |
| app_context_(std::move(app_context)), |
| // TODO(fxbug.dev/40997): 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))), |
| scenic_(std::make_shared<Scenic>(app_context_.get(), std::move(inspect_node), |
| [weak = std::weak_ptr<ShutdownManager>(shutdown_manager_)] { |
| if (auto strong = weak.lock()) { |
| strong->Shutdown( |
| LifecycleControllerImpl::kShutdownTimeout); |
| } |
| })), |
| 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::DefaultFlatlandPresenter>(async_get_default_dispatcher())), |
| flatland_manager_(std::make_shared<flatland::FlatlandManager>( |
| async_get_default_dispatcher(), flatland_presenter_, uber_struct_system_, link_system_, |
| std::vector<std::shared_ptr<flatland::BufferCollectionImporter>>({}))), |
| annotation_registry_(app_context_.get()), |
| lifecycle_controller_impl_(app_context_.get(), |
| std::weak_ptr<ShutdownManager>(shutdown_manager_)) { |
| FX_DCHECK(!device_watcher_); |
| |
| fit::bridge<escher::EscherUniquePtr> escher_bridge; |
| fit::bridge<std::shared_ptr<display::Display>> display_bridge; |
| |
| device_watcher_ = fsl::DeviceWatcher::Create( |
| kDependencyDir, [this, completer = std::move(escher_bridge.completer)]( |
| int dir_fd, std::string filename) mutable { |
| completer.complete_ok(gfx::GfxSystem::CreateEscher(app_context_.get())); |
| device_watcher_.reset(); |
| }); |
| |
| // Instantiate DisplayManager and schedule a task to inject the display controller into it, once |
| // it becomes available. |
| display_manager_ = std::make_unique<display::DisplayManager>( |
| [this, completer = std::move(display_bridge.completer)]() mutable { |
| completer.complete_ok(display_manager_->default_display_shared()); |
| }); |
| executor_.schedule_task( |
| dc_handles_promise.then([this](fit::result<ui_display::DisplayControllerHandles>& handles) { |
| display_manager_->BindDefaultDisplayController(std::move(handles.value().controller), |
| std::move(handles.value().dc_device)); |
| })); |
| |
| // Schedule a task to finish initialization once all promises have been completed. |
| auto p = |
| fit::join_promises(escher_bridge.consumer.promise(), display_bridge.consumer.promise()) |
| .and_then([this](std::tuple<fit::result<escher::EscherUniquePtr>, |
| fit::result<std::shared_ptr<display::Display>>>& results) { |
| InitializeServices(std::move(std::get<0>(results).value()), |
| std::move(std::get<1>(results).value())); |
| }); |
| |
| executor_.schedule_task(std::move(p)); |
| |
| #ifdef NDEBUG |
| // TODO(fxbug.dev/48596): 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 |
| |
| watchdog_ = std::make_unique<Watchdog>(kWatchdogWarningIntervalMs, kWatchdogTimeoutMs, |
| async_get_default_dispatcher()); |
| |
| // TODO(fxbug.dev/67206): this should be moved into FlatlandManager. |
| fit::function<void(fidl::InterfaceRequest<fuchsia::ui::scenic::internal::Flatland>)> handler = |
| fit::bind_member(flatland_manager_.get(), &flatland::FlatlandManager::CreateFlatland); |
| zx_status_t status = app_context_->outgoing()->AddPublicService(std::move(handler)); |
| FX_DCHECK(status == ZX_OK); |
| } |
| |
| 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(LifecycleControllerImpl::kShutdownTimeout); |
| return; |
| } |
| |
| if (!escher || !escher->device()) { |
| FX_LOGS(ERROR) << "No Vulkan on device, Graphics system exiting."; |
| shutdown_manager_->Shutdown(LifecycleControllerImpl::kShutdownTimeout); |
| return; |
| } |
| |
| escher_ = std::move(escher); |
| |
| std::shared_ptr<cobalt::CobaltLogger> cobalt_logger = cobalt::NewCobaltLoggerFromProjectId( |
| async_get_default_dispatcher(), app_context_->svc(), cobalt_registry::kProjectId); |
| if (!cobalt_logger) { |
| FX_LOGS(ERROR) << "CobaltLogger creation failed!"; |
| } |
| |
| // 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. |
| { |
| auto pipeline_builder = std::make_unique<escher::PipelineBuilder>(escher_->vk_device()); |
| pipeline_builder->set_log_pipeline_creation_callback( |
| [cobalt_logger](const vk::GraphicsPipelineCreateInfo* graphics_info, |
| const vk::ComputePipelineCreateInfo* compute_info) { |
| // TODO(fxbug.dev/49972): 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."; |
| |
| cobalt_logger->LogEvent( |
| cobalt_registry::kScenicRareEventMetricId, |
| cobalt_registry::ScenicRareEventMetricDimensionEvent_LazyPipelineCreation); |
| }); |
| escher_->set_pipeline_builder(std::move(pipeline_builder)); |
| } |
| |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[frame-scheduler]"); |
| frame_scheduler_ = std::make_shared<scheduling::DefaultFrameScheduler>( |
| display->vsync_timing(), |
| std::make_unique<scheduling::WindowedFramePredictor>( |
| GetMinimumPredictedFrameDuration(), |
| scheduling::DefaultFrameScheduler::kInitialRenderDuration, |
| scheduling::DefaultFrameScheduler::kInitialUpdateDuration), |
| scenic_->inspect_node()->CreateChild("FrameScheduler"), cobalt_logger); |
| } |
| |
| { |
| TRACE_DURATION("gfx", "App::InitializeServices[engine]"); |
| engine_ = |
| std::make_shared<gfx::Engine>(app_context_.get(), frame_scheduler_, escher_->GetWeakPtr(), |
| scenic_->inspect_node()->CreateChild("Engine")); |
| } |
| frame_scheduler_->SetFrameRenderer(engine_); |
| scenic_->SetFrameScheduler(frame_scheduler_); |
| annotation_registry_.InitializeWithGfxAnnotationManager(engine_->annotation_manager()); |
| |
| #ifdef SCENIC_ENABLE_GFX_SUBSYSTEM |
| auto gfx = |
| scenic_->RegisterSystem<gfx::GfxSystem>(engine_.get(), &sysmem_, display_manager_.get()); |
| FX_DCHECK(gfx); |
| |
| frame_scheduler_->AddSessionUpdater(scenic_); |
| scenic_->SetScreenshotDelegate(gfx.get()); |
| display_info_delegate_ = std::make_unique<DisplayInfoDelegate>(display); |
| scenic_->SetDisplayInfoDelegate(display_info_delegate_.get()); |
| #endif |
| |
| #ifdef SCENIC_ENABLE_INPUT_SUBSYSTEM |
| auto input = scenic_->RegisterSystem<input::InputSystem>(engine_->scene_graph()); |
| FX_DCHECK(input); |
| #endif |
| |
| flatland_presenter_->SetFrameScheduler(frame_scheduler_); |
| frame_scheduler_->AddSessionUpdater(flatland_manager_); |
| |
| // Create the snapshotter and pass it to scenic. |
| auto snapshotter = |
| std::make_unique<gfx::InternalSnapshotImpl>(engine_->scene_graph(), escher_->GetWeakPtr()); |
| scenic_->InitializeSnapshotService(std::move(snapshotter)); |
| |
| scenic_->SetInitialized(engine_->scene_graph()); |
| } |
| |
| } // namespace scenic_impl |