| // Copyright 2023 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/fake/fake-display.h" |
| |
| #include <fidl/fuchsia.sysmem/cpp/wire.h> |
| #include <fuchsia/hardware/display/controller/c/banjo.h> |
| #include <lib/fpromise/result.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/image-format/image_format.h> |
| #include <lib/inspect/cpp/hierarchy.h> |
| #include <lib/inspect/cpp/reader.h> |
| #include <lib/inspect/cpp/vmo/types.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/sync/cpp/completion.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/rights.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <fbl/algorithm.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/devices/sysmem/drivers/sysmem/device.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| #include "src/graphics/display/drivers/fake/fake-display-stack.h" |
| #include "src/graphics/display/drivers/fake/sysmem-device-wrapper.h" |
| #include "src/graphics/display/lib/api-types-cpp/config-stamp.h" |
| #include "src/graphics/display/lib/api-types-cpp/display-id.h" |
| #include "src/graphics/display/lib/api-types-cpp/driver-buffer-collection-id.h" |
| #include "src/lib/testing/predicates/status.h" |
| |
| namespace fake_display { |
| |
| namespace { |
| |
| class FakeDisplayTest : public testing::Test { |
| public: |
| FakeDisplayTest() = default; |
| |
| void SetUp() override { |
| mock_root_ = MockDevice::FakeRootParent(); |
| auto sysmem = std::make_unique<display::GenericSysmemDeviceWrapper<sysmem_driver::Device>>( |
| mock_root_.get()); |
| tree_ = std::make_unique<display::FakeDisplayStack>(mock_root_, std::move(sysmem), |
| GetFakeDisplayDeviceConfig()); |
| } |
| |
| void TearDown() override { |
| tree_->SyncShutdown(); |
| tree_.reset(); |
| } |
| |
| virtual FakeDisplayDeviceConfig GetFakeDisplayDeviceConfig() const { |
| return FakeDisplayDeviceConfig{ |
| .manual_vsync_trigger = true, |
| .no_buffer_access = false, |
| }; |
| } |
| |
| fake_display::FakeDisplay* display() { return tree_->display(); } |
| |
| fidl::WireSyncClient<fuchsia_sysmem::Allocator> ConnectToSysmemAllocatorV1() { |
| return fidl::WireSyncClient(tree_->ConnectToSysmemAllocatorV1()); |
| } |
| |
| MockDevice* mock_root() const { return mock_root_.get(); } |
| |
| private: |
| std::shared_ptr<MockDevice> mock_root_; |
| std::unique_ptr<display::FakeDisplayStack> tree_; |
| }; |
| |
| TEST_F(FakeDisplayTest, Inspect) { |
| fpromise::result<inspect::Hierarchy> read_result = |
| inspect::ReadFromVmo(display()->inspector().DuplicateVmo()); |
| ASSERT_TRUE(read_result.is_ok()); |
| |
| const inspect::Hierarchy& hierarchy = read_result.value(); |
| const inspect::Hierarchy* config = hierarchy.GetByPath({"device_config"}); |
| ASSERT_NE(config, nullptr); |
| |
| // Must be the same as `kWidth` defined in fake-display.cc. |
| // TODO(https://fxbug.dev/42065258): Use configurable values instead. |
| constexpr int kWidth = 1280; |
| const inspect::IntPropertyValue* width_px = |
| config->node().get_property<inspect::IntPropertyValue>("width_px"); |
| ASSERT_NE(width_px, nullptr); |
| EXPECT_EQ(width_px->value(), kWidth); |
| |
| // Must be the same as `kHeight` defined in fake-display.cc. |
| // TODO(https://fxbug.dev/42065258): Use configurable values instead. |
| constexpr int kHeight = 800; |
| const inspect::IntPropertyValue* height_px = |
| config->node().get_property<inspect::IntPropertyValue>("height_px"); |
| ASSERT_NE(height_px, nullptr); |
| EXPECT_EQ(height_px->value(), kHeight); |
| |
| // Must be the same as `kRefreshRateFps` defined in fake-display.cc. |
| // TODO(https://fxbug.dev/42065258): Use configurable values instead. |
| constexpr double kRefreshRateHz = 60.0; |
| const inspect::DoublePropertyValue* refresh_rate_hz = |
| config->node().get_property<inspect::DoublePropertyValue>("refresh_rate_hz"); |
| ASSERT_NE(refresh_rate_hz, nullptr); |
| EXPECT_DOUBLE_EQ(refresh_rate_hz->value(), kRefreshRateHz); |
| |
| const inspect::BoolPropertyValue* manual_vsync_trigger = |
| config->node().get_property<inspect::BoolPropertyValue>("manual_vsync_trigger"); |
| ASSERT_NE(manual_vsync_trigger, nullptr); |
| EXPECT_EQ(manual_vsync_trigger->value(), true); |
| |
| const inspect::BoolPropertyValue* no_buffer_access = |
| config->node().get_property<inspect::BoolPropertyValue>("no_buffer_access"); |
| ASSERT_NE(no_buffer_access, nullptr); |
| EXPECT_EQ(no_buffer_access->value(), false); |
| } |
| |
| class FakeDisplayRealSysmemTest : public FakeDisplayTest { |
| public: |
| struct BufferCollectionAndToken { |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection_client; |
| fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken> token; |
| }; |
| |
| FakeDisplayRealSysmemTest() = default; |
| ~FakeDisplayRealSysmemTest() override = default; |
| |
| zx::result<BufferCollectionAndToken> CreateBufferCollection() { |
| zx::result<fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>> token_endpoints = |
| fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>(); |
| |
| EXPECT_OK(token_endpoints.status_value()); |
| if (!token_endpoints.is_ok()) { |
| return token_endpoints.take_error(); |
| } |
| |
| auto& [token_client, token_server] = token_endpoints.value(); |
| fidl::Status allocate_token_status = sysmem_->AllocateSharedCollection(std::move(token_server)); |
| EXPECT_OK(allocate_token_status.status()); |
| if (!allocate_token_status.ok()) { |
| return zx::error(allocate_token_status.status()); |
| } |
| |
| fidl::Status sync_status = fidl::WireCall(token_client)->Sync(); |
| EXPECT_OK(sync_status.status()); |
| if (!sync_status.ok()) { |
| return zx::error(sync_status.status()); |
| } |
| |
| // At least one sysmem participant should specify buffer memory constraints. |
| // The driver may not specify buffer memory constraints, so the test should |
| // always provide one for sysmem through `buffer_collection_` client. |
| // |
| // Here we duplicate the token to set buffer collection constraints in |
| // the test. |
| std::vector<zx_rights_t> rights = {ZX_RIGHT_SAME_RIGHTS}; |
| fidl::WireResult duplicate_result = |
| fidl::WireCall(token_client) |
| ->DuplicateSync(fidl::VectorView<zx_rights_t>::FromExternal(rights)); |
| EXPECT_OK(duplicate_result.status()); |
| if (!duplicate_result.ok()) { |
| return zx::error(duplicate_result.status()); |
| } |
| |
| auto& duplicate_value = duplicate_result.value(); |
| EXPECT_EQ(duplicate_value.tokens.count(), 1u); |
| if (duplicate_value.tokens.count() != 1u) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| // Bind duplicated token to BufferCollection client. |
| auto [collection_client, collection_server] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| fidl::Status bind_status = sysmem_->BindSharedCollection(std::move(duplicate_value.tokens[0]), |
| std::move(collection_server)); |
| EXPECT_OK(bind_status.status()); |
| if (!bind_status.ok()) { |
| return zx::error(bind_status.status()); |
| } |
| |
| return zx::ok(BufferCollectionAndToken{ |
| .collection_client = fidl::WireSyncClient(std::move(collection_client)), |
| .token = std::move(token_client), |
| }); |
| } |
| |
| void SetUp() override { |
| FakeDisplayTest::SetUp(); |
| sysmem_ = ConnectToSysmemAllocatorV1(); |
| EXPECT_TRUE(sysmem_.is_valid()); |
| } |
| |
| void TearDown() override { |
| sysmem_ = {}; |
| FakeDisplayTest::TearDown(); |
| } |
| |
| const fidl::WireSyncClient<fuchsia_sysmem::Allocator>& sysmem() const { return sysmem_; } |
| |
| private: |
| fidl::WireSyncClient<fuchsia_sysmem::Allocator> sysmem_; |
| }; |
| |
| // A completion semaphore indicating the display capture is completed. |
| class DisplayCaptureCompletion { |
| public: |
| // Tests can import the display controller interface protocol to set up the |
| // callback to trigger the semaphore. |
| display_controller_interface_protocol_t GetDisplayControllerInterfaceProtocol() { |
| static constexpr display_controller_interface_protocol_ops_t |
| kDisplayControllerInterfaceProtocolOps = { |
| .on_displays_changed = [](void* ctx, const added_display_args_t* added_displays_list, |
| size_t added_displays_count, |
| const uint64_t* removed_display_ids_list, |
| size_t removed_display_ids_count) {}, |
| .on_display_vsync = [](void* ctx, uint64_t display_id, zx_time_t timestamp, |
| const config_stamp_t* config_stamp) {}, |
| .on_capture_complete = |
| [](void* ctx) { |
| reinterpret_cast<DisplayCaptureCompletion*>(ctx)->OnCaptureComplete(); |
| }, |
| }; |
| return display_controller_interface_protocol_t{ |
| .ops = &kDisplayControllerInterfaceProtocolOps, |
| .ctx = this, |
| }; |
| } |
| |
| libsync::Completion& completed() { return completed_; } |
| |
| private: |
| void OnCaptureComplete() { completed().Signal(); } |
| libsync::Completion completed_; |
| }; |
| |
| // Creates a BufferCollectionConstraints that tests can use to configure their |
| // own BufferCollections to allocate buffers. |
| // |
| // It provides sysmem Constraints that request exactly 1 CPU-readable and |
| // writable buffer with size of at least `min_size_bytes` bytes and can hold |
| // an image of pixel format `pixel_format`. |
| // |
| // As we require the image to be both readable and writable, the constraints |
| // will work for both simple (scanout) images and capture images. |
| fuchsia_sysmem::wire::BufferCollectionConstraints CreateImageConstraints( |
| uint32_t min_size_bytes, fuchsia_sysmem::wire::PixelFormat pixel_format) { |
| return fuchsia_sysmem::wire::BufferCollectionConstraints{ |
| .usage = |
| { |
| .cpu = fuchsia_sysmem::wire::kCpuUsageRead | fuchsia_sysmem::wire::kCpuUsageWrite, |
| }, |
| .min_buffer_count = 1, |
| .max_buffer_count = 1, |
| .has_buffer_memory_constraints = true, |
| .buffer_memory_constraints = |
| { |
| .min_size_bytes = min_size_bytes, |
| .max_size_bytes = std::numeric_limits<uint32_t>::max(), |
| // The test cases need direct CPU access to the buffers and we |
| // don't enforce cache flushing, so we should narrow down the |
| // allowed sysmem heaps to the heaps supporting CPU domain and |
| // reject all the other heaps. |
| .ram_domain_supported = false, |
| .cpu_domain_supported = true, |
| .inaccessible_domain_supported = false, |
| }, |
| // fake-display driver doesn't add extra image format constraints when |
| // SetBufferCollectionConstraints() is called. To make sure we allocate an |
| // image buffer, we add constraints here. |
| .image_format_constraints_count = 1, |
| .image_format_constraints = |
| { |
| fuchsia_sysmem::wire::ImageFormatConstraints{ |
| .pixel_format = pixel_format, |
| .color_spaces_count = 1, |
| .color_space = |
| { |
| fuchsia_sysmem::wire::ColorSpace{ |
| .type = fuchsia_sysmem::ColorSpaceType::kSrgb, |
| }, |
| }, |
| }, |
| }, |
| }; |
| } |
| |
| // Creates a primary layer config for an opaque layer that holds the `image` |
| // on the top-left corner of the screen without any scaling. |
| layer_t CreatePrimaryLayerConfig(uint64_t image_handle, const image_metadata_t& image_metadata) { |
| return layer_t{ |
| .type = LAYER_TYPE_PRIMARY, |
| .z_index = 0, |
| .cfg = |
| { |
| .primary = |
| { |
| .image_handle = image_handle, |
| .image_metadata = image_metadata, |
| .alpha_mode = ALPHA_DISABLE, |
| .alpha_layer_val = 1.0, |
| .transform_mode = FRAME_TRANSFORM_IDENTITY, |
| .src_frame = |
| { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = image_metadata.width, |
| .height = image_metadata.height, |
| }, |
| .dest_frame = |
| { |
| .x_pos = 0, |
| .y_pos = 0, |
| .width = image_metadata.width, |
| .height = image_metadata.height, |
| }, |
| }, |
| }, |
| }; |
| } |
| |
| std::pair<zx::vmo, fuchsia_sysmem::wire::SingleBufferSettings> GetAllocatedBufferAndSettings( |
| const fidl::WireSyncClient<fuchsia_sysmem::BufferCollection>& client) { |
| auto wait_result = client->WaitForBuffersAllocated(); |
| ZX_ASSERT_MSG(wait_result.ok(), "WaitForBuffersAllocated() FIDL call failed: %s", |
| wait_result.status_string()); |
| ZX_ASSERT_MSG(wait_result.value().status == ZX_OK, |
| "WaitForBuffersAllocated() responds with error: %s", |
| zx_status_get_string(wait_result.value().status)); |
| auto& buffer_collection_info = wait_result.value().buffer_collection_info; |
| ZX_ASSERT_MSG(buffer_collection_info.buffer_count == 1u, |
| "Incorrect number of buffers allocated: actual %u, expected 1", |
| buffer_collection_info.buffer_count); |
| return {std::move(buffer_collection_info.buffers[0].vmo), buffer_collection_info.settings}; |
| } |
| |
| void FillImageWithColor(cpp20::span<uint8_t> image_buffer, const std::vector<uint8_t>& color_raw, |
| int width, int height, uint32_t bytes_per_row_divisor) { |
| size_t bytes_per_pixel = color_raw.size(); |
| size_t row_stride_bytes = fbl::round_up(width * bytes_per_pixel, bytes_per_row_divisor); |
| |
| for (int row = 0; row < height; ++row) { |
| auto row_buffer = image_buffer.subspan(row * row_stride_bytes, row_stride_bytes); |
| auto it = row_buffer.begin(); |
| for (int col = 0; col < width; ++col) { |
| it = std::copy(color_raw.begin(), color_raw.end(), it); |
| } |
| } |
| } |
| |
| TEST_F(FakeDisplayRealSysmemTest, ImportBufferCollection) { |
| zx::result<BufferCollectionAndToken> new_buffer_collection_result = CreateBufferCollection(); |
| ASSERT_OK(new_buffer_collection_result.status_value()); |
| auto [collection_client, token] = std::move(new_buffer_collection_result.value()); |
| |
| // Test ImportBufferCollection(). |
| constexpr display::DriverBufferCollectionId kValidBufferCollectionId(1); |
| constexpr uint64_t kBanjoValidBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kValidBufferCollectionId); |
| EXPECT_OK(display()->DisplayControllerImplImportBufferCollection(kBanjoValidBufferCollectionId, |
| token.TakeChannel())); |
| |
| // `driver_buffer_collection_id` must be unused. |
| zx::result<fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>> another_token_endpoints = |
| fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>(); |
| ASSERT_OK(another_token_endpoints.status_value()); |
| EXPECT_EQ(display()->DisplayControllerImplImportBufferCollection( |
| kBanjoValidBufferCollectionId, another_token_endpoints->client.TakeChannel()), |
| ZX_ERR_ALREADY_EXISTS); |
| |
| // Driver sets BufferCollection buffer memory constraints. |
| static constexpr image_buffer_usage_t kDisplayUsage = { |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| EXPECT_OK(display()->DisplayControllerImplSetBufferCollectionConstraints( |
| &kDisplayUsage, kBanjoValidBufferCollectionId)); |
| |
| // Set BufferCollection buffer memory constraints. |
| fidl::Status set_constraints_status = collection_client->SetConstraints( |
| /* has_constraints= */ true, |
| CreateImageConstraints(/*min_size_bytes=*/4096, |
| fuchsia_sysmem::wire::PixelFormat{ |
| .type = fuchsia_sysmem::PixelFormatType::kR8G8B8A8, |
| .has_format_modifier = true, |
| .format_modifier = |
| { |
| .value = fuchsia_sysmem::wire::kFormatModifierLinear, |
| }, |
| })); |
| EXPECT_TRUE(set_constraints_status.ok()); |
| |
| // Both the test-side client and the driver have set the constraints. |
| // The buffer should be allocated correctly in sysmem. |
| EXPECT_TRUE(collection_client->WaitForBuffersAllocated().ok()); |
| |
| // Test ReleaseBufferCollection(). |
| // TODO(https://fxbug.dev/42079040): Consider adding RAII handles to release the |
| // imported buffer collections. |
| constexpr display::DriverBufferCollectionId kInvalidBufferCollectionId(2); |
| constexpr uint64_t kBanjoInvalidBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kInvalidBufferCollectionId); |
| EXPECT_EQ( |
| display()->DisplayControllerImplReleaseBufferCollection(kBanjoInvalidBufferCollectionId), |
| ZX_ERR_NOT_FOUND); |
| EXPECT_OK(display()->DisplayControllerImplReleaseBufferCollection(kBanjoValidBufferCollectionId)); |
| } |
| |
| TEST_F(FakeDisplayRealSysmemTest, ImportImage) { |
| zx::result<BufferCollectionAndToken> new_buffer_collection_result = CreateBufferCollection(); |
| ASSERT_OK(new_buffer_collection_result.status_value()); |
| auto [collection_client, token] = std::move(new_buffer_collection_result.value()); |
| |
| constexpr display::DriverBufferCollectionId kBufferCollectionId(1); |
| constexpr uint64_t kBanjoBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kBufferCollectionId); |
| EXPECT_OK(display()->DisplayControllerImplImportBufferCollection(kBanjoBufferCollectionId, |
| token.TakeChannel())); |
| |
| // Driver sets BufferCollection buffer memory constraints. |
| static constexpr image_buffer_usage_t kDisplayUsage = { |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| EXPECT_OK(display()->DisplayControllerImplSetBufferCollectionConstraints( |
| &kDisplayUsage, kBanjoBufferCollectionId)); |
| |
| // Set BufferCollection buffer memory constraints. |
| static constexpr const image_metadata_t kDisplayImageMetadata = { |
| .width = 1024, |
| .height = 768, |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| static constexpr fuchsia_sysmem::wire::PixelFormat kPixelFormat = { |
| .type = fuchsia_sysmem::PixelFormatType::kBgra32, |
| .has_format_modifier = true, |
| .format_modifier = |
| { |
| .value = fuchsia_sysmem::wire::kFormatModifierLinear, |
| }, |
| }; |
| |
| const uint32_t bytes_per_pixel = ImageFormatStrideBytesPerWidthPixel(kPixelFormat); |
| fidl::Status set_constraints_status = collection_client->SetConstraints( |
| /* has_constraints= */ true, CreateImageConstraints( |
| /*min_size_bytes=*/kDisplayImageMetadata.width * |
| kDisplayImageMetadata.height * bytes_per_pixel, |
| kPixelFormat)); |
| EXPECT_TRUE(set_constraints_status.ok()); |
| |
| // Both the test-side client and the driver have set the constraints. |
| // The buffer should be allocated correctly in sysmem. |
| EXPECT_TRUE(collection_client->WaitForBuffersAllocated().ok()); |
| |
| // TODO(https://fxbug.dev/42079037): Split all valid / invalid imports into separate |
| // test cases. |
| // Invalid import: Bad image type. |
| static constexpr const image_metadata_t kInvalidTilingTypeMetadata = { |
| .width = 1024, |
| .height = 768, |
| .tiling_type = IMAGE_TILING_TYPE_CAPTURE, |
| }; |
| uint64_t image_handle = 0; |
| EXPECT_EQ(display()->DisplayControllerImplImportImage(&kInvalidTilingTypeMetadata, |
| kBanjoBufferCollectionId, |
| /*index=*/0, &image_handle), |
| ZX_ERR_INVALID_ARGS); |
| |
| // Invalid import: Invalid collection ID. |
| constexpr display::DriverBufferCollectionId kInvalidBufferCollectionId(100); |
| constexpr uint64_t kBanjoInvalidBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kInvalidBufferCollectionId); |
| image_handle = 0; |
| EXPECT_EQ(display()->DisplayControllerImplImportImage(&kDisplayImageMetadata, |
| kBanjoInvalidBufferCollectionId, |
| /*index=*/0, &image_handle), |
| ZX_ERR_NOT_FOUND); |
| |
| // Invalid import: Invalid buffer collection index. |
| constexpr uint64_t kInvalidBufferCollectionIndex = 100u; |
| image_handle = 0; |
| EXPECT_EQ( |
| display()->DisplayControllerImplImportImage(&kDisplayImageMetadata, kBanjoBufferCollectionId, |
| kInvalidBufferCollectionIndex, &image_handle), |
| ZX_ERR_OUT_OF_RANGE); |
| |
| // Valid import. |
| image_handle = 0; |
| EXPECT_OK(display()->DisplayControllerImplImportImage(&kDisplayImageMetadata, |
| kBanjoBufferCollectionId, |
| /*index=*/0, &image_handle)); |
| EXPECT_NE(image_handle, 0u); |
| |
| // Release the image. |
| display()->DisplayControllerImplReleaseImage(image_handle); |
| |
| EXPECT_OK(display()->DisplayControllerImplReleaseBufferCollection(kBanjoBufferCollectionId)); |
| } |
| |
| TEST_F(FakeDisplayRealSysmemTest, ImportImageForCapture) { |
| zx::result<BufferCollectionAndToken> new_buffer_collection_result = CreateBufferCollection(); |
| ASSERT_OK(new_buffer_collection_result.status_value()); |
| auto [collection_client, token] = std::move(new_buffer_collection_result.value()); |
| |
| constexpr display::DriverBufferCollectionId kBufferCollectionId(1); |
| constexpr uint64_t kBanjoBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kBufferCollectionId); |
| EXPECT_OK(display()->DisplayControllerImplImportBufferCollection(kBanjoBufferCollectionId, |
| token.TakeChannel())); |
| |
| const auto kPixelFormat = fuchsia_sysmem::wire::PixelFormat{ |
| .type = fuchsia_sysmem::PixelFormatType::kBgra32, |
| .has_format_modifier = true, |
| .format_modifier = |
| { |
| .value = fuchsia_sysmem::wire::kFormatModifierLinear, |
| }, |
| }; |
| |
| constexpr uint32_t kDisplayWidth = 1280; |
| constexpr uint32_t kDisplayHeight = 800; |
| |
| static constexpr image_buffer_usage_t kDisplayUsage = { |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| EXPECT_OK(display()->DisplayControllerImplSetBufferCollectionConstraints( |
| &kDisplayUsage, kBanjoBufferCollectionId)); |
| const uint32_t bytes_per_pixel = ImageFormatStrideBytesPerWidthPixel(kPixelFormat); |
| const uint32_t size_bytes = kDisplayWidth * kDisplayHeight * bytes_per_pixel; |
| // Set BufferCollection buffer memory constraints. |
| fidl::Status set_constraints_status = collection_client->SetConstraints( |
| /* has_constraints= */ true, CreateImageConstraints(size_bytes, kPixelFormat)); |
| EXPECT_TRUE(set_constraints_status.ok()); |
| |
| // Both the test-side client and the driver have set the constraints. |
| // The buffer should be allocated correctly in sysmem. |
| EXPECT_TRUE(collection_client->WaitForBuffersAllocated().ok()); |
| |
| uint64_t out_capture_handle = INVALID_ID; |
| |
| // TODO(https://fxbug.dev/42079037): Split all valid / invalid imports into separate |
| // test cases. |
| // Invalid import: Invalid collection ID. |
| constexpr display::DriverBufferCollectionId kInvalidBufferCollectionId(100); |
| constexpr uint64_t kBanjoInvalidBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kInvalidBufferCollectionId); |
| EXPECT_EQ(display()->DisplayControllerImplImportImageForCapture(kBanjoInvalidBufferCollectionId, |
| /*index=*/0, &out_capture_handle), |
| ZX_ERR_NOT_FOUND); |
| |
| // Invalid import: Invalid buffer collection index. |
| constexpr uint64_t kInvalidBufferCollectionIndex = 100u; |
| EXPECT_EQ(display()->DisplayControllerImplImportImageForCapture( |
| kBanjoBufferCollectionId, kInvalidBufferCollectionIndex, &out_capture_handle), |
| ZX_ERR_OUT_OF_RANGE); |
| |
| // Valid import. |
| EXPECT_OK(display()->DisplayControllerImplImportImageForCapture( |
| kBanjoBufferCollectionId, /*index=*/0, &out_capture_handle)); |
| EXPECT_NE(out_capture_handle, INVALID_ID); |
| |
| // Release the image. |
| // TODO(https://fxbug.dev/42079040): Consider adding RAII handles to release the |
| // imported images and buffer collections. |
| display()->DisplayControllerImplReleaseCapture(out_capture_handle); |
| |
| EXPECT_OK(display()->DisplayControllerImplReleaseBufferCollection(kBanjoBufferCollectionId)); |
| } |
| |
| TEST_F(FakeDisplayRealSysmemTest, Capture) { |
| zx::result<BufferCollectionAndToken> new_capture_buffer_collection_result = |
| CreateBufferCollection(); |
| ASSERT_OK(new_capture_buffer_collection_result.status_value()); |
| auto [capture_collection_client, capture_token] = |
| std::move(new_capture_buffer_collection_result.value()); |
| |
| zx::result<BufferCollectionAndToken> new_framebuffer_buffer_collection_result = |
| CreateBufferCollection(); |
| ASSERT_OK(new_framebuffer_buffer_collection_result.status_value()); |
| auto [framebuffer_collection_client, framebuffer_token] = |
| std::move(new_framebuffer_buffer_collection_result.value()); |
| |
| DisplayCaptureCompletion display_capture_completion = {}; |
| const display_controller_interface_protocol_t& controller_protocol = |
| display_capture_completion.GetDisplayControllerInterfaceProtocol(); |
| display()->DisplayControllerImplSetDisplayControllerInterface(&controller_protocol); |
| |
| constexpr display::DriverBufferCollectionId kCaptureBufferCollectionId(1); |
| constexpr uint64_t kBanjoCaptureBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kCaptureBufferCollectionId); |
| constexpr display::DriverBufferCollectionId kFramebufferBufferCollectionId(2); |
| constexpr uint64_t kBanjoFramebufferBufferCollectionId = |
| display::ToBanjoDriverBufferCollectionId(kFramebufferBufferCollectionId); |
| EXPECT_OK(display()->DisplayControllerImplImportBufferCollection(kBanjoCaptureBufferCollectionId, |
| capture_token.TakeChannel())); |
| EXPECT_OK(display()->DisplayControllerImplImportBufferCollection( |
| kBanjoFramebufferBufferCollectionId, framebuffer_token.TakeChannel())); |
| |
| const auto kPixelFormat = fuchsia_sysmem::wire::PixelFormat{ |
| .type = fuchsia_sysmem::PixelFormatType::kBgra32, |
| .has_format_modifier = true, |
| .format_modifier = |
| { |
| .value = fuchsia_sysmem::wire::kFormatModifierLinear, |
| }, |
| }; |
| |
| // Must match kWidth and kHeight defined in fake-display.cc. |
| // TODO(https://fxbug.dev/42078942): Do not hardcode the display width and height. |
| constexpr int kDisplayWidth = 1280; |
| constexpr int kDisplayHeight = 800; |
| |
| // Set BufferCollection buffer memory constraints from the display driver's |
| // end. |
| static constexpr image_buffer_usage_t kDisplayUsage = { |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| EXPECT_OK(display()->DisplayControllerImplSetBufferCollectionConstraints( |
| &kDisplayUsage, kBanjoFramebufferBufferCollectionId)); |
| static constexpr image_buffer_usage_t kCaptureUsage = { |
| .tiling_type = IMAGE_TILING_TYPE_CAPTURE, |
| }; |
| EXPECT_OK(display()->DisplayControllerImplSetBufferCollectionConstraints( |
| &kCaptureUsage, kBanjoCaptureBufferCollectionId)); |
| |
| // Set BufferCollection buffer memory constraints from the test's end. |
| const uint32_t bytes_per_pixel = ImageFormatStrideBytesPerWidthPixel(kPixelFormat); |
| const uint32_t size_bytes = kDisplayWidth * kDisplayHeight * bytes_per_pixel; |
| fidl::Status set_framebuffer_constraints_status = framebuffer_collection_client->SetConstraints( |
| /* has_constraints= */ true, CreateImageConstraints(size_bytes, kPixelFormat)); |
| EXPECT_TRUE(set_framebuffer_constraints_status.ok()); |
| fidl::Status set_capture_constraints_status = capture_collection_client->SetConstraints( |
| /* has_constraints= */ true, CreateImageConstraints(size_bytes, kPixelFormat)); |
| EXPECT_TRUE(set_capture_constraints_status.ok()); |
| |
| // Both the test-side client and the driver have set the constraints. |
| // The buffers should be allocated correctly in sysmem. |
| auto [framebuffer_vmo, framebuffer_settings] = |
| GetAllocatedBufferAndSettings(framebuffer_collection_client); |
| auto [capture_vmo, capture_settings] = GetAllocatedBufferAndSettings(capture_collection_client); |
| |
| // Fill the framebuffer. |
| fzl::VmoMapper framebuffer_mapper; |
| ASSERT_OK(framebuffer_mapper.Map(framebuffer_vmo)); |
| cpp20::span<uint8_t> framebuffer_bytes(reinterpret_cast<uint8_t*>(framebuffer_mapper.start()), |
| framebuffer_mapper.size()); |
| const std::vector<uint8_t> kBlueBgra = {0xff, 0, 0, 0xff}; |
| FillImageWithColor(framebuffer_bytes, kBlueBgra, kDisplayWidth, kDisplayHeight, |
| framebuffer_settings.image_format_constraints.bytes_per_row_divisor); |
| zx_cache_flush(framebuffer_bytes.data(), framebuffer_bytes.size(), |
| ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| framebuffer_mapper.Unmap(); |
| |
| // Import capture image. |
| uint64_t capture_handle = INVALID_ID; |
| EXPECT_OK(display()->DisplayControllerImplImportImageForCapture(kBanjoCaptureBufferCollectionId, |
| /*index=*/0, &capture_handle)); |
| EXPECT_NE(capture_handle, INVALID_ID); |
| |
| // Import framebuffer image. |
| static constexpr image_metadata_t kFramebufferImageMetadata = { |
| .width = kDisplayWidth, |
| .height = kDisplayHeight, |
| .tiling_type = IMAGE_TILING_TYPE_LINEAR, |
| }; |
| |
| uint64_t framebuffer_image_handle = 0; |
| EXPECT_OK(display()->DisplayControllerImplImportImage(&kFramebufferImageMetadata, |
| kBanjoFramebufferBufferCollectionId, |
| /*index=*/0, &framebuffer_image_handle)); |
| EXPECT_NE(framebuffer_image_handle, INVALID_ID); |
| |
| // Create display configuration. |
| constexpr size_t kLayerCount = 1; |
| std::array<const layer_t, kLayerCount> kLayers = { |
| CreatePrimaryLayerConfig(framebuffer_image_handle, kFramebufferImageMetadata), |
| }; |
| |
| // Must match kDisplayId in fake-display.cc. |
| // TODO(https://fxbug.dev/42078942): Do not hardcode the display ID. |
| constexpr display::DisplayId kDisplayId(1); |
| constexpr size_t kDisplayCount = 1; |
| std::array<const display_config_t, kDisplayCount> kDisplayConfigs = { |
| display_config_t{ |
| .display_id = display::ToBanjoDisplayId(kDisplayId), |
| .mode = {}, |
| |
| .cc_flags = 0u, |
| .cc_preoffsets = {}, |
| .cc_coefficients = {}, |
| .cc_postoffsets = {}, |
| |
| .layer_list = kLayers.data(), |
| .layer_count = kLayers.size(), |
| }, |
| }; |
| |
| std::array<client_composition_opcode_t, kLayerCount> client_composition_opcodes = {0u}; |
| size_t client_composition_opcodes_count = 0; |
| |
| // Check and apply the display configuration. |
| config_check_result_t config_check_result = display()->DisplayControllerImplCheckConfiguration( |
| kDisplayConfigs.data(), kDisplayConfigs.size(), client_composition_opcodes.data(), |
| client_composition_opcodes.size(), &client_composition_opcodes_count); |
| EXPECT_EQ(config_check_result, CONFIG_CHECK_RESULT_OK); |
| |
| const display::ConfigStamp config_stamp(1); |
| const config_stamp_t banjo_config_stamp = display::ToBanjoConfigStamp(config_stamp); |
| display()->DisplayControllerImplApplyConfiguration(kDisplayConfigs.data(), kDisplayConfigs.size(), |
| &banjo_config_stamp); |
| |
| // Start capture; wait until the capture ends. |
| EXPECT_FALSE(display_capture_completion.completed().signaled()); |
| EXPECT_OK(display()->DisplayControllerImplStartCapture(capture_handle)); |
| display_capture_completion.completed().Wait(); |
| EXPECT_TRUE(display_capture_completion.completed().signaled()); |
| |
| // Verify the captured image has the same content as the original image. |
| constexpr int kCaptureBytesPerPixel = 4; |
| uint32_t capture_bytes_per_row_divisor = |
| capture_settings.image_format_constraints.bytes_per_row_divisor; |
| uint32_t capture_row_stride_bytes = |
| fbl::round_up(uint32_t{kDisplayWidth} * kCaptureBytesPerPixel, capture_bytes_per_row_divisor); |
| |
| { |
| fzl::VmoMapper capture_mapper; |
| ASSERT_OK(capture_mapper.Map(capture_vmo)); |
| cpp20::span<const uint8_t> capture_bytes( |
| reinterpret_cast<const uint8_t*>(capture_mapper.start()), /*count=*/capture_mapper.size()); |
| zx_cache_flush(capture_bytes.data(), capture_bytes.size(), ZX_CACHE_FLUSH_DATA); |
| |
| for (int row = 0; row < kDisplayHeight; ++row) { |
| cpp20::span<const uint8_t> capture_row = |
| capture_bytes.subspan(row * capture_row_stride_bytes, capture_row_stride_bytes); |
| auto it = capture_row.begin(); |
| for (int col = 0; col < kDisplayWidth; ++col) { |
| std::vector<uint8_t> curr_color(it, it + kCaptureBytesPerPixel); |
| EXPECT_THAT(curr_color, testing::ElementsAreArray(kBlueBgra)) |
| << "Color mismatch at row " << row << " column " << col; |
| it += kCaptureBytesPerPixel; |
| } |
| } |
| } |
| |
| // Release the image. |
| // TODO(https://fxbug.dev/42079040): Consider adding RAII handles to release the |
| // imported images and buffer collections. |
| display()->DisplayControllerImplReleaseImage(framebuffer_image_handle); |
| display()->DisplayControllerImplReleaseCapture(capture_handle); |
| |
| EXPECT_OK( |
| display()->DisplayControllerImplReleaseBufferCollection(kBanjoFramebufferBufferCollectionId)); |
| EXPECT_OK( |
| display()->DisplayControllerImplReleaseBufferCollection(kBanjoCaptureBufferCollectionId)); |
| } |
| |
| class FakeDisplayWithoutCaptureRealSysmemTest : public FakeDisplayRealSysmemTest { |
| public: |
| FakeDisplayDeviceConfig GetFakeDisplayDeviceConfig() const override { |
| return { |
| .manual_vsync_trigger = true, |
| .no_buffer_access = true, |
| }; |
| } |
| }; |
| |
| TEST_F(FakeDisplayWithoutCaptureRealSysmemTest, SetDisplayCaptureInterface) { |
| EXPECT_EQ(display()->DisplayControllerImplIsCaptureSupported(), false); |
| } |
| |
| TEST_F(FakeDisplayWithoutCaptureRealSysmemTest, ImportImageForCapture) { |
| constexpr uint64_t kFakeCollectionId = 1; |
| constexpr uint32_t kFakeCollectionIndex = 0; |
| uint64_t out_capture_handle; |
| EXPECT_EQ(display()->DisplayControllerImplImportImageForCapture( |
| kFakeCollectionId, kFakeCollectionIndex, &out_capture_handle), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(FakeDisplayWithoutCaptureRealSysmemTest, StartCapture) { |
| constexpr uint64_t kFakeCaptureHandle = 1; |
| EXPECT_EQ(display()->DisplayControllerImplStartCapture(kFakeCaptureHandle), ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(FakeDisplayWithoutCaptureRealSysmemTest, ReleaseCapture) { |
| constexpr uint64_t kFakeCaptureHandle = 1; |
| EXPECT_EQ(display()->DisplayControllerImplReleaseCapture(kFakeCaptureHandle), |
| ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| } // namespace |
| } // namespace fake_display |