Merge "Split bugreport identifier into id and pid." into nyc-dev
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 8515a01..a4b5ff1 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -18,7 +18,7 @@
 # ZipArchive support, the order matters here to get all symbols.
 LOCAL_STATIC_LIBRARIES := libziparchive libz libbase libmincrypt
 LOCAL_HAL_STATIC_LIBRARIES := libdumpstate
-LOCAL_CFLAGS += -Wall -Wno-unused-parameter -std=gnu99
+LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter -std=gnu99
 LOCAL_INIT_RC := dumpstate.rc
 
 include $(BUILD_EXECUTABLE)
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6917c21..462cf7d 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -145,7 +145,7 @@
     mount_points.clear();
     DurationReporter duration_reporter(title, NULL);
     for_each_pid(do_mountinfo, NULL);
-    MYLOGD("%s: %lu entries added to zip file\n", title, mount_points.size());
+    MYLOGD("%s: %d entries added to zip file\n", title, (int) mount_points.size());
 }
 
 static void dump_dev_files(const char *title, const char *driverpath, const char *filename)
@@ -578,7 +578,7 @@
     return true;
 }
 
-static void dumpstate(const std::string& screenshot_path) {
+static void dumpstate(const std::string& screenshot_path, const std::string& version) {
     DurationReporter duration_reporter("DUMPSTATE");
     unsigned long timeout;
 
@@ -615,6 +615,7 @@
     run_command("LIST OF OPEN FILES", 10, SU_PATH, "root", "lsof", NULL);
     for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
     for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
+    for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");
 
     if (!screenshot_path.empty()) {
         MYLOGI("taking late screenshot\n");
@@ -848,7 +849,12 @@
     /* the full dumpsys is starting to take a long time, so we need
        to increase its timeout.  we really need to do the timeouts in
        dumpsys itself... */
-    run_command("DUMPSYS", 60, "dumpsys", NULL);
+    if (version == VERSION_DUMPSYS_SPLIT) {
+        // Skipping meminfo and cpuinfo services.
+        run_command("DUMPSYS", 60, "dumpsys", "--skip", "meminfo,cpuinfo", NULL);
+    } else {
+        run_command("DUMPSYS", 60, "dumpsys", NULL);
+    }
 
     printf("========================================================\n");
     printf("== Checkins\n");
@@ -1182,7 +1188,7 @@
 
         if (do_update_progress) {
             std::vector<std::string> am_args = {
-                 "--receiver-permission", "android.permission.DUMP",
+                 "--receiver-permission", "android.permission.DUMP", "--receiver-foreground",
                  "--es", "android.intent.extra.NAME", suffix,
                  "--ei", "android.intent.extra.ID", std::to_string(id),
                  "--ei", "android.intent.extra.PID", std::to_string(getpid()),
@@ -1229,18 +1235,6 @@
         }
     }
 
-    /* collect stack traces from Dalvik and native processes (needs root) */
-    dump_traces_path = dump_traces();
-
-    /* Get the tombstone fds, recovery files, and mount info here while we are running as root. */
-    get_tombstone_fds(tombstone_data);
-    add_dir(RECOVERY_DIR, true);
-    add_mountinfo();
-
-    if (!drop_root()) {
-        return -1;
-    }
-
     if (is_redirecting) {
         redirect_to_file(stderr, const_cast<char*>(log_path.c_str()));
         /* TODO: rather than generating a text file now and zipping it later,
@@ -1253,7 +1247,26 @@
     // duration is logged into MYLOG instead.
     print_header(version);
 
-    dumpstate(do_early_screenshot ? "": screenshot_path);
+    if (version == VERSION_DUMPSYS_SPLIT) {
+        // Invoking the following dumpsys calls before dump_traces() to try and
+        // keep the system stats as close to its initial state as possible.
+        run_command("DUMPSYS MEMINFO", 30, SU_PATH, "shell", "dumpsys", "meminfo", "-a", NULL);
+        run_command("DUMPSYS CPUINFO", 30, SU_PATH, "shell", "dumpsys", "cpuinfo", "-a", NULL);
+    }
+
+    /* collect stack traces from Dalvik and native processes (needs root) */
+    dump_traces_path = dump_traces();
+
+    /* Get the tombstone fds, recovery files, and mount info here while we are running as root. */
+    get_tombstone_fds(tombstone_data);
+    add_dir(RECOVERY_DIR, true);
+    add_mountinfo();
+
+    if (!drop_root()) {
+        return -1;
+    }
+
+    dumpstate(do_early_screenshot ? "": screenshot_path, version);
 
     /* done */
     if (vibrator) {
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 5a93f8c..a8aea42 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -30,15 +30,15 @@
 #endif
 
 #ifndef MYLOGD
-#define MYLOGD(fmt...) fprintf(stderr, fmt); ALOGD(fmt);
+#define MYLOGD(...) fprintf(stderr, __VA_ARGS__); ALOGD(__VA_ARGS__);
 #endif
 
 #ifndef MYLOGI
-#define MYLOGI(fmt...) fprintf(stderr, fmt); ALOGI(fmt);
+#define MYLOGI(...) fprintf(stderr, __VA_ARGS__); ALOGI(__VA_ARGS__);
 #endif
 
 #ifndef MYLOGE
-#define MYLOGE(fmt...) fprintf(stderr, fmt); ALOGE(fmt);
+#define MYLOGE(...) fprintf(stderr, __VA_ARGS__); ALOGE(__VA_ARGS__);
 #endif
 
 #include <time.h>
@@ -138,6 +138,9 @@
 /* Displays a blocked processes in-kernel wait channel */
 void show_wchan(int pid, int tid, const char *name);
 
+/* Displays a processes times */
+void show_showtime(int pid, const char *name);
+
 /* Runs "showmap" for a process */
 void do_showmap(int pid, const char *name);
 
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index 683091f..9d42939 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -27,6 +27,7 @@
 #include <string.h>
 #include <sys/inotify.h>
 #include <sys/stat.h>
+#include <sys/sysconf.h>
 #include <sys/time.h>
 #include <sys/wait.h>
 #include <sys/klog.h>
@@ -133,13 +134,32 @@
             continue;
         }
 
-        sprintf(cmdpath,"/proc/%d/cmdline", pid);
         memset(cmdline, 0, sizeof(cmdline));
-        if ((fd = TEMP_FAILURE_RETRY(open(cmdpath, O_RDONLY | O_CLOEXEC))) < 0) {
-            strcpy(cmdline, "N/A");
-        } else {
-            read(fd, cmdline, sizeof(cmdline) - 1);
+
+        snprintf(cmdpath, sizeof(cmdpath), "/proc/%d/cmdline", pid);
+        if ((fd = TEMP_FAILURE_RETRY(open(cmdpath, O_RDONLY | O_CLOEXEC))) >= 0) {
+            TEMP_FAILURE_RETRY(read(fd, cmdline, sizeof(cmdline) - 2));
             close(fd);
+            if (cmdline[0]) {
+                helper(pid, cmdline, arg);
+                continue;
+            }
+        }
+
+        // if no cmdline, a kernel thread has comm
+        snprintf(cmdpath, sizeof(cmdpath), "/proc/%d/comm", pid);
+        if ((fd = TEMP_FAILURE_RETRY(open(cmdpath, O_RDONLY | O_CLOEXEC))) >= 0) {
+            TEMP_FAILURE_RETRY(read(fd, cmdline + 1, sizeof(cmdline) - 4));
+            close(fd);
+            if (cmdline[1]) {
+                cmdline[0] = '[';
+                size_t len = strcspn(cmdline, "\f\b\r\n");
+                cmdline[len] = ']';
+                cmdline[len+1] = '\0';
+            }
+        }
+        if (!cmdline[0]) {
+            strcpy(cmdline, "N/A");
         }
         helper(pid, cmdline, arg);
     }
@@ -191,7 +211,7 @@
             strcpy(comm, "N/A");
         } else {
             char *c;
-            read(fd, comm, sizeof(comm) - 1);
+            TEMP_FAILURE_RETRY(read(fd, comm, sizeof(comm) - 2));
             close(fd);
 
             c = strrchr(comm, '\n');
@@ -214,7 +234,7 @@
     ON_DRY_RUN_RETURN();
     char path[255];
     char buffer[255];
-    int fd;
+    int fd, ret, save_errno;
     char name_buffer[255];
 
     memset(buffer, 0, sizeof(buffer));
@@ -225,9 +245,13 @@
         return;
     }
 
-    if (read(fd, buffer, sizeof(buffer)) < 0) {
-        printf("Failed to read '%s' (%s)\n", path, strerror(errno));
-        goto out_close;
+    ret = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+    save_errno = errno;
+    close(fd);
+
+    if (ret < 0) {
+        printf("Failed to read '%s' (%s)\n", path, strerror(save_errno));
+        return;
     }
 
     snprintf(name_buffer, sizeof(name_buffer), "%*s%s",
@@ -235,8 +259,103 @@
 
     printf("%-7d %-32s %s\n", tid, name_buffer, buffer);
 
-out_close:
+    return;
+}
+
+// print time in centiseconds
+static void snprcent(char *buffer, size_t len, size_t spc,
+                     unsigned long long time) {
+    static long hz; // cache discovered hz
+
+    if (hz <= 0) {
+        hz = sysconf(_SC_CLK_TCK);
+        if (hz <= 0) {
+            hz = 1000;
+        }
+    }
+
+    // convert to centiseconds
+    time = (time * 100 + (hz / 2)) / hz;
+
+    char str[16];
+
+    snprintf(str, sizeof(str), " %llu.%02u",
+             time / 100, (unsigned)(time % 100));
+    size_t offset = strlen(buffer);
+    snprintf(buffer + offset, (len > offset) ? len - offset : 0,
+             "%*s", (spc > offset) ? (int)(spc - offset) : 0, str);
+}
+
+// print permille as a percent
+static void snprdec(char *buffer, size_t len, size_t spc, unsigned permille) {
+    char str[16];
+
+    snprintf(str, sizeof(str), " %u.%u%%", permille / 10, permille % 10);
+    size_t offset = strlen(buffer);
+    snprintf(buffer + offset, (len > offset) ? len - offset : 0,
+             "%*s", (spc > offset) ? (int)(spc - offset) : 0, str);
+}
+
+void show_showtime(int pid, const char *name) {
+    ON_DRY_RUN_RETURN();
+    char path[255];
+    char buffer[1023];
+    int fd, ret, save_errno;
+
+    memset(buffer, 0, sizeof(buffer));
+
+    sprintf(path, "/proc/%d/stat", pid);
+    if ((fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC))) < 0) {
+        printf("Failed to open '%s' (%s)\n", path, strerror(errno));
+        return;
+    }
+
+    ret = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+    save_errno = errno;
     close(fd);
+
+    if (ret < 0) {
+        printf("Failed to read '%s' (%s)\n", path, strerror(save_errno));
+        return;
+    }
+
+    // field 14 is utime
+    // field 15 is stime
+    // field 42 is iotime
+    unsigned long long utime = 0, stime = 0, iotime = 0;
+    if (sscanf(buffer,
+               "%*llu %*s %*s %*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld "
+               "%*lld %*lld %llu %llu %*lld %*lld %*lld %*lld %*lld %*lld "
+               "%*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld "
+               "%*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld %*lld %llu ",
+               &utime, &stime, &iotime) != 3) {
+        return;
+    }
+
+    unsigned long long total = utime + stime;
+    if (!total) {
+        return;
+    }
+
+    unsigned permille = (iotime * 1000 + (total / 2)) / total;
+    if (permille > 1000) {
+        permille = 1000;
+    }
+
+    // try to beautify and stabilize columns at <80 characters
+    snprintf(buffer, sizeof(buffer), "%-6d%s", pid, name);
+    if ((name[0] != '[') || utime) {
+        snprcent(buffer, sizeof(buffer), 57, utime);
+    }
+    snprcent(buffer, sizeof(buffer), 65, stime);
+    if ((name[0] != '[') || iotime) {
+        snprcent(buffer, sizeof(buffer), 73, iotime);
+    }
+    if (iotime) {
+        snprdec(buffer, sizeof(buffer), 79, permille);
+    }
+    puts(buffer); // adds a trailing newline
+
     return;
 }
 
@@ -586,9 +705,9 @@
         fprintf(stderr, "send_broadcast: too many arguments (%d)\n", (int) args.size());
         return;
     }
-    const char *am_args[1024] = { "/system/bin/am", "broadcast",
+    const char *am_args[1024] = { SU_PATH, "shell", "/system/bin/am", "broadcast",
                                   "--user", "0", "-a", action.c_str() };
-    size_t am_index = 5; // Starts at the index of last initial value above.
+    size_t am_index = 7; // Starts at the index of last initial value above.
     for (const std::string& arg : args) {
         am_args[++am_index] = arg.c_str();
     }
@@ -658,7 +777,7 @@
 }
 
 void create_parent_dirs(const char *path) {
-    char *chp = (char*) path;
+    char *chp = const_cast<char *> (path);
 
     /* skip initial slash */
     if (chp[0] == '/')
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 9d42464..136a14a 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -222,9 +222,9 @@
     if (mCore->mFreeSlots.empty()) {
         return BufferQueueCore::INVALID_BUFFER_SLOT;
     }
-    auto slot = mCore->mFreeSlots.begin();
+    int slot = *(mCore->mFreeSlots.begin());
     mCore->mFreeSlots.erase(slot);
-    return *slot;
+    return slot;
 }
 
 status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d39075f..28e5e43 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1000,8 +1000,9 @@
                 // Signal our end of the sync point and then dispose of it
                 mRemoteSyncPoints.front()->setTransactionApplied();
                 mRemoteSyncPoints.pop_front();
+            } else {
+                break;
             }
