blob: a4c2ae817fa62716482f4a46d09c3a6cfd1e3dd6 [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/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