blob: 8e650bf2d0e3507c66d10ad06cdfa528baa4d997 [file] [log] [blame] [edit]
// Copyright 2020 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/layer.h"
#include <fidl/fuchsia.hardware.display.types/cpp/wire.h>
#include <fidl/fuchsia.math/cpp/wire.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/scoped_global_logger.h>
#include <lib/fit/defer.h>
#include <lib/sync/cpp/completion.h>
#include <lib/zx/result.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_ptr.h>
#include <gtest/gtest.h>
#include "src/graphics/display/drivers/coordinator/fence.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/event-id.h"
#include "src/graphics/display/lib/api-types/cpp/image-id.h"
#include "src/graphics/display/lib/api-types/cpp/layer-id.h"
#include "src/lib/testing/predicates/status.h"
namespace display_coordinator {
namespace {
class StubImageLifecycleListener : public ImageLifecycleListener {
public:
using ImageWillBeDestroyedChecker = fit::function<void(display::DriverImageId)>;
StubImageLifecycleListener() = default;
~StubImageLifecycleListener() = default;
StubImageLifecycleListener(const StubImageLifecycleListener&) = delete;
StubImageLifecycleListener& operator=(const StubImageLifecycleListener&) = delete;
// ImageLifecycleListener:
void ImageWillBeDestroyed(display::DriverImageId driver_image_id) override {}
};
class StubFenceListener : public FenceListener {
public:
StubFenceListener() = default;
~StubFenceListener() = default;
StubFenceListener(const StubFenceListener&) = delete;
StubFenceListener& operator=(const StubFenceListener&) = delete;
// `FenceListener`:
void OnFenceSignaled(Fence& fence) override {}
};
} // namespace
class LayerTest : public ::testing::Test {
public:
LayerTest() : fences_(&fence_listener_, driver_runtime_.GetForegroundDispatcher()->borrow()) {}
fbl::RefPtr<Image> CreateReadyImage() {
static constexpr ClientId kClientId(1);
static constexpr display::ImageMetadata kImageMetadata({
.width = kDisplayWidth,
.height = kDisplayHeight,
.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;
}
static void MakeLayerApplied(
Layer& layer, fbl::DoublyLinkedList<LayerNode*>& applied_display_config_layer_list) {
applied_display_config_layer_list.push_front(&layer.applied_display_config_list_node_);
}
protected:
fdf_testing::ScopedGlobalLogger logger_;
fdf_testing::DriverRuntime driver_runtime_;
static constexpr uint32_t kDisplayWidth = 1024;
static constexpr uint32_t kDisplayHeight = 600;
display::ImageId next_image_id_ = display::ImageId(1000);
display::DriverImageId next_driver_image_id_ = display::DriverImageId(2000);
StubImageLifecycleListener image_lifecycle_listener_;
StubFenceListener fence_listener_;
FenceCollection fences_;
};
TEST_F(LayerTest, PrimaryBasic) {
Layer layer(display::LayerId(1));
const display::ImageMetadata image_metadata({.width = kDisplayWidth,
.height = kDisplayHeight,
.tiling_type = display::ImageTilingType::kLinear});
const display::Rectangle display_area(
{.x = 0, .y = 0, .width = kDisplayWidth, .height = kDisplayHeight});
layer.SetPrimaryConfig(image_metadata);
layer.SetPrimaryPosition(display::CoordinateTransformation::kIdentity, display_area,
display_area);
layer.SetPrimaryAlpha(display::AlphaMode::kDisable, 0);
fbl::RefPtr<Image> image = CreateReadyImage();
layer.SetImage(image, display::kInvalidEventId);
layer.ApplyChanges();
}
TEST_F(LayerTest, CleanUpImage) {
Layer layer(display::LayerId(1));
const display::ImageMetadata image_metadata({.width = kDisplayWidth,
.height = kDisplayHeight,
.tiling_type = display::ImageTilingType::kLinear});
const display::Rectangle display_area(
{.x = 0, .y = 0, .width = kDisplayWidth, .height = kDisplayHeight});
layer.SetPrimaryConfig(image_metadata);
layer.SetPrimaryPosition(display::CoordinateTransformation::kIdentity, display_area,
display_area);
layer.SetPrimaryAlpha(display::AlphaMode::kDisable, 0);
auto displayed_image = CreateReadyImage();
layer.SetImage(displayed_image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(1)));
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
constexpr display::EventId kWaitFenceId(1);
ASSERT_OK(fences_.ImportEvent(std::move(event), kWaitFenceId));
auto fence_release = fit::defer([&] { fences_.ReleaseEvent(kWaitFenceId); });
auto waiting_image = CreateReadyImage();
layer.SetImage(waiting_image, kWaitFenceId);
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(2)));
auto draft_image = CreateReadyImage();
layer.SetImage(draft_image, display::kInvalidEventId);
ASSERT_TRUE(layer.ActivateLatestReadyImage());
EXPECT_TRUE(layer.applied_image());
// Nothing should happen if image doesn't match.
auto not_matching_image = CreateReadyImage();
EXPECT_FALSE(layer.CleanUpImage(*not_matching_image));
EXPECT_TRUE(layer.applied_image());
// Test cleaning up a waiting image.
EXPECT_FALSE(layer.CleanUpImage(*waiting_image));
EXPECT_TRUE(layer.applied_image());
// Test cleaning up a draft image.
EXPECT_FALSE(layer.CleanUpImage(*draft_image));
EXPECT_TRUE(layer.applied_image());
// Test cleaning up the associated image.
//
// The layer is not in a display's applied configuration list. So, cleaning up
// the layer's image doesn't change the applied config.
EXPECT_FALSE(layer.CleanUpImage(*displayed_image));
EXPECT_FALSE(layer.applied_image());
}
TEST_F(LayerTest, CleanUpImage_CheckConfigChange) {
fbl::DoublyLinkedList<LayerNode*> applied_layers;
Layer layer(display::LayerId(1));
const display::ImageMetadata image_metadata({.width = kDisplayWidth,
.height = kDisplayHeight,
.tiling_type = display::ImageTilingType::kLinear});
const display::Rectangle display_area(
{.x = 0, .y = 0, .width = kDisplayWidth, .height = kDisplayHeight});
layer.SetPrimaryConfig(image_metadata);
layer.SetPrimaryPosition(display::CoordinateTransformation::kIdentity, display_area,
display_area);
layer.SetPrimaryAlpha(display::AlphaMode::kDisable, 0);
// Clean up images, which doesn't change the applied config.
{
fbl::RefPtr<Image> image = CreateReadyImage();
layer.SetImage(image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(1)));
ASSERT_TRUE(layer.ActivateLatestReadyImage());
EXPECT_TRUE(layer.applied_image());
// The layer is not in a display's applied configuration list. So, cleaning
// up the layer's image doesn't change the applied config.
EXPECT_FALSE(layer.CleanUpImage(*image));
EXPECT_FALSE(layer.applied_image());
}
// Clean up images, which changes the applied config.
{
MakeLayerApplied(layer, applied_layers);
fbl::RefPtr<Image> image = CreateReadyImage();
layer.SetImage(image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(2)));
ASSERT_TRUE(layer.ActivateLatestReadyImage());
EXPECT_TRUE(layer.applied_image());
// The layer is in a display's applied configuration list. So, cleaning up
// the layer's image changes the applied config.
EXPECT_TRUE(layer.CleanUpImage(*image));
EXPECT_FALSE(layer.applied_image());
applied_layers.clear();
}
}
TEST_F(LayerTest, CleanUpAllImages) {
Layer layer(display::LayerId(1));
const display::ImageMetadata image_metadata({.width = kDisplayWidth,
.height = kDisplayHeight,
.tiling_type = display::ImageTilingType::kLinear});
const display::Rectangle display_area(
{.x = 0, .y = 0, .width = kDisplayWidth, .height = kDisplayHeight});
layer.SetPrimaryConfig(image_metadata);
layer.SetPrimaryPosition(display::CoordinateTransformation::kIdentity, display_area,
display_area);
layer.SetPrimaryAlpha(display::AlphaMode::kDisable, 0);
auto displayed_image = CreateReadyImage();
layer.SetImage(displayed_image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(1)));
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
constexpr display::EventId kWaitFenceId(1);
ASSERT_OK(fences_.ImportEvent(std::move(event), kWaitFenceId));
auto fence_release = fit::defer([&] { fences_.ReleaseEvent(kWaitFenceId); });
auto waiting_image = CreateReadyImage();
layer.SetImage(waiting_image, kWaitFenceId);
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(2)));
auto draft_image = CreateReadyImage();
layer.SetImage(draft_image, display::kInvalidEventId);
ASSERT_TRUE(layer.ActivateLatestReadyImage());
// The layer is not in a display's applied configuration list. So, cleaning
// up the layer's image doesn't change the applied config.
EXPECT_FALSE(layer.CleanUpAllImages());
EXPECT_FALSE(layer.applied_image());
}
TEST_F(LayerTest, CleanUpAllImages_CheckConfigChange) {
fbl::DoublyLinkedList<LayerNode*> applied_layers;
Layer layer(display::LayerId(1));
const display::ImageMetadata image_metadata({.width = kDisplayWidth,
.height = kDisplayHeight,
.tiling_type = display::ImageTilingType::kLinear});
const display::Rectangle display_area(
{.x = 0, .y = 0, .width = kDisplayWidth, .height = kDisplayHeight});
layer.SetPrimaryConfig(image_metadata);
layer.SetPrimaryPosition(display::CoordinateTransformation::kIdentity, display_area,
display_area);
layer.SetPrimaryAlpha(display::AlphaMode::kDisable, 0);
// Clean up all images, which doesn't change the applied config.
{
fbl::RefPtr<Image> image = CreateReadyImage();
layer.SetImage(image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(1)));
ASSERT_TRUE(layer.ActivateLatestReadyImage());
EXPECT_TRUE(layer.applied_image());
// The layer is not in a display's applied configuration list. So, cleaning
// up the layer's image doesn't change the applied config.
EXPECT_FALSE(layer.CleanUpAllImages());
EXPECT_FALSE(layer.applied_image());
}
// Clean up all images, which changes the applied config.
{
MakeLayerApplied(layer, applied_layers);
fbl::RefPtr<Image> image = CreateReadyImage();
layer.SetImage(image, display::kInvalidEventId);
layer.ApplyChanges();
ASSERT_TRUE(layer.ResolveDraftImage(&fences_, display::ConfigStamp(2)));
ASSERT_TRUE(layer.ActivateLatestReadyImage());
EXPECT_TRUE(layer.applied_image());
// The layer is in a display's applied configuration list. So, cleaning up
// the layer's image changes the applied config.
EXPECT_TRUE(layer.CleanUpAllImages());
EXPECT_FALSE(layer.applied_image());
applied_layers.clear();
}
}
} // namespace display_coordinator