-            break;
         } else {
             popPendingState();
             stateUpdateAvailable = true;
@@ -1240,6 +1241,8 @@
     // request without any other state updates shouldn't actually induce a delay
     mCurrentState.modified = true;
     pushPendingState();
+    mCurrentState.handle = nullptr;
+    mCurrentState.frameNumber = 0;
     mCurrentState.modified = false;
 }
 
diff --git a/vulkan/include/vulkan/vk_layer_interface.h b/vulkan/include/vulkan/vk_layer_interface.h
new file mode 100644
index 0000000..8aef495
--- /dev/null
+++ b/vulkan/include/vulkan/vk_layer_interface.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and/or associated documentation files (the "Materials"), to
+ * deal in the Materials without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Materials, and to permit persons to whom the Materials are
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice(s) and this permission notice shall be included in
+ * all copies or substantial portions of the Materials.
+ *
+ * The Materials are Confidential Information as defined by the Khronos
+ * Membership Agreement until designated non-confidential by Khronos, at which
+ * point this condition clause shall be removed.
+ *
+ * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
+ * USE OR OTHER DEALINGS IN THE MATERIALS.
+ *
+ */
+#pragma once
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vk_ext_debug_report.h>
+
+// ------------------------------------------------------------------------------------------------
+// CreateInstance and CreateDevice support structures
+
+typedef enum VkLayerFunction_ {
+    VK_LAYER_FUNCTION_LINK = 0,
+    VK_LAYER_FUNCTION_DEVICE = 1,
+    VK_LAYER_FUNCTION_INSTANCE = 2
+} VkLayerFunction;
+
+/*
+ * When creating the device chain the loader needs to pass
+ * down information about it's device structure needed at
+ * the end of the chain. Passing the data via the
+ * VkLayerInstanceInfo avoids issues with finding the
+ * exact instance being used.
+ */
+typedef struct VkLayerInstanceInfo_ {
+    void* instance_info;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+} VkLayerInstanceInfo;
+
+typedef struct VkLayerInstanceLink_ {
+    struct VkLayerInstanceLink_* pNext;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+} VkLayerInstanceLink;
+
+/*
+ * When creating the device chain the loader needs to pass
+ * down information about it's device structure needed at
+ * the end of the chain. Passing the data via the
+ * VkLayerDeviceInfo avoids issues with finding the
+ * exact instance being used.
+ */
+typedef struct VkLayerDeviceInfo_ {
+    void* device_info;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+} VkLayerDeviceInfo;
+
+typedef struct {
+    VkStructureType sType;  // VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
+    const void* pNext;
+    VkLayerFunction function;
+    union {
+        VkLayerInstanceLink* pLayerInfo;
+        VkLayerInstanceInfo instanceInfo;
+    } u;
+} VkLayerInstanceCreateInfo;
+
+typedef struct VkLayerDeviceLink_ {
+    struct VkLayerDeviceLink_* pNext;
+    PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+    PFN_vkGetDeviceProcAddr pfnNextGetDeviceProcAddr;
+} VkLayerDeviceLink;
+
+typedef struct {
+    VkStructureType sType;  // VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
+    const void* pNext;
+    VkLayerFunction function;
+    union {
+        VkLayerDeviceLink* pLayerInfo;
+        VkLayerDeviceInfo deviceInfo;
+    } u;
+} VkLayerDeviceCreateInfo;
diff --git a/vulkan/libvulkan/Android.mk b/vulkan/libvulkan/Android.mk
index a196a36..779fedf 100644
--- a/vulkan/libvulkan/Android.mk
+++ b/vulkan/libvulkan/Android.mk
@@ -20,15 +20,16 @@
 	-std=c99 -fvisibility=hidden -fstrict-aliasing \
 	-Weverything -Werror \
 	-Wno-padded \
+	-Wno-switch-enum \
 	-Wno-undef
 #LOCAL_CFLAGS += -DLOG_NDEBUG=0
 LOCAL_CPPFLAGS := -std=c++14 \
 	-fexceptions \
+	-Wno-c99-extensions \
 	-Wno-c++98-compat-pedantic \
 	-Wno-exit-time-destructors \
-	-Wno-c99-extensions \
-	-Wno-zero-length-array \
-	-Wno-global-constructors
+	-Wno-global-constructors \
+	-Wno-zero-length-array
 
 LOCAL_C_INCLUDES := \
 	frameworks/native/vulkan/include \
diff --git a/vulkan/libvulkan/debug_report.cpp b/vulkan/libvulkan/debug_report.cpp
index fea9f18..41b6040 100644
--- a/vulkan/libvulkan/debug_report.cpp
+++ b/vulkan/libvulkan/debug_report.cpp
@@ -23,11 +23,16 @@
     const VkDebugReportCallbackCreateInfoEXT* create_info,
     const VkAllocationCallbacks* allocator,
     VkDebugReportCallbackEXT* callback) {
-    VkDebugReportCallbackEXT driver_callback;
-    VkResult result = GetDriverDispatch(instance).CreateDebugReportCallbackEXT(
-        GetDriverInstance(instance), create_info, allocator, &driver_callback);
-    if (result != VK_SUCCESS)
-        return result;
+    VkDebugReportCallbackEXT driver_callback = VK_NULL_HANDLE;
+
+    if (GetDriverDispatch(instance).CreateDebugReportCallbackEXT) {
+        VkResult result =
+            GetDriverDispatch(instance).CreateDebugReportCallbackEXT(
+                GetDriverInstance(instance), create_info, allocator,
+                &driver_callback);
+        if (result != VK_SUCCESS)
+            return result;
+    }
 
     const VkAllocationCallbacks* alloc =
         allocator ? allocator : GetAllocator(instance);
@@ -35,8 +40,10 @@
         alloc->pfnAllocation(alloc->pUserData, sizeof(Node), alignof(Node),
                              VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
     if (!mem) {
-        GetDriverDispatch(instance).DestroyDebugReportCallbackEXT(
-            GetDriverInstance(instance), driver_callback, allocator);
+        if (GetDriverDispatch(instance).DestroyDebugReportCallbackEXT) {
+            GetDriverDispatch(instance).DestroyDebugReportCallbackEXT(
+                GetDriverInstance(instance), driver_callback, allocator);
+        }
         return VK_ERROR_OUT_OF_HOST_MEMORY;
     }
 
@@ -61,8 +68,10 @@
     prev->next = node->next;
     lock.unlock();
 
-    GetDriverDispatch(instance).DestroyDebugReportCallbackEXT(
-        GetDriverInstance(instance), node->driver_callback, allocator);
+    if (GetDriverDispatch(instance).DestroyDebugReportCallbackEXT) {
+        GetDriverDispatch(instance).DestroyDebugReportCallbackEXT(
+            GetDriverInstance(instance), node->driver_callback, allocator);
+    }
 
     const VkAllocationCallbacks* alloc =
         allocator ? allocator : GetAllocator(instance);
@@ -112,9 +121,11 @@
                                   int32_t message_code,
                                   const char* layer_prefix,
                                   const char* message) {
-    GetDriverDispatch(instance).DebugReportMessageEXT(
-        GetDriverInstance(instance), flags, object_type, object, location,
-        message_code, layer_prefix, message);
+    if (GetDriverDispatch(instance).DebugReportMessageEXT) {
+        GetDriverDispatch(instance).DebugReportMessageEXT(
+            GetDriverInstance(instance), flags, object_type, object, location,
+            message_code, layer_prefix, message);
+    }
     GetDebugReportCallbacks(instance).Message(flags, object_type, object,
                                               location, message_code,
                                               layer_prefix, message);
diff --git a/vulkan/libvulkan/dispatch.tmpl b/vulkan/libvulkan/dispatch.tmpl
index 054a235..1a584e3 100644
--- a/vulkan/libvulkan/dispatch.tmpl
+++ b/vulkan/libvulkan/dispatch.tmpl
@@ -560,6 +560,7 @@
        objects */}}
   {{else if eq $.Name "vkGetDeviceQueue"}}true
   {{else if eq $.Name "vkAllocateCommandBuffers"}}true
