|  | // 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 "lib/framebuffer/framebuffer.h" | 
|  |  | 
|  | #include <fcntl.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/directory.h> | 
|  | #include <lib/fidl-async/cpp/bind.h> | 
|  | #include <lib/image-format-llcpp/image-format-llcpp.h> | 
|  | #include <zircon/pixelformat.h> | 
|  |  | 
|  | #include <thread> | 
|  |  | 
|  | #include <fbl/unique_fd.h> | 
|  | #include <zxtest/zxtest.h> | 
|  |  | 
|  | namespace fhd = llcpp::fuchsia::hardware::display; | 
|  | namespace sysmem = ::llcpp::fuchsia::sysmem; | 
|  |  | 
|  | zx_status_t fb_bind_with_channel(bool single_buffer, const char** err_msg_out, | 
|  | zx::channel dc_client_channel); | 
|  |  | 
|  | void RunSingleBufferTest() { | 
|  | fbl::unique_fd dc_fd(open("/dev/class/display-controller/000", O_RDWR)); | 
|  | if (!dc_fd) { | 
|  | fprintf(stdout, "Skipping test because of no display controller\n"); | 
|  | return; | 
|  | } | 
|  | dc_fd.reset(); | 
|  | constexpr uint32_t kIterations = 2; | 
|  |  | 
|  | for (uint32_t i = 0; i < kIterations; i++) { | 
|  | const char* error; | 
|  | zx_status_t status = fb_bind(true, &error); | 
|  | if (status == ZX_ERR_NOT_SUPPORTED) { | 
|  | // If the simple display driver is being used then sysmem isn't supported | 
|  | // and libframebuffer isn't either. | 
|  | fprintf(stderr, "Skipping because received ZX_ERR_NOT_SUPPORTED\n"); | 
|  | return; | 
|  | } | 
|  | ASSERT_OK(status); | 
|  | zx_handle_t buffer_handle = fb_get_single_buffer(); | 
|  | EXPECT_NE(ZX_HANDLE_INVALID, buffer_handle); | 
|  |  | 
|  | uint32_t width, height, linear_stride_px; | 
|  | zx_pixel_format_t format; | 
|  | fb_get_config(&width, &height, &linear_stride_px, &format); | 
|  | EXPECT_LE(width, linear_stride_px); | 
|  | EXPECT_LT(0u, ZX_PIXEL_FORMAT_BYTES(format)); | 
|  |  | 
|  | uint64_t buffer_size; | 
|  | EXPECT_OK(zx_vmo_get_size(buffer_handle, &buffer_size)); | 
|  | EXPECT_LE(linear_stride_px * ZX_PIXEL_FORMAT_BYTES(format) * height, buffer_size); | 
|  |  | 
|  | fb_release(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(Framebuffer, SingleBuffer) { | 
|  | zx::event finished; | 
|  | zx::event::create(0, &finished); | 
|  | std::thread execute_thread([&finished]() { | 
|  | RunSingleBufferTest(); | 
|  | finished.signal(0, ZX_USER_SIGNAL_0); | 
|  | }); | 
|  | zx_status_t status = | 
|  | finished.wait_one(ZX_USER_SIGNAL_0, zx::deadline_after(zx::sec(60)), nullptr); | 
|  | EXPECT_EQ(ZX_OK, status); | 
|  | if (status != ZX_OK) { | 
|  | fprintf(stderr, "Test timed out. Maybe no display is connected to device.\n"); | 
|  | execute_thread.detach(); | 
|  | } else { | 
|  | execute_thread.join(); | 
|  | } | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr uint32_t kBytesPerRowDivisor = 128; | 
|  |  | 
|  | class StubDisplayController : public fhd::Controller::Interface { | 
|  | public: | 
|  | StubDisplayController(bool use_ram_domain) : use_ram_domain_(use_ram_domain) { | 
|  | zx::channel sysmem_server, sysmem_client; | 
|  | ASSERT_OK(zx::channel::create(0, &sysmem_server, &sysmem_client)); | 
|  | ASSERT_OK(fdio_service_connect("/svc/fuchsia.sysmem.Allocator", sysmem_server.release())); | 
|  |  | 
|  | sysmem_allocator_ = std::make_unique<sysmem::Allocator::SyncClient>(std::move(sysmem_client)); | 
|  | } | 
|  |  | 
|  | ~StubDisplayController() { current_buffer_collection_->Close(); } | 
|  | void ImportVmoImage(fhd::ImageConfig image_config, ::zx::vmo vmo, int32_t offset, | 
|  | ImportVmoImageCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  | void ImportImage(fhd::ImageConfig image_config, uint64_t collection_id, uint32_t index, | 
|  | ImportImageCompleter::Sync& _completer) override { | 
|  | _completer.Reply(ZX_OK, 1); | 
|  | } | 
|  | void ReleaseImage(uint64_t image_id, ReleaseImageCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  | 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 { _completer.Reply(ZX_OK, 1); } | 
|  |  | 
|  | void DestroyLayer(uint64_t layer_id, DestroyLayerCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | 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::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::ImageConfig image_config, | 
|  | SetLayerPrimaryConfigCompleter::Sync& _completer) override { | 
|  | // Ignore | 
|  | } | 
|  |  | 
|  | void SetLayerPrimaryPosition(uint64_t layer_id, fhd::Transform transform, fhd::Frame src_frame, | 
|  | fhd::Frame dest_frame, | 
|  | SetLayerPrimaryPositionCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | void SetLayerPrimaryAlpha(uint64_t layer_id, fhd::AlphaMode mode, float val, | 
|  | SetLayerPrimaryAlphaCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | void SetLayerCursorConfig(uint64_t layer_id, fhd::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 { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | 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, ::zx::channel collection_token, | 
|  | ImportBufferCollectionCompleter::Sync& _completer) override { | 
|  | zx::channel server, client; | 
|  | ASSERT_OK(zx::channel::create(0, &server, &client)); | 
|  |  | 
|  | ASSERT_TRUE( | 
|  | sysmem_allocator_->BindSharedCollection(std::move(collection_token), std::move(server)) | 
|  | .ok()); | 
|  | current_buffer_collection_ = | 
|  | std::make_unique<sysmem::BufferCollection::SyncClient>(std::move(client)); | 
|  |  | 
|  | _completer.Reply(ZX_OK); | 
|  | } | 
|  | void ReleaseBufferCollection(uint64_t collection_id, | 
|  | ReleaseBufferCollectionCompleter::Sync& _completer) override {} | 
|  |  | 
|  | void SetBufferCollectionConstraints( | 
|  | uint64_t collection_id, fhd::ImageConfig config, | 
|  | SetBufferCollectionConstraintsCompleter::Sync& _completer) override { | 
|  | sysmem::BufferCollectionConstraints constraints; | 
|  | constraints.usage.cpu = sysmem::cpuUsageWriteOften | sysmem::cpuUsageRead; | 
|  | constraints.min_buffer_count = 1; | 
|  | constraints.image_format_constraints_count = 1; | 
|  | auto& image_constraints = constraints.image_format_constraints[0]; | 
|  | image_constraints = image_format::GetDefaultImageFormatConstraints(); | 
|  | image_constraints.pixel_format.type = sysmem::PixelFormatType::BGRA32; | 
|  | image_constraints.pixel_format.has_format_modifier = true; | 
|  | image_constraints.pixel_format.format_modifier.value = sysmem::FORMAT_MODIFIER_LINEAR; | 
|  | image_constraints.color_spaces_count = 1; | 
|  | image_constraints.color_space[0].type = sysmem::ColorSpaceType::SRGB; | 
|  | image_constraints.max_coded_width = 0xffffffff; | 
|  | image_constraints.max_coded_height = 0xffffffff; | 
|  | image_constraints.min_bytes_per_row = 0; | 
|  | image_constraints.max_bytes_per_row = 0xffffffff; | 
|  | image_constraints.bytes_per_row_divisor = kBytesPerRowDivisor; | 
|  |  | 
|  | constraints.has_buffer_memory_constraints = true; | 
|  | constraints.buffer_memory_constraints = image_format::GetDefaultBufferMemoryConstraints(); | 
|  | constraints.buffer_memory_constraints.ram_domain_supported = use_ram_domain_; | 
|  | constraints.buffer_memory_constraints.cpu_domain_supported = !use_ram_domain_; | 
|  |  | 
|  | current_buffer_collection_->SetConstraints(true, constraints); | 
|  | _completer.Reply(ZX_OK); | 
|  | } | 
|  |  | 
|  | void GetSingleBufferFramebuffer(GetSingleBufferFramebufferCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | void IsCaptureSupported(IsCaptureSupportedCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | void ImportImageForCapture(fhd::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 AcknowledgeVsync(uint64_t cookie, AcknowledgeVsyncCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | void SetMinimumRgb(uint8_t minimum_rgb, SetMinimumRgbCompleter::Sync& _completer) override { | 
|  | EXPECT_TRUE(false); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<sysmem::Allocator::SyncClient> sysmem_allocator_; | 
|  | std::unique_ptr<sysmem::BufferCollection::SyncClient> current_buffer_collection_; | 
|  | bool use_ram_domain_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void SendInitialDisplay(const fhd::Controller::EventSender& event_sender, fhd::Mode* mode, | 
|  | uint32_t pixel_format) { | 
|  | fhd::Info info; | 
|  | info.pixel_format = fidl::VectorView(fidl::unowned_ptr(&pixel_format), 1); | 
|  | info.modes = fidl::VectorView(fidl::unowned_ptr(mode), 1); | 
|  | fidl::VectorView<fhd::Info> added(fidl::unowned_ptr(&info), 1); | 
|  | fidl::VectorView<uint64_t> removed; | 
|  |  | 
|  | ASSERT_OK(event_sender.OnDisplaysChanged(std::move(added), std::move(removed))); | 
|  | } | 
|  |  | 
|  | void TestDisplayStride(bool ram_domain) { | 
|  | zx::channel server_channel, client_channel; | 
|  | ASSERT_OK(zx::channel::create(0u, &server_channel, &client_channel)); | 
|  | fhd::Controller::EventSender event_sender(std::move(server_channel)); | 
|  |  | 
|  | StubDisplayController controller(ram_domain); | 
|  | async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); | 
|  | fhd::Mode mode; | 
|  | mode.horizontal_resolution = 301; | 
|  | mode.vertical_resolution = 250; | 
|  | constexpr uint32_t kPixelFormat = ZX_PIXEL_FORMAT_ARGB_8888; | 
|  | SendInitialDisplay(event_sender, &mode, kPixelFormat); | 
|  |  | 
|  | loop.StartThread(); | 
|  |  | 
|  | ASSERT_OK(fidl::BindSingleInFlightOnly(loop.dispatcher(), std::move(event_sender.channel()), | 
|  | &controller)); | 
|  |  | 
|  | const char* error; | 
|  | zx_status_t status = fb_bind_with_channel(true, &error, std::move(client_channel)); | 
|  | EXPECT_OK(status); | 
|  | zx_handle_t buffer_handle = fb_get_single_buffer(); | 
|  | EXPECT_NE(ZX_HANDLE_INVALID, buffer_handle); | 
|  |  | 
|  | uint32_t width, height, linear_stride_px; | 
|  | zx_pixel_format_t format; | 
|  | fb_get_config(&width, &height, &linear_stride_px, &format); | 
|  | EXPECT_EQ(mode.horizontal_resolution, width); | 
|  | EXPECT_EQ(mode.vertical_resolution, height); | 
|  | EXPECT_EQ(kPixelFormat, format); | 
|  |  | 
|  | constexpr uint32_t kBytesPerPixel = 4; | 
|  |  | 
|  | // Round up to be a multiple of kBytesPerRowDivisor bytes. | 
|  | EXPECT_EQ(fbl::round_up(width * kBytesPerPixel, kBytesPerRowDivisor) / kBytesPerPixel, | 
|  | linear_stride_px); | 
|  |  | 
|  | uint64_t buffer_size; | 
|  | EXPECT_OK(zx_vmo_get_size(buffer_handle, &buffer_size)); | 
|  | EXPECT_LE(linear_stride_px * ZX_PIXEL_FORMAT_BYTES(format) * height, buffer_size); | 
|  | } | 
|  |  | 
|  | // Check that the correct stride is returned when a weird one is used. | 
|  | TEST(Framebuffer, DisplayStrideCpuDomain) { TestDisplayStride(false); } | 
|  |  | 
|  | TEST(Framebuffer, DisplayStrideRamDomain) { TestDisplayStride(true); } |