[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];
+};