+  {{else if eq $.Name "vkCreateDevice"}}true
 
   {{/* vkDestroy for dispatchable objects needs to handle VK_NULL_HANDLE;
        trying to dispatch through that would crash. */}}
diff --git a/vulkan/libvulkan/dispatch_gen.cpp b/vulkan/libvulkan/dispatch_gen.cpp
index 0d2d605..9028c3f 100644
--- a/vulkan/libvulkan/dispatch_gen.cpp
+++ b/vulkan/libvulkan/dispatch_gen.cpp
@@ -211,6 +211,7 @@
 const NameProc kLoaderTopProcs[] = {
     // clang-format off
     {"vkAllocateCommandBuffers", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkAllocateCommandBuffers>(AllocateCommandBuffers_Top))},
+    {"vkCreateDevice", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateDevice>(CreateDevice_Top))},
     {"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateInstance>(CreateInstance_Top))},
     {"vkDestroyDevice", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyDevice>(DestroyDevice_Top))},
     {"vkDestroyInstance", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyInstance>(DestroyInstance_Top))},
@@ -1401,7 +1402,7 @@
 
 __attribute__((visibility("default")))
 VKAPI_ATTR VkResult vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) {
-    return GetDispatchTable(physicalDevice).CreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice);
+    return CreateDevice_Top(physicalDevice, pCreateInfo, pAllocator, pDevice);
 }
 
 __attribute__((visibility("default")))
diff --git a/vulkan/libvulkan/layers_extensions.cpp b/vulkan/libvulkan/layers_extensions.cpp
index 287e69b..e77952a 100644
--- a/vulkan/libvulkan/layers_extensions.cpp
+++ b/vulkan/libvulkan/layers_extensions.cpp
@@ -394,6 +394,14 @@
     }
 }
 
+const char* LayerRef::GetName() {
+    return layer_->properties.layerName;
+}
+
+uint32_t LayerRef::GetSpecVersion() {
+    return layer_->properties.specVersion;
+}
+
 LayerRef::LayerRef(LayerRef&& other) : layer_(std::move(other.layer_)) {
     other.layer_ = nullptr;
 }
diff --git a/vulkan/libvulkan/loader.cpp b/vulkan/libvulkan/loader.cpp
index 939f3b9..a0c142e 100644
--- a/vulkan/libvulkan/loader.cpp
+++ b/vulkan/libvulkan/loader.cpp
@@ -36,6 +36,7 @@
 #include <hardware/hwvulkan.h>
 #include <log/log.h>
 #include <vulkan/vulkan_loader_data.h>
+#include <vulkan/vk_layer_interface.h>
 
 // #define ENABLE_ALLOC_CALLSTACKS 1
 #if ENABLE_ALLOC_CALLSTACKS
@@ -59,16 +60,6 @@
 
 namespace {
 
-// These definitions are taken from the LunarG Vulkan Loader. They are used to
-// enforce compatability between the Loader and Layers.
-typedef void* (*PFN_vkGetProcAddr)(void* obj, const char* pName);
-
-typedef struct VkLayerLinkedListElem_ {
-    PFN_vkGetProcAddr get_proc_addr;
-    void* next_element;
-    void* base_object;
-} VkLayerLinkedListElem;
-
 // ----------------------------------------------------------------------------
 
 // Standard-library allocator that delegates to VkAllocationCallbacks.
@@ -521,6 +512,41 @@
     return VK_SUCCESS;
 }
 
+/*
+ * This function will return the pNext pointer of any
+ * CreateInfo extensions that are not loader extensions.
+ * This is used to skip past the loader extensions prepended
+ * to the list during CreateInstance and CreateDevice.
+ */
+void* StripCreateExtensions(const void* pNext) {
+    VkLayerInstanceCreateInfo* create_info =
+        const_cast<VkLayerInstanceCreateInfo*>(
+            static_cast<const VkLayerInstanceCreateInfo*>(pNext));
+
+    while (
+        create_info &&
+        (create_info->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO ||
+         create_info->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO)) {
+        create_info = const_cast<VkLayerInstanceCreateInfo*>(
+            static_cast<const VkLayerInstanceCreateInfo*>(create_info->pNext));
+    }
+
+    return create_info;
+}
+
+// Separate out cleaning up the layers and instance storage
+// to avoid code duplication in the many failure cases in
+// in CreateInstance_Top
+void TeardownInstance(
+    VkInstance vkinstance,
+    const VkAllocationCallbacks* /* allocator */) {
+    Instance& instance = GetDispatchParent(vkinstance);
+    instance.active_layers.clear();
+    const VkAllocationCallbacks* alloc = instance.alloc;
+    instance.~Instance();
+    alloc->pfnFree(alloc->pUserData, &instance);
+}
+
 }  // anonymous namespace
 
 namespace vulkan {
@@ -532,9 +558,23 @@
 VkResult CreateInstance_Bottom(const VkInstanceCreateInfo* create_info,
                                const VkAllocationCallbacks* allocator,
                                VkInstance* vkinstance) {
-    Instance& instance = GetDispatchParent(*vkinstance);
     VkResult result;
 
+    VkLayerInstanceCreateInfo* chain_info =
+        const_cast<VkLayerInstanceCreateInfo*>(
+            static_cast<const VkLayerInstanceCreateInfo*>(create_info->pNext));
+    while (
+        chain_info &&
+        !(chain_info->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO &&
+          chain_info->function == VK_LAYER_FUNCTION_INSTANCE)) {
+        chain_info = const_cast<VkLayerInstanceCreateInfo*>(
+            static_cast<const VkLayerInstanceCreateInfo*>(chain_info->pNext));
+    }
+    ALOG_ASSERT(chain_info != nullptr, "Missing initialization chain info!");
+
+    Instance& instance = GetDispatchParent(
+        static_cast<VkInstance>(chain_info->u.instanceInfo.instance_info));
+
     // Check that all enabled extensions are supported
     InstanceExtensionSet enabled_extensions;
     uint32_t num_driver_extensions = 0;
@@ -547,11 +587,14 @@
                 enabled_extensions.set(id);
                 continue;
             }
-            if (id == kKHR_surface || id == kKHR_android_surface ||
-                id == kEXT_debug_report) {
+            if (id == kKHR_surface || id == kKHR_android_surface) {
                 enabled_extensions.set(id);
                 continue;
             }
+            // The loader natively supports debug report.
+            if (id == kEXT_debug_report) {
+                continue;
+            }
         }
         bool supported = false;
         for (const auto& layer : instance.active_layers) {
@@ -569,6 +612,7 @@
     }
 
     VkInstanceCreateInfo driver_create_info = *create_info;
+    driver_create_info.pNext = StripCreateExtensions(create_info->pNext);
     driver_create_info.enabledLayerCount = 0;
     driver_create_info.ppEnabledLayerNames = nullptr;
     driver_create_info.enabledExtensionCount = 0;
@@ -602,15 +646,14 @@
 
     hwvulkan_dispatch_t* drv_dispatch =
         reinterpret_cast<hwvulkan_dispatch_t*>(instance.drv.instance);
-    if (drv_dispatch->magic == HWVULKAN_DISPATCH_MAGIC) {
-        // Skip setting drv_dispatch->vtbl, since we never call through it;
-        // we go through instance.drv.dispatch instead.
-    } else {
+    if (drv_dispatch->magic != HWVULKAN_DISPATCH_MAGIC) {
         ALOGE("invalid VkInstance dispatch magic: 0x%" PRIxPTR,
               drv_dispatch->magic);
         DestroyInstance_Bottom(instance.handle, allocator);
         return VK_ERROR_INITIALIZATION_FAILED;
     }
+    // Skip setting drv_dispatch->vtbl, since we never call through it;
+    // we go through instance.drv.dispatch instead.
 
     if (!LoadDriverDispatchTable(instance.drv.instance,
                                  g_hwdevice->GetInstanceProcAddr,
@@ -689,6 +732,8 @@
     instance.drv.num_physical_devices = num_physical_devices;
     instance.num_physical_devices = instance.drv.num_physical_devices;
 
+    *vkinstance = instance.handle;
+
     return VK_SUCCESS;
 }
 
@@ -832,31 +877,28 @@
                              const VkDeviceCreateInfo* create_info,
                              const VkAllocationCallbacks* allocator,
                              VkDevice* device_out) {
-    Instance& instance = GetDispatchParent(gpu);
-    VkResult result;
-
-    // FIXME(jessehall): We don't have good conventions or infrastructure yet to
-    // do better than just using the instance allocator and scope for
-    // everything. See b/26732122.
-    if (true /*!allocator*/)
-        allocator = instance.alloc;
-
-    void* mem = allocator->pfnAllocation(allocator->pUserData, sizeof(Device),
-                                         alignof(Device),
-                                         VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
-    if (!mem)
-        return VK_ERROR_OUT_OF_HOST_MEMORY;
-    Device* device = new (mem) Device(&instance);
-
-    result = ActivateAllLayers(create_info, &instance, device);
-    if (result != VK_SUCCESS) {
-        DestroyDevice(device);
-        return result;
+    VkLayerDeviceCreateInfo* chain_info = const_cast<VkLayerDeviceCreateInfo*>(
+        static_cast<const VkLayerDeviceCreateInfo*>(create_info->pNext));
+    while (chain_info &&
+           !(chain_info->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO &&
+             chain_info->function == VK_LAYER_FUNCTION_DEVICE)) {
+        chain_info = const_cast<VkLayerDeviceCreateInfo*>(
+            static_cast<const VkLayerDeviceCreateInfo*>(chain_info->pNext));
     }
+    ALOG_ASSERT(chain_info != nullptr, "Missing initialization chain info!");
 
+    Instance& instance = GetDispatchParent(gpu);
     size_t gpu_idx = 0;
     while (instance.physical_devices[gpu_idx] != gpu)
         gpu_idx++;
+    Device* device = static_cast<Device*>(chain_info->u.deviceInfo.device_info);
+    PFN_vkGetInstanceProcAddr get_instance_proc_addr =
+        chain_info->u.deviceInfo.pfnNextGetInstanceProcAddr;
+
+    VkDeviceCreateInfo driver_create_info = *create_info;
+    driver_create_info.pNext = StripCreateExtensions(create_info->pNext);
+    driver_create_info.enabledLayerCount = 0;
+    driver_create_info.ppEnabledLayerNames = nullptr;
 
     uint32_t num_driver_extensions = 0;
     const char** driver_extensions = static_cast<const char**>(
@@ -869,6 +911,8 @@
                 driver_extensions[num_driver_extensions++] = name;
                 continue;
             }
+            // Add the VK_ANDROID_native_buffer extension to the list iff
+            // the VK_KHR_swapchain extension was requested
             if (id == kKHR_swapchain &&
                 instance.physical_device_driver_extensions
                     [gpu_idx][kANDROID_native_buffer]) {
@@ -887,28 +931,17 @@
                 "requested device extension '%s' not supported by loader, "
                 "driver, or any active layers",
                 name);
-            DestroyDevice(device);
             return VK_ERROR_EXTENSION_NOT_PRESENT;
         }
     }
 
-    VkDeviceCreateInfo driver_create_info = *create_info;
-    driver_create_info.enabledLayerCount = 0;
-    driver_create_info.ppEnabledLayerNames = nullptr;
-    // TODO(jessehall): As soon as we enumerate device extensions supported by
-    // the driver, we need to filter the requested extension list to those
-    // supported by the driver here. Also, add the VK_ANDROID_native_buffer
-    // extension to the list iff the VK_KHR_swapchain extension was requested,
-    // instead of adding it unconditionally like we do now.
     driver_create_info.enabledExtensionCount = num_driver_extensions;
     driver_create_info.ppEnabledExtensionNames = driver_extensions;
-
     VkDevice drv_device;
-    result = instance.drv.dispatch.CreateDevice(gpu, &driver_create_info,
-                                                allocator, &drv_device);
+    VkResult result = instance.drv.dispatch.CreateDevice(
+        gpu, &driver_create_info, allocator, &drv_device);
     if (result != VK_SUCCESS) {
-        DestroyDevice(device);
-        return result;
+        return VK_ERROR_INITIALIZATION_FAILED;
     }
 
     hwvulkan_dispatch_t* drv_dispatch =
@@ -921,68 +954,15 @@
                 instance.drv.dispatch.GetDeviceProcAddr(drv_device,
                                                         "vkDestroyDevice"));
         destroy_device(drv_device, allocator);
-        DestroyDevice(device);
         return VK_ERROR_INITIALIZATION_FAILED;
     }
+
+    // Set dispatch table for newly created Device
+    // CreateDevice_Top will fill in the details
     drv_dispatch->vtbl = &device->dispatch;
     device->get_device_proc_addr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
         instance.drv.dispatch.GetDeviceProcAddr(drv_device,
                                                 "vkGetDeviceProcAddr"));
