blob: a516794c54322d1998c0f3cd22c52b71f1ba0dd9 [file] [log] [blame]
// 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 <fidl/fuchsia.hardware.display.types/cpp/wire.h>
#include <fidl/fuchsia.hardware.display/cpp/wire.h>
#include <fidl/fuchsia.images2/cpp/wire.h>
#include <fidl/fuchsia.sysinfo/cpp/wire.h>
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/stdcompat/span.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/zircon-internal/align.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <memory>
#include <string_view>
#include <vector>
#include <fbl/string_buffer.h>
#include <fbl/vector.h>
#include "src/graphics/display/lib/api-types-cpp/buffer-collection-id.h"
#include "src/graphics/display/lib/api-types-cpp/display-id.h"
#include "src/graphics/display/lib/api-types-cpp/event-id.h"
#include "src/graphics/display/lib/api-types-cpp/image-id.h"
#include "src/graphics/display/lib/api-types-cpp/layer-id.h"
#include "src/graphics/display/testing/client-utils/display.h"
#include "src/graphics/display/testing/client-utils/virtual-layer.h"
namespace fhd = fuchsia_hardware_display;
namespace fhdt = fuchsia_hardware_display_types;
namespace images2 = fuchsia_images2;
namespace sysmem2 = fuchsia_sysmem2;
namespace sysinfo = fuchsia_sysinfo;
using display_test::ColorLayer;
using display_test::Display;
using display_test::PrimaryLayer;
using display_test::VirtualLayer;
static zx_handle_t device_handle;
static fidl::WireSyncClient<fhd::Coordinator> dc;
static bool has_ownership;
constexpr display::EventId kEventId(13);
constexpr display::BufferCollectionId kBufferCollectionId(12);
// Use a large ID to avoid conflict with Image IDs allocated by VirtualLayers.
constexpr display::ImageId kCaptureImageId(std::numeric_limits<uint64_t>::max());
zx::event client_event_;
fidl::SyncClient<sysmem2::BufferCollection> collection_;
zx::vmo capture_vmo;
enum TestBundle {
SIMPLE = 0, // BUNDLE0
FLIP, // BUNDLE1
INTEL, // BUNDLE2
BUNDLE3,
BLANK,
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::wire::kBoardNameLen> board_name;
Platforms GetPlatform();
void Usage();
static bool bind_display(const char* coordinator, fbl::Vector<Display>* displays) {
printf("Opening coordinator\n");
zx::result provider = component::Connect<fhd::Provider>(coordinator);
if (provider.is_error()) {
printf("Failed to open display coordinator (%s)\n", provider.status_string());
return false;
}
zx::result dc_endpoints = fidl::CreateEndpoints<fhd::Coordinator>();
if (dc_endpoints.is_error()) {
printf("Failed to create coordinator channel %d (%s)\n", dc_endpoints.error_value(),
dc_endpoints.status_string());
return false;
}
fidl::WireResult open_response =
fidl::WireCall(provider.value())->OpenCoordinatorForPrimary(std::move(dc_endpoints->server));
if (!open_response.ok()) {
printf("Failed to call service handle: %s\n", open_response.FormatDescription().c_str());
return false;
}
if (open_response.value().s != ZX_OK) {
printf("Failed to open coordinator %d (%s)\n", open_response.value().s,
zx_status_get_string(open_response.value().s));
return false;
}
dc = fidl::WireSyncClient(std::move(dc_endpoints->client));
class EventHandler : public fidl::WireSyncEventHandler<fhd::Coordinator> {
public:
EventHandler(fbl::Vector<Display>* displays, bool& has_ownership)
: displays_(displays), has_ownership_(has_ownership) {}
bool invalid_message() const { return invalid_message_; }
void OnDisplaysChanged(fidl::WireEvent<fhd::Coordinator::OnDisplaysChanged>* event) override {
for (size_t i = 0; i < event->added.count(); i++) {
displays_->push_back(Display(/*info=*/event->added[i]));
}
}
void OnVsync(fidl::WireEvent<fhd::Coordinator::OnVsync>* event) override {
invalid_message_ = true;
}
void OnClientOwnershipChange(
fidl::WireEvent<fhd::Coordinator::OnClientOwnershipChange>* event) override {
has_ownership_ = event->has_ownership;
}
private:
fbl::Vector<Display>* const displays_;
bool& has_ownership_;
bool invalid_message_ = false;
};
EventHandler event_handler(displays, has_ownership);
while (displays->is_empty()) {
printf("Waiting for display\n");
if (!dc.HandleOneEvent(event_handler).ok() || event_handler.invalid_message()) {
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) {
errno = 0;
uint64_t id_value = strtoul(id_str, nullptr, 10);
// strtoul() sets `errno` to non-zero on failure.
if (errno != 0) {
fprintf(stderr, "Failed to convert display ID \"%s\": %s\n", id_str, strerror(errno));
errno = 0;
return nullptr;
}
display::DisplayId id(id_value);
if (id != display::kInvalidDisplayId) {
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<display::LayerId>* current_layers) {
fbl::Vector<display::LayerId> new_layers;
for (auto& layer : layers) {
display::LayerId id = layer->id(display.id());
if (id.value() != fhdt::wire::kInvalidDispId) {
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);
std::vector<fhd::wire::LayerId> current_layers_fidl_id;
current_layers_fidl_id.reserve(current_layers->size());
for (const display::LayerId& layer_id : *current_layers) {
current_layers_fidl_id.push_back(display::ToFidlLayerId(layer_id));
}
if (!dc->SetDisplayLayers(
ToFidlDisplayId(display.id()),
fidl::VectorView<fhd::wire::LayerId>::FromExternal(current_layers_fidl_id))
.ok()) {
printf("Failed to set layers\n");
return false;
}
}
return true;
}
std::optional<fhdt::wire::ConfigStamp> apply_config() {
auto result = dc->CheckConfig(false);
if (!result.ok()) {
printf("Failed to make check call: %s\n", result.FormatDescription().c_str());
return std::nullopt;
}
if (result.value().res != fhdt::wire::ConfigResult::kOk) {
printf("Config not valid (%d)\n", static_cast<uint32_t>(result.value().res));
for (const auto& op : result.value().ops) {
printf("Client composition op (display %ld, layer %ld): %hhu\n", op.display_id.value,
op.layer_id.value, static_cast<uint8_t>(op.opcode));
}
return std::nullopt;
}
if (!dc->ApplyConfig().ok()) {
printf("Apply failed\n");
return std::nullopt;
}
auto config_stamp_result = dc->GetLatestAppliedConfigStamp();
if (!config_stamp_result.ok()) {
printf("GetLatestAppliedConfigStamp failed\n");
return std::nullopt;
}
return config_stamp_result.value().stamp;
}
zx_status_t wait_for_vsync(fhdt::wire::ConfigStamp expected_stamp) {
class EventHandler : public fidl::WireSyncEventHandler<fhd::Coordinator> {
public:
explicit EventHandler(fhdt::wire::ConfigStamp expected_stamp)
: expected_stamp_(expected_stamp) {}
zx_status_t status() const { return status_; }
void OnDisplaysChanged(fidl::WireEvent<fhd::Coordinator::OnDisplaysChanged>* event) override {
printf("Display disconnected\n");
status_ = ZX_ERR_STOP;
}
void OnVsync(fidl::WireEvent<fhd::Coordinator::OnVsync>* event) override {
// Acknowledge cookie if non-zero
if (event->cookie) {
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)dc->AcknowledgeVsync(event->cookie);
}
if (event->applied_config_stamp.value >= expected_stamp_.value) {
status_ = ZX_OK;
} else {
status_ = ZX_ERR_NEXT;
}
}
void OnClientOwnershipChange(
fidl::WireEvent<fhd::Coordinator::OnClientOwnershipChange>* event) override {
has_ownership = event->has_ownership;
status_ = ZX_ERR_NEXT;
}
private:
fhdt::wire::ConfigStamp expected_stamp_;
zx_status_t status_ = ZX_OK;
};
EventHandler event_handler(expected_stamp);
const fidl::Status status = dc.HandleOneEvent(event_handler);
if (!status.ok()) {
if (status.reason() == fidl::Reason::kUnexpectedMessage) {
return ZX_ERR_STOP;
}
return status.status();
}
return event_handler.status();
}
zx_status_t set_minimum_rgb(uint8_t min_rgb) {
auto resp = dc->SetMinimumRgb(min_rgb);
return resp.status();
}
zx_status_t capture_setup(Display& display) {
// TODO(https://fxbug.dev/42117494): 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.FormatDescription().c_str());
return ZX_ERR_NOT_SUPPORTED;
}
if (!support_resp->value()->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 duplicate event %d\n", status);
return status;
}
auto event_status = dc->ImportEvent(std::move(e2), display::ToFidlEventId(kEventId));
if (event_status.status() != ZX_OK) {
printf("Could not import event: %s\n", event_status.FormatDescription().c_str());
return event_status.status();
}
// get connection to sysmem
zx::result sysmem_client = component::Connect<sysmem2::Allocator>();
if (sysmem_client.is_error()) {
printf("Could not connect to sysmem Allocator %s\n", sysmem_client.status_string());
return sysmem_client.status_value();
}
auto sysmem_allocator = fidl::SyncClient(std::move(sysmem_client.value()));
// Create and import token
zx::result token_endpoints = fidl::CreateEndpoints<sysmem2::BufferCollectionToken>();
if (token_endpoints.is_error()) {
printf("Could not create token channel %d\n", token_endpoints.error_value());
return token_endpoints.error_value();
}
auto token = fidl::SyncClient(std::move(token_endpoints->client));
// pass token server to sysmem allocator
sysmem2::AllocatorAllocateSharedCollectionRequest allocate_shared_request;
allocate_shared_request.token_request() = std::move(token_endpoints->server);
auto allocate_shared_result =
sysmem_allocator->AllocateSharedCollection(std::move(allocate_shared_request));
if (!allocate_shared_result.is_ok()) {
printf("Could not pass token to sysmem allocator: %s\n",
allocate_shared_result.error_value().FormatDescription().c_str());
return allocate_shared_result.error_value().status();
}
// duplicate the token and pass to display driver
zx::result token_dup_endpoints = fidl::CreateEndpoints<sysmem2::BufferCollectionToken>();
if (token_dup_endpoints.is_error()) {
printf("Could not create duplicate token channel %d\n", token_dup_endpoints.error_value());
return token_dup_endpoints.error_value();
}
fidl::SyncClient display_token(std::move(token_dup_endpoints->client));
fuchsia_sysmem2::BufferCollectionTokenDuplicateRequest dup_request;
dup_request.rights_attenuation_mask() = ZX_RIGHT_SAME_RIGHTS;
dup_request.token_request() = std::move(token_dup_endpoints->server);
auto dup_res = token->Duplicate(std::move(dup_request));
if (!dup_res.is_ok()) {
printf("Could not duplicate token: %s\n", dup_res.error_value().FormatDescription().c_str());
return dup_res.error_value().status();
}
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)token->Sync();
auto import_resp =
dc->ImportBufferCollection(display::ToFidlBufferCollectionId(kBufferCollectionId),
fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken>(
display_token.TakeClientEnd().TakeChannel()));
if (import_resp.status() != ZX_OK) {
printf("Could not import token: %s\n", import_resp.FormatDescription().c_str());
return import_resp.status();
}
// set buffer constraints
fhdt::wire::ImageBufferUsage image_buffer_usage = {
.tiling_type = fhdt::wire::kImageTilingTypeCapture,
};
auto constraints_resp = dc->SetBufferCollectionConstraints(
display::ToFidlBufferCollectionId(kBufferCollectionId), image_buffer_usage);
if (constraints_resp.status() != ZX_OK) {
printf("Could not set capture constraints %s\n", constraints_resp.FormatDescription().c_str());
return constraints_resp.status();
}
// setup our our constraints for buffer to be allocated
zx::result collection_endpoints = fidl::CreateEndpoints<sysmem2::BufferCollection>();
if (collection_endpoints.is_error()) {
printf("Could not create collection channel %d\n", collection_endpoints.error_value());
return collection_endpoints.error_value();
}
// let's return token
fuchsia_sysmem2::AllocatorBindSharedCollectionRequest bind_shared_request;
bind_shared_request.token() = token.TakeClientEnd();
bind_shared_request.buffer_collection_request() = std::move(collection_endpoints->server);
auto bind_resp = sysmem_allocator->BindSharedCollection(std::move(bind_shared_request));
if (!bind_resp.is_ok()) {
printf("Could not bind to shared collection: %s\n",
bind_resp.error_value().FormatDescription().c_str());
return bind_resp.error_value().status();
}
// finally setup our constraints
fuchsia_sysmem2::BufferCollectionSetConstraintsRequest set_constraints_request;
auto& constraints = set_constraints_request.constraints().emplace();
constraints.usage().emplace().cpu() = sysmem2::kCpuUsageReadOften | sysmem2::kCpuUsageWriteOften;
constraints.min_buffer_count_for_camping() = 1;
constraints.buffer_memory_constraints().emplace().ram_domain_supported() = true;
sysmem2::ImageFormatConstraints& image_constraints =
constraints.image_format_constraints().emplace().emplace_back();
if (platform == AMLOGIC_PLATFORM) {
image_constraints.pixel_format() = images2::PixelFormat::kB8G8R8;
} else {
image_constraints.pixel_format() = images2::PixelFormat::kB8G8R8A8;
}
image_constraints.color_spaces().emplace().emplace_back(images2::ColorSpace::kSrgb);
collection_ = fidl::SyncClient(std::move(collection_endpoints->client));
auto set_constraints_result = collection_->SetConstraints(std::move(set_constraints_request));
if (!set_constraints_result.is_ok()) {
printf("Could not set buffer constraints: %s\n",
set_constraints_result.error_value().FormatDescription().c_str());
return set_constraints_result.error_value().status();
}
// wait for allocation
auto wait_resp = collection_->WaitForAllBuffersAllocated();
if (!wait_resp.is_ok()) {
printf("Wait for buffer allocation failed: %s\n",
wait_resp.error_value().FormatDescription().c_str());
zx_status_t status;
if (wait_resp.error_value().is_framework_error()) {
status = ZX_ERR_INTERNAL;
} else {
status = sysmem::V1CopyFromV2Error(wait_resp.error_value().domain_error());
}
return status;
}
capture_vmo =
std::move(wait_resp.value().buffer_collection_info()->buffers()->at(0).vmo().value());
// import image for capture
// TODO(https://fxbug.dev/332521780): Display clients will be required to
// pass the captured display's mode information.
fhdt::wire::ImageMetadata capture_metadata = {
.width = display.mode().horizontal_resolution,
.height = display.mode().vertical_resolution,
.tiling_type = fhdt::wire::kImageTilingTypeCapture,
};
fidl::WireResult import_capture_result = dc->ImportImage(
capture_metadata,
fhd::wire::BufferId{
.buffer_collection_id = display::ToFidlBufferCollectionId(kBufferCollectionId),
.buffer_index = 0,
},
display::ToFidlImageId(kCaptureImageId));
if (import_capture_result.status() != ZX_OK) {
printf("Failed to start capture: %s\n", import_capture_result.FormatDescription().c_str());
return import_capture_result.status();
}
return ZX_OK;
}
zx_status_t capture_start() {
// start capture
fidl::WireResult start_capture_result =
dc->StartCapture(display::ToFidlEventId(kEventId), display::ToFidlImageId(kCaptureImageId));
if (start_capture_result.status() != ZX_OK) {
printf("Could not start capture: %s\n", start_capture_result.FormatDescription().c_str());
return start_capture_result.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 AmlogicCompareCapturedImage(cpp20::span<const uint8_t> captured_image,
cpp20::span<const uint8_t> input_image,
fuchsia_images2::wire::PixelFormat input_image_pixel_format,
int height, int width) {
assert(input_image_pixel_format == fuchsia_images2::wire::PixelFormat::kB8G8R8A8 ||
input_image_pixel_format == fuchsia_images2::wire::PixelFormat::kR8G8B8A8);
auto expected_image = std::vector<uint8_t>(input_image.begin(), input_image.end());
// Amlogic captured images are always in packed (least-significant) B8G8R8
// (most-siginificant) format. To avoid out-of-order data access, we convert
// the endianness of the input image, if they are in (least-significant)
// R8G8B8A8 (most-significant) order.
if (input_image_pixel_format == fuchsia_images2::wire::PixelFormat::kB8G8R8A8) {
for (size_t i = 0; i + 3 < expected_image.size(); i += 4) {
std::swap(expected_image[i], expected_image[i + 3]);
std::swap(expected_image[i + 1], expected_image[i + 2]);
}
}
// Capture image are of RGB888 formats; each pixel has 3 bytes.
constexpr int kCaptureImageBytesPerPixel = 3;
const int capture_stride = ZX_ALIGN(width * kCaptureImageBytesPerPixel, 64);
// Input image are of R8G8B8A8 or B8R8G8A8 formats; each pixel has 4 bytes.
constexpr int kInputImageByetsPerPixel = 4;
const int expected_stride = ZX_ALIGN(width * kInputImageByetsPerPixel, 64);
// Ignore the first row. It sometimes contains junk (hardware bug).
int start_row = 1;
int end_row = height;
int start_column = 0;
// Ignore the last column for Astro only. It contains junk bytes (hardware bug).
const bool board_is_astro =
std::string_view(board_name.data(), board_name.size()).find("astro") !=
std::string_view::npos;
int end_column = board_is_astro ? (width - 1) : width;
for (int row = start_row; row < end_row; row++) {
for (int column = start_column; column < end_column; column++) {
for (int channel = 0; channel < 3; channel++) {
// On expected image, the first byte is alpha channel, so we ignore it.
int expected_byte_index = row * expected_stride + column * 4 + 1 + channel;
int captured_byte_index = row * capture_stride + column * 3 + channel;
constexpr int kColorDifferenceThreshold = 2;
if (abs(expected_image[expected_byte_index] - captured_image[captured_byte_index]) >
kColorDifferenceThreshold) {
printf("Pixel different: (row=%d, col=%d, channel=%d) expected 0x%02x captured 0x%02x\n",
row, column, channel, expected_image[expected_byte_index],
captured_image[captured_byte_index]);
return false;
}
}
}
}
return true;
}
bool CompareCapturedImage(cpp20::span<const uint8_t> input_image,
fuchsia_images2::wire::PixelFormat input_image_pixel_format, int height,
int width) {
if (input_image.data() == nullptr) {
printf("%s: input image is null\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 AmlogicCompareCapturedImage(
cpp20::span(reinterpret_cast<const uint8_t*>(mapped_capture_vmo.start()), capture_vmo_size),
input_image, input_image_pixel_format, height, width);
}
return !memcmp(input_image.data(), mapped_capture_vmo.start(), capture_vmo_size);
}
void capture_release() {
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)dc->ReleaseImage(display::ToFidlImageId(kCaptureImageId));
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)dc->ReleaseBufferCollection(display::ToFidlBufferCollectionId(kBufferCollectionId));
}
void usage(void) {
printf(
"Usage: display-test [OPTIONS]\n\n"
"--controller N : open coordinator N [/dev/class/display-coordinator/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"
" N can be an integer or 'infinite'\n"
"--delay N : Add delay (ms) between Vsync complete and next configuration\n"
"--capture : Capture each display frame and verify\n"
"--fgcolor 0xaarrggbb : Set foreground color\n"
"--bgcolor 0xaarrggbb : Set background color\n"
"--preoffsets x,y,z : set preoffsets for color correction\n"
"--postoffsets x,y,z : set postoffsets for color correction\n"
"--coeff c00,c01,...,,c22 : 3x3 coefficient matrix for color correction\n"
"--enable-alpha : Enable per-pixel alpha blending.\n"
"--opacity o : Set the opacity of the screen\n"
" <o> is a value between [0 1] inclusive\n"
"--enable-compression : Enable framebuffer compression.\n"
"--apply-config-once : Apply configuration once in single buffer mode.\n"
"--clamp-rgb c : Set minimum RGB value [0 255].\n"
"--configs-per-vsync n : Number of configs applied per vsync\n"
"--pattern pattern : Image pattern to use - 'checkerboard' (default) or 'border'\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"
" bundle %d: Blank the screen and sleep for --num-frames.\n"
" (default: bundle %d)\n\n"
"--help : Show this help message\n",
SIMPLE, FLIP, INTEL, BUNDLE3, BLANK, INTEL);
}
Platforms GetPlatform() {
zx::result channel = component::Connect<sysinfo::SysInfo>();
if (channel.is_error()) {
return UNKNOWN_PLATFORM;
}
const fidl::WireResult result = fidl::WireCall(channel.value())->GetBoardName();
if (!result.ok()) {
return UNKNOWN_PLATFORM;
}
const fidl::WireResponse response = result.value();
if (response.status != ZX_OK) {
return UNKNOWN_PLATFORM;
};
board_name.Clear();
board_name.Append(response.name.get());
printf("Found board %s\n", board_name.c_str());
std::string_view board_name_cmp(board_name);
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("vim3") != std::string_view::npos ||
board_name_cmp.find("nelson") != std::string_view::npos ||
board_name_cmp.find("luis") != 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<display::LayerId>> display_layers;
fbl::Vector<std::unique_ptr<VirtualLayer>> layers;
std::optional<int32_t> num_frames = 120; // default to 120 frames. std::nullopt means infinite
int32_t delay = 0;
bool capture = false;
bool verify_capture = false;
const char* coordinator = "/dev/class/display-coordinator/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")) {
coordinator = argv[i + 1];
break;
}
}
if (!bind_display(coordinator, &displays)) {
usage();
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<display::LayerId>());
}
argc--;
argv++;
display_test::Image::Pattern image_pattern = display_test::Image::Pattern::kCheckerboard;
uint32_t fgcolor_rgba = 0xffff0000; // red (default)
uint32_t bgcolor_rgba = 0xffffffff; // white (default)
bool use_color_correction = false;
int clamp_rgb = -1;
display_test::ColorCorrectionArgs color_correction_args;
float alpha_val = std::nanf("");
bool enable_alpha = false;
bool enable_compression = false;
bool apply_config_once = false;
uint32_t configs_per_vsync = 1;
while (argc) {
if (strcmp(argv[0], "--dump") == 0) {
for (auto& display : displays) {
display.Dump();
}
return 0;
}
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) {
if (strcmp(argv[1], "infinite") == 0) {
num_frames = std::nullopt;
} else {
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], "--clamp-rgb") == 0) {
clamp_rgb = atoi(argv[1]);
if (clamp_rgb < 0 || clamp_rgb > 255) {
usage();
return -1;
}
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--fgcolor") == 0) {
fgcolor_rgba = static_cast<uint32_t>(strtoul(argv[1], nullptr, 16));
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--bgcolor") == 0) {
bgcolor_rgba = static_cast<uint32_t>(strtoul(argv[1], nullptr, 16));
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--preoffsets") == 0) {
sscanf(argv[1], "%f,%f,%f", &color_correction_args.preoffsets[0],
&color_correction_args.preoffsets[1], &color_correction_args.preoffsets[2]);
use_color_correction = true;
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--postoffsets") == 0) {
sscanf(argv[1], "%f,%f,%f", &color_correction_args.postoffsets[0],
&color_correction_args.postoffsets[1], &color_correction_args.postoffsets[2]);
use_color_correction = true;
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--coeff") == 0) {
sscanf(argv[1], "%f,%f,%f,%f,%f,%f,%f,%f,%f", &color_correction_args.coeff[0],
&color_correction_args.coeff[1], &color_correction_args.coeff[2],
&color_correction_args.coeff[3], &color_correction_args.coeff[4],
&color_correction_args.coeff[5], &color_correction_args.coeff[6],
&color_correction_args.coeff[7], &color_correction_args.coeff[8]);
use_color_correction = true;
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--enable-alpha") == 0) {
enable_alpha = true;
argv += 1;
argc -= 1;
} else if (strcmp(argv[0], "--opacity") == 0) {
enable_alpha = true;
alpha_val = std::stof(argv[1]);
if (alpha_val < 0 || alpha_val > 1) {
printf("Invalid alpha value. Must be between 0 and 1\n");
usage();
return -1;
}
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--enable-compression") == 0) {
enable_compression = true;
argv += 1;
argc -= 1;
} else if (strcmp(argv[0], "--apply-config-once") == 0) {
apply_config_once = true;
argv += 1;
argc -= 1;
} else if (strcmp(argv[0], "--configs-per-vsync") == 0) {
configs_per_vsync = atoi(argv[1]);
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--pattern") == 0) {
if (strcmp(argv[1], "checkerboard") == 0) {
image_pattern = display_test::Image::Pattern::kCheckerboard;
} else if (strcmp(argv[1], "border") == 0) {
image_pattern = display_test::Image::Pattern::kBorder;
} else {
printf("Invalid image pattern \"%s\".\n", argv[1]);
usage();
return 0;
}
argv += 2;
argc -= 2;
} else if (strcmp(argv[0], "--help") == 0) {
usage();
return 0;
} else {
printf("Unrecognized argument \"%s\"\n", argv[0]);
usage();
return -1;
}
}
// TODO(https://fxbug.dev/42076494): AFBC compression test doesn't work on
// amlogic-display; the AFBC encoding format supported by amlogic-display
// driver is not compatible with the AFBC buffer formats and generation logic
// used by this test. Once amlogic-display supports non-tiled-header formats
// and AFBC format switching on the fly, this test will work again and we can
// delete the error message below.
if (enable_compression) {
fprintf(stderr,
"AFBC compression test is not working for amlogic-display. "
"The --enable-compression option will be re-enabled once "
"https://fxbug.dev/42076494 is fixed.\n");
return -1;
}
if (use_color_correction) {
for (auto& d : displays) {
d.apply_color_correction(true);
}
}
if (capture && capture_setup(displays[0]) != ZX_OK) {
printf("Could not setup capture\n");
capture = false;
}
if (clamp_rgb != -1) {
if (set_minimum_rgb(static_cast<uint8_t>(clamp_rgb)) != ZX_OK) {
printf("Warning: RGB Clamping Not Supported!\n");
}
}
// Call apply_config for each frame by default.
std::optional<int32_t> max_apply_configs(num_frames);
fbl::AllocChecker ac;
if (testbundle == INTEL) {
// Intel only supports 90/270 rotation for Y-tiled images, so enable it for testing.
constexpr fuchsia_images2::wire::PixelFormatModifier kIntelYTilingModifier =
fuchsia_images2::wire::PixelFormatModifier::kIntelI915YTiled;
// 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->SetFormatModifier(kIntelYTilingModifier);
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->SetFormatModifier(kIntelYTilingModifier);
layers.push_back(std::move(layer2));
// 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 = std::min(
std::max(displays[0].mode().vertical_resolution / 2,
displays[0].mode().horizontal_resolution / 2),
std::min(displays[0].mode().vertical_resolution, displays[0].mode().horizontal_resolution));
uint32_t height = std::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->SetFormatModifier(kIntelYTilingModifier);
layers.push_back(std::move(layer3));
} 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, image_pattern, fgcolor_rgba, bgcolor_rgba);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if (enable_alpha) {
layer1->SetAlpha(true, alpha_val);
}
layer1->SetLayerFlipping(true);
if (enable_compression) {
layer1->SetFormatModifier(fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16);
}
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, image_pattern, fgcolor_rgba, bgcolor_rgba, mirrors);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if (enable_compression) {
layer1->SetFormatModifier(fuchsia_images2::wire::PixelFormatModifier::kArmAfbc16X16);
}
if (apply_config_once) {
max_apply_configs = 1;
}
layers.push_back(std::move(layer1));
} else if (testbundle == BLANK) {
// 0 layers, applied one time
max_apply_configs = 1;
}
printf("Initializing layers\n");
for (auto& layer : layers) {
if (!layer->Init(dc)) {
printf("Layer init failed\n");
return -1;
}
}
for (auto& display : displays) {
display.Init(dc, color_correction_args);
}
if (capture && layers.size() != 1) {
printf("Capture disabled: verification only works for single-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; !num_frames || 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);
}
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)));
fhdt::wire::ConfigStamp expected_stamp = {.value = fhdt::wire::kInvalidConfigStampValue};
if (!max_apply_configs || i < max_apply_configs) {
for (uint32_t cpv = 0; cpv < configs_per_vsync; cpv++) {
auto maybe_expected_stamp = apply_config();
if (!maybe_expected_stamp.has_value()) {
return -1;
} else {
expected_stamp = *maybe_expected_stamp;
}
}
}
for (auto& layer : layers) {
layer->Render(i);
}
zx_status_t status = ZX_OK;
while (layers.size() != 0 && (status = wait_for_vsync(expected_stamp)) == 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 &&
!CompareCapturedImage(
cpp20::span(reinterpret_cast<const uint8_t*>(layers[0]->GetCurrentImageBuf()),
layers[0]->GetCurrentImageSize()),
/*input_image_pixel_format=*/displays[0].format(),
/*height=*/displays[0].mode().vertical_resolution,
/*width=*/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;
}