blob: b6fa634b3581bd4b68febc9cfb1c0806a624c630 [file] [log] [blame]
// Copyright 2025 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 <dlfcn.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <atomic>
#include <set>
#include <gtest/gtest.h>
#include <vulkan/vulkan.h>
#include "sdk/lib/ui/scenic/cpp/view_creation_tokens.h"
#include "src/lib/fsl/handles/object_info.h"
namespace {
uint64_t ZirconIdFromHandle(uint32_t handle) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK)
return 0;
return info.koid;
}
// FakeFlatland runs async loop on its own thread to allow the test
// to use blocking Vulkan calls while present callbacks are processed.
class FakeFlatland : public fuchsia::ui::composition::testing::Allocator_TestBase,
public fuchsia::ui::composition::testing::Flatland_TestBase,
public fuchsia::ui::composition::testing::ParentViewportWatcher_TestBase {
public:
FakeFlatland(fidl::InterfaceRequest<fuchsia::ui::composition::Allocator> allocator_request,
fidl::InterfaceRequest<fuchsia::ui::composition::Flatland> flatland_request,
bool should_present)
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
allocator_binding_(this, std::move(allocator_request), loop_.dispatcher()),
flatland_binding_(this, std::move(flatland_request), loop_.dispatcher()),
should_present_(should_present) {
loop_.StartThread("fake flatland");
}
~FakeFlatland() { loop_.Shutdown(); }
void NotImplemented_(const std::string& name) override {
fprintf(stderr, "NotImplemented: %s\n", name.c_str());
}
// |fuchsia::ui::composition::testing::Allocator|
void RegisterBufferCollection(fuchsia::ui::composition::RegisterBufferCollectionArgs args,
RegisterBufferCollectionCallback callback) override {
EXPECT_EQ(fuchsia::ui::composition::RegisterBufferCollectionUsage::DEFAULT, args.usage());
auto [_, import_token_koid] = fsl::GetKoids(args.export_token().value.get());
registered_koids.insert(import_token_koid);
fuchsia::sysmem2::AllocatorSyncPtr sysmem_allocator;
zx_status_t status = fdio_service_connect(
"/svc/fuchsia.sysmem2.Allocator", sysmem_allocator.NewRequest().TakeChannel().release());
EXPECT_EQ(status, ZX_OK);
sysmem_allocator->SetDebugClientInfo(
std::move(fuchsia::sysmem2::AllocatorSetDebugClientInfoRequest{}
.set_name(fsl::GetCurrentProcessName())
.set_id(fsl::GetCurrentProcessKoid())));
// Exactly one of these must be set.
EXPECT_TRUE(!!args.has_buffer_collection_token2() ^ !!args.has_buffer_collection_token());
fuchsia::sysmem2::BufferCollectionTokenHandle token;
if (args.has_buffer_collection_token2()) {
token = std::move(*args.mutable_buffer_collection_token2());
} else {
token = fuchsia::sysmem2::BufferCollectionTokenHandle(
args.mutable_buffer_collection_token()->TakeChannel());
}
fuchsia::sysmem2::BufferCollectionSyncPtr buffer_collection;
status = sysmem_allocator->BindSharedCollection(
std::move(fuchsia::sysmem2::AllocatorBindSharedCollectionRequest{}
.set_token(std::move(token))
.set_buffer_collection_request(buffer_collection.NewRequest())));
EXPECT_EQ(status, ZX_OK);
status = buffer_collection->SetConstraints(
fuchsia::sysmem2::BufferCollectionSetConstraintsRequest{});
EXPECT_EQ(status, ZX_OK);
status = buffer_collection->Release();
EXPECT_EQ(status, ZX_OK);
callback(fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result::WithResponse({}));
}
// |fuchsia::ui::composition::testing::Flatland|
void SetDebugName(std::string debug_name) override {
// Do nothing.
}
// |fuchsia::ui::composition::testing::Flatland|
void CreateView(fuchsia::ui::views::ViewCreationToken token,
fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
parent_viewport_watcher) override {
// Do nothing.
}
// |fuchsia::ui::composition::testing::Flatland|
void CreateView2(fuchsia::ui::views::ViewCreationToken token,
fuchsia::ui::views::ViewIdentityOnCreation view_identity,
fuchsia::ui::composition::ViewBoundProtocols view_protocols,
fidl::InterfaceRequest<fuchsia::ui::composition::ParentViewportWatcher>
parent_viewport_watcher) override {
// Do nothing.
}
// |fuchsia::ui::composition::testing::Flatland|
void CreateImage(fuchsia::ui::composition::ContentId image_id,
fuchsia::ui::composition::BufferCollectionImportToken import_token,
uint32_t vmo_index,
fuchsia::ui::composition::ImageProperties properties) override {
auto [import_token_koid, _] = fsl::GetKoids(import_token.value.get());
EXPECT_TRUE(registered_koids.find(import_token_koid) != registered_koids.end());
EXPECT_TRUE(registered_images.find(image_id.value) == registered_images.end());
registered_images.insert(image_id.value);
}
// |fuchsia::ui::composition::testing::Flatland|
void ReleaseImage(fuchsia::ui::composition::ContentId image_id) override {
EXPECT_TRUE(registered_images.find(image_id.value) != registered_images.end());
registered_images.erase(image_id.value);
}
// |fuchsia::ui::composition::testing::Flatland|
void CreateTransform(fuchsia::ui::composition::TransformId transform_id) override {
EXPECT_EQ(kRootTransform.value, transform_id.value);
}
// |fuchsia::ui::composition::testing::Flatland|
void SetRootTransform(fuchsia::ui::composition::TransformId transform_id) override {
EXPECT_EQ(kRootTransform.value, transform_id.value);
}
// |fuchsia::ui::composition::testing::Flatland|
void SetImageDestinationSize(fuchsia::ui::composition::ContentId image_id,
fuchsia::math::SizeU size) override {
EXPECT_TRUE(registered_images.find(image_id.value) != registered_images.end());
}
// |fuchsia::ui::composition::testing::Flatland|
void SetContent(fuchsia::ui::composition::TransformId transform_id,
fuchsia::ui::composition::ContentId content_id) override {
EXPECT_EQ(kRootTransform.value, transform_id.value);
EXPECT_TRUE(registered_images.find(content_id.value) != registered_images.end());
image_on_root_transform_ = content_id.value;
}
static bool squashable(const fuchsia::ui::composition::PresentArgs& args) {
return !args.has_unsquashable() || !args.unsquashable();
}
// |fuchsia::ui::composition::testing::Flatland|
void Present(fuchsia::ui::composition::PresentArgs args) override {
std::unique_lock<std::mutex> lock(mutex_);
ASSERT_EQ(args.acquire_fences().size(), 1U);
acquire_fences_.insert(ZirconIdFromHandle(args.acquire_fences()[0].get()));
zx_signals_t pending;
zx_status_t status = args.acquire_fences()[0].wait_one(
ZX_EVENT_SIGNALED, zx::deadline_after(zx::sec(10)), &pending);
if (status != ZX_OK || !should_present_) {
presented_.push_back({image_on_root_transform_, status});
return;
}
pending_present_count_++;
if (!squashable(args) || pending_present_count_ == 1) {
static zx::time last_present_time;
zx::time current_time = zx::clock::get_monotonic();
zx::duration time_since_last_present = current_time - last_present_time;
zx::duration time_to_next_vsync = zx::usec(16666) - time_since_last_present;
if (time_to_next_vsync < zx::duration(0)) {
time_to_next_vsync = zx::duration(0);
}
last_present_time = current_time + time_to_next_vsync;
zx_status_t status = async::PostDelayedTask(
loop_.dispatcher(),
[this, args = std::move(args)]() mutable {
PresentImpl(image_on_root_transform_, std::move(args));
},
time_to_next_vsync);
// Loop may be shutting down.
EXPECT_TRUE(status == ZX_OK || status == ZX_ERR_BAD_STATE);
} else {
dropped_frame_count_ += 1;
if (!args.release_fences().empty()) {
args.release_fences()[0].signal(0, ZX_EVENT_SIGNALED);
}
}
}
void PresentImpl(uint64_t image_id, fuchsia::ui::composition::PresentArgs args) {
std::unique_lock<std::mutex> lock(mutex_);
if (!args.release_fences().empty()) {
args.release_fences()[0].signal(0, ZX_EVENT_SIGNALED);
}
bool first_time = presented_.empty();
// When the client is sending squashable presents assume they're trying to maximize fps,
// so allow lots of present credits.
uint32_t present_credits = (first_time && squashable(args)) ? 10 : pending_present_count_;
// Run OnNextFrameBegin callback.
fuchsia::ui::composition::OnNextFrameBeginValues values;
values.set_additional_present_credits(present_credits);
flatland_binding_.events().OnNextFrameBegin(std::move(values));
pending_present_count_ = 0;
presented_.push_back({image_id, ZX_OK});
// Run OnFramePresented callback.
fuchsia::scenic::scheduling::FramePresentedInfo frame_presented_info;
fuchsia::scenic::scheduling::PresentReceivedInfo received_info;
frame_presented_info.presentation_infos.emplace_back(std::move(received_info));
flatland_binding_.events().OnFramePresented(std::move(frame_presented_info));
}
uint32_t presented_count() {
std::unique_lock<std::mutex> lock(mutex_);
return static_cast<uint32_t>(presented_.size());
}
uint32_t acquire_fences_count() {
std::unique_lock<std::mutex> lock(mutex_);
return static_cast<uint32_t>(acquire_fences_.size());
}
int dropped_frame_count() { return dropped_frame_count_.load(); }
struct Presented {
uint64_t image_id;
zx_status_t acquire_wait_status;
};
private:
async::Loop loop_;
fidl::Binding<fuchsia::ui::composition::Allocator> allocator_binding_;
fidl::Binding<fuchsia::ui::composition::Flatland> flatland_binding_;
const fuchsia::ui::composition::TransformId kRootTransform = {1};
std::set<zx_koid_t> registered_koids;
std::set<uint64_t> registered_images;
uint64_t image_on_root_transform_ = 0;
const bool should_present_;
std::mutex mutex_;
std::vector<Presented> presented_;
std::set<uint64_t> acquire_fences_;
int pending_present_count_ = 0;
std::atomic_int dropped_frame_count_ = 0;
};
void GetFakeFlatlandInjectedToLib(std::unique_ptr<FakeFlatland>* flatland, bool should_present) {
// Instantiate a FakeFlatland. Pass it InterfaceRequests for the
// Allocator and Flatland protocols, and inject the client ends of
// these channels into static variables in VkLayer_image_pipe_swapchain.so,
// so that they can be called by Vulkan.
zx::channel local_allocator_endpoint, remote_allocator_endpoint;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &local_allocator_endpoint, &remote_allocator_endpoint));
zx::channel local_flatland_endpoint, remote_flatland_endpoint;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &local_flatland_endpoint, &remote_flatland_endpoint));
// Inject it to vulkan swapchain lib.
void* libvulkan = dlopen("VkLayer_image_pipe_swapchain.so", RTLD_NOW | RTLD_LOCAL);
ASSERT_NE(libvulkan, nullptr);
typedef bool (*imagepipe_initialize_service_channel_fn)(zx::channel, zx::channel);
auto fn = reinterpret_cast<imagepipe_initialize_service_channel_fn>(
dlsym(libvulkan, "imagepipe_initialize_service_channel"));
ASSERT_NE(fn, nullptr);
ASSERT_TRUE(fn(std::move(remote_allocator_endpoint), std::move(remote_flatland_endpoint)));
*flatland =
std::make_unique<FakeFlatland>(fidl::InterfaceRequest<fuchsia::ui::composition::Allocator>(
std::move(local_allocator_endpoint)),
fidl::InterfaceRequest<fuchsia::ui::composition::Flatland>(
std::move(local_flatland_endpoint)),
should_present);
}
} // namespace