-
-    void* base_object = static_cast<void*>(drv_device);
-    void* next_object = base_object;
-    VkLayerLinkedListElem* next_element;
-    PFN_vkGetDeviceProcAddr next_get_proc_addr = GetDeviceProcAddr_Bottom;
-    Vector<VkLayerLinkedListElem> elem_list(
-        CallbackAllocator<VkLayerLinkedListElem>(instance.alloc));
-    try {
-        elem_list.resize(device->active_layers.size());
-    } catch (std::bad_alloc&) {
-        ALOGE("device creation failed: out of memory");
-        PFN_vkDestroyDevice destroy_device =
-            reinterpret_cast<PFN_vkDestroyDevice>(
-                instance.drv.dispatch.GetDeviceProcAddr(drv_device,
-                                                        "vkDestroyDevice"));
-        destroy_device(drv_device, allocator);
-        DestroyDevice(device);
-        return VK_ERROR_OUT_OF_HOST_MEMORY;
-    }
-
-    for (size_t i = elem_list.size(); i > 0; i--) {
-        size_t idx = i - 1;
-        next_element = &elem_list[idx];
-        next_element->get_proc_addr =
-            reinterpret_cast<PFN_vkGetProcAddr>(next_get_proc_addr);
-        next_element->base_object = base_object;
-        next_element->next_element = next_object;
-        next_object = static_cast<void*>(next_element);
-
-        next_get_proc_addr = device->active_layers[idx].GetGetDeviceProcAddr();
-        if (!next_get_proc_addr) {
-            next_object = next_element->next_element;
-            next_get_proc_addr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
-                next_element->get_proc_addr);
-        }
-    }
-
-    // This is the magic call that initializes all the layer devices and
-    // allows them to create their device_handle -> device_data mapping.
-    next_get_proc_addr(static_cast<VkDevice>(next_object),
-                       "vkGetDeviceProcAddr");
-
-    // We must create all the layer devices *before* retrieving the device
-    // procaddrs, so that the layers know which extensions are enabled and
-    // therefore which functions to return procaddrs for.
-    PFN_vkCreateDevice create_device = reinterpret_cast<PFN_vkCreateDevice>(
-        next_get_proc_addr(drv_device, "vkCreateDevice"));
-    create_device(gpu, create_info, allocator, &drv_device);
-
-    if (!LoadDeviceDispatchTable(static_cast<VkDevice>(base_object),
-                                 next_get_proc_addr, device->dispatch)) {
-        DestroyDevice(device);
-        return VK_ERROR_INITIALIZATION_FAILED;
-    }
-
     *device_out = drv_device;
     return VK_SUCCESS;
 }
@@ -1006,25 +986,12 @@
                                       "vkDestroyDebugReportCallbackEXT"));
         destroy_debug_report_callback(vkinstance, instance.message, allocator);
     }
