| // Copyright 2021 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/lib/flatland/flatland_display.h" |
| |
| #include <lib/async/default.h> |
| #include <lib/ui/scenic/cpp/view_identity.h> |
| |
| #include "src/ui/scenic/lib/utils/logging.h" |
| |
| #include <glm/glm.hpp> |
| #include <glm/gtc/constants.hpp> |
| #include <glm/gtc/type_ptr.hpp> |
| #include <glm/gtx/matrix_transform_2d.hpp> |
| |
| static void ReportError() { |
| // TODO(https://fxbug.dev/42157006): investigate how to propagate errors back to clients. |
| // TODO(https://fxbug.dev/42156567): OK to crash until we have error propagation? Probably so: better that |
| // clients get feedback that they've done something wrong. These are all in-tree clients, anyway. |
| FX_CHECK(false) << "Crashing on error."; |
| } |
| |
| using fuchsia::ui::composition::ChildViewWatcher; |
| using fuchsia::ui::composition::ViewportProperties; |
| using fuchsia::ui::views::ViewportCreationToken; |
| |
| namespace flatland { |
| |
| std::shared_ptr<FlatlandDisplay> FlatlandDisplay::New( |
| std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, |
| fidl::InterfaceRequest<fuchsia::ui::composition::FlatlandDisplay> request, |
| scheduling::SessionId session_id, std::shared_ptr<scenic_impl::display::Display> display, |
| std::function<void()> destroy_display_function, |
| std::shared_ptr<FlatlandPresenter> flatland_presenter, std::shared_ptr<LinkSystem> link_system, |
| std::shared_ptr<UberStructSystem::UberStructQueue> uber_struct_queue) { |
| return std::shared_ptr<FlatlandDisplay>( |
| new FlatlandDisplay(std::move(dispatcher_holder), std::move(request), session_id, display, |
| std::move(destroy_display_function), std::move(flatland_presenter), |
| std::move(link_system), std::move(uber_struct_queue))); |
| } |
| |
| FlatlandDisplay::FlatlandDisplay( |
| std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, |
| fidl::InterfaceRequest<fuchsia::ui::composition::FlatlandDisplay> request, |
| scheduling::SessionId session_id, std::shared_ptr<scenic_impl::display::Display> display, |
| std::function<void()> destroy_display_function, |
| std::shared_ptr<FlatlandPresenter> flatland_presenter, std::shared_ptr<LinkSystem> link_system, |
| std::shared_ptr<UberStructSystem::UberStructQueue> uber_struct_queue) |
| : dispatcher_holder_(std::move(dispatcher_holder)), |
| binding_(this, std::move(request), dispatcher_holder_->dispatcher()), |
| session_id_(session_id), |
| display_(std::move(display)), |
| destroy_display_function_(std::move(destroy_display_function)), |
| peer_closed_waiter_(binding_.channel().get(), ZX_CHANNEL_PEER_CLOSED), |
| flatland_presenter_(std::move(flatland_presenter)), |
| link_system_(std::move(link_system)), |
| uber_struct_queue_(std::move(uber_struct_queue)), |
| transform_graph_(session_id), |
| root_transform_(transform_graph_.CreateTransform()) { |
| FX_DCHECK(session_id_); |
| FX_DCHECK(flatland_presenter_); |
| FX_DCHECK(link_system_); |
| FX_DCHECK(uber_struct_queue_); |
| |
| zx_status_t status = peer_closed_waiter_.Begin( |
| dispatcher(), |
| [this](async_dispatcher_t* dispatcher, async::WaitOnce* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { destroy_display_function_(); }); |
| FX_DCHECK(status == ZX_OK); |
| |
| FLATLAND_VERBOSE_LOG << "FlatlandDisplay new with ID: " << session_id_; |
| } |
| |
| FlatlandDisplay::~FlatlandDisplay() { |
| // Unlike Flatland, FlatlandDisplay doesn't manage any client images, therefore doesn't need to |
| // pass a release fence to RemoveSession(). |
| flatland_presenter_->RemoveSession(session_id_, std::nullopt); |
| } |
| |
| void FlatlandDisplay::SetContent(ViewportCreationToken token, |
| fidl::InterfaceRequest<ChildViewWatcher> child_view_watcher) { |
| // Attempting to link with an invalid token will never succeed, so its better to fail early and |
| // immediately close the link connection. |
| if (!token.value.is_valid()) { |
| FX_LOGS(ERROR) << "CreateViewport failed, ViewportCreationToken was invalid"; |
| ReportError(); |
| return; |
| } |
| |
| // TODO(https://fxbug.dev/42156567): In order to replace content from a previous call to SetContent(), need |
| // to detach from root_transform_, and otherwise clean up. Flatland::ReleaseViewport() seems like |
| // a good place to start. |
| FX_CHECK(link_to_child_.parent_transform_handle == flatland::TransformHandle()) |
| << "Replacing FlatlandDisplay content is not yet supported."; |
| |
| auto child_transform = transform_graph_.CreateTransform(); |
| |
| const auto device_pixel_ratio = display_->device_pixel_ratio(); |
| FX_LOGS(INFO) << "Device Pixel Ratio: " << device_pixel_ratio.x << "x" << device_pixel_ratio.y; |
| |
| ViewportProperties properties; |
| { |
| // To calculate the logical size, we need to divide the physical pixel size by the DPR. |
| properties.set_logical_size({ |
| .width = static_cast<uint32_t>(static_cast<float>(display_->width_in_px()) / |
| device_pixel_ratio.x), |
| .height = static_cast<uint32_t>(static_cast<float>(display_->height_in_px()) / |
| device_pixel_ratio.y), |
| }); |
| properties.set_inset({0, 0, 0, 0}); |
| } |
| |
| // We can initialize the Link importer immediately, since no state changes actually occur before |
| // the feed-forward portion of this method. We also forward the initial ViewportProperties through |
| // the LinkSystem immediately, so the child can receive them as soon as possible. |
| // NOTE: clients won't receive CONNECTED_TO_DISPLAY until LinkSystem::UpdateLinks() is called, |
| // typically during rendering. |
| link_to_child_ = link_system_->CreateLinkToChild( |
| dispatcher_holder_, std::move(token), std::move(properties), std::move(child_view_watcher), |
| child_transform, |
| [ref = weak_from_this(), |
| dispatcher_holder = dispatcher_holder_](const std::string& error_log) { |
| FX_CHECK(dispatcher_holder->dispatcher() == async_get_default_dispatcher()) |
| << "Link protocol error reported on the wrong dispatcher."; |
| |
| // TODO(https://fxbug.dev/42157006): FlatlandDisplay currently has no way to notify clients of errors. |
| FX_LOGS(ERROR) << "FlatlandDisplay illegal client usage: " << error_log; |
| }); |
| FX_CHECK(child_transform == link_to_child_.parent_transform_handle); |
| |
| // This is the feed-forward portion of the method, i.e. the part which enqueues an updated |
| // UberStruct. |
| bool child_added; |
| child_added = transform_graph_.AddChild(link_to_child_.parent_transform_handle, |
| link_to_child_.internal_link_handle); |
| FX_DCHECK(child_added); |
| child_added = transform_graph_.AddChild(root_transform_, link_to_child_.parent_transform_handle); |
| FX_DCHECK(child_added); |
| |
| // TODO(https://fxbug.dev/42156567): given this fixed topology, we probably don't need to use |
| // ComputeAndCleanup(), we can just stamp something out based on a fixed template. |
| // TODO(https://fxbug.dev/42116832): Decide on a proper limit on compute time for topological sorting. |
| auto data = |
| transform_graph_.ComputeAndCleanup(root_transform_, std::numeric_limits<uint64_t>::max()); |
| FX_DCHECK(data.iterations != std::numeric_limits<uint64_t>::max()); |
| FX_DCHECK(data.sorted_transforms[0].handle == root_transform_); |
| |
| auto uber_struct = std::make_unique<UberStruct>(); |
| uber_struct->debug_name = "FlatlandDisplay"; |
| uber_struct->local_topology = std::move(data.sorted_transforms); |
| uber_struct->local_clip_regions[link_to_child_.parent_transform_handle] = { |
| .x = 0, |
| .y = 0, |
| .width = static_cast<int32_t>(display_->width_in_px()), |
| .height = static_cast<int32_t>(display_->height_in_px()), |
| }; |
| |
| // By scaling the local matrix of the uberstruct here by the device pixel ratio, we ensure that |
| // internally, the sizes of content on flatland instances that are hooked up to this display are |
| // scaled up by the appropriate amount. |
| uber_struct->local_matrices[link_to_child_.parent_transform_handle] = |
| glm::scale(glm::mat3(), device_pixel_ratio); |
| |
| auto present_id = scheduling::GetNextPresentId(); |
| uber_struct_queue_->Push(present_id, std::move(uber_struct)); |
| flatland_presenter_->ScheduleUpdateForSession(zx::time(0), {session_id_, present_id}, |
| /*squashable=*/true, /*release_fences=*/{}); |
| |
| // TODO(https://fxbug.dev/42156567): Flatland::Present() does: |
| // for (auto& operation : link_operations) { operation(); } |
| // ... we should do something similar? I believe that this will become necessary when we allow |
| // SetContent() to be called more than once. |
| } |
| |
| void FlatlandDisplay::SetDevicePixelRatio(fuchsia::math::VecF device_pixel_ratio) { |
| if (device_pixel_ratio.x < 1.f || device_pixel_ratio.y < 1.f) { |
| FX_LOGS(ERROR) << "SetDevicePixelRatio failed, device_pixel_ratio is invalid"; |
| ReportError(); |
| return; |
| } |
| |
| display_->set_device_pixel_ratio({device_pixel_ratio.x, device_pixel_ratio.y}); |
| } |
| |
| } // namespace flatland |