| // 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/time.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <limits> |
| |
| #include <gtest/gtest.h> |
| |
| #include "fuchsia/ui/scenic/internal/cpp/fidl.h" |
| #include "lib/gtest/test_loop_fixture.h" |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/ui/scenic/lib/flatland/global_matrix_data.h" |
| #include "src/ui/scenic/lib/flatland/global_topology_data.h" |
| #include "src/ui/scenic/lib/flatland/tests/mock_buffer_collection_importer.h" |
| #include "src/ui/scenic/lib/flatland/tests/mock_flatland_presenter.h" |
| #include "src/ui/scenic/lib/flatland/tests/mock_renderer.h" |
| #include "src/ui/scenic/lib/scheduling/frame_scheduler.h" |
| #include "src/ui/scenic/lib/scheduling/id.h" |
| |
| #include <glm/gtx/matrix_transform_2d.hpp> |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| using BufferCollectionId = flatland::Flatland::BufferCollectionId; |
| using ContentId = flatland::Flatland::ContentId; |
| using TransformId = flatland::Flatland::TransformId; |
| using flatland::BufferCollectionImporter; |
| using flatland::BufferCollectionMetadata; |
| using flatland::Flatland; |
| using flatland::FlatlandPresenter; |
| using flatland::GlobalMatrixVector; |
| using flatland::GlobalTopologyData; |
| using flatland::ImageMetadata; |
| using flatland::LinkSystem; |
| using flatland::MockBufferCollectionImporter; |
| using flatland::MockFlatlandPresenter; |
| using flatland::TransformGraph; |
| using flatland::TransformHandle; |
| using flatland::UberStruct; |
| using flatland::UberStructSystem; |
| using fuchsia::ui::scenic::internal::ContentLink; |
| using fuchsia::ui::scenic::internal::ContentLinkStatus; |
| using fuchsia::ui::scenic::internal::ContentLinkToken; |
| using fuchsia::ui::scenic::internal::Flatland_Present_Result; |
| using fuchsia::ui::scenic::internal::GraphLink; |
| using fuchsia::ui::scenic::internal::GraphLinkStatus; |
| using fuchsia::ui::scenic::internal::GraphLinkToken; |
| using fuchsia::ui::scenic::internal::ImageProperties; |
| using fuchsia::ui::scenic::internal::LayoutInfo; |
| using fuchsia::ui::scenic::internal::LinkProperties; |
| using fuchsia::ui::scenic::internal::Orientation; |
| using fuchsia::ui::scenic::internal::Vec2; |
| |
| namespace { |
| |
| // Convenience struct for the PRESENT_WITH_ARGS macro to avoid having to update it every time |
| // a new argument is added to Flatland::Present(). This struct also includes additional flags |
| // to PRESENT_WITH_ARGS itself for testing timing-related Present() functionality. |
| struct PresentArgs { |
| // Arguments to Flatland::Present(). |
| zx::time requested_presentation_time; |
| std::vector<zx::event> acquire_fences; |
| std::vector<zx::event> release_fences; |
| |
| // Arguments to the PRESENT_WITH_ARGS macro. |
| bool skip_session_update_and_release_fences = false; |
| }; |
| |
| struct GlobalIdPair { |
| sysmem_util::GlobalBufferCollectionId collection_id; |
| flatland::GlobalImageId image_id; |
| }; |
| |
| } // namespace |
| |
| // These macros works like functions that check a variety of conditions, but if those conditions |
| // fail, the line number for the failure will appear in-line rather than in a function. |
| |
| // This macro calls Present() on a Flatland object and immediately triggers the session update |
| // for all sessions so that changes from that Present() are visible in global systems. This is |
| // primarily useful for testing the user-facing Flatland API. |
| // |
| // This macro must be used within a test using the FlatlandTest harness. |
| // |
| // |flatland| is a Flatland object constructed with the MockFlatlandPresenter owned by the |
| // FlatlandTest harness. |expect_success| should be false if the call to Present() is expected to |
| // trigger an error. |
| #define PRESENT_WITH_ARGS(flatland, args, expect_success) \ |
| { \ |
| bool had_acquire_fences = !args.acquire_fences.empty(); \ |
| if (expect_success) { \ |
| EXPECT_CALL(*mock_flatland_presenter_, \ |
| RegisterPresent(flatland.GetRoot().GetInstanceId(), _)); \ |
| } \ |
| bool processed_callback = false; \ |
| flatland.Present(args.requested_presentation_time.get(), std::move(args.acquire_fences), \ |
| std::move(args.release_fences), [&](Flatland_Present_Result result) { \ |
| EXPECT_EQ(!expect_success, result.is_err()); \ |
| if (expect_success) { \ |
| EXPECT_EQ(1u, result.response().num_presents_remaining); \ |
| } else { \ |
| EXPECT_EQ(fuchsia::ui::scenic::internal::Error::BAD_OPERATION, \ |
| result.err()); \ |
| } \ |
| processed_callback = true; \ |
| }); \ |
| EXPECT_TRUE(processed_callback); \ |
| if (expect_success) { \ |
| /* Even with no acquire_fences, UberStruct updates queue on the dispatcher. */ \ |
| if (!had_acquire_fences) { \ |
| EXPECT_CALL(*mock_flatland_presenter_, \ |
| ScheduleUpdateForSession(args.requested_presentation_time, _)); \ |
| } \ |
| RunLoopUntilIdle(); \ |
| if (!args.skip_session_update_and_release_fences) { \ |
| ApplySessionUpdatesAndSignalFences(); \ |
| } \ |
| } \ |
| } |
| |
| // Identical to PRESENT_WITH_ARGS, but supplies an empty PresentArgs to the Present() call. |
| #define PRESENT(flatland, expect_success) \ |
| { PRESENT_WITH_ARGS(flatland, PresentArgs(), expect_success); } |
| |
| // This macro searches for a local matrix associated with a specific TransformHandle. |
| // |
| // |uber_struct| is the UberStruct to search to find the matrix. |target_handle| is the |
| // TransformHandle of the matrix to compare. |expected_matrix| is the expected value of that |
| // matrix. |
| #define EXPECT_MATRIX(uber_struct, target_handle, expected_matrix) \ |
| { \ |
| glm::mat3 matrix = glm::mat3(); \ |
| auto matrix_kv = uber_struct->local_matrices.find(target_handle); \ |
| if (matrix_kv != uber_struct->local_matrices.end()) { \ |
| matrix = matrix_kv->second; \ |
| } \ |
| for (size_t i = 0; i < 3; ++i) { \ |
| for (size_t j = 0; j < 3; ++j) { \ |
| EXPECT_FLOAT_EQ(matrix[i][j], expected_matrix[i][j]) << " row " << j << " column " << i; \ |
| } \ |
| } \ |
| } |
| |
| namespace { |
| |
| // TODO(fxbug.dev/56879): consolidate the following 3 helper functions when splitting escher into |
| // multiple libraries. |
| |
| zx::event CreateEvent() { |
| zx::event event; |
| FX_CHECK(zx::event::create(0, &event) == ZX_OK); |
| return event; |
| } |
| |
| std::vector<zx::event> CreateEventArray(size_t n) { |
| std::vector<zx::event> events; |
| for (size_t i = 0; i < n; i++) { |
| events.push_back(CreateEvent()); |
| } |
| return events; |
| } |
| |
| zx::event CopyEvent(const zx::event& event) { |
| zx::event event_copy; |
| if (event.duplicate(ZX_RIGHT_SAME_RIGHTS, &event_copy) != ZX_OK) { |
| FX_LOGS(ERROR) << "Copying zx::event failed."; |
| } |
| return event_copy; |
| } |
| |
| bool IsEventSignaled(const zx::event& fence, zx_signals_t signal) { |
| zx_signals_t pending = 0u; |
| fence.wait_one(signal, zx::time(), &pending); |
| return (pending & signal) != 0u; |
| } |
| |
| const float kDefaultSize = 1.f; |
| const glm::vec2 kDefaultPixelScale = {1.f, 1.f}; |
| |
| float 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>(); |
| } |
| } |
| |
| class FlatlandTest : public gtest::TestLoopFixture { |
| public: |
| FlatlandTest() |
| : uber_struct_system_(std::make_shared<UberStructSystem>()), |
| link_system_(std::make_shared<LinkSystem>(uber_struct_system_->GetNextInstanceId())) {} |
| |
| void SetUp() override { |
| mock_flatland_presenter_ = new ::testing::StrictMock<MockFlatlandPresenter>(); |
| |
| ON_CALL(*mock_flatland_presenter_, RegisterPresent(_, _)) |
| .WillByDefault(::testing::Invoke( |
| [&](scheduling::SessionId session_id, std::vector<zx::event> release_fences) { |
| const auto next_present_id = scheduling::GetNextPresentId(); |
| |
| // Store all release fences. |
| pending_release_fences_[{session_id, next_present_id}] = std::move(release_fences); |
| |
| return next_present_id; |
| })); |
| |
| ON_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)) |
| .WillByDefault(::testing::Invoke( |
| [&](zx::time requested_presentation_time, scheduling::SchedulingIdPair id_pair) { |
| // The ID must be already registered. |
| EXPECT_TRUE(pending_release_fences_.find(id_pair) != pending_release_fences_.end()); |
| |
| // Ensure IDs are strictly increasing. |
| auto current_id_kv = pending_session_updates_.find(id_pair.session_id); |
| EXPECT_TRUE(current_id_kv == pending_session_updates_.end() || |
| current_id_kv->second < id_pair.present_id); |
| |
| // Only save the latest PresentId: the UberStructSystem will flush all Presents prior |
| // to it. |
| pending_session_updates_[id_pair.session_id] = id_pair.present_id; |
| |
| // Store all requested presentation times to verify in test. |
| requested_presentation_times_[id_pair] = requested_presentation_time; |
| })); |
| |
| flatland_presenter_ = std::shared_ptr<FlatlandPresenter>(mock_flatland_presenter_); |
| |
| mock_buffer_collection_importer_ = new MockBufferCollectionImporter(); |
| buffer_collection_importer_ = |
| std::shared_ptr<BufferCollectionImporter>(mock_buffer_collection_importer_); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| auto link_topologies = link_system_->GetResolvedTopologyLinks(); |
| EXPECT_TRUE(link_topologies.empty()); |
| |
| buffer_collection_importer_.reset(); |
| flatland_presenter_.reset(); |
| |
| // Move the channel to a local variable which will go out of scope |
| // and close when this function returns. |
| auto local = local_.release(); |
| } |
| |
| Flatland CreateFlatland() { |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator; |
| auto session_id = scheduling::GetNextSessionId(); |
| return Flatland(session_id, flatland_presenter_, link_system_, |
| uber_struct_system_->AllocateQueueForSession(session_id), |
| {buffer_collection_importer_}, std::move(sysmem_allocator)); |
| } |
| |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> CreateToken() { |
| zx::channel remote; |
| zx::channel::create(0, &local_, &remote); |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token{std::move(remote)}; |
| return token; |
| } |
| |
| // Applies the most recently scheduled session update for each session and signals the release |
| // fences of all Presents up to and including that update. |
| void ApplySessionUpdatesAndSignalFences() { |
| uber_struct_system_->UpdateSessions(pending_session_updates_); |
| |
| // Signal all release fences up to and including the PresentId in |pending_session_updates_|. |
| for (const auto& [session_id, present_id] : pending_session_updates_) { |
| auto begin = pending_release_fences_.lower_bound({session_id, 0}); |
| auto end = pending_release_fences_.upper_bound({session_id, present_id}); |
| for (auto fences_kv = begin; fences_kv != end; ++fences_kv) { |
| for (auto& event : fences_kv->second) { |
| event.signal(0, ZX_EVENT_SIGNALED); |
| } |
| } |
| pending_release_fences_.erase(begin, end); |
| } |
| |
| pending_session_updates_.clear(); |
| requested_presentation_times_.clear(); |
| } |
| |
| // Gets the list of registered PresentIds for a particular |session_id|. |
| std::vector<scheduling::PresentId> GetRegisteredPresents(scheduling::SessionId session_id) const { |
| std::vector<scheduling::PresentId> present_ids; |
| |
| auto begin = pending_release_fences_.lower_bound({session_id, 0}); |
| auto end = pending_release_fences_.upper_bound({session_id + 1, 0}); |
| for (auto fence_kv = begin; fence_kv != end; ++fence_kv) { |
| present_ids.push_back(fence_kv->first.present_id); |
| } |
| |
| return present_ids; |
| } |
| |
| // Returns true if |session_id| currently has a session update pending. |
| bool HasSessionUpdate(scheduling::SessionId session_id) const { |
| return pending_session_updates_.count(session_id); |
| } |
| |
| // Returns the requested presentation time for a particular |id_pair|, or std::nullopt if that |
| // pair has not had a presentation scheduled for it. |
| std::optional<zx::time> GetRequestedPresentationTime(scheduling::SchedulingIdPair id_pair) { |
| auto iter = requested_presentation_times_.find(id_pair); |
| if (iter == requested_presentation_times_.end()) { |
| return std::nullopt; |
| } |
| return iter->second; |
| } |
| |
| void SetDisplayPixelScale(const glm::vec2& pixel_scale) { display_pixel_scale_ = pixel_scale; } |
| |
| // The parent transform must be a topology root or ComputeGlobalTopologyData() will crash. |
| bool IsDescendantOf(TransformHandle parent, TransformHandle child) { |
| auto snapshot = uber_struct_system_->Snapshot(); |
| auto links = link_system_->GetResolvedTopologyLinks(); |
| auto data = GlobalTopologyData::ComputeGlobalTopologyData( |
| snapshot, links, link_system_->GetInstanceId(), parent); |
| for (auto handle : data.topology_vector) { |
| if (handle == child) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Snapshots the UberStructSystem and fetches the UberStruct associated with |flatland|. If no |
| // UberStruct exists for |flatland|, returns nullptr. |
| std::shared_ptr<UberStruct> GetUberStruct(const Flatland& flatland) { |
| auto snapshot = uber_struct_system_->Snapshot(); |
| |
| auto root = flatland.GetRoot(); |
| auto uber_struct_kv = snapshot.find(root.GetInstanceId()); |
| if (uber_struct_kv == snapshot.end()) { |
| return nullptr; |
| } |
| |
| auto uber_struct = uber_struct_kv->second; |
| EXPECT_FALSE(uber_struct->local_topology.empty()); |
| EXPECT_EQ(uber_struct->local_topology[0].handle, root); |
| |
| return uber_struct; |
| } |
| |
| // Updates all Links reachable from |root_transform|, which must be the root transform of one of |
| // the active Flatland instances. |
| // |
| // Tests that call this function are testing both Flatland and LinkSystem::UpdateLinks(). |
| void UpdateLinks(TransformHandle root_transform) { |
| // Run the looper in case there are queued commands in, e.g., ObjectLinker. |
| RunLoopUntilIdle(); |
| |
| // This is a replica of the core render loop. |
| const auto snapshot = uber_struct_system_->Snapshot(); |
| const auto links = link_system_->GetResolvedTopologyLinks(); |
| const auto data = GlobalTopologyData::ComputeGlobalTopologyData( |
| snapshot, links, link_system_->GetInstanceId(), root_transform); |
| const auto matrices = |
| flatland::ComputeGlobalMatrices(data.topology_vector, data.parent_indices, snapshot); |
| |
| link_system_->UpdateLinks(data.topology_vector, data.live_handles, matrices, |
| display_pixel_scale_, snapshot); |
| |
| // Run the looper again to process any queued FIDL events (i.e., Link callbacks). |
| RunLoopUntilIdle(); |
| } |
| |
| void CreateLink(Flatland* parent, Flatland* child, ContentId id, |
| fidl::InterfacePtr<ContentLink>* content_link, |
| fidl::InterfacePtr<GraphLink>* graph_link) { |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent->CreateLink(id, std::move(parent_token), std::move(properties), |
| content_link->NewRequest()); |
| child->LinkToParent(std::move(child_token), graph_link->NewRequest()); |
| PRESENT((*parent), true); |
| PRESENT((*child), true); |
| } |
| |
| // Creates an image in |flatland| with the specified |image_id| and backing properties. |
| // This function also returns the GlobalBufferCollectionId that will be in the ImageMetadata |
| // struct for that Image. |
| GlobalIdPair CreateImage(Flatland* flatland, ContentId image_id, BufferCollectionId collection_id, |
| ImageProperties properties) { |
| sysmem_util::GlobalBufferCollectionId global_collection_id = sysmem_util::kInvalidId; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce(testing::Invoke( |
| [&global_collection_id]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id = collection_id; |
| return true; |
| })); |
| |
| flatland->RegisterBufferCollection(collection_id, CreateToken()); |
| EXPECT_NE(global_collection_id, sysmem_util::kInvalidId); |
| |
| // Ensure all buffer constraints are valid for the desired image by generating constraints based |
| // on the image properties. |
| BufferCollectionMetadata metadata; |
| metadata.vmo_count = 1; |
| |
| FX_DCHECK(properties.has_width()); |
| metadata.image_constraints.min_coded_width = properties.width(); |
| metadata.image_constraints.max_coded_width = properties.width(); |
| |
| FX_DCHECK(properties.has_height()); |
| metadata.image_constraints.min_coded_height = properties.height(); |
| metadata.image_constraints.max_coded_height = properties.height(); |
| |
| flatland::GlobalImageId global_image_id; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)) |
| .WillOnce(testing::Invoke([&global_image_id](const ImageMetadata& meta_data) { |
| global_image_id = meta_data.identifier; |
| return true; |
| })); |
| |
| flatland->CreateImage(image_id, collection_id, 0, std::move(properties)); |
| PRESENT((*flatland), true); |
| |
| return {.collection_id = global_collection_id, image_id = global_image_id}; |
| } |
| |
| protected: |
| ::testing::StrictMock<MockFlatlandPresenter>* mock_flatland_presenter_; |
| MockBufferCollectionImporter* mock_buffer_collection_importer_; |
| std::shared_ptr<BufferCollectionImporter> buffer_collection_importer_; |
| const std::shared_ptr<UberStructSystem> uber_struct_system_; |
| std::shared_ptr<FlatlandPresenter> flatland_presenter_; |
| const std::shared_ptr<LinkSystem> link_system_; |
| |
| private: |
| glm::vec2 display_pixel_scale_ = kDefaultPixelScale; |
| |
| // Storage for |mock_flatland_presenter_|. |
| std::map<scheduling::SchedulingIdPair, std::vector<zx::event>> pending_release_fences_; |
| std::map<scheduling::SchedulingIdPair, zx::time> requested_presentation_times_; |
| std::unordered_map<scheduling::SessionId, scheduling::PresentId> pending_session_updates_; |
| zx::channel local_; |
| }; |
| |
| } // namespace |
| |
| namespace flatland { |
| namespace test { |
| |
| TEST_F(FlatlandTest, PresentShouldReturnOne) { |
| Flatland flatland = CreateFlatland(); |
| PRESENT(flatland, true); |
| } |
| |
| TEST_F(FlatlandTest, PresentWaitsForAcquireFences) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create two events to serve as acquire fences. |
| PresentArgs args; |
| args.acquire_fences = CreateEventArray(2); |
| auto acquire1_copy = CopyEvent(args.acquire_fences[0]); |
| auto acquire2_copy = CopyEvent(args.acquire_fences[1]); |
| |
| // Create an event to serve as a release fence. |
| args.release_fences = CreateEventArray(1); |
| auto release_copy = CopyEvent(args.release_fences[0]); |
| |
| // Because the Present includes acquire fences, it should only be registered with the |
| // FlatlandPresenter. The UberStructSystem shouldn't have any entries and applying session |
| // updates shouldn't signal the release fence. |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| auto registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 1ul); |
| |
| EXPECT_EQ(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_FALSE(IsEventSignaled(release_copy, ZX_EVENT_SIGNALED)); |
| |
| // Signal the second fence and ensure the Present is still registered, the UberStructSystem |
| // doesn't update, and the release fence isn't signaled. |
| acquire2_copy.signal(0, ZX_EVENT_SIGNALED); |
| RunLoopUntilIdle(); |
| ApplySessionUpdatesAndSignalFences(); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 1ul); |
| |
| EXPECT_EQ(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_FALSE(IsEventSignaled(release_copy, ZX_EVENT_SIGNALED)); |
| |
| // Signal the first fence and ensure the Present is no longer registered (because it has been |
| // applied), the UberStructSystem contains an UberStruct for the instance, and the release fence |
| // is signaled. |
| acquire1_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| ApplySessionUpdatesAndSignalFences(); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_TRUE(registered_presents.empty()); |
| |
| EXPECT_NE(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_TRUE(IsEventSignaled(release_copy, ZX_EVENT_SIGNALED)); |
| } |
| |
| TEST_F(FlatlandTest, PresentForwardsRequestedPresentationTime) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create an event to serve as an acquire fence. |
| const zx::time requested_presentation_time = zx::time(123); |
| |
| PresentArgs args; |
| args.requested_presentation_time = requested_presentation_time; |
| args.acquire_fences = CreateEventArray(1); |
| auto acquire_copy = CopyEvent(args.acquire_fences[0]); |
| |
| // Because the Present includes acquire fences, it should only be registered with the |
| // FlatlandPresenter. There should be no requested presentation time. |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| auto registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 1ul); |
| |
| const auto id_pair = scheduling::SchedulingIdPair({ |
| .session_id = flatland.GetRoot().GetInstanceId(), |
| .present_id = registered_presents[0], |
| }); |
| |
| auto maybe_presentation_time = GetRequestedPresentationTime(id_pair); |
| EXPECT_FALSE(maybe_presentation_time.has_value()); |
| |
| // Signal the fence and ensure the Present is still registered, but now with a requested |
| // presentation time. |
| acquire_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 1ul); |
| |
| maybe_presentation_time = GetRequestedPresentationTime(id_pair); |
| EXPECT_TRUE(maybe_presentation_time.has_value()); |
| EXPECT_EQ(maybe_presentation_time.value(), requested_presentation_time); |
| } |
| |
| TEST_F(FlatlandTest, PresentWithSignaledFencesUpdatesImmediately) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create an event to serve as the acquire fence. |
| PresentArgs args; |
| args.acquire_fences = CreateEventArray(1); |
| auto acquire_copy = CopyEvent(args.acquire_fences[0]); |
| |
| // Create an event to serve as a release fence. |
| args.release_fences = CreateEventArray(1); |
| auto release_copy = CopyEvent(args.release_fences[0]); |
| |
| // Signal the event before the Present() call. |
| acquire_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| // The PresentId is no longer registered because it has been applied, the UberStructSystem should |
| // update immediately, and the release fence should be signaled. The PRESENT macro only expects |
| // the ScheduleUpdateForSession() call when no acquire fences are present, but since this test |
| // specifically tests pre-signaled fences, the EXPECT_CALL must be added here. |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| auto registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_TRUE(registered_presents.empty()); |
| |
| EXPECT_NE(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_TRUE(IsEventSignaled(release_copy, ZX_EVENT_SIGNALED)); |
| } |
| |
| TEST_F(FlatlandTest, PresentsUpdateInCallOrder) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create an event to serve as the acquire fence for the first Present(). |
| PresentArgs args1; |
| args1.acquire_fences = CreateEventArray(1); |
| auto acquire1_copy = CopyEvent(args1.acquire_fences[0]); |
| |
| // Create an event to serve as a release fence. |
| args1.release_fences = CreateEventArray(1); |
| auto release1_copy = CopyEvent(args1.release_fences[0]); |
| |
| // Present, but do not signal the fence, and ensure Present is registered, the UberStructSystem is |
| // empty, and the release fence is unsignaled. |
| PRESENT_WITH_ARGS(flatland, std::move(args1), true); |
| |
| auto registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 1ul); |
| |
| EXPECT_EQ(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_FALSE(IsEventSignaled(release1_copy, ZX_EVENT_SIGNALED)); |
| |
| // Create a transform and make it the root. |
| const TransformId kId = 1; |
| |
| flatland.CreateTransform(kId); |
| flatland.SetRootTransform(kId); |
| |
| // Create another event to serve as the acquire fence for the second Present(). |
| PresentArgs args2; |
| args2.acquire_fences = CreateEventArray(1); |
| auto acquire2_copy = CopyEvent(args2.acquire_fences[0]); |
| |
| // Create an event to serve as a release fence. |
| args2.release_fences = CreateEventArray(1); |
| auto release2_copy = CopyEvent(args2.release_fences[0]); |
| |
| // Present, but do not signal the fence, and ensure there are two Presents registered, but the |
| // UberStructSystem is still empty and both release fences are unsignaled. |
| PRESENT_WITH_ARGS(flatland, std::move(args2), true); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 2ul); |
| |
| EXPECT_EQ(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_FALSE(IsEventSignaled(release1_copy, ZX_EVENT_SIGNALED)); |
| EXPECT_FALSE(IsEventSignaled(release2_copy, ZX_EVENT_SIGNALED)); |
| |
| // Signal the fence for the second Present(). Since the first one is not done, there should still |
| // be two Presents registered, no UberStruct for the instance, and neither fence should be |
| // signaled. |
| acquire2_copy.signal(0, ZX_EVENT_SIGNALED); |
| RunLoopUntilIdle(); |
| ApplySessionUpdatesAndSignalFences(); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_EQ(registered_presents.size(), 2ul); |
| |
| EXPECT_EQ(GetUberStruct(flatland), nullptr); |
| |
| EXPECT_FALSE(IsEventSignaled(release1_copy, ZX_EVENT_SIGNALED)); |
| EXPECT_FALSE(IsEventSignaled(release2_copy, ZX_EVENT_SIGNALED)); |
| |
| // Signal the fence for the first Present(). This should trigger both Presents(), resulting no |
| // registered Presents and an UberStruct with a 2-element topology: the local root, and kId. |
| acquire1_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)).Times(2); |
| RunLoopUntilIdle(); |
| |
| ApplySessionUpdatesAndSignalFences(); |
| |
| registered_presents = GetRegisteredPresents(flatland.GetRoot().GetInstanceId()); |
| EXPECT_TRUE(registered_presents.empty()); |
| |
| auto uber_struct = GetUberStruct(flatland); |
| EXPECT_NE(uber_struct, nullptr); |
| EXPECT_EQ(uber_struct->local_topology.size(), 2ul); |
| |
| EXPECT_TRUE(IsEventSignaled(release1_copy, ZX_EVENT_SIGNALED)); |
| EXPECT_TRUE(IsEventSignaled(release2_copy, ZX_EVENT_SIGNALED)); |
| } |
| |
| TEST_F(FlatlandTest, CreateAndReleaseTransformValidCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| |
| // Create two transforms. |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| PRESENT(flatland, true); |
| |
| // Clear, then create two transforms in the other order. |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId2); |
| flatland.CreateTransform(kId1); |
| PRESENT(flatland, true); |
| |
| // Clear, create and release transforms, non-overlapping. |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId1); |
| flatland.ReleaseTransform(kId1); |
| flatland.CreateTransform(kId2); |
| flatland.ReleaseTransform(kId2); |
| PRESENT(flatland, true); |
| |
| // Clear, create and release transforms, nested. |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId2); |
| flatland.CreateTransform(kId1); |
| flatland.ReleaseTransform(kId1); |
| flatland.ReleaseTransform(kId2); |
| PRESENT(flatland, true); |
| |
| // Reuse the same id, legally, in a single present call. |
| flatland.CreateTransform(kId1); |
| flatland.ReleaseTransform(kId1); |
| flatland.CreateTransform(kId1); |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId1); |
| PRESENT(flatland, true); |
| |
| // Create and clear, overlapping, with multiple present calls. |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId2); |
| PRESENT(flatland, true); |
| flatland.CreateTransform(kId1); |
| flatland.ReleaseTransform(kId2); |
| PRESENT(flatland, true); |
| flatland.ReleaseTransform(kId1); |
| PRESENT(flatland, true); |
| } |
| |
| TEST_F(FlatlandTest, CreateAndReleaseTransformErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| |
| // Zero is not a valid transform id. |
| flatland.CreateTransform(0); |
| PRESENT(flatland, false); |
| flatland.ReleaseTransform(0); |
| PRESENT(flatland, false); |
| |
| // Double creation is an error. |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId1); |
| PRESENT(flatland, false); |
| |
| // Releasing a non-existent transform is an error. |
| flatland.ReleaseTransform(kId2); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, AddAndRemoveChildValidCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdParent = 1; |
| const TransformId kIdChild1 = 2; |
| const TransformId kIdChild2 = 3; |
| const TransformId kIdGrandchild = 4; |
| |
| flatland.CreateTransform(kIdParent); |
| flatland.CreateTransform(kIdChild1); |
| flatland.CreateTransform(kIdChild2); |
| flatland.CreateTransform(kIdGrandchild); |
| PRESENT(flatland, true); |
| |
| // Add and remove. |
| flatland.AddChild(kIdParent, kIdChild1); |
| flatland.RemoveChild(kIdParent, kIdChild1); |
| PRESENT(flatland, true); |
| |
| // Add two children. |
| flatland.AddChild(kIdParent, kIdChild1); |
| flatland.AddChild(kIdParent, kIdChild2); |
| PRESENT(flatland, true); |
| |
| // Remove two children. |
| flatland.RemoveChild(kIdParent, kIdChild1); |
| flatland.RemoveChild(kIdParent, kIdChild2); |
| PRESENT(flatland, true); |
| |
| // Add two-deep hierarchy. |
| flatland.AddChild(kIdParent, kIdChild1); |
| flatland.AddChild(kIdChild1, kIdGrandchild); |
| PRESENT(flatland, true); |
| |
| // Add sibling. |
| flatland.AddChild(kIdParent, kIdChild2); |
| PRESENT(flatland, true); |
| |
| // Add shared grandchild (deadly diamond dependency). |
| flatland.AddChild(kIdChild2, kIdGrandchild); |
| PRESENT(flatland, true); |
| |
| // Remove original deep-hierarchy. |
| flatland.RemoveChild(kIdChild1, kIdGrandchild); |
| PRESENT(flatland, true); |
| } |
| |
| TEST_F(FlatlandTest, AddAndRemoveChildErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdParent = 1; |
| const TransformId kIdChild = 2; |
| const TransformId kIdNotCreated = 3; |
| |
| // Setup. |
| flatland.CreateTransform(kIdParent); |
| flatland.CreateTransform(kIdChild); |
| flatland.AddChild(kIdParent, kIdChild); |
| PRESENT(flatland, true); |
| |
| // Zero is not a valid transform id. |
| flatland.AddChild(0, 0); |
| PRESENT(flatland, false); |
| flatland.AddChild(kIdParent, 0); |
| PRESENT(flatland, false); |
| flatland.AddChild(0, kIdChild); |
| PRESENT(flatland, false); |
| |
| // Child does not exist. |
| flatland.AddChild(kIdParent, kIdNotCreated); |
| PRESENT(flatland, false); |
| flatland.RemoveChild(kIdParent, kIdNotCreated); |
| PRESENT(flatland, false); |
| |
| // Parent does not exist. |
| flatland.AddChild(kIdNotCreated, kIdChild); |
| PRESENT(flatland, false); |
| flatland.RemoveChild(kIdNotCreated, kIdChild); |
| PRESENT(flatland, false); |
| |
| // Child is already a child of parent. |
| flatland.AddChild(kIdParent, kIdChild); |
| PRESENT(flatland, false); |
| |
| // Both nodes exist, but not in the correct relationship. |
| flatland.RemoveChild(kIdChild, kIdParent); |
| PRESENT(flatland, false); |
| } |
| |
| // Test that Transforms can be children to multiple different parents. |
| TEST_F(FlatlandTest, MultichildUsecase) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdParent1 = 1; |
| const TransformId kIdParent2 = 2; |
| const TransformId kIdChild1 = 3; |
| const TransformId kIdChild2 = 4; |
| const TransformId kIdChild3 = 5; |
| |
| // Setup |
| flatland.CreateTransform(kIdParent1); |
| flatland.CreateTransform(kIdParent2); |
| flatland.CreateTransform(kIdChild1); |
| flatland.CreateTransform(kIdChild2); |
| flatland.CreateTransform(kIdChild3); |
| PRESENT(flatland, true); |
| |
| // Add all children to first parent. |
| flatland.AddChild(kIdParent1, kIdChild1); |
| flatland.AddChild(kIdParent1, kIdChild2); |
| flatland.AddChild(kIdParent1, kIdChild3); |
| PRESENT(flatland, true); |
| |
| // Add all children to second parent. |
| flatland.AddChild(kIdParent2, kIdChild1); |
| flatland.AddChild(kIdParent2, kIdChild2); |
| flatland.AddChild(kIdParent2, kIdChild3); |
| PRESENT(flatland, true); |
| } |
| |
| // Test that Present() fails if it detects a graph cycle. |
| TEST_F(FlatlandTest, CycleDetector) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| const TransformId kId3 = 3; |
| const TransformId kId4 = 4; |
| |
| // Create an immediate cycle. |
| { |
| flatland.CreateTransform(kId1); |
| flatland.AddChild(kId1, kId1); |
| PRESENT(flatland, false); |
| } |
| |
| // Create a legal chain of depth one. |
| // Then, create a cycle of length 2. |
| { |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| flatland.AddChild(kId1, kId2); |
| PRESENT(flatland, true); |
| |
| flatland.AddChild(kId2, kId1); |
| PRESENT(flatland, false); |
| } |
| |
| // Create two legal chains of length one. |
| // Then, connect each chain into a cycle of length four. |
| { |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| flatland.CreateTransform(kId3); |
| flatland.CreateTransform(kId4); |
| flatland.AddChild(kId1, kId2); |
| flatland.AddChild(kId3, kId4); |
| PRESENT(flatland, true); |
| |
| flatland.AddChild(kId2, kId3); |
| flatland.AddChild(kId4, kId1); |
| PRESENT(flatland, false); |
| } |
| |
| // Create a cycle, where the root is not involved in the cycle. |
| { |
| flatland.ClearGraph(); |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| flatland.CreateTransform(kId3); |
| flatland.CreateTransform(kId4); |
| |
| flatland.AddChild(kId1, kId2); |
| flatland.AddChild(kId2, kId3); |
| flatland.AddChild(kId3, kId2); |
| flatland.AddChild(kId3, kId4); |
| |
| flatland.SetRootTransform(kId1); |
| flatland.ReleaseTransform(kId1); |
| flatland.ReleaseTransform(kId2); |
| flatland.ReleaseTransform(kId3); |
| flatland.ReleaseTransform(kId4); |
| PRESENT(flatland, false); |
| } |
| } |
| |
| TEST_F(FlatlandTest, SetRootTransform) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kId1 = 1; |
| const TransformId kIdNotCreated = 2; |
| |
| flatland.CreateTransform(kId1); |
| PRESENT(flatland, true); |
| |
| // Even with no root transform, so clearing it is not an error. |
| flatland.SetRootTransform(0); |
| PRESENT(flatland, true); |
| |
| // Setting the root to an unknown transform is an error. |
| flatland.SetRootTransform(kIdNotCreated); |
| PRESENT(flatland, false); |
| |
| flatland.SetRootTransform(kId1); |
| PRESENT(flatland, true); |
| |
| // Setting the root to a non-existent transform does not clear the root, which means the local |
| // topology will contain two handles: the "local root" and kId1. |
| auto uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.size(), 2ul); |
| |
| flatland.SetRootTransform(kIdNotCreated); |
| PRESENT(flatland, false); |
| |
| // The previous Present() fails, so we Present() again to ensure the UberStruct is updated, |
| // even though we expect no changes. |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.size(), 2ul); |
| |
| // Releasing the root is allowed, though it will remain in the hierarchy until reset. |
| flatland.ReleaseTransform(kId1); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.size(), 2ul); |
| |
| // Clearing the root after release is also allowed. |
| flatland.SetRootTransform(0); |
| PRESENT(flatland, true); |
| |
| // Setting the root to a released transform is not allowed. |
| flatland.SetRootTransform(kId1); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, SetTranslationErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdNotCreated = 1; |
| |
| // Zero is not a valid transform ID. |
| flatland.SetTranslation(0, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| |
| // Transform does not exist. |
| flatland.SetTranslation(kIdNotCreated, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, SetOrientationErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdNotCreated = 1; |
| |
| // Zero is not a valid transform ID. |
| flatland.SetOrientation(0, Orientation::CCW_90_DEGREES); |
| PRESENT(flatland, false); |
| |
| // Transform does not exist. |
| flatland.SetOrientation(kIdNotCreated, Orientation::CCW_90_DEGREES); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, SetScaleErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const TransformId kIdNotCreated = 1; |
| |
| // Zero is not a valid transform ID. |
| flatland.SetScale(0, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| |
| // Transform does not exist. |
| flatland.SetScale(kIdNotCreated, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| } |
| |
| // Test that changing geometric transform properties affects the local matrix of Transforms. |
| TEST_F(FlatlandTest, SetGeometricTransformProperties) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create two Transforms to ensure properties are local to individual Transforms. |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| |
| flatland.SetRootTransform(kId1); |
| flatland.AddChild(kId1, kId2); |
| |
| PRESENT(flatland, true); |
| |
| // Get the TransformHandles for kId1 and kId2. |
| auto uber_struct = GetUberStruct(flatland); |
| ASSERT_EQ(uber_struct->local_topology.size(), 3ul); |
| ASSERT_EQ(uber_struct->local_topology[0].handle, flatland.GetRoot()); |
| |
| const auto handle1 = uber_struct->local_topology[1].handle; |
| const auto handle2 = uber_struct->local_topology[2].handle; |
| |
| // The local topology will always have 3 transforms: the local root, kId1, and kId2. With no |
| // properties set, there will be no local matrices. |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_TRUE(uber_struct->local_matrices.empty()); |
| |
| // Set up one property per transform. |
| flatland.SetTranslation(kId1, {1.f, 2.f}); |
| flatland.SetScale(kId2, {2.f, 3.f}); |
| PRESENT(flatland, true); |
| |
| // The two handles should have the expected matrices. |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_MATRIX(uber_struct, handle1, glm::translate(glm::mat3(), {1.f, 2.f})); |
| EXPECT_MATRIX(uber_struct, handle2, glm::scale(glm::mat3(), {2.f, 3.f})); |
| |
| // Fill out the remaining properties on both transforms. |
| flatland.SetOrientation(kId1, Orientation::CCW_90_DEGREES); |
| flatland.SetScale(kId1, {4.f, 5.f}); |
| |
| flatland.SetTranslation(kId2, {6.f, 7.f}); |
| flatland.SetOrientation(kId2, Orientation::CCW_270_DEGREES); |
| |
| PRESENT(flatland, true); |
| |
| // Verify the new properties were applied in the correct orders. |
| uber_struct = GetUberStruct(flatland); |
| |
| glm::mat3 matrix1 = glm::mat3(); |
| matrix1 = glm::translate(matrix1, {1.f, 2.f}); |
| matrix1 = glm::rotate(matrix1, GetOrientationAngle(Orientation::CCW_90_DEGREES)); |
| matrix1 = glm::scale(matrix1, {4.f, 5.f}); |
| EXPECT_MATRIX(uber_struct, handle1, matrix1); |
| |
| glm::mat3 matrix2 = glm::mat3(); |
| matrix2 = glm::translate(matrix2, {6.f, 7.f}); |
| matrix2 = glm::rotate(matrix2, GetOrientationAngle(Orientation::CCW_270_DEGREES)); |
| matrix2 = glm::scale(matrix2, {2.f, 3.f}); |
| EXPECT_MATRIX(uber_struct, handle2, matrix2); |
| } |
| |
| // Ensure that local matrix data is only cleaned up when a Transform is completely unreferenced, |
| // meaning no Transforms reference it as a child. |
| TEST_F(FlatlandTest, MatrixReleasesWhenTransformNotReferenced) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create two Transforms to ensure properties are local to individual Transforms. |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| |
| flatland.CreateTransform(kId1); |
| flatland.CreateTransform(kId2); |
| |
| flatland.SetRootTransform(kId1); |
| flatland.AddChild(kId1, kId2); |
| |
| PRESENT(flatland, true); |
| |
| // Get the TransformHandles for kId1 and kId2. |
| auto uber_struct = GetUberStruct(flatland); |
| ASSERT_EQ(uber_struct->local_topology.size(), 3ul); |
| ASSERT_EQ(uber_struct->local_topology[0].handle, flatland.GetRoot()); |
| |
| const auto handle1 = uber_struct->local_topology[1].handle; |
| const auto handle2 = uber_struct->local_topology[2].handle; |
| |
| // Set a geometric property on kId1. |
| flatland.SetTranslation(kId1, {1.f, 2.f}); |
| PRESENT(flatland, true); |
| |
| // Only handle1 should have a local matrix. |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_MATRIX(uber_struct, handle1, glm::translate(glm::mat3(), {1.f, 2.f})); |
| |
| // Release kId1, but ensure its matrix stays around. |
| flatland.ReleaseTransform(kId1); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_MATRIX(uber_struct, handle1, glm::translate(glm::mat3(), {1.f, 2.f})); |
| |
| // Clear kId1 as the root transform, which should clear the matrix. |
| flatland.SetRootTransform(0); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_TRUE(uber_struct->local_matrices.empty()); |
| } |
| |
| TEST_F(FlatlandTest, GraphLinkReplaceWithoutConnection) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| flatland.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| ContentLinkToken parent_token2; |
| GraphLinkToken child_token2; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token2.value, &child_token2.value)); |
| |
| fidl::InterfacePtr<GraphLink> graph_link2; |
| flatland.LinkToParent(std::move(child_token2), graph_link2.NewRequest()); |
| |
| RunLoopUntilIdle(); |
| |
| // Until Present() is called, the previous GraphLink is not unbound. |
| EXPECT_TRUE(graph_link.is_bound()); |
| EXPECT_TRUE(graph_link2.is_bound()); |
| |
| PRESENT(flatland, true); |
| |
| EXPECT_FALSE(graph_link.is_bound()); |
| EXPECT_TRUE(graph_link2.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, GraphLinkReplaceWithConnection) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| |
| fidl::InterfacePtr<GraphLink> graph_link2; |
| |
| // Don't use the helper function for the second link to test when the previous links are closed. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // Creating the new GraphLink doesn't invalidate either of the old links until Present() is |
| // called on the child. |
| child.LinkToParent(std::move(child_token), graph_link2.NewRequest()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| EXPECT_TRUE(graph_link2.is_bound()); |
| |
| // Present() replaces the original GraphLink, which also results in the invalidation of both ends |
| // of the original link. |
| PRESENT(child, true); |
| |
| EXPECT_FALSE(content_link.is_bound()); |
| EXPECT_FALSE(graph_link.is_bound()); |
| EXPECT_TRUE(graph_link2.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, GraphLinkUnbindsOnParentDeath) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| flatland.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| parent_token.value.reset(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(graph_link.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, GraphLinkUnbindsImmediatelyWithInvalidToken) { |
| Flatland flatland = CreateFlatland(); |
| |
| GraphLinkToken child_token; |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| flatland.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| // The link will be unbound even before Present() is called. |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(graph_link.is_bound()); |
| |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, GraphUnlinkFailsWithoutLink) { |
| Flatland flatland = CreateFlatland(); |
| |
| flatland.UnlinkFromParent([](GraphLinkToken token) { EXPECT_TRUE(false); }); |
| |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, GraphUnlinkReturnsOrphanedTokenOnParentDeath) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| flatland.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| // Killing the peer token does not prevent the instance from returning a valid token. |
| parent_token.value.reset(); |
| RunLoopUntilIdle(); |
| |
| GraphLinkToken graph_token; |
| flatland.UnlinkFromParent( |
| [&graph_token](GraphLinkToken token) { graph_token = std::move(token); }); |
| PRESENT(flatland, true); |
| |
| EXPECT_TRUE(graph_token.value.is_valid()); |
| |
| // But trying to link with that token will immediately fail because it is already orphaned. |
| fidl::InterfacePtr<GraphLink> graph_link2; |
| flatland.LinkToParent(std::move(graph_token), graph_link2.NewRequest()); |
| PRESENT(flatland, true); |
| |
| EXPECT_FALSE(graph_link2.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, GraphUnlinkReturnsOriginalToken) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const zx_koid_t expected_koid = fsl::GetKoid(child_token.value.get()); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| flatland.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| GraphLinkToken graph_token; |
| flatland.UnlinkFromParent( |
| [&graph_token](GraphLinkToken token) { graph_token = std::move(token); }); |
| |
| RunLoopUntilIdle(); |
| |
| // Until Present() is called and the acquire fence is signaled, the previous GraphLink is not |
| // unbound. |
| EXPECT_TRUE(graph_link.is_bound()); |
| EXPECT_FALSE(graph_token.value.is_valid()); |
| |
| PresentArgs args; |
| args.acquire_fences = CreateEventArray(1); |
| auto event_copy = CopyEvent(args.acquire_fences[0]); |
| |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| EXPECT_TRUE(graph_link.is_bound()); |
| EXPECT_FALSE(graph_token.value.is_valid()); |
| |
| // Signal the acquire fence to unbind the link. |
| event_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(graph_link.is_bound()); |
| EXPECT_TRUE(graph_token.value.is_valid()); |
| EXPECT_EQ(fsl::GetKoid(graph_token.value.get()), expected_koid); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkUnbindsOnChildDeath) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId1, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| child_token.value.reset(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(content_link.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkUnbindsImmediatelyWithInvalidToken) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| flatland.CreateLink(kLinkId1, std::move(parent_token), {}, content_link.NewRequest()); |
| |
| // The link will be unbound even before Present() is called. |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(content_link.is_bound()); |
| |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkFailsIdIsZero) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(0, std::move(parent_token), std::move(properties), content_link.NewRequest()); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkFailsNoLogicalSize) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| flatland.CreateLink(0, std::move(parent_token), std::move(properties), content_link.NewRequest()); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkFailsInvalidLogicalSize) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| |
| // The X value must be positive. |
| LinkProperties properties; |
| properties.set_logical_size({0.f, kDefaultSize}); |
| flatland.CreateLink(0, std::move(parent_token), std::move(properties), content_link.NewRequest()); |
| PRESENT(flatland, false); |
| |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // The Y value must be positive. |
| LinkProperties properties2; |
| properties2.set_logical_size({kDefaultSize, 0.f}); |
| flatland.CreateLink(0, std::move(parent_token), std::move(properties2), |
| content_link.NewRequest()); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ContentLinkFailsIdCollision) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kId1, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| ContentLinkToken parent_token2; |
| GraphLinkToken child_token2; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token2.value, &child_token2.value)); |
| |
| flatland.CreateLink(kId1, std::move(parent_token2), std::move(properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ClearGraphDelaysLinkDestructionUntilPresent) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| // Clearing the parent graph should not unbind the interfaces until Present() is called and the |
| // acquire fence is signaled. |
| parent.ClearGraph(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| PresentArgs args; |
| args.acquire_fences = CreateEventArray(1); |
| auto event_copy = CopyEvent(args.acquire_fences[0]); |
| |
| PRESENT_WITH_ARGS(parent, std::move(args), true); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| // Signal the acquire fence to unbind the links. |
| event_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(content_link.is_bound()); |
| EXPECT_FALSE(graph_link.is_bound()); |
| |
| // Recreate the Link. The parent graph was cleared so we can reuse the LinkId. |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| // Clearing the child graph should not unbind the interfaces until Present() is called and the |
| // acquire fence is signaled. |
| child.ClearGraph(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| PresentArgs args2; |
| args2.acquire_fences = CreateEventArray(1); |
| event_copy = CopyEvent(args2.acquire_fences[0]); |
| |
| PRESENT_WITH_ARGS(child, std::move(args2), true); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_TRUE(graph_link.is_bound()); |
| |
| // Signal the acquire fence to unbind the links. |
| event_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(content_link.is_bound()); |
| EXPECT_FALSE(graph_link.is_bound()); |
| } |
| |
| // This test doesn't use the helper function to create a link, because it tests intermediate steps |
| // and timing corner cases. |
| TEST_F(FlatlandTest, ChildGetsLayoutUpdateWithoutPresenting) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| // Set up a link, but don't call Present() on either instance. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({1.0f, 2.0f}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| child.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| // Request a layout update. |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(1.0f, info.logical_size().x); |
| EXPECT_EQ(2.0f, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| // Without even presenting, the child is able to get the initial properties from the parent. |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // This test doesn't use the helper function to create a link, because it tests intermediate steps |
| // and timing corner cases. |
| TEST_F(FlatlandTest, ConnectedToDisplayParentPresentsBeforeChild) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| // Set up a link and attach it to the parent's root, but don't call Present() on either instance. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const TransformId kTransformId = 1; |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({1.0f, 2.0f}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| child.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| // Request a status update. |
| bool status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::DISCONNECTED_FROM_DISPLAY); |
| status_updated = true; |
| }); |
| |
| // The child begins disconnected from the display. |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| |
| // The GraphLinkStatus will update when both the parent and child Present(). |
| status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::CONNECTED_TO_DISPLAY); |
| status_updated = true; |
| }); |
| |
| // The parent presents first, no update. |
| PRESENT(parent, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_FALSE(status_updated); |
| |
| // The child presents second and the status updates. |
| PRESENT(child, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| } |
| |
| // This test doesn't use the helper function to create a link, because it tests intermediate steps |
| // and timing corner cases. |
| TEST_F(FlatlandTest, ConnectedToDisplayChildPresentsBeforeParent) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| // Set up a link and attach it to the parent's root, but don't call Present() on either instance. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const TransformId kTransformId = 1; |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({1.0f, 2.0f}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| child.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| // Request a status update. |
| bool status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::DISCONNECTED_FROM_DISPLAY); |
| status_updated = true; |
| }); |
| |
| // The child begins disconnected from the display. |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| |
| // The GraphLinkStatus will update when both the parent and child Present(). |
| status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::CONNECTED_TO_DISPLAY); |
| status_updated = true; |
| }); |
| |
| // The child presents first, no update. |
| PRESENT(child, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_FALSE(status_updated); |
| |
| // The parent presents second and the status updates. |
| PRESENT(parent, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| } |
| |
| // This test doesn't use the helper function to create a link, because it tests intermediate steps |
| // and timing corner cases. |
| TEST_F(FlatlandTest, ChildReceivesDisconnectedFromDisplay) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| // Set up a link and attach it to the parent's root, but don't call Present() on either instance. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const TransformId kTransformId = 1; |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({1.0f, 2.0f}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| child.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| // The GraphLinkStatus will update when both the parent and child Present(). |
| bool status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::CONNECTED_TO_DISPLAY); |
| status_updated = true; |
| }); |
| |
| PRESENT(child, true); |
| PRESENT(parent, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| |
| // The GraphLinkStatus will update again if the parent removes the child link from its topology. |
| status_updated = false; |
| graph_link->GetStatus([&](GraphLinkStatus status) { |
| EXPECT_EQ(status, GraphLinkStatus::DISCONNECTED_FROM_DISPLAY); |
| status_updated = true; |
| }); |
| |
| parent.SetContentOnTransform(0, kTransformId); |
| PRESENT(parent, true); |
| |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| } |
| |
| // This test doesn't use the helper function to create a link, because it tests intermediate steps |
| // and timing corner cases. |
| TEST_F(FlatlandTest, ValidChildToParentFlow) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({1.0f, 2.0f}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| |
| fidl::InterfacePtr<GraphLink> graph_link; |
| child.LinkToParent(std::move(child_token), graph_link.NewRequest()); |
| |
| bool status_updated = false; |
| content_link->GetStatus([&](ContentLinkStatus status) { |
| ASSERT_EQ(ContentLinkStatus::CONTENT_HAS_PRESENTED, status); |
| status_updated = true; |
| }); |
| |
| // The content link status changes as soon as the child presents - the parent does not have to |
| // present. |
| EXPECT_FALSE(status_updated); |
| |
| PRESENT(child, true); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(status_updated); |
| } |
| |
| TEST_F(FlatlandTest, LayoutOnlyUpdatesChildrenInGlobalTopology) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| UpdateLinks(parent.GetRoot()); |
| |
| // Confirm that the initial logical size is available immediately. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kDefaultSize, info.logical_size().x); |
| EXPECT_EQ(kDefaultSize, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // Set the logical size to something new. |
| { |
| LinkProperties properties; |
| properties.set_logical_size({2.0f, 3.0f}); |
| parent.SetLinkProperties(kLinkId, std::move(properties)); |
| PRESENT(parent, true); |
| } |
| |
| // Confirm that no update is triggered since the child is not in the global topology. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { layout_updated = true; }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_FALSE(layout_updated); |
| } |
| |
| // Attach the child to the global topology. |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| // Confirm that the new logical size is accessible. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(2.0f, info.logical_size().x); |
| EXPECT_EQ(3.0f, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, SetLinkPropertiesDefaultBehavior) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| UpdateLinks(parent.GetRoot()); |
| |
| // Confirm that the initial layout is the default. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kDefaultSize, info.logical_size().x); |
| EXPECT_EQ(kDefaultSize, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // Set the logical size to something new. |
| { |
| LinkProperties properties; |
| properties.set_logical_size({2.0f, 3.0f}); |
| parent.SetLinkProperties(kLinkId, std::move(properties)); |
| PRESENT(parent, true); |
| } |
| |
| // Confirm that the new logical size is accessible. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(2.0f, info.logical_size().x); |
| EXPECT_EQ(3.0f, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // Set link properties using a properties object with an unset size field. |
| { |
| LinkProperties default_properties; |
| parent.SetLinkProperties(kLinkId, std::move(default_properties)); |
| PRESENT(parent, true); |
| } |
| |
| // Confirm that no update has been triggered. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { layout_updated = true; }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_FALSE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, SetLinkPropertiesMultisetBehavior) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| |
| // Our initial layout (from link creation) should be the default size. |
| { |
| int num_updates = 0; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kDefaultSize, info.logical_size().x); |
| EXPECT_EQ(kDefaultSize, info.logical_size().y); |
| ++num_updates; |
| }); |
| |
| EXPECT_EQ(0, num_updates); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_EQ(1, num_updates); |
| } |
| |
| // Create a full chain of transforms from parent root to child root. |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| const float kInitialSize = 100.0f; |
| |
| // Set the logical size to something new multiple times. |
| for (int i = 10; i >= 0; --i) { |
| LinkProperties properties; |
| properties.set_logical_size({kInitialSize + i + 1.0f, kInitialSize + i + 1.0f}); |
| parent.SetLinkProperties(kLinkId, std::move(properties)); |
| LinkProperties properties2; |
| properties2.set_logical_size({kInitialSize + i, kInitialSize + i}); |
| parent.SetLinkProperties(kLinkId, std::move(properties2)); |
| PRESENT(parent, true); |
| } |
| |
| // Confirm that the callback is fired once, and that it has the most up-to-date data. |
| { |
| int num_updates = 0; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kInitialSize, info.logical_size().x); |
| EXPECT_EQ(kInitialSize, info.logical_size().y); |
| ++num_updates; |
| }); |
| |
| EXPECT_EQ(0, num_updates); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_EQ(1, num_updates); |
| } |
| |
| const float kNewSize = 50.0f; |
| |
| // Confirm that calling GetLayout again results in a hung get. |
| int num_updates = 0; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| // When we receive the new layout information, confirm that we receive the last update in the |
| // batch. |
| EXPECT_EQ(kNewSize, info.logical_size().x); |
| EXPECT_EQ(kNewSize, info.logical_size().y); |
| ++num_updates; |
| }); |
| |
| EXPECT_EQ(0, num_updates); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_EQ(0, num_updates); |
| |
| // Update the properties twice, once with the old value, once with the new value. |
| { |
| LinkProperties properties; |
| properties.set_logical_size({kInitialSize, kInitialSize}); |
| parent.SetLinkProperties(kLinkId, std::move(properties)); |
| LinkProperties properties2; |
| properties2.set_logical_size({kNewSize, kNewSize}); |
| parent.SetLinkProperties(kLinkId, std::move(properties2)); |
| PRESENT(parent, true); |
| } |
| |
| // Confirm that we receive the update. |
| EXPECT_EQ(0, num_updates); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_EQ(1, num_updates); |
| } |
| |
| TEST_F(FlatlandTest, SetLinkPropertiesOnMultipleChildren) { |
| const int kNumChildren = 3; |
| const TransformId kRootTransform = 1; |
| const TransformId kTransformIds[kNumChildren] = {2, 3, 4}; |
| const ContentId kLinkIds[kNumChildren] = {5, 6, 7}; |
| |
| Flatland parent = CreateFlatland(); |
| Flatland children[kNumChildren] = {CreateFlatland(), CreateFlatland(), CreateFlatland()}; |
| fidl::InterfacePtr<ContentLink> content_link[kNumChildren]; |
| fidl::InterfacePtr<GraphLink> graph_link[kNumChildren]; |
| |
| parent.CreateTransform(kRootTransform); |
| parent.SetRootTransform(kRootTransform); |
| |
| for (int i = 0; i < kNumChildren; ++i) { |
| parent.CreateTransform(kTransformIds[i]); |
| parent.AddChild(kRootTransform, kTransformIds[i]); |
| CreateLink(&parent, &children[i], kLinkIds[i], &content_link[i], &graph_link[i]); |
| parent.SetContentOnTransform(kLinkIds[i], kTransformIds[i]); |
| } |
| UpdateLinks(parent.GetRoot()); |
| |
| const float kDefaultSize = 1.0f; |
| |
| // Confirm that all children are at the default value |
| for (int i = 0; i < kNumChildren; ++i) { |
| bool layout_updated = false; |
| graph_link[i]->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kDefaultSize, info.logical_size().x); |
| EXPECT_EQ(kDefaultSize, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // Resize the content on all children. |
| for (auto id : kLinkIds) { |
| LinkProperties properties; |
| properties.set_logical_size({static_cast<float>(id), id * 2.0f}); |
| parent.SetLinkProperties(id, std::move(properties)); |
| } |
| |
| PRESENT(parent, true); |
| |
| for (int i = 0; i < kNumChildren; ++i) { |
| bool layout_updated = false; |
| graph_link[i]->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(kLinkIds[i], info.logical_size().x); |
| EXPECT_EQ(kLinkIds[i] * 2.0f, info.logical_size().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, DisplayPixelScaleAffectsPixelScale) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| UpdateLinks(parent.GetRoot()); |
| |
| // Change the display pixel scale. |
| const glm::vec2 new_display_pixel_scale = {0.1f, 0.2f}; |
| SetDisplayPixelScale(new_display_pixel_scale); |
| |
| // Call and ignore GetLayout() to guarantee the next call hangs. |
| graph_link->GetLayout([&](LayoutInfo info) {}); |
| |
| // Confirm that the new pixel scale is (.1, .2). |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_EQ(new_display_pixel_scale.x, info.pixel_scale().x); |
| EXPECT_EQ(new_display_pixel_scale.y, info.pixel_scale().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, LinkSizesAffectPixelScale) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| UpdateLinks(parent.GetRoot()); |
| |
| // Change the link size and logical size of the link. |
| const Vec2 kNewLinkSize = {2.f, 3.f}; |
| parent.SetLinkSize(kLinkId, kNewLinkSize); |
| |
| const Vec2 kNewLogicalSize = {5.f, 7.f}; |
| { |
| LinkProperties properties; |
| properties.set_logical_size(kNewLogicalSize); |
| parent.SetLinkProperties(kLinkId, std::move(properties)); |
| } |
| |
| PRESENT(parent, true); |
| |
| // Call and ignore GetLayout() to guarantee the next call hangs. |
| graph_link->GetLayout([&](LayoutInfo info) {}); |
| |
| // Confirm that the new pixel scale is (2 / 5, 3 / 7). |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_FLOAT_EQ(kNewLinkSize.x / kNewLogicalSize.x, info.pixel_scale().x); |
| EXPECT_FLOAT_EQ(kNewLinkSize.y / kNewLogicalSize.y, info.pixel_scale().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, GeometricAttributesAffectPixelScale) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const TransformId kTransformId = 1; |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId, &content_link, &graph_link); |
| |
| parent.CreateTransform(kTransformId); |
| parent.SetRootTransform(kTransformId); |
| parent.SetContentOnTransform(kLinkId, kTransformId); |
| PRESENT(parent, true); |
| |
| UpdateLinks(parent.GetRoot()); |
| |
| // Set a scale on the parent transform. |
| const Vec2 scale = {2.f, 3.f}; |
| parent.SetScale(kTransformId, scale); |
| PRESENT(parent, true); |
| |
| // Call and ignore GetLayout() to guarantee the next call hangs. |
| graph_link->GetLayout([&](LayoutInfo info) {}); |
| |
| // Confirm that the new pixel scale is (2, 3). |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_FLOAT_EQ(scale.x, info.pixel_scale().x); |
| EXPECT_FLOAT_EQ(scale.y, info.pixel_scale().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| |
| // Set a negative scale, but confirm that pixel scale is still positive. |
| parent.SetScale(kTransformId, {-scale.x, -scale.y}); |
| PRESENT(parent, true); |
| |
| // Call and ignore GetLayout() to guarantee the next call hangs. |
| graph_link->GetLayout([&](LayoutInfo info) {}); |
| |
| // Pixel scale is still (2, 3), so nothing changes. |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { layout_updated = true; }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_FALSE(layout_updated); |
| } |
| |
| // Set a rotation on the parent transform. |
| parent.SetOrientation(kTransformId, Orientation::CCW_90_DEGREES); |
| PRESENT(parent, true); |
| |
| // Call and ignore GetLayout() to guarantee the next call hangs. |
| graph_link->GetLayout([&](LayoutInfo info) {}); |
| |
| // Confirm that this flips the new pixel scale to (3, 2). |
| { |
| bool layout_updated = false; |
| graph_link->GetLayout([&](LayoutInfo info) { |
| EXPECT_FLOAT_EQ(scale.y, info.pixel_scale().x); |
| EXPECT_FLOAT_EQ(scale.x, info.pixel_scale().y); |
| layout_updated = true; |
| }); |
| |
| EXPECT_FALSE(layout_updated); |
| UpdateLinks(parent.GetRoot()); |
| EXPECT_TRUE(layout_updated); |
| } |
| } |
| |
| TEST_F(FlatlandTest, SetLinkOnTransformErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup. |
| |
| const TransformId kId1 = 1; |
| const TransformId kId2 = 2; |
| |
| flatland.CreateTransform(kId1); |
| |
| const ContentId kLinkId1 = 1; |
| const ContentId kLinkId2 = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| |
| // Creating a link with an empty property object is an error. Logical size must be provided at |
| // creation time. |
| { |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| LinkProperties empty_properties; |
| flatland.CreateLink(kLinkId1, std::move(parent_token), std::move(empty_properties), |
| content_link.NewRequest()); |
| |
| PRESENT(flatland, false); |
| } |
| |
| // We have to recreate our tokens to get a valid link object. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId1, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| |
| PRESENT(flatland, true); |
| |
| // Zero is not a valid transform_id. |
| flatland.SetContentOnTransform(kLinkId1, 0); |
| PRESENT(flatland, false); |
| |
| // Setting a valid link on an ivnalid transform is not valid. |
| flatland.SetContentOnTransform(kLinkId1, kId2); |
| PRESENT(flatland, false); |
| |
| // Setting an invalid link on a valid transform is not valid. |
| flatland.SetContentOnTransform(kLinkId2, kId1); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ReleaseLinkErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Zero is not a valid link_id. |
| flatland.ReleaseLink(0, [](ContentLinkToken token) { EXPECT_TRUE(false); }); |
| PRESENT(flatland, false); |
| |
| // Using a link_id that does not exist is not valid. |
| const ContentId kLinkId1 = 1; |
| flatland.ReleaseLink(kLinkId1, [](ContentLinkToken token) { EXPECT_TRUE(false); }); |
| PRESENT(flatland, false); |
| |
| // ContentId is not a Link. |
| const ContentId kImageId = 2; |
| const BufferCollectionId kBufferCollectionId = 3; |
| |
| ImageProperties properties; |
| properties.set_width(100); |
| properties.set_height(200); |
| |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)); |
| |
| flatland.ReleaseLink(kImageId, [](ContentLinkToken token) { EXPECT_TRUE(false); }); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ReleaseLinkReturnsOriginalToken) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const zx_koid_t expected_koid = fsl::GetKoid(parent_token.value.get()); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId1, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| ContentLinkToken content_token; |
| flatland.ReleaseLink( |
| kLinkId1, [&content_token](ContentLinkToken token) { content_token = std::move(token); }); |
| |
| RunLoopUntilIdle(); |
| |
| // Until Present() is called and the acquire fence is signaled, the previous ContentLink is not |
| // unbound. |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_FALSE(content_token.value.is_valid()); |
| |
| PresentArgs args; |
| args.acquire_fences = CreateEventArray(1); |
| auto event_copy = CopyEvent(args.acquire_fences[0]); |
| |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| EXPECT_TRUE(content_link.is_bound()); |
| EXPECT_FALSE(content_token.value.is_valid()); |
| |
| // Signal the acquire fence to unbind the link. |
| event_copy.signal(0, ZX_EVENT_SIGNALED); |
| |
| EXPECT_CALL(*mock_flatland_presenter_, ScheduleUpdateForSession(_, _)); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(content_link.is_bound()); |
| EXPECT_TRUE(content_token.value.is_valid()); |
| EXPECT_EQ(fsl::GetKoid(content_token.value.get()), expected_koid); |
| } |
| |
| TEST_F(FlatlandTest, ReleaseLinkReturnsOrphanedTokenOnChildDeath) { |
| Flatland flatland = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId1, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| // Killing the peer token does not prevent the instance from returning a valid token. |
| child_token.value.reset(); |
| RunLoopUntilIdle(); |
| |
| ContentLinkToken content_token; |
| flatland.ReleaseLink( |
| kLinkId1, [&content_token](ContentLinkToken token) { content_token = std::move(token); }); |
| PRESENT(flatland, true); |
| |
| EXPECT_TRUE(content_token.value.is_valid()); |
| |
| // But trying to link with that token will immediately fail because it is already orphaned. |
| const ContentId kLinkId2 = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link2; |
| flatland.CreateLink(kLinkId2, std::move(content_token), std::move(properties), |
| content_link2.NewRequest()); |
| PRESENT(flatland, true); |
| |
| EXPECT_FALSE(content_link2.is_bound()); |
| } |
| |
| TEST_F(FlatlandTest, CreateLinkPresentedBeforeLinkToParent) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // Create a transform, add it to the parent, then create a link and assign to the transform. |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> parent_content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| parent_content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kId1); |
| |
| PRESENT(parent, true); |
| |
| // Link the child to the parent. |
| fidl::InterfacePtr<GraphLink> child_graph_link; |
| child.LinkToParent(std::move(child_token), child_graph_link.NewRequest()); |
| |
| // The child should only be accessible from the parent when Present() is called on the child. |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| PRESENT(child, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, LinkToParentPresentedBeforeCreateLink) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // Link the child to the parent |
| fidl::InterfacePtr<GraphLink> child_graph_link; |
| child.LinkToParent(std::move(child_token), child_graph_link.NewRequest()); |
| |
| PRESENT(child, true); |
| |
| // Create a transform, add it to the parent, then create a link and assign to the transform. |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| |
| // Present the parent once so that it has a topology or else IsDescendantOf() will crash. |
| PRESENT(parent, true); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> parent_content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| parent_content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kId1); |
| |
| // The child should only be accessible from the parent when Present() is called on the parent. |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, LinkResolvedBeforeEitherPresent) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // Create a transform, add it to the parent, then create a link and assign to the transform. |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| |
| // Present the parent once so that it has a topology or else IsDescendantOf() will crash. |
| PRESENT(parent, true); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> parent_content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| parent_content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kId1); |
| |
| // Link the child to the parent. |
| fidl::InterfacePtr<GraphLink> child_graph_link; |
| child.LinkToParent(std::move(child_token), child_graph_link.NewRequest()); |
| |
| // The child should only be accessible from the parent when Present() is called on both the parent |
| // and the child. |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| PRESENT(child, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, ClearChildLink) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| // Create and link the two instances. |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| |
| const ContentId kLinkId = 1; |
| |
| fidl::InterfacePtr<ContentLink> parent_content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| parent_content_link.NewRequest()); |
| parent.SetContentOnTransform(kLinkId, kId1); |
| |
| fidl::InterfacePtr<GraphLink> child_graph_link; |
| child.LinkToParent(std::move(child_token), child_graph_link.NewRequest()); |
| |
| PRESENT(parent, true); |
| PRESENT(child, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| // Reset the child link using zero as the link id. |
| parent.SetContentOnTransform(0, kId1); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, RelinkUnlinkedParentSameToken) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| RunLoopUntilIdle(); |
| |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| parent.SetContentOnTransform(kId1, kLinkId1); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| GraphLinkToken graph_token; |
| child.UnlinkFromParent([&graph_token](GraphLinkToken token) { graph_token = std::move(token); }); |
| |
| PRESENT(child, true); |
| |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| // The same token can be used to link a different instance. |
| Flatland child2 = CreateFlatland(); |
| child2.LinkToParent(std::move(graph_token), graph_link.NewRequest()); |
| |
| PRESENT(child2, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child2.GetRoot())); |
| |
| // The old instance is not re-linked. |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, RecreateReleasedLinkSameToken) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| RunLoopUntilIdle(); |
| |
| const TransformId kId1 = 1; |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| parent.SetContentOnTransform(kId1, kLinkId1); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| ContentLinkToken content_token; |
| parent.ReleaseLink( |
| kLinkId1, [&content_token](ContentLinkToken token) { content_token = std::move(token); }); |
| |
| PRESENT(parent, true); |
| |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| |
| // The same token can be used to create a different link to the same child with a different |
| // parent. |
| Flatland parent2 = CreateFlatland(); |
| |
| const TransformId kId2 = 2; |
| parent2.CreateTransform(kId2); |
| parent2.SetRootTransform(kId2); |
| |
| const ContentId kLinkId2 = 2; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent2.CreateLink(kLinkId2, std::move(content_token), std::move(properties), |
| content_link.NewRequest()); |
| parent2.SetContentOnTransform(kId2, kLinkId2); |
| |
| PRESENT(parent2, true); |
| |
| EXPECT_TRUE(IsDescendantOf(parent2.GetRoot(), child.GetRoot())); |
| |
| // The old instance is not re-linked. |
| EXPECT_FALSE(IsDescendantOf(parent.GetRoot(), child.GetRoot())); |
| } |
| |
| TEST_F(FlatlandTest, SetLinkSizeErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| const ContentId kIdNotCreated = 1; |
| |
| // Zero is not a valid transform ID. |
| flatland.SetLinkSize(0, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| |
| // Size contains non-positive components. |
| flatland.SetLinkSize(0, {-1.f, 2.f}); |
| PRESENT(flatland, false); |
| |
| flatland.SetLinkSize(0, {1.f, 0.f}); |
| PRESENT(flatland, false); |
| |
| // Link does not exist. |
| flatland.SetLinkSize(kIdNotCreated, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| |
| // ContentId is not a Link. |
| const ContentId kImageId = 2; |
| const BufferCollectionId kBufferCollectionId = 3; |
| |
| ImageProperties properties; |
| properties.set_width(100); |
| properties.set_height(200); |
| |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)); |
| |
| flatland.SetLinkSize(kImageId, {1.f, 2.f}); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, LinkSizeRatiosCreateScaleMatrix) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| |
| const TransformId kId1 = 1; |
| |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| parent.SetContentOnTransform(kLinkId1, kId1); |
| |
| PRESENT(parent, true); |
| |
| const auto maybe_link_handle = parent.GetContentHandle(kLinkId1); |
| ASSERT_TRUE(maybe_link_handle.has_value()); |
| const auto link_handle = maybe_link_handle.value(); |
| |
| // The default size is the same as the logical size, so the link handle won't have a matrix. |
| auto uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, glm::mat3()); |
| |
| // Change the link size to half the width and a quarter the height. |
| const float kNewLinkWidth = 0.5f * kDefaultSize; |
| const float kNewLinkHeight = 0.25f * kDefaultSize; |
| parent.SetLinkSize(kLinkId1, {kNewLinkWidth, kNewLinkHeight}); |
| |
| PRESENT(parent, true); |
| |
| // This should change the expected matrix to apply the same scales. |
| const glm::mat3 expected_scale_matrix = glm::scale(glm::mat3(), {kNewLinkWidth, kNewLinkHeight}); |
| |
| uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, expected_scale_matrix); |
| |
| // Changing the logical size to the same values returns the matrix to the identity matrix. |
| LinkProperties properties; |
| properties.set_logical_size({kNewLinkWidth, kNewLinkHeight}); |
| parent.SetLinkProperties(kLinkId1, std::move(properties)); |
| |
| PRESENT(parent, true); |
| |
| uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, glm::mat3()); |
| |
| // Change the logical size back to the default size. |
| LinkProperties properties2; |
| properties2.set_logical_size({kDefaultSize, kDefaultSize}); |
| parent.SetLinkProperties(kLinkId1, std::move(properties2)); |
| |
| PRESENT(parent, true); |
| |
| // This should change the expected matrix back to applying the scales. |
| uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, expected_scale_matrix); |
| } |
| |
| TEST_F(FlatlandTest, EmptyLogicalSizePreservesOldSize) { |
| Flatland parent = CreateFlatland(); |
| Flatland child = CreateFlatland(); |
| |
| const ContentId kLinkId1 = 1; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| fidl::InterfacePtr<GraphLink> graph_link; |
| CreateLink(&parent, &child, kLinkId1, &content_link, &graph_link); |
| |
| const TransformId kId1 = 1; |
| |
| parent.CreateTransform(kId1); |
| parent.SetRootTransform(kId1); |
| parent.SetContentOnTransform(kLinkId1, kId1); |
| |
| PRESENT(parent, true); |
| |
| const auto maybe_link_handle = parent.GetContentHandle(kLinkId1); |
| ASSERT_TRUE(maybe_link_handle.has_value()); |
| const auto link_handle = maybe_link_handle.value(); |
| |
| // Set the link size and logical size to new values |
| const float kNewLinkWidth = 2.f * kDefaultSize; |
| const float kNewLinkHeight = 3.f * kDefaultSize; |
| parent.SetLinkSize(kLinkId1, {kNewLinkWidth, kNewLinkHeight}); |
| |
| const float kNewLinkLogicalWidth = 5.f * kDefaultSize; |
| const float kNewLinkLogicalHeight = 7.f * kDefaultSize; |
| LinkProperties properties; |
| properties.set_logical_size({kNewLinkLogicalWidth, kNewLinkLogicalHeight}); |
| parent.SetLinkProperties(kLinkId1, std::move(properties)); |
| |
| PRESENT(parent, true); |
| |
| // This should result in an expected matrix that applies the ratio of the scales. |
| glm::mat3 expected_scale_matrix = glm::scale( |
| glm::mat3(), {kNewLinkWidth / kNewLinkLogicalWidth, kNewLinkHeight / kNewLinkLogicalHeight}); |
| |
| auto uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, expected_scale_matrix); |
| |
| // Setting a new LinkProperties with no logical size shouldn't change the matrix. |
| LinkProperties properties2; |
| parent.SetLinkProperties(kLinkId1, std::move(properties2)); |
| |
| PRESENT(parent, true); |
| |
| uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, expected_scale_matrix); |
| |
| // But it should still preserve the old logical size so that a subsequent link size update uses |
| // the old logical size. |
| const float kNewLinkWidth2 = 11.f * kDefaultSize; |
| const float kNewLinkHeight2 = 13.f * kDefaultSize; |
| parent.SetLinkSize(kLinkId1, {kNewLinkWidth2, kNewLinkHeight2}); |
| |
| PRESENT(parent, true); |
| |
| // This should result in an expected matrix that applies the ratio of the scales. |
| expected_scale_matrix = glm::scale(glm::mat3(), {kNewLinkWidth2 / kNewLinkLogicalWidth, |
| kNewLinkHeight2 / kNewLinkLogicalHeight}); |
| |
| uber_struct = GetUberStruct(parent); |
| EXPECT_MATRIX(uber_struct, link_handle, expected_scale_matrix); |
| } |
| |
| TEST_F(FlatlandTest, RegisterBufferCollectionErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Zero is not a valid buffer collection ID. |
| { |
| flatland.RegisterBufferCollection(0, CreateToken()); |
| PRESENT(flatland, false); |
| } |
| |
| // Passing an uninitiated token is not valid. |
| { |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token; |
| flatland.RegisterBufferCollection(0, std::move(token)); |
| PRESENT(flatland, false); |
| } |
| |
| // Passing a token whose channel(s) have closed or gone out of scope is |
| // also not valid. |
| { |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token; |
| { |
| zx::channel local; |
| zx::channel remote; |
| zx::channel::create(0, &local, &remote); |
| token = fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>(std::move(remote)); |
| } |
| |
| flatland.RegisterBufferCollection(0, std::move(token)); |
| PRESENT(flatland, false); |
| } |
| |
| // The buffer importer call can fail. |
| { |
| // Mock the importer call to fail. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(false)); |
| |
| flatland.RegisterBufferCollection(1, CreateToken()); |
| PRESENT(flatland, false); |
| } |
| |
| // Two buffer collections cannot use the same ID. |
| { |
| const BufferCollectionId kId = 2; |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(true)); |
| |
| flatland.RegisterBufferCollection(kId, CreateToken()); |
| PRESENT(flatland, true); |
| |
| flatland.RegisterBufferCollection(kId, CreateToken()); |
| PRESENT(flatland, false); |
| } |
| } |
| |
| // Tests that Flatland passes the Sysmem token to the importer even if the client has not called |
| // Present(). This is necessary since the client may block on buffers being allocated before |
| // presenting. |
| TEST_F(FlatlandTest, BufferImporterGetsSysmemTokenBeforePresent) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Register a buffer collection and expect the mock buffer importer call, even without presenting. |
| const BufferCollectionId kId = 2; |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)).Times(1); |
| flatland.RegisterBufferCollection(kId, CreateToken()); |
| } |
| |
| TEST_F(FlatlandTest, CreateImageErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Default image properties. |
| const uint32_t kDefaultVmoIndex = 1; |
| const uint32_t kDefaultWidth = 100; |
| const uint32_t kDefaultHeight = 1000; |
| |
| // Setup a valid buffer collection. |
| const BufferCollectionId kBufferCollectionId = 1; |
| sysmem_util::GlobalBufferCollectionId global_collection_id; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id = collection_id; |
| return true; |
| })); |
| |
| flatland.RegisterBufferCollection(kBufferCollectionId, CreateToken()); |
| PRESENT(flatland, true); |
| |
| // Zero is not a valid image ID. |
| { |
| ImageProperties properties; |
| flatland.CreateImage(0, kBufferCollectionId, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // The buffer collection ID must also be valid. |
| { |
| ImageProperties properties; |
| flatland.CreateImage(1, 0, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // The buffer collection can fail to create an image. |
| { |
| ImageProperties properties; |
| flatland.CreateImage(1, kBufferCollectionId, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // Check to make sure that if the BufferCollectionImporter returns false, then the call |
| // to Flatland::CreateImage() also returns false. |
| { |
| const ContentId kId = 100; |
| ImageProperties properties; |
| properties.set_width(kDefaultWidth); |
| properties.set_height(kDefaultHeight); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).WillOnce(Return(false)); |
| flatland.CreateImage(kId, kBufferCollectionId, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // Two images cannot have the same ID. |
| const ContentId kId = 1; |
| { |
| ImageProperties properties; |
| properties.set_width(kDefaultWidth); |
| properties.set_height(kDefaultHeight); |
| |
| // This is the first call in these series of test components that makes it down to |
| // the BufferCollectionImporter. We have to make sure it returns true here so that |
| // the test doesn't erroneously fail. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).WillOnce(Return(true)); |
| |
| flatland.CreateImage(kId, kBufferCollectionId, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, true); |
| } |
| |
| { |
| ImageProperties properties; |
| properties.set_width(kDefaultWidth); |
| properties.set_height(kDefaultHeight); |
| |
| // We shouldn't even make it to the BufferCollectionImporter here due to the duplicate |
| // ID causing CreateImage() to return early. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).Times(0); |
| flatland.CreateImage(kId, kBufferCollectionId, kDefaultVmoIndex, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // A Link id cannot be used for an image. |
| const ContentId kLinkId = 2; |
| { |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties link_properties; |
| link_properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId, std::move(parent_token), std::move(link_properties), |
| content_link.NewRequest()); |
| PRESENT(flatland, true); |
| |
| ImageProperties image_properties; |
| image_properties.set_width(kDefaultWidth); |
| image_properties.set_height(kDefaultHeight); |
| |
| flatland.CreateImage(kLinkId, kBufferCollectionId, kDefaultVmoIndex, |
| std::move(image_properties)); |
| PRESENT(flatland, false); |
| } |
| } |
| |
| TEST_F(FlatlandTest, SetContentOnTransformErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| const uint32_t kWidth = 100; |
| const uint32_t kHeight = 200; |
| |
| ImageProperties properties; |
| properties.set_width(kWidth); |
| properties.set_height(kHeight); |
| |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)); |
| |
| // Create a transform. |
| const TransformId kTransformId = 1; |
| |
| flatland.CreateTransform(kTransformId); |
| PRESENT(flatland, true); |
| |
| // Zero is not a valid transform. |
| flatland.SetContentOnTransform(kImageId, 0); |
| PRESENT(flatland, false); |
| |
| // The transform must exist. |
| flatland.SetContentOnTransform(kImageId, 2); |
| PRESENT(flatland, false); |
| |
| // The image must exist. |
| flatland.SetContentOnTransform(2, kTransformId); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, ClearContentOnTransform) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties; |
| properties.set_width(100); |
| properties.set_height(200); |
| |
| auto global_collection_id = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)).collection_id; |
| |
| const auto maybe_image_handle = flatland.GetContentHandle(kImageId); |
| ASSERT_TRUE(maybe_image_handle.has_value()); |
| const auto image_handle = maybe_image_handle.value(); |
| |
| // Create a transform, make it the root transform, and attach the image. |
| const TransformId kTransformId = 1; |
| |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| |
| // The image handle should be the last handle in the local_topology, and the image should be in |
| // the image map. |
| auto uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.back().handle, image_handle); |
| |
| auto image_kv = uber_struct->images.find(image_handle); |
| EXPECT_NE(image_kv, uber_struct->images.end()); |
| EXPECT_EQ(image_kv->second.collection_id, global_collection_id); |
| |
| // An ContentId of 0 indicates to remove any content on the specified transform. |
| flatland.SetContentOnTransform(0, kTransformId); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| for (const auto& entry : uber_struct->local_topology) { |
| EXPECT_NE(entry.handle, image_handle); |
| } |
| } |
| |
| TEST_F(FlatlandTest, TopologyVisitsContentBeforeChildren) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup two valid images. |
| const ContentId kImageId1 = 1; |
| const BufferCollectionId kBufferCollectionId1 = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| CreateImage(&flatland, kImageId1, kBufferCollectionId1, std::move(properties1)); |
| |
| const auto maybe_image_handle1 = flatland.GetContentHandle(kImageId1); |
| ASSERT_TRUE(maybe_image_handle1.has_value()); |
| const auto image_handle1 = maybe_image_handle1.value(); |
| |
| const ContentId kImageId2 = 2; |
| const BufferCollectionId kBufferCollectionId2 = 2; |
| |
| ImageProperties properties2; |
| properties2.set_width(300); |
| properties2.set_height(400); |
| |
| CreateImage(&flatland, kImageId2, kBufferCollectionId2, std::move(properties2)); |
| |
| const auto maybe_image_handle2 = flatland.GetContentHandle(kImageId2); |
| ASSERT_TRUE(maybe_image_handle2.has_value()); |
| const auto image_handle2 = maybe_image_handle2.value(); |
| |
| // Create a root transform with two children. |
| const TransformId kTransformId1 = 3; |
| const TransformId kTransformId2 = 4; |
| const TransformId kTransformId3 = 5; |
| |
| flatland.CreateTransform(kTransformId1); |
| flatland.CreateTransform(kTransformId2); |
| flatland.CreateTransform(kTransformId3); |
| |
| flatland.AddChild(kTransformId1, kTransformId2); |
| flatland.AddChild(kTransformId1, kTransformId3); |
| |
| flatland.SetRootTransform(kTransformId1); |
| PRESENT(flatland, true); |
| |
| // Attach image 1 to the root and the second child. Attach image 2 to the first child. |
| flatland.SetContentOnTransform(kImageId1, kTransformId1); |
| flatland.SetContentOnTransform(kImageId2, kTransformId2); |
| flatland.SetContentOnTransform(kImageId1, kTransformId3); |
| PRESENT(flatland, true); |
| |
| // The images should appear pre-order toplogically sorted: 1, 2, 1 again. The same image is |
| // allowed to appear multiple times. |
| std::queue<TransformHandle> expected_handle_order; |
| expected_handle_order.push(image_handle1); |
| expected_handle_order.push(image_handle2); |
| expected_handle_order.push(image_handle1); |
| auto uber_struct = GetUberStruct(flatland); |
| for (const auto& entry : uber_struct->local_topology) { |
| if (entry.handle == expected_handle_order.front()) { |
| expected_handle_order.pop(); |
| } |
| } |
| EXPECT_TRUE(expected_handle_order.empty()); |
| |
| // Clearing the image from the parent removes the first entry of the list since images are |
| // visited before children. |
| flatland.SetContentOnTransform(0, kTransformId1); |
| PRESENT(flatland, true); |
| |
| // Meaning the new list of images should be: 2, 1. |
| expected_handle_order.push(image_handle2); |
| expected_handle_order.push(image_handle1); |
| uber_struct = GetUberStruct(flatland); |
| for (const auto& entry : uber_struct->local_topology) { |
| if (entry.handle == expected_handle_order.front()) { |
| expected_handle_order.pop(); |
| } |
| } |
| EXPECT_TRUE(expected_handle_order.empty()); |
| } |
| |
| TEST_F(FlatlandTest, DeregisterBufferCollectionErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Zero is not a buffer collection ID. |
| flatland.DeregisterBufferCollection(0); |
| PRESENT(flatland, false); |
| |
| // The buffer collection ID must exist. |
| flatland.DeregisterBufferCollection(1); |
| PRESENT(flatland, false); |
| |
| // A buffer collection cannot be deregistered twice. |
| sysmem_util::GlobalBufferCollectionId global_collection_id; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id = collection_id; |
| return true; |
| })); |
| |
| const BufferCollectionId kBufferCollectionId = 3; |
| flatland.RegisterBufferCollection(kBufferCollectionId, CreateToken()); |
| PRESENT(flatland, true); |
| |
| // The PRESENT macro triggers release fences, which in turn triggers the buffer importer call. |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(1); |
| PRESENT(flatland, true); |
| |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| PRESENT(flatland, false); |
| } |
| |
| TEST_F(FlatlandTest, DeregisterMultipleBufferCollectionsSameEvent) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Register the first buffer collection. |
| sysmem_util::GlobalBufferCollectionId global_collection_id_1; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id_1]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id_1 = collection_id; |
| return true; |
| })); |
| |
| const BufferCollectionId kBufferCollectionId1 = 2; |
| { flatland.RegisterBufferCollection(kBufferCollectionId1, CreateToken()); } |
| |
| // Register the second buffer collection. |
| sysmem_util::GlobalBufferCollectionId global_collection_id_2; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id_2]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id_2 = collection_id; |
| return true; |
| })); |
| |
| const BufferCollectionId kBufferCollectionId2 = 4; |
| { flatland.RegisterBufferCollection(kBufferCollectionId2, CreateToken()); } |
| |
| PRESENT(flatland, true); |
| |
| // Deregister both buffer collections so they're both waiting on the same release fence. |
| flatland.DeregisterBufferCollection(kBufferCollectionId1); |
| flatland.DeregisterBufferCollection(kBufferCollectionId2); |
| |
| // Skip session updates to test that release fences are what trigger the buffer importer calls. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_1)) |
| .Times(0); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_2)) |
| .Times(0); |
| |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| // Signal the release fence. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_1)) |
| .Times(1); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_2)) |
| .Times(1); |
| ApplySessionUpdatesAndSignalFences(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(FlatlandTest, DeregisteredBufferCollectionIdCanBeReused) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Create a valid BufferCollectionId. |
| sysmem_util::GlobalBufferCollectionId global_collection_id_1; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id_1]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id_1 = collection_id; |
| return true; |
| })); |
| |
| const BufferCollectionId kBufferCollectionId = 2; |
| { |
| flatland.RegisterBufferCollection(kBufferCollectionId, CreateToken()); |
| PRESENT(flatland, true); |
| } |
| |
| // Deregister it, but don't signal the release fence yet. |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| |
| { |
| // Skip session updates to test that release fences are what trigger the buffer importer calls. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_1)) |
| .Times(0); |
| |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| } |
| |
| // Register another buffer collection with that same ID. |
| sysmem_util::GlobalBufferCollectionId global_collection_id_2; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id_2]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id_2 = collection_id; |
| return true; |
| })); |
| |
| { |
| flatland.RegisterBufferCollection(kBufferCollectionId, CreateToken()); |
| |
| // Skip session updates to test that release fences are what trigger the buffer importer calls. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_1)) |
| .Times(0); |
| |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| } |
| |
| // Signal the release fences, which should result deregister the first one from the importer. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_1)) |
| .Times(1); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_2)) |
| .Times(0); |
| ApplySessionUpdatesAndSignalFences(); |
| RunLoopUntilIdle(); |
| |
| // Deregister the second one, signal the release fences, and verify the second global ID was |
| // deregistered. |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id_2)) |
| .Times(1); |
| PRESENT(flatland, true); |
| } |
| |
| // Tests that a buffer collection is not deregistered from the importer until it is not referenced |
| // by any active Image (including released Images still on Transforms) and the release fence is |
| // signaled. |
| TEST_F(FlatlandTest, DeregisterBufferCollectionWaitsForReleaseFence) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid buffer collection and Image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 2; |
| |
| ImageProperties properties; |
| properties.set_width(100); |
| properties.set_height(200); |
| |
| const auto global_id_pair = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)); |
| auto& global_collection_id = global_id_pair.collection_id; |
| |
| // Attach the Image to a transform. |
| const TransformId kTransformId = 3; |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| |
| // Deregister the buffer collection, but ensure that the deregistration call on the importer has |
| // not happened. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(0); |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| PRESENT(flatland, true); |
| |
| // Release the Image that referenced the buffer collection. Because the Image is still attached |
| // to a Transform, the deregestration call should still not happen. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(0); |
| flatland.ReleaseImage(kImageId); |
| PRESENT(flatland, true); |
| |
| // Remove the Image from the transform. This triggers the creation of the release fence, but |
| // still does not result in a deregestration call. Skip session updates to test that release |
| // fences are what trigger the importer calls. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(0); |
| flatland.SetContentOnTransform(0, kTransformId); |
| |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| // Signal the release fences, which triggers the deregistration call. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(1); |
| ApplySessionUpdatesAndSignalFences(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(FlatlandTest, DeregisterCollectionCompletesAfterFlatlandDestruction) { |
| sysmem_util::GlobalBufferCollectionId global_collection_id; |
| ContentId global_image_id; |
| { |
| Flatland flatland = CreateFlatland(); |
| |
| const ContentId kImageId = 3; |
| const BufferCollectionId kBufferCollectionId = 2; |
| ImageProperties properties; |
| properties.set_width(200); |
| properties.set_height(200); |
| auto global_id_pair = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties)); |
| global_collection_id = global_id_pair.collection_id; |
| global_image_id = global_id_pair.image_id; |
| |
| // Release the image. |
| flatland.ReleaseImage(kImageId); |
| |
| // Deregister the buffer collection. |
| flatland.DeregisterBufferCollection(kBufferCollectionId); |
| |
| // Skip session updates to test that release fences are what trigger the importer calls. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(0); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseImage(global_image_id)).Times(0); |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| // |flatland| falls out of scope. |
| } |
| |
| // Reset the last known reference to the BufferImporter to demonstrate that the Wait keeps it |
| // alive. |
| buffer_collection_importer_.reset(); |
| |
| // Signal the release fences, which triggers the deregistration call, even though the Flatland |
| // instance and Renderer associated with the call have been cleaned up. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id)) |
| .Times(1); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseImage(global_image_id)).Times(1); |
| ApplySessionUpdatesAndSignalFences(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(FlatlandTest, ReleaseImageErrorCases) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Zero is not a valid image ID. |
| flatland.ReleaseImage(0); |
| PRESENT(flatland, false); |
| |
| // The image must exist. |
| flatland.ReleaseImage(1); |
| PRESENT(flatland, false); |
| |
| // ContentId is not an Image. |
| ContentLinkToken parent_token; |
| GraphLinkToken child_token; |
| ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &parent_token.value, &child_token.value)); |
| |
| const ContentId kLinkId = 2; |
| |
| fidl::InterfacePtr<ContentLink> content_link; |
| LinkProperties properties; |
| properties.set_logical_size({kDefaultSize, kDefaultSize}); |
| flatland.CreateLink(kLinkId, std::move(parent_token), std::move(properties), |
| content_link.NewRequest()); |
| |
| flatland.ReleaseImage(kLinkId); |
| PRESENT(flatland, false); |
| } |
| |
| // If we have multiple BufferCollectionImporters, some of them may properly import |
| // a buffer collection while others do not. We have to therefore make sure that if |
| // improter A properly imports a buffer collection and then importer B fails, that |
| // Flatland automatically releases the buffer collection from importer A. |
| TEST_F(FlatlandTest, BufferCollectionImportPassesAndFailsOnDifferentImportersTest) { |
| // Create a second buffer collection importer. |
| auto local_mock_buffer_collection_importer = new MockBufferCollectionImporter(); |
| auto local_buffer_collection_importer = |
| std::shared_ptr<BufferCollectionImporter>(local_mock_buffer_collection_importer); |
| |
| // Create a flatland instance that has two BufferCollectionImporters. |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator; |
| auto session_id = scheduling::GetNextSessionId(); |
| auto flatland = Flatland(session_id, flatland_presenter_, link_system_, |
| uber_struct_system_->AllocateQueueForSession(session_id), |
| {buffer_collection_importer_, local_buffer_collection_importer}, |
| std::move(sysmem_allocator)); |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*local_mock_buffer_collection_importer, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(_)).Times(1); |
| EXPECT_CALL(*local_mock_buffer_collection_importer, ReleaseBufferCollection(_)).Times(0); |
| |
| const auto kCollectionId = 2; |
| flatland.RegisterBufferCollection(kCollectionId, CreateToken()); |
| } |
| |
| // If we have multiple BufferCollectionImporters, some of them may properly import |
| // an image while others do not. We have to therefore make sure that if importer A |
| // properly imports an image and then importer B fails, that Flatland automatically |
| // releases the image from importer A. |
| TEST_F(FlatlandTest, ImageImportPassesAndFailsOnDifferentImportersTest) { |
| // Create a second buffer collection importer. |
| auto local_mock_buffer_collection_importer = new MockBufferCollectionImporter(); |
| auto local_buffer_collection_importer = |
| std::shared_ptr<BufferCollectionImporter>(local_mock_buffer_collection_importer); |
| |
| // Create a flatland instance that has |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator; |
| auto session_id = scheduling::GetNextSessionId(); |
| auto flatland = Flatland(session_id, flatland_presenter_, link_system_, |
| uber_struct_system_->AllocateQueueForSession(session_id), |
| {buffer_collection_importer_, local_buffer_collection_importer}, |
| std::move(sysmem_allocator)); |
| |
| sysmem_util::GlobalBufferCollectionId global_collection_id; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce( |
| testing::Invoke([&global_collection_id]( |
| sysmem_util::GlobalBufferCollectionId collection_id, |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token) { |
| global_collection_id = collection_id; |
| return true; |
| })); |
| EXPECT_CALL(*local_mock_buffer_collection_importer, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(true)); |
| |
| const auto kCollectionId = 2; |
| flatland.RegisterBufferCollection(kCollectionId, CreateToken()); |
| |
| ImageProperties properties; |
| properties.set_width(100); |
| properties.set_height(200); |
| |
| // We have the first importer return true, signifying a successful import, and the second one |
| // returning false. This should trigger the first importer to call ReleaseImage(). |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*local_mock_buffer_collection_importer, ImportImage(_)).WillOnce(Return(false)); |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseImage(_)).WillOnce(Return()); |
| flatland.CreateImage(/*image_id*/ 1, kCollectionId, /*vmo_idx*/ 0, std::move(properties)); |
| } |
| |
| // Test to make sure that if a buffer collection importer returns |false| |
| // on |ImportImage()| that this is caught when we try to present. |
| TEST_F(FlatlandTest, BufferImporterImportImageReturnsFalseTest) { |
| Flatland flatland = CreateFlatland(); |
| |
| const auto kCollectionId = 3; |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportBufferCollection(_, _, _)) |
| .WillOnce(Return(true)); |
| flatland.RegisterBufferCollection(kCollectionId, CreateToken()); |
| |
| // Create a proper properties struct. |
| ImageProperties properties; |
| properties.set_width(150); |
| properties.set_height(175); |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).WillOnce(Return(true)); |
| |
| // We've imported a proper image and we have the importer returning true, so |
| // PRESENT should return true. |
| flatland.CreateImage(/*image_id*/ 1, kCollectionId, /*vmo_idx*/ 0, std::move(properties)); |
| PRESENT(flatland, true); |
| |
| // We're using the same buffer collection so we don't need to validate, only import. |
| EXPECT_CALL(*mock_buffer_collection_importer_, ImportImage(_)).WillOnce(Return(false)); |
| |
| // Import again, but this time have the importer return false. Flatland should catch |
| // this and PRESENT should return false. |
| properties.set_width(150); |
| properties.set_height(175); |
| flatland.CreateImage(/*image_id*/ 2, kCollectionId, /*vmo_idx*/ 0, std::move(properties)); |
| PRESENT(flatland, false); |
| } |
| |
| // Test to make sure that the release fences signal to the buffer importer |
| // to release the image. |
| TEST_F(FlatlandTest, BufferImporterImageReleaseTest) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id1 = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties1)).collection_id; |
| |
| // Create a transform, make it the root transform, and attach the image. |
| const TransformId kTransformId = 2; |
| |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| |
| // Now release the image. |
| flatland.ReleaseImage(kImageId); |
| PRESENT(flatland, true); |
| |
| // Now remove the image from the transform, which should result in it being |
| // garbage collected. |
| flatland.SetContentOnTransform(0, kTransformId); |
| PresentArgs args; |
| args.skip_session_update_and_release_fences = true; |
| PRESENT_WITH_ARGS(flatland, std::move(args), true); |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseImage(_)).Times(1); |
| ApplySessionUpdatesAndSignalFences(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(FlatlandTest, ReleasedImageRemainsUntilCleared) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties1)).collection_id; |
| |
| const auto maybe_image_handle = flatland.GetContentHandle(kImageId); |
| ASSERT_TRUE(maybe_image_handle.has_value()); |
| const auto image_handle = maybe_image_handle.value(); |
| |
| // Create a transform, make it the root transform, and attach the image. |
| const TransformId kTransformId = 2; |
| |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| |
| // The image handle should be the last handle in the local_topology, and the image should be in |
| // the image map. |
| auto uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.back().handle, image_handle); |
| |
| auto image_kv = uber_struct->images.find(image_handle); |
| EXPECT_NE(image_kv, uber_struct->images.end()); |
| EXPECT_EQ(image_kv->second.collection_id, global_collection_id); |
| |
| // Releasing the image succeeds, but all data remains in the UberStruct. |
| flatland.ReleaseImage(kImageId); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.back().handle, image_handle); |
| |
| image_kv = uber_struct->images.find(image_handle); |
| EXPECT_NE(image_kv, uber_struct->images.end()); |
| EXPECT_EQ(image_kv->second.collection_id, global_collection_id); |
| |
| // Clearing the Transform of its Image removes all references from the UberStruct. |
| flatland.SetContentOnTransform(0, kTransformId); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| for (const auto& entry : uber_struct->local_topology) { |
| EXPECT_NE(entry.handle, image_handle); |
| } |
| |
| EXPECT_FALSE(uber_struct->images.count(image_handle)); |
| } |
| |
| TEST_F(FlatlandTest, ReleasedImageIdCanBeReused) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id1 = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties1)).collection_id; |
| |
| const auto maybe_image_handle1 = flatland.GetContentHandle(kImageId); |
| ASSERT_TRUE(maybe_image_handle1.has_value()); |
| const auto image_handle1 = maybe_image_handle1.value(); |
| |
| // Create a transform, make it the root transform, attach the image, then release it. |
| const TransformId kTransformId1 = 2; |
| |
| flatland.CreateTransform(kTransformId1); |
| flatland.SetRootTransform(kTransformId1); |
| flatland.SetContentOnTransform(kImageId, kTransformId1); |
| flatland.ReleaseImage(kImageId); |
| PRESENT(flatland, true); |
| |
| // The ContentId can be re-used even though the old image is still present. Add a second |
| // transform so that both images show up in the global image vector. |
| ImageProperties properties2; |
| properties2.set_width(300); |
| properties2.set_height(400); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id2 = |
| CreateImage(&flatland, kImageId, 2, std::move(properties2)).collection_id; |
| |
| const TransformId kTransformId2 = 3; |
| |
| flatland.CreateTransform(kTransformId2); |
| flatland.AddChild(kTransformId1, kTransformId2); |
| flatland.SetContentOnTransform(kImageId, kTransformId2); |
| PRESENT(flatland, true); |
| |
| const auto maybe_image_handle2 = flatland.GetContentHandle(kImageId); |
| ASSERT_TRUE(maybe_image_handle2.has_value()); |
| const auto image_handle2 = maybe_image_handle2.value(); |
| |
| // Both images should appear in the image map. |
| auto uber_struct = GetUberStruct(flatland); |
| |
| auto image_kv1 = uber_struct->images.find(image_handle1); |
| EXPECT_NE(image_kv1, uber_struct->images.end()); |
| EXPECT_EQ(image_kv1->second.collection_id, global_collection_id1); |
| |
| auto image_kv2 = uber_struct->images.find(image_handle2); |
| EXPECT_NE(image_kv2, uber_struct->images.end()); |
| EXPECT_EQ(image_kv2->second.collection_id, global_collection_id2); |
| } |
| |
| // Test that released Images, when attached to a Transform, are not garbage collected even if |
| // the Transform is not part of the most recently presented global topology. |
| TEST_F(FlatlandTest, ReleasedImagePersistsOutsideGlobalTopology) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id1 = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties1)).collection_id; |
| |
| const auto maybe_image_handle = flatland.GetContentHandle(kImageId); |
| ASSERT_TRUE(maybe_image_handle.has_value()); |
| const auto image_handle = maybe_image_handle.value(); |
| |
| // Create a transform, make it the root transform, attach the image, then release it. |
| const TransformId kTransformId = 2; |
| |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| flatland.ReleaseImage(kImageId); |
| PRESENT(flatland, true); |
| |
| // Remove the entire hierarchy, then verify that the image is still present. |
| flatland.SetRootTransform(0); |
| PRESENT(flatland, true); |
| |
| auto uber_struct = GetUberStruct(flatland); |
| auto image_kv = uber_struct->images.find(image_handle); |
| EXPECT_NE(image_kv, uber_struct->images.end()); |
| EXPECT_EQ(image_kv->second.collection_id, global_collection_id1); |
| |
| // Reintroduce the hierarchy and confirm the Image is still present, even though it was |
| // temporarily not reachable from the root transform. |
| flatland.SetRootTransform(kTransformId); |
| PRESENT(flatland, true); |
| |
| uber_struct = GetUberStruct(flatland); |
| EXPECT_EQ(uber_struct->local_topology.back().handle, image_handle); |
| |
| image_kv = uber_struct->images.find(image_handle); |
| EXPECT_NE(image_kv, uber_struct->images.end()); |
| EXPECT_EQ(image_kv->second.collection_id, global_collection_id1); |
| } |
| |
| TEST_F(FlatlandTest, ClearGraphReleasesImagesAndBufferCollections) { |
| Flatland flatland = CreateFlatland(); |
| |
| // Setup a valid image. |
| const ContentId kImageId = 1; |
| const BufferCollectionId kBufferCollectionId = 1; |
| |
| ImageProperties properties1; |
| properties1.set_width(100); |
| properties1.set_height(200); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id1 = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties1)).collection_id; |
| |
| // Create a transform, make it the root transform, and attach the Image. |
| const TransformId kTransformId = 2; |
| |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| |
| // Clear the graph, then signal the release fence and ensure the buffer collection is released. |
| flatland.ClearGraph(); |
| |
| EXPECT_CALL(*mock_buffer_collection_importer_, ReleaseBufferCollection(global_collection_id1)) |
| .Times(1); |
| PRESENT(flatland, true); |
| |
| // The buffer collection and Image ID should be available for re-use. |
| ImageProperties properties2; |
| properties2.set_width(400); |
| properties2.set_height(800); |
| |
| const sysmem_util::GlobalBufferCollectionId global_collection_id2 = |
| CreateImage(&flatland, kImageId, kBufferCollectionId, std::move(properties2)).collection_id; |
| |
| EXPECT_NE(global_collection_id1, global_collection_id2); |
| |
| // Verify that the Image is valid and can be attached to a transform. |
| flatland.CreateTransform(kTransformId); |
| flatland.SetRootTransform(kTransformId); |
| flatland.SetContentOnTransform(kImageId, kTransformId); |
| PRESENT(flatland, true); |
| } |
| |
| #undef EXPECT_MATRIX |
| #undef PRESENT |
| |
| } // namespace test |
| } // namespace flatland |