| // 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/graphics/display/drivers/goldfish-display/display-engine.h" |
| |
| #include <fidl/fuchsia.hardware.goldfish.pipe/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.goldfish/cpp/wire.h> |
| #include <fidl/fuchsia.sysmem/cpp/wire.h> |
| #include <fidl/fuchsia.sysmem/cpp/wire_test_base.h> |
| #include <fuchsia/hardware/display/controller/c/banjo.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/ddk/device.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/driver/testing/cpp/driver_runtime.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| |
| #include <array> |
| #include <cstdio> |
| #include <memory> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/array.h> |
| #include <fbl/vector.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/graphics/display/lib/api-types-cpp/driver-buffer-collection-id.h" |
| #include "src/lib/testing/predicates/status.h" |
| |
| namespace goldfish { |
| |
| namespace { |
| |
| constexpr int32_t kDisplayWidthPx = 1024; |
| constexpr int32_t kDisplayHeightPx = 768; |
| constexpr int32_t kDisplayRefreshRateHz = 60; |
| |
| constexpr size_t kDisplayCount = 1; |
| constexpr size_t kMaxLayerCount = 3; // This is the max size of layer array. |
| |
| } // namespace |
| |
| // TODO(https://fxbug.dev/42072949): Consider creating and using a unified set of sysmem |
| // testing doubles instead of writing mocks for each display driver test. |
| class FakeAllocator : public fidl::testing::WireTestBase<fuchsia_sysmem::Allocator> { |
| public: |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {} |
| }; |
| |
| class FakePipe : public fidl::WireServer<fuchsia_hardware_goldfish_pipe::GoldfishPipe> {}; |
| |
| class GoldfishDisplayEngineTest : public testing::Test { |
| public: |
| GoldfishDisplayEngineTest() : loop_(&kAsyncLoopConfigNeverAttachToThread) {} |
| |
| void SetUp() override; |
| void TearDown() override; |
| |
| protected: |
| fdf_testing::DriverRuntime driver_runtime_; |
| fdf::UnownedSynchronizedDispatcher display_event_dispatcher_ = |
| driver_runtime_.StartBackgroundDispatcher(); |
| fdf::Logger logger_{"test", FUCHSIA_LOG_TRACE, zx::socket{}, |
| fidl::WireClient<fuchsia_logger::LogSink>{}}; |
| |
| std::array<std::array<layer_t, kMaxLayerCount>, kDisplayCount> layer_ = {}; |
| |
| std::array<display_config_t, kDisplayCount> configs_ = {}; |
| |
| std::array<client_composition_opcode_t, kMaxLayerCount * kDisplayCount> results_ = {}; |
| |
| std::unique_ptr<DisplayEngine> display_engine_; |
| |
| std::optional<fidl::ServerBindingRef<fuchsia_hardware_goldfish_pipe::GoldfishPipe>> binding_; |
| std::optional<fidl::ServerBindingRef<fuchsia_sysmem::Allocator>> allocator_binding_; |
| async::Loop loop_; |
| FakePipe* fake_pipe_; |
| FakeAllocator mock_allocator_; |
| }; |
| |
| void GoldfishDisplayEngineTest::SetUp() { |
| fdf::Logger::SetGlobalInstance(&logger_); |
| |
| auto [control_client, control_server] = |
| fidl::Endpoints<fuchsia_hardware_goldfish::ControlDevice>::Create(); |
| auto [pipe_client, pipe_server] = |
| fidl::Endpoints<fuchsia_hardware_goldfish_pipe::GoldfishPipe>::Create(); |
| auto [sysmem_client, sysmem_server] = fidl::Endpoints<fuchsia_sysmem::Allocator>::Create(); |
| allocator_binding_ = |
| fidl::BindServer(loop_.dispatcher(), std::move(sysmem_server), &mock_allocator_); |
| |
| display_engine_ = std::make_unique<DisplayEngine>( |
| std::move(control_client), std::move(pipe_client), std::move(sysmem_client), |
| std::make_unique<RenderControl>(), display_event_dispatcher_->async_dispatcher()); |
| |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| configs_[i].display_id = i + 1; |
| configs_[i].layer_list = layer_[i].data(); |
| configs_[i].layer_count = 1; |
| } |
| |
| // Call SetupPrimaryDisplayForTesting() so that we can set up the display |
| // devices without any dependency on proper driver binding. |
| display_engine_->SetupPrimaryDisplayForTesting(kDisplayWidthPx, kDisplayHeightPx, |
| kDisplayRefreshRateHz); |
| } |
| |
| void GoldfishDisplayEngineTest::TearDown() { |
| allocator_binding_->Unbind(); |
| fdf::Logger::SetGlobalInstance(nullptr); |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigNoDisplay) { |
| // Test No display |
| size_t client_composition_opcodes_actual = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), 0, results_.data(), results_.size(), &client_composition_opcodes_actual); |
| EXPECT_OK(res); |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigMultiLayer) { |
| // ensure we fail correctly if layers more than 1 |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| configs_[i].layer_count = kMaxLayerCount; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kMaxLayerCount); |
| int result_cfg_offset = 0; |
| for (size_t j = 0; j < kDisplayCount; j++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_MERGE_BASE, |
| results_[result_cfg_offset] & CLIENT_COMPOSITION_OPCODE_MERGE_BASE); |
| for (unsigned i = 1; i < kMaxLayerCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_MERGE_SRC, results_[result_cfg_offset + i]); |
| } |
| result_cfg_offset += kMaxLayerCount; |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerColor) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].type = LAYER_TYPE_COLOR; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_USE_PRIMARY, |
| results_[i] & CLIENT_COMPOSITION_OPCODE_USE_PRIMARY); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerPrimary) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| layer_[i][0].cfg.primary.alpha_mode = 0; |
| layer_[i][0].cfg.primary.transform_mode = 0; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(0u, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerDestFrame) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 768, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_FRAME_SCALE, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerSrcFrame) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 768, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_SRC_FRAME, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerAlpha) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| layer_[i][0].cfg.primary.alpha_mode = ALPHA_HW_MULTIPLY; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_ALPHA, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerTransform) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| layer_[i][0].cfg.primary.transform_mode = FRAME_TRANSFORM_REFLECT_X; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_TRANSFORM, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigLayerColorCoversion) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 1024, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| configs_[i].cc_flags = COLOR_CONVERSION_POSTOFFSET; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| // TODO(payamm): For now, driver will pretend it supports color conversion. |
| // It should return CLIENT_COMPOSITION_OPCODE_COLOR_CONVERSION instead. |
| EXPECT_EQ(0u, results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, CheckConfigAllFeatures) { |
| constexpr int kNumLayersPerDisplay = 1; |
| // First create layer for each device |
| frame_t dest_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 768, |
| .height = 768, |
| }; |
| frame_t src_frame = { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = 768, |
| .height = 768, |
| }; |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| layer_[i][0].cfg.primary.dest_frame = dest_frame; |
| layer_[i][0].cfg.primary.src_frame = src_frame; |
| layer_[i][0].cfg.primary.image_metadata.width = 1024; |
| layer_[i][0].cfg.primary.image_metadata.height = 768; |
| layer_[i][0].cfg.primary.alpha_mode = ALPHA_HW_MULTIPLY; |
| layer_[i][0].cfg.primary.transform_mode = FRAME_TRANSFORM_ROT_180; |
| configs_[i].cc_flags = COLOR_CONVERSION_POSTOFFSET; |
| } |
| |
| size_t actual_result_size = 0; |
| config_check_result_t res = display_engine_->DisplayControllerImplCheckConfiguration( |
| configs_.data(), kDisplayCount, results_.data(), results_.size(), &actual_result_size); |
| EXPECT_OK(res); |
| EXPECT_EQ(actual_result_size, kDisplayCount * kNumLayersPerDisplay); |
| for (size_t i = 0; i < kDisplayCount; i++) { |
| // TODO(https://fxbug.dev/42080897): Driver will pretend it supports color conversion |
| // for now. Instead this should contain |
| // CLIENT_COMPOSITION_OPCODE_COLOR_CONVERSION bit. |
| EXPECT_EQ(CLIENT_COMPOSITION_OPCODE_FRAME_SCALE | CLIENT_COMPOSITION_OPCODE_SRC_FRAME | |
| CLIENT_COMPOSITION_OPCODE_ALPHA | CLIENT_COMPOSITION_OPCODE_TRANSFORM, |
| results_[i]); |
| } |
| } |
| |
| TEST_F(GoldfishDisplayEngineTest, ImportBufferCollection) { |
| zx::result token1_endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>(); |
| ASSERT_TRUE(token1_endpoints.is_ok()); |
| zx::result token2_endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>(); |
| ASSERT_TRUE(token2_endpoints.is_ok()); |
| |
| // Test ImportBufferCollection(). |
| constexpr display::DriverBufferCollectionId kValidCollectionId(1); |
| constexpr uint64_t kBanjoValidCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kValidCollectionId); |
| EXPECT_OK(display_engine_->DisplayControllerImplImportBufferCollection( |
| kBanjoValidCollectionId, token1_endpoints->client.TakeChannel())); |
| |
| // `collection_id` must be unused. |
| EXPECT_EQ(display_engine_->DisplayControllerImplImportBufferCollection( |
| kBanjoValidCollectionId, token2_endpoints->client.TakeChannel()), |
| ZX_ERR_ALREADY_EXISTS); |
| |
| // Test ReleaseBufferCollection(). |
| constexpr display::DriverBufferCollectionId kInvalidCollectionId(2); |
| constexpr uint64_t kBanjoInvalidCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kInvalidCollectionId); |
| EXPECT_EQ( |
| display_engine_->DisplayControllerImplReleaseBufferCollection(kBanjoInvalidCollectionId), |
| ZX_ERR_NOT_FOUND); |
| EXPECT_OK(display_engine_->DisplayControllerImplReleaseBufferCollection(kBanjoValidCollectionId)); |
| |
| loop_.Shutdown(); |
| } |
| |
| // TODO(https://fxbug.dev/42073664): Implement a fake sysmem and a fake goldfish-pipe |
| // driver to test importing images using ImportImage(). |
| |
| } // namespace goldfish |