| // 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 <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/eventpair.h> |
| |
| #include <memory> |
| |
| #include <glm/gtc/constants.hpp> |
| #include <glm/gtc/matrix_access.hpp> |
| #include <glm/gtc/type_ptr.hpp> |
| |
| using fuchsia::ui::scenic::internal::ContentLink; |
| using fuchsia::ui::scenic::internal::ContentLinkStatus; |
| using fuchsia::ui::scenic::internal::ContentLinkToken; |
| using fuchsia::ui::scenic::internal::Error; |
| using fuchsia::ui::scenic::internal::GraphLink; |
| using fuchsia::ui::scenic::internal::GraphLinkToken; |
| using fuchsia::ui::scenic::internal::ImageProperties; |
| using fuchsia::ui::scenic::internal::LinkProperties; |
| using fuchsia::ui::scenic::internal::Orientation; |
| using fuchsia::ui::scenic::internal::Vec2; |
| |
| namespace flatland { |
| |
| namespace { |
| |
| GlobalImageId GenerateUniqueImageId() { |
| // This function will be called from multiple threads, and thus needs an atomic |
| // incrementor for the id. |
| static std::atomic<GlobalImageId> image_id = 0; |
| return ++image_id; |
| } |
| |
| } // namespace |
| |
| Flatland::Flatland( |
| scheduling::SessionId session_id, const std::shared_ptr<FlatlandPresenter>& flatland_presenter, |
| const std::shared_ptr<LinkSystem>& link_system, |
| const std::shared_ptr<UberStructSystem::UberStructQueue>& uber_struct_queue, |
| const std::vector<std::shared_ptr<BufferCollectionImporter>>& buffer_collection_importers, |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator) |
| : session_id_(session_id), |
| flatland_presenter_(flatland_presenter), |
| link_system_(link_system), |
| uber_struct_queue_(uber_struct_queue), |
| buffer_collection_importers_(buffer_collection_importers), |
| sysmem_allocator_(std::move(sysmem_allocator)), |
| transform_graph_(session_id_), |
| local_root_(transform_graph_.CreateTransform()) {} |
| |
| Flatland::~Flatland() { |
| // TODO(fxbug.dev/55374): consider if Link tokens should be returned or not. |
| } |
| |
| void Flatland::Present(zx_time_t requested_presentation_time, std::vector<zx::event> acquire_fences, |
| std::vector<zx::event> release_fences, PresentCallback callback) { |
| auto root_handle = GetRoot(); |
| |
| // TODO(fxbug.dev/40818): 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()); |
| |
| // TODO(fxbug.dev/36166): Once the 2D scene graph is externalized, don't commit changes if a cycle |
| // is detected. Instead, kill the channel and remove the sub-graph from the global graph. |
| failure_since_previous_present_ |= !data.cyclical_edges.empty(); |
| |
| if (!failure_since_previous_present_) { |
| 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. |
| std::vector<GlobalImageId> images_to_release; |
| for (const auto& dead_handle : data.dead_transforms) { |
| matrices_.erase(dead_handle); |
| |
| auto image_kv = image_metadatas_.find(dead_handle); |
| if (image_kv != image_metadatas_.end()) { |
| // The buffer collection metadata referenced by the image must still be alive. Decrement |
| // its usage count, which may trigger garbage collection if the collection has been |
| // released. |
| auto buffer_data_kv = buffer_usage_counts_.find(image_kv->second.collection_id); |
| FX_DCHECK(buffer_data_kv != buffer_usage_counts_.end()); |
| |
| --buffer_data_kv->second; |
| |
| // The importers will release the images in this vector at the same time they release |
| // their buffer collections. |
| images_to_release.push_back(image_kv->second.identifier); |
| |
| image_metadatas_.erase(image_kv); |
| } |
| } |
| |
| // Collect the list of deregistered buffer collections that are unreferenced by any Images, |
| // meaning they can be released from the BufferCollectionImporters. |
| std::vector<sysmem_util::GlobalBufferCollectionId> buffers_to_release; |
| for (auto released_iter = released_buffer_collection_ids_.begin(); |
| released_iter != released_buffer_collection_ids_.end();) { |
| const auto global_collection_id = *released_iter; |
| auto buffer_data_kv = buffer_usage_counts_.find(global_collection_id); |
| FX_DCHECK(buffer_data_kv != buffer_usage_counts_.end()); |
| |
| if (buffer_data_kv->second == 0) { |
| buffers_to_release.push_back(global_collection_id); |
| |
| // Delete local references to the sysmem_util::GlobalBufferCollectionId. |
| buffer_usage_counts_.erase(buffer_data_kv); |
| released_iter = released_buffer_collection_ids_.erase(released_iter); |
| } else { |
| ++released_iter; |
| } |
| } |
| |
| // If there are buffer collections and/or images ready for release, create a release fence for |
| // the current Present() and delay release until that fence is reached to ensure that the buffer |
| // collections and/or images are no longer referenced in any render data. |
| if (!images_to_release.empty() || !buffers_to_release.empty()) { |
| // Use the default dispatcher, which is the same one this Present() is running on. |
| auto dispatcher = async_get_default_dispatcher(); |
| |
| // Create a release fence specifically for the buffer collections and their images. |
| zx::event buffer_collection_and_image_release_fence; |
| zx_status_t status = zx::event::create(0, &buffer_collection_and_image_release_fence); |
| FX_DCHECK(status == ZX_OK); |
| |
| // Use a self-referencing async::WaitOnce to perform BufferCollectionImporter 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>(buffer_collection_and_image_release_fence.get(), |
| ZX_EVENT_SIGNALED); |
| status = wait->Begin( |
| dispatcher, |
| [copy_ref = wait, importer_ref = buffer_collection_importers_, buffers_to_release, |
| images_to_release](async_dispatcher_t*, async::WaitOnce*, zx_status_t status, |
| const zx_packet_signal_t* /*signal*/) mutable { |
| FX_DCHECK(status == ZX_OK); |
| |
| // Release images first, since they need to be released before we release their |
| // associated buffer collections. |
| for (auto& image_id : images_to_release) { |
| for (auto& importer : importer_ref) { |
| importer->ReleaseImage(image_id); |
| } |
| } |
| |
| // Now we can release the buffer collections. |
| for (const auto& global_collection_id : buffers_to_release) { |
| for (auto& importer : importer_ref) { |
| importer->ReleaseBufferCollection(global_collection_id); |
| } |
| } |
| }); |
| FX_DCHECK(status == ZX_OK); |
| |
| // Push the new release fence into the user-provided list. |
| release_fences.push_back(std::move(buffer_collection_and_image_release_fence)); |
| } |
| |
| auto uber_struct = std::make_unique<UberStruct>(); |
| uber_struct->local_topology = std::move(data.sorted_transforms); |
| |
| for (const auto& [link_id, child_link] : child_links_) { |
| LinkProperties initial_properties; |
| fidl::Clone(child_link.properties, &initial_properties); |
| uber_struct->link_properties[child_link.link.graph_handle] = std::move(initial_properties); |
| } |
| |
| for (const auto& [handle, matrix_data] : matrices_) { |
| uber_struct->local_matrices[handle] = matrix_data.GetMatrix(); |
| } |
| |
| uber_struct->images = image_metadatas_; |
| |
| // Register a Present to get the PresentId needed to queue the UberStruct. This happens before |
| // waiting on the acquire fences to indicate that a Present is pending. |
| auto present_id = flatland_presenter_->RegisterPresent(session_id_, std::move(release_fences)); |
| |
| // 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. |
| fence_queue_->QueueTask( |
| [this, present_id, requested_presentation_time, uber_struct = std::move(uber_struct), |
| link_operations = std::move(pending_link_operations_), |
| release_fences = std::move(release_fences)]() mutable { |
| // 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}); |
| |
| // 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(); |
| } |
| }, |
| std::move(acquire_fences)); |
| |
| // TODO(fxbug.dev/36161): Once present operations can be pipelined, this variable will change |
| // state based on the number of outstanding Present calls. Until then, this call is synchronous, |
| // and we can always return 1 as the number of remaining presents. |
| callback(fit::ok(num_presents_remaining_)); |
| } else { |
| // TODO(fxbug.dev/56869): determine if pending link operations should still be run here. |
| callback(fit::error(Error::BAD_OPERATION)); |
| } |
| |
| failure_since_previous_present_ = false; |
| } |
| |
| void Flatland::LinkToParent(GraphLinkToken token, fidl::InterfaceRequest<GraphLink> graph_link) { |
| // 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) << "LinkToParent failed, GraphLinkToken was invalid"; |
| ReportError(); |
| return; |
| } |
| |
| FX_DCHECK(link_system_); |
| |
| // 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 link_origin = transform_graph_.CreateTransform(); |
| LinkSystem::ParentLink link = |
| link_system_->CreateParentLink(std::move(token), std::move(graph_link), link_origin); |
| |
| // This portion of the method is feed-forward. The parent-child relationship between |
| // |link_origin| 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 (parent_link_.has_value()) { |
| bool child_removed = transform_graph_.RemoveChild(parent_link_->link_origin, local_root_); |
| FX_DCHECK(child_removed); |
| |
| bool transform_released = transform_graph_.ReleaseTransform(parent_link_->link_origin); |
| FX_DCHECK(transform_released); |
| |
| // Delay the destruction of the previous parent link until the next Present(). |
| pending_link_operations_.push_back( |
| [local_link = std::move(parent_link_)]() mutable { local_link.reset(); }); |
| } |
| |
| bool child_added = transform_graph_.AddChild(link.link_origin, local_root_); |
| FX_DCHECK(child_added); |
| parent_link_ = std::move(link); |
| } |
| |
| void Flatland::UnlinkFromParent( |
| fuchsia::ui::scenic::internal::Flatland::UnlinkFromParentCallback callback) { |
| if (!parent_link_) { |
| FX_LOGS(ERROR) << "UnlinkFromParent failed, no existing parent Link"; |
| ReportError(); |
| return; |
| } |
| |
| // Deleting the old ParentLink's Transform effectively changes this intance's root back to |
| // |local_root_|. |
| bool child_removed = transform_graph_.RemoveChild(parent_link_->link_origin, local_root_); |
| FX_DCHECK(child_removed); |
| |
| bool transform_released = transform_graph_.ReleaseTransform(parent_link_->link_origin); |
| 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 local_link = std::move(parent_link_.value()); |
| parent_link_.reset(); |
| |
| // Delay the actual destruction of the Link until the next Present(). |
| pending_link_operations_.push_back( |
| [local_link = std::move(local_link), callback = std::move(callback)]() mutable { |
| GraphLinkToken return_token; |
| |
| // If the link is still valid, return the original token. If not, create an orphaned |
| // zx::eventpair and return it since the ObjectLinker does not retain the orphaned token. |
| auto link_token = local_link.exporter.ReleaseToken(); |
| if (link_token.has_value()) { |
| return_token.value = zx::eventpair(std::move(link_token.value())); |
| } else { |
| // |peer_token| immediately falls out of scope, orphaning |return_token|. |
| zx::eventpair peer_token; |
| zx::eventpair::create(0, &return_token.value, &peer_token); |
| } |
| |
| callback(std::move(return_token)); |
| }); |
| } |
| |
| void Flatland::ClearGraph() { |
| // Clear user-defined mappings and local matrices. |
| transforms_.clear(); |
| content_handles_.clear(); |
| buffer_collection_ids_.clear(); |
| matrices_.clear(); |
| |
| // List all global buffer collection IDs as "released", which will trigger cleanup in Present(). |
| for (const auto& [collection_id, buffer_collection] : buffer_usage_counts_) { |
| released_buffer_collection_ids_.insert(collection_id); |
| } |
| |
| // 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 (parent_link_.has_value()) { |
| auto local_link = std::move(parent_link_); |
| parent_link_.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(child_links_); |
| child_links_.clear(); |
| |
| pending_link_operations_.push_back( |
| [local_links = std::move(local_links)]() mutable { local_links.clear(); }); |
| } |
| |
| void Flatland::CreateTransform(TransformId transform_id) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "CreateTransform called with transform_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| if (transforms_.count(transform_id)) { |
| FX_LOGS(ERROR) << "CreateTransform called with pre-existing transform_id " << transform_id; |
| ReportError(); |
| return; |
| } |
| |
| TransformHandle handle = transform_graph_.CreateTransform(); |
| transforms_.insert({transform_id, handle}); |
| } |
| |
| void Flatland::SetTranslation(TransformId transform_id, Vec2 translation) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "SetTranslation called with transform_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "SetTranslation failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetTranslation(translation); |
| } |
| |
| void Flatland::SetOrientation(TransformId transform_id, Orientation orientation) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "SetOrientation called with transform_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "SetOrientation failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetOrientation(orientation); |
| } |
| |
| void Flatland::SetScale(TransformId transform_id, Vec2 scale) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "SetScale called with transform_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "SetScale failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| matrices_[transform_kv->second].SetScale(scale); |
| } |
| |
| void Flatland::AddChild(TransformId parent_transform_id, TransformId child_transform_id) { |
| if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "AddChild called with transform_id zero"; |
| ReportError(); |
| 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()) { |
| FX_LOGS(ERROR) << "AddChild failed, parent_transform_id " << parent_transform_id |
| << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| if (child_global_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "AddChild failed, child_transform_id " << child_transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| bool added = transform_graph_.AddChild(parent_global_kv->second, child_global_kv->second); |
| |
| if (!added) { |
| FX_LOGS(ERROR) << "AddChild failed, connection already exists between parent " |
| << parent_transform_id << " and child " << child_transform_id; |
| ReportError(); |
| } |
| } |
| |
| void Flatland::RemoveChild(TransformId parent_transform_id, TransformId child_transform_id) { |
| if (parent_transform_id == kInvalidId || child_transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "RemoveChild failed, transform_id " << parent_transform_id << " not found"; |
| ReportError(); |
| 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()) { |
| FX_LOGS(ERROR) << "RemoveChild failed, parent_transform_id " << parent_transform_id |
| << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| if (child_global_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "RemoveChild failed, child_transform_id " << child_transform_id |
| << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| bool removed = transform_graph_.RemoveChild(parent_global_kv->second, child_global_kv->second); |
| |
| if (!removed) { |
| FX_LOGS(ERROR) << "RemoveChild failed, connection between parent " << parent_transform_id |
| << " and child " << child_transform_id << " not found"; |
| ReportError(); |
| } |
| } |
| |
| void Flatland::SetRootTransform(TransformId transform_id) { |
| // SetRootTransform(0) is special -- it only clears the existing root transform. |
| if (transform_id == kInvalidId) { |
| transform_graph_.ClearChildren(local_root_); |
| return; |
| } |
| |
| auto global_kv = transforms_.find(transform_id); |
| if (global_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "SetRootTransform failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| transform_graph_.ClearChildren(local_root_); |
| |
| bool added = transform_graph_.AddChild(local_root_, global_kv->second); |
| FX_DCHECK(added); |
| } |
| |
| void Flatland::CreateLink(ContentId link_id, ContentLinkToken token, LinkProperties properties, |
| fidl::InterfaceRequest<ContentLink> content_link) { |
| // 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) << "CreateLink failed, ContentLinkToken was invalid"; |
| ReportError(); |
| return; |
| } |
| |
| if (!properties.has_logical_size()) { |
| FX_LOGS(ERROR) << "CreateLink must be provided a LinkProperties with a logical size"; |
| ReportError(); |
| return; |
| } |
| |
| auto logical_size = properties.logical_size(); |
| if (logical_size.x <= 0.f || logical_size.y <= 0.f) { |
| FX_LOGS(ERROR) << "CreateLink must be provided a logical size with positive X and Y values"; |
| ReportError(); |
| return; |
| } |
| |
| FX_DCHECK(link_system_); |
| |
| // The LinkProperties and ContentLinkImpl live on a handle from this Flatland instance. |
| auto graph_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 LinkProperties through |
| // the LinkSystem immediately, so the child can receive them as soon as possible. |
| LinkProperties initial_properties; |
| fidl::Clone(properties, &initial_properties); |
| LinkSystem::ChildLink link = link_system_->CreateChildLink( |
| std::move(token), std::move(initial_properties), std::move(content_link), graph_handle); |
| |
| if (link_id == 0) { |
| FX_LOGS(ERROR) << "CreateLink called with ContentId zero"; |
| ReportError(); |
| return; |
| } |
| |
| if (content_handles_.count(link_id)) { |
| FX_LOGS(ERROR) << "CreateLink called with existing ContentId " << link_id; |
| ReportError(); |
| return; |
| } |
| |
| // 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. |
| bool child_added = transform_graph_.AddChild(link.graph_handle, link.link_handle); |
| FX_DCHECK(child_added); |
| |
| // 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. |
| Vec2 size = properties.logical_size(); |
| |
| content_handles_[link_id] = link.graph_handle; |
| child_links_[link.graph_handle] = { |
| .link = std::move(link), .properties = std::move(properties), .size = std::move(size)}; |
| } |
| |
| void Flatland::RegisterBufferCollection( |
| BufferCollectionId collection_id, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| if (collection_id == 0) { |
| FX_LOGS(ERROR) << "RegisterBufferCollection called with collection_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| if (buffer_collection_ids_.count(collection_id)) { |
| FX_LOGS(ERROR) << "RegisterBufferCollection called with pre-existing collection_id " |
| << collection_id; |
| ReportError(); |
| return; |
| } |
| |
| if (!token.is_valid()) { |
| FX_LOGS(ERROR) << "Buffer collection token is not valid."; |
| ReportError(); |
| return; |
| } |
| |
| // Grab a new unique global buffer collection id. |
| auto global_collection_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| FX_DCHECK(!buffer_usage_counts_.count(global_collection_id)); |
| |
| // Create a token for each of the buffer collection importers and stick all |
| // of the tokens into an std::vector. |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr sync_token = token.BindSync(); |
| std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr> tokens; |
| for (uint32_t i = 0; i < buffer_collection_importers_.size() - 1; i++) { |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr extra_token; |
| zx_status_t status = |
| sync_token->Duplicate(std::numeric_limits<uint32_t>::max(), extra_token.NewRequest()); |
| FX_DCHECK(status == ZX_OK); |
| tokens.push_back(std::move(extra_token)); |
| } |
| tokens.push_back(std::move(sync_token)); |
| |
| // Loop over each of the importers and provide each of them with a token from the vector |
| // we created above. We declare the iterator |i| outside the loop to aid in cleanup if |
| // importing fails. |
| uint32_t i = 0; |
| for (i = 0; i < buffer_collection_importers_.size(); i++) { |
| auto importer = buffer_collection_importers_[i]; |
| auto result = importer->ImportBufferCollection(global_collection_id, sysmem_allocator_.get(), |
| std::move(tokens[i])); |
| // Exit the loop early if an importer fails to import the buffer collection. |
| if (!result) { |
| break; |
| } |
| } |
| |
| // If the iterator |i| isn't equal to the number of importers than we know that one of the |
| // importers has failed. |
| if (i < buffer_collection_importers_.size()) { |
| // We have to clean up the buffer collection from the importers where importation was |
| // successful. |
| for (uint32_t j = 0; j < i; j++) { |
| buffer_collection_importers_[j]->ReleaseBufferCollection(global_collection_id); |
| } |
| |
| FX_LOGS(ERROR) << "Failed to import the buffer collection to the BufferCollectionImporter."; |
| ReportError(); |
| return; |
| } |
| |
| buffer_collection_ids_[collection_id] = global_collection_id; |
| buffer_usage_counts_[global_collection_id] = 0u; |
| } |
| |
| void Flatland::CreateImage(ContentId image_id, BufferCollectionId collection_id, uint32_t vmo_index, |
| ImageProperties properties) { |
| if (image_id == 0) { |
| FX_LOGS(ERROR) << "CreateImage called with image_id 0"; |
| ReportError(); |
| return; |
| } |
| |
| if (content_handles_.count(image_id)) { |
| FX_LOGS(ERROR) << "CreateImage called with pre-existing image_id " << image_id; |
| ReportError(); |
| return; |
| } |
| |
| auto buffer_id_kv = buffer_collection_ids_.find(collection_id); |
| |
| if (buffer_id_kv == buffer_collection_ids_.end()) { |
| FX_LOGS(ERROR) << "CreateImage failed, collection_id " << collection_id << " not found."; |
| ReportError(); |
| return; |
| } |
| |
| const auto global_collection_id = buffer_id_kv->second; |
| |
| auto buffer_collection_kv = buffer_usage_counts_.find(global_collection_id); |
| FX_DCHECK(buffer_collection_kv != buffer_usage_counts_.end()); |
| |
| auto& buffer_usage_count = buffer_collection_kv->second; |
| |
| if (!properties.has_width()) { |
| FX_LOGS(ERROR) << "CreateImage failed, ImageProperties did not specify a width"; |
| ReportError(); |
| return; |
| } |
| |
| if (!properties.has_height()) { |
| FX_LOGS(ERROR) << "CreateImage failed, ImageProperties did not specify a height"; |
| ReportError(); |
| return; |
| } |
| |
| ImageMetadata metadata; |
| metadata.identifier = GenerateUniqueImageId(); |
| metadata.collection_id = global_collection_id; |
| metadata.vmo_idx = vmo_index; |
| metadata.width = properties.width(); |
| metadata.height = properties.height(); |
| |
| for (uint32_t i = 0; i < buffer_collection_importers_.size(); i++) { |
| auto& importer = buffer_collection_importers_[i]; |
| |
| // TODO(62240): Give more detailed errors. |
| auto result = importer->ImportImage(metadata); |
| 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]->ReleaseImage(metadata.identifier); |
| } |
| |
| FX_LOGS(ERROR) << "Importer could not import image."; |
| ReportError(); |
| 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_[image_id] = handle; |
| image_metadatas_[handle] = metadata; |
| |
| // Increment the buffer's usage count. |
| ++buffer_usage_count; |
| } |
| |
| void Flatland::SetContentOnTransform(ContentId content_id, TransformId transform_id) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "SetContentOnTransform called with transform_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "SetContentOnTransform failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| if (content_id == 0) { |
| transform_graph_.ClearPriorityChild(transform_kv->second); |
| return; |
| } |
| |
| auto handle_kv = content_handles_.find(content_id); |
| |
| if (handle_kv == content_handles_.end()) { |
| FX_LOGS(ERROR) << "SetContentOnTransform failed, content_id " << content_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| transform_graph_.SetPriorityChild(transform_kv->second, handle_kv->second); |
| } |
| |
| void Flatland::SetLinkProperties(ContentId link_id, LinkProperties properties) { |
| if (link_id == 0) { |
| FX_LOGS(ERROR) << "SetLinkProperties called with link_id zero."; |
| ReportError(); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(link_id); |
| |
| if (content_kv == content_handles_.end()) { |
| FX_LOGS(ERROR) << "SetLinkProperties failed, link_id " << link_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| auto link_kv = child_links_.find(content_kv->second); |
| |
| if (link_kv == child_links_.end()) { |
| FX_LOGS(ERROR) << "SetLinkProperties failed, content_id " << link_id << " is not a Link"; |
| ReportError(); |
| return; |
| } |
| |
| // Callers do not have to provide a new logical size on every call to SetLinkProperties, but if |
| // they do, it must have positive X and Y values. |
| if (properties.has_logical_size()) { |
| auto logical_size = properties.logical_size(); |
| if (logical_size.x <= 0.f || logical_size.y <= 0.f) { |
| FX_LOGS(ERROR) << "SetLinkProperties failed, logical_size components must be positive, " |
| << "given (" << logical_size.x << ", " << logical_size.y << ")"; |
| ReportError(); |
| return; |
| } |
| } else { |
| // Preserve the old logical size if no logical size was passed as an argument. The |
| // HangingGetHelper no-ops if no data changes, so if logical size is empty and no other |
| // properties changed, the hanging get won't fire. |
| properties.set_logical_size(link_kv->second.properties.logical_size()); |
| } |
| |
| FX_DCHECK(link_kv->second.link.importer.valid()); |
| |
| link_kv->second.properties = std::move(properties); |
| UpdateLinkScale(link_kv->second); |
| } |
| |
| void Flatland::SetLinkSize(ContentId link_id, Vec2 size) { |
| if (link_id == 0) { |
| FX_LOGS(ERROR) << "SetLinkSize called with link_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| if (size.x <= 0.f || size.y <= 0.f) { |
| FX_LOGS(ERROR) << "SetLinkSize failed, size components must be positive, given (" << size.x |
| << ", " << size.y << ")"; |
| ReportError(); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(link_id); |
| |
| if (content_kv == content_handles_.end()) { |
| FX_LOGS(ERROR) << "SetLinkSize failed, link_id " << link_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| auto link_kv = child_links_.find(content_kv->second); |
| |
| if (link_kv == child_links_.end()) { |
| FX_LOGS(ERROR) << "SetLinkSize failed, content_id " << link_id << " is not a Link"; |
| ReportError(); |
| return; |
| } |
| |
| FX_DCHECK(link_kv->second.link.importer.valid()); |
| |
| link_kv->second.size = std::move(size); |
| UpdateLinkScale(link_kv->second); |
| } |
| |
| void Flatland::ReleaseTransform(TransformId transform_id) { |
| if (transform_id == kInvalidId) { |
| FX_LOGS(ERROR) << "ReleaseTransform called with transform_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| auto transform_kv = transforms_.find(transform_id); |
| |
| if (transform_kv == transforms_.end()) { |
| FX_LOGS(ERROR) << "ReleaseTransform failed, transform_id " << transform_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| bool erased_from_graph = transform_graph_.ReleaseTransform(transform_kv->second); |
| FX_DCHECK(erased_from_graph); |
| transforms_.erase(transform_kv); |
| } |
| |
| void Flatland::ReleaseLink(ContentId link_id, |
| fuchsia::ui::scenic::internal::Flatland::ReleaseLinkCallback callback) { |
| if (link_id == 0) { |
| FX_LOGS(ERROR) << "ReleaseLink called with link_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(link_id); |
| |
| if (content_kv == content_handles_.end()) { |
| FX_LOGS(ERROR) << "ReleaseLink failed, link_id " << link_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| auto link_kv = child_links_.find(content_kv->second); |
| |
| if (link_kv == child_links_.end()) { |
| FX_LOGS(ERROR) << "ReleaseLink failed, content_id " << link_id << " is not a Link"; |
| ReportError(); |
| return; |
| } |
| |
| // Deleting the ChildLink's |graph_handle| effectively deletes the link from the local topology, |
| // even if the link object itself is not deleted. |
| bool child_removed = transform_graph_.RemoveChild(link_kv->second.link.graph_handle, |
| link_kv->second.link.link_handle); |
| FX_DCHECK(child_removed); |
| |
| bool content_released = transform_graph_.ReleaseTransform(link_kv->second.link.graph_handle); |
| FX_DCHECK(content_released); |
| |
| // Move the old child link into the delayed operation so that the ContentId is immeditely free |
| // for re-use, but it doesn't get deleted until after the new UberStruct is published. |
| auto child_link = std::move(link_kv->second); |
| child_links_.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( |
| [child_link = std::move(child_link), callback = std::move(callback)]() mutable { |
| ContentLinkToken return_token; |
| |
| // If the link is still valid, return the original token. If not, create an orphaned |
| // zx::eventpair and return it since the ObjectLinker does not retain the orphaned token. |
| auto link_token = child_link.link.importer.ReleaseToken(); |
| if (link_token.has_value()) { |
| return_token.value = zx::eventpair(std::move(link_token.value())); |
| } else { |
| // |peer_token| immediately falls out of scope, orphaning |return_token|. |
| zx::eventpair peer_token; |
| zx::eventpair::create(0, &return_token.value, &peer_token); |
| } |
| |
| callback(std::move(return_token)); |
| }); |
| } |
| |
| void Flatland::DeregisterBufferCollection(BufferCollectionId collection_id) { |
| if (collection_id == kInvalidId) { |
| FX_LOGS(ERROR) << "DeregisterBufferCollection called with collection_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| auto buffer_id_kv = buffer_collection_ids_.find(collection_id); |
| |
| if (buffer_id_kv == buffer_collection_ids_.end()) { |
| FX_LOGS(ERROR) << "DeregisterBufferCollection failed, collection_id " << collection_id |
| << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| auto global_collection_id = buffer_id_kv->second; |
| FX_DCHECK(buffer_usage_counts_.count(global_collection_id)); |
| |
| // Erase the user-facing mapping of the ID and queue the global ID for garbage collection. The |
| // actual buffer collection data will be cleared once all Images reference the collection are |
| // released and garbage collected. |
| buffer_collection_ids_.erase(buffer_id_kv); |
| released_buffer_collection_ids_.insert(global_collection_id); |
| } |
| |
| void Flatland::ReleaseImage(ContentId image_id) { |
| if (image_id == kInvalidId) { |
| FX_LOGS(ERROR) << "ReleaseImage called with image_id zero"; |
| ReportError(); |
| return; |
| } |
| |
| auto content_kv = content_handles_.find(image_id); |
| |
| if (content_kv == content_handles_.end()) { |
| FX_LOGS(ERROR) << "ReleaseImage failed, image_id " << image_id << " not found"; |
| ReportError(); |
| return; |
| } |
| |
| auto image_kv = image_metadatas_.find(content_kv->second); |
| |
| if (image_kv == image_metadatas_.end()) { |
| FX_LOGS(ERROR) << "ReleaseImage failed, content_id " << image_id << " is not an Image"; |
| ReportError(); |
| 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(image_id); |
| } |
| |
| TransformHandle Flatland::GetRoot() const { |
| return parent_link_ ? parent_link_->link_origin : local_root_; |
| } |
| |
| std::optional<TransformHandle> Flatland::GetContentHandle(ContentId content_id) const { |
| auto handle_kv = content_handles_.find(content_id); |
| if (handle_kv == content_handles_.end()) { |
| return std::nullopt; |
| } |
| return handle_kv->second; |
| } |
| |
| void Flatland::ReportError() { failure_since_previous_present_ = true; } |
| |
| void Flatland::UpdateLinkScale(const ChildLinkData& link_data) { |
| FX_DCHECK(link_data.properties.has_logical_size()); |
| |
| auto logical_size = link_data.properties.logical_size(); |
| matrices_[link_data.link.graph_handle].SetScale( |
| {link_data.size.x / logical_size.x, link_data.size.y / logical_size.y}); |
| } |
| |
| // MatrixData function implementations |
| |
| // static |
| float Flatland::MatrixData::GetOrientationAngle( |
| fuchsia::ui::scenic::internal::Orientation orientation) { |
| switch (orientation) { |
| case Orientation::CCW_0_DEGREES: |
| return 0.f; |
| case Orientation::CCW_90_DEGREES: |
| return glm::half_pi<float>(); |
| case Orientation::CCW_180_DEGREES: |
| return glm::pi<float>(); |
| case Orientation::CCW_270_DEGREES: |
| return glm::three_over_two_pi<float>(); |
| } |
| } |
| |
| void Flatland::MatrixData::SetTranslation(fuchsia::ui::scenic::internal::Vec2 translation) { |
| translation_.x = translation.x; |
| translation_.y = translation.y; |
| |
| RecomputeMatrix(); |
| } |
| |
| void Flatland::MatrixData::SetOrientation(fuchsia::ui::scenic::internal::Orientation orientation) { |
| angle_ = GetOrientationAngle(orientation); |
| |
| RecomputeMatrix(); |
| } |
| |
| void Flatland::MatrixData::SetScale(fuchsia::ui::scenic::internal::Vec2 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 |