[display-test] Add tests for layers
Test: self
Change-Id: I11224c7ce7ada9913dfb7b28184215d66ebb3ba7
diff --git a/system/uapp/display-test/display.cpp b/system/uapp/display-test/display.cpp
new file mode 100644
index 0000000..0663fd5
--- /dev/null
+++ b/system/uapp/display-test/display.cpp
@@ -0,0 +1,36 @@
+// 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 "display.h"
+
+Display::Display(fuchsia_display_Info* info) {
+ id_ = info->id;
+
+ auto pixel_format = reinterpret_cast<int32_t*>(info->pixel_format.data);
+ for (unsigned i = 0; i < info->pixel_format.count; i++) {
+ pixel_formats_.push_back(pixel_format[i]);
+ }
+
+ auto mode = reinterpret_cast<fuchsia_display_Mode*>(info->modes.data);
+ for (unsigned i = 0; i < info->modes.count; i++) {
+ modes_.push_back(mode[i]);
+ }
+}
+
+void Display::Dump() {
+ printf("Display id = %ld\n", id_);
+
+ printf("\tSupported pixel formats:\n");
+ for (unsigned i = 0; i < pixel_formats_.size(); i++) {
+ printf("\t\t%d\t: %08x\n", i, pixel_formats_[i]);
+ }
+
+ printf("\n\tSupported display modes:\n");
+ for (unsigned i = 0; i < modes_.size(); i++) {
+ printf("\t\t%d\t: %dx%d\t%d.%02d\n", i,
+ modes_[i].horizontal_resolution, modes_[i].vertical_resolution,
+ modes_[i].refresh_rate_e2 / 100, modes_[i].refresh_rate_e2 % 100);
+ }
+ printf("\n");
+}
diff --git a/system/uapp/display-test/display.h b/system/uapp/display-test/display.h
new file mode 100644
index 0000000..451d98b
--- /dev/null
+++ b/system/uapp/display-test/display.h
@@ -0,0 +1,39 @@
+// 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.
+
+#pragma once
+
+#include <fbl/vector.h>
+#include <zircon/pixelformat.h>
+
+#include "fuchsia/display/c/fidl.h"
+
+class Display {
+public:
+ Display(fuchsia_display_Info* info);
+
+ zx_pixel_format_t format() const { return pixel_formats_[format_idx_]; }
+ fuchsia_display_Mode mode() const { return modes_[mode_idx_]; }
+ uint64_t id() const { return id_; }
+
+ bool set_format_idx(uint32_t idx) {
+ format_idx_ = idx;
+ return format_idx_ < pixel_formats_.size();
+ }
+
+ bool set_mode_idx(uint32_t idx) {
+ mode_idx_ = idx;
+ return mode_idx_ < modes_.size();
+ }
+
+ void Dump();
+
+private:
+ uint32_t format_idx_ = 0;
+ uint32_t mode_idx_ = 0;
+
+ uint64_t id_;
+ fbl::Vector<zx_pixel_format_t> pixel_formats_;
+ fbl::Vector<fuchsia_display_Mode> modes_;
+};
diff --git a/system/uapp/display-test/image.cpp b/system/uapp/display-test/image.cpp
new file mode 100644
index 0000000..5a105ea
--- /dev/null
+++ b/system/uapp/display-test/image.cpp
@@ -0,0 +1,186 @@
+// 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 <fbl/algorithm.h>
+#include <lib/zx/event.h>
+#include <lib/zx/vmar.h>
+#include <lib/zx/vmo.h>
+#include <stdio.h>
+#include <string.h>
+#include <zircon/device/display-controller.h>
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+
+#include "fuchsia/display/c/fidl.h"
+#include "image.h"
+#include "utils.h"
+
+static constexpr uint32_t kRenderPeriod = 120;
+
+Image::Image(uint32_t width, uint32_t height, int32_t stride,
+ zx_pixel_format_t format, zx_handle_t vmo, void* buf, uint32_t fg_color)
+ : width_(width), height_(height), stride_(stride), format_(format),
+ vmo_(vmo), buf_(buf), fg_color_(fg_color) {}
+
+Image* Image::Create(zx_handle_t dc_handle,
+ uint32_t width, uint32_t height, zx_pixel_format_t format, uint32_t fg_color) {
+ fuchsia_display_ControllerComputeLinearImageStrideRequest stride_msg;
+ stride_msg.hdr.ordinal = fuchsia_display_ControllerComputeLinearImageStrideOrdinal;
+ stride_msg.width = width;
+ stride_msg.pixel_format = format;
+
+ fuchsia_display_ControllerComputeLinearImageStrideResponse stride_rsp;
+ zx_channel_call_args_t stride_call = {};
+ stride_call.wr_bytes = &stride_msg;
+ stride_call.rd_bytes = &stride_rsp;
+ stride_call.wr_num_bytes = sizeof(stride_msg);
+ stride_call.rd_num_bytes = sizeof(stride_rsp);
+ uint32_t actual_bytes, actual_handles;
+ zx_status_t read_status;
+ if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE,
+ &stride_call, &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
+ printf("Failed to make stride call\n");
+ return nullptr;
+ }
+
+ if (stride_rsp.stride < width) {
+ printf("Invalid stride\n");
+ return nullptr;
+ }
+
+ zx::vmo vmo;
+ fuchsia_display_ControllerAllocateVmoRequest alloc_msg;
+ alloc_msg.hdr.ordinal = fuchsia_display_ControllerAllocateVmoOrdinal;
+ alloc_msg.size = stride_rsp.stride * height * ZX_PIXEL_FORMAT_BYTES(format);
+
+ fuchsia_display_ControllerAllocateVmoResponse alloc_rsp;
+ zx_channel_call_args_t call_args = {};
+ call_args.wr_bytes = &alloc_msg;
+ call_args.rd_bytes = &alloc_rsp;
+ call_args.rd_handles = vmo.reset_and_get_address();
+ call_args.wr_num_bytes = sizeof(alloc_msg);
+ call_args.rd_num_bytes = sizeof(alloc_rsp);
+ call_args.rd_num_handles = 1;
+ if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args,
+ &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
+ printf("Vmo alloc call failed\n");
+ return nullptr;
+ }
+ if (alloc_rsp.res != ZX_OK) {
+ printf("Failed to alloc vmo %d\n", alloc_rsp.res);
+ return nullptr;
+ }
+
+ uintptr_t addr;
+ uint32_t len = stride_rsp.stride * height * ZX_PIXEL_FORMAT_BYTES(format);
+ uint32_t perms = ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE;
+ if (zx::vmar::root_self().map(0, vmo, 0, len, perms, &addr) != ZX_OK) {
+ printf("Failed to map vmar\n");
+ return nullptr;
+ }
+
+ void* ptr = reinterpret_cast<void*>(addr);
+ memset(ptr, 0xff, len);
+ zx_cache_flush(ptr, len, ZX_CACHE_FLUSH_DATA);
+
+ return new Image(width, height, stride_rsp.stride, format, vmo.release(), ptr, fg_color);
+}
+
+#define STRIPE_SIZE 37 // prime to make movement more interesting
+
+void Image::Render(int32_t prev_step, int32_t step_num) {
+ uint32_t start, end;
+ bool draw_stripe;
+ if (step_num < 0) {
+ start = 0;
+ end = height_;
+ draw_stripe = true;
+ } else {
+ uint32_t prev = interpolate(height_, prev_step, kRenderPeriod);
+ uint32_t cur = interpolate(height_, step_num, kRenderPeriod);
+ start = fbl::min(cur, prev);
+ end = fbl::max(cur, prev);
+ draw_stripe = cur > prev;
+ }
+
+ for (unsigned y = start; y < end; y++) {
+ for (unsigned x = 0; x < width_; x++) {
+ int32_t in_stripe = draw_stripe && ((x / STRIPE_SIZE % 2) != (y / STRIPE_SIZE % 2));
+ int32_t color = in_stripe ? fg_color_: 0xffffffff;
+ *(static_cast<int32_t*>(buf_) + (y * stride_) + x) = color;
+ }
+ }
+ uint32_t byte_stride = stride_ * ZX_PIXEL_FORMAT_BYTES(format_);
+ zx_cache_flush(reinterpret_cast<uint8_t*>(buf_) + (byte_stride * start),
+ byte_stride * (end - start), ZX_CACHE_FLUSH_DATA);
+}
+
+bool Image::Import(zx_handle_t dc_handle, image_import_t* info_out) {
+ for (int i = 0; i < 3; i++) {
+ static int event_id = INVALID_ID + 1;
+ zx_handle_t e1, e2;
+ if (zx_event_create(0, &e1) != ZX_OK
+ || zx_handle_duplicate(e1, ZX_RIGHT_SAME_RIGHTS, &e2) != ZX_OK) {
+ printf("Failed to create event\n");
+ return false;
+ }
+
+ fuchsia_display_ControllerImportEventRequest import_evt_msg;
+ import_evt_msg.hdr.ordinal = fuchsia_display_ControllerImportEventOrdinal;
+ import_evt_msg.id = event_id++;
+ import_evt_msg.event = FIDL_HANDLE_PRESENT;
+
+ if (zx_channel_write(dc_handle, 0, &import_evt_msg,
+ sizeof(import_evt_msg), &e2, 1) != ZX_OK) {
+ printf("Failed to send import message\n");
+ return false;
+ }
+
+ if (i != WAIT_EVENT) {
+ zx_object_signal(e1, 0, ZX_EVENT_SIGNALED);
+ }
+
+ info_out->events[i] = e1;
+ info_out->event_ids[i] = import_evt_msg.id;
+ }
+
+ fuchsia_display_ControllerImportVmoImageRequest import_msg;
+ import_msg.hdr.ordinal = fuchsia_display_ControllerImportVmoImageOrdinal;
+ import_msg.image_config.height = height_;
+ import_msg.image_config.width = width_;
+ import_msg.image_config.pixel_format = format_;
+ import_msg.image_config.type = IMAGE_TYPE_SIMPLE;
+ import_msg.vmo = FIDL_HANDLE_PRESENT;
+ import_msg.offset = 0;
+ zx_handle_t vmo_dup;
+ if (zx_handle_duplicate(vmo_, ZX_RIGHT_SAME_RIGHTS, &vmo_dup) != ZX_OK) {
+ printf("Failed to dup handle\n");
+ return false;
+ }
+
+ fuchsia_display_ControllerImportVmoImageResponse import_rsp;
+ zx_channel_call_args_t import_call = {};
+ import_call.wr_bytes = &import_msg;
+ import_call.wr_handles = &vmo_dup;
+ import_call.rd_bytes = &import_rsp;
+ import_call.wr_num_bytes = sizeof(import_msg);
+ import_call.wr_num_handles = 1;
+ import_call.rd_num_bytes = sizeof(import_rsp);
+ uint32_t actual_bytes, actual_handles;
+ zx_status_t read_status;
+ if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &import_call,
+ &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
+ printf("Failed to make import call\n");
+ return false;
+ }
+
+ if (import_rsp.res != ZX_OK) {
+ printf("Failed to import vmo\n");
+ return false;
+ }
+
+ info_out->id = import_rsp.image_id;
+
+ return true;
+}
diff --git a/system/uapp/display-test/image.h b/system/uapp/display-test/image.h
new file mode 100644
index 0000000..0f51aeb
--- /dev/null
+++ b/system/uapp/display-test/image.h
@@ -0,0 +1,54 @@
+// 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.
+
+#pragma once
+
+#include <fbl/unique_ptr.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/event.h>
+#include <zircon/types.h>
+#include <zircon/pixelformat.h>
+
+// Indicies into event and event_ids
+#define WAIT_EVENT 0
+#define PRESENT_EVENT 1
+#define SIGNAL_EVENT 2
+
+typedef struct image_import {
+ uint64_t id;
+ zx_handle_t events[3];
+ uint64_t event_ids[3];
+} image_import_t;
+
+class Image {
+public:
+ static Image* Create(zx_handle_t dc_handle,
+ uint32_t width, uint32_t height, zx_pixel_format_t format,
+ uint32_t fg_color);
+
+ void Render(int32_t prev_step, int32_t step_num);
+
+ void* buffer() { return buf_; }
+ uint32_t width() { return width_; }
+ uint32_t height() { return height_; }
+ uint32_t stride() { return stride_; }
+ zx_pixel_format_t format() { return format_; }
+
+ bool Import(zx_handle_t dc_handle, image_import_t* import_out);
+
+private:
+ Image(uint32_t width, uint32_t height, int32_t stride,
+ zx_pixel_format_t format, zx_handle_t handle, void* buf,
+ uint32_t fg_color_);
+
+ uint32_t width_;
+ uint32_t height_;
+ uint32_t stride_;
+ zx_pixel_format_t format_;
+
+ zx_handle_t vmo_;
+ void* buf_;
+
+ uint32_t fg_color_;
+};
diff --git a/system/uapp/display-test/main.cpp b/system/uapp/display-test/main.cpp
index 94c06c0..6aa15d7 100644
--- a/system/uapp/display-test/main.cpp
+++ b/system/uapp/display-test/main.cpp
@@ -14,6 +14,8 @@
#include <zircon/syscalls.h>
#include <zircon/types.h>
+#include <fbl/vector.h>
+#include <fbl/algorithm.h>
#include <lib/fidl/cpp/message.h>
#include <lib/fidl/cpp/string_view.h>
#include <lib/fidl/cpp/vector_view.h>
@@ -21,35 +23,13 @@
#include <zircon/pixelformat.h>
#include <zircon/syscalls.h>
+#include "display.h"
#include "fuchsia/display/c/fidl.h"
+#include "virtual-layer.h"
-#ifdef DEBUG
-#define dprintf(...) printf(__VA_ARGS__)
-#else
-#define dprintf(...)
-#endif
-
-static int64_t display_id;
static zx_handle_t dc_handle;
-static int32_t width;
-static int32_t height;
-static int32_t stride;
-static zx_pixel_format_t format;
-
-typedef struct image {
- int64_t id;
- void* buf;
- zx_handle_t events[3];
- int64_t event_ids[3];
-} image_t;
-
-// Indicies into image_t.event and image_t.event_ids
-#define WAIT_EVENT 0
-#define PRESENT_EVENT 1
-#define SIGNAL_EVENT 2
-
-static bool bind_display() {
+static bool bind_display(fbl::Vector<Display>* displays) {
printf("Opening controller\n");
int vfd = open("/dev/class/display-controller/000", O_RDWR);
if (vfd < 0) {
@@ -57,7 +37,6 @@
return false;
}
- printf("Getting handle\n");
if (ioctl_display_controller_get_handle(vfd, &dc_handle) != sizeof(zx_handle_t)) {
printf("Failed to get display controller handle\n");
return false;
@@ -91,241 +70,243 @@
auto changes = reinterpret_cast<fuchsia_display_ControllerDisplaysChangedEvent*>(
msg.bytes().data());
- auto display = reinterpret_cast<fuchsia_display_Info*>(changes->added.data);
- auto mode = reinterpret_cast<fuchsia_display_Mode*>(display->modes.data);
- auto pixel_format = reinterpret_cast<int32_t*>(display->pixel_format.data)[0];
+ auto display_info = reinterpret_cast<fuchsia_display_Info*>(changes->added.data);
- printf("Getting stride\n");
- fuchsia_display_ControllerComputeLinearImageStrideRequest stride_msg;
- stride_msg.hdr.ordinal = fuchsia_display_ControllerComputeLinearImageStrideOrdinal;
- stride_msg.width = mode->horizontal_resolution;
- stride_msg.pixel_format = pixel_format;
-
- fuchsia_display_ControllerComputeLinearImageStrideResponse stride_rsp;
- zx_channel_call_args_t stride_call = {};
- stride_call.wr_bytes = &stride_msg;
- stride_call.rd_bytes = &stride_rsp;
- stride_call.wr_num_bytes = sizeof(stride_msg);
- stride_call.rd_num_bytes = sizeof(stride_rsp);
- uint32_t actual_bytes, actual_handles;
- zx_status_t read_status;
- if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE,
- &stride_call, &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
- printf("Failed to make stride call\n");
- return false;
+ for (unsigned i = 0; i < changes->added.count; i++) {
+ displays->push_back(Display(display_info + i));
}
- display_id = display->id;
-
- width = mode->horizontal_resolution;
- height = mode->vertical_resolution;
- stride = stride_rsp.stride;
- format = pixel_format;
-
- printf("Bound to display %dx%d (stride = %d, format=%d)\n", width, height, stride, format);
-
return true;
}
-static bool create_image(image_t* img) {
- printf("Creating image\n");
- for (int i = 0; i < 3; i++) {
- printf("Creating event %d\n", i);
-
- zx_handle_t e1, e2;
- zx_info_handle_basic_t info;
- if (zx_event_create(0, &e1) != ZX_OK
- || zx_handle_duplicate(e1, ZX_RIGHT_SAME_RIGHTS, &e2) != ZX_OK
- || zx_object_get_info(e1, ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
- nullptr, nullptr) != ZX_OK) {
- printf("Failed to create event\n");
- return false;
+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;
+}
- fuchsia_display_ControllerImportEventRequest import_evt_msg;
- import_evt_msg.hdr.ordinal = fuchsia_display_ControllerImportEventOrdinal;
- import_evt_msg.id = info.koid;
- import_evt_msg.event = FIDL_HANDLE_PRESENT;
+bool update_display_layers(const fbl::Vector<VirtualLayer>& layers,
+ const Display& display, fbl::Vector<uint64_t>* current_layers) {
+ fbl::Vector<uint64_t> new_layers;
- if (zx_channel_write(dc_handle, 0, &import_evt_msg,
- sizeof(import_evt_msg), &e2, 1) != ZX_OK) {
- printf("Failed to send import message\n");
- return false;
+ for (auto& layer : layers) {
+ uint64_t id = layer.id(display.id());
+ if (id != INVALID_ID) {
+ new_layers.push_back(id);
}
-
- img->events[i] = e1;
- img->event_ids[i] = import_evt_msg.id;
}
- printf("Creating and mapping vmo\n");
- zx_handle_t vmo;
- fuchsia_display_ControllerAllocateVmoRequest alloc_msg;
- alloc_msg.hdr.ordinal = fuchsia_display_ControllerAllocateVmoOrdinal;
- alloc_msg.size = stride * height * ZX_PIXEL_FORMAT_BYTES(format);
+ 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;
+ }
+ }
+ }
- fuchsia_display_ControllerAllocateVmoResponse alloc_rsp;
- zx_channel_call_args_t call_args = {};
- call_args.wr_bytes = &alloc_msg;
- call_args.rd_bytes = &alloc_rsp;
- call_args.rd_handles = &vmo;
- call_args.wr_num_bytes = sizeof(alloc_msg);
- call_args.rd_num_bytes = sizeof(alloc_rsp);
- call_args.rd_num_handles = 1;
+ if (layer_change) {
+ current_layers->swap(new_layers);
+
+ uint32_t size = static_cast<int32_t>(
+ sizeof(fuchsia_display_ControllerSetDisplayLayersRequest) +
+ FIDL_ALIGN(sizeof(uint64_t) * current_layers->size()));
+ uint8_t fidl_bytes[size];
+
+ auto set_layers_msg =
+ reinterpret_cast<fuchsia_display_ControllerSetDisplayLayersRequest*>(fidl_bytes);
+ set_layers_msg->hdr.ordinal = fuchsia_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_display_ControllerCheckConfigRequest check_msg;
+ uint8_t check_resp_bytes[ZX_CHANNEL_MAX_MSG_BYTES];
+ check_msg.discard = false;
+ check_msg.hdr.ordinal = fuchsia_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 read_status;
- zx_status_t status;
- if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args,
+ zx_status_t read_status, status;
+ if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &check_call,
&actual_bytes, &actual_handles, &read_status)) != ZX_OK) {
- printf("Vmo alloc call failed %d %d\n", status, read_status);
- return false;
- }
- if (alloc_rsp.res != ZX_OK) {
- printf("Failed to alloc vmo %d\n", alloc_rsp.res);
+ printf("Failed to make check call %d %d\n", status, read_status);
return false;
}
- uintptr_t addr;
- uint32_t len = stride * height * ZX_PIXEL_FORMAT_BYTES(format);
- uint32_t perms = ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE;
- if (zx_vmar_map(zx_vmar_root_self(), 0, vmo, 0, len, perms, &addr) != ZX_OK) {
- printf("Failed to map vmar\n");
+ 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_display_ControllerCheckConfigResponseTable, &err_msg) != ZX_OK) {
+ return false;
+ }
+ auto check_rsp =
+ reinterpret_cast<fuchsia_display_ControllerCheckConfigResponse*>(msg.bytes().data());
+
+ if (check_rsp->res.count) {
+ printf("Config not valid\n");
return false;
}
- printf("Importing image\n");
- fuchsia_display_ControllerImportVmoImageRequest import_msg;
- import_msg.hdr.ordinal = fuchsia_display_ControllerImportVmoImageOrdinal;
- import_msg.image_config.height = height;
- import_msg.image_config.width = width;
- import_msg.image_config.pixel_format = format;
- import_msg.image_config.type = IMAGE_TYPE_SIMPLE;
- import_msg.vmo = FIDL_HANDLE_PRESENT;
- import_msg.offset = 0;
- zx_handle_t vmo_dup;
- if (zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, &vmo_dup) != ZX_OK) {
- printf("Failed to dup handle\n");
+ fuchsia_display_ControllerApplyConfigRequest apply_msg;
+ apply_msg.hdr.ordinal = fuchsia_display_ControllerApplyConfigOrdinal;
+ if (zx_channel_write(dc_handle, 0, &apply_msg, sizeof(apply_msg), nullptr, 0) != ZX_OK) {
+ printf("Apply failed\n");
return false;
}
-
- fuchsia_display_ControllerImportVmoImageResponse import_rsp;
- zx_channel_call_args_t import_call = {};
- import_call.wr_bytes = &import_msg;
- import_call.wr_handles = &vmo_dup;
- import_call.rd_bytes = &import_rsp;
- import_call.wr_num_bytes = sizeof(import_msg);
- import_call.wr_num_handles = 1;
- import_call.rd_num_bytes = sizeof(import_rsp);
- if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &import_call,
- &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
- printf("Failed to make import call\n");
- return false;
- }
-
- if (import_rsp.res != ZX_OK) {
- printf("Failed to import vmo\n");
- return false;
- }
-
- img->id = import_rsp.image_id;
- img->buf = reinterpret_cast<void*>(addr);
-
- printf("Created display\n");
-
return true;
}
-#define NUM_FRAMES 120
-
-int main(int argc, char* argv[]) {
+int main(int argc, const char* argv[]) {
printf("Running display test\n");
- if (!bind_display()) {
+ fbl::Vector<Display> displays;
+ fbl::Vector<fbl::Vector<uint64_t>> display_layers;
+ fbl::Vector<VirtualLayer> layers;
+ int32_t num_frames = 120; // default to 120 frames
+
+ if (!bind_display(&displays)) {
return -1;
}
- image_t img1, img2;
-
- if (!create_image(&img1) || !create_image(&img2)) {
- return -1;
+ if (displays.is_empty()) {
+ printf("No displays available\n");
+ return 0;
}
- for (int i = 0; i < NUM_FRAMES; i++) {
- printf("Rendering frame %d\n", i);
- image_t* img = i % 2 == 0 ? &img1 : &img2;
+ for (unsigned i = 0; i < displays.size(); i++) {
+ display_layers.push_back(fbl::Vector<uint64_t>());
+ }
- // No signals the first iteration
- if (i / 2 >= 1) {
- zx_signals_t observed;
- if (zx_object_wait_one(img->events[SIGNAL_EVENT], ZX_EVENT_SIGNALED,
- zx_deadline_after(ZX_SEC(1)), &observed) != ZX_OK) {
- dprintf("Buffer failed to become free\n");
+ 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], "--num-frames") == 0) {
+ num_frames = atoi(argv[1]);
+ argv += 2;
+ argc -= 2;
+ } else {
+ printf("Unrecognized argument \"%s\"\n", argv[0]);
+ return -1;
+ }
+ }
+
+ // Layer which covers all displays and uses page flipping.
+ VirtualLayer layer1(displays);
+ layer1.SetLayerFlipping(true);
+ layers.push_back(fbl::move(layer1));
+
+ // Layer which covers the left half of the of the first display
+ // and toggles on and off every frame.
+ VirtualLayer layer2(&displays[0]);
+ layer2.SetImageDimens(displays[0].mode().horizontal_resolution / 2,
+ displays[0].mode().vertical_resolution);
+ layer2.SetLayerToggle(true);
+ layers.push_back(fbl::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.
+ VirtualLayer layer3(displays);
+ layer3.SetImageDimens(displays[0].mode().horizontal_resolution,
+ displays[0].mode().vertical_resolution / 2);
+ layer3.SetDestFrame(displays[0].mode().horizontal_resolution / 2,
+ displays[0].mode().vertical_resolution / 2);
+ layer3.SetSrcFrame(displays[0].mode().horizontal_resolution / 2,
+ displays[0].mode().vertical_resolution / 2);
+ layer3.SetPanDest(true);
+ layer3.SetPanSrc(true);
+ layers.push_back(fbl::move(layer3));
+
+ printf("Initializing layers\n");
+ for (auto& layer : layers) {
+ if (!layer.Init(dc_handle)) {
+ printf("Layer init failed\n");
+ return -1;
+ }
+ }
+
+ 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.SendLayout(dc_handle);
+ }
+
+ for (unsigned i = 0; i < displays.size(); i++) {
+ if (!update_display_layers(layers, displays[i], &display_layers[i])) {
return -1;
}
}
- zx_object_signal(img->events[SIGNAL_EVENT], ZX_EVENT_SIGNALED, 0);
- zx_object_signal(img->events[PRESENT_EVENT], ZX_EVENT_SIGNALED, 0);
-
- fuchsia_display_ControllerSetDisplayImageRequest set_msg;
- set_msg.hdr.ordinal = fuchsia_display_ControllerSetDisplayImageOrdinal;
- set_msg.display = display_id;
- set_msg.image_id = img->id;
- set_msg.wait_event_id = img->event_ids[WAIT_EVENT];
- set_msg.present_event_id = img->event_ids[PRESENT_EVENT];
- set_msg.signal_event_id = img->event_ids[SIGNAL_EVENT];
- if (zx_channel_write(dc_handle, 0, &set_msg, sizeof(set_msg), nullptr, 0) != ZX_OK) {
- dprintf("Failed to set image\n");
+ if (!apply_config()) {
return -1;
}
- fuchsia_display_ControllerCheckConfigRequest check_msg;
- fuchsia_display_ControllerCheckConfigResponse check_rsp;
- check_msg.discard = false;
- check_msg.hdr.ordinal = fuchsia_display_ControllerCheckConfigOrdinal;
- zx_channel_call_args_t check_call = {};
- check_call.wr_bytes = &check_msg;
- check_call.rd_bytes = &check_rsp;
- check_call.wr_num_bytes = sizeof(check_msg);
- check_call.rd_num_bytes = sizeof(check_rsp);
- uint32_t actual_bytes, actual_handles;
- zx_status_t read_status;
- if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &check_call,
- &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
- dprintf("Failed to make check call\n");
- return -1;
+ for (auto& layer : layers) {
+ layer.Render(i);
}
- if (check_rsp.res.count != 0) {
- dprintf("Config not valid\n");
- return -1;
- }
-
- fuchsia_display_ControllerApplyConfigRequest apply_msg;
- apply_msg.hdr.ordinal = fuchsia_display_ControllerApplyConfigOrdinal;
- if (zx_channel_write(dc_handle, 0, &apply_msg, sizeof(apply_msg), nullptr, 0) != ZX_OK) {
- dprintf("Apply failed\n");
- return -1;
- }
-
- for (int y = 0; y < height; y++) {
- int32_t color =
- y < ((((double) height) / NUM_FRAMES) * (i + 1)) ? 0xffff0000 : 0xff00ff00;
- for (int x = 0; x < width; x++) {
- *(static_cast<int32_t*>(img->buf) + (y * stride) + x) = color;
- }
- }
- zx_cache_flush(img->buf,
- stride * height * ZX_PIXEL_FORMAT_BYTES(format), ZX_CACHE_FLUSH_DATA);
-
- dprintf("Signaling wait sem\n");
- zx_object_signal(img->events[WAIT_EVENT], 0, ZX_EVENT_SIGNALED);
-
- dprintf("Waiting on present sem %d\n", img->event_ids[PRESENT_EVENT]);
- zx_signals_t observed;
- if (zx_object_wait_one(img->events[PRESENT_EVENT], ZX_EVENT_SIGNALED,
- zx_deadline_after(ZX_SEC(1)), &observed) != ZX_OK) {
- dprintf("Buffer failed to become visible\n");
- return -1;
+ for (auto& layer : layers) {
+ ZX_ASSERT(layer.WaitForPresent());
}
}
diff --git a/system/uapp/display-test/rules.mk b/system/uapp/display-test/rules.mk
index 2df0984..2be31c5 100644
--- a/system/uapp/display-test/rules.mk
+++ b/system/uapp/display-test/rules.mk
@@ -10,9 +10,16 @@
MODULE_GROUP := misc
MODULE_SRCS += \
- $(LOCAL_DIR)/main.cpp
+ $(LOCAL_DIR)/display.cpp \
+ $(LOCAL_DIR)/image.cpp \
+ $(LOCAL_DIR)/main.cpp \
+ $(LOCAL_DIR)/virtual-layer.cpp \
-MODULE_STATIC_LIBS := system/ulib/fidl
+MODULE_STATIC_LIBS := \
+ system/ulib/fbl \
+ system/ulib/fidl \
+ system/ulib/zx \
+ system/ulib/zxcpp \
MODULE_FIDL_LIBS := system/fidl/display
diff --git a/system/uapp/display-test/utils.h b/system/uapp/display-test/utils.h
new file mode 100644
index 0000000..994236a
--- /dev/null
+++ b/system/uapp/display-test/utils.h
@@ -0,0 +1,14 @@
+// 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.
+
+#pragma once
+
+#include <inttypes.h>
+
+static inline uint32_t interpolate(uint32_t max, int32_t cur_frame, int32_t period) {
+ float fraction = ((float) (cur_frame % period)) / ((float) period - 1);
+ fraction = (cur_frame / period) % 2 ? 1.0f - fraction : fraction;
+ return (uint32_t) ((float) max * fraction);
+}
+
diff --git a/system/uapp/display-test/virtual-layer.cpp b/system/uapp/display-test/virtual-layer.cpp
new file mode 100644
index 0000000..fa9a941
--- /dev/null
+++ b/system/uapp/display-test/virtual-layer.cpp
@@ -0,0 +1,263 @@
+// 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 <fbl/algorithm.h>
+#include <zircon/device/display-controller.h>
+
+#include "utils.h"
+#include "virtual-layer.h"
+
+static constexpr uint32_t kSrcFrameBouncePeriod = 90;
+static constexpr uint32_t kDestFrameBouncePeriod = 60;
+
+static uint32_t colors[] = {
+ 0xffff0000, 0xff00ff00, 0xff0000ff,
+};
+
+// Checks if two rectangles intersect, and if so, returns their intersection.
+static bool compute_intersection(const frame_t& a, const frame_t& b, frame_t* intersection) {
+ uint32_t left = fbl::max(a.x_pos, b.x_pos);
+ uint32_t right = fbl::min(a.x_pos + a.width, b.x_pos + b.width);
+ uint32_t top = fbl::max(a.y_pos, b.y_pos);
+ uint32_t bottom = fbl::min(a.y_pos + a.height, b.y_pos + b.height);
+
+ if (left >= right || top >= bottom) {
+ return false;
+ }
+
+ intersection->x_pos = left;
+ intersection->y_pos = top;
+ intersection->width = right - left;
+ intersection->height = bottom - top;
+
+ return true;
+}
+
+VirtualLayer::VirtualLayer(Display* display) {
+ displays_.push_back(display);
+ width_ = display->mode().horizontal_resolution;
+ height_ = display->mode().vertical_resolution;
+ image_format_ = display->format();
+ SetImageDimens(width_, height_);
+}
+
+VirtualLayer::VirtualLayer(const fbl::Vector<Display>& displays) {
+ for (auto& d : displays) {
+ displays_.push_back(&d);
+ }
+
+ width_ = 0;
+ height_ = 0;
+ for (auto* d : displays_) {
+ width_ += d->mode().horizontal_resolution;
+ height_ = fbl::max(height_, d->mode().vertical_resolution);
+ }
+ image_format_ = displays_[0]->format();
+ SetImageDimens(width_, height_);
+}
+
+bool VirtualLayer::Init(zx_handle_t dc_handle) {
+ fuchsia_display_ControllerCreateLayerRequest create_layer_msg;
+ create_layer_msg.hdr.ordinal = fuchsia_display_ControllerCreateLayerOrdinal;
+
+ static uint32_t layer_count = 0;
+ uint32_t fg_color = colors[layer_count++ % fbl::count_of(colors)];
+
+ images_[0] = Image::Create(dc_handle, image_width_, image_height_, image_format_, fg_color);
+ if (layer_flipping_) {
+ images_[1] = Image::Create(dc_handle, image_width_, image_height_, image_format_, fg_color);
+ } else {
+ images_[0]->Render(-1, -1);
+ }
+
+ if (!images_[0] || (layer_flipping_ && !images_[1])) {
+ return false;
+ }
+
+ for (unsigned i = 0; i < displays_.size(); i++) {
+ layers_.push_back(layer_t());
+ layers_[i].active = false;
+
+ images_[0]->Import(dc_handle, &layers_[i].import_info[0]);
+ if (layer_flipping_) {
+ images_[1]->Import(dc_handle, &layers_[i].import_info[1]);
+ } else {
+ zx_object_signal(layers_[i].import_info[alt_image_].events[WAIT_EVENT],
+ 0, ZX_EVENT_SIGNALED);
+ }
+
+ fuchsia_display_ControllerCreateLayerResponse create_layer_rsp;
+ zx_channel_call_args_t call_args = {};
+ call_args.wr_bytes = &create_layer_msg;
+ call_args.rd_bytes = &create_layer_rsp;
+ call_args.wr_num_bytes = sizeof(create_layer_msg);
+ call_args.rd_num_bytes = sizeof(create_layer_rsp);
+ uint32_t actual_bytes, actual_handles;
+ zx_status_t read_status;
+ if (zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &call_args,
+ &actual_bytes, &actual_handles, &read_status) != ZX_OK) {
+ printf("Creating layer failed\n");
+ return false;
+ }
+ if (create_layer_rsp.res != ZX_OK) {
+ printf("Creating layer failed\n");
+ return false;
+ }
+ layers_[i].id = create_layer_rsp.layer_id;
+
+ fuchsia_display_ControllerSetLayerPrimaryConfigRequest config;
+ config.hdr.ordinal = fuchsia_display_ControllerSetLayerPrimaryConfigOrdinal;
+ config.layer_id = layers_[i].id;
+ config.image_config.height = image_height_;
+ config.image_config.width = image_width_;
+ config.image_config.pixel_format = image_format_;
+ config.image_config.type = IMAGE_TYPE_SIMPLE;
+
+ if (zx_channel_write(dc_handle, 0, &config, sizeof(config), nullptr, 0) != ZX_OK) {
+ printf("Setting layer config failed\n");
+ return false;
+ }
+ }
+
+ StepLayout(0);
+ if (!layer_flipping_) {
+ SetLayerImages(dc_handle);
+ }
+ if (!(pan_src_ || pan_dest_)) {
+ SetLayerPositions(dc_handle);
+ }
+
+ return true;
+}
+
+void VirtualLayer::StepLayout(int32_t frame_num) {
+ if (layer_flipping_) {
+ alt_image_ = frame_num % 2;
+ }
+ if (pan_src_) {
+ src_frame_.x_pos =
+ interpolate(image_width_ - src_frame_.width, frame_num, kSrcFrameBouncePeriod);
+ }
+ if (pan_dest_) {
+ dest_frame_.x_pos =
+ interpolate(width_ - dest_frame_.width, frame_num, kDestFrameBouncePeriod);
+ }
+
+ frame_t display = {};
+ for (unsigned i = 0; i < displays_.size(); i++) {
+ display.height = displays_[i]->mode().vertical_resolution;
+ display.width = displays_[i]->mode().horizontal_resolution;
+
+ // Calculate the portion of the dest frame which shows up on this display
+ if (compute_intersection(display, dest_frame_, &layers_[i].dest)) {
+ // Find the subset of the src region which shows up on this display
+ layers_[i].src.x_pos = src_frame_.x_pos + (layers_[i].dest.x_pos - dest_frame_.x_pos);
+ layers_[i].src.y_pos = src_frame_.y_pos;
+ layers_[i].src.width = layers_[i].dest.width;
+ layers_[i].src.height = layers_[i].dest.height;
+
+ // Put the dest frame coordinates in the display's coord space
+ layers_[i].dest.x_pos -= display.x_pos;
+ layers_[i].active = true;
+ } else {
+ layers_[i].active = false;
+ }
+
+ display.x_pos += display.width;
+ }
+
+ if (layer_toggle_) {
+ for (auto& layer : layers_) {
+ layer.active = !(frame_num % 2);
+ }
+ }
+}
+
+void VirtualLayer::SendLayout(zx_handle_t channel) {
+ if (layer_flipping_) {
+ SetLayerImages(channel);
+ }
+ if (pan_src_ || pan_dest_) {
+ SetLayerPositions(channel);
+ }
+}
+
+bool VirtualLayer::WaitForReady() {
+ return Wait(SIGNAL_EVENT);
+}
+
+bool VirtualLayer::WaitForPresent() {
+ return Wait(PRESENT_EVENT);
+}
+
+void VirtualLayer::Render(int32_t frame_num) {
+ if (!layer_flipping_) {
+ return;
+ }
+ images_[alt_image_]->Render(frame_num < 2 ? 0 : frame_num - 2, frame_num);
+ for (auto& layer : layers_) {
+ zx_object_signal(layer.import_info[alt_image_].events[WAIT_EVENT], 0, ZX_EVENT_SIGNALED);
+ }
+}
+
+void VirtualLayer::SetLayerPositions(zx_handle_t dc_handle) {
+ fuchsia_display_ControllerSetLayerPrimaryPositionRequest msg;
+ msg.hdr.ordinal = fuchsia_display_ControllerSetLayerPrimaryPositionOrdinal;
+
+ for (auto& layer : layers_) {
+ msg.layer_id = layer.id;
+ msg.transform = fuchsia_display_Transform_IDENTITY;
+
+ msg.src_frame.width = layer.src.width;
+ msg.src_frame.height = layer.src.height;
+ msg.src_frame.x_pos = layer.src.x_pos;
+ msg.src_frame.y_pos = layer.src.y_pos;
+
+ msg.dest_frame.width = layer.dest.width;
+ msg.dest_frame.height = layer.dest.height;
+ msg.dest_frame.x_pos = layer.dest.x_pos;
+ msg.dest_frame.y_pos = layer.dest.y_pos;
+
+ if (zx_channel_write(dc_handle, 0, &msg, sizeof(msg), nullptr, 0) != ZX_OK) {
+ ZX_ASSERT(false);
+ }
+ }
+}
+
+void VirtualLayer::SetLayerImages(zx_handle_t dc_handle) {
+ fuchsia_display_ControllerSetLayerImageRequest msg;
+ msg.hdr.ordinal = fuchsia_display_ControllerSetLayerImageOrdinal;
+
+ for (auto& layer : layers_) {
+ msg.layer_id = layer.id;
+ msg.image_id = layer.import_info[alt_image_].id;
+ msg.wait_event_id = layer.import_info[alt_image_].event_ids[WAIT_EVENT];
+ msg.present_event_id = layer.import_info[alt_image_].event_ids[PRESENT_EVENT];
+ msg.signal_event_id = layer.import_info[alt_image_].event_ids[SIGNAL_EVENT];
+
+ if (zx_channel_write(dc_handle, 0, &msg, sizeof(msg), nullptr, 0) != ZX_OK) {
+ ZX_ASSERT(false);
+ }
+ }
+}
+
+bool VirtualLayer::Wait(uint32_t idx) {
+ zx_time_t deadline = zx_deadline_after(ZX_MSEC(100));
+ for (auto& layer : layers_) {
+ uint32_t observed;
+ if (!layer.active) {
+ continue;
+ }
+ zx_handle_t event = layer.import_info[alt_image_].events[idx];
+ zx_status_t res;
+ if ((res = zx_object_wait_one(event, ZX_EVENT_SIGNALED, deadline, &observed)) == ZX_OK) {
+ if (layer_flipping_) {
+ zx_object_signal(event, ZX_EVENT_SIGNALED, 0);
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/system/uapp/display-test/virtual-layer.h b/system/uapp/display-test/virtual-layer.h
new file mode 100644
index 0000000..26e5189
--- /dev/null
+++ b/system/uapp/display-test/virtual-layer.h
@@ -0,0 +1,115 @@
+// 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.
+
+#pragma once
+
+#include <zircon/device/display-controller.h>
+#include <zircon/types.h>
+#include <lib/zx/channel.h>
+
+#include "display.h"
+#include "fuchsia/display/c/fidl.h"
+#include "image.h"
+
+typedef struct frame {
+ uint32_t width;
+ uint32_t height;
+ uint32_t x_pos;
+ uint32_t y_pos;
+} frame_t;
+
+typedef struct layer {
+ uint64_t id;
+ bool active;
+
+ frame_t src;
+ frame_t dest;
+
+ image_import_t import_info[2];
+} layer_t;
+
+// A layer whose output can appear on multiple displays.
+class VirtualLayer {
+public:
+ VirtualLayer(Display* display);
+ VirtualLayer(const fbl::Vector<Display>& displays);
+
+ // Set* methods to configure the layer.
+ void SetImageDimens(uint32_t width, uint32_t height) {
+ image_width_ = width;
+ image_height_ = height;
+
+ src_frame_.width = width;
+ src_frame_.height = height;
+ dest_frame_.width = width;
+ dest_frame_.height = height;
+ }
+ void SetSrcFrame(uint32_t width, uint32_t height) {
+ src_frame_.width = width;
+ src_frame_.height = height;
+ }
+ void SetDestFrame(uint32_t width, uint32_t height) {
+ dest_frame_.width = width;
+ dest_frame_.height = height;
+ }
+ void SetLayerFlipping(bool flip) { layer_flipping_ = flip; }
+ void SetPanSrc(bool pan) { pan_src_ = pan; }
+ void SetPanDest(bool pan) { pan_dest_ = pan; }
+ void SetLayerToggle(bool toggle) { layer_toggle_ = toggle; }
+
+ // Finish initializing the layer. All Set* methods should be called before this.
+ bool Init(zx_handle_t channel);
+
+ // Steps the local layout state to frame_num.
+ void StepLayout(int32_t frame_num);
+
+ // Waits for the display controller to be done with the previous version of this frame.
+ bool WaitForReady();
+
+ // Sets the current layout to the display contorller.
+ void SendLayout(zx_handle_t channel);
+
+ // Renders the current frame (and signals the fence if necessary).
+ void Render(int32_t frame_num);
+
+ // Waits for the current layer configuration to be presented.
+ bool WaitForPresent();
+
+ // Gets the display controller layer ID for usage on the given display.
+ uint64_t id(uint64_t display_id) const {
+ for (unsigned i = 0; i < displays_.size(); i++) {
+ if (displays_[i]->id() == display_id) {
+ if (layers_[i].active) {
+ return layers_[i].id;
+ }
+ }
+ }
+ return INVALID_ID;
+ }
+
+private:
+ void SetLayerImages(zx_handle_t handle);
+ void SetLayerPositions(zx_handle_t handle);
+ bool Wait(uint32_t idx);
+ void InitImageDimens();
+
+ fbl::Vector<Display*> displays_;
+ fbl::Vector<layer_t> layers_;
+
+ uint32_t width_;
+ uint32_t height_;
+
+ uint32_t image_width_ = 0;
+ uint32_t image_height_ = 0;
+ uint32_t image_format_ = 0;
+ frame_t src_frame_ = {};
+ frame_t dest_frame_ = {};
+ bool layer_flipping_ = false;
+ bool pan_src_ = false;
+ bool pan_dest_ = false;
+ bool layer_toggle_ = false;
+
+ bool alt_image_ = false;
+ Image* images_[2];
+};