| // 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 "vc-display.h" |
| |
| #include <fcntl.h> |
| #include <fuchsia/hardware/display/controller/c/banjo.h> |
| #include <fuchsia/hardware/display/llcpp/fidl.h> |
| #include <fuchsia/sysmem/llcpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fidl/coding.h> |
| #include <lib/image-format-llcpp/image-format-llcpp.h> |
| #include <lib/stdcompat/optional.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/vmo.h> |
| #include <string.h> |
| #include <zircon/assert.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <list> |
| #include <unordered_map> |
| |
| #include <fbl/unique_fd.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "vc.h" |
| |
| namespace fhd = fuchsia_hardware_display; |
| namespace sysmem = fuchsia_sysmem; |
| |
| namespace { |
| |
| // Arbitrary |
| constexpr uint32_t kSingleBufferStride = 4; |
| |
| class StubDisplayController : public fidl::WireInterface<fhd::Controller> { |
| public: |
| StubDisplayController() {} |
| |
| virtual ~StubDisplayController() {} |
| |
| void ImportVmoImage(fhd::wire::ImageConfig image_config, ::zx::vmo vmo, int32_t offset, |
| ImportVmoImageCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void ImportImage(fhd::wire::ImageConfig image_config, uint64_t collection_id, uint32_t index, |
| ImportImageCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void ReleaseImage(uint64_t image_id, ReleaseImageCompleter::Sync& _completer) override { |
| images_.remove_if([image_id](uint64_t image) { return (image == image_id); }); |
| } |
| void ImportEvent(::zx::event event, uint64_t id, |
| ImportEventCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void ReleaseEvent(uint64_t id, ReleaseEventCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void CreateLayer(CreateLayerCompleter::Sync& _completer) override { |
| layers_.push_back(next_layer_); |
| _completer.Reply(ZX_OK, next_layer_++); |
| } |
| |
| void DestroyLayer(uint64_t layer_id, DestroyLayerCompleter::Sync& _completer) override { |
| layers_.remove_if([layer_id](uint64_t layer) { return (layer == layer_id); }); |
| } |
| |
| void ImportGammaTable(uint64_t gamma_table_id, ::fidl::Array<float, 256> r, |
| ::fidl::Array<float, 256> g, ::fidl::Array<float, 256> b, |
| ImportGammaTableCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void ReleaseGammaTable(uint64_t gamma_table_id, |
| ReleaseGammaTableCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetDisplayMode(uint64_t display_id, fhd::wire::Mode mode, |
| SetDisplayModeCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void SetDisplayColorConversion(uint64_t display_id, ::fidl::Array<float, 3> preoffsets, |
| ::fidl::Array<float, 9> coefficients, |
| ::fidl::Array<float, 3> postoffsets, |
| SetDisplayColorConversionCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetDisplayGammaTable(uint64_t display_id, uint64_t gamma_table_id, |
| SetDisplayGammaTableCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetDisplayLayers(uint64_t display_id, ::fidl::VectorView<uint64_t> layer_ids, |
| SetDisplayLayersCompleter::Sync& _completer) override { |
| // Ignore |
| } |
| |
| void SetLayerPrimaryConfig(uint64_t layer_id, fhd::wire::ImageConfig image_config, |
| SetLayerPrimaryConfigCompleter::Sync& _completer) override { |
| // Ignore |
| } |
| |
| void SetLayerPrimaryPosition(uint64_t layer_id, fhd::wire::Transform transform, |
| fhd::wire::Frame src_frame, fhd::wire::Frame dest_frame, |
| SetLayerPrimaryPositionCompleter::Sync& _completer) override { |
| // Ignore |
| } |
| |
| void SetLayerPrimaryAlpha(uint64_t layer_id, fhd::wire::AlphaMode mode, float val, |
| SetLayerPrimaryAlphaCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetLayerCursorConfig(uint64_t layer_id, fhd::wire::ImageConfig image_config, |
| SetLayerCursorConfigCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetLayerCursorPosition(uint64_t layer_id, int32_t x, int32_t y, |
| SetLayerCursorPositionCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetLayerColorConfig(uint64_t layer_id, uint32_t pixel_format, |
| ::fidl::VectorView<uint8_t> color_bytes, |
| SetLayerColorConfigCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetLayerImage(uint64_t layer_id, uint64_t image_id, uint64_t wait_event_id, |
| uint64_t signal_event_id, SetLayerImageCompleter::Sync& _completer) override { |
| // Ignore |
| } |
| |
| void CheckConfig(bool discard, CheckConfigCompleter::Sync& _completer) override { |
| _completer.Reply(fhd::wire::ConfigResult::kOk, |
| fidl::VectorView<fhd::wire::ClientCompositionOp>()); |
| } |
| |
| void ApplyConfig(ApplyConfigCompleter::Sync& _completer) override { |
| // Ignore |
| } |
| |
| void EnableVsync(bool enable, EnableVsyncCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetVirtconMode(uint8_t mode, SetVirtconModeCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void ImportBufferCollection(uint64_t collection_id, |
| fidl::ClientEnd<sysmem::BufferCollectionToken> collection_token, |
| ImportBufferCollectionCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| void ReleaseBufferCollection(uint64_t collection_id, |
| ReleaseBufferCollectionCompleter::Sync& _completer) override {} |
| |
| void SetBufferCollectionConstraints( |
| uint64_t collection_id, fhd::wire::ImageConfig config, |
| SetBufferCollectionConstraintsCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void IsCaptureSupported(IsCaptureSupportedCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void ImportImageForCapture(fhd::wire::ImageConfig image_config, uint64_t collection_id, |
| uint32_t index, |
| ImportImageForCaptureCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void StartCapture(uint64_t signal_event_id, uint64_t image_id, |
| StartCaptureCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void ReleaseCapture(uint64_t image_id, ReleaseCaptureCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void SetMinimumRgb(uint8_t minimum_rgb, SetMinimumRgbCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| } |
| |
| void AcknowledgeVsync(uint64_t cookie, AcknowledgeVsyncCompleter::Sync& _completer) override { |
| EXPECT_TRUE(false); |
| }; |
| |
| const std::list<uint64_t>& images() const { return images_; } |
| const std::list<uint64_t>& layers() const { return layers_; } |
| |
| const std::unordered_map<uint64_t, |
| std::unique_ptr<fidl::WireSyncClient<sysmem::BufferCollection>>>& |
| buffer_collections() const { |
| return buffer_collections_; |
| } |
| |
| protected: |
| std::list<uint64_t> images_; |
| uint64_t next_image_ = 1; |
| // We want to make sure that we destroy every layer we create. |
| std::list<uint64_t> layers_; |
| uint64_t next_layer_ = 1; |
| |
| std::unordered_map<uint64_t, std::unique_ptr<fidl::WireSyncClient<sysmem::BufferCollection>>> |
| buffer_collections_; |
| }; |
| |
| class StubSingleBufferDisplayController : public StubDisplayController { |
| public: |
| void ImportVmoImage(fhd::wire::ImageConfig image_config, ::zx::vmo vmo, int32_t offset, |
| ImportVmoImageCompleter::Sync& _completer) override { |
| EXPECT_NE(ZX_HANDLE_INVALID, vmo.get()); |
| images_.push_back(next_image_); |
| _completer.Reply(ZX_OK, next_image_++); |
| } |
| void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override { |
| zx::vmo vmo; |
| zx::vmo::create(4096, 0, &vmo); |
| _completer.Reply(ZX_OK, std::move(vmo), kSingleBufferStride); |
| } |
| }; |
| |
| class StubMultiBufferDisplayController : public StubDisplayController { |
| public: |
| void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override { |
| _completer.Reply(ZX_ERR_NOT_SUPPORTED, zx::vmo(), 0); |
| } |
| void ImportImage(fhd::wire::ImageConfig image_config, uint64_t collection_id, uint32_t index, |
| ImportImageCompleter::Sync& _completer) override { |
| images_.push_back(next_image_); |
| _completer.Reply(ZX_OK, next_image_++); |
| } |
| |
| void ImportBufferCollection(uint64_t collection_id, |
| fidl::ClientEnd<sysmem::BufferCollectionToken> collection_token, |
| ImportBufferCollectionCompleter::Sync& _completer) override { |
| auto endpoints = fidl::CreateEndpoints<sysmem::BufferCollection>(); |
| ASSERT_OK(endpoints.status_value()); |
| ASSERT_OK(get_sysmem_allocator() |
| ->BindSharedCollection(std::move(collection_token), std::move(endpoints->server)) |
| .status()); |
| buffer_collections_[collection_id] = |
| std::make_unique<fidl::WireSyncClient<sysmem::BufferCollection>>( |
| fidl::BindSyncClient(std::move(endpoints->client))); |
| |
| _completer.Reply(ZX_OK); |
| } |
| void ReleaseBufferCollection(uint64_t collection_id, |
| ReleaseBufferCollectionCompleter::Sync& _completer) override { |
| buffer_collections_.erase(collection_id); |
| } |
| |
| void SetBufferCollectionConstraints( |
| uint64_t collection_id, fhd::wire::ImageConfig config, |
| SetBufferCollectionConstraintsCompleter::Sync& _completer) override { |
| sysmem::wire::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = sysmem::wire::kCpuUsageWriteOften | sysmem::wire::kCpuUsageRead; |
| constraints.min_buffer_count = 1; |
| constraints.image_format_constraints_count = 2; |
| for (uint32_t i = 0; i < 2; i++) { |
| auto& image_constraints = constraints.image_format_constraints[i]; |
| image_constraints = image_format::GetDefaultImageFormatConstraints(); |
| if (i == 0) { |
| image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::kBgra32; |
| } else { |
| image_constraints.pixel_format.type = sysmem::wire::PixelFormatType::kRgb565; |
| } |
| image_constraints.pixel_format.has_format_modifier = true; |
| image_constraints.pixel_format.format_modifier.value = sysmem::wire::kFormatModifierLinear; |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0].type = sysmem::wire::ColorSpaceType::kSrgb; |
| image_constraints.max_coded_width = 0xffffffff; |
| image_constraints.max_coded_height = 0xffffffff; |
| image_constraints.max_bytes_per_row = 0xffffffff; |
| image_constraints.bytes_per_row_divisor = 4; |
| } |
| |
| buffer_collections_[collection_id]->SetConstraints(true, constraints); |
| _completer.Reply(ZX_OK); |
| } |
| }; |
| } // namespace |
| |
| zx_status_t log_create_vc(vc_gfx_t* graphics, vc_t** vc_out) { return ZX_OK; } |
| |
| void log_delete_vc(vc_t* vc) {} |
| |
| void set_log_listener_active(bool active) {} |
| |
| void vc_attach_gfx(vc_t* vc) {} |
| |
| zx_status_t vc_init_gfx(vc_gfx_t* gfx, zx_handle_t fb_vmo, int32_t width, int32_t height, |
| zx_pixel_format_t format, int32_t stride) { |
| return ZX_OK; |
| } |
| |
| void vc_change_graphics(vc_gfx_t* graphics) {} |
| |
| class VcDisplayTest : public zxtest::Test { |
| void SetUp() override { ASSERT_TRUE(vc_sysmem_connect()); } |
| |
| void TearDown() override { |
| if (loop_) { |
| // Ensure the loop processes all queued FIDL messages. |
| loop_->Quit(); |
| loop_->JoinThreads(); |
| loop_->ResetQuit(); |
| loop_->RunUntilIdle(); |
| } |
| |
| loop_.reset(); |
| if (controller_) { |
| ASSERT_EQ(controller_->layers().size(), 0); |
| ASSERT_EQ(controller_->images().size(), 0); |
| ASSERT_EQ(controller_->buffer_collections().size(), 0u); |
| } |
| controller_.reset(); |
| } |
| |
| protected: |
| void InitializeServer() { |
| auto endpoints = fidl::CreateEndpoints<fhd::Controller>(); |
| ASSERT_OK(endpoints.status_value()); |
| initialize_display_channel(std::move(endpoints->client)); |
| loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread); |
| loop_->StartThread(); |
| |
| server_binding_ = |
| fidl::BindServer(loop_->dispatcher(), std::move(endpoints->server), controller_.get()); |
| } |
| void SendAddDisplay(fhd::wire::Info* display) { |
| server_binding_.value()->OnDisplaysChanged( |
| fidl::VectorView<fhd::wire::Info>::FromExternal(display, 1), fidl::VectorView<uint64_t>()); |
| } |
| void SendRemoveDisplay(uint64_t id) { |
| server_binding_.value()->OnDisplaysChanged(fidl::VectorView<fhd::wire::Info>(), |
| fidl::VectorView<uint64_t>::FromExternal(&id, 1)); |
| } |
| |
| void ProcessEvent() { ASSERT_OK(dc_callback_handler(ZX_CHANNEL_READABLE)); } |
| |
| std::unique_ptr<StubDisplayController> controller_; |
| |
| // Loop needs to be torn down before controller, because that causes the |
| // binding to close. |
| std::unique_ptr<async::Loop> loop_; |
| |
| // Server binding reference used to send events. |
| cpp17::optional<fidl::ServerBindingRef<fhd::Controller>> server_binding_; |
| }; |
| |
| TEST_F(VcDisplayTest, EmptyRebind) { ASSERT_EQ(rebind_display(true), ZX_ERR_NO_RESOURCES); } |
| |
| TEST_F(VcDisplayTest, OneDisplay) { |
| controller_ = std::make_unique<StubSingleBufferDisplayController>(); |
| InitializeServer(); |
| |
| fhd::wire::Info info; |
| info.id = 1; |
| uint32_t format = 0x0; |
| fhd::wire::Mode mode; |
| info.modes = fidl::VectorView<fhd::wire::Mode>::FromExternal(&mode, 1); |
| info.pixel_format = fidl::VectorView<uint32_t>::FromExternal(&format, 1); |
| |
| SendAddDisplay(&info); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| |
| handle_display_removed(1); |
| ASSERT_FALSE(is_primary_bound()); |
| } |
| |
| TEST_F(VcDisplayTest, TwoDisplays) { |
| controller_ = std::make_unique<StubSingleBufferDisplayController>(); |
| InitializeServer(); |
| |
| fhd::wire::Info hardware_display; |
| hardware_display.id = 1; |
| uint32_t format = 0x0; |
| fhd::wire::Mode mode; |
| hardware_display.modes = fidl::VectorView<fhd::wire::Mode>::FromExternal(&mode, 1); |
| hardware_display.pixel_format = fidl::VectorView<uint32_t>::FromExternal(&format, 1); |
| |
| // Add the first display. |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| |
| // Add the second display. |
| hardware_display.id = 2; |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| // Check that all of the displays were bound. |
| display_info_t* info; |
| int num_displays = 0; |
| list_for_every_entry (get_display_list(), info, display_info_t, node) { |
| ASSERT_TRUE(info->bound); |
| num_displays++; |
| } |
| ASSERT_EQ(num_displays, 2); |
| |
| // Remove the second display. |
| SendRemoveDisplay(2u); |
| ProcessEvent(); |
| // handle_display_removed(2); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| // Remove the first display. |
| SendRemoveDisplay(1u); |
| ProcessEvent(); |
| ASSERT_FALSE(is_primary_bound()); |
| } |
| |
| // This test checks that the primary display switches over correctly. |
| // It allocates display 1 and then display 2, then removes display 1. |
| // Display 2 should switch over to the primary display. |
| TEST_F(VcDisplayTest, ChangePrimaryDisplay) { |
| controller_ = std::make_unique<StubSingleBufferDisplayController>(); |
| InitializeServer(); |
| |
| fhd::wire::Info hardware_display; |
| hardware_display.id = 1; |
| uint32_t format = 0x0; |
| fhd::wire::Mode mode; |
| hardware_display.modes = fidl::VectorView<fhd::wire::Mode>::FromExternal(&mode, 1); |
| hardware_display.pixel_format = fidl::VectorView<uint32_t>::FromExternal(&format, 1); |
| |
| // Add the first display. |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| |
| // Add the second display. |
| hardware_display.id = 2; |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| // Check that all of the displays were bound. |
| display_info_t* info; |
| int num_displays = 0; |
| list_for_every_entry (get_display_list(), info, display_info_t, node) { |
| ASSERT_TRUE(info->bound); |
| num_displays++; |
| } |
| ASSERT_EQ(num_displays, 2); |
| |
| // Remove the first display. |
| SendRemoveDisplay(1); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| // Remove the second display. |
| SendRemoveDisplay(2); |
| ProcessEvent(); |
| ASSERT_FALSE(is_primary_bound()); |
| } |
| |
| TEST_F(VcDisplayTest, SingleBufferVmo) { |
| controller_ = std::make_unique<StubSingleBufferDisplayController>(); |
| InitializeServer(); |
| |
| fhd::wire::Info hardware_display; |
| hardware_display.id = 1; |
| uint32_t format = 0x0; |
| fhd::wire::Mode mode; |
| hardware_display.modes = fidl::VectorView<fhd::wire::Mode>::FromExternal(&mode, 1); |
| hardware_display.pixel_format = fidl::VectorView<uint32_t>::FromExternal(&format, 1); |
| |
| // Add the first display. |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| EXPECT_EQ(1u, controller_->images().size()); |
| |
| EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo); |
| EXPECT_EQ(kSingleBufferStride, primary->stride); |
| |
| SendRemoveDisplay(1); |
| ProcessEvent(); |
| } |
| |
| class VcDisplayMultibufferTest : public VcDisplayTest { |
| public: |
| void SetupMode(zx_pixel_format_t format) { |
| controller_ = std::make_unique<StubMultiBufferDisplayController>(); |
| InitializeServer(); |
| |
| fhd::wire::Info hardware_display; |
| hardware_display.id = 1; |
| fhd::wire::Mode mode; |
| mode.horizontal_resolution = 641; |
| mode.vertical_resolution = 480; |
| hardware_display.modes = fidl::VectorView<fhd::wire::Mode>::FromExternal(&mode, 1); |
| hardware_display.pixel_format = fidl::VectorView<uint32_t>::FromExternal(&format, 1); |
| |
| // Add the first display. |
| SendAddDisplay(&hardware_display); |
| ProcessEvent(); |
| ASSERT_TRUE(is_primary_bound()); |
| } |
| |
| void TeardownDisplay() { |
| SendRemoveDisplay(1); |
| ProcessEvent(); |
| } |
| }; |
| |
| TEST_F(VcDisplayMultibufferTest, RGBA32) { |
| SetupMode(ZX_PIXEL_FORMAT_ARGB_8888); |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| EXPECT_EQ(primary->format, ZX_PIXEL_FORMAT_ARGB_8888); |
| |
| EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo); |
| EXPECT_EQ(641, primary->stride); |
| TeardownDisplay(); |
| } |
| |
| TEST_F(VcDisplayMultibufferTest, RGB565) { |
| SetupMode(ZX_PIXEL_FORMAT_RGB_565); |
| display_info_t* primary = list_peek_head_type(get_display_list(), display_info_t, node); |
| ASSERT_TRUE(primary->bound); |
| |
| EXPECT_EQ(primary->format, ZX_PIXEL_FORMAT_RGB_565); |
| |
| EXPECT_NE(ZX_HANDLE_INVALID, primary->image_vmo); |
| // Stride should be rounded up to be a multiple of 4 bytes. |
| EXPECT_EQ(642, primary->stride); |
| TeardownDisplay(); |
| } |