| // Copyright 2018 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 <endian.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/sysinfo/llcpp/fidl.h> |
| #include <fuchsia/sysmem/llcpp/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fidl/cpp/message.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/pixelformat.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstring> |
| #include <memory> |
| #include <string_view> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/string.h> |
| #include <fbl/string_buffer.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/vector.h> |
| |
| #include "ddk/driver.h" |
| #include "fuchsia/hardware/display/llcpp/fidl.h" |
| #include "lib/fdio/directory.h" |
| #include "lib/fzl/vmo-mapper.h" |
| #include "src/graphics/display/testing/display.h" |
| #include "src/graphics/display/testing/virtual-layer.h" |
| |
| namespace fhd = ::llcpp::fuchsia::hardware::display; |
| namespace sysmem = ::llcpp::fuchsia::sysmem; |
| namespace sysinfo = ::llcpp::fuchsia::sysinfo; |
| |
| using testing::display::ColorLayer; |
| using testing::display::CursorLayer; |
| using testing::display::Display; |
| using testing::display::PrimaryLayer; |
| using testing::display::VirtualLayer; |
| |
| static zx_handle_t device_handle; |
| static std::unique_ptr<fhd::Controller::SyncClient> dc; |
| static bool has_ownership; |
| |
| constexpr uint64_t kEventId = 13; |
| constexpr uint32_t kCollectionId = 12; |
| uint64_t capture_id = 0; |
| zx::event client_event_; |
| std::unique_ptr<sysmem::BufferCollection::SyncClient> collection_; |
| zx::vmo capture_vmo; |
| |
| enum TestBundle { |
| SIMPLE = 0, // BUNDLE0 |
| FLIP, // BUNDLE1 |
| INTEL, // BUNDLE2 |
| BUNDLE3, |
| BUNDLE_COUNT, |
| }; |
| |
| enum Platforms { |
| INTEL_PLATFORM = 0, |
| AMLOGIC_PLATFORM, |
| MEDIATEK_PLATFORM, |
| AEMU_PLATFORM, |
| QEMU_PLATFORM, |
| UNKNOWN_PLATFORM, |
| PLATFORM_COUNT, |
| }; |
| |
| Platforms platform = UNKNOWN_PLATFORM; |
| fbl::StringBuffer<sysinfo::BOARD_NAME_LEN> board_name; |
| |
| Platforms GetPlatform(); |
| void Usage(); |
| |
| static bool bind_display(const char* controller, fbl::Vector<Display>* displays) { |
| printf("Opening controller\n"); |
| fbl::unique_fd fd(open(controller, O_RDWR)); |
| if (!fd) { |
| printf("Failed to open display controller (%d)\n", errno); |
| return false; |
| } |
| |
| zx::channel device_server, device_client; |
| zx_status_t status = zx::channel::create(0, &device_server, &device_client); |
| if (status != ZX_OK) { |
| printf("Failed to create device channel %d (%s)\n", status, zx_status_get_string(status)); |
| return false; |
| } |
| |
| zx::channel dc_server, dc_client; |
| status = zx::channel::create(0, &dc_server, &dc_client); |
| if (status != ZX_OK) { |
| printf("Failed to create controller channel %d (%s)\n", status, zx_status_get_string(status)); |
| return false; |
| } |
| |
| fdio_cpp::FdioCaller caller(std::move(fd)); |
| auto open_response = fhd::Provider::Call::OpenController( |
| caller.channel(), std::move(device_server), std::move(dc_server)); |
| if (!open_response.ok()) { |
| printf("Failed to call service handle %d (%s)\n", open_response.status(), |
| open_response.error()); |
| return false; |
| } |
| if (open_response->s != ZX_OK) { |
| printf("Failed to open controller %d (%s)\n", open_response->s, |
| zx_status_get_string(open_response->s)); |
| return false; |
| } |
| |
| dc = std::make_unique<fhd::Controller::SyncClient>(std::move(dc_client)); |
| device_handle = device_client.release(); |
| |
| uint8_t byte_buffer[ZX_CHANNEL_MAX_MSG_BYTES]; |
| fidl::Message msg(fidl::BytePart(byte_buffer, ZX_CHANNEL_MAX_MSG_BYTES), fidl::HandlePart()); |
| while (displays->is_empty()) { |
| printf("Waiting for display\n"); |
| if (ZX_OK != |
| dc->HandleEvents({ |
| .on_displays_changed = |
| [&displays](::fidl::VectorView<fhd::Info> added, |
| ::fidl::VectorView<uint64_t> removed) { |
| for (size_t i = 0; i < added.count(); i++) { |
| displays->push_back(Display(added[i])); |
| } |
| return ZX_OK; |
| }, |
| .on_vsync = [](uint64_t display_id, uint64_t timestamp, |
| ::fidl::VectorView<uint64_t> images) { return ZX_ERR_INVALID_ARGS; }, |
| .on_client_ownership_change = |
| [](bool owns) { |
| has_ownership = owns; |
| return ZX_OK; |
| }, |
| .unknown = []() { return ZX_ERR_STOP; }, |
| })) { |
| printf("Got unexpected message\n"); |
| return false; |
| } |
| } |
| |
| if (!dc->EnableVsync(true).ok()) { |
| printf("Failed to enable vsync\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Display* find_display(fbl::Vector<Display>& displays, const char* id_str) { |
| uint64_t id = strtoul(id_str, nullptr, 10); |
| if (id != 0) { // 0 is the invalid id, and luckily what strtoul returns on failure |
| for (auto& d : displays) { |
| if (d.id() == id) { |
| return &d; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| bool update_display_layers(const fbl::Vector<std::unique_ptr<VirtualLayer>>& layers, |
| const Display& display, fbl::Vector<uint64_t>* current_layers) { |
| fbl::Vector<uint64_t> new_layers; |
| |
| for (auto& layer : layers) { |
| uint64_t id = layer->id(display.id()); |
| if (id != fhd::INVALID_DISP_ID) { |
| new_layers.push_back(id); |
| } |
| } |
| |
| bool layer_change = new_layers.size() != current_layers->size(); |
| if (!layer_change) { |
| for (unsigned i = 0; i < new_layers.size(); i++) { |
| if (new_layers[i] != (*current_layers)[i]) { |
| layer_change = true; |
| break; |
| } |
| } |
| } |
| |
| if (layer_change) { |
| current_layers->swap(new_layers); |
| if (!dc->SetDisplayLayers(display.id(), |
| {fidl::unowned_ptr(current_layers->data()), current_layers->size()}) |
| .ok()) { |
| printf("Failed to set layers\n"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool apply_config() { |
| auto result = dc->CheckConfig(false); |
| if (!result.ok()) { |
| printf("Failed to make check call: %d (%s)\n", result.status(), result.error()); |
| return false; |
| } |
| |
| if (result->res != fhd::ConfigResult::OK) { |
| printf("Config not valid (%d)\n", static_cast<uint32_t>(result->res)); |
| for (const auto& op : result->ops) { |
| printf("Client composition op (display %ld, layer %ld): %hhu\n", op.display_id, op.layer_id, |
| static_cast<uint8_t>(op.opcode)); |
| } |
| return false; |
| } |
| |
| if (!dc->ApplyConfig().ok()) { |
| printf("Apply failed\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| zx_status_t wait_for_vsync(const fbl::Vector<std::unique_ptr<VirtualLayer>>& layers) { |
| fhd::Controller::EventHandlers handlers = { |
| .on_displays_changed = |
| [](::fidl::VectorView<fhd::Info>, ::fidl::VectorView<uint64_t>) { |
| printf("Display disconnected\n"); |
| return ZX_ERR_STOP; |
| }, |
| .on_vsync = |
| [&layers](uint64_t display_id, uint64_t timestamp, ::fidl::VectorView<uint64_t> images) { |
| for (auto& layer : layers) { |
| uint64_t id = layer->image_id(display_id); |
| if (id == 0) { |
| continue; |
| } |
| for (auto image_id : images) { |
| if (image_id == id) { |
| layer->set_frame_done(display_id); |
| } |
| } |
| } |
| |
| for (auto& layer : layers) { |
| if (!layer->is_done()) { |
| return ZX_ERR_NEXT; |
| } |
| } |
| return ZX_OK; |
| }, |
| .on_client_ownership_change = |
| [](bool owned) { |
| has_ownership = owned; |
| return ZX_ERR_NEXT; |
| }, |
| .unknown = []() { return ZX_ERR_STOP; }, |
| }; |
| return dc->HandleEvents(std::move(handlers)); |
| } |
| |
| zx_status_t capture_setup() { |
| // TODO(41413): Pull common image setup code into a library |
| |
| // First make sure capture is supported on this platform |
| auto support_resp = dc->IsCaptureSupported(); |
| if (!support_resp.ok()) { |
| printf("%s: %s\n", __func__, support_resp.error()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (!support_resp.value().result.response().supported) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| // Import event used to get notified once capture is completed |
| auto status = zx::event::create(0, &client_event_); |
| if (status != ZX_OK) { |
| printf("Could not create event %d\n", status); |
| return status; |
| } |
| zx::event e2; |
| status = client_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &e2); |
| if (status != ZX_OK) { |
| printf("Could not dupicate event %d\n", status); |
| return status; |
| } |
| auto event_status = dc->ImportEvent(std::move(e2), kEventId); |
| if (event_status.status() != ZX_OK) { |
| printf("Could not import event: %s\n", event_status.error()); |
| return event_status.status(); |
| } |
| |
| // get connection to sysmem |
| zx::channel sysmem_server_channel; |
| zx::channel sysmem_client_channel; |
| status = zx::channel::create(0, &sysmem_server_channel, &sysmem_client_channel); |
| if (status != ZX_OK) { |
| printf("Could not create sysmem channel %d\n", status); |
| return status; |
| } |
| status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", sysmem_server_channel.release()); |
| if (status != ZX_OK) { |
| printf("Could not connect to sysmem Allocator %d\n", status); |
| return status; |
| } |
| std::unique_ptr<sysmem::Allocator::SyncClient> sysmem_allocator; |
| sysmem_allocator = |
| std::make_unique<sysmem::Allocator::SyncClient>(std::move(sysmem_client_channel)); |
| |
| // Create and import token |
| zx::channel token_server; |
| zx::channel token_client; |
| status = zx::channel::create(0, &token_server, &token_client); |
| if (status != ZX_OK) { |
| printf("Could not create token channel %d\n", status); |
| return status; |
| } |
| std::unique_ptr<sysmem::BufferCollectionToken::SyncClient> token = |
| std::make_unique<sysmem::BufferCollectionToken::SyncClient>(std::move(token_client)); |
| |
| // pass token server to sysmem allocator |
| auto alloc_status = sysmem_allocator->AllocateSharedCollection(std::move(token_server)); |
| if (alloc_status.status() != ZX_OK) { |
| printf("Could not pass token to sysmem allocator: %s\n", alloc_status.error()); |
| return alloc_status.status(); |
| } |
| |
| // duplicate the token and pass to display driver |
| zx::channel token_dup_client; |
| zx::channel token_dup_server; |
| status = zx::channel::create(0, &token_dup_server, &token_dup_client); |
| if (status != ZX_OK) { |
| printf("Could not create duplicate token channel %d\n", status); |
| return status; |
| } |
| sysmem::BufferCollectionToken::SyncClient display_token(std::move(token_dup_client)); |
| auto dup_res = token->Duplicate(ZX_RIGHT_SAME_RIGHTS, std::move(token_dup_server)); |
| if (dup_res.status() != ZX_OK) { |
| printf("Could not duplicate token: %s\n", dup_res.error()); |
| return dup_res.status(); |
| } |
| token->Sync(); |
| auto import_resp = |
| dc->ImportBufferCollection(kCollectionId, std::move(*display_token.mutable_channel())); |
| if (import_resp.status() != ZX_OK) { |
| printf("Could not import token: %s\n", import_resp.error()); |
| return import_resp.status(); |
| } |
| |
| // set buffer constraints |
| fhd::ImageConfig image_config = {}; |
| image_config.type = fhd::TYPE_CAPTURE; |
| auto constraints_resp = dc->SetBufferCollectionConstraints(kCollectionId, image_config); |
| if (constraints_resp.status() != ZX_OK) { |
| printf("Could not set capture constraints %s\n", constraints_resp.error()); |
| return constraints_resp.status(); |
| } |
| |
| // setup our our constraints for buffer to be allocated |
| zx::channel collection_client; |
| zx::channel collection_server; |
| status = zx::channel::create(0, &collection_server, &collection_client); |
| if (status != ZX_OK) { |
| printf("Could not create collection channel %d\n", status); |
| return status; |
| } |
| // let's return token |
| auto bind_resp = sysmem_allocator->BindSharedCollection(std::move(*token->mutable_channel()), |
| std::move(collection_server)); |
| if (bind_resp.status() != ZX_OK) { |
| printf("Could not bind to shared collection: %s\n", bind_resp.error()); |
| return bind_resp.status(); |
| } |
| |
| // finally setup our constraints |
| sysmem::BufferCollectionConstraints constraints = {}; |
| constraints.usage.cpu = sysmem::cpuUsageReadOften | sysmem::cpuUsageWriteOften; |
| constraints.min_buffer_count_for_camping = 1; |
| constraints.has_buffer_memory_constraints = true; |
| constraints.buffer_memory_constraints.ram_domain_supported = true; |
| constraints.image_format_constraints_count = 1; |
| sysmem::ImageFormatConstraints& image_constraints = constraints.image_format_constraints[0]; |
| if (platform == AMLOGIC_PLATFORM) { |
| image_constraints.pixel_format.type = sysmem::PixelFormatType::BGR24; |
| } else { |
| image_constraints.pixel_format.type = sysmem::PixelFormatType::BGRA32; |
| } |
| image_constraints.color_spaces_count = 1; |
| image_constraints.color_space[0] = sysmem::ColorSpace{ |
| .type = sysmem::ColorSpaceType::SRGB, |
| }; |
| image_constraints.min_coded_width = 0; |
| image_constraints.max_coded_width = std::numeric_limits<uint32_t>::max(); |
| image_constraints.min_coded_height = 0; |
| image_constraints.max_coded_height = std::numeric_limits<uint32_t>::max(); |
| image_constraints.min_bytes_per_row = 0; |
| image_constraints.max_bytes_per_row = std::numeric_limits<uint32_t>::max(); |
| image_constraints.max_coded_width_times_coded_height = std::numeric_limits<uint32_t>::max(); |
| image_constraints.layers = 1; |
| image_constraints.coded_width_divisor = 1; |
| image_constraints.coded_height_divisor = 1; |
| image_constraints.bytes_per_row_divisor = 1; |
| image_constraints.start_offset_divisor = 1; |
| image_constraints.display_width_divisor = 1; |
| image_constraints.display_height_divisor = 1; |
| |
| collection_ = |
| std::make_unique<sysmem::BufferCollection::SyncClient>(std::move(collection_client)); |
| auto collection_resp = collection_->SetConstraints(true, constraints); |
| if (collection_resp.status() != ZX_OK) { |
| printf("Could not set buffer constraints: %s\n", collection_resp.error()); |
| return collection_resp.status(); |
| } |
| |
| // wait for allocation |
| auto wait_resp = collection_->WaitForBuffersAllocated(); |
| if (wait_resp.status() != ZX_OK) { |
| printf("Wait for buffer allocation failed: %s\n", wait_resp.error()); |
| return wait_resp.status(); |
| } |
| |
| capture_vmo = std::move(wait_resp.value().buffer_collection_info.buffers[0].vmo); |
| // import image for capture |
| fhd::ImageConfig capture_cfg = {}; // will contain a handle |
| auto importcap_resp = dc->ImportImageForCapture(capture_cfg, kCollectionId, 0); |
| if (importcap_resp.status() != ZX_OK) { |
| printf("Failed to start capture: %s\n", importcap_resp.error()); |
| return importcap_resp.status(); |
| } |
| if (importcap_resp.value().result.is_err()) { |
| printf("Could not import image for capture %d\n", importcap_resp.value().result.err()); |
| return importcap_resp.value().result.err(); |
| } |
| capture_id = importcap_resp.value().result.response().image_id; |
| return ZX_OK; |
| } |
| |
| zx_status_t capture_start() { |
| // start capture |
| auto capstart_resp = dc->StartCapture(kEventId, capture_id); |
| if (capstart_resp.status() != ZX_OK) { |
| printf("Could not start capture: %s\n", capstart_resp.error()); |
| return capstart_resp.status(); |
| } |
| // wait for capture to complete |
| uint32_t observed; |
| auto event_res = |
| client_event_.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(zx::sec(1)), &observed); |
| if (event_res == ZX_OK) { |
| client_event_.signal(ZX_EVENT_SIGNALED, 0); |
| } else { |
| printf("capture failed %d\n", event_res); |
| return event_res; |
| } |
| return ZX_OK; |
| } |
| |
| bool amlogic_capture_compare(void* capture_buf, void* actual_buf, size_t size, uint32_t height, |
| uint32_t width) { |
| auto image_buf = std::make_unique<uint8_t[]>(size); |
| std::memcpy(image_buf.get(), actual_buf, size); |
| |
| auto* imageptr = static_cast<uint8_t*>(image_buf.get()); |
| auto* captureptr = static_cast<uint8_t*>(capture_buf); |
| |
| // first fix endianess |
| auto* tmpptr = reinterpret_cast<uint32_t*>(image_buf.get()); |
| for (size_t i = 0; i < size / 4; i++) { |
| tmpptr[i] = be32toh(tmpptr[i]); |
| } |
| |
| uint32_t capture_stride = ALIGN(width * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_888), 64); |
| uint32_t buffer_stride = ALIGN(width * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_x888), 64); |
| uint32_t buffer_width_bytes = width * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_x888); |
| uint32_t capture_width_bytes = width * ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_888); |
| size_t buf_idx = 0; |
| if (std::string_view(board_name.data(), board_name.size()).find("astro") != |
| std::string_view::npos) { |
| // For Astro only: |
| // Ignore last column. Has junk (hardware bug) |
| // Ignoring last column, means there is a shift by one pixel. |
| // Therefore, image_buffer should start from pixel 1 (i.e. 4th byte since x888) and |
| // capture_buffer should end at width - 3 (i.e. 888) |
| capture_width_bytes -= ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_888); |
| buf_idx = ZX_PIXEL_FORMAT_BYTES(ZX_PIXEL_FORMAT_RGB_x888); |
| } |
| size_t cap_idx = 0; |
| // Ignore first line. It <sometimes> contains junk (hardware bug). |
| bool success = true; |
| for (size_t h = 1; h < height; h++) { |
| for (; cap_idx < capture_width_bytes && buf_idx < buffer_width_bytes;) { |
| // skip the alpha channel |
| if (((buf_idx) % 4) == 0) { |
| buf_idx++; |
| continue; |
| } |
| if (imageptr[h * buffer_stride + buf_idx] == captureptr[h * capture_stride + cap_idx]) { |
| buf_idx++; |
| cap_idx++; |
| continue; |
| } |
| if (imageptr[h * buffer_stride + buf_idx] != 0 && |
| (imageptr[h * buffer_stride + buf_idx] == captureptr[h * capture_stride + cap_idx] + 1 || |
| imageptr[h * buffer_stride + buf_idx] == captureptr[h * capture_stride + cap_idx] - 1)) { |
| buf_idx++; |
| cap_idx++; |
| continue; |
| } |
| success = false; |
| printf("h:%zu, buf[%zu] = 0x%x, cap[%zu] = 0x%x\n", h, h * buffer_stride + buf_idx, |
| imageptr[h * buffer_stride + buf_idx], h * capture_stride + cap_idx, |
| captureptr[h * capture_stride + cap_idx]); |
| break; |
| } |
| if (!success) { |
| break; |
| } |
| } |
| return success; |
| } |
| |
| bool capture_compare(void* input_image_buf, uint32_t height, uint32_t width) { |
| if (input_image_buf == nullptr) { |
| printf("%s: null buf\n", __func__); |
| return false; |
| } |
| |
| fzl::VmoMapper mapped_capture_vmo; |
| size_t capture_vmo_size; |
| auto status = capture_vmo.get_size(&capture_vmo_size); |
| if (status != ZX_OK) { |
| printf("capture vmo get size failed %d\n", status); |
| return status; |
| } |
| status = |
| mapped_capture_vmo.Map(capture_vmo, 0, capture_vmo_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE); |
| if (status != ZX_OK) { |
| printf("Could not map capture vmo %d\n", status); |
| return status; |
| } |
| auto* ptr = reinterpret_cast<uint8_t*>(mapped_capture_vmo.start()); |
| zx_cache_flush(ptr, capture_vmo_size, ZX_CACHE_FLUSH_INVALIDATE); |
| |
| if (platform == AMLOGIC_PLATFORM) { |
| return amlogic_capture_compare(mapped_capture_vmo.start(), input_image_buf, capture_vmo_size, |
| height, width); |
| } |
| |
| return !memcmp(input_image_buf, mapped_capture_vmo.start(), capture_vmo_size); |
| } |
| |
| void capture_release() { |
| dc->ReleaseCapture(capture_id); |
| dc->ReleaseBufferCollection(kCollectionId); |
| } |
| |
| void usage(void) { |
| printf( |
| "Usage: display-test [OPTIONS]\n\n" |
| "--controller N : open controller N [/dev/class/display-controller/N]\n" |
| "--dump : print properties of attached display\n" |
| "--mode-set D N : Set Display D to mode N (use dump option for choices)\n" |
| "--format-set D N : Set Display D to format N (use dump option for choices)\n" |
| "--grayscale : Display images in grayscale mode (default off)\n" |
| "--num-frames N : Run test in N number of frames (default 120)\n" |
| "--delay N : Add delay (ms) between Vsync complete and next configuration\n" |
| "--capture : Capture each display frame and verify\n" |
| "\nTest Modes:\n\n" |
| "--bundle N : Run test from test bundle N as described below\n\n" |
| " bundle %d: Display a single pattern using single buffer\n" |
| " bundle %d: Flip between two buffers to display a pattern\n" |
| " bundle %d: Run the standard Intel-based display tests. This includes\n" |
| " hardware composition of 1 color layer and 3 primary layers.\n" |
| " The tests include alpha blending, translation, scaling\n" |
| " and rotation\n" |
| " bundle %d: 4 layer hardware composition with alpha blending\n" |
| " and image translation\n" |
| " (default: bundle %d)\n\n" |
| "--help : Show this help message\n", |
| SIMPLE, FLIP, INTEL, BUNDLE3, INTEL); |
| } |
| |
| Platforms GetPlatform() { |
| zx::channel sysinfo_server_channel, sysinfo_client_channel; |
| auto status = zx::channel::create(0, &sysinfo_server_channel, &sysinfo_client_channel); |
| if (status != ZX_OK) { |
| return UNKNOWN_PLATFORM; |
| } |
| |
| const char* sysinfo_path = "svc/fuchsia.sysinfo.SysInfo"; |
| fbl::unique_fd sysinfo_fd(open(sysinfo_path, O_RDWR)); |
| if (!sysinfo_fd) { |
| return UNKNOWN_PLATFORM; |
| } |
| fdio_cpp::FdioCaller caller_sysinfo(std::move(sysinfo_fd)); |
| auto result = sysinfo::SysInfo::Call::GetBoardName(caller_sysinfo.channel()); |
| if (!result.ok() || result.value().status != ZX_OK) { |
| return UNKNOWN_PLATFORM; |
| } |
| |
| board_name.Clear(); |
| board_name.Append(result.value().name.data(), result.value().name.size()); |
| |
| printf("Found board %.*s\n", static_cast<int>(board_name.size()), result.value().name.data()); |
| |
| auto board_name_cmp = std::string_view(board_name.data(), board_name.size()); |
| if (board_name_cmp == "x64" || board_name_cmp == "chromebook-x64" || board_name_cmp == "Eve" || |
| board_name_cmp.find("Nocturne") != std::string_view::npos || |
| board_name_cmp.find("NUC") != std::string_view::npos) { |
| return INTEL_PLATFORM; |
| } |
| if (board_name_cmp.find("astro") != std::string_view::npos || |
| board_name_cmp.find("sherlock") != std::string_view::npos || |
| board_name_cmp.find("vim2") != std::string_view::npos || |
| board_name_cmp.find("nelson") != std::string_view::npos) { |
| return AMLOGIC_PLATFORM; |
| } |
| if (board_name_cmp.find("cleo") != std::string_view::npos || |
| board_name_cmp.find("mt8167s_ref") != std::string_view::npos) { |
| return MEDIATEK_PLATFORM; |
| } |
| if (board_name_cmp.find("qemu") != std::string_view::npos || |
| board_name_cmp.find("Standard PC (Q35 + ICH9, 2009)") != std::string_view::npos) { |
| return QEMU_PLATFORM; |
| } |
| return UNKNOWN_PLATFORM; |
| } |
| |
| int main(int argc, const char* argv[]) { |
| printf("Running display test\n"); |
| |
| fbl::Vector<Display> displays; |
| fbl::Vector<fbl::Vector<uint64_t>> display_layers; |
| fbl::Vector<std::unique_ptr<VirtualLayer>> layers; |
| int32_t num_frames = 120; // default to 120 frames |
| int32_t delay = 0; |
| bool capture = false; |
| bool verify_capture = false; |
| const char* controller = "/dev/class/display-controller/000"; |
| |
| platform = GetPlatform(); |
| |
| TestBundle testbundle; |
| switch (platform) { |
| case INTEL_PLATFORM: |
| testbundle = INTEL; |
| break; |
| case AMLOGIC_PLATFORM: |
| testbundle = FLIP; |
| break; |
| case MEDIATEK_PLATFORM: |
| testbundle = BUNDLE3; |
| break; |
| default: |
| testbundle = SIMPLE; |
| } |
| |
| for (int i = 1; i < argc - 1; i++) { |
| if (!strcmp(argv[i], "--controller")) { |
| controller = argv[i + 1]; |
| break; |
| } |
| } |
| |
| if (!bind_display(controller, &displays)) { |
| return -1; |
| } |
| |
| if (displays.is_empty()) { |
| printf("No displays available\n"); |
| return 0; |
| } |
| |
| for (unsigned i = 0; i < displays.size(); i++) { |
| display_layers.push_back(fbl::Vector<uint64_t>()); |
| } |
| |
| argc--; |
| argv++; |
| |
| while (argc) { |
| if (strcmp(argv[0], "--dump") == 0) { |
| for (auto& display : displays) { |
| display.Dump(); |
| } |
| return 0; |
| } else if (strcmp(argv[0], "--mode-set") == 0 || strcmp(argv[0], "--format-set") == 0) { |
| Display* display = find_display(displays, argv[1]); |
| if (!display) { |
| printf("Invalid display \"%s\" for %s\n", argv[1], argv[0]); |
| return -1; |
| } |
| if (strcmp(argv[0], "--mode-set") == 0) { |
| if (!display->set_mode_idx(atoi(argv[2]))) { |
| printf("Invalid mode id\n"); |
| return -1; |
| } |
| } else { |
| if (!display->set_format_idx(atoi(argv[2]))) { |
| printf("Invalid format id\n"); |
| return -1; |
| } |
| } |
| argv += 3; |
| argc -= 3; |
| } else if (strcmp(argv[0], "--grayscale") == 0) { |
| for (auto& d : displays) { |
| d.set_grayscale(true); |
| } |
| argv++; |
| argc--; |
| } else if (strcmp(argv[0], "--num-frames") == 0) { |
| num_frames = atoi(argv[1]); |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[0], "--controller") == 0) { |
| // We already processed this, skip it. |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[0], "--delay") == 0) { |
| delay = atoi(argv[1]); |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[0], "--bundle") == 0) { |
| testbundle = static_cast<TestBundle>(atoi(argv[1])); |
| if (testbundle >= BUNDLE_COUNT || testbundle < 0) { |
| printf("Invalid test bundle selected\n"); |
| usage(); |
| return -1; |
| } |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[0], "--capture") == 0) { |
| capture = true; |
| verify_capture = true; |
| argv += 1; |
| argc -= 1; |
| } else if (strcmp(argv[0], "--help") == 0) { |
| usage(); |
| return 0; |
| } else { |
| printf("Unrecognized argument \"%s\"\n", argv[0]); |
| usage(); |
| return -1; |
| } |
| } |
| |
| if (capture && capture_setup() != ZX_OK) { |
| printf("Cound not setup capture\n"); |
| capture = false; |
| } |
| |
| fbl::AllocChecker ac; |
| if (testbundle == INTEL) { |
| // Intel only supports 90/270 rotation for Y-tiled images, so enable it for testing. |
| constexpr bool kIntelYTiling = true; |
| |
| // Color layer which covers all displays |
| std::unique_ptr<ColorLayer> layer0 = fbl::make_unique_checked<ColorLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layers.push_back(std::move(layer0)); |
| |
| // Layer which covers all displays and uses page flipping. |
| std::unique_ptr<PrimaryLayer> layer1 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer1->SetLayerFlipping(true); |
| layer1->SetAlpha(true, .75); |
| layer1->SetIntelYTiling(kIntelYTiling); |
| layers.push_back(std::move(layer1)); |
| |
| // Layer which covers the left half of the of the first display |
| // and toggles on and off every frame. |
| std::unique_ptr<PrimaryLayer> layer2 = |
| fbl::make_unique_checked<PrimaryLayer>(&ac, &displays[0]); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer2->SetImageDimens(displays[0].mode().horizontal_resolution / 2, |
| displays[0].mode().vertical_resolution); |
| layer2->SetLayerToggle(true); |
| layer2->SetScaling(true); |
| layer2->SetIntelYTiling(kIntelYTiling); |
| layers.push_back(std::move(layer2)); |
| |
| // Intel only supports 3 layers, so add ifdef for quick toggling of the 3rd layer |
| #if 1 |
| // Layer which is smaller than the display and bigger than its image |
| // and which animates back and forth across all displays and also |
| // its src image and also rotates. |
| std::unique_ptr<PrimaryLayer> layer3 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| // Width is the larger of disp_width/2, display_height/2, but we also need |
| // to make sure that it's less than the smaller display dimension. |
| uint32_t width = fbl::min( |
| fbl::max(displays[0].mode().vertical_resolution / 2, |
| displays[0].mode().horizontal_resolution / 2), |
| fbl::min(displays[0].mode().vertical_resolution, displays[0].mode().horizontal_resolution)); |
| uint32_t height = fbl::min(displays[0].mode().vertical_resolution / 2, |
| displays[0].mode().horizontal_resolution / 2); |
| layer3->SetImageDimens(width * 2, height); |
| layer3->SetDestFrame(width, height); |
| layer3->SetSrcFrame(width, height); |
| layer3->SetPanDest(true); |
| layer3->SetPanSrc(true); |
| layer3->SetRotates(true); |
| layer3->SetIntelYTiling(kIntelYTiling); |
| layers.push_back(std::move(layer3)); |
| #else |
| CursorLayer* layer4 = new CursorLayer(displays); |
| layers.push_back(std::move(layer4)); |
| #endif |
| } else if (testbundle == BUNDLE3) { |
| // Mediatek display test |
| uint32_t width = displays[0].mode().horizontal_resolution; |
| uint32_t height = displays[0].mode().vertical_resolution; |
| std::unique_ptr<PrimaryLayer> layer1 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer1->SetAlpha(true, (float)0.2); |
| layer1->SetImageDimens(width, height); |
| layer1->SetSrcFrame(width / 2, height / 2); |
| layer1->SetDestFrame(width / 2, height / 2); |
| layer1->SetPanSrc(true); |
| layer1->SetPanDest(true); |
| layers.push_back(std::move(layer1)); |
| |
| // Layer which covers the left half of the of the first display |
| // and toggles on and off every frame. |
| float alpha2 = (float)0.5; |
| std::unique_ptr<PrimaryLayer> layer2 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer2->SetLayerFlipping(true); |
| layer2->SetAlpha(true, alpha2); |
| layers.push_back(std::move(layer2)); |
| |
| float alpha3 = (float)0.2; |
| std::unique_ptr<PrimaryLayer> layer3 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer3->SetAlpha(true, alpha3); |
| layers.push_back(std::move(layer3)); |
| |
| std::unique_ptr<PrimaryLayer> layer4 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer4->SetAlpha(true, (float)0.3); |
| layers.push_back(std::move(layer4)); |
| } else if (testbundle == FLIP) { |
| // Amlogic display test |
| std::unique_ptr<PrimaryLayer> layer1 = fbl::make_unique_checked<PrimaryLayer>(&ac, displays); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| layer1->SetLayerFlipping(true); |
| layers.push_back(std::move(layer1)); |
| } else if (testbundle == SIMPLE) { |
| // Simple display test |
| bool mirrors = true; |
| std::unique_ptr<PrimaryLayer> layer1 = |
| fbl::make_unique_checked<PrimaryLayer>(&ac, displays, mirrors); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| layers.push_back(std::move(layer1)); |
| } |
| |
| printf("Initializing layers\n"); |
| for (auto& layer : layers) { |
| if (!layer->Init(dc.get())) { |
| printf("Layer init failed\n"); |
| return -1; |
| } |
| } |
| |
| for (auto& display : displays) { |
| display.Init(dc.get()); |
| } |
| |
| if (capture && layers.size() > 1) { |
| printf("Capture verification disabled for multi-layer display tests\n"); |
| verify_capture = false; |
| } |
| |
| printf("Starting rendering\n"); |
| if (capture) { |
| printf("Capturing every frame. Verification is %s\n", verify_capture ? "enabled" : "disabled"); |
| } |
| bool capture_result = true; |
| for (int i = 0; i < num_frames; i++) { |
| for (auto& layer : layers) { |
| // Step before waiting, since not every layer is used every frame |
| // so we won't necessarily need to wait. |
| layer->StepLayout(i); |
| |
| if (!layer->WaitForReady()) { |
| printf("Buffer failed to become free\n"); |
| return -1; |
| } |
| |
| layer->clear_done(); |
| layer->SendLayout(dc.get()); |
| } |
| |
| for (unsigned i = 0; i < displays.size(); i++) { |
| if (!update_display_layers(layers, displays[i], &display_layers[i])) { |
| return -1; |
| } |
| } |
| |
| // This delay is used to skew the timing between vsync and ApplyConfiguration |
| // in order to observe any tearing effects |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(delay))); |
| if (!apply_config()) { |
| return -1; |
| } |
| |
| for (auto& layer : layers) { |
| layer->Render(i); |
| } |
| |
| zx_status_t status; |
| while ((status = wait_for_vsync(layers)) == ZX_ERR_NEXT) { |
| } |
| ZX_ASSERT(status == ZX_OK); |
| if (capture) { |
| // capture has been requested. |
| status = capture_start(); |
| if (status != ZX_OK) { |
| printf("Capture start failed %d\n", status); |
| capture_release(); |
| capture = false; |
| break; |
| } |
| if (verify_capture && |
| !capture_compare(layers[0]->GetCurrentImageBuf(), displays[0].mode().vertical_resolution, |
| displays[0].mode().horizontal_resolution)) { |
| capture_result = false; |
| break; |
| } |
| } |
| } |
| |
| printf("Done rendering\n"); |
| |
| if (capture) { |
| printf("Capture completed\n"); |
| if (verify_capture) { |
| if (capture_result) { |
| printf("Capture Verification Passed\n"); |
| } else { |
| printf("Capture Verification Failed!\n"); |
| } |
| } |
| capture_release(); |
| } |
| zx_handle_close(device_handle); |
| |
| return 0; |
| } |