blob: 26bdcec7a1aa31452d7fbe0b0bfa6387f4c29bc9 [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 "src/graphics/display/drivers/coordinator/waiting-image-list.h"
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/scoped_global_logger.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fit/defer.h>
#include <fbl/ref_ptr.h>
#include <gtest/gtest.h>
#include "src/graphics/display/drivers/coordinator/image-lifecycle-listener.h"
#include "src/graphics/display/drivers/coordinator/image.h"
#include "src/graphics/display/lib/api-types/cpp/driver-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/image-id.h"
#include "src/graphics/display/lib/api-types/cpp/image-metadata.h"
#include "src/graphics/display/lib/api-types/cpp/image-tiling-type.h"
#include "src/lib/testing/predicates/status.h"
namespace display_coordinator {
namespace {
class StubImageLifecycleListener : public ImageLifecycleListener {
public:
StubImageLifecycleListener() = default;
~StubImageLifecycleListener() = default;
StubImageLifecycleListener(const StubImageLifecycleListener&) = delete;
StubImageLifecycleListener& operator=(const StubImageLifecycleListener&) = delete;
// ImageLifecycleListener:
void ImageWillBeDestroyed(display::DriverImageId driver_image_id) override {}
};
class FakeFenceListener : public FenceListener {
public:
// `waiting_images` must not be null and must outlive this instance.
explicit FakeFenceListener(WaitingImageList* waiting_images) : waiting_images_(*waiting_images) {
ZX_DEBUG_ASSERT(waiting_images != nullptr);
}
FakeFenceListener(const FakeFenceListener&) = delete;
FakeFenceListener& operator=(const FakeFenceListener&) = delete;
~FakeFenceListener() override = default;
// `FenceListener`:
void OnFenceSignaled(Fence& fence) override { waiting_images_.MarkFenceReady(fence); }
private:
WaitingImageList& waiting_images_;
};
class WaitingImageListTest : public ::testing::Test {
public:
WaitingImageListTest()
: fences_(&fence_listener_, driver_runtime_.GetForegroundDispatcher()->borrow()) {}
~WaitingImageListTest() override = default;
fbl::RefPtr<Image> CreateImage() {
static constexpr ClientId kClientId(1);
static constexpr display::ImageMetadata kImageMetadata({
.width = 100,
.height = 200,
.tiling_type = display::ImageTilingType::kLinear,
});
display::ImageId image_id = next_image_id_;
++next_image_id_;
display::DriverImageId driver_image_id = next_driver_image_id_;
++next_driver_image_id_;
fbl::RefPtr<Image> image = fbl::AdoptRef(new Image(
&image_lifecycle_listener_, kImageMetadata, image_id, driver_image_id, nullptr, kClientId));
return image;
}
WaitingImageList& waiting_images() { return waiting_images_; }
FenceCollection& fences() { return fences_; }
protected:
fdf_testing::ScopedGlobalLogger logger_;
fdf_testing::DriverRuntime driver_runtime_;
display::ImageId next_image_id_ = display::ImageId(1000);
display::DriverImageId next_driver_image_id_ = display::DriverImageId(2000);
StubImageLifecycleListener image_lifecycle_listener_;
FakeFenceListener fence_listener_{&waiting_images_};
WaitingImageList waiting_images_;
FenceCollection fences_;
};
TEST_F(WaitingImageListTest, AddTooManyImages) {
fbl::RefPtr<Image> image = CreateImage();
// Add maximum number.
for (size_t i = 0; i < WaitingImageList::kMaxSize; ++i) {
EXPECT_OK(waiting_images().PushImage(image, nullptr));
}
// Try to add one too many.
{
zx::result<> result = waiting_images().PushImage(image, nullptr);
ASSERT_FALSE(result.is_ok());
EXPECT_STATUS(result.status_value(), ZX_ERR_BAD_STATE);
EXPECT_EQ(waiting_images().size(), WaitingImageList::kMaxSize);
}
}
TEST_F(WaitingImageListTest, RetireSpecificImage) {
fbl::RefPtr<Image> image1 = CreateImage();
fbl::RefPtr<Image> image2 = CreateImage();
// We will add each image twice, in all permutations:
// 1 1 2 2
// 1 2 1 2
// 1 2 2 1
// 2 1 1 2
// 2 1 2 1
// 2 2 1 1
static_assert(WaitingImageList::kMaxSize >= 4);
// 1 1 2 2
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
// 1 2 1 2
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
// 1 2 2 1
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
// 2 1 1 2
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
// 2 1 2 1
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
// 2 2 1 1
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
EXPECT_EQ(waiting_images().size(), 4u);
waiting_images().RemoveImage(*image1);
EXPECT_EQ(waiting_images().size(), 2u);
waiting_images().RemoveImage(*image2);
EXPECT_EQ(waiting_images().size(), 0u);
}
TEST_F(WaitingImageListTest, AddAndRemoveImages) {
std::array<fbl::RefPtr<Image>, 3> images = {CreateImage(), CreateImage(), CreateImage()};
static_assert(WaitingImageList::kMaxSize >= 5);
// Cycle through the images, adding them until the max count is reached.
for (size_t i = 0; i < WaitingImageList::kMaxSize; ++i) {
EXPECT_TRUE(waiting_images().PushImage(images[i % 3], nullptr).is_ok());
}
// Now that `waiting_images` is full, the next attempts to add should fail.
// Of course, it doesn't matter which image we try to add;
EXPECT_FALSE(waiting_images().PushImage(images[0], nullptr).is_ok());
EXPECT_FALSE(waiting_images().PushImage(images[1], nullptr).is_ok());
EXPECT_FALSE(waiting_images().PushImage(images[2], nullptr).is_ok());
// If we remove the oldest 3, we can add three more, but not a 4th.
// Also, because we're removing the oldest, the newest image doesn't change.
auto newest_image = waiting_images().GetNewestImageForTesting();
waiting_images().RemoveOldestImages(2);
EXPECT_EQ(waiting_images().size(), WaitingImageList::kMaxSize - 2);
EXPECT_EQ(newest_image, waiting_images().GetNewestImageForTesting());
EXPECT_TRUE(waiting_images().PushImage(images[0], nullptr).is_ok());
EXPECT_EQ(waiting_images().size(), WaitingImageList::kMaxSize - 1);
EXPECT_EQ(images[0], waiting_images().GetNewestImageForTesting());
EXPECT_TRUE(waiting_images().PushImage(images[1], nullptr).is_ok());
EXPECT_EQ(waiting_images().size(), WaitingImageList::kMaxSize);
EXPECT_EQ(images[1], waiting_images().GetNewestImageForTesting());
EXPECT_FALSE(waiting_images().PushImage(images[2], nullptr).is_ok());
EXPECT_EQ(images[1], waiting_images().GetNewestImageForTesting());
}
TEST_F(WaitingImageListTest, ReadyImages) {
// Create images used by the test.
auto image1 = CreateImage();
auto image2 = CreateImage();
auto image3 = CreateImage();
// Create and import events used by the test.
constexpr display::EventId kWaitFenceId_1(1);
constexpr display::EventId kWaitFenceId_2(2);
constexpr display::EventId kWaitFenceId_3(3);
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_1));
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_2));
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_3));
auto fence_release = fit::defer([&]() mutable {
fences().ReleaseEvent(kWaitFenceId_1);
fences().ReleaseEvent(kWaitFenceId_2);
fences().ReleaseEvent(kWaitFenceId_3);
});
// All images are ready.
ASSERT_OK(waiting_images().PushImage(image1, nullptr));
ASSERT_OK(waiting_images().PushImage(image2, nullptr));
ASSERT_OK(waiting_images().PushImage(image3, nullptr));
// Latest image will be popped. Afterward, older images will be discarded.
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image3);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
EXPECT_EQ(waiting_images().size(), 0U);
// One image is ready.
ASSERT_OK(waiting_images().PushImage(image1, fences().GetFence(kWaitFenceId_1)));
ASSERT_OK(waiting_images().PushImage(image2, nullptr));
ASSERT_OK(waiting_images().PushImage(image3, fences().GetFence(kWaitFenceId_3)));
// The only ready image will be popped, and older ones discarded. The newest image will remain,
// because it is both newer and waiting on a fence.
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image2);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
{
auto remaining = waiting_images().GetFullContentsForTesting();
ASSERT_EQ(remaining.size(), 1U);
EXPECT_EQ(remaining[0].image(), image3);
}
// Continuing from the current state, if we add two more images they will be in the order 3 1 2,
// all with unsignaled fences.
ASSERT_OK(waiting_images().PushImage(image1, fences().GetFence(kWaitFenceId_1)));
ASSERT_OK(waiting_images().PushImage(image2, fences().GetFence(kWaitFenceId_2)));
{
auto remaining = waiting_images().GetFullContentsForTesting();
ASSERT_EQ(remaining.size(), 3U);
// 3 1 2
EXPECT_EQ(remaining[0].image(), image3);
EXPECT_EQ(remaining[1].image(), image1);
EXPECT_EQ(remaining[2].image(), image2);
// 3 1 2
EXPECT_EQ(remaining[0].wait_fence(), fences().GetFence(kWaitFenceId_3));
EXPECT_EQ(remaining[1].wait_fence(), fences().GetFence(kWaitFenceId_1));
EXPECT_EQ(remaining[2].wait_fence(), fences().GetFence(kWaitFenceId_2));
}
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
// Signal third fence. Because the third image is the oldest (since the first/second images were
// retired, then re-added), it will be the only one popped/removed.
fences().GetFence(kWaitFenceId_3)->Signal();
driver_runtime_.RunUntilIdle();
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image3);
{
auto remaining = waiting_images().GetFullContentsForTesting();
ASSERT_EQ(remaining.size(), 2U);
EXPECT_EQ(remaining[0].image(), image1);
EXPECT_EQ(remaining[1].image(), image2);
}
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
// Signal the second fence. Because the second image is the newest (even newer than the first
// image), the first image will be retired and the second image will be popped.
fences().GetFence(kWaitFenceId_2)->Signal();
driver_runtime_.RunUntilIdle();
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image2);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
EXPECT_EQ(waiting_images().size(), 0U);
}
TEST_F(WaitingImageListTest, AddSameImage) {
// Create image used by the test.
auto image = CreateImage();
// Create and import events used by the test.
constexpr display::EventId kWaitFenceId_1(1);
constexpr display::EventId kWaitFenceId_2(2);
constexpr display::EventId kWaitFenceId_3(3);
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_1));
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_2));
ASSERT_OK(zx::event::create(0, &event));
ASSERT_OK(fences().ImportEvent(std::move(event), kWaitFenceId_3));
auto fence_release = fit::defer([&]() mutable {
fences().ReleaseEvent(kWaitFenceId_1);
fences().ReleaseEvent(kWaitFenceId_2);
fences().ReleaseEvent(kWaitFenceId_3);
});
ASSERT_OK(waiting_images().PushImage(image, nullptr));
ASSERT_OK(waiting_images().PushImage(image, fences().GetFence(kWaitFenceId_1)));
ASSERT_OK(waiting_images().PushImage(image, fences().GetFence(kWaitFenceId_2)));
ASSERT_OK(waiting_images().PushImage(image, fences().GetFence(kWaitFenceId_3)));
// Image can only be popped once, because the other additions are blocked on fences.
EXPECT_EQ(waiting_images().size(), 4U);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
EXPECT_EQ(waiting_images().size(), 3U);
// Signal the oldest fence.
fences().GetFence(kWaitFenceId_1)->Signal();
driver_runtime_.RunUntilIdle();
EXPECT_EQ(waiting_images().size(), 3U);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
EXPECT_EQ(waiting_images().size(), 2U);
// Signal the newest (i.e. 3rd) fence. Popping the newest image also removes the entry associated
// with the 2nd fence because although unsignaled, it is older than the entry associated with the
// 3rd fence.
fences().GetFence(kWaitFenceId_3)->Signal();
driver_runtime_.RunUntilIdle();
EXPECT_EQ(waiting_images().size(), 2U);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), image);
EXPECT_EQ(waiting_images().PopNewestReadyImage(), nullptr);
EXPECT_EQ(waiting_images().size(), 0U);
}
TEST_F(WaitingImageListTest, UpdateLatestClientConfigStamp) {
// Create images used by the test.
auto image1 = CreateImage();
auto image2 = CreateImage();
display::ConfigStamp stamp(1);
// It's OK to update the stamp when there are no images; there will be no effect.
waiting_images().UpdateLatestClientConfigStamp(++stamp);
// Add an image and update its stamp.
EXPECT_OK(waiting_images().PushImage(image1, nullptr));
waiting_images().UpdateLatestClientConfigStamp(++stamp);
EXPECT_EQ(waiting_images().GetNewestImageForTesting()->latest_client_config_stamp(),
display::ConfigStamp(3));
waiting_images().UpdateLatestClientConfigStamp(++stamp);
EXPECT_EQ(waiting_images().GetNewestImageForTesting()->latest_client_config_stamp(),
display::ConfigStamp(4));
// Add another image. Now it will be the one whose stamp is updated, not the older image.
EXPECT_OK(waiting_images().PushImage(image2, nullptr));
waiting_images().UpdateLatestClientConfigStamp(++stamp);
{
auto remaining = waiting_images().GetFullContentsForTesting();
ASSERT_EQ(remaining.size(), 2U);
EXPECT_EQ(remaining[0].image(), image1);
EXPECT_EQ(remaining[1].image(), image2);
EXPECT_EQ(remaining[0].image()->latest_client_config_stamp(), display::ConfigStamp(4));
EXPECT_EQ(remaining[1].image()->latest_client_config_stamp(), display::ConfigStamp(5));
}
// If we retire image2, then image1 will be the latest remaining image.
waiting_images().RemoveImage(*image2);
waiting_images().UpdateLatestClientConfigStamp(++stamp);
{
auto remaining = waiting_images().GetFullContentsForTesting();
ASSERT_EQ(remaining.size(), 1U);
EXPECT_EQ(remaining[0].image(), image1);
EXPECT_EQ(remaining[0].image()->latest_client_config_stamp(), display::ConfigStamp(6));
}
}
} // namespace
} // namespace display_coordinator