| // Copyright 2019 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.h" |
| |
| #include <fidl/fuchsia.math/cpp/fidl.h> |
| #include <fidl/fuchsia.ui.composition/cpp/natural_ostream.h> |
| #include <fidl/fuchsia.ui.composition/cpp/natural_types.h> |
| #include <lib/async/default.h> |
| #include <lib/async/time.h> |
| #include <lib/fidl/cpp/hlcpp_conversion.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <lib/ui/scenic/cpp/view_identity.h> |
| #include <lib/zx/eventpair.h> |
| #include <limits.h> |
| #include <zircon/errors.h> |
| |
| #include <cstdint> |
| #include <functional> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/ui/scenic/lib/allocation/id.h" |
| #include "src/ui/scenic/lib/flatland/flatland_types.h" |
| #include "src/ui/scenic/lib/scheduling/id.h" |
| #include "src/ui/scenic/lib/utils/helpers.h" |
| #include "src/ui/scenic/lib/utils/logging.h" |
| #include "src/ui/scenic/lib/utils/validate_eventpair.h" |
| |
| #include <glm/gtc/constants.hpp> |
| #include <glm/gtc/matrix_access.hpp> |
| #include <glm/gtc/type_ptr.hpp> |
| |
| using fuchsia_math::RectF; |
| using fuchsia_math::SizeU; |
| using fuchsia_math::Vec; |
| using fuchsia_math::VecF; |
| using fuchsia_ui_composition::ChildViewStatus; |
| using fuchsia_ui_composition::ChildViewWatcher; |
| using fuchsia_ui_composition::FlatlandError; |
| using fuchsia_ui_composition::HitRegion; |
| using fuchsia_ui_composition::ImageProperties; |
| using fuchsia_ui_composition::OnNextFrameBeginValues; |
| using fuchsia_ui_composition::Orientation; |
| using fuchsia_ui_composition::ParentViewportWatcher; |
| using fuchsia_ui_composition::ViewportProperties; |
| using fuchsia_ui_views::ViewCreationToken; |
| using fuchsia_ui_views::ViewportCreationToken; |
| |
| namespace { |
| |
| // Handle floating point errors up to an epsilon for sample region calls. |
| void ClampIfNear(float* val, float difference) { |
| if (difference > 0.f && difference < 1e-3f) { |
| *val -= difference; |
| } |
| } |
| |
| std::optional<std::string> ValidateViewportProperties(const ViewportProperties& properties) { |
| if (properties.logical_size().has_value()) { |
| const auto& logical_size = properties.logical_size(); |
| if (logical_size->width() == 0 || logical_size->height() == 0) { |
| std::ostringstream stream; |
| stream << "Logical_size components must be positive, given (" << logical_size->width() << ", " |
| << logical_size->height() << ")"; |
| return stream.str(); |
| } |
| } |
| |
| if (properties.inset().has_value()) { |
| const auto inset = properties.inset(); |
| if (inset->top() < 0 || inset->right() < 0 || inset->bottom() < 0 || inset->left() < 0) { |
| std::ostringstream stream; |
| stream << "Inset components must be >= 0, given (" << inset->top() << ", " << inset->right() |
| << ", " << inset->bottom() << ", " << inset->left() << ")"; |
| return stream.str(); |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| void SetViewportPropertiesMissingDefaults(ViewportProperties& properties, |
| const fuchsia_math::SizeU& logical_size, |
| const fuchsia_math::Inset& inset) { |
| if (!properties.logical_size().has_value()) { |
| properties.logical_size(logical_size); |
| } |
| if (!properties.inset().has_value()) { |
| properties.inset(inset); |
| } |
| } |
| |
| } // namespace |
| |
| namespace flatland { |
| |
| std::shared_ptr<Flatland> Flatland::New( |
| std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, |
| fidl::ServerEnd<fuchsia_ui_composition::Flatland> server_end, scheduling::SessionId session_id, |
| std::function<void()> destroy_instance_function, |
| std::shared_ptr<FlatlandPresenter> flatland_presenter, std::shared_ptr<LinkSystem> link_system, |
| std::shared_ptr<UberStructSystem::UberStructQueue> uber_struct_queue, |
| const std::vector<std::shared_ptr<allocation::BufferCollectionImporter>>& |
| buffer_collection_importers, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_views::Focuser>, zx_koid_t)> |
| register_view_focuser, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_views::ViewRefFocused>, zx_koid_t)> |
| register_view_ref_focused, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_pointer::TouchSource>, zx_koid_t)> |
| register_touch_source, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_pointer::MouseSource>, zx_koid_t)> |
| register_mouse_source, |
| fuchsia_ui_composition::TrustedFlatlandConfig config) { |
| // clang-format off |
| auto flatland = std::shared_ptr<Flatland>(new Flatland( |
| dispatcher_holder, |
| session_id, |
| std::move(flatland_presenter), |
| std::move(link_system), |
| std::move(uber_struct_queue), |
| buffer_collection_importers, |
| std::move(register_view_focuser), |
| std::move(register_view_ref_focused), |
| std::move(register_touch_source), |
| std::move(register_mouse_source), |
| std::move(config))); |
| // clang-format on |
| |
| // Natural FIDL bindings must be created and deleted on the same thread that it handles messages. |
| async::PostTask(dispatcher_holder->dispatcher(), |
| [flatland, server_end = std::move(server_end), |
| destroy_instance_function = std::move(destroy_instance_function)]() mutable { |
| flatland->Bind(std::move(server_end), std::move(destroy_instance_function)); |
| }); |
| |
| return flatland; |
| } |
| |
| Flatland::Flatland(std::shared_ptr<utils::DispatcherHolder> dispatcher_holder, |
| scheduling::SessionId session_id, |
| std::shared_ptr<FlatlandPresenter> flatland_presenter, |
| std::shared_ptr<LinkSystem> link_system, |
| std::shared_ptr<UberStructSystem::UberStructQueue> uber_struct_queue, |
| const std::vector<std::shared_ptr<allocation::BufferCollectionImporter>>& |
| buffer_collection_importers, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_views::Focuser>, zx_koid_t)> |
| register_view_focuser, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_views::ViewRefFocused>, zx_koid_t)> |
| register_view_ref_focused, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_pointer::TouchSource>, zx_koid_t)> |
| register_touch_source, |
| fit::function<void(fidl::ServerEnd<fuchsia_ui_pointer::MouseSource>, zx_koid_t)> |
| register_mouse_source, |
| fuchsia_ui_composition::TrustedFlatlandConfig config) |
| : dispatcher_holder_(std::move(dispatcher_holder)), |
| session_id_(session_id), |
| present2_helper_([this](fuchsia_scenic_scheduling::FramePresentedInfo info) { |
| // If this callback is invoked, we know that `Present()` must have been called, and |
| // therefore also know that binding must have been completed, because otherwise `Present()` |
| // wouldn't have been called. |
| // |
| // Caveat: in Flatland unit tests, we invoke methods directly on the Flatland object, not |
| // via a FIDL client. It is conceivable that flakes might arise if the timing relationship |
| // with the scheduler changes. |
| if (this->binding_data_) { |
| this->binding_data_->SendOnFramePresented(std::move(info)); |
| } |
| }), |
| flatland_presenter_(std::move(flatland_presenter)), |
| link_system_(std::move(link_system)), |
| uber_struct_queue_(std::move(uber_struct_queue)), |
| buffer_collection_importers_(buffer_collection_importers), |
| transform_graph_(session_id_), |
| local_root_(transform_graph_.CreateTransform()), |
| error_reporter_(scenic_impl::ErrorReporter::DefaultUnique()), |
| images_to_release_(std::make_shared<std::unordered_set<allocation::GlobalImageId>>()), |
| register_view_focuser_(std::move(register_view_focuser)), |
| register_view_ref_focused_(std::move(register_view_ref_focused)), |
| register_touch_source_(std::move(register_touch_source)), |
| register_mouse_source_(std::move(register_mouse_source)), |
| config_(std::move(config)) { |
| FX_DCHECK(flatland_presenter_); |
| |
| FX_LOGS(INFO) << "Flatland NEW session_id=" << session_id_; |
| } |
| |
| void Flatland::Bind(fidl::ServerEnd<fuchsia_ui_composition::Flatland> server_end, |
| std::function<void()> destroy_instance_function) { |
| // Only called once, by the constructor. |
| FX_DCHECK(!binding_data_); |
| binding_data_ = |
| std::make_unique<BindingData>(this, dispatcher_holder_->dispatcher(), std::move(server_end), |
| std::move(destroy_instance_function)); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland session_id=" << session_id_ << " bound to FIDL channel."; |
| } |
| |
| Flatland::BindingData::BindingData(Flatland* flatland, async_dispatcher_t* dispatcher, |
| fidl::ServerEnd<fuchsia_ui_composition::Flatland> server_end, |
| std::function<void()> destroy_instance_function) |
| : binding_(dispatcher, std::move(server_end), flatland, std::mem_fn(&Flatland::OnFidlClosed)), |
| destroy_instance_function_(std::move(destroy_instance_function)) {} |
| |
| Flatland::BindingData::~BindingData() { destroy_instance_function_(); } |
| |
| void Flatland::BindingData::SendOnFramePresented( |
| fuchsia_scenic_scheduling::FramePresentedInfo info) { |
| auto result = fidl::SendEvent(binding_)->OnFramePresented( |
| fuchsia_ui_composition::FlatlandOnFramePresentedRequest(std::move(info))); |
| if (result.is_error()) { |
| auto& error = result.error_value().error(); |
| FX_LOGS(WARNING) << "SendOnFramePresented(): error while sending FIDL event: " << error.status() |
| << " " << error.status_string(); |
| } |
| } |
| |
| void Flatland::BindingData::SendOnNextFrameBegin(uint32_t additional_present_credits, |
| FuturePresentationInfos presentation_infos) { |
| OnNextFrameBeginValues values; |
| values.additional_present_credits(additional_present_credits); |
| values.future_presentation_infos(std::move(presentation_infos)); |
| |
| auto result = fidl::SendEvent(binding_)->OnNextFrameBegin( |
| fuchsia_ui_composition::FlatlandOnNextFrameBeginRequest(std::move(values))); |
| if (result.is_error()) { |
| auto& error = result.error_value().error(); |
| FX_LOGS(WARNING) << "SendOnNextFrameBegin(): error while sending FIDL event: " << error.status() |
| << " " << error.status_string(); |
| } |
| } |
| |
| void Flatland::BindingData::CloseConnection(FlatlandError error) { |
| // NOTE: there's no need to test the return values of OnError()/Cancel()/Close(). If they fail, |
| // the binding and waiter will be cleaned up anyway because we'll soon be destroyed (since |
| // destroy_instance_function_ has been or will be invoked). |
| |
| // Send the error to the client before closing the connection. |
| auto result = fidl::SendEvent(binding_)->OnError(error); |
| if (result.is_error()) { |
| auto& error = result.error_value().error(); |
| FX_LOGS(WARNING) << "CloseConnection(): error while sending FIDL event: " << error.status() |
| << " " << error.status_string(); |
| } |
| |
| // Immediately close the FIDL interface to prevent future requests. |
| binding_.Close(ZX_ERR_BAD_STATE); |
| } |
| |
| Flatland::~Flatland() { |
| // TODO(https://fxbug.dev/42132996): consider if Link tokens should be returned or not. |
| |
| // Clear the scene graph, then collect the images to release. |
| Clear(); |
| auto data = transform_graph_.ComputeAndCleanup(GetRoot(), std::numeric_limits<uint64_t>::max()); |
| |
| // We don't care about the images returned by `ProcessDeadTransforms()` because we want to release |
| // all the images in `images_to_release_`, which potentially includes some added by |
| // `ProcessDeadTransforms()`. |
| ProcessDeadTransforms(data); |
| FX_DCHECK(image_metadatas_.empty()); |
| |
| // If there are any images to release, set up a waiter, and pass the event-to-be-signaled to |
| // `FlatlandPresenter::RemoveSession`. This will schedule another frame and signal the event |
| // just like any other release fence. |
| std::optional<zx::event> image_release_fence; |
| if (!images_to_release_->empty()) { |
| zx::event evt = utils::CreateEvent(); |
| image_release_fence = utils::CopyEvent(evt); |
| |
| auto wait = std::make_shared<async::WaitOnce>(evt.get(), ZX_EVENT_SIGNALED); |
| zx_status_t status = wait->Begin( |
| dispatcher(), |
| [importer_refs = buffer_collection_importers_, images_to_release = images_to_release_, |
| // We keep several objects alive in the closure: |
| // - the dispatcher, which is about to be released by the Flatland and FlatlandManager. |
| // - the wait object keeps itself alive via the ref in this closure |
| // - the waited-upon fence event: we retain a copy of the handle to avoid reasoning about |
| // whether the FlatlandPresenter implementation will safely keep it alive. |
| keepalive_dispatcher = dispatcher_holder_, keepalive_wait = wait, |
| keepalive_evt = std::move(evt)](async_dispatcher_t*, async::WaitOnce*, zx_status_t status, |
| const zx_packet_signal_t* /*signal*/) mutable { |
| for (auto& image_id : *images_to_release) { |
| for (auto& importer : importer_refs) { |
| importer->ReleaseBufferImage(image_id); |
| } |
| } |
| images_to_release->clear(); |
| }); |
| FX_DCHECK(status == ZX_OK); |
| } |
| |
| // This will signal the release fence (if any) that we pass to it, and therefore enable the wait |
| // above to succeed. |
| flatland_presenter_->RemoveSession(session_id_, std::move(image_release_fence)); |
| |
| FX_LOGS(INFO) << "Flatland DESTROYED session_id=" << session_id_; |
| } |
| |
| void Flatland::Present(PresentRequest& request, PresentCompleter::Sync& completer) { |
| Present(std::move(request.args())); |
| } |
| |
| void Flatland::Present(fuchsia_ui_composition::PresentArgs args) { |
| // In Flatland unit tests, we invoke methods directly on this object, rather than using a FIDL |
| // client over a Zircon channel. In production situations, the channel is torn down at or before |
| // the time that `binding_data_` is destroyed, and therefore there will be no subsequent method |
| // invocations, including of `Present()`. |
| if (!binding_data_) { |
| FX_LOGS(WARNING) |
| << "Ignoring Flatland::Present() called after binding_data_ was destroyed in session: " |
| << session_id_ << "\nThis should not occur outside of unit tests."; |
| return; |
| } |
| |
| TRACE_DURATION("gfx", "Flatland::Present", "debug_name", TA_STRING(debug_name_.c_str())); |
| std::string per_app_tracing_name = "Flatland::PerAppPresent[" + debug_name_ + "]"; |
| TRACE_DURATION("gfx", per_app_tracing_name.c_str()); |
| TRACE_FLOW_END("gfx", per_app_tracing_name.c_str(), present_count_); |
| |
| ++present_count_; |
| |
| // Close any clients that had invalid operations on link protocols. |
| if (link_protocol_error_) { |
| const char* kError = "Link protocol error"; |
| FLATLAND_VERBOSE_LOG << "Flatland::Present() session_id=" << session_id_ |
| << " present_count=" << present_count_ |
| << " closing connection: " << kError; |
| error_reporter_->ERROR() << kError; |
| CloseConnection(FlatlandError::kBadHangingGet); |
| return; |
| } |
| |
| // Close any clients that call Present() without any present tokens. |
| if (present_credits_ == 0) { |
| const char* kError = "Out of present credits"; |
| FLATLAND_VERBOSE_LOG << "Flatland::Present() session_id=" << session_id_ |
| << " present_count=" << present_count_ |
| << " closing connection: " << kError; |
| error_reporter_->ERROR() << kError; |
| CloseConnection(FlatlandError::kNoPresentsRemaining); |
| return; |
| } |
| present_credits_--; |
| |
| // If any fields are missing, replace them with the default values. |
| if (!args.requested_presentation_time().has_value()) { |
| args.requested_presentation_time(0); |
| } |
| if (!args.release_fences().has_value()) { |
| args.release_fences(std::vector<zx::event>{}); |
| } |
| if (!args.acquire_fences().has_value()) { |
| args.acquire_fences(std::vector<zx::event>{}); |
| } |
| if (!args.unsquashable().has_value()) { |
| args.unsquashable(false); |
| } |
| |
| auto root_handle = GetRoot(); |
| |
| // TODO(https://fxbug.dev/42116832): Decide on a proper limit on compute time for topological |
| // sorting. |
| auto data = transform_graph_.ComputeAndCleanup(root_handle, std::numeric_limits<uint64_t>::max()); |
| FX_DCHECK(data.iterations != std::numeric_limits<uint64_t>::max()); |
| |
| // Don't commit changes if a cycle is detected. Instead, kill the channel and remove the sub-graph |
| // from the global graph (the latter is the responsibility of the manager that is notified by |
| // `BindingData::destroy_instance_function_`). |
| if (!data.cyclical_edges.empty()) { |
| FLATLAND_VERBOSE_LOG << "Flatland::Present() session_id=" << session_id_ |
| << " present_count=" << present_count_ |
| << " closing connection: Cycle was detected"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FX_DCHECK(data.sorted_transforms[0].handle == root_handle); |
| |
| // Cleanup released resources. Here we also collect the list of unused images so they can be |
| // released by the buffer collection importers. |
| auto images_to_release = ProcessDeadTransforms(data); |
| |
| // If there are images ready for release, create a release fence for the current Present() and |
| // delay release until that fence is reached to ensure that the images are no longer referenced |
| // in any render data. |
| if (!images_to_release.empty()) { |
| // Create a release fence specifically for the images. |
| zx::event image_release_fence; |
| zx_status_t status = zx::event::create(0, &image_release_fence); |
| FX_DCHECK(status == ZX_OK); |
| |
| // Use a self-referencing async::WaitOnce to perform ImageImporter deregistration. |
| // This is primarily so the handler does not have to live in the Flatland instance, which may |
| // be destroyed before the release fence is signaled. `WaitOnce` moves the handler to the stack |
| // prior to invoking it, so it is safe for the handler to delete the WaitOnce on exit. |
| // Specifically, we move the wait object into the lambda function via |copy_ref = wait| to |
| // ensure that the wait object lives. The callback will not trigger without this. |
| auto wait = std::make_shared<async::WaitOnce>(image_release_fence.get(), ZX_EVENT_SIGNALED); |
| status = wait->Begin( |
| dispatcher(), |
| [copy_ref = wait, importer_refs = buffer_collection_importers_, images_to_release, |
| all_images_to_release = images_to_release_, |
| session_id = session_id_](async_dispatcher_t*, async::WaitOnce*, zx_status_t status, |
| const zx_packet_signal_t* /*signal*/) mutable { |
| // The wait is canceled if the dispatcher is destroyed before the event is signaled. |
| // In this case, we expect the images to have already been released by the wait in the |
| // ~Flatland() destructor. |
| FX_DCHECK(status == ZX_OK || status == ZX_ERR_CANCELED) |
| << "status is: " << zx_status_get_string(status) << " (" << status << ")"; |
| if (status == ZX_ERR_CANCELED) { |
| FX_DCHECK(all_images_to_release->empty()); |
| return; |
| } |
| |
| for (auto& image_id : images_to_release) { |
| if (!all_images_to_release->erase(image_id)) { |
| // This is harmless, but typically shouldn't happen. The rare exception is a race |
| // when the Flatland session is being torn down, if this runs after the session is |
| // destroyed, but before the session's loop/thread is stopped. |
| FX_LOGS(WARNING) << "Flatland session << " << session_id |
| << " did not find expected image " << image_id.value() |
| << " in images_to_release_"; |
| continue; |
| } |
| |
| for (auto& importer : importer_refs) { |
| importer->ReleaseBufferImage(image_id); |
| } |
| } |
| }); |
| FX_DCHECK(status == ZX_OK) << "status is: " << status; |
| |
| // Push the new release fence into the user-provided list. |
| args.release_fences()->push_back(std::move(image_release_fence)); |
| } |
| |
| auto uber_struct = std::make_unique<UberStruct>(); |
| uber_struct->local_topology = std::move(data.sorted_transforms); |
| |
| for (const auto& [handle, matrix_data] : matrices_) { |
| uber_struct->local_matrices[handle] = matrix_data.GetMatrix(); |
| } |
| |
| for (const auto& [handle, sample_region] : image_sample_regions_) { |
| uber_struct->local_image_sample_regions[handle] = sample_region; |
| } |
| |
| for (const auto& [handle, opacity_value] : opacity_values_) { |
| uber_struct->local_opacity_values[handle] = opacity_value; |
| } |
| |
| for (const auto& [handle, clip_region] : clip_regions_) { |
| uber_struct->local_clip_regions[handle] = clip_region; |
| } |
| |
| for (const auto& [handle, hit_regions] : hit_regions_) { |
| uber_struct->local_hit_regions_map[handle] = hit_regions; |
| } |
| |
| // As per the default hit region policy, if the client has not explicitly set a hit region on the |
| // root, add a full screen one. |
| if (root_transform_.GetInstanceId() != 0 && |
| hit_regions_.find(root_transform_) == hit_regions_.end()) { |
| uber_struct->local_hit_regions_map[root_transform_] = {{flatland::HitRegion::Infinite()}}; |
| } |
| |
| uber_struct->images = image_metadatas_; |
| |
| if (link_to_parent_.has_value()) { |
| uber_struct->view_ref = link_to_parent_->view_ref; |
| } |
| |
| uber_struct->debug_name = debug_name_; |
| const zx::time_monotonic now(async_now(dispatcher())); |
| uber_struct->creation_time = now; |
| |
| // Obtain the PresentId which is needed to: |
| // - enqueue the UberStruct. |
| // - schedule a frame |
| // - notify client when the frame has been presented |
| auto present_id = scheduling::GetNextPresentId(); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::Present() session_id=" << session_id_ |
| << " present_count=" << present_count_ << " present_id=" << present_id; |
| |
| present2_helper_.RegisterPresent(present_id, /*present_received_time=*/now); |
| |
| // TODO(https://fxbug.dev/414450649): remove this, since it is a subset of the |
| // `scenic_session_present` flow. This will require updating trace-processing scripts. |
| TRACE_FLOW_BEGIN("gfx", "ScheduleUpdate", present_id); |
| |
| // TODO(https://fxbug.dev/414450649): this is load-bearing. The `scenic_session_present` passes |
| // through the same code, but TRACE_INSTAFLOW_* behaves differently because it encapsulates each |
| // flow event within its own instantaneous slice, therefore it can't implicitly hook up to other |
| // flows and thus become load-bearing. In this case, it is relied upon by: |
| // `//sdk/testing/sl4f/client/lib/src/trace_processing/metrics/flutter_frame_stats.dart` |
| auto kLoadBearingTraceNonce = TRACE_NONCE(); |
| TRACE_FLOW_BEGIN("gfx", "wait_for_fences", kLoadBearingTraceNonce); |
| |
| TRACE_INSTAFLOW_BEGIN("gfx", "scenic_session_present", "flatland_present", |
| SESSION_TRACE_ID(session_id_, present_id), "session_id", |
| TA_UINT64(session_id_), "present_id", TA_UINT64(present_id)); |
| |
| // Safe to capture |this| because the Flatland is guaranteed to outlive |fence_queue_|, |
| // Flatland is non-movable and FenceQueue does not fire closures after destruction. |
| // TODO(https://fxbug.dev/42156567): make the fences be the first arg, and the closure be the |
| // second. |
| auto task = |
| [this, present_id, requested_presentation_time = args.requested_presentation_time().value(), |
| unsquashable = args.unsquashable().value(), uber_struct = std::move(uber_struct), |
| link_operations = std::move(pending_link_operations_), |
| release_fences = std::move(*args.release_fences()), kLoadBearingTraceNonce]() mutable { |
| // 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", "scenic_impl::Session::ScheduleNextPresent", "session_id", |
| session_id_, "requested_presentation_time", requested_presentation_time); |
| |
| // TODO(https://fxbug.dev/414450649): Load-bearing. See discussion at flow start. |
| TRACE_FLOW_END("gfx", "wait_for_fences", kLoadBearingTraceNonce); |
| |
| TRACE_INSTAFLOW_STEP("gfx", "scenic_session_present", "acquire_fences_signaled", |
| SESSION_TRACE_ID(session_id_, present_id), "session_id", |
| TA_UINT64(session_id_), "present_id", TA_UINT64(present_id)); |
| |
| // Push the UberStruct, then schedule the associated Present that will eventually publish |
| // it to the InstanceMap used for rendering. |
| uber_struct_queue_->Push(present_id, std::move(uber_struct)); |
| flatland_presenter_->ScheduleUpdateForSession( |
| zx::time(requested_presentation_time), {session_id_, present_id}, unsquashable, |
| std::move(release_fences), config_.schedule_asap().value_or(false)); |
| |
| // Finalize Link destruction operations after publishing the new UberStruct. This |
| // ensures that any local Transforms referenced by the to-be-deleted Links are already |
| // removed from the now-published UberStruct. |
| for (auto& operation : link_operations) { |
| operation(); |
| } |
| }; |
| |
| if (config_.pass_acquire_fences().value_or(false)) { |
| task(); |
| } else { |
| fence_queue_->QueueTask(std::move(task), std::move(*args.acquire_fences())); |
| } |
| pending_link_operations_.clear(); |
| } |
| |
| void Flatland::CreateView(CreateViewRequest& request, CreateViewCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Flatland::CreateView", "debug_name", TA_STRING(debug_name_.c_str())); |
| CreateView(std::move(request.token()), std::move(request.parent_viewport_watcher())); |
| } |
| |
| void Flatland::CreateView( |
| fuchsia_ui_views::ViewCreationToken token, |
| fidl::ServerEnd<fuchsia_ui_composition::ParentViewportWatcher> parent_viewport_watcher) { |
| CreateViewHelper(std::move(token), std::move(parent_viewport_watcher), std::nullopt, |
| std::nullopt); |
| } |
| |
| void Flatland::CreateView2(CreateView2Request& request, CreateView2Completer::Sync& completer) { |
| TRACE_DURATION("gfx", "Flatland::CreateView2", "debug_name", TA_STRING(debug_name_.c_str())); |
| CreateView2(std::move(request.token()), std::move(request.view_identity()), |
| std::move(request.protocols()), std::move(request.parent_viewport_watcher())); |
| } |
| |
| void Flatland::CreateView2( |
| fuchsia_ui_views::ViewCreationToken token, |
| fuchsia_ui_views::ViewIdentityOnCreation view_identity, |
| fuchsia_ui_composition::ViewBoundProtocols protocols, |
| fidl::ServerEnd<fuchsia_ui_composition::ParentViewportWatcher> parent_viewport_watcher) { |
| CreateViewHelper(std::move(token), std::move(parent_viewport_watcher), std::move(view_identity), |
| std::move(protocols)); |
| } |
| |
| void Flatland::CreateViewHelper( |
| fuchsia_ui_views::ViewCreationToken token, |
| fidl::ServerEnd<fuchsia_ui_composition::ParentViewportWatcher> parent_viewport_watcher, |
| std::optional<fuchsia_ui_views::ViewIdentityOnCreation> view_identity, |
| std::optional<fuchsia_ui_composition::ViewBoundProtocols> protocols) { |
| // 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()) { |
| error_reporter_->ERROR() << "CreateView failed, ViewCreationToken was invalid"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (view_identity.has_value() && |
| !utils::validate_viewref(view_identity->view_ref_control(), view_identity->view_ref())) { |
| error_reporter_->ERROR() << "CreateView failed, ViewIdentityOnCreation was invalid"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FX_DCHECK(link_system_); |
| |
| if (protocols.has_value()) { |
| FX_DCHECK(view_identity.has_value()) << "required for view-bound protocols"; |
| RegisterViewBoundProtocols(std::move(*protocols), |
| utils::ExtractKoid(view_identity->view_ref())); |
| } |
| // This portion of the method is not feed forward. This makes it possible for clients to receive |
| // layout information before this operation has been presented. By initializing the link |
| // immediately, parents can inform children of layout changes, and child clients can perform |
| // layout decisions before their first call to Present(). |
| auto child_transform_handle = transform_graph_.CreateTransform(); |
| |
| LinkSystem::LinkToParent new_link_to_parent = link_system_->CreateLinkToParent( |
| dispatcher_holder_, fidl::NaturalToHLCPP(token), |
| view_identity.has_value() ? std::optional(fidl::NaturalToHLCPP(*view_identity)) |
| : std::nullopt, |
| fidl::NaturalToHLCPP(parent_viewport_watcher), child_transform_handle, |
| [ref = weak_from_this(), weak_dispatcher_holder = std::weak_ptr<utils::DispatcherHolder>( |
| dispatcher_holder_)](const std::string& error_log) { |
| if (auto dispatcher_holder = weak_dispatcher_holder.lock()) { |
| FX_CHECK(dispatcher_holder->dispatcher() == async_get_default_dispatcher()) |
| << "Link protocol error reported on the wrong dispatcher."; |
| } |
| if (auto impl = ref.lock()) |
| impl->ReportLinkProtocolError(error_log); |
| }); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::CreateView() session_id=" << session_id_ |
| << " link-attachment-point=" << child_transform_handle; |
| |
| // This portion of the method is feed-forward. The parent-child relationship between |
| // |child_transform_handle| and |local_root_| establishes the Transform hierarchy between the two |
| // instances, but the operation will not be visible until the next Present() call includes that |
| // topology. |
| if (link_to_parent_.has_value()) { |
| bool child_removed = |
| transform_graph_.RemoveChild(link_to_parent_->child_transform_handle, local_root_); |
| FX_DCHECK(child_removed); |
| |
| bool transform_released = |
| transform_graph_.ReleaseTransform(link_to_parent_->child_transform_handle); |
| FX_DCHECK(transform_released); |
| |
| // Delay the destruction of the previous parent link until the next Present(). |
| pending_link_operations_.push_back([old_link_to_parent = std::move(link_to_parent_)]() mutable { |
| old_link_to_parent.reset(); |
| }); |
| } |
| |
| { |
| const bool child_added = |
| transform_graph_.AddChild(new_link_to_parent.child_transform_handle, local_root_); |
| FX_DCHECK(child_added); |
| } |
| link_to_parent_ = std::move(new_link_to_parent); |
| } |
| |
| void Flatland::RegisterViewBoundProtocols(fuchsia_ui_composition::ViewBoundProtocols protocols, |
| const zx_koid_t view_ref_koid) { |
| FX_DCHECK(register_view_focuser_); |
| FX_DCHECK(register_view_ref_focused_); |
| FX_DCHECK(register_touch_source_); |
| FX_DCHECK(register_mouse_source_); |
| |
| if (protocols.view_focuser().has_value()) { |
| register_view_focuser_(std::move(*protocols.view_focuser()), view_ref_koid); |
| } |
| |
| if (protocols.view_ref_focused().has_value()) { |
| register_view_ref_focused_(std::move(*protocols.view_ref_focused()), view_ref_koid); |
| } |
| |
| if (protocols.touch_source().has_value()) { |
| register_touch_source_(std::move(*protocols.touch_source()), view_ref_koid); |
| } |
| |
| if (protocols.mouse_source().has_value()) { |
| register_mouse_source_(std::move(*protocols.mouse_source()), view_ref_koid); |
| } |
| } |
| |
| void Flatland::ReleaseView(ReleaseViewCompleter::Sync& completer) { ReleaseView(); } |
| |
| void Flatland::ReleaseView() { |
| FLATLAND_VERBOSE_LOG << "Flatland::ReleaseView() session_id=" << session_id_; |
| |
| if (!link_to_parent_) { |
| error_reporter_->ERROR() << "ReleaseView failed, no existing parent Link"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // Deleting the old LinkToParent's Transform effectively changes this intance's root back to |
| // |local_root_|. |
| bool child_removed = |
| transform_graph_.RemoveChild(link_to_parent_->child_transform_handle, local_root_); |
| FX_DCHECK(child_removed); |
| |
| bool transform_released = |
| transform_graph_.ReleaseTransform(link_to_parent_->child_transform_handle); |
| FX_DCHECK(transform_released); |
| |
| // Move the old parent link into the delayed operation so that it isn't taken into account when |
| // computing the local topology, but doesn't get deleted until after the new UberStruct is |
| // published. |
| auto old_link_to_parent = std::move(link_to_parent_.value()); |
| link_to_parent_.reset(); |
| |
| // Delay the actual destruction of the Link until the next Present(). |
| pending_link_operations_.push_back([old_link_to_parent = std::move(old_link_to_parent)]() {}); |
| } |
| |
| void Flatland::Clear(ClearCompleter::Sync& completer) { Clear(); } |
| |
| void Flatland::Clear() { |
| // Clear user-defined mappings and local matrices. |
| transforms_.clear(); |
| content_handles_.clear(); |
| matrices_.clear(); |
| |
| // We always preserve the link origin when clearing the graph. This call will place all other |
| // TransformHandles in the dead_transforms set in the next Present(), which will trigger cleanup |
| // of Images and BufferCollections. |
| transform_graph_.ResetGraph(local_root_); |
| |
| // If a parent Link exists, delay its destruction until Present(). |
| if (link_to_parent_.has_value()) { |
| auto local_link = std::move(link_to_parent_); |
| link_to_parent_.reset(); |
| |
| pending_link_operations_.push_back( |
| [local_link = std::move(local_link)]() mutable { local_link.reset(); }); |
| } |
| |
| // Delay destruction of all child Links until Present(). |
| auto local_links = std::move(links_to_children_); |
| links_to_children_.clear(); |
| |
| pending_link_operations_.push_back( |
| [local_links = std::move(local_links)]() mutable { local_links.clear(); }); |
| |
| debug_name_.clear(); |
| } |
| |
| void Flatland::CreateTransform(CreateTransformRequest& request, |
| CreateTransformCompleter::Sync& completer) { |
| CreateTransform(request.transform_id()); |
| } |
| |
| void Flatland::CreateTransform(TransformId transform_id) { |
| const uint64_t client_transform_id = transform_id.value(); |
| |
| if (client_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "CreateTransform called with transform_id=" << kInvalidId; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (transforms_.contains(client_transform_id)) { |
| error_reporter_->ERROR() << "CreateTransform called with pre-existing transform_id=" |
| << client_transform_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| TransformHandle transform_handle = transform_graph_.CreateTransform(); |
| FLATLAND_VERBOSE_LOG << "Flatland::CreateTransform() session_id=" << session_id_ |
| << " client_transform_id=" << client_transform_id |
| << " transform=" << transform_handle; |
| transforms_.insert({client_transform_id, transform_handle}); |
| } |
| |
| void Flatland::SetTranslation(SetTranslationRequest& request, |
| SetTranslationCompleter::Sync& completer) { |
| SetTranslation(request.transform_id(), request.translation()); |
| } |
| |
| void Flatland::SetTranslation(TransformId transform_identifier, fuchsia_math::Vec translation) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetTranslation() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " translation= " << translation; |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetTranslation called with transform_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetTranslation failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetTranslation(translation); |
| } |
| |
| void Flatland::SetOrientation(SetOrientationRequest& request, |
| SetOrientationCompleter::Sync& completer) { |
| SetOrientation(request.transform_id(), request.orientation()); |
| } |
| |
| void Flatland::SetOrientation(TransformId transform_identifier, |
| fuchsia_ui_composition::Orientation orientation) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetOrientation() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " orientation=" << orientation; |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetOrientation called with transform_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetOrientation failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetOrientation(orientation); |
| } |
| |
| void Flatland::SetScale(SetScaleRequest& request, SetScaleCompleter::Sync& completer) { |
| SetScale(request.transform_id(), request.scale()); |
| } |
| |
| void Flatland::SetScale(TransformId transform_identifier, fuchsia_math::VecF scale) { |
| const uint64_t transform_id = transform_identifier.value(); |
| const float scale_x = scale.x(); |
| const float scale_y = scale.y(); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetScale() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " scale=" << scale; |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetScale called with transform_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetScale failed, transform_id " << transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (scale_x == 0.f || scale_y == 0.f) { |
| error_reporter_->ERROR() << "SetScale failed, zero values not allowed (" << scale_x << ", " |
| << scale_y << " )."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (isinf(scale_x) || isinf(scale_y) || isnan(scale_x) || isnan(scale_y)) { |
| error_reporter_->ERROR() << "SetScale failed, invalid scale values (" << scale_x << ", " |
| << scale_y << " )."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetScale(scale); |
| } |
| |
| void Flatland::SetOpacity(SetOpacityRequest& request, SetOpacityCompleter::Sync& completer) { |
| SetOpacity(request.transform_id().value(), request.value()); |
| } |
| |
| void Flatland::SetOpacity(TransformId transform_identifier, float opacity) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetOpacity() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " opacity=" << opacity; |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetOpacity called with transform_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (isinf(opacity) || isnan(opacity)) { |
| error_reporter_->ERROR() << "SetOpacity failed, invalid opacity value " << opacity; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (opacity < 0.f || opacity > 1.f) { |
| error_reporter_->ERROR() << "Opacity value is not within valid range [0,1]."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetOpacity failed, transform_id " << transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // Erase the value from the map since we store 1.f implicity. |
| if (opacity == 1.f) { |
| opacity_values_.erase(transform_kv->second); |
| } else { |
| opacity_values_[transform_kv->second] = opacity; |
| } |
| } |
| |
| void Flatland::SetClipBoundary(SetClipBoundaryRequest& request, |
| SetClipBoundaryCompleter::Sync& completer) { |
| SetClipBoundary(request.transform_id(), std::move(request.rect())); |
| } |
| |
| void Flatland::SetClipBoundary(TransformId transform_identifier, |
| fidl::Box<fuchsia_math::Rect> bounds) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetClipBoundary called with transform_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetClipBoundary failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // If the optional bounds are empty, then remove them. |
| if (!bounds) { |
| FLATLAND_VERBOSE_LOG << "Flatland::SetClipBoundary() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " ... clearing clip region"; |
| clip_regions_.erase(transform_kv->second); |
| return; |
| } |
| |
| if (!TransformClipRegion::IsValid(*bounds)) { |
| error_reporter_->ERROR() << "SetClipBoundary failed, rectangle bounds overflow " << *bounds; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetClipBoundary() session_id=" << session_id_ |
| << " transform_id=" << transform_id << " rect=" << *bounds; |
| SetClipBoundaryInternal(transform_kv->second, TransformClipRegion::From(*bounds)); |
| } |
| |
| void Flatland::SetClipBoundaryInternal(TransformHandle handle, TransformClipRegion bounds) { |
| if (bounds.width() <= 0 || bounds.height() <= 0) { |
| error_reporter_->ERROR() << "SetClipBoundary failed, width/height must both be positive " |
| << "(" << bounds.width() << ", " << bounds.height() << ")"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| clip_regions_[handle] = bounds; |
| } |
| |
| std::vector<allocation::GlobalImageId> Flatland::ProcessDeadTransforms( |
| const TransformGraph::TopologyData& data) { |
| std::vector<allocation::GlobalImageId> images_to_release; |
| for (const auto& dead_handle : data.dead_transforms) { |
| matrices_.erase(dead_handle); |
| |
| // Gather all images corresponding to dead transforms. |
| auto image_kv = image_metadatas_.find(dead_handle); |
| if (image_kv != image_metadatas_.end()) { |
| const auto image_id = image_kv->second.identifier; |
| image_metadatas_.erase(image_kv); |
| |
| // FilledRects do not need to be released. |
| if (image_id == allocation::kInvalidImageId) |
| continue; |
| |
| // Remember all dead images so that we can release them in the destructor if necessary. |
| // Typically this won't be necessary: we'll release them as soon as it is safe (roughly, |
| // when the next present takes effect). |
| images_to_release_->insert(image_id); |
| |
| images_to_release.push_back(image_id); |
| } |
| } |
| |
| return images_to_release; |
| } |
| |
| void Flatland::AddChild(AddChildRequest& request, AddChildCompleter::Sync& completer) { |
| AddChild(request.parent_transform_id(), request.child_transform_id()); |
| } |
| |
| void Flatland::AddChild(TransformId parent_transform_identifier, |
| TransformId child_transform_identifier) { |
| const uint64_t parent_transform_id = parent_transform_identifier.value(); |
| const uint64_t child_transform_id = child_transform_identifier.value(); |
| |
| if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "AddChild called with transform_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto parent_global_kv = transforms_.find(parent_transform_id); |
| auto child_global_kv = transforms_.find(child_transform_id); |
| |
| if (parent_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "AddChild failed, parent_transform_id " << parent_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (child_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "AddChild failed, child_transform_id " << child_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| bool added = transform_graph_.AddChild(parent_global_kv->second, child_global_kv->second); |
| |
| if (!added) { |
| error_reporter_->ERROR() << "AddChild failed, connection already exists between parent " |
| << parent_transform_id << " and child " << child_transform_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| } |
| |
| void Flatland::RemoveChild(RemoveChildRequest& request, RemoveChildCompleter::Sync& completer) { |
| RemoveChild(request.parent_transform_id(), request.child_transform_id()); |
| } |
| |
| void Flatland::RemoveChild(TransformId parent_transform_identifier, |
| TransformId child_transform_identifier) { |
| const uint64_t parent_transform_id = parent_transform_identifier.value(); |
| const uint64_t child_transform_id = child_transform_identifier.value(); |
| |
| if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "RemoveChild failed, transform_id " << parent_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto parent_global_kv = transforms_.find(parent_transform_id); |
| auto child_global_kv = transforms_.find(child_transform_id); |
| |
| if (parent_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "RemoveChild failed, parent_transform_id " << parent_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (child_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "RemoveChild failed, child_transform_id " << child_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| bool removed = transform_graph_.RemoveChild(parent_global_kv->second, child_global_kv->second); |
| |
| if (!removed) { |
| error_reporter_->ERROR() << "RemoveChild failed, connection between parent " |
| << parent_transform_id << " and child " << child_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| } |
| |
| void Flatland::ReplaceChildren(ReplaceChildrenRequest& request, |
| ReplaceChildrenCompleter::Sync& completer) { |
| ReplaceChildren(request.parent_transform_id(), request.new_child_transform_ids()); |
| } |
| |
| void Flatland::ReplaceChildren(TransformId parent_transform_identifier, |
| const std::vector<TransformId>& new_child_transform_ids) { |
| const uint64_t parent_transform_id = parent_transform_identifier.value(); |
| if (parent_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReplaceChildren failed, parent transform_id " |
| << parent_transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto parent_global_kv = transforms_.find(parent_transform_id); |
| if (parent_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "ReplaceChildren failed, parent transform_id " |
| << parent_transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| std::vector<TransformHandle> children; |
| for (auto child_transform_identifier : new_child_transform_ids) { |
| const uint64_t child_transform_id = child_transform_identifier.value(); |
| if (child_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReplaceChildren failed, child transform_id " |
| << child_transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto child_global_kv = transforms_.find(child_transform_id); |
| if (child_global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "ReplaceChildren failed, child transform_id " |
| << child_transform_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| children.push_back(child_global_kv->second); |
| } |
| |
| bool replaced = transform_graph_.ReplaceChildren(parent_global_kv->second, children); |
| if (!replaced) { |
| error_reporter_->ERROR() |
| << "ReplaceChildren failed, cannot add duplicate children to the same parent: " |
| << parent_transform_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| } |
| |
| void Flatland::SetRootTransform(SetRootTransformRequest& request, |
| SetRootTransformCompleter::Sync& completer) { |
| SetRootTransform(request.transform_id()); |
| } |
| |
| void Flatland::SetRootTransform(TransformId transform_id) { |
| const uint64_t client_transform_id = transform_id.value(); |
| |
| // SetRootTransform(0) is special -- it only clears the existing root transform. |
| if (client_transform_id == kInvalidId) { |
| transform_graph_.ClearChildren(local_root_); |
| return; |
| } |
| |
| const auto global_kv = transforms_.find(client_transform_id); |
| if (global_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetRootTransform failed, transform_id " << client_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| transform_graph_.ClearChildren(local_root_); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetRootTransform() session_id=" << session_id_ |
| << " client_transform_id=" << client_transform_id |
| << " transform=" << global_kv->second; |
| |
| bool added = transform_graph_.AddChild(local_root_, global_kv->second); |
| FX_DCHECK(added); |
| |
| root_transform_ = global_kv->second; |
| } |
| |
| void Flatland::CreateViewport(CreateViewportRequest& request, |
| CreateViewportCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Flatland::CreateViewport", "debug_name", TA_STRING(debug_name_.c_str())); |
| |
| CreateViewport(request.viewport_id(), std::move(request.token()), std::move(request.properties()), |
| std::move(request.child_view_watcher())); |
| } |
| |
| void Flatland::CreateViewport( |
| ContentId viewport_id, fuchsia_ui_views::ViewportCreationToken token, |
| fuchsia_ui_composition::ViewportProperties properties, |
| fidl::ServerEnd<fuchsia_ui_composition::ChildViewWatcher> child_view_watcher) { |
| const uint64_t client_viewport_id = viewport_id.value(); |
| |
| // 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()) { |
| error_reporter_->ERROR() << "CreateViewport failed, ViewportCreationToken was invalid"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (!properties.logical_size().has_value()) { |
| error_reporter_->ERROR() |
| << "CreateViewport must be provided a ViewportProperties with a logical size"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (auto error = ValidateViewportProperties(properties)) { |
| error_reporter_->ERROR() << "CreateViewport failed: " << *error; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| SetViewportPropertiesMissingDefaults(properties, properties.logical_size().value(), |
| /*inset*/ {0, 0, 0, 0}); |
| |
| if (client_viewport_id == kInvalidId) { |
| error_reporter_->ERROR() << "CreateViewport called with ContentId zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (content_handles_.count(client_viewport_id)) { |
| error_reporter_->ERROR() << "CreateViewport called with existing ContentId " |
| << client_viewport_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FX_DCHECK(link_system_); |
| |
| // The ViewportProperties and ChildViewWatcherImpl live on a handle from this Flatland instance. |
| const auto parent_transform_handle = transform_graph_.CreateTransform(); |
| |
| // 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. |
| LinkSystem::LinkToChild link_to_child = link_system_->CreateLinkToChild( |
| dispatcher_holder_, fidl::NaturalToHLCPP(token), fidl::NaturalToHLCPP(properties), |
| fidl::NaturalToHLCPP(child_view_watcher), parent_transform_handle, |
| [ref = weak_from_this(), weak_dispatcher_holder = std::weak_ptr<utils::DispatcherHolder>( |
| dispatcher_holder_)](const std::string& error_log) { |
| if (auto dispatcher_holder = weak_dispatcher_holder.lock()) { |
| FX_CHECK(dispatcher_holder->dispatcher() == async_get_default_dispatcher()) |
| << "Link protocol error reported on the wrong dispatcher."; |
| } |
| if (auto impl = ref.lock()) |
| impl->ReportLinkProtocolError(error_log); |
| }); |
| |
| // This is the feed-forward portion of the method. Here, we add the link to the map, and |
| // initialize its layout with the desired properties. The Link will not actually result in |
| // additions to the Transform hierarchy until it is added to a Transform. |
| { |
| const bool child_added = transform_graph_.AddChild(link_to_child.parent_transform_handle, |
| link_to_child.internal_link_handle); |
| FX_DCHECK(child_added); |
| } |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::CreateViewport() session_id=" << session_id_ |
| << " client_viewport_id=" << client_viewport_id |
| << " parent_transform=" << link_to_child.parent_transform_handle |
| << " internal_link_handle=" << link_to_child.internal_link_handle; |
| |
| // Default the link size to the logical size, which is just an identity scale matrix, so |
| // that future logical size changes will result in the correct scale matrix. |
| const SizeU size = *properties.logical_size(); |
| |
| content_handles_[client_viewport_id] = link_to_child.parent_transform_handle; |
| links_to_children_[link_to_child.parent_transform_handle] = {.link = std::move(link_to_child), |
| .properties = std::move(properties)}; |
| |
| // Set clip bounds on the transform associated with the viewport content. |
| const int32_t width = static_cast<int32_t>(size.width()); |
| const int32_t height = static_cast<int32_t>(size.height()); |
| FX_DCHECK(width >= 0 && height >= 0) |
| << "Integer overflow. width=" << width << ", height=" << height; |
| SetClipBoundaryInternal(parent_transform_handle, |
| TransformClipRegion({.x = 0, .y = 0, .width = width, .height = height})); |
| } |
| |
| void Flatland::CreateImage(CreateImageRequest& request, CreateImageCompleter::Sync& completer) { |
| TRACE_DURATION("gfx", "Flatland::CreateImage", "debug_name", TA_STRING(debug_name_.c_str())); |
| |
| CreateImage(request.image_id(), std::move(request.import_token()), request.vmo_index(), |
| std::move(request.properties())); |
| } |
| |
| void Flatland::CreateImage(ContentId image_id, |
| fuchsia_ui_composition::BufferCollectionImportToken import_token, |
| uint32_t vmo_index, fuchsia_ui_composition::ImageProperties properties) { |
| const uint64_t client_image_id = image_id.value(); |
| |
| if (client_image_id == kInvalidId) { |
| error_reporter_->ERROR() << "CreateImage called with image_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (content_handles_.contains(client_image_id)) { |
| error_reporter_->ERROR() << "CreateImage called with pre-existing image_id " << client_image_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| const BufferCollectionId global_collection_id = fsl::GetRelatedKoid(import_token.value().get()); |
| |
| // Check if there is a valid peer. |
| if (global_collection_id == ZX_KOID_INVALID) { |
| error_reporter_->ERROR() << "CreateImage called with no valid export token"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (!properties.size().has_value()) { |
| error_reporter_->ERROR() << "CreateImage failed, ImageProperties did not specify size"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (!properties.size()->width()) { |
| error_reporter_->ERROR() << "CreateImage failed, ImageProperties did not specify a width"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (!properties.size()->height()) { |
| error_reporter_->ERROR() << "CreateImage failed, ImageProperties did not specify a height"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| allocation::ImageMetadata metadata; |
| metadata.identifier = allocation::GenerateUniqueImageId(); |
| metadata.collection_id = global_collection_id; |
| metadata.vmo_index = vmo_index; |
| metadata.width = properties.size()->width(); |
| metadata.height = properties.size()->height(); |
| metadata.blend_mode = BlendMode::kReplace(); |
| |
| for (uint32_t i = 0; i < buffer_collection_importers_.size(); i++) { |
| auto& importer = buffer_collection_importers_[i]; |
| |
| // TODO(https://fxbug.dev/42140615): Give more detailed errors. |
| auto result = |
| importer->ImportBufferImage(metadata, allocation::BufferCollectionUsage::kClientImage); |
| if (!result) { |
| // If this importer fails, we need to release the image from |
| // all of the importers that it passed on. Luckily we can do |
| // this right here instead of waiting for a fence since we know |
| // this image isn't being used by anything yet. |
| for (uint32_t j = 0; j < i; j++) { |
| buffer_collection_importers_[j]->ReleaseBufferImage(metadata.identifier); |
| } |
| |
| error_reporter_->ERROR() << "Importer could not import image."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| } |
| |
| // Now that we've successfully been able to import the image into the importers, |
| // we can now create a handle for it in the transform graph, and add the metadata |
| // to our map. |
| auto handle = transform_graph_.CreateTransform(); |
| content_handles_[client_image_id] = handle; |
| image_metadatas_[handle] = metadata; |
| |
| // Set the default sample region of the image to be the full image. |
| SetImageSampleRegion( |
| image_id, types::RectangleF({.x = 0, |
| .y = 0, |
| .width = static_cast<float>(properties.size()->width()), |
| .height = static_cast<float>(properties.size()->height())})); |
| |
| // Set the default destination region of the image to be the full image. |
| SetImageDestinationSize(image_id, properties.size().value()); |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::CreateImage() session_id=" << session_id_ |
| << " image_id=" << client_image_id << " handle=" << handle |
| << " size=" << properties.size()->width() << "x" |
| << properties.size()->height(); |
| } |
| |
| void Flatland::SetImageSampleRegion(SetImageSampleRegionRequest& request, |
| SetImageSampleRegionCompleter::Sync& completer) { |
| SetImageSampleRegion(request.image_id(), types::RectangleF::From(request.rect())); |
| } |
| |
| void Flatland::SetImageSampleRegion(ContentId image_id, types::RectangleF rect) { |
| if ((image_id.value()) == kInvalidId) { |
| error_reporter_->ERROR() << "SetImageSampleRegion called with content id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| const auto content_kv = content_handles_.find((image_id.value())); |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetImageSampleRegion called with non-existent image_id " |
| << (image_id.value()); |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| const auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "SetImageSampleRegion called on non-image content."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // The provided sample region needs to be within the bounds of the image. |
| { |
| const auto& metadata = image_kv->second; |
| const float image_width = static_cast<float>(metadata.width); |
| const float image_height = static_cast<float>(metadata.height); |
| // This clamping is required in cases where (x+width>image_width) or (y+height>image_height) |
| // by a small epsilon. The downstream code expects these numbers to be within the |
| // (image_width, image_height) limits, so we only clamp the positive differences. The root |
| // cause is the precision errors in floating point arithmetic when a client tries to calculate |
| // floats within pixel space. |
| // TODO(https://fxbug.dev/42082599): Remove floating point precision error checks and use |
| // uints instead. |
| float clamped_width = rect.width(); |
| float clamped_height = rect.height(); |
| ClampIfNear(&clamped_width, rect.x() + clamped_width - image_width); |
| ClampIfNear(&clamped_height, rect.y() + clamped_height - image_height); |
| if (rect.x() < 0.f || clamped_width < 0.f || (rect.x() + clamped_width) > image_width || |
| rect.y() < 0.f || clamped_height < 0.f || (rect.y() + clamped_height) > image_height) { |
| error_reporter_->ERROR() << "SetImageSampleRegion rect " << rect.x() << "," << rect.y() << "," |
| << clamped_width << "," << clamped_height |
| << " out of bounds for image (" << image_width << ", " |
| << image_height << ")"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| rect = ImageSampleRegion( |
| {.x = rect.x(), .y = rect.y(), .width = clamped_width, .height = clamped_height}); |
| } |
| |
| image_sample_regions_[content_kv->second] = rect; |
| } |
| |
| void Flatland::SetImageDestinationSize(SetImageDestinationSizeRequest& request, |
| SetImageDestinationSizeCompleter::Sync& completer) { |
| SetImageDestinationSize(request.image_id(), request.size()); |
| } |
| |
| void Flatland::SetImageDestinationSize(ContentId image_id, fuchsia_math::SizeU size) { |
| if (image_id.value() == kInvalidId) { |
| error_reporter_->ERROR() << "SetImageSize called with image_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(image_id.value()); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetImageSize called with non-existent image_id " |
| << image_id.value(); |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "SetImageSize called on non-image content " << image_id.value(); |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| matrices_[content_kv->second].SetScale( |
| {static_cast<float>(size.width()), static_cast<float>(size.height())}); |
| } |
| |
| void Flatland::SetImageBlendingFunction(SetImageBlendingFunctionRequest& request, |
| SetImageBlendingFunctionCompleter::Sync& completer) { |
| SetImageBlendMode(request.image_id(), BlendMode::From(request.blend_mode())); |
| } |
| |
| void Flatland::SetImageBlendMode(SetImageBlendModeRequest& request, |
| SetImageBlendModeCompleter::Sync& completer) { |
| SetImageBlendMode(request.image_id(), BlendMode::From(request.blend_mode())); |
| } |
| |
| void Flatland::SetImageBlendMode(ContentId image_identifier, BlendMode blend_mode) { |
| const uint64_t image_id = image_identifier.value(); |
| |
| if (image_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetImageBlendMode called with content id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(image_id); |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetImageBlendMode called with non-existent image_id " << image_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "SetImageBlendMode called on non-image content."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| image_kv->second.blend_mode = blend_mode; |
| } |
| |
| void Flatland::SetImageFlip(SetImageFlipRequest& request, SetImageFlipCompleter::Sync& completer) { |
| SetImageFlip(request.image_id(), request.flip()); |
| } |
| |
| void Flatland::SetImageFlip(ContentId image_identifier, fuchsia_ui_composition::ImageFlip flip) { |
| const uint64_t image_id = image_identifier.value(); |
| |
| if (image_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetImageFlip called with content id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(image_id); |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetImageFlip called with non-existent image_id " << image_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "SetImageFlip called on non-image content."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| image_kv->second.flip = flip; |
| } |
| |
| void Flatland::CreateFilledRect(CreateFilledRectRequest& request, |
| CreateFilledRectCompleter::Sync& completer) { |
| CreateFilledRect(request.rect_id()); |
| } |
| |
| void Flatland::CreateFilledRect(ContentId rect_identifier) { |
| const uint64_t rect_id = rect_identifier.value(); |
| |
| if (rect_id == kInvalidId) { |
| error_reporter_->ERROR() << "CreateFilledRect called with rect_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (content_handles_.count(rect_id)) { |
| error_reporter_->ERROR() << "CreateFilledRect called with pre-existing content id " << rect_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| allocation::ImageMetadata metadata; |
| // allocation::kInvalidImageId is overloaded in the renderer to signal that a |
| // default 1x1 white texture should be applied to this rectangle. |
| metadata.identifier = allocation::kInvalidImageId; |
| metadata.blend_mode = BlendMode::kReplace(); |
| |
| // Now that we've successfully been able to import the image into the importers, |
| // we can now create a handle for it in the transform graph, and add the metadata |
| // to our map. |
| auto handle = transform_graph_.CreateTransform(); |
| content_handles_[rect_id] = handle; |
| image_metadatas_[handle] = metadata; |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::CreateFilledRect() session_id=" << session_id_ |
| << " rect_id=" << rect_id << " handle=" << handle; |
| } |
| |
| void Flatland::SetSolidFill(SetSolidFillRequest& request, SetSolidFillCompleter::Sync& completer) { |
| SetSolidFill(request.rect_id(), request.color(), request.size()); |
| } |
| |
| void Flatland::SetSolidFill(ContentId rect_identifier, fuchsia_ui_composition::ColorRgba color, |
| fuchsia_math::SizeU size) { |
| const uint64_t rect_id = rect_identifier.value(); |
| |
| if (rect_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetSolidFill called with rect_id 0"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(rect_id); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetSolidFill called with non-existent rect_id " << rect_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "Missing metadata for rect with id " << rect_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (color.red() < 0.f || color.red() > 1.f || isinf(color.red()) || isnan(color.red()) || |
| color.green() < 0.f || color.green() > 1.f || isinf(color.green()) || isnan(color.green()) || |
| color.blue() < 0.f || color.blue() > 1.f || isinf(color.blue()) || isnan(color.blue()) || |
| color.alpha() < 0.f || color.alpha() > 1.f || isinf(color.alpha()) || isnan(color.alpha())) { |
| error_reporter_->ERROR() << "Invalid color channel(s) (" << color.red() << ", " << color.green() |
| << ", " << color.blue() << ", " << color.alpha() << ")"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetSolidFill() session_id=" << session_id_ |
| << " rect_id=" << rect_id << " handle=" << content_kv->second |
| << " rgba=" << color.red() << "," << color.green() << "," << color.blue() |
| << "," << color.alpha() << " size=" << size.width() << "x" << size.height(); |
| |
| image_kv->second.blend_mode = |
| color.alpha() < 1.f ? BlendMode::kPremultipliedAlpha() : BlendMode::kReplace(); |
| image_kv->second.collection_id = allocation::kInvalidId; |
| image_kv->second.identifier = allocation::kInvalidImageId; |
| image_kv->second.multiply_color = {color.red(), color.green(), color.blue(), color.alpha()}; |
| matrices_[content_kv->second].SetScale( |
| {static_cast<float>(size.width()), static_cast<float>(size.height())}); |
| } |
| |
| void Flatland::ReleaseFilledRect(ReleaseFilledRectRequest& request, |
| ReleaseFilledRectCompleter::Sync& completer) { |
| ReleaseFilledRect(request.rect_id()); |
| } |
| |
| void Flatland::ReleaseFilledRect(ContentId rect_identifier) { |
| const uint64_t rect_id = rect_identifier.value(); |
| |
| if (rect_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReleaseFilledRect called with rect_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(rect_id); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "ReleaseFilledRect failed, rect_id " << rect_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "ReleaseFilledRect failed, content_id " << rect_id |
| << " has no metadata."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| bool erased_from_graph = transform_graph_.ReleaseTransform(content_kv->second); |
| FX_DCHECK(erased_from_graph); |
| |
| // Even though the handle is released, it may still be referenced by client Transforms. The |
| // image_metadatas_ map preserves the entry until it shows up in the dead_transforms list. |
| content_handles_.erase(rect_id); |
| } |
| |
| void Flatland::SetImageOpacity(SetImageOpacityRequest& request, |
| SetImageOpacityCompleter::Sync& completer) { |
| SetImageOpacity(request.image_id(), request.val()); |
| } |
| |
| void Flatland::SetImageOpacity(ContentId image_identifier, float opacity) { |
| const uint64_t image_id = image_identifier.value(); |
| |
| if (image_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetImageOpacity called with invalid image_id"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(image_id); |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetImageOpacity called with non-existent image_id " << image_id; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "SetImageOpacity called on non-rectangle content."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto& metadata = image_kv->second; |
| if (metadata.identifier == allocation::kInvalidImageId) { |
| error_reporter_->ERROR() << "SetImageOpacity called on solid color content."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (opacity < 0.f || opacity > 1.f) { |
| error_reporter_->ERROR() << "Opacity value is not within valid range [0,1]."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // Opacity is stored as the alpha channel of the multiply color. |
| metadata.multiply_color[3] = opacity; |
| } |
| |
| void Flatland::SetHitRegions(SetHitRegionsRequest& request, |
| SetHitRegionsCompleter::Sync& completer) { |
| SetHitRegions(request.transform_id(), std::move(request.regions())); |
| } |
| |
| void Flatland::SetHitRegions(TransformId transform_identifier, |
| std::vector<fuchsia_ui_composition::HitRegion> regions) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetHitRegions called with invalid transform ID"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetHitRegions failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| // Validate |regions|. |
| for (auto& region : regions) { |
| auto& rect = region.region(); |
| |
| if (rect.width() < 0 || rect.height() < 0) { |
| error_reporter_->ERROR() << "SetHitRegions failed, contains invalid (negative) dimensions: (" |
| << rect.width() << "," << rect.height() << ")"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| } |
| |
| // Reformat into internal type. |
| std::vector<flatland::HitRegion> list; |
| list.reserve(regions.size()); |
| for (auto& region : regions) { |
| list.emplace_back(types::RectangleF::From(region.region()), |
| fidl::NaturalToHLCPP(region.hit_test())); |
| } |
| hit_regions_[transform_kv->second] = list; |
| } |
| |
| void Flatland::SetInfiniteHitRegion(SetInfiniteHitRegionRequest& request, |
| SetInfiniteHitRegionCompleter::Sync& completer) { |
| SetInfiniteHitRegion(request.transform_id(), std::move(request.hit_test())); |
| } |
| |
| void Flatland::SetInfiniteHitRegion(TransformId transform_identifier, |
| fuchsia_ui_composition::HitTestInteraction hit_test) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetHitRegions called with invalid transform ID"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetHitRegions failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| hit_regions_[transform_kv->second] = { |
| flatland::HitRegion::Infinite(fidl::NaturalToHLCPP(hit_test))}; |
| } |
| |
| void Flatland::SetContent(SetContentRequest& request, SetContentCompleter::Sync& completer) { |
| SetContent(request.transform_id(), request.content_id()); |
| } |
| |
| void Flatland::SetContent(TransformId transform_identifier, ContentId content_identifier) { |
| const uint64_t client_transform_id = transform_identifier.value(); |
| const uint64_t client_content_id = content_identifier.value(); |
| |
| if (client_transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetContent called with transform_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(client_transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "SetContent failed, transform_id " << client_transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| if (client_content_id == kInvalidId) { |
| transform_graph_.ClearPriorityChild(transform_kv->second); |
| FLATLAND_VERBOSE_LOG << "Flatland::SetContent() session_id=" << session_id_ |
| << " client_transform_id=" << client_transform_id |
| << " transform=" << transform_kv->second << " ... cleared content."; |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(client_content_id); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetContent failed, content_id " << client_content_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::SetContent() session_id=" << session_id_ |
| << " client_transform_id=" << client_transform_id |
| << " transform=" << transform_kv->second |
| << " client_content_id=" << client_content_id |
| << " content=" << content_kv->second; |
| |
| transform_graph_.SetPriorityChild(transform_kv->second, content_kv->second); |
| } |
| |
| void Flatland::SetViewportProperties(SetViewportPropertiesRequest& request, |
| SetViewportPropertiesCompleter::Sync& completer) { |
| SetViewportProperties(request.viewport_id(), std::move(request.properties())); |
| } |
| |
| void Flatland::SetViewportProperties(ContentId viewport_id, |
| fuchsia_ui_composition::ViewportProperties properties) { |
| const uint64_t link_id = viewport_id.value(); |
| |
| if (link_id == kInvalidId) { |
| error_reporter_->ERROR() << "SetViewportProperties called with link_id zero."; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| const auto content_kv = content_handles_.find(link_id); |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "SetViewportProperties failed, link_id " << link_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| const auto viewport_handle = content_kv->second; |
| |
| auto link_kv = links_to_children_.find(viewport_handle); |
| if (link_kv == links_to_children_.end()) { |
| error_reporter_->ERROR() << "SetViewportProperties failed, content_id " << link_id |
| << " is not a Link"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| LinkToChildData& link_data = link_kv->second; |
| if (!link_data.link.importer.valid()) { |
| // Other side of the Viewport has been invalidated and the Viewport should be released. |
| // Calling SetViewportProperties() must still be allowed since the client may not have gotten |
| // the destruction message yet. |
| return; |
| } |
| |
| if (auto error = ValidateViewportProperties(properties)) { |
| error_reporter_->ERROR() << "SetViewportProperties failed: " << *error; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FX_DCHECK(link_data.properties.logical_size().has_value()); |
| FX_DCHECK(link_data.properties.inset().has_value()); |
| SetViewportPropertiesMissingDefaults(properties, *link_data.properties.logical_size(), |
| *link_data.properties.inset()); |
| |
| // Update the clip boundaries when the properties change. |
| const int32_t width = static_cast<int32_t>(properties.logical_size()->width()); |
| const int32_t height = static_cast<int32_t>(properties.logical_size()->height()); |
| FX_DCHECK(width >= 0 && height >= 0) |
| << "Integer overflow. width=" << width << ", height=" << height; |
| SetClipBoundaryInternal(viewport_handle, |
| TransformClipRegion({.x = 0, .y = 0, .width = width, .height = height})); |
| |
| link_data.properties = properties; |
| link_system_->UpdateViewportPropertiesFor(viewport_handle, fidl::NaturalToHLCPP(properties)); |
| } |
| |
| void Flatland::ReleaseTransform(ReleaseTransformRequest& request, |
| ReleaseTransformCompleter::Sync& completer) { |
| ReleaseTransform(request.transform_id()); |
| } |
| |
| void Flatland::ReleaseTransform(TransformId transform_identifier) { |
| const uint64_t transform_id = transform_identifier.value(); |
| |
| if (transform_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReleaseTransform called with transform_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| error_reporter_->ERROR() << "ReleaseTransform failed, transform_id " << transform_id |
| << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| bool erased_from_graph = transform_graph_.ReleaseTransform(transform_kv->second); |
| FX_DCHECK(erased_from_graph); |
| transforms_.erase(transform_kv); |
| } |
| |
| void Flatland::ReleaseViewport(ReleaseViewportRequest& request, |
| ReleaseViewportCompleter::Sync& completer) { |
| ReleaseViewport( |
| request.viewport_id(), |
| [completer = completer.ToAsync()](fuchsia_ui_views::ViewportCreationToken token) mutable { |
| completer.Reply(std::move(token)); |
| }); |
| } |
| |
| void Flatland::ReleaseViewport( |
| ContentId viewport_id, fit::function<void(fuchsia_ui_views::ViewportCreationToken)> completer) { |
| const uint64_t link_id = viewport_id.value(); |
| |
| if (link_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReleaseViewport called with link_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(link_id); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "ReleaseViewport failed, link_id " << link_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto link_kv = links_to_children_.find(content_kv->second); |
| |
| if (link_kv == links_to_children_.end()) { |
| error_reporter_->ERROR() << "ReleaseViewport failed, content_id " << link_id |
| << " is not a Link"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| LinkToChildData& link_data = link_kv->second; |
| |
| // Deleting the LinkToChild's |parent_transform_handle| effectively deletes the link from |
| // the local topology, even if the link object itself is not deleted. |
| { |
| const bool child_removed = transform_graph_.RemoveChild(link_data.link.parent_transform_handle, |
| link_data.link.internal_link_handle); |
| FX_DCHECK(child_removed); |
| const bool content_released = |
| transform_graph_.ReleaseTransform(link_data.link.parent_transform_handle); |
| FX_DCHECK(content_released); |
| } |
| |
| // Move the old child link into the delayed operation so that the ContentId is immediately free |
| // for re-use, but it doesn't get deleted until after the new UberStruct is published. |
| auto link_to_child = std::move(link_data); |
| links_to_children_.erase(content_kv->second); |
| content_handles_.erase(content_kv); |
| |
| // Delay the actual destruction of the link until the next Present(). |
| pending_link_operations_.push_back( |
| [link_to_child = std::move(link_to_child), completer = std::move(completer)]() mutable { |
| ViewportCreationToken return_token; |
| |
| // If the link is still valid, return the original token. If not, create an orphaned |
| // zx::channel and return it since the ObjectLinker does not retain the orphaned token. |
| auto link_token = link_to_child.link.importer.ReleaseToken(); |
| if (link_token.has_value()) { |
| return_token.value(zx::channel(std::move(link_token.value()))); |
| } else { |
| // |peer_token| immediately falls out of scope, orphaning |return_token|. |
| zx::channel peer_token; |
| zx::channel::create(0, &return_token.value(), &peer_token); |
| } |
| |
| completer(std::move(return_token)); |
| }); |
| } |
| |
| void Flatland::ReleaseImage(ReleaseImageRequest& request, ReleaseImageCompleter::Sync& completer) { |
| ReleaseImage(request.image_id()); |
| } |
| |
| void Flatland::ReleaseImage(ContentId image_id) { |
| const uint64_t client_image_id = image_id.value(); |
| |
| if (client_image_id == kInvalidId) { |
| error_reporter_->ERROR() << "ReleaseImage called with image_id zero"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(client_image_id); |
| |
| if (content_kv == content_handles_.end()) { |
| error_reporter_->ERROR() << "ReleaseImage failed, image_id " << client_image_id << " not found"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| |
| if (image_kv == image_metadatas_.end()) { |
| error_reporter_->ERROR() << "ReleaseImage failed, content_id " << client_image_id |
| << " is not an Image"; |
| CloseConnection(FlatlandError::kBadOperation); |
| return; |
| } |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::ReleaseImage() session_id=" << session_id_ |
| << " client_image_id=" << client_image_id |
| << " image_handle=" << content_kv->second; |
| |
| bool erased_from_graph = transform_graph_.ReleaseTransform(content_kv->second); |
| FX_DCHECK(erased_from_graph); |
| |
| // Even though the handle is released, it may still be referenced by client Transforms. The |
| // image_metadatas_ map preserves the entry until it shows up in the dead_transforms list. |
| content_handles_.erase(client_image_id); |
| } |
| |
| void Flatland::SetDebugName(SetDebugNameRequest& request, SetDebugNameCompleter::Sync& completer) { |
| std::string name(std::move(request.name())); |
| |
| TRACE_INSTANT("gfx", "Flatland::SetDebugName()", TRACE_SCOPE_PROCESS, "name", |
| TA_STRING(name.c_str())); |
| |
| SetDebugName(std::move(name)); |
| } |
| |
| void Flatland::SetDebugName(std::string name) { |
| std::stringstream stream; |
| if (!name.empty()) |
| stream << "Flatland client(" << name << "): "; |
| |
| FX_LOGS(INFO) << "Flatland::SetDebugName() session_id=" << session_id_ << " name: " << name; |
| |
| error_reporter_->SetPrefix(stream.str()); |
| debug_name_ = std::move(name); |
| } |
| |
| void Flatland::OnNextFrameBegin(uint32_t additional_present_credits, |
| FuturePresentationInfos presentation_infos) { |
| TRACE_DURATION("gfx", "Flatland::OnNextFrameBegin"); |
| present_credits_ += additional_present_credits; |
| |
| FLATLAND_VERBOSE_LOG << "Flatland::OnNextFrameBegin() session_id=" << session_id_ |
| << " additional_present_credits=" << additional_present_credits; |
| |
| // Only send an `OnNextFrameBegin` event if the client has at least one present credit. It is |
| // guaranteed that this won't stall clients because the current policy is to always return |
| // present tokens upon processing them. If and when a new policy is adopted, we should take care |
| // to ensure this guarantee is upheld. |
| if (binding_data_) { |
| if (present_credits_ > 0) { |
| binding_data_->SendOnNextFrameBegin(additional_present_credits, |
| std::move(presentation_infos)); |
| } |
| } |
| } |
| |
| void Flatland::OnFramePresented(const std::map<scheduling::PresentId, zx::time>& latched_times, |
| scheduling::PresentTimestamps present_times) { |
| TRACE_DURATION("gfx", "Flatland::OnFramePresented"); |
| |
| #if USE_FLATLAND_VERBOSE_LOGGING |
| std::ostringstream oss; |
| oss << "Flatland::OnFramePresented() session_id=" << session_id_; |
| for (const auto& [present_id, time] : latched_times) { |
| oss << "\n present_id=" << present_id << " time=" << time.get(); |
| } |
| FLATLAND_VERBOSE_LOG << oss.str(); |
| #endif |
| |
| for (const auto& [present_id, latched_timestamp] : latched_times) { |
| TRACE_INSTAFLOW_END("gfx", "scenic_session_present", "flatland_frame_presented", |
| SESSION_TRACE_ID(session_id_, present_id), "session_id", |
| TA_UINT64(session_id_), "present_id", TA_UINT64(present_id), "latched_time", |
| TA_INT64(latched_timestamp.get()), "presentation_time", |
| TA_INT64(present_times.presented_time.get())); |
| } |
| |
| // TODO(https://fxbug.dev/42141795): remove `num_presents_allowed` from this event. Clients |
| // should obtain this information from OnPresentProcessedValues(). |
| present2_helper_.OnPresented(latched_times, present_times, /*num_presents_allowed=*/0); |
| } |
| |
| TransformHandle Flatland::GetRoot() const { |
| return link_to_parent_ ? link_to_parent_->child_transform_handle : local_root_; |
| } |
| |
| std::optional<TransformHandle> Flatland::GetContentHandle(ContentId content_id) const { |
| auto handle_kv = content_handles_.find(content_id.value()); |
| if (handle_kv == content_handles_.end()) { |
| return std::nullopt; |
| } |
| return handle_kv->second; |
| } |
| |
| // For validating properties associated with transforms in tests only. If |transform_id| does not |
| // exist for this Flatland instance, returns std::nullopt. |
| std::optional<TransformHandle> Flatland::GetTransformHandle(TransformId transform_id) const { |
| auto handle_kv = transforms_.find(transform_id.value()); |
| if (handle_kv == transforms_.end()) { |
| return std::nullopt; |
| } |
| return handle_kv->second; |
| } |
| |
| void Flatland::SetErrorReporter(std::unique_ptr<scenic_impl::ErrorReporter> error_reporter) { |
| error_reporter_ = std::move(error_reporter); |
| } |
| |
| scheduling::SessionId Flatland::GetSessionId() const { return session_id_; } |
| |
| void Flatland::OnFidlClosed(fidl::UnbindInfo unbind_info) { |
| if (!unbind_info.is_user_initiated()) { |
| FX_LOGS(INFO) << "Flatland::OnFidlClosed() session_id=" << session_id_ |
| << " because: " << unbind_info.FormatDescription(); |
| } |
| |
| binding_data_.reset(); |
| } |
| |
| void Flatland::ReportLinkProtocolError(const std::string& error_log) { |
| error_reporter_->ERROR() << error_log; |
| link_protocol_error_ = true; |
| } |
| |
| void Flatland::CloseConnection(FlatlandError error) { |
| if (binding_data_) { |
| FX_LOGS(INFO) << "Flatland::CloseConnection() session_id=" << session_id_ |
| << " error: " << error; |
| |
| binding_data_->CloseConnection(error); |
| binding_data_.reset(); |
| } |
| } |
| |
| // MatrixData function implementations |
| |
| // static |
| float Flatland::MatrixData::GetOrientationAngle(fuchsia_ui_composition::Orientation orientation) { |
| // The matrix is specified in view-space coordinates, in which the +y axis points downwards (not |
| // upwards). Rotations which are specified as counter-clockwise must actually occur in a |
| // clockwise fashion in this coordinate space (a vector on the +x axis rotates towards -y axis |
| // to give the appearance of a counter-clockwise rotation). |
| switch (orientation) { |
| case Orientation::kCcw0Degrees: |
| return 0.f; |
| case Orientation::kCcw90Degrees: |
| return -glm::half_pi<float>(); |
| case Orientation::kCcw180Degrees: |
| return -glm::pi<float>(); |
| case Orientation::kCcw270Degrees: |
| return -glm::three_over_two_pi<float>(); |
| } |
| } |
| |
| void Flatland::MatrixData::SetTranslation(Vec translation) { |
| translation_.x = static_cast<float>(translation.x()); |
| translation_.y = static_cast<float>(translation.y()); |
| RecomputeMatrix(); |
| } |
| |
| void Flatland::MatrixData::SetOrientation(fuchsia_ui_composition::Orientation orientation) { |
| angle_ = GetOrientationAngle(orientation); |
| |
| RecomputeMatrix(); |
| } |
| |
| void Flatland::MatrixData::SetScale(VecF scale) { |
| scale_.x = scale.x(); |
| scale_.y = scale.y(); |
| RecomputeMatrix(); |
| } |
| |
| void Flatland::MatrixData::RecomputeMatrix() { |
| // Manually compose the matrix rather than use glm transformations since the order of operations |
| // is always the same. glm matrices are column-major. |
| float* vals = static_cast<float*>(glm::value_ptr(matrix_)); |
| |
| // Translation in the third column. |
| vals[6] = translation_.x; |
| vals[7] = translation_.y; |
| |
| // Rotation and scale combined into the first two columns. |
| const float s = sin(angle_); |
| const float c = cos(angle_); |
| |
| vals[0] = c * scale_.x; |
| vals[1] = s * scale_.x; |
| vals[3] = -1.f * s * scale_.y; |
| vals[4] = c * scale_.y; |
| } |
| |
| glm::mat3 Flatland::MatrixData::GetMatrix() const { return matrix_; } |
| |
| } // namespace flatland |