-    instance.active_layers.clear();
-    const VkAllocationCallbacks* alloc = instance.alloc;
-    instance.~Instance();
-    alloc->pfnFree(alloc->pUserData, &instance);
 }
 
 PFN_vkVoidFunction GetDeviceProcAddr_Bottom(VkDevice vkdevice,
                                             const char* name) {
     if (strcmp(name, "vkCreateDevice") == 0) {
-        // TODO(jessehall): Blegh, having this here is disgusting. The current
-        // layer init process can't call through the instance dispatch table's
-        // vkCreateDevice, because that goes through the instance layers rather
-        // than through the device layers. So we need to be able to get the
-        // vkCreateDevice pointer through the *device* layer chain.
-        //
-        // Because we've already created the driver device before calling
-        // through the layer vkCreateDevice functions, the loader bottom proc
-        // is a no-op.
-        return reinterpret_cast<PFN_vkVoidFunction>(Noop);
+        return reinterpret_cast<PFN_vkVoidFunction>(CreateDevice_Bottom);
     }
 
     // VK_ANDROID_native_buffer should be hidden from applications and layers.
@@ -1118,51 +1085,97 @@
     result = ActivateAllLayers(create_info, instance, instance);
     if (result != VK_SUCCESS) {
         DestroyInstance_Bottom(instance->handle, allocator);
+        TeardownInstance(instance->handle, allocator);
         return result;
     }
 
-    void* base_object = static_cast<void*>(instance->handle);
-    void* next_object = base_object;
-    VkLayerLinkedListElem* next_element;
-    PFN_vkGetInstanceProcAddr next_get_proc_addr = GetInstanceProcAddr_Bottom;
-    Vector<VkLayerLinkedListElem> elem_list(
-        CallbackAllocator<VkLayerLinkedListElem>(instance->alloc));
-    try {
-        elem_list.resize(instance->active_layers.size());
-    } catch (std::bad_alloc&) {
-        ALOGE("instance creation failed: out of memory");
-        DestroyInstance_Bottom(instance->handle, allocator);
-        return VK_ERROR_OUT_OF_HOST_MEMORY;
-    }
+    uint32_t activated_layers = 0;
+    VkLayerInstanceCreateInfo chain_info;
+    VkLayerInstanceLink* layer_instance_link_info = nullptr;
+    PFN_vkGetInstanceProcAddr next_gipa = GetInstanceProcAddr_Bottom;
+    VkInstance local_instance = nullptr;
 
-    for (size_t i = elem_list.size(); i > 0; i--) {
-        size_t idx = i - 1;
-        next_element = &elem_list[idx];
-        next_element->get_proc_addr =
-            reinterpret_cast<PFN_vkGetProcAddr>(next_get_proc_addr);
-        next_element->base_object = base_object;
-        next_element->next_element = next_object;
-        next_object = static_cast<void*>(next_element);
+    if (instance->active_layers.size() > 0) {
+        chain_info.u.pLayerInfo = nullptr;
+        chain_info.pNext = create_info->pNext;
+        chain_info.sType = VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO;
+        chain_info.function = VK_LAYER_FUNCTION_LINK;
+        local_create_info.pNext = &chain_info;
 
-        next_get_proc_addr =
-            instance->active_layers[idx].GetGetInstanceProcAddr();
-        if (!next_get_proc_addr) {
-            next_object = next_element->next_element;
-            next_get_proc_addr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(
-                next_element->get_proc_addr);
+        layer_instance_link_info = static_cast<VkLayerInstanceLink*>(alloca(
+            sizeof(VkLayerInstanceLink) * instance->active_layers.size()));
+        if (!layer_instance_link_info) {
+            ALOGE("Failed to alloc Instance objects for layers");
+            DestroyInstance_Bottom(instance->handle, allocator);
+            TeardownInstance(instance->handle, allocator);
+            return VK_ERROR_OUT_OF_HOST_MEMORY;
+        }
+
+        /* Create instance chain of enabled layers */
+        for (auto rit = instance->active_layers.rbegin();
+             rit != instance->active_layers.rend(); ++rit) {
+            LayerRef& layer = *rit;
+            layer_instance_link_info[activated_layers].pNext =
+                chain_info.u.pLayerInfo;
+            layer_instance_link_info[activated_layers]
+                .pfnNextGetInstanceProcAddr = next_gipa;
+            chain_info.u.pLayerInfo =
+                &layer_instance_link_info[activated_layers];
+            next_gipa = layer.GetGetInstanceProcAddr();
+
+            ALOGV("Insert instance layer %s (v%u)", layer.GetName(),
+                  layer.GetSpecVersion());
+
+            activated_layers++;
         }
     }
 
-    // This is the magic call that initializes all the layer instances and
-    // allows them to create their instance_handle -> instance_data mapping.
-    next_get_proc_addr(static_cast<VkInstance>(next_object),
-                       "vkGetInstanceProcAddr");
-
-    if (!LoadInstanceDispatchTable(static_cast<VkInstance>(base_object),
-                                   next_get_proc_addr, instance->dispatch)) {
+    PFN_vkCreateInstance create_instance =
+        reinterpret_cast<PFN_vkCreateInstance>(
+            next_gipa(VK_NULL_HANDLE, "vkCreateInstance"));
+    if (!create_instance) {
         DestroyInstance_Bottom(instance->handle, allocator);
+        TeardownInstance(instance->handle, allocator);
         return VK_ERROR_INITIALIZATION_FAILED;
     }
+    VkLayerInstanceCreateInfo instance_create_info;
+
+    instance_create_info.sType = VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO;
+    instance_create_info.function = VK_LAYER_FUNCTION_INSTANCE;
+
+    instance_create_info.u.instanceInfo.instance_info = instance;
+    instance_create_info.u.instanceInfo.pfnNextGetInstanceProcAddr = next_gipa;
+
+    instance_create_info.pNext = local_create_info.pNext;
+    local_create_info.pNext = &instance_create_info;
+
+    result = create_instance(&local_create_info, allocator, &local_instance);
+
+    if (result != VK_SUCCESS) {
+        DestroyInstance_Bottom(instance->handle, allocator);
+        TeardownInstance(instance->handle, allocator);
+        return result;
+    }
+
+    const InstanceDispatchTable& instance_dispatch =
+        GetDispatchTable(local_instance);
+    if (!LoadInstanceDispatchTable(
+            local_instance, next_gipa,
+            const_cast<InstanceDispatchTable&>(instance_dispatch))) {
+        ALOGV("Failed to initialize instance dispatch table");
+        PFN_vkDestroyInstance destroy_instance =
+            reinterpret_cast<PFN_vkDestroyInstance>(
+                next_gipa(VK_NULL_HANDLE, "vkDestroyInstance"));
+        if (!destroy_instance) {
+            ALOGD("Loader unable to find DestroyInstance");
+            return VK_ERROR_INITIALIZATION_FAILED;
+        }
+        destroy_instance(local_instance, allocator);
+        DestroyInstance_Bottom(instance->handle, allocator);
+        TeardownInstance(instance->handle, allocator);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    *instance_out = local_instance;
 
     // Force enable callback extension if required
     bool enable_callback = false;
@@ -1177,30 +1190,6 @@
         }
     }
 
-    VkInstance handle = instance->handle;
-    PFN_vkCreateInstance create_instance =
-        reinterpret_cast<PFN_vkCreateInstance>(
-            next_get_proc_addr(instance->handle, "vkCreateInstance"));
-    result = create_instance(create_info, allocator, &handle);
-    if (enable_callback)
-        FreeAllocatedCreateInfo(local_create_info, instance->alloc);
-    if (result >= 0) {
-        *instance_out = instance->handle;
-    } else {
-        // For every layer, including the loader top and bottom layers:
-        // - If a call to the next CreateInstance fails, the layer must clean
-        //   up anything it has successfully done so far, and propagate the
-        //   error upwards.
-        // - If a layer successfully calls the next layer's CreateInstance, and
-        //   afterwards must fail for some reason, it must call the next layer's
-        //   DestroyInstance before returning.
-        // - The layer must not call the next layer's DestroyInstance if that
-        //   layer's CreateInstance wasn't called, or returned failure.
-
-        // On failure, CreateInstance_Bottom frees the instance struct, so it's
-        // already gone at this point. Nothing to do.
-    }
-
     if (enable_logging) {
         const VkDebugReportCallbackCreateInfoEXT callback_create_info = {
             .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT,
@@ -1240,11 +1229,138 @@
     return GetLoaderExportProcAddr(name);
 }
 
-void DestroyInstance_Top(VkInstance instance,
+void DestroyInstance_Top(VkInstance vkinstance,
                          const VkAllocationCallbacks* allocator) {
-    if (!instance)
+    if (!vkinstance)
         return;
-    GetDispatchTable(instance).DestroyInstance(instance, allocator);
+    GetDispatchTable(vkinstance).DestroyInstance(vkinstance, allocator);
+
+    TeardownInstance(vkinstance, allocator);
+}
+
+VKAPI_ATTR
+VkResult CreateDevice_Top(VkPhysicalDevice gpu,
+                          const VkDeviceCreateInfo* create_info,
+                          const VkAllocationCallbacks* allocator,
+                          VkDevice* device_out) {
+    Instance& instance = GetDispatchParent(gpu);
+    VkResult result;
+
+    // FIXME(jessehall): We don't have good conventions or infrastructure yet to
+    // do better than just using the instance allocator and scope for
+    // everything. See b/26732122.
+    if (true /*!allocator*/)
+        allocator = instance.alloc;
+
+    void* mem = allocator->pfnAllocation(allocator->pUserData, sizeof(Device),
+                                         alignof(Device),
+                                         VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
+    if (!mem)
+        return VK_ERROR_OUT_OF_HOST_MEMORY;
+    Device* device = new (mem) Device(&instance);
+
+    result = ActivateAllLayers(create_info, &instance, device);
+    if (result != VK_SUCCESS) {
+        DestroyDevice(device);
+        return result;
+    }
+
+    size_t gpu_idx = 0;
+    while (instance.physical_devices[gpu_idx] != gpu)
+        gpu_idx++;
+
+    uint32_t activated_layers = 0;
+    VkLayerDeviceCreateInfo chain_info;
+    VkLayerDeviceLink* layer_device_link_info = nullptr;
+    PFN_vkGetInstanceProcAddr next_gipa = GetInstanceProcAddr_Bottom;
+    PFN_vkGetDeviceProcAddr next_gdpa = GetDeviceProcAddr_Bottom;
+    VkDeviceCreateInfo local_create_info = *create_info;
+    VkDevice local_device = nullptr;
+
+    if (device->active_layers.size() > 0) {
+        chain_info.u.pLayerInfo = nullptr;
+        chain_info.pNext = local_create_info.pNext;
+        chain_info.sType = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO;
+        chain_info.function = VK_LAYER_FUNCTION_LINK;
+        local_create_info.pNext = &chain_info;
+
+        layer_device_link_info = static_cast<VkLayerDeviceLink*>(
+            alloca(sizeof(VkLayerDeviceLink) * device->active_layers.size()));
+        if (!layer_device_link_info) {
+            ALOGE("Failed to alloc Device objects for layers");
+            DestroyDevice(device);
+            return VK_ERROR_OUT_OF_HOST_MEMORY;
+        }
+
+        /* Create device chain of enabled layers */
+        for (auto rit = device->active_layers.rbegin();
+             rit != device->active_layers.rend(); ++rit) {
+            LayerRef& layer = *rit;
+            layer_device_link_info[activated_layers].pNext =
+                chain_info.u.pLayerInfo;
+            layer_device_link_info[activated_layers].pfnNextGetDeviceProcAddr =
+                next_gdpa;
+            layer_device_link_info[activated_layers]
+                .pfnNextGetInstanceProcAddr = next_gipa;
+            chain_info.u.pLayerInfo = &layer_device_link_info[activated_layers];
+
+            next_gipa = layer.GetGetInstanceProcAddr();
+            next_gdpa = layer.GetGetDeviceProcAddr();
+
+            ALOGV("Insert device layer %s (v%u)", layer.GetName(),
+                  layer.GetSpecVersion());
+
+            activated_layers++;
+        }
+    }
+
+    PFN_vkCreateDevice create_device = reinterpret_cast<PFN_vkCreateDevice>(
+        next_gipa(VK_NULL_HANDLE, "vkCreateDevice"));
+    if (!create_device) {
+        ALOGE("Unable to find vkCreateDevice for driver");
+        DestroyDevice(device);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    VkLayerDeviceCreateInfo device_create_info;
+
+    device_create_info.sType = VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO;
+    device_create_info.function = VK_LAYER_FUNCTION_DEVICE;
+
+    device_create_info.u.deviceInfo.device_info = device;
+    device_create_info.u.deviceInfo.pfnNextGetInstanceProcAddr = next_gipa;
+
+    device_create_info.pNext = local_create_info.pNext;
+    local_create_info.pNext = &device_create_info;
+
+    result = create_device(gpu, &local_create_info, allocator, &local_device);
+
+    if (result != VK_SUCCESS) {
+        DestroyDevice(device);
+        return result;
+    }
+
+    // Set dispatch table for newly created Device
+    hwvulkan_dispatch_t* vulkan_dispatch =
+        reinterpret_cast<hwvulkan_dispatch_t*>(local_device);
+    vulkan_dispatch->vtbl = &device->dispatch;
+
+    const DeviceDispatchTable& device_dispatch = GetDispatchTable(local_device);
+    if (!LoadDeviceDispatchTable(
+            local_device, next_gdpa,
+            const_cast<DeviceDispatchTable&>(device_dispatch))) {
+        ALOGV("Failed to initialize device dispatch table");
+        PFN_vkDestroyDevice destroy_device =
+            reinterpret_cast<PFN_vkDestroyDevice>(
+                next_gipa(VK_NULL_HANDLE, "vkDestroyDevice"));
+        ALOG_ASSERT(destroy_device != nullptr,
+                    "Loader unable to find DestroyDevice");
+        destroy_device(local_device, allocator);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    *device_out = local_device;
+
+    return VK_SUCCESS;
 }
 
 PFN_vkVoidFunction GetDeviceProcAddr_Top(VkDevice device, const char* name) {
diff --git a/vulkan/libvulkan/loader.h b/vulkan/libvulkan/loader.h
index 3e2d1c4..8081c0e 100644
--- a/vulkan/libvulkan/loader.h
+++ b/vulkan/libvulkan/loader.h
@@ -94,6 +94,7 @@
 VKAPI_ATTR PFN_vkVoidFunction GetDeviceProcAddr_Top(VkDevice drv_device, const char* name);
 VKAPI_ATTR void GetDeviceQueue_Top(VkDevice drv_device, uint32_t family, uint32_t index, VkQueue* out_queue);
 VKAPI_ATTR VkResult AllocateCommandBuffers_Top(VkDevice device, const VkCommandBufferAllocateInfo* alloc_info, VkCommandBuffer* cmdbufs);
+VKAPI_ATTR VkResult CreateDevice_Top(VkPhysicalDevice pdev, const VkDeviceCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkDevice* device_out);
 VKAPI_ATTR void DestroyDevice_Top(VkDevice drv_device, const VkAllocationCallbacks* allocator);
 
 VKAPI_ATTR VkResult CreateInstance_Bottom(const VkInstanceCreateInfo* create_info, const VkAllocationCallbacks* allocator, VkInstance* vkinstance);
@@ -150,6 +151,9 @@
     LayerRef(const LayerRef&) = delete;
     LayerRef& operator=(const LayerRef&) = delete;
 
+    const char* GetName();
+    uint32_t GetSpecVersion();
+
     // provides bool-like behavior
     operator const Layer*() const { return layer_; }
 
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index bab5a59..f8de675 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -238,18 +238,20 @@
         return VK_ERROR_INITIALIZATION_FAILED;
     }
 
-    capabilities->currentExtent =
-        VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
-
     // TODO(jessehall): Figure out what the min/max values should be.
     capabilities->minImageCount = 2;
     capabilities->maxImageCount = 3;
 
+    capabilities->currentExtent =
+        VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+
     // TODO(jessehall): Figure out what the max extent should be. Maximum
     // texture dimension maybe?
     capabilities->minImageExtent = VkExtent2D{1, 1};
     capabilities->maxImageExtent = VkExtent2D{4096, 4096};
 
+    capabilities->maxImageArrayLayers = 1;
+
     // TODO(jessehall): We can support all transforms, fix this once
     // implemented.
     capabilities->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
@@ -257,7 +259,9 @@
     // TODO(jessehall): Implement based on NATIVE_WINDOW_TRANSFORM_HINT.
     capabilities->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
 
-    capabilities->maxImageArrayLayers = 1;
+    // On Android, window composition is a WindowManager property, not something
+    // associated with the bufferqueue. It can't be changed from here.
+    capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
 
     // TODO(jessehall): I think these are right, but haven't thought hard about
     // it. Do we need to query the driver for support of any of these?
@@ -289,6 +293,7 @@
     const VkSurfaceFormatKHR kFormats[] = {
         {VK_FORMAT_R8G8B8A8_UNORM, VK_COLORSPACE_SRGB_NONLINEAR_KHR},
         {VK_FORMAT_R8G8B8A8_SRGB, VK_COLORSPACE_SRGB_NONLINEAR_KHR},
+        {VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLORSPACE_SRGB_NONLINEAR_KHR},
     };
     const uint32_t kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]);
 
@@ -338,22 +343,53 @@
              "Swapchain imageArrayLayers (%u) != 1 not supported",
              create_info->imageArrayLayers);
 
-    ALOGE_IF(create_info->imageFormat != VK_FORMAT_R8G8B8A8_UNORM,
-             "swapchain formats other than R8G8B8A8_UNORM not yet implemented");
     ALOGE_IF(create_info->imageColorSpace != VK_COLORSPACE_SRGB_NONLINEAR_KHR,
              "color spaces other than SRGB_NONLINEAR not yet implemented");
     ALOGE_IF(create_info->oldSwapchain,
              "swapchain re-creation not yet implemented");
     ALOGE_IF(create_info->preTransform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
              "swapchain preTransform not yet implemented");
-    ALOGE_IF(create_info->presentMode != VK_PRESENT_MODE_FIFO_KHR,
-             "present modes other than FIFO are not yet implemented");
+    ALOGW_IF(!(create_info->presentMode == VK_PRESENT_MODE_FIFO_KHR ||
+               create_info->presentMode == VK_PRESENT_MODE_MAILBOX_KHR),
+             "swapchain present mode %d not supported",
+             create_info->presentMode);
 
     // -- Configure the native window --
 
     Surface& surface = *SurfaceFromHandle(create_info->surface);
     const DriverDispatchTable& dispatch = GetDriverDispatch(device);
 
+    int native_format = HAL_PIXEL_FORMAT_RGBA_8888;
+    switch (create_info->imageFormat) {
+        case VK_FORMAT_R8G8B8A8_UNORM:
+        case VK_FORMAT_R8G8B8A8_SRGB:
+            native_format = HAL_PIXEL_FORMAT_RGBA_8888;
+            break;
+        case VK_FORMAT_R5G6B5_UNORM_PACK16:
+            native_format = HAL_PIXEL_FORMAT_RGB_565;
+            break;
+        default:
+            ALOGE("unsupported swapchain format %d", create_info->imageFormat);
+            break;
+    }
+    err = native_window_set_buffers_format(surface.window.get(), native_format);
+    if (err != 0) {
+        // TODO(jessehall): Improve error reporting. Can we enumerate possible
+        // errors and translate them to valid Vulkan result codes?
+        ALOGE("native_window_set_buffers_format(%d) failed: %s (%d)",
+              native_format, strerror(-err), err);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    err = native_window_set_buffers_data_space(surface.window.get(),
+                                               HAL_DATASPACE_SRGB_LINEAR);
+    if (err != 0) {
+        // TODO(jessehall): Improve error reporting. Can we enumerate possible
+        // errors and translate them to valid Vulkan result codes?
+        ALOGE("native_window_set_buffers_data_space(%d) failed: %s (%d)",
+              HAL_DATASPACE_SRGB_LINEAR, strerror(-err), err);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
     err = native_window_set_buffers_dimensions(
         surface.window.get(), static_cast<int>(create_info->imageExtent.width),
         static_cast<int>(create_info->imageExtent.height));
@@ -386,6 +422,13 @@
         ALOGE("window->query failed: %s (%d)", strerror(-err), err);
         return VK_ERROR_INITIALIZATION_FAILED;
     }
+    // The MIN_UNDEQUEUED_BUFFERS query doesn't know whether we'll be using
+    // async mode or not, and assumes not. But in async mode, the BufferQueue
+    // requires an extra undequeued buffer.
+    // See BufferQueueCore::getMinUndequeuedBufferCountLocked().
+    if (create_info->presentMode == VK_PRESENT_MODE_MAILBOX_KHR)
+        min_undequeued_buffers += 1;
+
     uint32_t num_images =
         (create_info->minImageCount - 1) + min_undequeued_buffers;
     err = native_window_set_buffer_count(surface.window.get(), num_images);
@@ -418,6 +461,17 @@
         return VK_ERROR_INITIALIZATION_FAILED;
     }
 
+    err = surface.window->setSwapInterval(
+        surface.window.get(),
+        create_info->presentMode == VK_PRESENT_MODE_MAILBOX_KHR ? 0 : 1);
+    if (err != 0) {
+        // TODO(jessehall): Improve error reporting. Can we enumerate possible
+        // errors and translate them to valid Vulkan result codes?
+        ALOGE("native_window->setSwapInterval failed: %s (%d)", strerror(-err),
+              err);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
     // -- Allocate our Swapchain object --
     // After this point, we must deallocate the swapchain on error.
 
@@ -442,7 +496,7 @@
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = &image_native_buffer,
         .imageType = VK_IMAGE_TYPE_2D,
-        .format = VK_FORMAT_R8G8B8A8_UNORM,  // TODO(jessehall)
+        .format = create_info->imageFormat,
         .extent = {0, 0, 1},
         .mipLevels = 1,
         .arrayLayers = 1,
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 8f47bd1..cd61e86 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -89,7 +89,7 @@
 };
 }  // namespace HandleType
 
-const VkDeviceSize kMaxDeviceMemory = VkDeviceSize(INTPTR_MAX) + 1;
+const VkDeviceSize kMaxDeviceMemory = 0x10000000;  // 256 MiB, arbitrary
 
 }  // anonymous namespace
 
@@ -346,6 +346,121 @@
     strcpy(properties->deviceName, "Android Vulkan Null Driver");
     memset(properties->pipelineCacheUUID, 0,
            sizeof(properties->pipelineCacheUUID));
+    properties->limits = VkPhysicalDeviceLimits{
+        4096,     // maxImageDimension1D
+        4096,     // maxImageDimension2D
+        256,      // maxImageDimension3D
+        4096,     // maxImageDimensionCube
+        256,      // maxImageArrayLayers
+        65536,    // maxTexelBufferElements
+        16384,    // maxUniformBufferRange
+        1 << 27,  // maxStorageBufferRange
+        128,      // maxPushConstantsSize
+        4096,     // maxMemoryAllocationCount
+        4000,     // maxSamplerAllocationCount
+        1,        // bufferImageGranularity
+        0,        // sparseAddressSpaceSize
+        4,        // maxBoundDescriptorSets
+        16,       // maxPerStageDescriptorSamplers
+        12,       // maxPerStageDescriptorUniformBuffers
+        4,        // maxPerStageDescriptorStorageBuffers
+        16,       // maxPerStageDescriptorSampledImages
+        4,        // maxPerStageDescriptorStorageImages
+        4,        // maxPerStageDescriptorInputAttachments
+        128,      // maxPerStageResources
+        96,       // maxDescriptorSetSamplers
+        72,       // maxDescriptorSetUniformBuffers
+        8,        // maxDescriptorSetUniformBuffersDynamic
+        24,       // maxDescriptorSetStorageBuffers
+        4,        // maxDescriptorSetStorageBuffersDynamic
+        96,       // maxDescriptorSetSampledImages
+        24,       // maxDescriptorSetStorageImages
+        4,        // maxDescriptorSetInputAttachments
+        16,       // maxVertexInputAttributes
+        16,       // maxVertexInputBindings
+        2047,     // maxVertexInputAttributeOffset
+        2048,     // maxVertexInputBindingStride
+        64,       // maxVertexOutputComponents
+        0,        // maxTessellationGenerationLevel
+        0,        // maxTessellationPatchSize
+        0,        // maxTessellationControlPerVertexInputComponents
+        0,        // maxTessellationControlPerVertexOutputComponents
+        0,        // maxTessellationControlPerPatchOutputComponents
+        0,        // maxTessellationControlTotalOutputComponents
+        0,        // maxTessellationEvaluationInputComponents
+        0,        // maxTessellationEvaluationOutputComponents
+        0,        // maxGeometryShaderInvocations
+        0,        // maxGeometryInputComponents
+        0,        // maxGeometryOutputComponents
+        0,        // maxGeometryOutputVertices
+        0,        // maxGeometryTotalOutputComponents
+        64,       // maxFragmentInputComponents
+        4,        // maxFragmentOutputAttachments
+        0,        // maxFragmentDualSrcAttachments
+        4,        // maxFragmentCombinedOutputResources
+        16384,    // maxComputeSharedMemorySize
+        {65536, 65536, 65536},  // maxComputeWorkGroupCount[3]
+        128,                    // maxComputeWorkGroupInvocations
+        {128, 128, 64},         // maxComputeWorkGroupSize[3]
+        4,                      // subPixelPrecisionBits
+        4,                      // subTexelPrecisionBits
+        4,                      // mipmapPrecisionBits
+        UINT32_MAX,             // maxDrawIndexedIndexValue
+        1,                      // maxDrawIndirectCount
+        2,                      // maxSamplerLodBias
+        1,                      // maxSamplerAnisotropy
+        1,                      // maxViewports
+        {4096, 4096},           // maxViewportDimensions[2]
+        {-8192.0f, 8191.0f},    // viewportBoundsRange[2]
+        0,                      // viewportSubPixelBits
+        64,                     // minMemoryMapAlignment
+        256,                    // minTexelBufferOffsetAlignment
+        256,                    // minUniformBufferOffsetAlignment
+        256,                    // minStorageBufferOffsetAlignment
+        -8,                     // minTexelOffset
+        7,                      // maxTexelOffset
+        0,                      // minTexelGatherOffset
+        0,                      // maxTexelGatherOffset
+        0.0f,                   // minInterpolationOffset
+        0.0f,                   // maxInterpolationOffset
+        0,                      // subPixelInterpolationOffsetBits
+        4096,                   // maxFramebufferWidth
+        4096,                   // maxFramebufferHeight
+        256,                    // maxFramebufferLayers
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // framebufferColorSampleCounts
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // framebufferDepthSampleCounts
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // framebufferStencilSampleCounts
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // framebufferNoAttachmentsSampleCounts
+        4,                          // maxColorAttachments
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // sampledImageColorSampleCounts
+        VK_SAMPLE_COUNT_1_BIT,      // sampledImageIntegerSampleCounts
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // sampledImageDepthSampleCounts
+        VK_SAMPLE_COUNT_1_BIT |
+            VK_SAMPLE_COUNT_4_BIT,  // sampledImageStencilSampleCounts
+        VK_SAMPLE_COUNT_1_BIT,      // storageImageSampleCounts
+        1,                          // maxSampleMaskWords
+        VK_TRUE,                    // timestampComputeAndGraphics
+        1,                          // timestampPeriod
+        0,                          // maxClipDistances
+        0,                          // maxCullDistances
+        0,                          // maxCombinedClipAndCullDistances
+        2,                          // discreteQueuePriorities
+        {1.0f, 1.0f},               // pointSizeRange[2]
+        {1.0f, 1.0f},               // lineWidthRange[2]
+        0.0f,                       // pointSizeGranularity
+        0.0f,                       // lineWidthGranularity
+        VK_TRUE,                    // strictLines
+        VK_TRUE,                    // standardSampleLocations
+        1,                          // optimalBufferCopyOffsetAlignment
+        1,                          // optimalBufferCopyRowPitchAlignment
+        64,                         // nonCoherentAtomSize
+    };
 }
 
 void GetPhysicalDeviceQueueFamilyProperties(
diff --git a/vulkan/tools/vkinfo.cpp b/vulkan/tools/vkinfo.cpp
index 6a63667..42bdb9d 100644
--- a/vulkan/tools/vkinfo.cpp
+++ b/vulkan/tools/vkinfo.cpp
@@ -161,10 +161,12 @@
     }
 
     VkDevice device;
+    float queue_priorities[] = {0.0};
     const VkDeviceQueueCreateInfo queue_create_info = {
         .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
         .queueFamilyIndex = 0,
         .queueCount = 1,
+        queue_priorities
     };
     const VkDeviceCreateInfo create_info = {
         .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
@@ -247,6 +249,25 @@
 
 // ----------------------------------------------------------------------------
 
+struct Options {
+    bool layer_description;
+    bool layer_extensions;
+};
+
+const size_t kMaxIndent = 8;
+const size_t kIndentSize = 3;
+std::array<char, kMaxIndent * kIndentSize + 1> kIndent;
+const char* Indent(size_t n) {
+    static bool initialized = false;
+    if (!initialized) {
+        kIndent.fill(' ');
+        kIndent.back() = '\0';
+        initialized = true;
+    }
+    return kIndent.data() +
+           (kIndent.size() - (kIndentSize * std::min(n, kMaxIndent) + 1));
+}
+
 uint32_t ExtractMajorVersion(uint32_t version) {
     return (version >> 22) & 0x3FF;
 }
@@ -274,51 +295,41 @@
     }
 }
 
-const char* VkQueueFlagBitStr(VkQueueFlagBits bit) {
-    switch (bit) {
-        case VK_QUEUE_GRAPHICS_BIT:
-            return "GRAPHICS";
-        case VK_QUEUE_COMPUTE_BIT:
-            return "COMPUTE";
-        case VK_QUEUE_TRANSFER_BIT:
-            return "TRANSFER";
-        case VK_QUEUE_SPARSE_BINDING_BIT:
-            return "SPARSE";
-    }
-}
-
 void PrintExtensions(const std::vector<VkExtensionProperties>& extensions,
-                     const char* prefix) {
+                     const Options& /*options*/,
+                     size_t indent) {
     for (const auto& e : extensions)
-        printf("%s%s (v%u)\n", prefix, e.extensionName, e.specVersion);
+        printf("%s%s (v%u)\n", Indent(indent), e.extensionName, e.specVersion);
 }
 
 void PrintLayers(
     const std::vector<VkLayerProperties>& layers,
     const std::vector<std::vector<VkExtensionProperties>> extensions,
-    const char* prefix) {
-    std::string ext_prefix(prefix);
-    ext_prefix.append("    ");
+    const Options& options,
+    size_t indent) {
     for (size_t i = 0; i < layers.size(); i++) {
-        printf(
-            "%s%s %u.%u.%u/%u\n"
-            "%s  %s\n",
-            prefix, layers[i].layerName,
-            ExtractMajorVersion(layers[i].specVersion),
-            ExtractMinorVersion(layers[i].specVersion),
-            ExtractPatchVersion(layers[i].specVersion),
-            layers[i].implementationVersion, prefix, layers[i].description);
-        if (!extensions[i].empty())
-            printf("%s  Extensions [%zu]:\n", prefix, extensions[i].size());
-        PrintExtensions(extensions[i], ext_prefix.c_str());
+        printf("%s%s %u.%u.%u/%u\n", Indent(indent), layers[i].layerName,
+               ExtractMajorVersion(layers[i].specVersion),
+               ExtractMinorVersion(layers[i].specVersion),
+               ExtractPatchVersion(layers[i].specVersion),
+               layers[i].implementationVersion);
+        if (options.layer_description)
+            printf("%s%s\n", Indent(indent + 1), layers[i].description);
+        if (options.layer_extensions && !extensions[i].empty()) {
+            if (!extensions[i].empty()) {
+                printf("%sExtensions [%zu]:\n", Indent(indent + 1),
+                       extensions[i].size());
+                PrintExtensions(extensions[i], options, indent + 2);
+            }
+        }
     }
 }
 
-void PrintGpuInfo(const GpuInfo& info) {
+void PrintGpuInfo(const GpuInfo& info, const Options& options, size_t indent) {
     VkResult result;
     std::ostringstream strbuf;
 
-    printf("  \"%s\" (%s) %u.%u.%u/%#x [%04x:%04x]\n",
+    printf("%s\"%s\" (%s) %u.%u.%u/%#x [%04x:%04x]\n", Indent(indent),
            info.properties.deviceName,
            VkPhysicalDeviceTypeStr(info.properties.deviceType),
            ExtractMajorVersion(info.properties.apiVersion),
@@ -331,8 +342,9 @@
         if ((info.memory.memoryHeaps[heap].flags &
              VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0)
             strbuf << "DEVICE_LOCAL";
-        printf("    Heap %u: %" PRIu64 " MiB (0x%" PRIx64 " B) %s\n", heap,
-               info.memory.memoryHeaps[heap].size / 0x1000000,
+        printf("%sHeap %u: %" PRIu64 " MiB (0x%" PRIx64 " B) %s\n",
+               Indent(indent + 1), heap,
+               info.memory.memoryHeaps[heap].size / 0x100000,
                info.memory.memoryHeaps[heap].size, strbuf.str().c_str());
         strbuf.str(std::string());
 
@@ -351,7 +363,8 @@
                 strbuf << " CACHED";
             if ((flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) != 0)
                 strbuf << " LAZILY_ALLOCATED";
-            printf("      Type %u:%s\n", type, strbuf.str().c_str());
+            printf("%sType %u:%s\n", Indent(indent + 2), type,
+                   strbuf.str().c_str());
             strbuf.str(std::string());
         }
     }
@@ -366,47 +379,120 @@
         flags_str[3] = (flags & VK_QUEUE_SPARSE_BINDING_BIT) ? 'S' : '_';
         flags_str[4] = '\0';
         printf(
-            "    Queue Family %u: %ux %s\n"
-            "      timestampValidBits: %ub\n"
-            "      minImageTransferGranularity: (%u,%u,%u)\n",
-            family, qprops.queueCount, flags_str, qprops.timestampValidBits,
+            "%sQueue Family %u: %ux %s\n"
+            "%stimestampValidBits: %ub\n"
+            "%sminImageTransferGranularity: (%u,%u,%u)\n",
+            Indent(indent + 1), family, qprops.queueCount, flags_str,
+            Indent(indent + 2), qprops.timestampValidBits, Indent(indent + 2),
             qprops.minImageTransferGranularity.width,
             qprops.minImageTransferGranularity.height,
             qprops.minImageTransferGranularity.depth);
     }
 
-    if (!info.extensions.empty()) {
-        printf("    Extensions [%zu]:\n", info.extensions.size());
-        PrintExtensions(info.extensions, "      ");
-    }
-    if (!info.layers.empty()) {
-        printf("    Layers [%zu]:\n", info.layers.size());
-        PrintLayers(info.layers, info.layer_extensions, "      ");
-    }
+    // clang-format off
+    printf("%sFeatures:\n", Indent(indent + 1));
+    printf("%srobustBufferAccess: %s\n", Indent(indent + 2), info.features.robustBufferAccess ? "YES" : "NO");
+    printf("%sfullDrawIndexUint32: %s\n", Indent(indent + 2), info.features.fullDrawIndexUint32 ? "YES" : "NO");
+    printf("%simageCubeArray: %s\n", Indent(indent + 2), info.features.imageCubeArray ? "YES" : "NO");
+    printf("%sindependentBlend: %s\n", Indent(indent + 2), info.features.independentBlend ? "YES" : "NO");
+    printf("%sgeometryShader: %s\n", Indent(indent + 2), info.features.geometryShader ? "YES" : "NO");
+    printf("%stessellationShader: %s\n", Indent(indent + 2), info.features.tessellationShader ? "YES" : "NO");
+    printf("%ssampleRateShading: %s\n", Indent(indent + 2), info.features.sampleRateShading ? "YES" : "NO");
+    printf("%sdualSrcBlend: %s\n", Indent(indent + 2), info.features.dualSrcBlend ? "YES" : "NO");
+    printf("%slogicOp: %s\n", Indent(indent + 2), info.features.logicOp ? "YES" : "NO");
+    printf("%smultiDrawIndirect: %s\n", Indent(indent + 2), info.features.multiDrawIndirect ? "YES" : "NO");
+    printf("%sdrawIndirectFirstInstance: %s\n", Indent(indent + 2), info.features.drawIndirectFirstInstance ? "YES" : "NO");
+    printf("%sdepthClamp: %s\n", Indent(indent + 2), info.features.depthClamp ? "YES" : "NO");
+    printf("%sdepthBiasClamp: %s\n", Indent(indent + 2), info.features.depthBiasClamp ? "YES" : "NO");
+    printf("%sfillModeNonSolid: %s\n", Indent(indent + 2), info.features.fillModeNonSolid ? "YES" : "NO");
+    printf("%sdepthBounds: %s\n", Indent(indent + 2), info.features.depthBounds ? "YES" : "NO");
+    printf("%swideLines: %s\n", Indent(indent + 2), info.features.wideLines ? "YES" : "NO");
+    printf("%slargePoints: %s\n", Indent(indent + 2), info.features.largePoints ? "YES" : "NO");
+    printf("%salphaToOne: %s\n", Indent(indent + 2), info.features.alphaToOne ? "YES" : "NO");
+    printf("%smultiViewport: %s\n", Indent(indent + 2), info.features.multiViewport ? "YES" : "NO");
+    printf("%ssamplerAnisotropy: %s\n", Indent(indent + 2), info.features.samplerAnisotropy ? "YES" : "NO");
+    printf("%stextureCompressionETC2: %s\n", Indent(indent + 2), info.features.textureCompressionETC2 ? "YES" : "NO");
+    printf("%stextureCompressionASTC_LDR: %s\n", Indent(indent + 2), info.features.textureCompressionASTC_LDR ? "YES" : "NO");
+    printf("%stextureCompressionBC: %s\n", Indent(indent + 2), info.features.textureCompressionBC ? "YES" : "NO");
+    printf("%socclusionQueryPrecise: %s\n", Indent(indent + 2), info.features.occlusionQueryPrecise ? "YES" : "NO");
+    printf("%spipelineStatisticsQuery: %s\n", Indent(indent + 2), info.features.pipelineStatisticsQuery ? "YES" : "NO");
+    printf("%svertexPipelineStoresAndAtomics: %s\n", Indent(indent + 2), info.features.vertexPipelineStoresAndAtomics ? "YES" : "NO");
+    printf("%sfragmentStoresAndAtomics: %s\n", Indent(indent + 2), info.features.fragmentStoresAndAtomics ? "YES" : "NO");
+    printf("%sshaderTessellationAndGeometryPointSize: %s\n", Indent(indent + 2), info.features.shaderTessellationAndGeometryPointSize ? "YES" : "NO");
+    printf("%sshaderImageGatherExtended: %s\n", Indent(indent + 2), info.features.shaderImageGatherExtended ? "YES" : "NO");
+    printf("%sshaderStorageImageExtendedFormats: %s\n", Indent(indent + 2), info.features.shaderStorageImageExtendedFormats ? "YES" : "NO");
+    printf("%sshaderStorageImageMultisample: %s\n", Indent(indent + 2), info.features.shaderStorageImageMultisample ? "YES" : "NO");
+    printf("%sshaderStorageImageReadWithoutFormat: %s\n", Indent(indent + 2), info.features.shaderStorageImageReadWithoutFormat ? "YES" : "NO");
+    printf("%sshaderStorageImageWriteWithoutFormat: %s\n", Indent(indent + 2), info.features.shaderStorageImageWriteWithoutFormat ? "YES" : "NO");
+    printf("%sshaderUniformBufferArrayDynamicIndexing: %s\n", Indent(indent + 2), info.features.shaderUniformBufferArrayDynamicIndexing ? "YES" : "NO");
+    printf("%sshaderSampledImageArrayDynamicIndexing: %s\n", Indent(indent + 2), info.features.shaderSampledImageArrayDynamicIndexing ? "YES" : "NO");
+    printf("%sshaderStorageBufferArrayDynamicIndexing: %s\n", Indent(indent + 2), info.features.shaderStorageBufferArrayDynamicIndexing ? "YES" : "NO");
+    printf("%sshaderStorageImageArrayDynamicIndexing: %s\n", Indent(indent + 2), info.features.shaderStorageImageArrayDynamicIndexing ? "YES" : "NO");
+    printf("%sshaderClipDistance: %s\n", Indent(indent + 2), info.features.shaderClipDistance ? "YES" : "NO");
+    printf("%sshaderCullDistance: %s\n", Indent(indent + 2), info.features.shaderCullDistance ? "YES" : "NO");
+    printf("%sshaderFloat64: %s\n", Indent(indent + 2), info.features.shaderFloat64 ? "YES" : "NO");
+    printf("%sshaderInt64: %s\n", Indent(indent + 2), info.features.shaderInt64 ? "YES" : "NO");
+    printf("%sshaderInt16: %s\n", Indent(indent + 2), info.features.shaderInt16 ? "YES" : "NO");
+    printf("%sshaderResourceResidency: %s\n", Indent(indent + 2), info.features.shaderResourceResidency ? "YES" : "NO");
+    printf("%sshaderResourceMinLod: %s\n", Indent(indent + 2), info.features.shaderResourceMinLod ? "YES" : "NO");
+    printf("%ssparseBinding: %s\n", Indent(indent + 2), info.features.sparseBinding ? "YES" : "NO");
+    printf("%ssparseResidencyBuffer: %s\n", Indent(indent + 2), info.features.sparseResidencyBuffer ? "YES" : "NO");
+    printf("%ssparseResidencyImage2D: %s\n", Indent(indent + 2), info.features.sparseResidencyImage2D ? "YES" : "NO");
+    printf("%ssparseResidencyImage3D: %s\n", Indent(indent + 2), info.features.sparseResidencyImage3D ? "YES" : "NO");
+    printf("%ssparseResidency2Samples: %s\n", Indent(indent + 2), info.features.sparseResidency2Samples ? "YES" : "NO");
+    printf("%ssparseResidency4Samples: %s\n", Indent(indent + 2), info.features.sparseResidency4Samples ? "YES" : "NO");
+    printf("%ssparseResidency8Samples: %s\n", Indent(indent + 2), info.features.sparseResidency8Samples ? "YES" : "NO");
+    printf("%ssparseResidency16Samples: %s\n", Indent(indent + 2), info.features.sparseResidency16Samples ? "YES" : "NO");
+    printf("%ssparseResidencyAliased: %s\n", Indent(indent + 2), info.features.sparseResidencyAliased ? "YES" : "NO");
+    printf("%svariableMultisampleRate: %s\n", Indent(indent + 2), info.features.variableMultisampleRate ? "YES" : "NO");
+    printf("%sinheritedQueries: %s\n", Indent(indent + 2), info.features.inheritedQueries ? "YES" : "NO");
+    // clang-format on
+
+    printf("%sExtensions [%zu]:\n", Indent(indent + 1), info.extensions.size());
+    if (!info.extensions.empty())
+        PrintExtensions(info.extensions, options, indent + 2);
+    printf("%sLayers [%zu]:\n", Indent(indent + 1), info.layers.size());
+    if (!info.layers.empty())
+        PrintLayers(info.layers, info.layer_extensions, options, indent + 2);
 }
 
-void PrintInfo(const VulkanInfo& info) {
+void PrintInfo(const VulkanInfo& info, const Options& options) {
     std::ostringstream strbuf;
+    size_t indent = 0;
 
-    printf("Instance Extensions [%zu]:\n", info.extensions.size());
-    PrintExtensions(info.extensions, "  ");
-    if (!info.layers.empty()) {
-        printf("Instance Layers [%zu]:\n", info.layers.size());
-        PrintLayers(info.layers, info.layer_extensions, "  ");
-    }
+    printf("%sInstance Extensions [%zu]:\n", Indent(indent),
+           info.extensions.size());
+    PrintExtensions(info.extensions, options, indent + 1);
+    printf("%sInstance Layers [%zu]:\n", Indent(indent), info.layers.size());
+    if (!info.layers.empty())
+        PrintLayers(info.layers, info.layer_extensions, options, indent + 1);
 
-    printf("PhysicalDevices [%zu]:\n", info.gpus.size());
+    printf("%sPhysicalDevices [%zu]:\n", Indent(indent), info.gpus.size());
     for (const auto& gpu : info.gpus)
-        PrintGpuInfo(gpu);
+        PrintGpuInfo(gpu, options, indent + 1);
 }
 
 }  // namespace
 
 // ----------------------------------------------------------------------------
 
-int main(int /*argc*/, char const* /*argv*/ []) {
+int main(int argc, char const* argv[]) {
+    Options options = {
+        .layer_description = false, .layer_extensions = false,
+    };
+    for (int argi = 1; argi < argc; argi++) {
+        if (strcmp(argv[argi], "-v") == 0) {
+            options.layer_description = true;
+            options.layer_extensions = true;
+        } else if (strcmp(argv[argi], "-layer_description") == 0) {
+            options.layer_description = true;
+        } else if (strcmp(argv[argi], "-layer_extensions") == 0) {
+            options.layer_extensions = true;
+        }
+    }
+
     VulkanInfo info;
     GatherInfo(&info);
-    PrintInfo(info);
+    PrintInfo(info, options);
     return 0;
 }