blob: 79bc0c6862b2ea9a9727a100129730a19d01aa46 [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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <zircon/device/display-controller.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <fbl/algorithm.h>
#include <fbl/vector.h>
#include <fbl/unique_fd.h>
#include <lib/fidl/cpp/message.h>
#include <lib/fidl/cpp/string_view.h>
#include <lib/fidl/cpp/vector_view.h>
#include <lib/fzl/fdio.h>
#include <zircon/pixelformat.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include "display.h"
#include "fuchsia/hardware/display/c/fidl.h"
#include "virtual-layer.h"
static zx_handle_t device_handle;
static zx_handle_t dc_handle;
static bool has_ownership;
static bool wait_for_driver_event(zx_time_t deadline) {
zx_handle_t observed;
uint32_t signals = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
if (zx_object_wait_one(dc_handle, signals, ZX_TIME_INFINITE, &observed) != ZX_OK) {
printf("Wait failed\n");
return false;
}
if (observed & ZX_CHANNEL_PEER_CLOSED) {
printf("Display controller died\n");
return false;
}
return true;
}
static bool bind_display(fbl::Vector<Display>* displays) {
printf("Opening controller\n");
fbl::unique_fd fd(open("/dev/class/display-controller/000", 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;
}
fzl::FdioCaller caller(std::move(fd));
zx_status_t fidl_status = fuchsia_hardware_display_ProviderOpenController(
caller.borrow_channel(), device_server.release(), dc_server.release(), &status);
if (fidl_status != ZX_OK) {
printf("Failed to call service handle %d (%s)\n", fidl_status,
zx_status_get_string(fidl_status));
return false;
}
if (status != ZX_OK) {
printf("Failed to open controller %d (%s)\n", status, zx_status_get_string(status));
return false;
}
dc_handle = dc_client.release();
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("Wating for display\n");
if (!wait_for_driver_event(ZX_TIME_INFINITE)) {
return false;
}
printf("Querying display\n");
if (msg.Read(dc_handle, 0) != ZX_OK) {
printf("Read failed\n");
return false;
}
if (msg.ordinal() == fuchsia_hardware_display_ControllerDisplaysChangedOrdinal) {
const char* err_msg;
if (msg.Decode(&fuchsia_hardware_display_ControllerDisplaysChangedEventTable,
&err_msg) != ZX_OK) {
printf("Fidl decode error %d %s\n", msg.ordinal(), err_msg);
return false;
}
auto changes =
reinterpret_cast<fuchsia_hardware_display_ControllerDisplaysChangedEvent*>(
msg.bytes().data());
auto display_info =
reinterpret_cast<fuchsia_hardware_display_Info*>(changes->added.data);
for (unsigned i = 0; i < changes->added.count; i++) {
displays->push_back(Display(display_info + i));
}
} else if (msg.ordinal() ==
fuchsia_hardware_display_ControllerClientOwnershipChangeOrdinal) {
has_ownership =
((fuchsia_hardware_display_ControllerClientOwnershipChangeEvent*)msg.bytes().data())
->has_ownership;
} else {
printf("Got unexpected message %d\n", msg.ordinal());
return false;
}
}
fuchsia_hardware_display_ControllerEnableVsyncRequest enable_vsync;
enable_vsync.hdr.ordinal = fuchsia_hardware_display_ControllerEnableVsyncOrdinal;
enable_vsync.enable = true;
if (zx_channel_write(dc_handle, 0, &enable_vsync, sizeof(enable_vsync), nullptr, 0) != ZX_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<fbl::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 != INVALID_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);
uint32_t size = static_cast<int32_t>(
sizeof(fuchsia_hardware_display_ControllerSetDisplayLayersRequest) +
FIDL_ALIGN(sizeof(uint64_t) * current_layers->size()));
uint8_t fidl_bytes[size];
auto set_layers_msg =
reinterpret_cast<fuchsia_hardware_display_ControllerSetDisplayLayersRequest*>(
fidl_bytes);
set_layers_msg->hdr.ordinal = fuchsia_hardware_display_ControllerSetDisplayLayersOrdinal;
set_layers_msg->layer_ids.count = current_layers->size();
set_layers_msg->layer_ids.data = reinterpret_cast<void*>(FIDL_ALLOC_PRESENT);
set_layers_msg->display_id = display.id();
auto layer_list = reinterpret_cast<uint64_t*>(set_layers_msg + 1);
for (auto layer_id : *current_layers) {
*(layer_list++) = layer_id;
}
if (zx_channel_write(dc_handle, 0, fidl_bytes, size, nullptr, 0) != ZX_OK) {
printf("Failed to set layers\n");
return false;
}
}
return true;
}
bool apply_config() {
fuchsia_hardware_display_ControllerCheckConfigRequest check_msg;
uint8_t check_resp_bytes[ZX_CHANNEL_MAX_MSG_BYTES];
check_msg.discard = false;
check_msg.hdr.ordinal = fuchsia_hardware_display_ControllerCheckConfigOrdinal;
zx_channel_call_args_t check_call = {};
check_call.wr_bytes = &check_msg;
check_call.rd_bytes = check_resp_bytes;
check_call.wr_num_bytes = sizeof(check_msg);
check_call.rd_num_bytes = sizeof(check_resp_bytes);
uint32_t actual_bytes, actual_handles;
zx_status_t status;
if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &check_call,
&actual_bytes, &actual_handles)) != ZX_OK) {
printf("Failed to make check call: %d (%s)\n", status, zx_status_get_string(status));
return false;
}
fidl::Message msg(fidl::BytePart(check_resp_bytes, ZX_CHANNEL_MAX_MSG_BYTES, actual_bytes),
fidl::HandlePart());
const char* err_msg;
if (msg.Decode(&fuchsia_hardware_display_ControllerCheckConfigResponseTable, &err_msg) !=
ZX_OK) {
return false;
}
auto check_rsp = reinterpret_cast<fuchsia_hardware_display_ControllerCheckConfigResponse*>(
msg.bytes().data());
if (check_rsp->res != fuchsia_hardware_display_ConfigResult_OK) {
printf("Config not valid (%d)\n", check_rsp->res);
auto* arr = static_cast<fuchsia_hardware_display_ClientCompositionOp*>(check_rsp->ops.data);
for (unsigned i = 0; i < check_rsp->ops.count; i++) {
printf("Client composition op (display %ld, layer %ld): %d\n",
arr[i].display_id, arr[i].layer_id, arr[i].opcode);
}
return false;
}
fuchsia_hardware_display_ControllerApplyConfigRequest apply_msg;
apply_msg.hdr.ordinal = fuchsia_hardware_display_ControllerApplyConfigOrdinal;
if (zx_channel_write(dc_handle, 0, &apply_msg, sizeof(apply_msg), nullptr, 0) != ZX_OK) {
printf("Apply failed\n");
return false;
}
return true;
}
zx_status_t wait_for_vsync(const fbl::Vector<fbl::unique_ptr<VirtualLayer>>& layers) {
zx_time_t deadline = has_ownership
? zx_clock_get_monotonic() + ZX_MSEC(100) : ZX_TIME_INFINITE;
if (!wait_for_driver_event(deadline)) {
return ZX_ERR_STOP;
}
uint8_t byte_buffer[ZX_CHANNEL_MAX_MSG_BYTES];
fidl::Message msg(fidl::BytePart(byte_buffer, ZX_CHANNEL_MAX_MSG_BYTES), fidl::HandlePart());
if (msg.Read(dc_handle, 0) != ZX_OK) {
printf("Read failed\n");
return ZX_ERR_STOP;
}
switch (msg.ordinal()) {
case fuchsia_hardware_display_ControllerDisplaysChangedOrdinal:
printf("Display disconnected\n");
return ZX_ERR_STOP;
case fuchsia_hardware_display_ControllerClientOwnershipChangeOrdinal:
printf("Ownership change\n");
has_ownership =
((fuchsia_hardware_display_ControllerClientOwnershipChangeEvent*)msg.bytes().data())
->has_ownership;
return ZX_ERR_NEXT;
case fuchsia_hardware_display_ControllerVsyncOrdinal:
break;
default:
printf("Unknown ordinal %d\n", msg.ordinal());
return ZX_ERR_STOP;
}
const char* err_msg;
if (msg.Decode(&fuchsia_hardware_display_ControllerVsyncEventTable, &err_msg) != ZX_OK) {
printf("Fidl decode error %s\n", err_msg);
return ZX_ERR_STOP;
}
auto vsync =
reinterpret_cast<fuchsia_hardware_display_ControllerVsyncEvent*>(msg.bytes().data());
uint64_t* image_ids = reinterpret_cast<uint64_t*>(vsync->images.data);
for (auto& layer : layers) {
uint64_t id = layer->image_id(vsync->display_id);
if (id == 0) {
continue;
}
for (unsigned i = 0; i < vsync->images.count; i++) {
if (image_ids[i] == layer->image_id(vsync->display_id)) {
layer->set_frame_done(vsync->display_id);
}
}
}
for (auto& layer : layers) {
if (!layer->is_done()) {
return ZX_ERR_NEXT;
}
}
return ZX_OK;
}
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<fbl::unique_ptr<VirtualLayer>> layers;
int32_t num_frames = 120; // default to 120 frames
enum Platform {
SIMPLE,
INTEL,
ARM_MEDIATEK,
ARM_AMLOGIC,
};
Platform platform = INTEL; // default to Intel
if (!bind_display(&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], "--mediatek") == 0) {
platform = ARM_MEDIATEK;
argv += 1;
argc -= 1;
} else if (strcmp(argv[0], "--amlogic") == 0) {
platform = ARM_AMLOGIC;
argv += 1;
argc -= 1;
} else if (strcmp(argv[0], "--simple") == 0) {
platform = SIMPLE;
argv += 1;
argc -= 1;
} else {
printf("Unrecognized argument \"%s\"\n", argv[0]);
return -1;
}
}
fbl::AllocChecker ac;
if (platform == 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
fbl::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.
fbl::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.
fbl::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.
fbl::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 (platform == ARM_MEDIATEK) {
// Mediatek display test
uint32_t width = displays[0].mode().horizontal_resolution;
uint32_t height = displays[0].mode().vertical_resolution;
fbl::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;
fbl::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;
fbl::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));
fbl::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 (platform == ARM_AMLOGIC) {
// Amlogic display test
fbl::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 (platform == SIMPLE) {
// Simple display test
fbl::unique_ptr<PrimaryLayer> layer1 = fbl::make_unique_checked<PrimaryLayer>(&ac,
displays);
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_handle)) {
printf("Layer init failed\n");
return -1;
}
}
for (auto& display : displays) {
display.Init(dc_handle);
}
printf("Starting rendering\n");
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_handle);
}
for (unsigned i = 0; i < displays.size(); i++) {
if (!update_display_layers(layers, displays[i], &display_layers[i])) {
return -1;
}
}
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) {
// wait again
}
ZX_ASSERT(status == ZX_OK);
}
printf("Done rendering\n");
zx_nanosleep(zx_deadline_after(ZX_MSEC(500)));
zx_handle_close(dc_handle);
zx_handle_close(device_handle);
return 0;
}