blob: ad3d0af0a19baa3a17f152e132ac05d899abc04d [file] [log] [blame]
// 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/async/time.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/eventpair.h>
#include <memory>
#include "src/lib/fsl/handles/object_info.h"
#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 {
Flatland::Flatland(async_dispatcher_t* dispatcher,
fidl::InterfaceRequest<fuchsia::ui::scenic::internal::Flatland> request,
scheduling::SessionId session_id,
std::function<void()> destroy_instance_function,
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<allocation::BufferCollectionImporter>>&
buffer_collection_importers)
: dispatcher_(dispatcher),
binding_(this, std::move(request), dispatcher_),
session_id_(session_id),
destroy_instance_function_(std::move(destroy_instance_function)),
peer_closed_waiter_(binding_.channel().get(), ZX_CHANNEL_PEER_CLOSED),
present2_helper_([this](fuchsia::scenic::scheduling::FramePresentedInfo info) {
if (binding_.is_bound()) {
binding_.events().OnFramePresented(std::move(info));
}
}),
flatland_presenter_(flatland_presenter),
link_system_(link_system),
uber_struct_queue_(uber_struct_queue),
buffer_collection_importers_(buffer_collection_importers),
transform_graph_(session_id_),
local_root_(transform_graph_.CreateTransform()) {
zx_status_t status = peer_closed_waiter_.Begin(
dispatcher_, [this](async_dispatcher_t* dispatcher, async::WaitOnce* wait, zx_status_t status,
const zx_packet_signal_t* signal) { destroy_instance_function_(); });
FX_DCHECK(status == ZX_OK);
}
Flatland::~Flatland() {
// TODO(fxbug.dev/55374): consider if Link tokens should be returned or not.
}
void Flatland::Present(fuchsia::ui::scenic::internal::PresentArgs args, PresentCallback callback) {
// Close any clients that call Present() without any present tokens.
if (present_tokens_remaining_ == 0) {
callback(fit::error(Error::NO_PRESENTS_REMAINING));
CloseConnection();
return;
}
present_tokens_remaining_--;
// If any fields are missing, replace them with the default values.
if (!args.has_requested_presentation_time()) {
args.set_requested_presentation_time(0);
}
if (!args.has_release_fences()) {
args.set_release_fences({});
}
if (!args.has_acquire_fences()) {
args.set_acquire_fences({});
}
if (!args.has_squashable()) {
args.set_squashable(true);
}
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<allocation::ImageMetadata> 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()) {
images_to_release.push_back(image_kv->second);
image_metadatas_.erase(image_kv);
}
}
// 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_ref = buffer_collection_importers_,
images_to_release](async_dispatcher_t*, async::WaitOnce*, zx_status_t status,
const zx_packet_signal_t* /*signal*/) mutable {
FX_DCHECK(status == ZX_OK);
for (auto& image_id : images_to_release) {
for (auto& importer : importer_ref) {
importer->ReleaseBufferImage(image_id.identifier);
}
}
});
FX_DCHECK(status == ZX_OK);
// Push the new release fence into the user-provided list.
args.mutable_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& [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();
}
for (const auto& [handle, opacity_value] : opacity_values_) {
uber_struct->local_opacity_values[handle] = opacity_value;
}
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(*args.mutable_release_fences()));
present2_helper_.RegisterPresent(present_id,
/*present_received_time=*/zx::time(async_now(dispatcher_)));
// 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 = args.requested_presentation_time(),
squashable = args.squashable(), uber_struct = std::move(uber_struct),
link_operations = std::move(pending_link_operations_),
release_fences = std::move(*args.mutable_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}, squashable);
// 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(*args.mutable_acquire_fences()));
callback(fit::ok());
} 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();
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 (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;
}
if (opacity_values_.find(parent_global_kv->second) != opacity_values_.end() &&
opacity_values_[parent_global_kv->second] != 1.f) {
FX_LOGS(ERROR) << "Cannot add a child to a node with an opacity value < 1.0.";
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::CreateImage(ContentId image_id,
fuchsia::scenic::allocation::BufferCollectionImportToken import_token,
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;
}
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) {
FX_LOGS(ERROR) << "CreateImage called with no valid export token";
ReportError();
return;
}
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;
}
allocation::ImageMetadata metadata;
metadata.identifier = allocation::GenerateUniqueImageId();
metadata.collection_id = global_collection_id;
metadata.vmo_index = vmo_index;
metadata.width = properties.width();
metadata.height = properties.height();
metadata.is_opaque = false;
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->ImportBufferImage(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]->ReleaseBufferImage(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;
}
void Flatland::SetOpacity(TransformId transform_id, float val) {
if (transform_id == kInvalidId) {
FX_LOGS(ERROR) << "SetOpacity called with transform_id 0";
ReportError();
return;
}
if (val < 0.f || val > 1.f) {
FX_LOGS(ERROR) << "Opacity value is not within valid range [0,1].";
ReportError();
return;
}
auto transform_kv = transforms_.find(transform_id);
if (transform_kv == transforms_.end()) {
FX_LOGS(ERROR) << "SetOpacity failed, transform_id " << transform_id << " not found";
ReportError();
return;
}
if (transform_graph_.HasChildren(transform_kv->second)) {
FX_LOGS(ERROR) << "Cannot set the opacity value of a non-leaf node below 1.0";
ReportError();
return;
}
// Erase the value from the map since we store 1.f implicity.
if (val == 1.f) {
opacity_values_.erase(transform_kv->second);
} else {
opacity_values_[transform_kv->second] = val;
}
}
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::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);
}
void Flatland::OnPresentProcessed(uint32_t num_present_tokens,
FuturePresentationInfos presentation_infos) {
present_tokens_remaining_ += num_present_tokens;
if (binding_.is_bound()) {
binding_.events().OnPresentProcessed(num_present_tokens, std::move(presentation_infos));
}
}
void Flatland::OnFramePresented(const std::map<scheduling::PresentId, zx::time>& latched_times,
scheduling::PresentTimestamps present_times) {
present2_helper_.OnPresented(latched_times, present_times, /*num_presents_allowed=*/0);
}
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::CloseConnection() {
// Cancel the async::Wait before closing the connection, or it will assert on destruction.
zx_status_t status = peer_closed_waiter_.Cancel();
// Immediately close the FIDL interface to prevent future requests.
binding_.Close(ZX_ERR_BAD_STATE);
// Finally, trigger the destruction of this instance.
destroy_instance_function_();
}
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