| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/ozone/platform/wayland/host/wayland_surface.h" |
| |
| #include <drm_fourcc.h> |
| #include <wayland-server-core.h> |
| #include <wayland-server-protocol.h> |
| |
| #include "base/posix/eintr_wrapper.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/gpu_fence_handle.h" |
| #include "ui/ozone/platform/wayland/common/wayland_object.h" |
| #include "ui/ozone/platform/wayland/host/wayland_buffer_handle.h" |
| #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h" |
| #include "ui/ozone/platform/wayland/host/wayland_screen.h" |
| #include "ui/ozone/platform/wayland/host/wayland_syncobj_timeline.h" |
| #include "ui/ozone/platform/wayland/test/mock_drm_syncobj_ioctl_wrapper.h" |
| #include "ui/ozone/platform/wayland/test/mock_surface.h" |
| #include "ui/ozone/platform/wayland/test/mock_zwp_linux_dmabuf.h" |
| #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" |
| #include "ui/ozone/platform/wayland/test/test_zwp_linux_buffer_params.h" |
| #include "ui/ozone/platform/wayland/test/wayland_test.h" |
| |
| namespace ui { |
| namespace { |
| |
| using ::testing::ElementsAre; |
| |
| using testing::_; |
| using testing::Mock; |
| |
| constexpr gfx::Size kDefaultSize(1024, 768); |
| |
| MATCHER_P(IsSameFile, fd2, "") { |
| struct stat stat1, stat2; |
| if (fstat(arg, &stat1) == -1 || fstat(fd2, &stat2) == -1) { |
| return false; |
| } |
| return (stat1.st_ino == stat2.st_ino) && (stat1.st_dev == stat2.st_dev); |
| } |
| |
| using WaylandSurfaceTest = WaylandTest; |
| |
| TEST_P(WaylandSurfaceTest, SurfaceReenterOutput) { |
| WaylandSurface* wayland_surface = window_->root_surface(); |
| EXPECT_TRUE(wayland_surface->entered_outputs().empty()); |
| |
| // Client side WaylandOutput id. |
| const uint32_t output_id = |
| screen_->GetOutputIdForDisplayId(screen_->GetPrimaryDisplay().id()); |
| |
| const uint32_t surface_id_ = window_->root_surface()->get_surface_id(); |
| |
| PostToServerAndWait([surface_id_](wl::TestWaylandServerThread* server) { |
| wl_surface_send_enter( |
| server->GetObject<wl::MockSurface>(surface_id_)->resource(), |
| server->output()->resource()); |
| }); |
| EXPECT_THAT(wayland_surface->entered_outputs(), ElementsAre(output_id)); |
| |
| // Send enter again, but entered outputs should not have duplicate values. |
| PostToServerAndWait([surface_id_](wl::TestWaylandServerThread* server) { |
| wl_surface_send_enter( |
| server->GetObject<wl::MockSurface>(surface_id_)->resource(), |
| server->output()->resource()); |
| }); |
| EXPECT_THAT(wayland_surface->entered_outputs(), ElementsAre(output_id)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, |
| WaylandSurfaceTest, |
| ::testing::Values(wl::ServerConfig{})); |
| |
| } // namespace |
| |
| class WaylandSurfaceExplicitSyncTest : public WaylandTestSimple { |
| public: |
| WaylandSurfaceExplicitSyncTest() |
| : WaylandTestSimple( |
| wl::ServerConfig{.use_linux_drm_syncobj = |
| wl::ShouldUseLinuxDrmSyncobjProtocol::kUse}) {} |
| |
| void SetUp() override { |
| WaylandTestSimple::SetUp(); |
| surface_id_ = window_->root_surface()->get_surface_id(); |
| auto drm = std::make_unique<MockDrmSyncobjIoctlWrapper>(); |
| drm_ = drm.get(); |
| connection_->buffer_manager_host()->SetDrmSyncobjWrapper(std::move(drm)); |
| EXPECT_TRUE(connection_->buffer_manager_host()); |
| auto interface_ptr = connection_->buffer_manager_host()->BindInterface(); |
| buffer_manager_gpu_->Initialize(std::move(interface_ptr), {}, |
| /*supports_dma_buf=*/false, |
| /*supports_viewporter=*/true, |
| /*supports_acquire_fence=*/false, |
| /*supports_overlays=*/true, |
| /*supports_single_pixel_buffer=*/true); |
| } |
| |
| void CreateDmabufBasedBuffer(uint32_t buffer_id, |
| const gfx::Size& size = kDefaultSize, |
| const std::vector<uint32_t>& strides = {1}, |
| const std::vector<uint32_t>& offsets = {2}, |
| const std::vector<uint64_t>& modifiers = {3}, |
| uint32_t format = DRM_FORMAT_R8, |
| uint32_t planes_count = 1) { |
| buffer_manager_gpu_->CreateDmabufBasedBuffer( |
| GetFdFactory()->CreateFd(), kDefaultSize, strides, offsets, modifiers, |
| format, planes_count, gfx::ColorSpace(), gfx::HDRMetadata(), buffer_id); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| PostToServerAndWait([](wl::TestWaylandServerThread* server) { |
| auto params_vector = server->zwp_linux_dmabuf_v1()->buffer_params(); |
| // To ensure, no other buffers are created, test the size of the |
| // vector. |
| EXPECT_EQ(params_vector.size(), static_cast<unsigned>(1)); |
| |
| for (wl::TestZwpLinuxBufferParamsV1* mock_params : params_vector) { |
| zwp_linux_buffer_params_v1_send_created(mock_params->resource(), |
| mock_params->buffer_resource()); |
| } |
| }); |
| } |
| |
| protected: |
| gfx::GpuFenceHandle CreateFence() { |
| auto fd = fd_factory_->CreateFd(); |
| gfx::GpuFenceHandle fence; |
| fence.Adopt(std::move(fd)); |
| return fence; |
| } |
| |
| wl::TestFdFactory* GetFdFactory() { |
| if (!fd_factory_) { |
| fd_factory_ = std::make_unique<wl::TestFdFactory>(); |
| } |
| return fd_factory_.get(); |
| } |
| |
| void VerifyAndClearExpectations() { |
| Mock::VerifyAndClearExpectations(drm_); |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| Mock::VerifyAndClearExpectations( |
| server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface()); |
| }); |
| } |
| |
| uint32_t surface_id_; |
| raw_ptr<MockDrmSyncobjIoctlWrapper> drm_ = nullptr; |
| |
| private: |
| std::unique_ptr<wl::TestFdFactory> fd_factory_; |
| }; |
| |
| TEST_F(WaylandSurfaceExplicitSyncTest, ConfigureWithExplicitSync) { |
| constexpr uint32_t kBufferId = 1; |
| const auto& syncobjs = drm_->syncobjs(); |
| constexpr uint32_t kBufferReleaseSyncObj = 1; |
| |
| // Create a buffer. |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| // Creating the buffer should create the first syncobj for the release |
| // timeline. |
| EXPECT_EQ(drm_->syncobjs().size(), static_cast<unsigned>(1)); |
| auto it = syncobjs.find(kBufferReleaseSyncObj); |
| EXPECT_NE(it, syncobjs.end()); |
| int buffer_release_syncobj = it->second.get(); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->set_acquire_fence(CreateFence()); |
| |
| surface->EnsureAcquireTimeline(); |
| |
| // The second syncobj is created for the surface acquire timeline. |
| EXPECT_EQ(drm_->syncobjs().size(), static_cast<unsigned>(2)); |
| constexpr uint32_t kSurfaceAcquireSyncObj = 2; |
| it = syncobjs.find(kSurfaceAcquireSyncObj); |
| EXPECT_NE(it, syncobjs.end()); |
| int surface_acquire_syncobj = it->second.get(); |
| |
| surface->EnsureSurfaceSync(); |
| |
| PostToServerAndWait([this, surface_acquire_syncobj, buffer_release_syncobj]( |
| wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, |
| SetAcquirePoint(IsSameFile(surface_acquire_syncobj), 1)); |
| EXPECT_CALL(*syncobj_surface, |
| SetReleasePoint(IsSameFile(buffer_release_syncobj), 1)); |
| }); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| |
| // There should still only be 2 syncobjs. |
| EXPECT_EQ(drm_->syncobjs().size(), static_cast<unsigned>(2)); |
| } |
| |
| TEST_F(WaylandSurfaceExplicitSyncTest, ConfigureWithExplicitSyncAndCommit) { |
| constexpr int kCurrentSyncPoint = 1; |
| constexpr uint32_t kBufferId = 1; |
| |
| // Create a buffer. |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->set_acquire_fence(CreateFence()); |
| |
| base::RunLoop run_loop; |
| wl_buffer* buffer = nullptr; |
| base::ScopedFD fd; |
| |
| auto callback = base::BindOnce( |
| [](base::RepeatingClosure quit_closure, wl_buffer** buffer_out, |
| base::ScopedFD* fd_out, wl_buffer* buffer, base::ScopedFD fd) { |
| *buffer_out = buffer; |
| *fd_out = std::move(fd); |
| quit_closure.Run(); |
| }, |
| run_loop.QuitClosure(), &buffer, &fd); |
| |
| surface->RequestExplicitRelease(std::move(callback)); |
| |
| // Explicit sync should be set when pending state is applied. |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| |
| // When eventfd is set notify fence is available immediately by writing to it. |
| EXPECT_CALL(*drm_, SyncobjEventfd(_, kCurrentSyncPoint, _, _)) |
| .WillOnce([](uint32_t, uint64_t, int ev_fd, uint32_t) { |
| uint64_t value = 1; |
| HANDLE_EINTR(write(ev_fd, &value, sizeof(value))); |
| return 0; |
| }); |
| |
| // Ensure exporting current sync point works. |
| base::ScopedFD sync_file_fd = GetFdFactory()->CreateFd(); |
| EXPECT_CALL(*drm_, SyncobjExportSyncFile(_, _)) |
| .WillOnce([fd = sync_file_fd.get()](uint32_t, int* sync_file_fd) { |
| *sync_file_fd = HANDLE_EINTR(dup(fd)); |
| return 0; |
| }); |
| |
| surface->Commit(); |
| connection_->Flush(); |
| run_loop.Run(); |
| EXPECT_EQ(connection_->buffer_manager_host() |
| ->GetBufferHandle(surface, kBufferId) |
| ->buffer(), |
| buffer); |
| EXPECT_TRUE(fd.is_valid()); |
| } |
| |
| // Tests that if acquire timeline creation fails, explicit sync is not set and |
| // explicit release callback is cleared. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitSyncNotSet_AcquireTimelineCreationFailed) { |
| constexpr uint32_t kBufferId = 1; |
| |
| // Create a buffer. |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->set_acquire_fence(CreateFence()); |
| |
| surface->EnsureSurfaceSync(); |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, SetAcquirePoint(_, _)).Times(0); |
| EXPECT_CALL(*syncobj_surface, SetReleasePoint(_, _)).Times(0); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| // No acquire timeline should be set at this point. |
| EXPECT_FALSE(surface->acquire_timeline_); |
| { |
| // Ensure acquire timeline creation fails when applying pending state for |
| // the first time. |
| base::AutoReset<bool> fail_on_create( |
| &MockDrmSyncobjIoctlWrapper::fail_on_syncobj_create, true); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| } |
| VerifyAndClearExpectations(); |
| EXPECT_TRUE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| // Tests that if release timeline creation fails, explicit sync is not set and |
| // explicit release callback is cleared. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitSyncNotSet_ReleaseTimelineCreationFailed) { |
| constexpr uint32_t kBufferId = 1; |
| { |
| // Ensure release timeline creation fails when creating the buffer. |
| base::AutoReset<bool> fail_on_create( |
| &MockDrmSyncobjIoctlWrapper::fail_on_syncobj_create, true); |
| CreateDmabufBasedBuffer(kBufferId); |
| } |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->set_acquire_fence(CreateFence()); |
| |
| surface->EnsureSurfaceSync(); |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, SetAcquirePoint(_, _)).Times(0); |
| EXPECT_CALL(*syncobj_surface, SetReleasePoint(_, _)).Times(0); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| EXPECT_TRUE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| // Tests that if initial acquire fence is not set, explicit sync is not set and |
| // explicit release callback is cleared. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitSyncNotSet_InitialAcquireFenceNotSet) { |
| constexpr uint32_t kBufferId = 1; |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| |
| surface->EnsureSurfaceSync(); |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, SetAcquirePoint(_, _)).Times(0); |
| EXPECT_CALL(*syncobj_surface, SetReleasePoint(_, _)).Times(0); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| EXPECT_TRUE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| // Tests that if initial acquire fence import fails, explicit sync is not set |
| // and explicit release callback is cleared. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitNotSyncSet_InitialAcquireFenceImportFail) { |
| constexpr uint32_t kBufferId = 1; |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->EnsureAcquireTimeline(); |
| |
| surface->set_acquire_fence(CreateFence()); |
| |
| surface->EnsureSurfaceSync(); |
| |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, SetAcquirePoint(_, _)).Times(0); |
| EXPECT_CALL(*syncobj_surface, SetReleasePoint(_, _)).Times(0); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| |
| EXPECT_CALL(*drm_, SyncobjImportSyncFile(_, _)).WillOnce([] { |
| return ENOENT; |
| }); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| EXPECT_TRUE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| // Tests that previous acquire sync point is used if a fence is not passed in a |
| // subsequent frame. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitSyncSet_SubsequentAcquireFenceNotSet) { |
| constexpr uint32_t kBufferId = 1; |
| CreateDmabufBasedBuffer(kBufferId); |
| constexpr uint32_t kBufferReleaseSyncObj = 1; |
| int buffer_release_syncobj = drm_->syncobjs().at(kBufferReleaseSyncObj).get(); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->EnsureAcquireTimeline(); |
| // Ensure initial acquire sync point is set to 1. |
| surface->acquire_timeline_->IncrementSyncPoint(); |
| |
| surface->EnsureSurfaceSync(); |
| constexpr uint32_t kSurfaceAcquireSyncObj = 2; |
| int surface_acquire_syncobj = |
| drm_->syncobjs().at(kSurfaceAcquireSyncObj).get(); |
| |
| PostToServerAndWait([this, surface_acquire_syncobj, buffer_release_syncobj]( |
| wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, |
| SetAcquirePoint(IsSameFile(surface_acquire_syncobj), 1)); |
| EXPECT_CALL(*syncobj_surface, |
| SetReleasePoint(IsSameFile(buffer_release_syncobj), 1)); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| EXPECT_TRUE(surface->ApplyPendingState().value()); |
| VerifyAndClearExpectations(); |
| EXPECT_FALSE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| // Tests that explicit sync is not set and ApplySurfaceConfigure fails if fence |
| // is passed but importing the fence fails. |
| TEST_F(WaylandSurfaceExplicitSyncTest, |
| ExplicitSyncNotSet_SubsequentAcquireFenceImportFail) { |
| constexpr uint32_t kBufferId = 1; |
| CreateDmabufBasedBuffer(kBufferId); |
| |
| auto* surface = window_->root_surface(); |
| EXPECT_TRUE(surface->AttachBuffer( |
| connection_->buffer_manager_host()->EnsureBufferHandle(surface, |
| kBufferId))); |
| surface->EnsureAcquireTimeline(); |
| // Ensure initial acquire sync point is set to 1. |
| surface->acquire_timeline_->IncrementSyncPoint(); |
| |
| surface->set_acquire_fence(CreateFence()); |
| |
| surface->EnsureSurfaceSync(); |
| |
| PostToServerAndWait([this](wl::TestWaylandServerThread* server) { |
| auto* syncobj_surface = server->GetObject<wl::MockSurface>(surface_id_) |
| ->linux_drm_syncobj_surface(); |
| EXPECT_TRUE(syncobj_surface); |
| EXPECT_CALL(*syncobj_surface, SetAcquirePoint(_, _)).Times(0); |
| EXPECT_CALL(*syncobj_surface, SetReleasePoint(_, _)).Times(0); |
| }); |
| |
| surface->RequestExplicitRelease( |
| base::BindOnce([](wl_buffer*, base::ScopedFD) {})); |
| |
| EXPECT_CALL(*drm_, SyncobjImportSyncFile(_, _)).WillOnce([] { |
| return ENOENT; |
| }); |
| EXPECT_FALSE(surface->ApplyPendingState().has_value()); |
| VerifyAndClearExpectations(); |
| EXPECT_TRUE(surface->next_explicit_release_request_.is_null()); |
| } |
| |
| } // namespace ui |