Merge "lshal: allow 10s for passthroughManager()->debugDump" into pi-dev
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 21d9ace..f65f4f8 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -105,7 +105,6 @@
     { "video",      "Video",            ATRACE_TAG_VIDEO, { } },
     { "camera",     "Camera",           ATRACE_TAG_CAMERA, { } },
     { "hal",        "Hardware Modules", ATRACE_TAG_HAL, { } },
-    { "app",        "Application",      ATRACE_TAG_APP, { } },
     { "res",        "Resource Loading", ATRACE_TAG_RESOURCES, { } },
     { "dalvik",     "Dalvik VM",        ATRACE_TAG_DALVIK, { } },
     { "rs",         "RenderScript",     ATRACE_TAG_RS, { } },
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index bbff6fb..bcdd03e 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -33,7 +33,7 @@
 #define TEST_APP_PRIVATE_DIR "/data/app-private/"
 #define TEST_APP_EPHEMERAL_DIR "/data/app-ephemeral/"
 #define TEST_ASEC_DIR "/mnt/asec/"
-#define TEST_EXPAND_DIR "/mnt/expand/"
+#define TEST_EXPAND_DIR "/mnt/expand/00000000-0000-0000-0000-000000000000/"
 
 #define TEST_SYSTEM_DIR1 "/system/app/"
 #define TEST_SYSTEM_DIR2 "/vendor/app/"
@@ -116,6 +116,41 @@
             << bad_path5 << " should be rejected as a invalid path";
 }
 
+TEST_F(UtilsTest, IsValidApkPath_TopDir) {
+    EXPECT_EQ(0, validate_apk_path(TEST_DATA_DIR "app/com.example"));
+    EXPECT_EQ(0, validate_apk_path(TEST_EXPAND_DIR "app/com.example"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_DATA_DIR "data/com.example"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_EXPAND_DIR "data/com.example"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_TopFile) {
+    EXPECT_EQ(0, validate_apk_path(TEST_DATA_DIR "app/com.example/base.apk"));
+    EXPECT_EQ(0, validate_apk_path(TEST_EXPAND_DIR "app/com.example/base.apk"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_DATA_DIR "data/com.example/base.apk"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_EXPAND_DIR "data/com.example/base.apk"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDir) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDirDir) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat/arm64"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat/arm64"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat/arm64"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat/arm64"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDirDirFile) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat/arm64/base.odex"));
+}
+
 TEST_F(UtilsTest, IsValidApkPath_Private) {
     // Internal directories
     const char *private1 = TEST_APP_PRIVATE_DIR "example.apk";
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index a8c32ed..1ff45e4 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -855,21 +855,25 @@
  * that path. Returns -1 when an invalid path is encountered and 0 when a valid path
  * is encountered.
  */
-static int validate_apk_path_internal(const char *path, int maxSubdirs) {
-    std::string path_ = path;
-    if (validate_path(android_app_dir, path_, maxSubdirs) == 0) {
+static int validate_apk_path_internal(const std::string& path, int maxSubdirs) {
+    if (validate_path(android_app_dir, path, maxSubdirs) == 0) {
         return 0;
-    } else if (validate_path(android_app_private_dir, path_, maxSubdirs) == 0) {
+    } else if (validate_path(android_app_private_dir, path, maxSubdirs) == 0) {
         return 0;
-    } else if (validate_path(android_app_ephemeral_dir, path_, maxSubdirs) == 0) {
+    } else if (validate_path(android_app_ephemeral_dir, path, maxSubdirs) == 0) {
         return 0;
-    } else if (validate_path(android_asec_dir, path_, maxSubdirs) == 0) {
+    } else if (validate_path(android_asec_dir, path, maxSubdirs) == 0) {
         return 0;
-    } else if (validate_path(android_mnt_expand_dir, path_, std::max(maxSubdirs, 2)) == 0) {
-        return 0;
-    } else {
-        return -1;
+    } else if (android::base::StartsWith(path, android_mnt_expand_dir)) {
+        // Rewrite the path as if it were on internal storage, and test that
+        size_t end = path.find('/', android_mnt_expand_dir.size() + 1);
+        if (end != std::string::npos) {
+            auto modified = path;
+            modified.replace(0, end + 1, android_data_dir);
+            return validate_apk_path_internal(modified, maxSubdirs);
+        }
     }
+    return -1;
 }
 
 int validate_apk_path(const char* path) {
diff --git a/data/etc/go_handheld_core_hardware.xml b/data/etc/go_handheld_core_hardware.xml
new file mode 100644
index 0000000..ffebc9f
--- /dev/null
+++ b/data/etc/go_handheld_core_hardware.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- These are the hardware components that all Android Go handheld devices
+     must include. Devices with optional hardware must also include extra
+     hardware files, per the comments below.
+-->
+<permissions>
+    <!-- This is Android and fully CTS compatible.  Basically this is for CTS tests to use. -->
+    <feature name="android.software.cts" />
+
+    <feature name="android.hardware.audio.output" />
+    <feature name="android.hardware.camera" />
+    <feature name="android.hardware.location" />
+    <feature name="android.hardware.location.network" />
+    <feature name="android.hardware.sensor.compass" />
+    <feature name="android.hardware.sensor.accelerometer" />
+    <feature name="android.hardware.bluetooth" />
+    <feature name="android.hardware.touchscreen" />
+    <feature name="android.hardware.microphone" />
+    <feature name="android.hardware.screen.portrait" />
+    <feature name="android.hardware.screen.landscape" />
+
+    <!-- basic system services -->
+    <feature name="android.software.connectionservice" />
+    <feature name="android.software.backup" />
+    <feature name="android.software.home_screen" />
+    <feature name="android.software.input_methods" />
+    <feature name="android.software.print" />
+    <feature name="android.software.companion_device_setup" />
+    <feature name="android.software.autofill" />
+
+    <!-- Feature to specify if the device supports adding device admins. -->
+    <feature name="android.software.device_admin" />
+
+    <!-- Devices with all optimizations required to support VR Mode and
+         pass all CDD requirements for this feature may include
+         android.hardware.vr.high_performance -->
+    <!-- Devices that support VR headtracking features and pass all CDD
+         requirements may include
+         android.hardware.vr.headtracking -->
+
+    <!-- devices with GPS must include android.hardware.location.gps.xml -->
+    <!-- devices with an autofocus camera and/or flash must include either
+         android.hardware.camera.autofocus.xml or
+         android.hardware.camera.autofocus-flash.xml -->
+    <!-- devices with a front facing camera must include
+         android.hardware.camera.front.xml -->
+    <!-- devices with WiFi must also include android.hardware.wifi.xml -->
+    <!-- devices that support multitouch must include the most appropriate one
+         of these files:
+
+         If only partial (non-independent) pointers are supported:
+         android.hardware.touchscreen.multitouch.xml
+
+         If up to 4 independently tracked pointers are supported:
+         include android.hardware.touchscreen.multitouch.distinct.xml
+
+         If 5 or more independently tracked pointers are supported:
+         include android.hardware.touchscreen.multitouch.jazzhand.xml
+
+         ONLY ONE of the above should be included. -->
+    <!-- devices with an ambient light sensor must also include
+         android.hardware.sensor.light.xml -->
+    <!-- devices with a proximity sensor must also include
+         android.hardware.sensor.proximity.xml -->
+    <!-- GSM phones must also include android.hardware.telephony.gsm.xml -->
+    <!-- CDMA phones must also include android.hardware.telephony.cdma.xml -->
+    <!-- Devices that have low-latency audio stacks suitable for apps like
+         VoIP may include android.hardware.audio.low_latency.xml. ONLY apps
+         that meet the requirements specified in the CDD may include this. -->
+</permissions>
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
index 6d739a1..c76e611 100644
--- a/data/etc/handheld_core_hardware.xml
+++ b/data/etc/handheld_core_hardware.xml
@@ -14,9 +14,9 @@
      limitations under the License.
 -->
 
-<!-- These are the hardware components that all handheld devices
-     must include. Devices with optional hardware must also include extra
-     hardware files, per the comments below.
+<!-- These are the hardware components that all handheld devices except Android Go
+     must include, for Android Go devices include go_handheld_core_hardware.xml.
+     Devices with optional hardware must also include extra hardware files, per the comments below.
 
      Handheld devices include phones, mobile Internet devices (MIDs),
      Personal Media Players (PMPs), small tablets (7" or less), and similar
diff --git a/headers/media_plugin/media/cas/CasAPI.h b/headers/media_plugin/media/cas/CasAPI.h
index 67f4511..4de314d 100644
--- a/headers/media_plugin/media/cas/CasAPI.h
+++ b/headers/media_plugin/media/cas/CasAPI.h
@@ -63,7 +63,7 @@
     // Construct a new instance of a CasPlugin given a CA_system_id
     virtual status_t createPlugin(
             int32_t CA_system_id,
-            uint64_t appData,
+            void *appData,
             CasPluginCallback callback,
             CasPlugin **plugin) = 0;
 
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index f739f07..cb542bf 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -433,6 +433,7 @@
 
     mDataPos = pos;
     mNextObjectHint = 0;
+    mObjectsSorted = false;
 }
 
 status_t Parcel::setDataCapacity(size_t size)
@@ -1276,7 +1277,7 @@
     if (err) return err;
 
     // payload
-    void* const buf = this->writeInplace(pad_size(len));
+    void* const buf = this->writeInplace(len);
     if (buf == NULL)
         return BAD_VALUE;
 
@@ -1469,6 +1470,59 @@
     LOG_ALWAYS_FATAL("Parcel::remove() not yet implemented!");
 }
 
+status_t Parcel::validateReadData(size_t upperBound) const
+{
+    // Don't allow non-object reads on object data
+    if (mObjectsSorted || mObjectsSize <= 1) {
+data_sorted:
+        // Expect to check only against the next object
+        if (mNextObjectHint < mObjectsSize && upperBound > mObjects[mNextObjectHint]) {
+            // For some reason the current read position is greater than the next object
+            // hint. Iterate until we find the right object
+            size_t nextObject = mNextObjectHint;
+            do {
+                if (mDataPos < mObjects[nextObject] + sizeof(flat_binder_object)) {
+                    // Requested info overlaps with an object
+                    ALOGE("Attempt to read from protected data in Parcel %p", this);
+                    return PERMISSION_DENIED;
+                }
+                nextObject++;
+            } while (nextObject < mObjectsSize && upperBound > mObjects[nextObject]);
+            mNextObjectHint = nextObject;
+        }
+        return NO_ERROR;
+    }
+    // Quickly determine if mObjects is sorted.
+    binder_size_t* currObj = mObjects + mObjectsSize - 1;
+    binder_size_t* prevObj = currObj;
+    while (currObj > mObjects) {
+        prevObj--;
+        if(*prevObj > *currObj) {
+            goto data_unsorted;
+        }
+        currObj--;
+    }
+    mObjectsSorted = true;
+    goto data_sorted;
+
+data_unsorted:
+    // Insertion Sort mObjects
+    // Great for mostly sorted lists. If randomly sorted or reverse ordered mObjects become common,
+    // switch to std::sort(mObjects, mObjects + mObjectsSize);
+    for (binder_size_t* iter0 = mObjects + 1; iter0 < mObjects + mObjectsSize; iter0++) {
+        binder_size_t temp = *iter0;
+        binder_size_t* iter1 = iter0 - 1;
+        while (iter1 >= mObjects && *iter1 > temp) {
+            *(iter1 + 1) = *iter1;
+            iter1--;
+        }
+        *(iter1 + 1) = temp;
+    }
+    mNextObjectHint = 0;
+    mObjectsSorted = true;
+    goto data_sorted;
+}
+
 status_t Parcel::read(void* outData, size_t len) const
 {
     if (len > INT32_MAX) {
@@ -1479,6 +1533,10 @@
 
     if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize
             && len <= pad_size(len)) {
+        if (mObjectsSize > 0) {
+            status_t err = validateReadData(mDataPos + pad_size(len));
+            if(err != NO_ERROR) return err;
+        }
         memcpy(outData, mData+mDataPos, len);
         mDataPos += pad_size(len);
         ALOGV("read Setting data pos of %p to %zu", this, mDataPos);
@@ -1497,6 +1555,11 @@
 
     if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize
             && len <= pad_size(len)) {
+        if (mObjectsSize > 0) {
+            status_t err = validateReadData(mDataPos + pad_size(len));
+            if(err != NO_ERROR) return NULL;
+        }
+
         const void* data = mData+mDataPos;
         mDataPos += pad_size(len);
         ALOGV("readInplace Setting data pos of %p to %zu", this, mDataPos);
@@ -1510,6 +1573,11 @@
     COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
 
     if ((mDataPos+sizeof(T)) <= mDataSize) {
+        if (mObjectsSize > 0) {
+            status_t err = validateReadData(mDataPos + sizeof(T));
+            if(err != NO_ERROR) return err;
+        }
+
         const void* data = mData+mDataPos;
         mDataPos += sizeof(T);
         *pArg =  *reinterpret_cast<const T*>(data);
@@ -2366,6 +2434,7 @@
     mObjects = const_cast<binder_size_t*>(objects);
     mObjectsSize = mObjectsCapacity = objectsCount;
     mNextObjectHint = 0;
+    mObjectsSorted = false;
     mOwner = relFunc;
     mOwnerCookie = relCookie;
     for (size_t i = 0; i < mObjectsSize; i++) {
@@ -2524,6 +2593,7 @@
     mObjects = NULL;
     mObjectsSize = mObjectsCapacity = 0;
     mNextObjectHint = 0;
+    mObjectsSorted = false;
     mHasFds = false;
     mFdsKnown = true;
     mAllowFds = true;
@@ -2610,6 +2680,7 @@
         mDataCapacity = desired;
         mObjectsSize = mObjectsCapacity = objectsSize;
         mNextObjectHint = 0;
+        mObjectsSorted = false;
 
     } else if (mData) {
         if (objectsSize < mObjectsSize) {
@@ -2631,6 +2702,7 @@
             }
             mObjectsSize = objectsSize;
             mNextObjectHint = 0;
+            mObjectsSorted = false;
         }
 
         // We own the data, so we can just do a realloc().
@@ -2703,6 +2775,7 @@
     mObjectsSize = 0;
     mObjectsCapacity = 0;
     mNextObjectHint = 0;
+    mObjectsSorted = false;
     mHasFds = false;
     mFdsKnown = true;
     mAllowFds = true;
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 5d36526..dede78f 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -417,6 +417,7 @@
     void                freeDataNoInit();
     void                initState();
     void                scanForFds() const;
+    status_t            validateReadData(size_t len) const;
                         
     template<class T>
     status_t            readAligned(T *pArg) const;
@@ -463,6 +464,7 @@
     size_t              mObjectsSize;
     size_t              mObjectsCapacity;
     mutable size_t      mNextObjectHint;
+    mutable bool        mObjectsSorted;
 
     mutable bool        mFdsKnown;
     mutable bool        mHasFds;
diff --git a/libs/gui/BufferHubProducer.cpp b/libs/gui/BufferHubProducer.cpp
index 70321ca..061710a 100644
--- a/libs/gui/BufferHubProducer.cpp
+++ b/libs/gui/BufferHubProducer.cpp
@@ -26,12 +26,6 @@
 
 /* static */
 sp<BufferHubProducer> BufferHubProducer::Create(const std::shared_ptr<ProducerQueue>& queue) {
-    if (queue->metadata_size() != sizeof(DvrNativeBufferMetadata)) {
-        ALOGE("BufferHubProducer::Create producer's metadata size is different "
-              "than the size of DvrNativeBufferMetadata");
-        return nullptr;
-    }
-
     sp<BufferHubProducer> producer = new BufferHubProducer;
     producer->queue_ = queue;
     return producer;
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 1988690..2917f45 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -113,8 +113,7 @@
 
     dvr::ProducerQueueConfigBuilder configBuilder;
     std::shared_ptr<dvr::ProducerQueue> producerQueue =
-            dvr::ProducerQueue::Create(configBuilder.SetMetadata<DvrNativeBufferMetadata>().Build(),
-                                       dvr::UsagePolicy{});
+            dvr::ProducerQueue::Create(configBuilder.Build(), dvr::UsagePolicy{});
     LOG_ALWAYS_FATAL_IF(producerQueue == NULL, "BufferQueue: failed to create ProducerQueue.");
 
     std::shared_ptr<dvr::ConsumerQueue> consumerQueue = producerQueue->CreateConsumerQueue();
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index bbf681e..63560c4 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -101,7 +101,8 @@
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other) :
     mForceSynchronous(other.mForceSynchronous),
     mTransactionNestCount(other.mTransactionNestCount),
-    mAnimation(other.mAnimation) {
+    mAnimation(other.mAnimation),
+    mEarlyWakeup(other.mEarlyWakeup) {
     mDisplayStates = other.mDisplayStates;
     mComposerStates = other.mComposerStates;
 }
@@ -157,9 +158,13 @@
     if (mAnimation) {
         flags |= ISurfaceComposer::eAnimation;
     }
+    if (mEarlyWakeup) {
+        flags |= ISurfaceComposer::eEarlyWakeup;
+    }
 
     mForceSynchronous = false;
     mAnimation = false;
+    mEarlyWakeup = false;
 
     sf->setTransactionState(composerStates, displayStates, flags);
     mStatus = NO_ERROR;
@@ -185,6 +190,10 @@
     mAnimation = true;
 }
 
+void SurfaceComposerClient::Transaction::setEarlyWakeup() {
+    mEarlyWakeup = true;
+}
+
 layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp<SurfaceControl>& sc) {
     if (mComposerStates.count(sc) == 0) {
         // we don't have it, add an initialized layer_state to our list
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 3591090..e401572 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -61,6 +61,11 @@
     enum {
         eSynchronous = 0x01,
         eAnimation   = 0x02,
+
+        // Indicates that this transaction will likely result in a lot of layers being composed, and
+        // thus, SurfaceFlinger should wake-up earlier to avoid missing frame deadlines. In this
+        // case SurfaceFlinger will wake up at (sf vsync offset - debug.sf.early_phase_offset_ns)
+        eEarlyWakeup = 0x04
     };
 
     enum {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index ffc22f6..377fe68 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -154,6 +154,7 @@
         uint32_t                    mForceSynchronous = 0;
         uint32_t                    mTransactionNestCount = 0;
         bool                        mAnimation = false;
+        bool                        mEarlyWakeup = false;
 
         int mStatus = NO_ERROR;
 
@@ -273,6 +274,7 @@
                 const Rect& displayRect);
         void setDisplaySize(const sp<IBinder>& token, uint32_t width, uint32_t height);
         void setAnimationTransaction();
+        void setEarlyWakeup();
     };
 
     status_t    destroySurface(const sp<IBinder>& id);
diff --git a/libs/gui/tests/SurfaceParcelable_test.cpp b/libs/gui/tests/SurfaceParcelable_test.cpp
index 99a8a7a..686dc82 100644
--- a/libs/gui/tests/SurfaceParcelable_test.cpp
+++ b/libs/gui/tests/SurfaceParcelable_test.cpp
@@ -49,7 +49,6 @@
         mProducerQueue = dvr::ProducerQueue::Create(configBuilder.SetDefaultWidth(kBufferWidth)
                                                             .SetDefaultHeight(kBufferHeight)
                                                             .SetDefaultFormat(kBufferFormat)
-                                                            .SetMetadata<DvrNativeBufferMetadata>()
                                                             .Build(),
                                                     dvr::UsagePolicy{});
         mBufferHubProducer = BufferHubProducer::Create(mProducerQueue);
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 2598451..765dcd9 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -113,7 +113,10 @@
     constexpr int32_t kAllTransformBits =
             ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
             ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL |
-            ANATIVEWINDOW_TRANSFORM_ROTATE_90;
+            ANATIVEWINDOW_TRANSFORM_ROTATE_90 |
+            // We don't expose INVERSE_DISPLAY as an NDK constant, but someone could have read it
+            // from a buffer already set by Camera framework, so we allow it to be forwarded.
+            NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
     if (!window || !query(window, NATIVE_WINDOW_IS_VALID))
         return -EINVAL;
     if ((transform & ~kAllTransformBits) != 0)
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 58fed84..61df02d 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -23,6 +23,7 @@
 
 using android::base::StringPrintf;
 using android::ui::ColorMode;
+using android::ui::RenderIntent;
 
 std::string decodeStandard(android_dataspace dataspace) {
     const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
@@ -229,6 +230,15 @@
 
         case ColorMode::DISPLAY_P3:
             return std::string("ColorMode::DISPLAY_P3");
+
+        case ColorMode::BT2020:
+            return std::string("ColorMode::BT2020");
+
+        case ColorMode::BT2100_PQ:
+            return std::string("ColorMode::BT2100_PQ");
+
+        case ColorMode::BT2100_HLG:
+            return std::string("ColorMode::BT2100_HLG");
     }
 
     return android::base::StringPrintf("Unknown color mode %d", colorMode);
@@ -294,6 +304,20 @@
     }
 }
 
+std::string decodeRenderIntent(RenderIntent renderIntent) {
+    switch(renderIntent) {
+      case RenderIntent::COLORIMETRIC:
+          return std::string("RenderIntent::COLORIMETRIC");
+      case RenderIntent::ENHANCE:
+          return std::string("RenderIntent::ENHANCE");
+      case RenderIntent::TONE_MAP_COLORIMETRIC:
+          return std::string("RenderIntent::TONE_MAP_COLORIMETRIC");
+      case RenderIntent::TONE_MAP_ENHANCE:
+          return std::string("RenderIntent::TONE_MAP_ENHANCE");
+    }
+    return std::string("Unknown RenderIntent");
+}
+
 std::string to_string(const android::Rect& rect) {
     return StringPrintf("(%4d,%4d,%4d,%4d)", rect.left, rect.top, rect.right, rect.bottom);
 }
diff --git a/libs/ui/Region.cpp b/libs/ui/Region.cpp
index 36da084..fe4ae6c 100644
--- a/libs/ui/Region.cpp
+++ b/libs/ui/Region.cpp
@@ -838,6 +838,11 @@
 }
 
 Region::const_iterator Region::end() const {
+    // Workaround for b/77643177
+    // mStorage should never be empty, but somehow it is and it's causing
+    // an abort in ubsan
+    if (mStorage.isEmpty()) return mStorage.array();
+
     size_t numRects = isRect() ? 1 : mStorage.size() - 1;
     return mStorage.array() + numRects;
 }
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 5e5df43..92b2bfb 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -32,4 +32,5 @@
 std::string decodeColorMode(android::ui::ColorMode colormode);
 std::string decodeColorTransform(android_color_transform colorTransform);
 std::string decodePixelFormat(android::PixelFormat format);
+std::string decodeRenderIntent(android::ui::RenderIntent renderIntent);
 std::string to_string(const android::Rect& rect);
diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h
index 92a5519..bd5722f 100644
--- a/libs/ui/include/ui/GraphicTypes.h
+++ b/libs/ui/include/ui/GraphicTypes.h
@@ -24,9 +24,10 @@
 namespace android {
 namespace ui {
 
-using android::hardware::graphics::common::V1_0::ColorMode;
+using android::hardware::graphics::common::V1_1::ColorMode;
 using android::hardware::graphics::common::V1_1::Dataspace;
 using android::hardware::graphics::common::V1_1::PixelFormat;
+using android::hardware::graphics::common::V1_1::RenderIntent;
 
 }  // namespace ui
 }  // namespace android
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 39814cc..b38ecc7 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -15,6 +15,7 @@
 sourceFiles = [
     "buffer_hub_client.cpp",
     "buffer_hub_rpc.cpp",
+    "detached_buffer.cpp",
     "ion_buffer.cpp",
 ]
 
@@ -59,6 +60,11 @@
     vndk: {
         enabled: true,
     },
+    target: {
+        vendor: {
+            exclude_srcs: ["detached_buffer.cpp"],
+        },
+    },
 }
 
 cc_test {
diff --git a/libs/vr/libbufferhub/buffer_hub-test.cpp b/libs/vr/libbufferhub/buffer_hub-test.cpp
index 660a200..2302828 100644
--- a/libs/vr/libbufferhub/buffer_hub-test.cpp
+++ b/libs/vr/libbufferhub/buffer_hub-test.cpp
@@ -2,8 +2,10 @@
 #include <poll.h>
 #include <private/dvr/buffer_hub_client.h>
 #include <private/dvr/bufferhub_rpc.h>
+#include <private/dvr/detached_buffer.h>
 #include <sys/epoll.h>
 #include <sys/eventfd.h>
+#include <ui/DetachedBufferHandle.h>
 
 #include <mutex>
 #include <thread>
@@ -17,22 +19,28 @@
     return result;                            \
   })()
 
+using android::sp;
+using android::GraphicBuffer;
 using android::dvr::BufferConsumer;
 using android::dvr::BufferHubDefs::kConsumerStateMask;
+using android::dvr::BufferHubDefs::kMetadataHeaderSize;
 using android::dvr::BufferHubDefs::kProducerStateBit;
 using android::dvr::BufferHubDefs::IsBufferGained;
 using android::dvr::BufferHubDefs::IsBufferPosted;
 using android::dvr::BufferHubDefs::IsBufferAcquired;
 using android::dvr::BufferHubDefs::IsBufferReleased;
 using android::dvr::BufferProducer;
+using android::dvr::DetachedBuffer;
 using android::pdx::LocalChannelHandle;
 using android::pdx::LocalHandle;
 using android::pdx::Status;
 
 const int kWidth = 640;
 const int kHeight = 480;
+const int kLayerCount = 1;
 const int kFormat = HAL_PIXEL_FORMAT_RGBA_8888;
 const int kUsage = 0;
+const size_t kUserMetadataSize = 0;
 const uint64_t kContext = 42;
 const size_t kMaxConsumerCount = 63;
 const int kPollTimeoutMs = 100;
@@ -730,6 +738,7 @@
 
   DvrNativeBufferMetadata metadata;
   LocalHandle invalid_fence;
+  int p_id = p->id();
 
   // Detach in posted state should fail.
   EXPECT_EQ(0, p->PostAsync(&metadata, invalid_fence));
@@ -753,8 +762,8 @@
   s1 = p->Detach();
   EXPECT_TRUE(s1);
 
-  LocalChannelHandle detached_buffer = s1.take();
-  EXPECT_TRUE(detached_buffer.valid());
+  LocalChannelHandle handle = s1.take();
+  EXPECT_TRUE(handle.valid());
 
   // Both producer and consumer should have hangup.
   EXPECT_GT(RETRY_EINTR(p->Poll(kPollTimeoutMs)), 0);
@@ -779,4 +788,80 @@
   // ConsumerChannel::HandleMessage as the socket is still open but the producer
   // is gone.
   EXPECT_EQ(s3.error(), EPIPE);
+
+  // Detached buffer handle can be use to construct a new DetachedBuffer object.
+  auto d = DetachedBuffer::Import(std::move(handle));
+  EXPECT_FALSE(handle.valid());
+  EXPECT_TRUE(d->IsValid());
+
+  ASSERT_TRUE(d->buffer() != nullptr);
+  EXPECT_EQ(d->buffer()->initCheck(), 0);
+  EXPECT_EQ(d->id(), p_id);
+}
+
+TEST_F(LibBufferHubTest, TestCreateDetachedBufferFails) {
+  // Buffer Creation will fail: BLOB format requires height to be 1.
+  auto b1 = DetachedBuffer::Create(kWidth, /*height=2*/2, kLayerCount,
+                                   /*format=*/HAL_PIXEL_FORMAT_BLOB, kUsage,
+                                   kUserMetadataSize);
+
+  EXPECT_FALSE(b1->IsValid());
+  EXPECT_TRUE(b1->buffer() == nullptr);
+
+  // Buffer Creation will fail: user metadata size too large.
+  auto b2 = DetachedBuffer::Create(
+      kWidth, kHeight, kLayerCount, kFormat, kUsage,
+      /*user_metadata_size=*/std::numeric_limits<size_t>::max());
+
+  EXPECT_FALSE(b2->IsValid());
+  EXPECT_TRUE(b2->buffer() == nullptr);
+
+  // Buffer Creation will fail: user metadata size too large.
+  auto b3 = DetachedBuffer::Create(
+      kWidth, kHeight, kLayerCount, kFormat, kUsage,
+      /*user_metadata_size=*/std::numeric_limits<size_t>::max() -
+          kMetadataHeaderSize);
+
+  EXPECT_FALSE(b3->IsValid());
+  EXPECT_TRUE(b3->buffer() == nullptr);
+}
+
+TEST_F(LibBufferHubTest, TestCreateDetachedBuffer) {
+  auto b1 = DetachedBuffer::Create(kWidth, kHeight, kLayerCount, kFormat,
+                                   kUsage, kUserMetadataSize);
+  int b1_id = b1->id();
+
+  EXPECT_TRUE(b1->IsValid());
+  ASSERT_TRUE(b1->buffer() != nullptr);
+  EXPECT_NE(b1->id(), 0);
+  EXPECT_EQ(b1->buffer()->initCheck(), 0);
+  EXPECT_FALSE(b1->buffer()->isDetachedBuffer());
+
+  // Takes a standalone GraphicBuffer which still holds on an
+  // PDX::LocalChannelHandle towards BufferHub.
+  sp<GraphicBuffer> g1 = b1->TakeGraphicBuffer();
+  ASSERT_TRUE(g1 != nullptr);
+  EXPECT_TRUE(g1->isDetachedBuffer());
+
+  EXPECT_FALSE(b1->IsValid());
+  EXPECT_TRUE(b1->buffer() == nullptr);
+
+  sp<GraphicBuffer> g2 = b1->TakeGraphicBuffer();
+  ASSERT_TRUE(g2 == nullptr);
+
+  auto h1 = g1->takeDetachedBufferHandle();
+  ASSERT_TRUE(h1 != nullptr);
+  ASSERT_TRUE(h1->isValid());
+  EXPECT_FALSE(g1->isDetachedBuffer());
+
+  auto b2 = DetachedBuffer::Import(std::move(h1->handle()));
+  ASSERT_FALSE(h1->isValid());
+  EXPECT_TRUE(b2->IsValid());
+
+  ASSERT_TRUE(b2->buffer() != nullptr);
+  EXPECT_EQ(b2->buffer()->initCheck(), 0);
+
+  // The newly created DetachedBuffer should share the original buffer_id.
+  EXPECT_EQ(b2->id(), b1_id);
+  EXPECT_FALSE(b2->buffer()->isDetachedBuffer());
 }
diff --git a/libs/vr/libbufferhub/buffer_hub_client.cpp b/libs/vr/libbufferhub/buffer_hub_client.cpp
index 13971eb..159f2bd 100644
--- a/libs/vr/libbufferhub/buffer_hub_client.cpp
+++ b/libs/vr/libbufferhub/buffer_hub_client.cpp
@@ -15,10 +15,30 @@
 using android::pdx::LocalChannelHandle;
 using android::pdx::LocalHandle;
 using android::pdx::Status;
+using android::pdx::default_transport::ClientChannel;
+using android::pdx::default_transport::ClientChannelFactory;
 
 namespace android {
 namespace dvr {
 
+BufferHubClient::BufferHubClient()
+    : Client(ClientChannelFactory::Create(BufferHubRPC::kClientPath)) {}
+
+BufferHubClient::BufferHubClient(LocalChannelHandle channel_handle)
+    : Client(ClientChannel::Create(std::move(channel_handle))) {}
+
+bool BufferHubClient::IsValid() const {
+  return IsConnected() && GetChannelHandle().valid();
+}
+
+LocalChannelHandle BufferHubClient::TakeChannelHandle() {
+  if (IsConnected()) {
+    return std::move(GetChannelHandle());
+  } else {
+    return {};
+  }
+}
+
 BufferHubBuffer::BufferHubBuffer(LocalChannelHandle channel_handle)
     : Client{pdx::default_transport::ClientChannel::Create(
           std::move(channel_handle))},
diff --git a/libs/vr/libbufferhub/detached_buffer.cpp b/libs/vr/libbufferhub/detached_buffer.cpp
new file mode 100644
index 0000000..1d59cf3
--- /dev/null
+++ b/libs/vr/libbufferhub/detached_buffer.cpp
@@ -0,0 +1,104 @@
+#include <private/dvr/detached_buffer.h>
+
+#include <pdx/file_handle.h>
+#include <ui/DetachedBufferHandle.h>
+
+using android::pdx::LocalHandle;
+
+namespace android {
+namespace dvr {
+
+DetachedBuffer::DetachedBuffer(uint32_t width, uint32_t height,
+                               uint32_t layer_count, uint32_t format,
+                               uint64_t usage, size_t user_metadata_size) {
+  ATRACE_NAME("DetachedBuffer::DetachedBuffer");
+  ALOGD_IF(TRACE,
+           "DetachedBuffer::DetachedBuffer: width=%u height=%u layer_count=%u, "
+           "format=%u usage=%" PRIx64 " user_metadata_size=%zu",
+           width, height, layer_count, format, usage, user_metadata_size);
+
+  auto status = client_.InvokeRemoteMethod<DetachedBufferRPC::Create>(
+      width, height, layer_count, format, usage, user_metadata_size);
+  if (!status) {
+    ALOGE(
+        "DetachedBuffer::DetachedBuffer: Failed to create detached buffer: %s",
+        status.GetErrorMessage().c_str());
+    client_.Close(-status.error());
+  }
+
+  const int ret = ImportGraphicBuffer();
+  if (ret < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import buffer: %s",
+          strerror(-ret));
+    client_.Close(ret);
+  }
+}
+
+DetachedBuffer::DetachedBuffer(LocalChannelHandle channel_handle)
+    : client_(std::move(channel_handle)) {
+  const int ret = ImportGraphicBuffer();
+  if (ret < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import buffer: %s",
+          strerror(-ret));
+    client_.Close(ret);
+  }
+}
+
+int DetachedBuffer::ImportGraphicBuffer() {
+  ATRACE_NAME("DetachedBuffer::DetachedBuffer");
+
+  auto status = client_.InvokeRemoteMethod<DetachedBufferRPC::Import>();
+  if (!status) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Failed to import GraphicBuffer: %s",
+          status.GetErrorMessage().c_str());
+    return -status.error();
+  }
+
+  BufferDescription<LocalHandle> buffer_desc = status.take();
+  if (buffer_desc.id() < 0) {
+    ALOGE("DetachedBuffer::DetachedBuffer: Received an invalid id!");
+    return -EIO;
+  }
+
+  // Stash the buffer id to replace the value in id_.
+  const int buffer_id = buffer_desc.id();
+
+  // Import the buffer.
+  IonBuffer ion_buffer;
+  ALOGD_IF(TRACE, "DetachedBuffer::DetachedBuffer: id=%d.", buffer_id);
+
+  if (const int ret = buffer_desc.ImportBuffer(&ion_buffer)) {
+    ALOGE("Failed to import GraphicBuffer, error=%d", ret);
+    return ret;
+  }
+
+  // If all imports succeed, replace the previous buffer and id.
+  id_ = buffer_id;
+  buffer_ = std::move(ion_buffer);
+  return 0;
+}
+
+std::unique_ptr<BufferProducer> DetachedBuffer::Promote() {
+  ALOGE("DetachedBuffer::Promote: Not implemented.");
+  return nullptr;
+}
+
+sp<GraphicBuffer> DetachedBuffer::TakeGraphicBuffer() {
+  if (!client_.IsValid() || !buffer_.buffer()) {
+    ALOGE("DetachedBuffer::TakeGraphicBuffer: Invalid buffer.");
+    return nullptr;
+  }
+
+  // Technically this should never happen.
+  LOG_FATAL_IF(
+      buffer_.buffer()->isDetachedBuffer(),
+      "DetachedBuffer::TakeGraphicBuffer: GraphicBuffer is already detached.");
+
+  sp<GraphicBuffer> buffer = std::move(buffer_.buffer());
+  buffer->setDetachedBufferHandle(
+      DetachedBufferHandle::Create(client_.TakeChannelHandle()));
+  return buffer;
+}
+
+}  // namespace dvr
+}  // namespace android
diff --git a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
index 32448a1..c1cc7f3 100644
--- a/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
+++ b/libs/vr/libbufferhub/include/private/dvr/buffer_hub_client.h
@@ -16,6 +16,21 @@
 namespace android {
 namespace dvr {
 
+class BufferHubClient : public pdx::Client {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+
+  BufferHubClient();
+  explicit BufferHubClient(LocalChannelHandle channel_handle);
+
+  bool IsValid() const;
+  LocalChannelHandle TakeChannelHandle();
+
+  using pdx::Client::Close;
+  using pdx::Client::InvokeRemoteMethod;
+  using pdx::Client::IsConnected;
+};
+
 class BufferHubBuffer : public pdx::Client {
  public:
   using LocalHandle = pdx::LocalHandle;
diff --git a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
index fabefd5..f4918c4 100644
--- a/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
+++ b/libs/vr/libbufferhub/include/private/dvr/bufferhub_rpc.h
@@ -375,7 +375,7 @@
     kOpConsumerSetIgnore,
     kOpProducerBufferDetach,
     kOpConsumerBufferDetach,
-    kOpCreateDetachedBuffer,
+    kOpDetachedBufferCreate,
     kOpDetachedBufferPromote,
     kOpCreateProducerQueue,
     kOpCreateConsumerQueue,
@@ -383,6 +383,8 @@
     kOpProducerQueueAllocateBuffers,
     kOpProducerQueueRemoveBuffer,
     kOpConsumerQueueImportBuffers,
+    // TODO(b/77153033): Separate all those RPC operations into subclasses.
+    kOpDetachedBufferBase = 1000,
   };
 
   // Aliases.
@@ -416,17 +418,6 @@
   PDX_REMOTE_METHOD(ConsumerBufferDetach, kOpConsumerBufferDetach,
                     LocalChannelHandle(Void));
 
-  // Creates a standalone DetachedBuffer not associated with any
-  // producer/consumer set.
-  PDX_REMOTE_METHOD(CreateDetachedBuffer, kOpCreateDetachedBuffer,
-                    LocalChannelHandle(Void));
-
-  // Promotes a DetachedBuffer to become a ProducerBuffer. Once promoted the
-  // DetachedBuffer channel will be closed automatically on successful IPC
-  // return. Further IPCs towards this channel will return error.
-  PDX_REMOTE_METHOD(DetachedBufferPromote, kOpDetachedBufferPromote,
-                    LocalChannelHandle(Void));
-
   // Buffer Queue Methods.
   PDX_REMOTE_METHOD(CreateProducerQueue, kOpCreateProducerQueue,
                     QueueInfo(const ProducerQueueConfig& producer_config,
@@ -445,6 +436,25 @@
                     std::vector<std::pair<LocalChannelHandle, size_t>>(Void));
 };
 
+struct DetachedBufferRPC final : public BufferHubRPC {
+ private:
+  enum {
+    kOpCreate = kOpDetachedBufferBase,
+    kOpImport,
+    kOpPromote,
+  };
+
+ public:
+  PDX_REMOTE_METHOD(Create, kOpCreate,
+                    void(uint32_t width, uint32_t height, uint32_t layer_count,
+                         uint32_t format, uint64_t usage,
+                         size_t user_metadata_size));
+  PDX_REMOTE_METHOD(Import, kOpImport, BufferDescription<LocalHandle>(Void));
+  PDX_REMOTE_METHOD(Promote, kOpPromote, LocalChannelHandle(Void));
+
+  PDX_REMOTE_API(API, Create, Promote);
+};
+
 }  // namespace dvr
 }  // namespace android
 
diff --git a/libs/vr/libbufferhub/include/private/dvr/detached_buffer.h b/libs/vr/libbufferhub/include/private/dvr/detached_buffer.h
new file mode 100644
index 0000000..73e895d
--- /dev/null
+++ b/libs/vr/libbufferhub/include/private/dvr/detached_buffer.h
@@ -0,0 +1,65 @@
+#ifndef ANDROID_DVR_DETACHED_BUFFER_H_
+#define ANDROID_DVR_DETACHED_BUFFER_H_
+
+#include <private/dvr/buffer_hub_client.h>
+
+namespace android {
+namespace dvr {
+
+class DetachedBuffer {
+ public:
+  using LocalChannelHandle = pdx::LocalChannelHandle;
+
+  // Allocates a standalone DetachedBuffer not associated with any producer
+  // consumer set.
+  static std::unique_ptr<DetachedBuffer> Create(uint32_t width, uint32_t height,
+                                                uint32_t layer_count,
+                                                uint32_t format, uint64_t usage,
+                                                size_t user_metadata_size) {
+    return std::unique_ptr<DetachedBuffer>(new DetachedBuffer(
+        width, height, layer_count, format, usage, user_metadata_size));
+  }
+
+  // Imports the given channel handle to a DetachedBuffer, taking ownership.
+  static std::unique_ptr<DetachedBuffer> Import(
+      LocalChannelHandle channel_handle) {
+    return std::unique_ptr<DetachedBuffer>(
+        new DetachedBuffer(std::move(channel_handle)));
+  }
+
+  DetachedBuffer(const DetachedBuffer&) = delete;
+  void operator=(const DetachedBuffer&) = delete;
+
+  const sp<GraphicBuffer>& buffer() const { return buffer_.buffer(); }
+
+  int id() const { return id_; }
+  bool IsValid() const { return client_.IsValid(); }
+
+  // Promotes a DetachedBuffer to become a ProducerBuffer. Once promoted the
+  // DetachedBuffer channel will be closed automatically on successful IPC
+  // return. Further IPCs towards this channel will return error.
+  std::unique_ptr<BufferProducer> Promote();
+
+  // Takes the underlying graphic buffer out of this DetachedBuffer. This call
+  // immediately invalidates this DetachedBuffer object and transfers the
+  // underlying pdx::LocalChannelHandle into the GraphicBuffer.
+  sp<GraphicBuffer> TakeGraphicBuffer();
+
+ private:
+  DetachedBuffer(uint32_t width, uint32_t height, uint32_t layer_count,
+                 uint32_t format, uint64_t usage, size_t user_metadata_size);
+
+  DetachedBuffer(LocalChannelHandle channel_handle);
+
+  int ImportGraphicBuffer();
+
+  // Global id for the buffer that is consistent across processes.
+  int id_;
+  IonBuffer buffer_;
+  BufferHubClient client_;
+};
+
+}  // namespace dvr
+}  // namespace android
+
+#endif  // ANDROID_DVR_DETACHED_BUFFER_H_
diff --git a/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h b/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h
index 0d337f7..f6bc547 100644
--- a/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h
+++ b/libs/vr/libbufferhub/include/private/dvr/ion_buffer.h
@@ -23,6 +23,9 @@
   IonBuffer(IonBuffer&& other);
   IonBuffer& operator=(IonBuffer&& other);
 
+  // Returns check this IonBuffer holds a valid Gralloc buffer.
+  bool IsValid() const { return buffer_ && buffer_->initCheck() == NO_ERROR; }
+
   // Frees the underlying native handle and leaves the instance initialized to
   // empty.
   void FreeHandle();
@@ -66,6 +69,7 @@
               struct android_ycbcr* yuv);
   int Unlock();
 
+  sp<GraphicBuffer>& buffer() { return buffer_; }
   const sp<GraphicBuffer>& buffer() const { return buffer_; }
   buffer_handle_t handle() const {
     return buffer_.get() ? buffer_->handle : nullptr;
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
index 3e96989..4f10f83 100644
--- a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
@@ -94,9 +94,7 @@
     ALOGD_IF(TRACE, "Begin test: %s.%s", testInfo->test_case_name(),
              testInfo->name());
 
-    auto config = ProducerQueueConfigBuilder()
-                      .SetMetadata<DvrNativeBufferMetadata>()
-                      .Build();
+    auto config = ProducerQueueConfigBuilder().Build();
     auto queue = ProducerQueue::Create(config, UsagePolicy{});
     ASSERT_TRUE(queue != nullptr);
 
diff --git a/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp b/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp
index 301458a..5d9d8b5 100644
--- a/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp
+++ b/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp
@@ -221,7 +221,7 @@
 TEST_F(DvrBufferQueueTest, GetANativeWindow) {
   int ret = api_.WriteBufferQueueCreate(
       kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      /*capacity=*/0, sizeof(DvrNativeBufferMetadata), &write_queue_);
+      /*capacity=*/0, /*user_metadata_size=*/0, &write_queue_);
   ASSERT_EQ(0, ret);
   ASSERT_NE(nullptr, write_queue_);
 
diff --git a/libs/vr/libpdx/client.cpp b/libs/vr/libpdx/client.cpp
index a01c4d6..3c66a40 100644
--- a/libs/vr/libpdx/client.cpp
+++ b/libs/vr/libpdx/client.cpp
@@ -119,6 +119,10 @@
   return channel_->GetChannelHandle();
 }
 
+const LocalChannelHandle& Client::GetChannelHandle() const {
+  return channel_->GetChannelHandle();
+}
+
 ///////////////////////////// Transaction implementation //////////////////////
 
 Transaction::Transaction(Client& client) : client_{client} {}
diff --git a/libs/vr/libpdx/private/pdx/client.h b/libs/vr/libpdx/private/pdx/client.h
index 656de7e..c35dabd 100644
--- a/libs/vr/libpdx/private/pdx/client.h
+++ b/libs/vr/libpdx/private/pdx/client.h
@@ -49,6 +49,7 @@
 
   // Returns a reference to IPC channel handle.
   LocalChannelHandle& GetChannelHandle();
+  const LocalChannelHandle& GetChannelHandle() const;
 
  protected:
   friend Transaction;
diff --git a/libs/vr/libpdx/private/pdx/client_channel.h b/libs/vr/libpdx/private/pdx/client_channel.h
index 8f5fdfe..f33a60e 100644
--- a/libs/vr/libpdx/private/pdx/client_channel.h
+++ b/libs/vr/libpdx/private/pdx/client_channel.h
@@ -33,6 +33,7 @@
   virtual std::vector<EventSource> GetEventSources() const = 0;
 
   virtual LocalChannelHandle& GetChannelHandle() = 0;
+  virtual const LocalChannelHandle& GetChannelHandle() const = 0;
   virtual void* AllocateTransactionState() = 0;
   virtual void FreeTransactionState(void* state) = 0;
 
diff --git a/libs/vr/libpdx/private/pdx/mock_client_channel.h b/libs/vr/libpdx/private/pdx/mock_client_channel.h
index ecc20b3..b1a1299 100644
--- a/libs/vr/libpdx/private/pdx/mock_client_channel.h
+++ b/libs/vr/libpdx/private/pdx/mock_client_channel.h
@@ -14,6 +14,7 @@
   MOCK_CONST_METHOD0(GetEventSources, std::vector<EventSource>());
   MOCK_METHOD1(GetEventMask, Status<int>(int));
   MOCK_METHOD0(GetChannelHandle, LocalChannelHandle&());
+  MOCK_CONST_METHOD0(GetChannelHandle, const LocalChannelHandle&());
   MOCK_METHOD0(AllocateTransactionState, void*());
   MOCK_METHOD1(FreeTransactionState, void(void* state));
   MOCK_METHOD3(SendImpulse,
diff --git a/libs/vr/libpdx/private/pdx/service.h b/libs/vr/libpdx/private/pdx/service.h
index 0d30614..13aa3e9 100644
--- a/libs/vr/libpdx/private/pdx/service.h
+++ b/libs/vr/libpdx/private/pdx/service.h
@@ -589,6 +589,14 @@
   }
 
   /*
+   * Return true if a channel with the given ID exists in the Channel map.
+   */
+  bool HasChannelId(int channel_id) const {
+    std::lock_guard<std::mutex> autolock(channels_mutex_);
+    return channels_.find(channel_id) != channels_.end();
+  }
+
+  /*
    * Subclasses of Service may override this method to provide a text string
    * describing the state of the service. This method is called by
    * HandleSystemMessage in response to the standard
diff --git a/libs/vr/libpdx_uds/private/uds/client_channel.h b/libs/vr/libpdx_uds/private/uds/client_channel.h
index b5524d8..3561c6f 100644
--- a/libs/vr/libpdx_uds/private/uds/client_channel.h
+++ b/libs/vr/libpdx_uds/private/uds/client_channel.h
@@ -41,6 +41,9 @@
   }
 
   LocalChannelHandle& GetChannelHandle() override { return channel_handle_; }
+  const LocalChannelHandle& GetChannelHandle() const override {
+    return channel_handle_;
+  }
   void* AllocateTransactionState() override;
   void FreeTransactionState(void* state) override;
 
diff --git a/libs/vr/libvrflinger/display_manager_service.cpp b/libs/vr/libvrflinger/display_manager_service.cpp
index ef8cca3..34b3b0a 100644
--- a/libs/vr/libvrflinger/display_manager_service.cpp
+++ b/libs/vr/libvrflinger/display_manager_service.cpp
@@ -45,6 +45,14 @@
   const int user_id = message.GetEffectiveUserId();
   const bool trusted = user_id == AID_ROOT || IsTrustedUid(user_id);
 
+  // Check if the display_manager_ has a defunct channel.
+  if (display_manager_ && !HasChannelId(display_manager_->channel_id())) {
+    ALOGE("DisplayManagerService::OnChannelOpen: Found defunct channel %d with "
+          "no OnChannelClose, clearing prior display manager.",
+          display_manager_->channel_id());
+    display_manager_ = nullptr;
+  }
+
   // Prevent more than one display manager from registering at a time or
   // untrusted UIDs from connecting.
   if (display_manager_ || !trusted) {
diff --git a/opengl/include/GLES2/gl2ext.h b/opengl/include/GLES2/gl2ext.h
index 3ad39d9..e8d7515 100644
--- a/opengl/include/GLES2/gl2ext.h
+++ b/opengl/include/GLES2/gl2ext.h
@@ -3612,17 +3612,6 @@
 #define GL_SHADER_BINARY_VIV              0x8FC4
 #endif /* GL_VIV_shader_binary */
 
-/* Temporary hack to allow frameworks/base/libs/hwui/debug to build.
- * This function was removed from the Khronos version of the headers
- * (it is specified with the EXT prefix, not OES). */
-#ifndef GL_ANDROID_draw_elements_base_vertex_backwards_compatibility
-#define GL_ANDROID_draw_elements_base_vertex_backwards_compatibility 1
-typedef void (GL_APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXOESPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex);
-#ifdef GL_GLEXT_PROTOTYPES
-GL_APICALL void GL_APIENTRY glMultiDrawElementsBaseVertexOES (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex);
-#endif
-#endif /* GL_ANDROID_draw_elements_base_vertex_backwards_compatibility */
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp
index c05e840..0183621 100644
--- a/opengl/libs/EGL/getProcAddress.cpp
+++ b/opengl/libs/EGL/getProcAddress.cpp
@@ -54,7 +54,7 @@
             : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
               [ext] "J"(__builtin_offsetof(gl_hooks_t,          \
                                       ext.extensions[0])),      \
-              [api] "J"(_api*sizeof(void*))                     \
+              [api] "I"(_api*sizeof(void*))                     \
             : "r12"                                             \
             );
 
diff --git a/services/displayservice/DisplayEventReceiver.cpp b/services/displayservice/DisplayEventReceiver.cpp
index 5993e44..2bb74c2 100644
--- a/services/displayservice/DisplayEventReceiver.cpp
+++ b/services/displayservice/DisplayEventReceiver.cpp
@@ -102,10 +102,20 @@
 
             switch(buf[i].header.type) {
                 case FwkReceiver::DISPLAY_EVENT_VSYNC: {
-                    mCallback->onVsync(timestamp, event.vsync.count);
+                    auto ret = mCallback->onVsync(timestamp, event.vsync.count);
+                    if (!ret.isOk()) {
+                        LOG(ERROR) << "AttachedEvent handleEvent fails on onVsync callback"
+                                   << " because of " << ret.description();
+                        return 0;  // remove the callback
+                    }
                 } break;
                 case FwkReceiver::DISPLAY_EVENT_HOTPLUG: {
-                    mCallback->onHotplug(timestamp, event.hotplug.connected);
+                    auto ret = mCallback->onHotplug(timestamp, event.hotplug.connected);
+                    if (!ret.isOk()) {
+                        LOG(ERROR) << "AttachedEvent handleEvent fails on onHotplug callback"
+                                   << " because of " << ret.description();
+                        return 0;  // remove the callback
+                    }
                 } break;
                 default: {
                     LOG(ERROR) << "AttachedEvent handleEvent unknown type: " << type;
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 82300e6..2aa4cd3 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -778,6 +778,7 @@
 }
 
 void BufferLayer::drawWithOpenGL(const RenderArea& renderArea, bool useIdentityTransform) const {
+    ATRACE_CALL();
     const State& s(getDrawingState());
 
     computeGeometry(renderArea, getBE().mMesh, useIdentityTransform);
diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp
index 6b4f5db..87333d0 100644
--- a/services/surfaceflinger/BufferLayerConsumer.cpp
+++ b/services/surfaceflinger/BufferLayerConsumer.cpp
@@ -356,6 +356,7 @@
 }
 
 status_t BufferLayerConsumer::bindTextureImageLocked() {
+    ATRACE_CALL();
     mRE.checkErrors();
 
     if (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT && mCurrentTextureImage == nullptr) {
diff --git a/services/surfaceflinger/ColorLayer.cpp b/services/surfaceflinger/ColorLayer.cpp
index 80a90a7..c87b669 100644
--- a/services/surfaceflinger/ColorLayer.cpp
+++ b/services/surfaceflinger/ColorLayer.cpp
@@ -44,13 +44,13 @@
 
 void ColorLayer::onDraw(const RenderArea& renderArea, const Region& /* clip */,
                         bool useIdentityTransform) const {
-    const State& s(getDrawingState());
-    if (s.color.a > 0) {
+    half4 color = getColor();
+    if (color.a > 0) {
         Mesh mesh(Mesh::TRIANGLE_FAN, 4, 2);
         computeGeometry(renderArea, mesh, useIdentityTransform);
         auto& engine(mFlinger->getRenderEngine());
         engine.setupLayerBlending(getPremultipledAlpha(), false /* opaque */,
-                                  true /* disableTexture */, s.color);
+                                  true /* disableTexture */, color);
         engine.drawMesh(mesh);
         engine.disableBlending();
     }
diff --git a/services/surfaceflinger/DispSync.cpp b/services/surfaceflinger/DispSync.cpp
index 9e01fd0..7acbd11 100644
--- a/services/surfaceflinger/DispSync.cpp
+++ b/services/surfaceflinger/DispSync.cpp
@@ -209,6 +209,28 @@
         return BAD_VALUE;
     }
 
+    status_t changePhaseOffset(DispSync::Callback* callback, nsecs_t phase) {
+        if (kTraceDetailedInfo) ATRACE_CALL();
+        Mutex::Autolock lock(mMutex);
+
+        for (size_t i = 0; i < mEventListeners.size(); i++) {
+            if (mEventListeners[i].mCallback == callback) {
+                EventListener& listener = mEventListeners.editItemAt(i);
+                const nsecs_t oldPhase = listener.mPhase;
+                listener.mPhase = phase;
+
+                // Pretend that the last time this event was handled at the same frame but with the
+                // new offset to allow for a seamless offset change without double-firing or
+                // skipping.
+                listener.mLastEventTime -= (oldPhase - phase);
+                mCond.signal();
+                return NO_ERROR;
+            }
+        }
+
+        return BAD_VALUE;
+    }
+
     // This method is only here to handle the !SurfaceFlinger::hasSyncFramework
     // case.
     bool hasAnyEventListeners() {
@@ -487,6 +509,11 @@
     return mThread->removeEventListener(callback);
 }
 
+status_t DispSync::changePhaseOffset(Callback* callback, nsecs_t phase) {
+    Mutex::Autolock lock(mMutex);
+    return mThread->changePhaseOffset(callback, phase);
+}
+
 void DispSync::setPeriod(nsecs_t period) {
     Mutex::Autolock lock(mMutex);
     mPeriod = period;
diff --git a/services/surfaceflinger/DispSync.h b/services/surfaceflinger/DispSync.h
index 9336f4d..077256a 100644
--- a/services/surfaceflinger/DispSync.h
+++ b/services/surfaceflinger/DispSync.h
@@ -113,6 +113,11 @@
     // DispSync object.
     status_t removeEventListener(Callback* callback);
 
+    // changePhaseOffset changes the phase offset of an already-registered event callback. The
+    // method will make sure that there is no skipping or double-firing on the listener per frame,
+    // even when changing the offsets multiple times.
+    status_t changePhaseOffset(Callback* callback, nsecs_t phase);
+
     // computeNextRefresh computes when the next refresh is expected to begin.
     // The periodOffset value can be used to move forward or backward; an
     // offset of zero is the next refresh, -1 is the previous refresh, 1 is
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 58a774b..d90ab1d 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -55,6 +55,7 @@
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
 using android::ui::ColorMode;
+using android::ui::RenderIntent;
 
 /*
  * Initialize the display to the specified values.
@@ -75,8 +76,8 @@
         std::unique_ptr<RE::Surface> renderSurface,
         int displayWidth,
         int displayHeight,
-        bool supportWideColor,
-        bool supportHdr,
+        bool hasWideColorGamut,
+        bool hasHdr10,
         int initialPowerMode)
     : lastCompositionHadVisibleLayers(false),
       mFlinger(flinger),
@@ -98,8 +99,8 @@
       mActiveConfig(0),
       mActiveColorMode(ColorMode::NATIVE),
       mColorTransform(HAL_COLOR_TRANSFORM_IDENTITY),
-      mDisplayHasWideColor(supportWideColor),
-      mDisplayHasHdr(supportHdr)
+      mHasWideColorGamut(hasWideColorGamut),
+      mHasHdr10(hasHdr10)
 {
     // clang-format on
 
@@ -268,6 +269,14 @@
     return mActiveColorMode;
 }
 
+RenderIntent DisplayDevice::getActiveRenderIntent() const {
+    return mActiveRenderIntent;
+}
+
+void DisplayDevice::setActiveRenderIntent(RenderIntent renderIntent) {
+    mActiveRenderIntent = renderIntent;
+}
+
 void DisplayDevice::setColorTransform(const mat4& transform) {
     const bool isIdentity = (transform == mat4());
     mColorTransform =
@@ -279,10 +288,15 @@
 }
 
 void DisplayDevice::setCompositionDataSpace(ui::Dataspace dataspace) {
+    mCompositionDataSpace = dataspace;
     ANativeWindow* const window = mNativeWindow.get();
     native_window_set_buffers_data_space(window, static_cast<android_dataspace>(dataspace));
 }
 
+ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
+    return mCompositionDataSpace;
+}
+
 // ----------------------------------------------------------------------------
 
 void DisplayDevice::setLayerStack(uint32_t stack) {
@@ -464,8 +478,8 @@
                         tr[0][1], tr[1][1], tr[2][1], tr[0][2], tr[1][2], tr[2][2]);
     auto const surface = static_cast<Surface*>(window);
     ui::Dataspace dataspace = surface->getBuffersDataSpace();
-    result.appendFormat("   wideColor=%d, hdr=%d, colorMode=%s, dataspace: %s (%d)\n",
-                        mDisplayHasWideColor, mDisplayHasHdr,
+    result.appendFormat("   wideColorGamut=%d, hdr10=%d, colorMode=%s, dataspace: %s (%d)\n",
+                        mHasWideColorGamut, mHasHdr10,
                         decodeColorMode(mActiveColorMode).c_str(),
                         dataspaceDetails(static_cast<android_dataspace>(dataspace)).c_str(), dataspace);
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index e844d11..bedf765 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -84,8 +84,8 @@
             std::unique_ptr<RE::Surface> renderSurface,
             int displayWidth,
             int displayHeight,
-            bool supportWideColor,
-            bool supportHdr,
+            bool hasWideColorGamut,
+            bool hasHdr10,
             int initialPowerMode);
     // clang-format on
 
@@ -135,8 +135,8 @@
     // machine happy without actually queueing a buffer if nothing has changed
     status_t beginFrame(bool mustRecompose) const;
     status_t prepareFrame(HWComposer& hwc);
-    bool getWideColorSupport() const { return mDisplayHasWideColor; }
-    bool getHdrSupport() const { return mDisplayHasHdr; }
+    bool hasWideColorGamut() const { return mHasWideColorGamut; }
+    bool hasHdr10() const { return mHasHdr10; }
 
     void swapBuffers(HWComposer& hwc) const;
 
@@ -165,9 +165,12 @@
 
     ui::ColorMode getActiveColorMode() const;
     void setActiveColorMode(ui::ColorMode mode);
+    ui::RenderIntent getActiveRenderIntent() const;
+    void setActiveRenderIntent(ui::RenderIntent renderIntent);
     android_color_transform_t getColorTransform() const;
     void setColorTransform(const mat4& transform);
     void setCompositionDataSpace(ui::Dataspace dataspace);
+    ui::Dataspace getCompositionDataSpace() const;
 
     /* ------------------------------------------------------------------------
      * Display active config management.
@@ -241,14 +244,17 @@
     int mActiveConfig;
     // current active color mode
     ui::ColorMode mActiveColorMode;
+    // Current active render intent.
+    ui::RenderIntent mActiveRenderIntent;
+    ui::Dataspace mCompositionDataSpace;
     // Current color transform
     android_color_transform_t mColorTransform;
 
     // Need to know if display is wide-color capable or not.
     // Initialized by SurfaceFlinger when the DisplayDevice is created.
     // Fed to RenderEngine during composition.
-    bool mDisplayHasWideColor;
-    bool mDisplayHasHdr;
+    bool mHasWideColorGamut;
+    bool mHasHdr10;
 };
 
 struct DisplayDeviceState {
@@ -290,9 +296,9 @@
     bool isSecure() const override { return mDevice->isSecure(); }
     bool needsFiltering() const override { return mDevice->needsFiltering(); }
     Rect getSourceCrop() const override { return mSourceCrop; }
-    bool getWideColorSupport() const override { return mDevice->getWideColorSupport(); }
-    ui::ColorMode getActiveColorMode() const override {
-        return mDevice->getActiveColorMode();
+    bool getWideColorSupport() const override { return mDevice->hasWideColorGamut(); }
+    ui::Dataspace getDataSpace() const override {
+        return mDevice->getCompositionDataSpace();
     }
 
 private:
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index bbffd0a..5daa87e 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -350,15 +350,29 @@
         std::vector<ColorMode>* outModes)
 {
     Error error = kDefaultError;
-    mClient->getColorModes(display,
-            [&](const auto& tmpError, const auto& tmpModes) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
 
-                *outModes = tmpModes;
-            });
+    if (mClient_2_2) {
+        mClient_2_2->getColorModes_2_2(display,
+                [&](const auto& tmpError, const auto& tmpModes) {
+                    error = tmpError;
+                    if (error != Error::NONE) {
+                        return;
+                    }
+
+                    *outModes = tmpModes;
+                });
+    } else {
+        mClient->getColorModes(display,
+                [&](const auto& tmpError, const auto& tmpModes) {
+                    error = tmpError;
+                    if (error != Error::NONE) {
+                        return;
+                    }
+                    for (V1_0::ColorMode colorMode : tmpModes) {
+                        outModes->push_back(static_cast<ColorMode>(colorMode));
+                    }
+                });
+    }
 
     return error;
 }
@@ -479,25 +493,6 @@
     return error;
 }
 
-Error Composer::getPerFrameMetadataKeys(
-        Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) {
-    if (!mClient_2_2) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError;
-    mClient_2_2->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
-        error = tmpError;
-        if (error != Error::NONE) {
-            return;
-        }
-
-        *outKeys = tmpKeys;
-    });
-
-    return error;
-}
-
 Error Composer::getReleaseFences(Display display,
         std::vector<Layer>* outLayers, std::vector<int>* outReleaseFences)
 {
@@ -553,9 +548,16 @@
     return Error::NONE;
 }
 
-Error Composer::setColorMode(Display display, ColorMode mode)
+Error Composer::setColorMode(Display display, ColorMode mode,
+        RenderIntent renderIntent)
 {
-    auto ret = mClient->setColorMode(display, mode);
+    hardware::Return<Error> ret(kDefaultError);
+    if (mClient_2_2) {
+        ret = mClient_2_2->setColorMode_2_2(display, mode, renderIntent);
+    } else {
+        ret = mClient->setColorMode(display,
+                static_cast<V1_0::ColorMode>(mode));
+    }
     return unwrapRet(ret);
 }
 
@@ -862,39 +864,44 @@
     }
 
     Error error = kDefaultError;
-    auto ret = mClient->executeCommands(commandLength, commandHandles,
-            [&](const auto& tmpError, const auto& tmpOutChanged,
-                const auto& tmpOutLength, const auto& tmpOutHandles)
-            {
-                error = tmpError;
+    hardware::Return<void> ret;
+    auto hidl_callback = [&](const auto& tmpError, const auto& tmpOutChanged,
+                             const auto& tmpOutLength, const auto& tmpOutHandles)
+                         {
+                             error = tmpError;
 
-                // set up new output command queue if necessary
-                if (error == Error::NONE && tmpOutChanged) {
-                    error = kDefaultError;
-                    mClient->getOutputCommandQueue(
-                            [&](const auto& tmpError,
-                                const auto& tmpDescriptor)
-                            {
-                                error = tmpError;
-                                if (error != Error::NONE) {
-                                    return;
-                                }
+                             // set up new output command queue if necessary
+                             if (error == Error::NONE && tmpOutChanged) {
+                                 error = kDefaultError;
+                                 mClient->getOutputCommandQueue(
+                                     [&](const auto& tmpError,
+                                         const auto& tmpDescriptor)
+                                     {
+                                         error = tmpError;
+                                         if (error != Error::NONE) {
+                                             return;
+                                     }
 
-                                mReader.setMQDescriptor(tmpDescriptor);
-                            });
-                }
+                                     mReader.setMQDescriptor(tmpDescriptor);
+                                 });
+                             }
 
-                if (error != Error::NONE) {
-                    return;
-                }
+                             if (error != Error::NONE) {
+                                 return;
+                             }
 
-                if (mReader.readQueue(tmpOutLength, tmpOutHandles)) {
-                    error = mReader.parse();
-                    mReader.reset();
-                } else {
-                    error = Error::NO_RESOURCES;
-                }
-            });
+                             if (mReader.readQueue(tmpOutLength, tmpOutHandles)) {
+                                 error = mReader.parse();
+                                 mReader.reset();
+                             } else {
+                                 error = Error::NO_RESOURCES;
+                             }
+                         };
+    if (mClient_2_2) {
+        ret = mClient_2_2->executeCommands_2_2(commandLength, commandHandles, hidl_callback);
+    } else {
+        ret = mClient->executeCommands(commandLength, commandHandles, hidl_callback);
+    }
     // executeCommands can fail because of out-of-fd and we do not want to
     // abort() in that case
     if (!ret.isOk()) {
@@ -925,6 +932,68 @@
     return error;
 }
 
+// Composer HAL 2.2
+
+Error Composer::getPerFrameMetadataKeys(
+        Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) {
+    if (!mClient_2_2) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError;
+    mClient_2_2->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outKeys = tmpKeys;
+    });
+
+    return error;
+}
+
+Error Composer::getRenderIntents(Display display, ColorMode colorMode,
+        std::vector<RenderIntent>* outRenderIntents) {
+    if (!mClient_2_2) {
+        outRenderIntents->push_back(RenderIntent::COLORIMETRIC);
+        return Error::NONE;
+    }
+
+    Error error = kDefaultError;
+    mClient_2_2->getRenderIntents(display, colorMode,
+            [&](const auto& tmpError, const auto& tmpKeys) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outRenderIntents = tmpKeys;
+    });
+
+    return error;
+}
+
+Error Composer::getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix)
+{
+    if (!mClient_2_2) {
+        *outMatrix = mat4();
+        return Error::NONE;
+    }
+
+    Error error = kDefaultError;
+    mClient_2_2->getDataspaceSaturationMatrix(dataspace, [&](const auto& tmpError, const auto& tmpMatrix) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outMatrix = mat4(tmpMatrix.data());
+    });
+
+    return error;
+}
+
 CommandReader::~CommandReader()
 {
     resetData();
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 98f2f9d..08901f6 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -29,6 +29,7 @@
 #include <android/hardware/graphics/composer/2.2/IComposerClient.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <gui/HdrMetadata.h>
+#include <math/mat4.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
 
@@ -38,12 +39,13 @@
 
 using android::frameworks::vr::composer::V1_0::IVrComposerClient;
 
-using android::hardware::graphics::common::V1_0::ColorMode;
 using android::hardware::graphics::common::V1_0::ColorTransform;
 using android::hardware::graphics::common::V1_0::Hdr;
 using android::hardware::graphics::common::V1_0::Transform;
+using android::hardware::graphics::common::V1_1::ColorMode;
 using android::hardware::graphics::common::V1_1::Dataspace;
 using android::hardware::graphics::common::V1_1::PixelFormat;
+using android::hardware::graphics::common::V1_1::RenderIntent;
 
 using android::hardware::graphics::composer::V2_1::Config;
 using android::hardware::graphics::composer::V2_1::Display;
@@ -114,9 +116,6 @@
                                      float* outMaxLuminance, float* outMaxAverageLuminance,
                                      float* outMinLuminance) = 0;
 
-    virtual Error getPerFrameMetadataKeys(
-            Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) = 0;
-
     virtual Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                                    std::vector<int>* outReleaseFences) = 0;
 
@@ -132,7 +131,7 @@
     virtual Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                                   int acquireFence, Dataspace dataspace,
                                   const std::vector<IComposerClient::Rect>& damage) = 0;
-    virtual Error setColorMode(Display display, ColorMode mode) = 0;
+    virtual Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) = 0;
     virtual Error setColorTransform(Display display, const float* matrix, ColorTransform hint) = 0;
     virtual Error setOutputBuffer(Display display, const native_handle_t* buffer,
                                   int releaseFence) = 0;
@@ -175,6 +174,13 @@
                                         const std::vector<IComposerClient::Rect>& visible) = 0;
     virtual Error setLayerZOrder(Display display, Layer layer, uint32_t z) = 0;
     virtual Error setLayerInfo(Display display, Layer layer, uint32_t type, uint32_t appId) = 0;
+
+    // Composer HAL 2.2
+    virtual Error getPerFrameMetadataKeys(
+            Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) = 0;
+    virtual Error getRenderIntents(Display display, ColorMode colorMode,
+            std::vector<RenderIntent>* outRenderIntents) = 0;
+    virtual Error getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) = 0;
 };
 
 namespace impl {
@@ -306,9 +312,6 @@
     Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
                              float* outMaxAverageLuminance, float* outMinLuminance) override;
 
-    Error getPerFrameMetadataKeys(
-            Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) override;
-
     Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                            std::vector<int>* outReleaseFences) override;
 
@@ -324,7 +327,7 @@
     Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
                           int acquireFence, Dataspace dataspace,
                           const std::vector<IComposerClient::Rect>& damage) override;
-    Error setColorMode(Display display, ColorMode mode) override;
+    Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
     Error setColorTransform(Display display, const float* matrix, ColorTransform hint) override;
     Error setOutputBuffer(Display display, const native_handle_t* buffer,
                           int releaseFence) override;
@@ -364,6 +367,13 @@
     Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
     Error setLayerInfo(Display display, Layer layer, uint32_t type, uint32_t appId) override;
 
+    // Composer HAL 2.2
+    Error getPerFrameMetadataKeys(
+            Display display, std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) override;
+    Error getRenderIntents(Display display, ColorMode colorMode,
+            std::vector<RenderIntent>* outRenderIntents) override;
+    Error getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) override;
+
 private:
     class CommandWriter : public CommandWriterBase {
     public:
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 2686788..5f94bb4 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -50,6 +50,7 @@
 using android::ui::ColorMode;
 using android::ui::Dataspace;
 using android::ui::PixelFormat;
+using android::ui::RenderIntent;
 
 namespace {
 
@@ -369,6 +370,19 @@
     return static_cast<Error>(intError);
 }
 
+Error Display::getRenderIntents(ColorMode colorMode,
+        std::vector<RenderIntent>* outRenderIntents) const
+{
+    auto intError = mComposer.getRenderIntents(mId, colorMode, outRenderIntents);
+    return static_cast<Error>(intError);
+}
+
+Error Display::getDataspaceSaturationMatrix(Dataspace dataspace, android::mat4* outMatrix)
+{
+    auto intError = mComposer.getDataspaceSaturationMatrix(dataspace, outMatrix);
+    return static_cast<Error>(intError);
+}
+
 std::vector<std::shared_ptr<const Display::Config>> Display::getConfigs() const
 {
     std::vector<std::shared_ptr<const Config>> configs;
@@ -526,9 +540,9 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::setColorMode(ColorMode mode)
+Error Display::setColorMode(ColorMode mode, RenderIntent renderIntent)
 {
-    auto intError = mComposer.setColorMode(mId, mode);
+    auto intError = mComposer.setColorMode(mId, mode, renderIntent);
     return static_cast<Error>(intError);
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 9b870e3..e5779d4 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -212,6 +212,11 @@
             std::unordered_map<Layer*, Composition>* outTypes);
     [[clang::warn_unused_result]] Error getColorModes(
             std::vector<android::ui::ColorMode>* outModes) const;
+    [[clang::warn_unused_result]] Error getRenderIntents(
+            android::ui::ColorMode colorMode,
+            std::vector<android::ui::RenderIntent>* outRenderIntents) const;
+    [[clang::warn_unused_result]] Error getDataspaceSaturationMatrix(
+            android::ui::Dataspace dataspace, android::mat4* outMatrix);
 
     // Doesn't call into the HWC2 device, so no errors are possible
     std::vector<std::shared_ptr<const Config>> getConfigs() const;
@@ -236,7 +241,8 @@
             const android::sp<android::Fence>& acquireFence,
             android::ui::Dataspace dataspace);
     [[clang::warn_unused_result]] Error setColorMode(
-            android::ui::ColorMode mode);
+            android::ui::ColorMode mode,
+            android::ui::RenderIntent renderIntent);
     [[clang::warn_unused_result]] Error setColorTransform(
             const android::mat4& matrix, android_color_transform_t hint);
     [[clang::warn_unused_result]] Error setOutputBuffer(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 29e2727..8db8aa6 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -330,17 +330,20 @@
     return modes;
 }
 
-status_t HWComposer::setActiveColorMode(int32_t displayId, ui::ColorMode mode) {
+status_t HWComposer::setActiveColorMode(int32_t displayId, ui::ColorMode mode,
+        ui::RenderIntent renderIntent) {
     if (!isValidDisplay(displayId)) {
         ALOGE("setActiveColorMode: Display %d is not valid", displayId);
         return BAD_INDEX;
     }
 
     auto& displayData = mDisplayData[displayId];
-    auto error = displayData.hwcDisplay->setColorMode(mode);
+    auto error = displayData.hwcDisplay->setColorMode(mode, renderIntent);
     if (error != HWC2::Error::None) {
-        ALOGE("setActiveConfig: Failed to set color mode %d on display %d: "
-                "%s (%d)", mode, displayId, to_string(error).c_str(),
+        ALOGE("setActiveConfig: Failed to set color mode %d"
+                "with render intent %d on display %d: "
+                "%s (%d)", mode, renderIntent, displayId,
+                to_string(error).c_str(),
                 static_cast<int32_t>(error));
         return UNKNOWN_ERROR;
     }
@@ -841,6 +844,44 @@
     return capabilities;
 }
 
+std::vector<ui::RenderIntent> HWComposer::getRenderIntents(int32_t displayId,
+        ui::ColorMode colorMode) const {
+    if (!isValidDisplay(displayId)) {
+        ALOGE("getRenderIntents: Attempted to access invalid display %d",
+                displayId);
+        return {};
+    }
+
+    std::vector<ui::RenderIntent> renderIntents;
+    auto error = mDisplayData[displayId].hwcDisplay->getRenderIntents(colorMode, &renderIntents);
+    if (error != HWC2::Error::None) {
+        ALOGE("getColorModes failed for display %d: %s (%d)", displayId,
+                to_string(error).c_str(), static_cast<int32_t>(error));
+        return std::vector<ui::RenderIntent>();
+    }
+
+    return renderIntents;
+}
+
+mat4 HWComposer::getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace) {
+    if (!isValidDisplay(displayId)) {
+        ALOGE("getDataSpaceSaturationMatrix: Attempted to access invalid display %d",
+                displayId);
+        return {};
+    }
+
+    mat4 matrix;
+    auto error = mDisplayData[displayId].hwcDisplay->getDataspaceSaturationMatrix(dataspace,
+            &matrix);
+    if (error != HWC2::Error::None) {
+        ALOGE("getDataSpaceSaturationMatrix failed for display %d: %s (%d)", displayId,
+                to_string(error).c_str(), static_cast<int32_t>(error));
+        return mat4();
+    }
+
+    return matrix;
+}
+
 // Converts a PixelFormat to a human-readable string.  Max 11 chars.
 // (Could use a table of prefab String8 objects.)
 /*
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 6e2e156..e86d621 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -137,6 +137,11 @@
     // Returns the HDR capabilities of the given display
     std::unique_ptr<HdrCapabilities> getHdrCapabilities(int32_t displayId);
 
+    // Returns the available RenderIntent of the given display.
+    std::vector<ui::RenderIntent> getRenderIntents(int32_t displayId, ui::ColorMode colorMode) const;
+
+    mat4 getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace);
+
     // Events handling ---------------------------------------------------------
 
     // Returns true if successful, false otherwise. The
@@ -161,7 +166,8 @@
 
     std::vector<ui::ColorMode> getColorModes(int32_t displayId) const;
 
-    status_t setActiveColorMode(int32_t displayId, ui::ColorMode mode);
+    status_t setActiveColorMode(int32_t displayId, ui::ColorMode mode,
+            ui::RenderIntent renderIntent);
 
     bool isUsingVrComposer() const;
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 9d356d8..8c0050e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1643,6 +1643,21 @@
     return true;
 }
 
+// Dataspace::UNKNOWN, Dataspace::SRGB, Dataspace::SRGB_LINEAR,
+// Dataspace::V0_SRGB and Dataspace::V0_SRGB_LINEAR are considered legacy
+// SRGB data space for now.
+// Note that Dataspace::V0_SRGB and Dataspace::V0_SRGB_LINEAR are not legacy
+// data space, however since framework doesn't distinguish them out of legacy
+// SRGB, we have to treat them as the same for now.
+bool Layer::isLegacySrgbDataSpace() const {
+    // TODO(lpy) b/77652630, need to figure out when UNKNOWN can be treated as SRGB.
+    return mDrawingState.dataSpace == ui::Dataspace::UNKNOWN ||
+        mDrawingState.dataSpace == ui::Dataspace::SRGB ||
+        mDrawingState.dataSpace == ui::Dataspace::SRGB_LINEAR ||
+        mDrawingState.dataSpace == ui::Dataspace::V0_SRGB ||
+        mDrawingState.dataSpace == ui::Dataspace::V0_SRGB_LINEAR;
+}
+
 void Layer::setParent(const sp<Layer>& layer) {
     mCurrentParent = layer;
 }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 8d2a048..d382a1a 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -297,6 +297,13 @@
     bool reparent(const sp<IBinder>& newParentHandle);
     bool detachChildren();
 
+    // Before color management is introduced, contents on Android have to be
+    // desaturated in order to match what they appears like visually.
+    // With color management, these contents will appear desaturated, thus
+    // needed to be saturated so that they match what they are designed for
+    // visually. When returns true, legacy SRGB data space is passed to HWC.
+    bool isLegacySrgbDataSpace() const;
+
     // If we have received a new buffer this frame, we will pass its surface
     // damage down to hardware composer. Otherwise, we must send a region with
     // one empty rect.
diff --git a/services/surfaceflinger/LayerStats.cpp b/services/surfaceflinger/LayerStats.cpp
index 38ea6ed..04ab121 100644
--- a/services/surfaceflinger/LayerStats.cpp
+++ b/services/surfaceflinger/LayerStats.cpp
@@ -57,11 +57,12 @@
 }
 
 void LayerStats::traverseLayerTreeStatsLocked(
-        std::vector<std::unique_ptr<LayerProtoParser::Layer>> layerTree,
-        const LayerProtoParser::LayerGlobal* layerGlobal, std::vector<std::string>& layerShapeVec) {
-    for (std::unique_ptr<LayerProtoParser::Layer>& layer : layerTree) {
+        const std::vector<std::unique_ptr<LayerProtoParser::Layer>>& layerTree,
+        const LayerProtoParser::LayerGlobal& layerGlobal,
+        std::vector<std::string>* const outLayerShapeVec) {
+    for (const auto& layer : layerTree) {
         if (!layer) continue;
-        traverseLayerTreeStatsLocked(std::move(layer->children), layerGlobal, layerShapeVec);
+        traverseLayerTreeStatsLocked(layer->children, layerGlobal, outLayerShapeVec);
         std::string key = "";
         base::StringAppendF(&key, ",%s", layer->type.c_str());
         base::StringAppendF(&key, ",%s", layerCompositionType(layer->hwcCompositionType));
@@ -70,21 +71,21 @@
         base::StringAppendF(&key, ",%s", layerPixelFormat(layer->activeBuffer.format));
         base::StringAppendF(&key, ",%s", layer->dataspace.c_str());
         base::StringAppendF(&key, ",%s",
-                            destinationLocation(layer->hwcFrame.left, layerGlobal->resolution[0],
+                            destinationLocation(layer->hwcFrame.left, layerGlobal.resolution[0],
                                                 true));
         base::StringAppendF(&key, ",%s",
-                            destinationLocation(layer->hwcFrame.top, layerGlobal->resolution[1],
+                            destinationLocation(layer->hwcFrame.top, layerGlobal.resolution[1],
                                                 false));
         base::StringAppendF(&key, ",%s",
                             destinationSize(layer->hwcFrame.right - layer->hwcFrame.left,
-                                            layerGlobal->resolution[0], true));
+                                            layerGlobal.resolution[0], true));
         base::StringAppendF(&key, ",%s",
                             destinationSize(layer->hwcFrame.bottom - layer->hwcFrame.top,
-                                            layerGlobal->resolution[1], false));
+                                            layerGlobal.resolution[1], false));
         base::StringAppendF(&key, ",%s", scaleRatioWH(layer.get()).c_str());
         base::StringAppendF(&key, ",%s", alpha(static_cast<float>(layer->color.a)));
 
-        layerShapeVec.push_back(key);
+        outLayerShapeVec->push_back(key);
         ALOGV("%s", key.c_str());
     }
 }
@@ -97,7 +98,7 @@
     std::vector<std::string> layerShapeVec;
 
     std::lock_guard<std::mutex> lock(mMutex);
-    traverseLayerTreeStatsLocked(std::move(layerTree), &layerGlobal, layerShapeVec);
+    traverseLayerTreeStatsLocked(layerTree, layerGlobal, &layerShapeVec);
 
     std::string layerShapeKey =
             base::StringPrintf("%d,%s,%s,%s", static_cast<int32_t>(layerShapeVec.size()),
diff --git a/services/surfaceflinger/LayerStats.h b/services/surfaceflinger/LayerStats.h
index 7871fc6..7a190fd 100644
--- a/services/surfaceflinger/LayerStats.h
+++ b/services/surfaceflinger/LayerStats.h
@@ -38,9 +38,9 @@
 private:
     // Traverse layer tree to get all visible layers' stats
     void traverseLayerTreeStatsLocked(
-            std::vector<std::unique_ptr<LayerProtoParser::Layer>> layerTree,
-            const LayerProtoParser::LayerGlobal* layerGlobal,
-            std::vector<std::string>& layerShapeVec);
+            const std::vector<std::unique_ptr<LayerProtoParser::Layer>>& layerTree,
+            const LayerProtoParser::LayerGlobal& layerGlobal,
+            std::vector<std::string>* const outLayerShapeVec);
     // Convert layer's top-left position into 8x8 percentage of the display
     static const char* destinationLocation(int32_t location, int32_t range, bool isHorizontal);
     // Convert layer's size into 8x8 percentage of the display
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index bf0707f..4f812fc 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -25,15 +25,14 @@
     virtual bool isSecure() const = 0;
     virtual bool needsFiltering() const = 0;
     virtual Rect getSourceCrop() const = 0;
+    virtual bool getWideColorSupport() const = 0;
+    virtual ui::Dataspace getDataSpace() const = 0;
 
     virtual void render(std::function<void()> drawLayers) { drawLayers(); }
 
     int getReqHeight() const { return mReqHeight; };
     int getReqWidth() const { return mReqWidth; };
     Transform::orientation_flags getRotationFlags() const { return mRotationFlags; };
-    virtual bool getWideColorSupport() const = 0;
-    virtual ui::ColorMode getActiveColorMode() const = 0;
-
     status_t updateDimensions();
 
 private:
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
index 1eda900..0fb3d28 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
@@ -293,6 +293,7 @@
 }
 
 void GLES20RenderEngine::drawMesh(const Mesh& mesh) {
+    ATRACE_CALL();
     if (mesh.getTexCoordsSize()) {
         glEnableVertexAttribArray(Program::texCoords);
         glVertexAttribPointer(Program::texCoords, mesh.getTexCoordsSize(), GL_FLOAT, GL_FALSE,
@@ -302,6 +303,7 @@
     glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
                           mesh.getByteStride(), mesh.getPositions());
 
+    // TODO(b/73825729) Refactor this code block to handle BT2020 color space properly.
     // DISPLAY_P3 is the only supported wide color output
     if (mPlatformHasWideColor && mOutputDataSpace == Dataspace::DISPLAY_P3) {
         Description wideColorState = mState;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c5bd0eb..a8680e2 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -106,6 +106,7 @@
 using namespace android::hardware::configstore::V1_0;
 using ui::ColorMode;
 using ui::Dataspace;
+using ui::RenderIntent;
 
 namespace {
 class ConditionalLock {
@@ -156,6 +157,31 @@
     return std::string(value) == "true";
 }
 
+DisplayColorSetting toDisplayColorSetting(int value) {
+    switch(value) {
+        case 0:
+            return DisplayColorSetting::MANAGED;
+        case 1:
+            return DisplayColorSetting::UNMANAGED;
+        case 2:
+            return DisplayColorSetting::ENHANCED;
+        default:
+            return DisplayColorSetting::MANAGED;
+    }
+}
+
+std::string decodeDisplayColorSetting(DisplayColorSetting displayColorSetting) {
+    switch(displayColorSetting) {
+        case DisplayColorSetting::MANAGED:
+            return std::string("Natural Mode");
+        case DisplayColorSetting::UNMANAGED:
+            return std::string("Saturated Mode");
+        case DisplayColorSetting::ENHANCED:
+            return std::string("Auto Color Mode");
+    }
+    return std::string("Unknown Display Color Setting");
+}
+
 NativeWindowSurface::~NativeWindowSurface() = default;
 
 namespace impl {
@@ -295,6 +321,12 @@
     auto listSize = property_get_int32("debug.sf.max_igbp_list_size", int32_t(defaultListSize));
     mMaxGraphicBufferProducerListSize = (listSize > 0) ? size_t(listSize) : defaultListSize;
 
+    property_get("debug.sf.early_phase_offset_ns", value, "0");
+    const int earlyWakeupOffsetOffsetNs = atoi(value);
+    ALOGI_IF(earlyWakeupOffsetOffsetNs != 0, "Enabling separate early offset");
+    mVsyncModulator.setPhaseOffsets(sfVsyncPhaseOffsetNs - earlyWakeupOffsetOffsetNs,
+            sfVsyncPhaseOffsetNs);
+
     // We should be reading 'persist.sys.sf.color_saturation' here
     // but since /data may be encrypted, we need to wait until after vold
     // comes online to attempt to read the property. The property is
@@ -521,19 +553,10 @@
             return;
         }
 
-        // Remove the listener with the old offset
-        status_t err = mDispSync->removeEventListener(
-                static_cast<DispSync::Callback*>(this));
+        status_t err = mDispSync->changePhaseOffset(static_cast<DispSync::Callback*>(this),
+                mPhaseOffset);
         if (err != NO_ERROR) {
-            ALOGE("error unregistering vsync callback: %s (%d)",
-                    strerror(-err), err);
-        }
-
-        // Add a listener with the new offset
-        err = mDispSync->addEventListener(mName, mPhaseOffset,
-                static_cast<DispSync::Callback*>(this));
-        if (err != NO_ERROR) {
-            ALOGE("error registering vsync callback: %s (%d)",
+            ALOGE("error changing vsync offset: %s (%d)",
                     strerror(-err), err);
         }
     }
@@ -622,6 +645,7 @@
     mSFEventThread = std::make_unique<impl::EventThread>(mSfEventThreadSource.get(), *this, true,
                                                          "sfEventThread");
     mEventQueue->setEventThread(mSFEventThread.get());
+    mVsyncModulator.setEventThread(mSFEventThread.get());
 
     // Get a RenderEngine for the given display / config (can't fail)
     getBE().mRenderEngine =
@@ -691,6 +715,9 @@
         ALOGE("Run StartPropertySetThread failed!");
     }
 
+    mLegacySrgbSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(HWC_DISPLAY_PRIMARY,
+            Dataspace::SRGB_LINEAR);
+
     ALOGV("Done initializing");
 }
 
@@ -698,14 +725,13 @@
     char value[PROPERTY_VALUE_MAX];
 
     property_get("persist.sys.sf.color_saturation", value, "1.0");
-    mSaturation = atof(value);
-    ALOGV("Saturation is set to %.2f", mSaturation);
+    mGlobalSaturationFactor = atof(value);
+    ALOGV("Saturation is set to %.2f", mGlobalSaturationFactor);
 
     property_get("persist.sys.sf.native_mode", value, "0");
-    mForceNativeColorMode = atoi(value) == 1;
-    if (mForceNativeColorMode) {
-        ALOGV("Forcing native color mode");
-    }
+    mDisplayColorSetting = toDisplayColorSetting(atoi(value));
+    ALOGV("Display Color Setting is set to %s.",
+          decodeDisplayColorSetting(mDisplayColorSetting).c_str());
 }
 
 void SurfaceFlinger::startBootAnim() {
@@ -990,11 +1016,29 @@
 }
 
 void SurfaceFlinger::setActiveColorModeInternal(const sp<DisplayDevice>& hw,
-        ColorMode mode) {
+                                                ColorMode mode, Dataspace dataSpace) {
     int32_t type = hw->getDisplayType();
     ColorMode currentMode = hw->getActiveColorMode();
+    Dataspace currentDataSpace = hw->getCompositionDataSpace();
+    RenderIntent currentRenderIntent = hw->getActiveRenderIntent();
 
-    if (mode == currentMode) {
+    // Natural Mode means it's color managed and the color must be right,
+    // thus we pick RenderIntent::COLORIMETRIC as render intent.
+    // Native Mode means the display is not color managed, and whichever
+    // render intent is picked doesn't matter, thus return
+    // RenderIntent::COLORIMETRIC as default here.
+    RenderIntent renderIntent = RenderIntent::COLORIMETRIC;
+
+    // In Auto Color Mode, we want to strech to panel color space, right now
+    // only the built-in display supports it.
+    if (mDisplayColorSetting == DisplayColorSetting::ENHANCED &&
+        mBuiltinDisplaySupportsEnhance &&
+        hw->getDisplayType() == DisplayDevice::DISPLAY_PRIMARY) {
+        renderIntent = RenderIntent::ENHANCE;
+    }
+
+    if (mode == currentMode && dataSpace == currentDataSpace &&
+        renderIntent == currentRenderIntent) {
         return;
     }
 
@@ -1003,11 +1047,15 @@
         return;
     }
 
-    ALOGD("Set active color mode: %s (%d), type=%d", decodeColorMode(mode).c_str(), mode,
-          hw->getDisplayType());
-
     hw->setActiveColorMode(mode);
-    getHwComposer().setActiveColorMode(type, mode);
+    hw->setCompositionDataSpace(dataSpace);
+    hw->setActiveRenderIntent(renderIntent);
+    getHwComposer().setActiveColorMode(type, mode, renderIntent);
+
+    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), type=%d",
+          decodeColorMode(mode).c_str(), mode,
+          decodeRenderIntent(renderIntent).c_str(), renderIntent,
+          hw->getDisplayType());
 }
 
 
@@ -1038,7 +1086,7 @@
                 ALOGW("Attempt to set active color mode %s %d for virtual display",
                       decodeColorMode(mMode).c_str(), mMode);
             } else {
-                mFlinger.setActiveColorModeInternal(hw, mMode);
+                mFlinger.setActiveColorModeInternal(hw, mMode, Dataspace::UNKNOWN);
             }
             return true;
         }
@@ -1073,7 +1121,7 @@
     std::unique_ptr<HdrCapabilities> capabilities =
             getBE().mHwc->getHdrCapabilities(displayDevice->getHwcDisplayId());
     if (capabilities) {
-        if (displayDevice->getWideColorSupport() && !displayDevice->getHdrSupport()) {
+        if (displayDevice->hasWideColorGamut() && !displayDevice->hasHdr10()) {
             // insert HDR10 as we will force client composition for HDR10
             // layers
             std::vector<int32_t> types = capabilities->getSupportedHdrTypes();
@@ -1497,6 +1545,7 @@
         mHadClientComposition = mHadClientComposition ||
                 getBE().mHwc->hasClientComposition(displayDevice->getHwcDisplayId());
     }
+    mVsyncModulator.setLastFrameUsedRenderEngine(mHadClientComposition);
 
     mLayersWithQueuedFrames.clear();
 }
@@ -1821,73 +1870,78 @@
 }
 
 mat4 SurfaceFlinger::computeSaturationMatrix() const {
-    if (mSaturation == 1.0f) {
+    if (mGlobalSaturationFactor == 1.0f) {
         return mat4();
     }
 
     // Rec.709 luma coefficients
     float3 luminance{0.213f, 0.715f, 0.072f};
-    luminance *= 1.0f - mSaturation;
+    luminance *= 1.0f - mGlobalSaturationFactor;
     return mat4(
-        vec4{luminance.r + mSaturation, luminance.r, luminance.r, 0.0f},
-        vec4{luminance.g, luminance.g + mSaturation, luminance.g, 0.0f},
-        vec4{luminance.b, luminance.b, luminance.b + mSaturation, 0.0f},
+        vec4{luminance.r + mGlobalSaturationFactor, luminance.r, luminance.r, 0.0f},
+        vec4{luminance.g, luminance.g + mGlobalSaturationFactor, luminance.g, 0.0f},
+        vec4{luminance.b, luminance.b, luminance.b + mGlobalSaturationFactor, 0.0f},
         vec4{0.0f, 0.0f, 0.0f, 1.0f}
     );
 }
 
-// pickColorMode translates a given dataspace into the best available color mode.
-// Currently only support sRGB and Display-P3.
-ColorMode SurfaceFlinger::pickColorMode(Dataspace dataSpace) const {
-    if (mForceNativeColorMode) {
-        return ColorMode::NATIVE;
+// Returns a dataspace that fits all visible layers.  The returned dataspace
+// can only be one of
+//
+//  - Dataspace::V0_SRGB
+//  - Dataspace::DISPLAY_P3
+//  - Dataspace::V0_SCRGB_LINEAR
+// TODO(b/73825729) Add BT2020 data space.
+ui::Dataspace SurfaceFlinger::getBestDataspace(
+        const sp<const DisplayDevice>& displayDevice) const {
+    Dataspace bestDataspace = Dataspace::V0_SRGB;
+    for (const auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
+        switch (layer->getDataSpace()) {
+            case Dataspace::V0_SCRGB:
+            case Dataspace::V0_SCRGB_LINEAR:
+                // return immediately
+                return Dataspace::V0_SCRGB_LINEAR;
+            case Dataspace::BT2020_PQ:
+            case Dataspace::BT2020_ITU_PQ:
+                // Historically, HDR dataspaces are ignored by SurfaceFlinger. But
+                // since SurfaceFlinger simulates HDR support now, it should honor
+                // them unless there is also native support.
+                if (!displayDevice->hasHdr10()) {
+                    return Dataspace::V0_SCRGB_LINEAR;
+                }
+                break;
+            case Dataspace::DISPLAY_P3:
+                bestDataspace = Dataspace::DISPLAY_P3;
+                break;
+            default:
+                break;
+        }
     }
 
-    switch (dataSpace) {
-        // treat Unknown as regular SRGB buffer, since that's what the rest of the
-        // system expects.
-        case Dataspace::UNKNOWN:
-        case Dataspace::SRGB:
-        case Dataspace::V0_SRGB:
-            return ColorMode::SRGB;
-            break;
-
-        case Dataspace::DISPLAY_P3:
-            return ColorMode::DISPLAY_P3;
-            break;
-
-        default:
-            // TODO (courtneygo): Do we want to assert an error here?
-            ALOGE("No color mode mapping for %s (%#x)",
-                  dataspaceDetails(static_cast<android_dataspace>(dataSpace)).c_str(),
-                  dataSpace);
-            return ColorMode::SRGB;
-            break;
-    }
+    return bestDataspace;
 }
 
-Dataspace SurfaceFlinger::bestTargetDataSpace(
-        Dataspace a, Dataspace b, bool hasHdr) const {
-    // Only support sRGB and Display-P3 right now.
-    if (a == Dataspace::DISPLAY_P3 || b == Dataspace::DISPLAY_P3) {
-        return Dataspace::DISPLAY_P3;
-    }
-    if (a == Dataspace::V0_SCRGB_LINEAR || b == Dataspace::V0_SCRGB_LINEAR) {
-        return Dataspace::DISPLAY_P3;
-    }
-    if (a == Dataspace::V0_SCRGB || b == Dataspace::V0_SCRGB) {
-        return Dataspace::DISPLAY_P3;
-    }
-    if (!hasHdr) {
-        if (a == Dataspace::BT2020_PQ || b == Dataspace::BT2020_PQ) {
-            return Dataspace::DISPLAY_P3;
-        }
-        if (a == Dataspace::BT2020_ITU_PQ || b == Dataspace::BT2020_ITU_PQ) {
-            return Dataspace::DISPLAY_P3;
-        }
+// Pick the ColorMode / Dataspace for the display device.
+// TODO(b/73825729) Add BT2020 color mode.
+void SurfaceFlinger::pickColorMode(const sp<DisplayDevice>& displayDevice,
+        ColorMode* outMode, Dataspace* outDataSpace) const {
+    if (mDisplayColorSetting == DisplayColorSetting::UNMANAGED) {
+        *outMode = ColorMode::NATIVE;
+        *outDataSpace = Dataspace::UNKNOWN;
+        return;
     }
 
-    return Dataspace::V0_SRGB;
+    switch (getBestDataspace(displayDevice)) {
+        case Dataspace::DISPLAY_P3:
+        case Dataspace::V0_SCRGB_LINEAR:
+            *outMode = ColorMode::DISPLAY_P3;
+            *outDataSpace = Dataspace::DISPLAY_P3;
+            break;
+        default:
+            *outMode = ColorMode::SRGB;
+            *outDataSpace = Dataspace::V0_SRGB;
+            break;
+    }
 }
 
 void SurfaceFlinger::setUpHWComposer() {
@@ -1970,7 +2024,7 @@
         for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
             if ((layer->getDataSpace() == Dataspace::BT2020_PQ ||
                  layer->getDataSpace() == Dataspace::BT2020_ITU_PQ) &&
-                    !displayDevice->getHdrSupport()) {
+                    !displayDevice->hasHdr10()) {
                 layer->forceClientComposition(hwcId);
             }
 
@@ -1984,19 +2038,10 @@
         }
 
         if (hasWideColorDisplay) {
-            ColorMode newColorMode;
-            Dataspace newDataSpace = Dataspace::V0_SRGB;
-
-            for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
-                newDataSpace = bestTargetDataSpace(layer->getDataSpace(), newDataSpace,
-                        displayDevice->getHdrSupport());
-                ALOGV("layer: %s, dataspace: %s (%#x), newDataSpace: %s (%#x)",
-                      layer->getName().string(), dataspaceDetails(static_cast<android_dataspace>(layer->getDataSpace())).c_str(),
-                      layer->getDataSpace(), dataspaceDetails(static_cast<android_dataspace>(newDataSpace)).c_str(), newDataSpace);
-            }
-            newColorMode = pickColorMode(newDataSpace);
-
-            setActiveColorModeInternal(displayDevice, newColorMode);
+            ColorMode colorMode;
+            Dataspace dataSpace;
+            pickColorMode(displayDevice, &colorMode, &dataSpace);
+            setActiveColorModeInternal(displayDevice, colorMode, dataSpace);
         }
     }
 
@@ -2121,6 +2166,7 @@
     // with mStateLock held to guarantee that mCurrentState won't change
     // until the transaction is committed.
 
+    mVsyncModulator.setTransactionStart(VSyncModulator::TransactionStart::NORMAL);
     transactionFlags = getTransactionFlags(eTransactionMask);
     handleTransactionLocked(transactionFlags);
 
@@ -2203,29 +2249,40 @@
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
         const wp<IBinder>& display, int hwcId, const DisplayDeviceState& state,
         const sp<DisplaySurface>& dispSurface, const sp<IGraphicBufferProducer>& producer) {
-    bool hasWideColorSupport = false;
+    bool hasWideColorGamut = false;
     if (hasWideColorDisplay) {
-        std::vector<ColorMode> modes = getHwComposer().getColorModes(state.type);
+        std::vector<ColorMode> modes = getHwComposer().getColorModes(hwcId);
         for (ColorMode colorMode : modes) {
             switch (colorMode) {
                 case ColorMode::DISPLAY_P3:
                 case ColorMode::ADOBE_RGB:
                 case ColorMode::DCI_P3:
-                    hasWideColorSupport = true;
+                    hasWideColorGamut = true;
                     break;
+                // TODO(lpy) Handle BT2020, BT2100_PQ and BT2100_HLG properly.
                 default:
                     break;
             }
+
+            std::vector<RenderIntent> renderIntents = getHwComposer().getRenderIntents(hwcId,
+                                                                                       colorMode);
+            if (state.type == DisplayDevice::DISPLAY_PRIMARY) {
+                for (auto intent : renderIntents) {
+                    if (intent == RenderIntent::ENHANCE) {
+                        mBuiltinDisplaySupportsEnhance = true;
+                        break;
+                    }
+                }
+            }
         }
     }
 
-    bool hasHdrSupport = false;
-    std::unique_ptr<HdrCapabilities> hdrCapabilities =
-            getHwComposer().getHdrCapabilities(state.type);
+    bool hasHdr10 = false;
+    std::unique_ptr<HdrCapabilities> hdrCapabilities = getHwComposer().getHdrCapabilities(hwcId);
     if (hdrCapabilities) {
         const std::vector<int32_t> types = hdrCapabilities->getSupportedHdrTypes();
         auto iter = std::find(types.cbegin(), types.cend(), HAL_HDR_HDR10);
-        hasHdrSupport = iter != types.cend();
+        hasHdr10 = iter != types.cend();
     }
 
     auto nativeWindowSurface = mCreateNativeWindowSurface(producer);
@@ -2259,18 +2316,19 @@
     sp<DisplayDevice> hw =
             new DisplayDevice(this, state.type, hwcId, state.isSecure, display, nativeWindow,
                               dispSurface, std::move(renderSurface), displayWidth, displayHeight,
-                              hasWideColorSupport, hasHdrSupport, initialPowerMode);
+                              hasWideColorGamut, hasHdr10, initialPowerMode);
 
     if (maxFrameBufferAcquiredBuffers >= 3) {
         nativeWindowSurface->preallocateBuffers();
     }
 
     ColorMode defaultColorMode = ColorMode::NATIVE;
-    if (hasWideColorSupport) {
+    Dataspace defaultDataSpace = Dataspace::UNKNOWN;
+    if (hasWideColorGamut) {
         defaultColorMode = ColorMode::SRGB;
+        defaultDataSpace = Dataspace::V0_SRGB;
     }
-    setActiveColorModeInternal(hw, defaultColorMode);
-    hw->setCompositionDataSpace(Dataspace::UNKNOWN);
+    setActiveColorModeInternal(hw, defaultColorMode, defaultDataSpace);
     hw->setLayerStack(state.layerStack);
     hw->setProjection(state.orientation, state.viewport, state.frame);
     hw->setDisplayName(state.displayName);
@@ -2821,23 +2879,27 @@
     const Region bounds(displayDevice->bounds());
     const DisplayRenderArea renderArea(displayDevice);
     const auto hwcId = displayDevice->getHwcDisplayId();
+    const bool hasClientComposition = getBE().mHwc->hasClientComposition(hwcId);
+    const bool hasDeviceComposition = getBE().mHwc->hasDeviceComposition(hwcId);
+    const bool skipClientColorTransform = getBE().mHwc->hasCapability(
+        HWC2::Capability::SkipClientColorTransform);
+    ATRACE_INT("hasClientComposition", hasClientComposition);
 
     mat4 oldColorMatrix;
-    const bool applyColorMatrix = !getBE().mHwc->hasDeviceComposition(hwcId) &&
-            !getBE().mHwc->hasCapability(HWC2::Capability::SkipClientColorTransform);
+    mat4 legacySrgbSaturationMatrix = mLegacySrgbSaturationMatrix;
+    const bool applyColorMatrix = !hasDeviceComposition && !skipClientColorTransform;
     if (applyColorMatrix) {
-        mat4 colorMatrix = mColorMatrix * mDaltonizer();
+        mat4 colorMatrix = mColorMatrix * computeSaturationMatrix() * mDaltonizer();
         oldColorMatrix = getRenderEngine().setupColorTransform(colorMatrix);
+        legacySrgbSaturationMatrix = colorMatrix * legacySrgbSaturationMatrix;
     }
 
-    bool hasClientComposition = getBE().mHwc->hasClientComposition(hwcId);
     if (hasClientComposition) {
         ALOGV("hasClientComposition");
 
         Dataspace outputDataspace = Dataspace::UNKNOWN;
-        if (displayDevice->getWideColorSupport() &&
-              displayDevice->getActiveColorMode() == ColorMode::DISPLAY_P3) {
-            outputDataspace = Dataspace::DISPLAY_P3;
+        if (displayDevice->hasWideColorGamut()) {
+            outputDataspace = displayDevice->getCompositionDataSpace();
         }
         getBE().mRenderEngine->setOutputDataSpace(outputDataspace);
 
@@ -2854,7 +2916,6 @@
         }
 
         // Never touch the framebuffer if we don't have any framebuffer layers
-        const bool hasDeviceComposition = getBE().mHwc->hasDeviceComposition(hwcId);
         if (hasDeviceComposition) {
             // when using overlays, we assume a fully transparent framebuffer
             // NOTE: we could reduce how much we need to clear, for instance
@@ -2929,7 +2990,20 @@
                         break;
                     }
                     case HWC2::Composition::Client: {
+                        // Only apply saturation matrix layer that is legacy SRGB dataspace
+                        // when auto color mode is on.
+                        bool restore = false;
+                        mat4 savedMatrix;
+                        if (mDisplayColorSetting == DisplayColorSetting::ENHANCED &&
+                            layer->isLegacySrgbDataSpace()) {
+                            savedMatrix =
+                                getRenderEngine().setupColorTransform(legacySrgbSaturationMatrix);
+                            restore = true;
+                        }
                         layer->draw(renderArea, clip);
+                        if (restore) {
+                            getRenderEngine().setupColorTransform(savedMatrix);
+                        }
                         break;
                     }
                     default:
@@ -3069,7 +3143,13 @@
 }
 
 uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) {
+    return setTransactionFlags(flags, VSyncModulator::TransactionStart::NORMAL);
+}
+
+uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags,
+        VSyncModulator::TransactionStart transactionStart) {
     uint32_t old = android_atomic_or(flags, &mTransactionFlags);
+    mVsyncModulator.setTransactionStart(transactionStart);
     if ((old & flags)==0) { // wake the server up
         signalTransaction();
     }
@@ -3158,7 +3238,10 @@
         }
 
         // this triggers the transaction
-        setTransactionFlags(transactionFlags);
+        const auto start = (flags & eEarlyWakeup)
+                ? VSyncModulator::TransactionStart::EARLY
+                : VSyncModulator::TransactionStart::NORMAL;
+        setTransactionFlags(transactionFlags, start);
 
         // if this is a synchronous transaction, wait for it to take effect
         // before returning.
@@ -3994,7 +4077,7 @@
 
 void SurfaceFlinger::dumpWideColorInfo(String8& result) const {
     result.appendFormat("hasWideColorDisplay: %d\n", hasWideColorDisplay);
-    result.appendFormat("forceNativeColorMode: %d\n", mForceNativeColorMode);
+    result.appendFormat("DisplayColorSetting: %d\n", mDisplayColorSetting);
 
     // TODO: print out if wide-color mode is active or not
 
@@ -4098,9 +4181,9 @@
     colorizer.bold(result);
     result.append("DispSync configuration: ");
     colorizer.reset(result);
-    result.appendFormat("app phase %" PRId64 " ns, sf phase %" PRId64 " ns, "
-            "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
-        vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs,
+    result.appendFormat("app phase %" PRId64 " ns, sf phase %" PRId64 " ns, early sf phase %" PRId64
+        " ns, present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
+        vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs, mVsyncModulator.getEarlyPhaseOffset(),
         dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
     result.append("\n");
 
@@ -4490,15 +4573,22 @@
                 return NO_ERROR;
             }
             case 1022: { // Set saturation boost
-                mSaturation = std::max(0.0f, std::min(data.readFloat(), 2.0f));
+                mGlobalSaturationFactor = std::max(0.0f, std::min(data.readFloat(), 2.0f));
 
                 invalidateHwcGeometry();
                 repaintEverything();
                 return NO_ERROR;
             }
             case 1023: { // Set native mode
-                mForceNativeColorMode = data.readInt32() == 1;
+                int32_t value = data.readInt32();
+                if (value > 2) {
+                    return BAD_VALUE;
+                }
+                if (value == 2 && !mBuiltinDisplaySupportsEnhance) {
+                    return BAD_VALUE;
+                }
 
+                mDisplayColorSetting = toDisplayColorSetting(value);
                 invalidateHwcGeometry();
                 repaintEverything();
                 return NO_ERROR;
@@ -4525,6 +4615,23 @@
                 reply->writeBool(mTracing.isEnabled());
                 return NO_ERROR;
             }
+            // Is a DisplayColorSetting supported?
+            case 1027: {
+                int32_t value = data.readInt32();
+                switch (value) {
+                    case 0:
+                        reply->writeBool(hasWideColorDisplay);
+                        return NO_ERROR;
+                    case 1:
+                        reply->writeBool(true);
+                        return NO_ERROR;
+                    case 2:
+                        reply->writeBool(mBuiltinDisplaySupportsEnhance);
+                        return NO_ERROR;
+                    default:
+                        return BAD_VALUE;
+                }
+            }
         }
     }
     return err;
@@ -4592,6 +4699,7 @@
                 mCrop(crop),
                 mFlinger(flinger),
                 mChildrenOnly(childrenOnly) {}
+        const Transform& getTransform() const override { return mTransform; }
         Rect getBounds() const override {
             const Layer::State& layerState(mLayer->getDrawingState());
             return Rect(layerState.active.w, layerState.active.h);
@@ -4600,7 +4708,15 @@
         int getWidth() const override { return mLayer->getDrawingState().active.w; }
         bool isSecure() const override { return false; }
         bool needsFiltering() const override { return false; }
-        const Transform& getTransform() const { return mTransform; }
+        Rect getSourceCrop() const override {
+            if (mCrop.isEmpty()) {
+                return getBounds();
+            } else {
+                return mCrop;
+            }
+        }
+        bool getWideColorSupport() const override { return false; }
+        Dataspace getDataSpace() const override { return Dataspace::UNKNOWN; }
 
         class ReparentForDrawing {
         public:
@@ -4629,16 +4745,6 @@
             }
         }
 
-        Rect getSourceCrop() const override {
-            if (mCrop.isEmpty()) {
-                return getBounds();
-            } else {
-                return mCrop;
-            }
-        }
-        bool getWideColorSupport() const override { return false; }
-        ColorMode getActiveColorMode() const override { return ColorMode::NATIVE; }
-
     private:
         const sp<Layer> mLayer;
         const Rect mCrop;
@@ -4810,9 +4916,8 @@
     }
 
     Dataspace outputDataspace = Dataspace::UNKNOWN;
-    if (renderArea.getWideColorSupport() &&
-          renderArea.getActiveColorMode() == ColorMode::DISPLAY_P3) {
-        outputDataspace = Dataspace::DISPLAY_P3;
+    if (renderArea.getWideColorSupport()) {
+        outputDataspace = renderArea.getDataSpace();
     }
     getBE().mRenderEngine->setOutputDataSpace(outputDataspace);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 89c9cfd..14028ff 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -33,6 +33,7 @@
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
 #include <utils/threads.h>
+#include <utils/Trace.h>
 
 #include <ui/FenceTime.h>
 #include <ui/PixelFormat.h>
@@ -54,6 +55,7 @@
 #include "Barrier.h"
 #include "DisplayDevice.h"
 #include "DispSync.h"
+#include "EventThread.h"
 #include "FrameTracker.h"
 #include "LayerStats.h"
 #include "LayerVector.h"
@@ -61,6 +63,7 @@
 #include "SurfaceInterceptor.h"
 #include "SurfaceTracing.h"
 #include "StartPropertySetThread.h"
+#include "VSyncModulator.h"
 
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/HWComposer.h"
@@ -120,6 +123,12 @@
     eTransactionMask          = 0x0f,
 };
 
+enum class DisplayColorSetting : int32_t {
+    MANAGED = 0,
+    UNMANAGED = 1,
+    ENHANCED = 2,
+};
+
 // A thin interface to abstract creating instances of Surface (gui/Surface.h) to
 // use as a NativeWindow.
 class NativeWindowSurface {
@@ -464,7 +473,9 @@
                               bool stateLockHeld);
 
     // Called on the main thread in response to setActiveColorMode()
-    void setActiveColorModeInternal(const sp<DisplayDevice>& hw, ui::ColorMode colorMode);
+    void setActiveColorModeInternal(const sp<DisplayDevice>& hw,
+                                    ui::ColorMode colorMode,
+                                    ui::Dataspace dataSpace);
 
     // Returns whether the transaction actually modified any state
     bool handleMessageTransaction();
@@ -492,6 +503,7 @@
     uint32_t peekTransactionFlags();
     // Can only be called from the main thread or with mStateLock held
     uint32_t setTransactionFlags(uint32_t flags);
+    uint32_t setTransactionFlags(uint32_t flags, VSyncModulator::TransactionStart transactionStart);
     void commitTransaction();
     bool containsAnyInvalidClientState(const Vector<ComposerState>& states);
     uint32_t setClientStateLocked(const ComposerState& composerState);
@@ -637,9 +649,10 @@
 
     // Given a dataSpace, returns the appropriate color_mode to use
     // to display that dataSpace.
-    ui::ColorMode pickColorMode(ui::Dataspace dataSpace) const;
-    ui::Dataspace bestTargetDataSpace(ui::Dataspace a, ui::Dataspace b,
-            bool hasHdr) const;
+    ui::Dataspace getBestDataspace(const sp<const DisplayDevice>& displayDevice) const;
+    void pickColorMode(const sp<DisplayDevice>& displayDevice,
+                       ui::ColorMode* outMode,
+                       ui::Dataspace* outDataSpace) const;
 
     mat4 computeSaturationMatrix() const;
 
@@ -765,6 +778,8 @@
     std::unique_ptr<EventControlThread> mEventControlThread;
     sp<IBinder> mBuiltinDisplays[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES];
 
+    VSyncModulator mVsyncModulator;
+
     // Can only accessed from the main thread, these members
     // don't need synchronization
     State mDrawingState{LayerVector::StateSet::Drawing};
@@ -840,7 +855,6 @@
 
     size_t mNumLayers;
 
-
     // Verify that transaction is being called by an approved process:
     // either AID_GRAPHICS or AID_SYSTEM.
     status_t CheckTransactCodeCredentials(uint32_t code);
@@ -850,8 +864,12 @@
     static bool useVrFlinger;
     std::thread::id mMainThreadId;
 
-    float mSaturation = 1.0f;
-    bool mForceNativeColorMode = false;
+    DisplayColorSetting mDisplayColorSetting = DisplayColorSetting::MANAGED;
+    // Applied on sRGB layers when the render intent is non-colorimetric.
+    mat4 mLegacySrgbSaturationMatrix;
+    // Applied globally.
+    float mGlobalSaturationFactor = 1.0f;
+    bool mBuiltinDisplaySupportsEnhance = false;
 
     using CreateBufferQueueFunction =
             std::function<void(sp<IGraphicBufferProducer>* /* outProducer */,
diff --git a/services/surfaceflinger/VSyncModulator.h b/services/surfaceflinger/VSyncModulator.h
new file mode 100644
index 0000000..3126deb
--- /dev/null
+++ b/services/surfaceflinger/VSyncModulator.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/Errors.h>
+
+#include <mutex>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+/*
+ * Modulates the vsync-offsets depending on current SurfaceFlinger state.
+ */
+class VSyncModulator {
+public:
+
+    enum TransactionStart {
+        EARLY,
+        NORMAL
+    };
+
+    // Sets the phase offsets
+    //
+    // early: the phase offset when waking up early. May be the same as late, in which case we don't
+    //        shift offsets.
+    // late: the regular sf phase offset.
+    void setPhaseOffsets(nsecs_t early, nsecs_t late) {
+        mEarlyPhaseOffset = early;
+        mLatePhaseOffset = late;
+        mPhaseOffset = late;
+    }
+
+    nsecs_t getEarlyPhaseOffset() const {
+        return mEarlyPhaseOffset;
+    }
+
+    void setEventThread(EventThread* eventThread) {
+        mEventThread = eventThread;
+    }
+
+    void setTransactionStart(TransactionStart transactionStart) {
+        if (transactionStart == mTransactionStart) return;
+        mTransactionStart = transactionStart;
+        updatePhaseOffsets();
+    }
+
+    void setLastFrameUsedRenderEngine(bool re) {
+        if (re == mLastFrameUsedRenderEngine) return;
+        mLastFrameUsedRenderEngine = re;
+        updatePhaseOffsets();
+    }
+
+private:
+
+    void updatePhaseOffsets() {
+
+        // Do not change phase offsets if disabled.
+        if (mEarlyPhaseOffset == mLatePhaseOffset) return;
+
+        if (mTransactionStart == TransactionStart::EARLY || mLastFrameUsedRenderEngine) {
+            if (mPhaseOffset != mEarlyPhaseOffset) {
+                if (mEventThread) {
+                    mEventThread->setPhaseOffset(mEarlyPhaseOffset);
+                }
+                mPhaseOffset = mEarlyPhaseOffset;
+            }
+        } else {
+            if (mPhaseOffset != mLatePhaseOffset) {
+                if (mEventThread) {
+                    mEventThread->setPhaseOffset(mLatePhaseOffset);
+                }
+                mPhaseOffset = mLatePhaseOffset;
+            }
+        }
+    }
+
+    nsecs_t mLatePhaseOffset = 0;
+    nsecs_t mEarlyPhaseOffset = 0;
+    EventThread* mEventThread = nullptr;
+    std::atomic<nsecs_t> mPhaseOffset = 0;
+    std::atomic<TransactionStart> mTransactionStart = TransactionStart::NORMAL;
+    std::atomic<bool> mLastFrameUsedRenderEngine = false;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 4e9db72..176c691 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -1008,6 +1008,32 @@
                               tolerance);
 }
 
+TEST_F(LayerTransactionTest, SetColorWithParentAlpha_Bug74220420) {
+    sp<SurfaceControl> bufferLayer;
+    sp<SurfaceControl> parentLayer;
+    sp<SurfaceControl> colorLayer;
+    ASSERT_NO_FATAL_FAILURE(bufferLayer = createLayer("test bg", 32, 32));
+    ASSERT_NO_FATAL_FAILURE(parentLayer = createLayer("parentWithAlpha", 32, 32));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(bufferLayer, Color::RED));
+    ASSERT_NO_FATAL_FAILURE(colorLayer = createLayer(
+            "childWithColor", 32, 32, ISurfaceComposerClient::eFXSurfaceColor));
+
+    const half3 color(15.0f / 255.0f, 51.0f / 255.0f, 85.0f / 255.0f);
+    const float alpha = 0.25f;
+    const ubyte3 expected((vec3(color) * alpha + vec3(1.0f, 0.0f, 0.0f) * (1.0f - alpha)) * 255.0f);
+    // this is handwavy, but the precision loss scaled by 255 (8-bit per
+    // channel) should be less than one
+    const uint8_t tolerance = 1;
+    Transaction()
+            .reparent(colorLayer, parentLayer->getHandle())
+            .setColor(colorLayer, color)
+            .setAlpha(parentLayer, alpha)
+            .setLayer(parentLayer, mLayerZBase + 1)
+            .apply();
+    screenshot()->expectColor(Rect(0, 0, 32, 32), {expected.r, expected.g, expected.b, 255},
+                              tolerance);
+}
+
 TEST_F(LayerTransactionTest, SetColorWithBuffer) {
     sp<SurfaceControl> bufferLayer;
     ASSERT_NO_FATAL_FAILURE(bufferLayer = createLayer("test", 32, 32));
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index c048c58..0705b5c 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -46,8 +46,8 @@
 using testing::Return;
 using testing::SetArgPointee;
 
-using android::hardware::graphics::common::V1_0::ColorMode;
 using android::hardware::graphics::common::V1_0::Hdr;
+using android::hardware::graphics::common::V1_1::ColorMode;
 using android::Hwc2::Error;
 using android::Hwc2::IComposer;
 using android::Hwc2::IComposerClient;
diff --git a/services/surfaceflinger/tests/unittests/MockComposer.h b/services/surfaceflinger/tests/unittests/MockComposer.h
index 00e565b..8be2779 100644
--- a/services/surfaceflinger/tests/unittests/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/MockComposer.h
@@ -27,12 +27,13 @@
 namespace Hwc2 {
 namespace mock {
 
-using android::hardware::graphics::common::V1_0::ColorMode;
 using android::hardware::graphics::common::V1_0::ColorTransform;
 using android::hardware::graphics::common::V1_0::Hdr;
 using android::hardware::graphics::common::V1_0::Transform;
+using android::hardware::graphics::common::V1_1::ColorMode;
 using android::hardware::graphics::common::V1_1::Dataspace;
 using android::hardware::graphics::common::V1_1::PixelFormat;
+using android::hardware::graphics::common::V1_1::RenderIntent;
 
 using android::hardware::graphics::composer::V2_1::Config;
 using android::hardware::graphics::composer::V2_1::Display;
@@ -75,13 +76,14 @@
     MOCK_METHOD5(getHdrCapabilities, Error(Display, std::vector<Hdr>*, float*, float*, float*));
     MOCK_METHOD2(getPerFrameMetadataKeys,
                  Error(Display, std::vector<IComposerClient::PerFrameMetadataKey>*));
+    MOCK_METHOD2(getDataspaceSaturationMatrix, Error(Dataspace, mat4*));
     MOCK_METHOD3(getReleaseFences, Error(Display, std::vector<Layer>*, std::vector<int>*));
     MOCK_METHOD2(presentDisplay, Error(Display, int*));
     MOCK_METHOD2(setActiveConfig, Error(Display, Config));
     MOCK_METHOD6(setClientTarget,
                  Error(Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
                        const std::vector<IComposerClient::Rect>&));
-    MOCK_METHOD2(setColorMode, Error(Display, ColorMode));
+    MOCK_METHOD3(setColorMode, Error(Display, ColorMode, RenderIntent));
     MOCK_METHOD3(setColorTransform, Error(Display, const float*, ColorTransform));
     MOCK_METHOD3(setOutputBuffer, Error(Display, const native_handle_t*, int));
     MOCK_METHOD2(setPowerMode, Error(Display, IComposerClient::PowerMode));
@@ -107,6 +109,7 @@
                  Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
     MOCK_METHOD3(setLayerZOrder, Error(Display, Layer, uint32_t));
     MOCK_METHOD4(setLayerInfo, Error(Display, Layer, uint32_t, uint32_t));
+    MOCK_METHOD3(getRenderIntents, Error(Display, ColorMode, std::vector<RenderIntent>*));
 };
 
 } // namespace mock
diff --git a/services/vr/bufferhubd/buffer_hub.cpp b/services/vr/bufferhubd/buffer_hub.cpp
index e4e19c7..72bf6f2 100644
--- a/services/vr/bufferhubd/buffer_hub.cpp
+++ b/services/vr/bufferhubd/buffer_hub.cpp
@@ -6,6 +6,7 @@
 #include <utils/Trace.h>
 
 #include <iomanip>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <thread>
@@ -13,6 +14,7 @@
 #include <pdx/default_transport/service_endpoint.h>
 #include <private/dvr/bufferhub_rpc.h>
 #include "consumer_channel.h"
+#include "detached_buffer_channel.h"
 #include "producer_channel.h"
 #include "producer_queue_channel.h"
 
@@ -245,6 +247,11 @@
           *this, &BufferHubService::OnCreateBuffer, message);
       return {};
 
+    case DetachedBufferRPC::Create::Opcode:
+      DispatchRemoteMethod<DetachedBufferRPC::Create>(
+          *this, &BufferHubService::OnCreateDetachedBuffer, message);
+      return {};
+
     case BufferHubRPC::CreateProducerQueue::Opcode:
       DispatchRemoteMethod<BufferHubRPC::CreateProducerQueue>(
           *this, &BufferHubService::OnCreateProducerQueue, message);
@@ -295,6 +302,43 @@
   }
 }
 
+pdx::Status<void> BufferHubService::OnCreateDetachedBuffer(
+    pdx::Message& message, uint32_t width, uint32_t height,
+    uint32_t layer_count, uint32_t format, uint64_t usage,
+    size_t user_metadata_size) {
+  // Use the producer channel id as the global buffer id.
+  const int buffer_id = message.GetChannelId();
+  ALOGD_IF(TRACE,
+           "BufferHubService::OnCreateDetachedBuffer: buffer_id=%d width=%u "
+           "height=%u layer_count=%u format=%u usage=%" PRIx64
+           " user_metadata_size=%zu",
+           buffer_id, width, height, layer_count, format, usage,
+           user_metadata_size);
+
+  // See if this channel is already attached to a buffer.
+  if (const auto channel = message.GetChannel<BufferHubChannel>()) {
+    ALOGE(
+        "BufferHubService::OnCreateDetachedBuffer: Buffer already created: "
+        "buffer=%d",
+        buffer_id);
+    return ErrorStatus(EALREADY);
+  }
+
+  std::unique_ptr<DetachedBufferChannel> channel =
+      DetachedBufferChannel::Create(this, buffer_id, width, height, layer_count,
+                                    format, usage, user_metadata_size);
+  if (!channel) {
+    ALOGE(
+        "BufferHubService::OnCreateDetachedBuffer: Failed to allocate buffer, "
+        "buffer=%d.",
+        buffer_id);
+    return ErrorStatus(ENOMEM);
+  }
+
+  message.SetChannel(std::move(channel));
+  return {};
+}
+
 Status<QueueInfo> BufferHubService::OnCreateProducerQueue(
     pdx::Message& message, const ProducerQueueConfig& producer_config,
     const UsagePolicy& usage_policy) {
diff --git a/services/vr/bufferhubd/buffer_hub.h b/services/vr/bufferhubd/buffer_hub.h
index e04967a..e47ffa3 100644
--- a/services/vr/bufferhubd/buffer_hub.h
+++ b/services/vr/bufferhubd/buffer_hub.h
@@ -147,6 +147,11 @@
   pdx::Status<void> OnCreateBuffer(pdx::Message& message, uint32_t width,
                                    uint32_t height, uint32_t format,
                                    uint64_t usage, size_t meta_size_bytes);
+  pdx::Status<void> OnCreateDetachedBuffer(pdx::Message& message,
+                                           uint32_t width, uint32_t height,
+                                           uint32_t layer_count,
+                                           uint32_t format, uint64_t usage,
+                                           size_t user_metadata_size);
   pdx::Status<QueueInfo> OnCreateProducerQueue(
       pdx::Message& message, const ProducerQueueConfig& producer_config,
       const UsagePolicy& usage_policy);
diff --git a/services/vr/bufferhubd/detached_buffer_channel.cpp b/services/vr/bufferhubd/detached_buffer_channel.cpp
index edb2111..4f4160a 100644
--- a/services/vr/bufferhubd/detached_buffer_channel.cpp
+++ b/services/vr/bufferhubd/detached_buffer_channel.cpp
@@ -1,5 +1,6 @@
 #include "detached_buffer_channel.h"
 
+using android::pdx::BorrowedHandle;
 using android::pdx::ErrorStatus;
 using android::pdx::Message;
 using android::pdx::RemoteChannelHandle;
@@ -17,7 +18,49 @@
     : BufferHubChannel(service, buffer_id, channel_id, kDetachedBufferType),
       buffer_(std::move(buffer)),
       metadata_buffer_(std::move(metadata_buffer)),
-      user_metadata_size_(user_metadata_size) {}
+      user_metadata_size_(user_metadata_size) {
+}
+
+DetachedBufferChannel::DetachedBufferChannel(BufferHubService* service,
+                                             int buffer_id, uint32_t width,
+                                             uint32_t height,
+                                             uint32_t layer_count,
+                                             uint32_t format, uint64_t usage,
+                                             size_t user_metadata_size)
+    : BufferHubChannel(service, buffer_id, buffer_id, kDetachedBufferType),
+      user_metadata_size_(user_metadata_size) {
+  // The size the of metadata buffer is used as the "width" parameter during
+  // allocation. Thus it cannot overflow uint32_t.
+  if (user_metadata_size_ >= (std::numeric_limits<uint32_t>::max() -
+                              BufferHubDefs::kMetadataHeaderSize)) {
+    ALOGE(
+        "DetachedBufferChannel::DetachedBufferChannel: metadata size too big.");
+    return;
+  }
+
+  if (int ret = buffer_.Alloc(width, height, layer_count, format, usage)) {
+    ALOGE(
+        "DetachedBufferChannel::DetachedBufferChannel: Failed to allocate "
+        "buffer: %s",
+        strerror(-ret));
+    return;
+  }
+
+  // Buffer metadata has two parts: 1) a fixed sized metadata header; and 2)
+  // user requested metadata.
+  const size_t size = BufferHubDefs::kMetadataHeaderSize + user_metadata_size_;
+  if (int ret = metadata_buffer_.Alloc(size,
+                                       /*height=*/1,
+                                       /*layer_count=*/1,
+                                       BufferHubDefs::kMetadataFormat,
+                                       BufferHubDefs::kMetadataUsage)) {
+    ALOGE(
+        "DetachedBufferChannel::DetachedBufferChannel: Failed to allocate "
+        "metadata: %s",
+        strerror(-ret));
+    return;
+  }
+}
 
 BufferHubChannel::BufferInfo DetachedBufferChannel::GetBufferInfo() const {
   return BufferInfo(buffer_id(), /*consumer_count=*/0, buffer_.width(),
@@ -33,8 +76,13 @@
 bool DetachedBufferChannel::HandleMessage(Message& message) {
   ATRACE_NAME("DetachedBufferChannel::HandleMessage");
   switch (message.GetOp()) {
-    case BufferHubRPC::DetachedBufferPromote::Opcode:
-      DispatchRemoteMethod<BufferHubRPC::DetachedBufferPromote>(
+    case DetachedBufferRPC::Import::Opcode:
+      DispatchRemoteMethod<DetachedBufferRPC::Import>(
+          *this, &DetachedBufferChannel::OnImport, message);
+      return true;
+
+    case DetachedBufferRPC::Promote::Opcode:
+      DispatchRemoteMethod<DetachedBufferRPC::Promote>(
           *this, &DetachedBufferChannel::OnPromote, message);
       return true;
 
@@ -43,6 +91,20 @@
   }
 }
 
+Status<BufferDescription<BorrowedHandle>> DetachedBufferChannel::OnImport(
+    Message& /*message*/) {
+  ATRACE_NAME("DetachedBufferChannel::OnGetBuffer");
+  ALOGD_IF(TRACE, "DetachedBufferChannel::OnGetBuffer: buffer=%d.",
+           buffer_id());
+
+  return BufferDescription<BorrowedHandle>{buffer_,
+                                           metadata_buffer_,
+                                           buffer_id(),
+                                           /*buffer_state_bit=*/0,
+                                           BorrowedHandle{},
+                                           BorrowedHandle{}};
+}
+
 Status<RemoteChannelHandle> DetachedBufferChannel::OnPromote(
     Message& /*message*/) {
   ATRACE_NAME("DetachedBufferChannel::OnPromote");
diff --git a/services/vr/bufferhubd/detached_buffer_channel.h b/services/vr/bufferhubd/detached_buffer_channel.h
index 7ce4aed..079ba72 100644
--- a/services/vr/bufferhubd/detached_buffer_channel.h
+++ b/services/vr/bufferhubd/detached_buffer_channel.h
@@ -3,20 +3,25 @@
 
 #include "buffer_hub.h"
 
-// #include <pdx/channel_handle.h>
-// #include <pdx/file_handle.h>
-// #include <pdx/rpc/buffer_wrapper.h>
-// #include <private/dvr/ion_buffer.h>
+#include <pdx/channel_handle.h>
+#include <pdx/file_handle.h>
 
 namespace android {
 namespace dvr {
 
 class DetachedBufferChannel : public BufferHubChannel {
  public:
-  // Creates a detached buffer.
-  DetachedBufferChannel(BufferHubService* service, int buffer_id,
-                        int channel_id, IonBuffer buffer,
-                        IonBuffer metadata_buffer, size_t user_metadata_size);
+  template <typename... Args>
+  static std::unique_ptr<DetachedBufferChannel> Create(Args&&... args) {
+    auto buffer = std::unique_ptr<DetachedBufferChannel>(
+        new DetachedBufferChannel(std::forward<Args>(args)...));
+    return buffer->IsValid() ? std::move(buffer) : nullptr;
+  }
+
+  // Returns whether the object holds a valid graphic buffer.
+  bool IsValid() const {
+    return buffer_.IsValid() && metadata_buffer_.IsValid();
+  }
 
   size_t user_metadata_size() const { return user_metadata_size_; }
 
@@ -27,6 +32,19 @@
   void HandleImpulse(pdx::Message& message) override;
 
  private:
+  // Creates a detached buffer from existing IonBuffers.
+  DetachedBufferChannel(BufferHubService* service, int buffer_id,
+                        int channel_id, IonBuffer buffer,
+                        IonBuffer metadata_buffer, size_t user_metadata_size);
+
+  // Allocates a new detached buffer.
+  DetachedBufferChannel(BufferHubService* service, int buffer_id,
+                        uint32_t width, uint32_t height, uint32_t layer_count,
+                        uint32_t format, uint64_t usage,
+                        size_t user_metadata_size);
+
+  pdx::Status<BufferDescription<pdx::BorrowedHandle>> OnImport(
+      pdx::Message& message);
   pdx::Status<pdx::RemoteChannelHandle> OnPromote(pdx::Message& message);
 
   // Gralloc buffer handles.
diff --git a/services/vr/bufferhubd/producer_channel.cpp b/services/vr/bufferhubd/producer_channel.cpp
index c38c12b..a753168 100644
--- a/services/vr/bufferhubd/producer_channel.cpp
+++ b/services/vr/bufferhubd/producer_channel.cpp
@@ -377,11 +377,17 @@
     return ErrorStatus(-ret);
   };
 
-  auto channel = std::make_shared<DetachedBufferChannel>(
-      service(), buffer_id(), channel_id, std::move(buffer_),
-      std::move(metadata_buffer_), user_metadata_size_);
+  std::unique_ptr<DetachedBufferChannel> channel =
+      DetachedBufferChannel::Create(
+          service(), buffer_id(), channel_id, std::move(buffer_),
+          std::move(metadata_buffer_), user_metadata_size_);
+  if (!channel) {
+    ALOGE("ProducerChannel::OnProducerDetach: Invalid buffer.");
+    return ErrorStatus(EINVAL);
+  }
 
-  const auto channel_status = service()->SetChannel(channel_id, channel);
+  const auto channel_status =
+      service()->SetChannel(channel_id, std::move(channel));
   if (!channel_status) {
     // Technically, this should never fail, as we just pushed the channel. Note
     // that LOG_FATAL will be stripped out in non-debug build.
diff --git a/vulkan/Android.bp b/vulkan/Android.bp
index a49b6dd..b15bed9 100644
--- a/vulkan/Android.bp
+++ b/vulkan/Android.bp
@@ -55,4 +55,5 @@
     "nulldrv",
     "libvulkan",
     "tools",
+    "vkjson",
 ]
diff --git a/vulkan/vkjson/.clang-format b/vulkan/vkjson/.clang-format
new file mode 100644
index 0000000..3f19e61
--- /dev/null
+++ b/vulkan/vkjson/.clang-format
@@ -0,0 +1 @@
+BasedOnStyle: Chromium
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
new file mode 100644
index 0000000..e387165
--- /dev/null
+++ b/vulkan/vkjson/Android.bp
@@ -0,0 +1,52 @@
+cc_library_static {
+    name: "libvkjson",
+    srcs: [
+        "vkjson.cc",
+        "vkjson_instance.cc",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    cppflags: [
+        "-std=c++11",
+        "-Wno-sign-compare",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    whole_static_libs: [
+        "libjsoncpp",
+    ],
+    header_libs: [
+        "vulkan_headers",
+    ],
+}
+
+cc_library_static {
+    name: "libvkjson_ndk",
+    clang: true,
+    srcs: [
+        "vkjson.cc",
+        "vkjson_instance.cc",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    cppflags: [
+        "-std=c++11",
+        "-Wno-sign-compare",
+    ],
+    export_include_dirs: [
+        ".",
+    ],
+    whole_static_libs: [
+        "libjsoncpp_ndk",
+    ],
+    header_libs: [
+        "vulkan_headers_ndk",
+    ],
+    sdk_version: "24",
+    stl: "libc++_static",
+}
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
new file mode 100644
index 0000000..6200383
--- /dev/null
+++ b/vulkan/vkjson/vkjson.cc
@@ -0,0 +1,1125 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2016 Valve Corporation
+// Copyright (c) 2015-2016 LunarG, Inc.
+// Copyright (c) 2015-2016 Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "vkjson.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <cmath>
+#include <cinttypes>
+#include <cstdio>
+#include <limits>
+#include <memory>
+#include <sstream>
+#include <type_traits>
+#include <utility>
+
+#include <json/json.h>
+
+namespace {
+
+inline bool IsIntegral(double value) {
+#if defined(ANDROID)
+  // Android NDK doesn't provide std::trunc yet
+  return trunc(value) == value;
+#else
+  return std::trunc(value) == value;
+#endif
+}
+
+template <typename T> struct EnumTraits;
+template <> struct EnumTraits<VkPhysicalDeviceType> {
+  static uint32_t min() { return VK_PHYSICAL_DEVICE_TYPE_BEGIN_RANGE; }
+  static uint32_t max() { return VK_PHYSICAL_DEVICE_TYPE_END_RANGE; }
+  static bool exist(uint32_t e) { return e >= min() && e <= max(); }
+};
+
+template <> struct EnumTraits<VkFormat> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_FORMAT_UNDEFINED:
+      case VK_FORMAT_R4G4_UNORM_PACK8:
+      case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+      case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
+      case VK_FORMAT_R5G6B5_UNORM_PACK16:
+      case VK_FORMAT_B5G6R5_UNORM_PACK16:
+      case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
+      case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
+      case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
+      case VK_FORMAT_R8_UNORM:
+      case VK_FORMAT_R8_SNORM:
+      case VK_FORMAT_R8_USCALED:
+      case VK_FORMAT_R8_SSCALED:
+      case VK_FORMAT_R8_UINT:
+      case VK_FORMAT_R8_SINT:
+      case VK_FORMAT_R8_SRGB:
+      case VK_FORMAT_R8G8_UNORM:
+      case VK_FORMAT_R8G8_SNORM:
+      case VK_FORMAT_R8G8_USCALED:
+      case VK_FORMAT_R8G8_SSCALED:
+      case VK_FORMAT_R8G8_UINT:
+      case VK_FORMAT_R8G8_SINT:
+      case VK_FORMAT_R8G8_SRGB:
+      case VK_FORMAT_R8G8B8_UNORM:
+      case VK_FORMAT_R8G8B8_SNORM:
+      case VK_FORMAT_R8G8B8_USCALED:
+      case VK_FORMAT_R8G8B8_SSCALED:
+      case VK_FORMAT_R8G8B8_UINT:
+      case VK_FORMAT_R8G8B8_SINT:
+      case VK_FORMAT_R8G8B8_SRGB:
+      case VK_FORMAT_B8G8R8_UNORM:
+      case VK_FORMAT_B8G8R8_SNORM:
+      case VK_FORMAT_B8G8R8_USCALED:
+      case VK_FORMAT_B8G8R8_SSCALED:
+      case VK_FORMAT_B8G8R8_UINT:
+      case VK_FORMAT_B8G8R8_SINT:
+      case VK_FORMAT_B8G8R8_SRGB:
+      case VK_FORMAT_R8G8B8A8_UNORM:
+      case VK_FORMAT_R8G8B8A8_SNORM:
+      case VK_FORMAT_R8G8B8A8_USCALED:
+      case VK_FORMAT_R8G8B8A8_SSCALED:
+      case VK_FORMAT_R8G8B8A8_UINT:
+      case VK_FORMAT_R8G8B8A8_SINT:
+      case VK_FORMAT_R8G8B8A8_SRGB:
+      case VK_FORMAT_B8G8R8A8_UNORM:
+      case VK_FORMAT_B8G8R8A8_SNORM:
+      case VK_FORMAT_B8G8R8A8_USCALED:
+      case VK_FORMAT_B8G8R8A8_SSCALED:
+      case VK_FORMAT_B8G8R8A8_UINT:
+      case VK_FORMAT_B8G8R8A8_SINT:
+      case VK_FORMAT_B8G8R8A8_SRGB:
+      case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
+      case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
+      case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
+      case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
+      case VK_FORMAT_A8B8G8R8_UINT_PACK32:
+      case VK_FORMAT_A8B8G8R8_SINT_PACK32:
+      case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+      case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
+      case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
+      case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
+      case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
+      case VK_FORMAT_A2R10G10B10_UINT_PACK32:
+      case VK_FORMAT_A2R10G10B10_SINT_PACK32:
+      case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+      case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
+      case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
+      case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
+      case VK_FORMAT_A2B10G10R10_UINT_PACK32:
+      case VK_FORMAT_A2B10G10R10_SINT_PACK32:
+      case VK_FORMAT_R16_UNORM:
+      case VK_FORMAT_R16_SNORM:
+      case VK_FORMAT_R16_USCALED:
+      case VK_FORMAT_R16_SSCALED:
+      case VK_FORMAT_R16_UINT:
+      case VK_FORMAT_R16_SINT:
+      case VK_FORMAT_R16_SFLOAT:
+      case VK_FORMAT_R16G16_UNORM:
+      case VK_FORMAT_R16G16_SNORM:
+      case VK_FORMAT_R16G16_USCALED:
+      case VK_FORMAT_R16G16_SSCALED:
+      case VK_FORMAT_R16G16_UINT:
+      case VK_FORMAT_R16G16_SINT:
+      case VK_FORMAT_R16G16_SFLOAT:
+      case VK_FORMAT_R16G16B16_UNORM:
+      case VK_FORMAT_R16G16B16_SNORM:
+      case VK_FORMAT_R16G16B16_USCALED:
+      case VK_FORMAT_R16G16B16_SSCALED:
+      case VK_FORMAT_R16G16B16_UINT:
+      case VK_FORMAT_R16G16B16_SINT:
+      case VK_FORMAT_R16G16B16_SFLOAT:
+      case VK_FORMAT_R16G16B16A16_UNORM:
+      case VK_FORMAT_R16G16B16A16_SNORM:
+      case VK_FORMAT_R16G16B16A16_USCALED:
+      case VK_FORMAT_R16G16B16A16_SSCALED:
+      case VK_FORMAT_R16G16B16A16_UINT:
+      case VK_FORMAT_R16G16B16A16_SINT:
+      case VK_FORMAT_R16G16B16A16_SFLOAT:
+      case VK_FORMAT_R32_UINT:
+      case VK_FORMAT_R32_SINT:
+      case VK_FORMAT_R32_SFLOAT:
+      case VK_FORMAT_R32G32_UINT:
+      case VK_FORMAT_R32G32_SINT:
+      case VK_FORMAT_R32G32_SFLOAT:
+      case VK_FORMAT_R32G32B32_UINT:
+      case VK_FORMAT_R32G32B32_SINT:
+      case VK_FORMAT_R32G32B32_SFLOAT:
+      case VK_FORMAT_R32G32B32A32_UINT:
+      case VK_FORMAT_R32G32B32A32_SINT:
+      case VK_FORMAT_R32G32B32A32_SFLOAT:
+      case VK_FORMAT_R64_UINT:
+      case VK_FORMAT_R64_SINT:
+      case VK_FORMAT_R64_SFLOAT:
+      case VK_FORMAT_R64G64_UINT:
+      case VK_FORMAT_R64G64_SINT:
+      case VK_FORMAT_R64G64_SFLOAT:
+      case VK_FORMAT_R64G64B64_UINT:
+      case VK_FORMAT_R64G64B64_SINT:
+      case VK_FORMAT_R64G64B64_SFLOAT:
+      case VK_FORMAT_R64G64B64A64_UINT:
+      case VK_FORMAT_R64G64B64A64_SINT:
+      case VK_FORMAT_R64G64B64A64_SFLOAT:
+      case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
+      case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+      case VK_FORMAT_D16_UNORM:
+      case VK_FORMAT_X8_D24_UNORM_PACK32:
+      case VK_FORMAT_D32_SFLOAT:
+      case VK_FORMAT_S8_UINT:
+      case VK_FORMAT_D16_UNORM_S8_UINT:
+      case VK_FORMAT_D24_UNORM_S8_UINT:
+      case VK_FORMAT_D32_SFLOAT_S8_UINT:
+      case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+      case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+      case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+      case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+      case VK_FORMAT_BC2_UNORM_BLOCK:
+      case VK_FORMAT_BC2_SRGB_BLOCK:
+      case VK_FORMAT_BC3_UNORM_BLOCK:
+      case VK_FORMAT_BC3_SRGB_BLOCK:
+      case VK_FORMAT_BC4_UNORM_BLOCK:
+      case VK_FORMAT_BC4_SNORM_BLOCK:
+      case VK_FORMAT_BC5_UNORM_BLOCK:
+      case VK_FORMAT_BC5_SNORM_BLOCK:
+      case VK_FORMAT_BC6H_UFLOAT_BLOCK:
+      case VK_FORMAT_BC6H_SFLOAT_BLOCK:
+      case VK_FORMAT_BC7_UNORM_BLOCK:
+      case VK_FORMAT_BC7_SRGB_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+      case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+      case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+      case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+      case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+      case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+      case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
+      case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
+      case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
+      case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:
+      case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
+      case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:
+      case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
+      case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:
+      case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
+      case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:
+      case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
+      case VK_FORMAT_G8B8G8R8_422_UNORM_KHR:
+      case VK_FORMAT_B8G8R8G8_422_UNORM_KHR:
+      case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR:
+      case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR:
+      case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR:
+      case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR:
+      case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR:
+      case VK_FORMAT_R10X6_UNORM_PACK16_KHR:
+      case VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR:
+      case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR:
+      case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR:
+      case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR:
+      case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR:
+      case VK_FORMAT_R12X4_UNORM_PACK16_KHR:
+      case VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR:
+      case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR:
+      case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR:
+      case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR:
+      case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR:
+      case VK_FORMAT_G16B16G16R16_422_UNORM_KHR:
+      case VK_FORMAT_B16G16R16G16_422_UNORM_KHR:
+      case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR:
+      case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR:
+      case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR:
+      case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR:
+      case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR:
+        return true;
+    }
+    return false;
+  }
+};
+
+template <>
+struct EnumTraits<VkPointClippingBehavior> {
+  static uint32_t min() { return VK_POINT_CLIPPING_BEHAVIOR_BEGIN_RANGE; }
+  static uint32_t max() { return VK_POINT_CLIPPING_BEHAVIOR_END_RANGE; }
+  static bool exist(uint32_t e) { return e >= min() && e <= max(); }
+};
+
+template <>
+struct EnumTraits<VkExternalFenceHandleTypeFlagBits> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT:
+      case VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT:
+      case VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT:
+      case VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT:
+        return true;
+    }
+    return false;
+  }
+};
+
+template <>
+struct EnumTraits<VkExternalSemaphoreHandleTypeFlagBits> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT:
+      case VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT:
+      case VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT:
+      case VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT:
+      case VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT:
+        return true;
+    }
+    return false;
+  }
+};
+
+// VkSparseImageFormatProperties
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkExtent3D* extents) {
+  return
+    visitor->Visit("width", &extents->width) &&
+    visitor->Visit("height", &extents->height) &&
+    visitor->Visit("depth", &extents->depth);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkImageFormatProperties* properties) {
+  return
+    visitor->Visit("maxExtent", &properties->maxExtent) &&
+    visitor->Visit("maxMipLevels", &properties->maxMipLevels) &&
+    visitor->Visit("maxArrayLayers", &properties->maxArrayLayers) &&
+    visitor->Visit("sampleCounts", &properties->sampleCounts) &&
+    visitor->Visit("maxResourceSize", &properties->maxResourceSize);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceLimits* limits) {
+  return
+    visitor->Visit("maxImageDimension1D", &limits->maxImageDimension1D) &&
+    visitor->Visit("maxImageDimension2D", &limits->maxImageDimension2D) &&
+    visitor->Visit("maxImageDimension3D", &limits->maxImageDimension3D) &&
+    visitor->Visit("maxImageDimensionCube", &limits->maxImageDimensionCube) &&
+    visitor->Visit("maxImageArrayLayers", &limits->maxImageArrayLayers) &&
+    visitor->Visit("maxTexelBufferElements", &limits->maxTexelBufferElements) &&
+    visitor->Visit("maxUniformBufferRange", &limits->maxUniformBufferRange) &&
+    visitor->Visit("maxStorageBufferRange", &limits->maxStorageBufferRange) &&
+    visitor->Visit("maxPushConstantsSize", &limits->maxPushConstantsSize) &&
+    visitor->Visit("maxMemoryAllocationCount", &limits->maxMemoryAllocationCount) &&
+    visitor->Visit("maxSamplerAllocationCount", &limits->maxSamplerAllocationCount) &&
+    visitor->Visit("bufferImageGranularity", &limits->bufferImageGranularity) &&
+    visitor->Visit("sparseAddressSpaceSize", &limits->sparseAddressSpaceSize) &&
+    visitor->Visit("maxBoundDescriptorSets", &limits->maxBoundDescriptorSets) &&
+    visitor->Visit("maxPerStageDescriptorSamplers", &limits->maxPerStageDescriptorSamplers) &&
+    visitor->Visit("maxPerStageDescriptorUniformBuffers", &limits->maxPerStageDescriptorUniformBuffers) &&
+    visitor->Visit("maxPerStageDescriptorStorageBuffers", &limits->maxPerStageDescriptorStorageBuffers) &&
+    visitor->Visit("maxPerStageDescriptorSampledImages", &limits->maxPerStageDescriptorSampledImages) &&
+    visitor->Visit("maxPerStageDescriptorStorageImages", &limits->maxPerStageDescriptorStorageImages) &&
+    visitor->Visit("maxPerStageDescriptorInputAttachments", &limits->maxPerStageDescriptorInputAttachments) &&
+    visitor->Visit("maxPerStageResources", &limits->maxPerStageResources) &&
+    visitor->Visit("maxDescriptorSetSamplers", &limits->maxDescriptorSetSamplers) &&
+    visitor->Visit("maxDescriptorSetUniformBuffers", &limits->maxDescriptorSetUniformBuffers) &&
+    visitor->Visit("maxDescriptorSetUniformBuffersDynamic", &limits->maxDescriptorSetUniformBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetStorageBuffers", &limits->maxDescriptorSetStorageBuffers) &&
+    visitor->Visit("maxDescriptorSetStorageBuffersDynamic", &limits->maxDescriptorSetStorageBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetSampledImages", &limits->maxDescriptorSetSampledImages) &&
+    visitor->Visit("maxDescriptorSetStorageImages", &limits->maxDescriptorSetStorageImages) &&
+    visitor->Visit("maxDescriptorSetInputAttachments", &limits->maxDescriptorSetInputAttachments) &&
+    visitor->Visit("maxVertexInputAttributes", &limits->maxVertexInputAttributes) &&
+    visitor->Visit("maxVertexInputBindings", &limits->maxVertexInputBindings) &&
+    visitor->Visit("maxVertexInputAttributeOffset", &limits->maxVertexInputAttributeOffset) &&
+    visitor->Visit("maxVertexInputBindingStride", &limits->maxVertexInputBindingStride) &&
+    visitor->Visit("maxVertexOutputComponents", &limits->maxVertexOutputComponents) &&
+    visitor->Visit("maxTessellationGenerationLevel", &limits->maxTessellationGenerationLevel) &&
+    visitor->Visit("maxTessellationPatchSize", &limits->maxTessellationPatchSize) &&
+    visitor->Visit("maxTessellationControlPerVertexInputComponents", &limits->maxTessellationControlPerVertexInputComponents) &&
+    visitor->Visit("maxTessellationControlPerVertexOutputComponents", &limits->maxTessellationControlPerVertexOutputComponents) &&
+    visitor->Visit("maxTessellationControlPerPatchOutputComponents", &limits->maxTessellationControlPerPatchOutputComponents) &&
+    visitor->Visit("maxTessellationControlTotalOutputComponents", &limits->maxTessellationControlTotalOutputComponents) &&
+    visitor->Visit("maxTessellationEvaluationInputComponents", &limits->maxTessellationEvaluationInputComponents) &&
+    visitor->Visit("maxTessellationEvaluationOutputComponents", &limits->maxTessellationEvaluationOutputComponents) &&
+    visitor->Visit("maxGeometryShaderInvocations", &limits->maxGeometryShaderInvocations) &&
+    visitor->Visit("maxGeometryInputComponents", &limits->maxGeometryInputComponents) &&
+    visitor->Visit("maxGeometryOutputComponents", &limits->maxGeometryOutputComponents) &&
+    visitor->Visit("maxGeometryOutputVertices", &limits->maxGeometryOutputVertices) &&
+    visitor->Visit("maxGeometryTotalOutputComponents", &limits->maxGeometryTotalOutputComponents) &&
+    visitor->Visit("maxFragmentInputComponents", &limits->maxFragmentInputComponents) &&
+    visitor->Visit("maxFragmentOutputAttachments", &limits->maxFragmentOutputAttachments) &&
+    visitor->Visit("maxFragmentDualSrcAttachments", &limits->maxFragmentDualSrcAttachments) &&
+    visitor->Visit("maxFragmentCombinedOutputResources", &limits->maxFragmentCombinedOutputResources) &&
+    visitor->Visit("maxComputeSharedMemorySize", &limits->maxComputeSharedMemorySize) &&
+    visitor->Visit("maxComputeWorkGroupCount", &limits->maxComputeWorkGroupCount) &&
+    visitor->Visit("maxComputeWorkGroupInvocations", &limits->maxComputeWorkGroupInvocations) &&
+    visitor->Visit("maxComputeWorkGroupSize", &limits->maxComputeWorkGroupSize) &&
+    visitor->Visit("subPixelPrecisionBits", &limits->subPixelPrecisionBits) &&
+    visitor->Visit("subTexelPrecisionBits", &limits->subTexelPrecisionBits) &&
+    visitor->Visit("mipmapPrecisionBits", &limits->mipmapPrecisionBits) &&
+    visitor->Visit("maxDrawIndexedIndexValue", &limits->maxDrawIndexedIndexValue) &&
+    visitor->Visit("maxDrawIndirectCount", &limits->maxDrawIndirectCount) &&
+    visitor->Visit("maxSamplerLodBias", &limits->maxSamplerLodBias) &&
+    visitor->Visit("maxSamplerAnisotropy", &limits->maxSamplerAnisotropy) &&
+    visitor->Visit("maxViewports", &limits->maxViewports) &&
+    visitor->Visit("maxViewportDimensions", &limits->maxViewportDimensions) &&
+    visitor->Visit("viewportBoundsRange", &limits->viewportBoundsRange) &&
+    visitor->Visit("viewportSubPixelBits", &limits->viewportSubPixelBits) &&
+    visitor->Visit("minMemoryMapAlignment", &limits->minMemoryMapAlignment) &&
+    visitor->Visit("minTexelBufferOffsetAlignment", &limits->minTexelBufferOffsetAlignment) &&
+    visitor->Visit("minUniformBufferOffsetAlignment", &limits->minUniformBufferOffsetAlignment) &&
+    visitor->Visit("minStorageBufferOffsetAlignment", &limits->minStorageBufferOffsetAlignment) &&
+    visitor->Visit("minTexelOffset", &limits->minTexelOffset) &&
+    visitor->Visit("maxTexelOffset", &limits->maxTexelOffset) &&
+    visitor->Visit("minTexelGatherOffset", &limits->minTexelGatherOffset) &&
+    visitor->Visit("maxTexelGatherOffset", &limits->maxTexelGatherOffset) &&
+    visitor->Visit("minInterpolationOffset", &limits->minInterpolationOffset) &&
+    visitor->Visit("maxInterpolationOffset", &limits->maxInterpolationOffset) &&
+    visitor->Visit("subPixelInterpolationOffsetBits", &limits->subPixelInterpolationOffsetBits) &&
+    visitor->Visit("maxFramebufferWidth", &limits->maxFramebufferWidth) &&
+    visitor->Visit("maxFramebufferHeight", &limits->maxFramebufferHeight) &&
+    visitor->Visit("maxFramebufferLayers", &limits->maxFramebufferLayers) &&
+    visitor->Visit("framebufferColorSampleCounts", &limits->framebufferColorSampleCounts) &&
+    visitor->Visit("framebufferDepthSampleCounts", &limits->framebufferDepthSampleCounts) &&
+    visitor->Visit("framebufferStencilSampleCounts", &limits->framebufferStencilSampleCounts) &&
+    visitor->Visit("framebufferNoAttachmentsSampleCounts", &limits->framebufferNoAttachmentsSampleCounts) &&
+    visitor->Visit("maxColorAttachments", &limits->maxColorAttachments) &&
+    visitor->Visit("sampledImageColorSampleCounts", &limits->sampledImageColorSampleCounts) &&
+    visitor->Visit("sampledImageIntegerSampleCounts", &limits->sampledImageIntegerSampleCounts) &&
+    visitor->Visit("sampledImageDepthSampleCounts", &limits->sampledImageDepthSampleCounts) &&
+    visitor->Visit("sampledImageStencilSampleCounts", &limits->sampledImageStencilSampleCounts) &&
+    visitor->Visit("storageImageSampleCounts", &limits->storageImageSampleCounts) &&
+    visitor->Visit("maxSampleMaskWords", &limits->maxSampleMaskWords) &&
+    visitor->Visit("timestampComputeAndGraphics", &limits->timestampComputeAndGraphics) &&
+    visitor->Visit("timestampPeriod", &limits->timestampPeriod) &&
+    visitor->Visit("maxClipDistances", &limits->maxClipDistances) &&
+    visitor->Visit("maxCullDistances", &limits->maxCullDistances) &&
+    visitor->Visit("maxCombinedClipAndCullDistances", &limits->maxCombinedClipAndCullDistances) &&
+    visitor->Visit("discreteQueuePriorities", &limits->discreteQueuePriorities) &&
+    visitor->Visit("pointSizeRange", &limits->pointSizeRange) &&
+    visitor->Visit("lineWidthRange", &limits->lineWidthRange) &&
+    visitor->Visit("pointSizeGranularity", &limits->pointSizeGranularity) &&
+    visitor->Visit("lineWidthGranularity", &limits->lineWidthGranularity) &&
+    visitor->Visit("strictLines", &limits->strictLines) &&
+    visitor->Visit("standardSampleLocations", &limits->standardSampleLocations) &&
+    visitor->Visit("optimalBufferCopyOffsetAlignment", &limits->optimalBufferCopyOffsetAlignment) &&
+    visitor->Visit("optimalBufferCopyRowPitchAlignment", &limits->optimalBufferCopyRowPitchAlignment) &&
+    visitor->Visit("nonCoherentAtomSize", &limits->nonCoherentAtomSize);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceSparseProperties* properties) {
+  return
+    visitor->Visit("residencyStandard2DBlockShape", &properties->residencyStandard2DBlockShape) &&
+    visitor->Visit("residencyStandard2DMultisampleBlockShape", &properties->residencyStandard2DMultisampleBlockShape) &&
+    visitor->Visit("residencyStandard3DBlockShape", &properties->residencyStandard3DBlockShape) &&
+    visitor->Visit("residencyAlignedMipSize", &properties->residencyAlignedMipSize) &&
+    visitor->Visit("residencyNonResidentStrict", &properties->residencyNonResidentStrict);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceProperties* properties) {
+  return
+    visitor->Visit("apiVersion", &properties->apiVersion) &&
+    visitor->Visit("driverVersion", &properties->driverVersion) &&
+    visitor->Visit("vendorID", &properties->vendorID) &&
+    visitor->Visit("deviceID", &properties->deviceID) &&
+    visitor->Visit("deviceType", &properties->deviceType) &&
+    visitor->Visit("deviceName", &properties->deviceName) &&
+    visitor->Visit("pipelineCacheUUID", &properties->pipelineCacheUUID) &&
+    visitor->Visit("limits", &properties->limits) &&
+    visitor->Visit("sparseProperties", &properties->sparseProperties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceFeatures* features) {
+  return
+    visitor->Visit("robustBufferAccess", &features->robustBufferAccess) &&
+    visitor->Visit("fullDrawIndexUint32", &features->fullDrawIndexUint32) &&
+    visitor->Visit("imageCubeArray", &features->imageCubeArray) &&
+    visitor->Visit("independentBlend", &features->independentBlend) &&
+    visitor->Visit("geometryShader", &features->geometryShader) &&
+    visitor->Visit("tessellationShader", &features->tessellationShader) &&
+    visitor->Visit("sampleRateShading", &features->sampleRateShading) &&
+    visitor->Visit("dualSrcBlend", &features->dualSrcBlend) &&
+    visitor->Visit("logicOp", &features->logicOp) &&
+    visitor->Visit("multiDrawIndirect", &features->multiDrawIndirect) &&
+    visitor->Visit("drawIndirectFirstInstance", &features->drawIndirectFirstInstance) &&
+    visitor->Visit("depthClamp", &features->depthClamp) &&
+    visitor->Visit("depthBiasClamp", &features->depthBiasClamp) &&
+    visitor->Visit("fillModeNonSolid", &features->fillModeNonSolid) &&
+    visitor->Visit("depthBounds", &features->depthBounds) &&
+    visitor->Visit("wideLines", &features->wideLines) &&
+    visitor->Visit("largePoints", &features->largePoints) &&
+    visitor->Visit("alphaToOne", &features->alphaToOne) &&
+    visitor->Visit("multiViewport", &features->multiViewport) &&
+    visitor->Visit("samplerAnisotropy", &features->samplerAnisotropy) &&
+    visitor->Visit("textureCompressionETC2", &features->textureCompressionETC2) &&
+    visitor->Visit("textureCompressionASTC_LDR", &features->textureCompressionASTC_LDR) &&
+    visitor->Visit("textureCompressionBC", &features->textureCompressionBC) &&
+    visitor->Visit("occlusionQueryPrecise", &features->occlusionQueryPrecise) &&
+    visitor->Visit("pipelineStatisticsQuery", &features->pipelineStatisticsQuery) &&
+    visitor->Visit("vertexPipelineStoresAndAtomics", &features->vertexPipelineStoresAndAtomics) &&
+    visitor->Visit("fragmentStoresAndAtomics", &features->fragmentStoresAndAtomics) &&
+    visitor->Visit("shaderTessellationAndGeometryPointSize", &features->shaderTessellationAndGeometryPointSize) &&
+    visitor->Visit("shaderImageGatherExtended", &features->shaderImageGatherExtended) &&
+    visitor->Visit("shaderStorageImageExtendedFormats", &features->shaderStorageImageExtendedFormats) &&
+    visitor->Visit("shaderStorageImageMultisample", &features->shaderStorageImageMultisample) &&
+    visitor->Visit("shaderStorageImageReadWithoutFormat", &features->shaderStorageImageReadWithoutFormat) &&
+    visitor->Visit("shaderStorageImageWriteWithoutFormat", &features->shaderStorageImageWriteWithoutFormat) &&
+    visitor->Visit("shaderUniformBufferArrayDynamicIndexing", &features->shaderUniformBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderSampledImageArrayDynamicIndexing", &features->shaderSampledImageArrayDynamicIndexing) &&
+    visitor->Visit("shaderStorageBufferArrayDynamicIndexing", &features->shaderStorageBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderStorageImageArrayDynamicIndexing", &features->shaderStorageImageArrayDynamicIndexing) &&
+    visitor->Visit("shaderClipDistance", &features->shaderClipDistance) &&
+    visitor->Visit("shaderCullDistance", &features->shaderCullDistance) &&
+    visitor->Visit("shaderFloat64", &features->shaderFloat64) &&
+    visitor->Visit("shaderInt64", &features->shaderInt64) &&
+    visitor->Visit("shaderInt16", &features->shaderInt16) &&
+    visitor->Visit("shaderResourceResidency", &features->shaderResourceResidency) &&
+    visitor->Visit("shaderResourceMinLod", &features->shaderResourceMinLod) &&
+    visitor->Visit("sparseBinding", &features->sparseBinding) &&
+    visitor->Visit("sparseResidencyBuffer", &features->sparseResidencyBuffer) &&
+    visitor->Visit("sparseResidencyImage2D", &features->sparseResidencyImage2D) &&
+    visitor->Visit("sparseResidencyImage3D", &features->sparseResidencyImage3D) &&
+    visitor->Visit("sparseResidency2Samples", &features->sparseResidency2Samples) &&
+    visitor->Visit("sparseResidency4Samples", &features->sparseResidency4Samples) &&
+    visitor->Visit("sparseResidency8Samples", &features->sparseResidency8Samples) &&
+    visitor->Visit("sparseResidency16Samples", &features->sparseResidency16Samples) &&
+    visitor->Visit("sparseResidencyAliased", &features->sparseResidencyAliased) &&
+    visitor->Visit("variableMultisampleRate", &features->variableMultisampleRate) &&
+    visitor->Visit("inheritedQueries", &features->inheritedQueries);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkJsonExtVariablePointerFeatures* features) {
+  return visitor->Visit("variablePointerFeaturesKHR",
+                        &features->variable_pointer_features_khr);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkMemoryType* type) {
+  return
+    visitor->Visit("propertyFlags", &type->propertyFlags) &&
+    visitor->Visit("heapIndex", &type->heapIndex);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkMemoryHeap* heap) {
+  return
+    visitor->Visit("size", &heap->size) &&
+    visitor->Visit("flags", &heap->flags);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceMemoryProperties* properties) {
+  return
+    visitor->Visit("memoryTypeCount", &properties->memoryTypeCount) &&
+    visitor->VisitArray("memoryTypes", properties->memoryTypeCount, &properties->memoryTypes) &&
+    visitor->Visit("memoryHeapCount", &properties->memoryHeapCount) &&
+    visitor->VisitArray("memoryHeaps", properties->memoryHeapCount, &properties->memoryHeaps);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceSubgroupProperties* properties) {
+  return visitor->Visit("subgroupSize", &properties->subgroupSize) &&
+         visitor->Visit("supportedStages", &properties->supportedStages) &&
+         visitor->Visit("supportedOperations",
+                        &properties->supportedOperations) &&
+         visitor->Visit("quadOperationsInAllStages",
+                        &properties->quadOperationsInAllStages);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDevicePointClippingProperties* properties) {
+  return visitor->Visit("pointClippingBehavior",
+                        &properties->pointClippingBehavior);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceMultiviewProperties* properties) {
+  return visitor->Visit("maxMultiviewViewCount",
+                        &properties->maxMultiviewViewCount) &&
+         visitor->Visit("maxMultiviewInstanceIndex",
+                        &properties->maxMultiviewInstanceIndex);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceIDProperties* properties) {
+  return visitor->Visit("deviceUUID", &properties->deviceUUID) &&
+         visitor->Visit("driverUUID", &properties->driverUUID) &&
+         visitor->Visit("deviceLUID", &properties->deviceLUID) &&
+         visitor->Visit("deviceNodeMask", &properties->deviceNodeMask) &&
+         visitor->Visit("deviceLUIDValid", &properties->deviceLUIDValid);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceMaintenance3Properties* properties) {
+  return visitor->Visit("maxPerSetDescriptors",
+                        &properties->maxPerSetDescriptors) &&
+         visitor->Visit("maxMemoryAllocationSize",
+                        &properties->maxMemoryAllocationSize);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDevice16BitStorageFeatures* features) {
+  return visitor->Visit("storageBuffer16BitAccess",
+                        &features->storageBuffer16BitAccess) &&
+         visitor->Visit("uniformAndStorageBuffer16BitAccess",
+                        &features->uniformAndStorageBuffer16BitAccess) &&
+         visitor->Visit("storagePushConstant16",
+                        &features->storagePushConstant16) &&
+         visitor->Visit("storageInputOutput16",
+                        &features->storageInputOutput16);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceMultiviewFeatures* features) {
+  return visitor->Visit("multiview", &features->multiview) &&
+         visitor->Visit("multiviewGeometryShader",
+                        &features->multiviewGeometryShader) &&
+         visitor->Visit("multiviewTessellationShader",
+                        &features->multiviewTessellationShader);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceVariablePointerFeatures* features) {
+  return visitor->Visit("variablePointersStorageBuffer",
+                        &features->variablePointersStorageBuffer) &&
+         visitor->Visit("variablePointers", &features->variablePointers);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceProtectedMemoryFeatures* features) {
+  return visitor->Visit("protectedMemory", &features->protectedMemory);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceSamplerYcbcrConversionFeatures* features) {
+  return visitor->Visit("samplerYcbcrConversion",
+                        &features->samplerYcbcrConversion);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkPhysicalDeviceShaderDrawParameterFeatures* features) {
+  return visitor->Visit("shaderDrawParameters",
+                        &features->shaderDrawParameters);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkExternalFenceProperties* properties) {
+  return visitor->Visit("exportFromImportedHandleTypes",
+                        &properties->exportFromImportedHandleTypes) &&
+         visitor->Visit("compatibleHandleTypes",
+                        &properties->compatibleHandleTypes) &&
+         visitor->Visit("externalFenceFeatures",
+                        &properties->externalFenceFeatures);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor,
+                    VkExternalSemaphoreProperties* properties) {
+  return visitor->Visit("exportFromImportedHandleTypes",
+                        &properties->exportFromImportedHandleTypes) &&
+         visitor->Visit("compatibleHandleTypes",
+                        &properties->compatibleHandleTypes) &&
+         visitor->Visit("externalSemaphoreFeatures",
+                        &properties->externalSemaphoreFeatures);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkQueueFamilyProperties* properties) {
+  return
+    visitor->Visit("queueFlags", &properties->queueFlags) &&
+    visitor->Visit("queueCount", &properties->queueCount) &&
+    visitor->Visit("timestampValidBits", &properties->timestampValidBits) &&
+    visitor->Visit("minImageTransferGranularity", &properties->minImageTransferGranularity);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkExtensionProperties* properties) {
+  return
+    visitor->Visit("extensionName", &properties->extensionName) &&
+    visitor->Visit("specVersion", &properties->specVersion);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkLayerProperties* properties) {
+  return
+    visitor->Visit("layerName", &properties->layerName) &&
+    visitor->Visit("specVersion", &properties->specVersion) &&
+    visitor->Visit("implementationVersion", &properties->implementationVersion) &&
+    visitor->Visit("description", &properties->description);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkFormatProperties* properties) {
+  return
+    visitor->Visit("linearTilingFeatures", &properties->linearTilingFeatures) &&
+    visitor->Visit("optimalTilingFeatures", &properties->optimalTilingFeatures) &&
+    visitor->Visit("bufferFeatures", &properties->bufferFeatures);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonLayer* layer) {
+  return visitor->Visit("properties", &layer->properties) &&
+         visitor->Visit("extensions", &layer->extensions);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonDeviceGroup* device_group) {
+  return visitor->Visit("devices", &device_group->device_inds) &&
+         visitor->Visit("subsetAllocation",
+                        &device_group->properties.subsetAllocation);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonDevice* device) {
+  bool ret = true;
+  switch (device->properties.apiVersion ^
+          VK_VERSION_PATCH(device->properties.apiVersion)) {
+    case VK_API_VERSION_1_1:
+      ret &=
+          visitor->Visit("subgroupProperties", &device->subgroup_properties) &&
+          visitor->Visit("pointClippingProperties",
+                         &device->point_clipping_properties) &&
+          visitor->Visit("multiviewProperties",
+                         &device->multiview_properties) &&
+          visitor->Visit("idProperties", &device->id_properties) &&
+          visitor->Visit("maintenance3Properties",
+                         &device->maintenance3_properties) &&
+          visitor->Visit("16bitStorageFeatures",
+                         &device->bit16_storage_features) &&
+          visitor->Visit("multiviewFeatures", &device->multiview_features) &&
+          visitor->Visit("variablePointerFeatures",
+                         &device->variable_pointer_features) &&
+          visitor->Visit("protectedMemoryFeatures",
+                         &device->protected_memory_features) &&
+          visitor->Visit("samplerYcbcrConversionFeatures",
+                         &device->sampler_ycbcr_conversion_features) &&
+          visitor->Visit("shaderDrawParameterFeatures",
+                         &device->shader_draw_parameter_features) &&
+          visitor->Visit("externalFenceProperties",
+                         &device->external_fence_properties) &&
+          visitor->Visit("externalSemaphoreProperties",
+                         &device->external_semaphore_properties);
+    case VK_API_VERSION_1_0:
+      ret &= visitor->Visit("properties", &device->properties) &&
+             visitor->Visit("features", &device->features) &&
+             visitor->Visit("VK_KHR_variable_pointers",
+                            &device->ext_variable_pointer_features) &&
+             visitor->Visit("memory", &device->memory) &&
+             visitor->Visit("queues", &device->queues) &&
+             visitor->Visit("extensions", &device->extensions) &&
+             visitor->Visit("layers", &device->layers) &&
+             visitor->Visit("formats", &device->formats);
+  }
+  return ret;
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonInstance* instance) {
+  bool ret = true;
+  switch (instance->api_version ^ VK_VERSION_PATCH(instance->api_version)) {
+    case VK_API_VERSION_1_1:
+      ret &= visitor->Visit("deviceGroups", &instance->device_groups);
+    case VK_API_VERSION_1_0:
+      ret &= visitor->Visit("layers", &instance->layers) &&
+             visitor->Visit("extensions", &instance->extensions) &&
+             visitor->Visit("devices", &instance->devices);
+  }
+  return ret;
+}
+
+template <typename T>
+using EnableForArithmetic =
+    typename std::enable_if<std::is_arithmetic<T>::value, void>::type;
+
+template <typename T>
+using EnableForStruct =
+    typename std::enable_if<std::is_class<T>::value, void>::type;
+
+template <typename T>
+using EnableForEnum =
+    typename std::enable_if<std::is_enum<T>::value, void>::type;
+
+template <typename T, typename = EnableForStruct<T>, typename = void>
+Json::Value ToJsonValue(const T& value);
+
+template <typename T, typename = EnableForArithmetic<T>>
+inline Json::Value ToJsonValue(const T& value) {
+  return Json::Value(static_cast<double>(value));
+}
+
+inline Json::Value ToJsonValue(const uint64_t& value) {
+  char string[19] = {0};  // "0x" + 16 digits + terminal \0
+  snprintf(string, sizeof(string), "0x%016" PRIx64, value);
+  return Json::Value(string);
+}
+
+template <typename T, typename = EnableForEnum<T>, typename = void,
+          typename = void>
+inline Json::Value ToJsonValue(const T& value) {
+  return Json::Value(static_cast<double>(value));
+}
+
+template <typename T>
+inline Json::Value ArrayToJsonValue(uint32_t count, const T* values) {
+  Json::Value array(Json::arrayValue);
+  for (unsigned int i = 0; i < count; ++i) array.append(ToJsonValue(values[i]));
+  return array;
+}
+
+template <typename T, unsigned int N>
+inline Json::Value ToJsonValue(const T (&value)[N]) {
+  return ArrayToJsonValue(N, value);
+}
+
+template <size_t N>
+inline Json::Value ToJsonValue(const char (&value)[N]) {
+  assert(strlen(value) < N);
+  return Json::Value(value);
+}
+
+template <typename T>
+inline Json::Value ToJsonValue(const std::vector<T>& value) {
+  assert(value.size() <= std::numeric_limits<uint32_t>::max());
+  return ArrayToJsonValue(static_cast<uint32_t>(value.size()), value.data());
+}
+
+template <typename F, typename S>
+inline Json::Value ToJsonValue(const std::pair<F, S>& value) {
+  Json::Value array(Json::arrayValue);
+  array.append(ToJsonValue(value.first));
+  array.append(ToJsonValue(value.second));
+  return array;
+}
+
+template <typename F, typename S>
+inline Json::Value ToJsonValue(const std::map<F, S>& value) {
+  Json::Value array(Json::arrayValue);
+  for (auto& kv : value) array.append(ToJsonValue(kv));
+  return array;
+}
+
+class JsonWriterVisitor {
+ public:
+  JsonWriterVisitor() : object_(Json::objectValue) {}
+
+  ~JsonWriterVisitor() {}
+
+  template <typename T> bool Visit(const char* key, const T* value) {
+    object_[key] = ToJsonValue(*value);
+    return true;
+  }
+
+  template <typename T, uint32_t N>
+  bool VisitArray(const char* key, uint32_t count, const T (*value)[N]) {
+    assert(count <= N);
+    object_[key] = ArrayToJsonValue(count, *value);
+    return true;
+  }
+
+  Json::Value get_object() const { return object_; }
+
+ private:
+  Json::Value object_;
+};
+
+template <typename Visitor, typename T>
+inline void VisitForWrite(Visitor* visitor, const T& t) {
+  Iterate(visitor, const_cast<T*>(&t));
+}
+
+template <typename T, typename /*= EnableForStruct<T>*/, typename /*= void*/>
+Json::Value ToJsonValue(const T& value) {
+  JsonWriterVisitor visitor;
+  VisitForWrite(&visitor, value);
+  return visitor.get_object();
+}
+
+template <typename T, typename = EnableForStruct<T>>
+bool AsValue(Json::Value* json_value, T* t);
+
+inline bool AsValue(Json::Value* json_value, int32_t* value) {
+  if (json_value->type() != Json::realValue) return false;
+  double d = json_value->asDouble();
+  if (!IsIntegral(d) ||
+      d < static_cast<double>(std::numeric_limits<int32_t>::min()) ||
+      d > static_cast<double>(std::numeric_limits<int32_t>::max()))
+    return false;
+  *value = static_cast<int32_t>(d);
+  return true;
+}
+
+inline bool AsValue(Json::Value* json_value, uint64_t* value) {
+  if (json_value->type() != Json::stringValue) return false;
+  int result =
+      std::sscanf(json_value->asString().c_str(), "0x%016" PRIx64, value);
+  return result == 1;
+}
+
+inline bool AsValue(Json::Value* json_value, uint32_t* value) {
+  if (json_value->type() != Json::realValue) return false;
+  double d = json_value->asDouble();
+  if (!IsIntegral(d) || d < 0.0 ||
+      d > static_cast<double>(std::numeric_limits<uint32_t>::max()))
+    return false;
+  *value = static_cast<uint32_t>(d);
+  return true;
+}
+
+inline bool AsValue(Json::Value* json_value, uint8_t* value) {
+  uint32_t value32 = 0;
+  AsValue(json_value, &value32);
+  if (value32 > std::numeric_limits<uint8_t>::max())
+    return false;
+  *value = static_cast<uint8_t>(value32);
+  return true;
+}
+
+inline bool AsValue(Json::Value* json_value, float* value) {
+  if (json_value->type() != Json::realValue) return false;
+  *value = static_cast<float>(json_value->asDouble());
+  return true;
+}
+
+template <typename T>
+inline bool AsArray(Json::Value* json_value, uint32_t count, T* values) {
+  if (json_value->type() != Json::arrayValue || json_value->size() != count)
+    return false;
+  for (uint32_t i = 0; i < count; ++i) {
+    if (!AsValue(&(*json_value)[i], values + i)) return false;
+  }
+  return true;
+}
+
+template <typename T, unsigned int N>
+inline bool AsValue(Json::Value* json_value, T (*value)[N]) {
+  return AsArray(json_value, N, *value);
+}
+
+template <size_t N>
+inline bool AsValue(Json::Value* json_value, char (*value)[N]) {
+  if (json_value->type() != Json::stringValue) return false;
+  size_t len = json_value->asString().length();
+  if (len >= N)
+    return false;
+  memcpy(*value, json_value->asString().c_str(), len);
+  memset(*value + len, 0, N-len);
+  return true;
+}
+
+template <typename T, typename = EnableForEnum<T>, typename = void>
+inline bool AsValue(Json::Value* json_value, T* t) {
+  uint32_t value = 0;
+  if (!AsValue(json_value, &value))
+      return false;
+  if (!EnumTraits<T>::exist(value)) return false;
+  *t = static_cast<T>(value);
+  return true;
+}
+
+template <typename T>
+inline bool AsValue(Json::Value* json_value, std::vector<T>* value) {
+  if (json_value->type() != Json::arrayValue) return false;
+  int size = json_value->size();
+  value->resize(size);
+  return AsArray(json_value, size, value->data());
+}
+
+template <typename F, typename S>
+inline bool AsValue(Json::Value* json_value, std::pair<F, S>* value) {
+  if (json_value->type() != Json::arrayValue || json_value->size() != 2)
+    return false;
+  return AsValue(&(*json_value)[0], &value->first) &&
+         AsValue(&(*json_value)[1], &value->second);
+}
+
+template <typename F, typename S>
+inline bool AsValue(Json::Value* json_value, std::map<F, S>* value) {
+  if (json_value->type() != Json::arrayValue) return false;
+  int size = json_value->size();
+  for (int i = 0; i < size; ++i) {
+    std::pair<F, S> elem;
+    if (!AsValue(&(*json_value)[i], &elem)) return false;
+    if (!value->insert(elem).second)
+      return false;
+  }
+  return true;
+}
+
+template <typename T>
+bool ReadValue(Json::Value* object, const char* key, T* value,
+               std::string* errors) {
+  Json::Value json_value = (*object)[key];
+  if (!json_value) {
+    if (errors)
+      *errors = std::string(key) + " missing.";
+    return false;
+  }
+  if (AsValue(&json_value, value)) return true;
+  if (errors)
+    *errors = std::string("Wrong type for ") + std::string(key) + ".";
+  return false;
+}
+
+template <typename Visitor, typename T>
+inline bool VisitForRead(Visitor* visitor, T* t) {
+  return Iterate(visitor, t);
+}
+
+class JsonReaderVisitor {
+ public:
+  JsonReaderVisitor(Json::Value* object, std::string* errors)
+      : object_(object), errors_(errors) {}
+
+  template <typename T> bool Visit(const char* key, T* value) const {
+    return ReadValue(object_, key, value, errors_);
+  }
+
+  template <typename T, uint32_t N>
+  bool VisitArray(const char* key, uint32_t count, T (*value)[N]) {
+    if (count > N)
+      return false;
+    Json::Value json_value = (*object_)[key];
+    if (!json_value) {
+      if (errors_)
+        *errors_ = std::string(key) + " missing.";
+      return false;
+    }
+    if (AsArray(&json_value, count, *value)) return true;
+    if (errors_)
+      *errors_ = std::string("Wrong type for ") + std::string(key) + ".";
+    return false;
+  }
+
+
+ private:
+  Json::Value* object_;
+  std::string* errors_;
+};
+
+template <typename T, typename /*= EnableForStruct<T>*/>
+bool AsValue(Json::Value* json_value, T* t) {
+  if (json_value->type() != Json::objectValue) return false;
+  JsonReaderVisitor visitor(json_value, nullptr);
+  return VisitForRead(&visitor, t);
+}
+
+
+template <typename T> std::string VkTypeToJson(const T& t) {
+  JsonWriterVisitor visitor;
+  VisitForWrite(&visitor, t);
+  return visitor.get_object().toStyledString();
+}
+
+template <typename T> bool VkTypeFromJson(const std::string& json,
+                                          T* t,
+                                          std::string* errors) {
+  *t = T();
+  Json::Value object(Json::objectValue);
+  Json::Reader reader;
+  reader.parse(json, object, false);
+  if (!object) {
+    if (errors) errors->assign(reader.getFormatedErrorMessages());
+    return false;
+  }
+  return AsValue(&object, t);
+}
+
+}  // anonymous namespace
+
+std::string VkJsonInstanceToJson(const VkJsonInstance& instance) {
+  return VkTypeToJson(instance);
+}
+
+bool VkJsonInstanceFromJson(const std::string& json,
+                            VkJsonInstance* instance,
+                            std::string* errors) {
+  return VkTypeFromJson(json, instance, errors);
+}
+
+std::string VkJsonDeviceToJson(const VkJsonDevice& device) {
+  return VkTypeToJson(device);
+}
+
+bool VkJsonDeviceFromJson(const std::string& json,
+                          VkJsonDevice* device,
+                          std::string* errors) {
+  return VkTypeFromJson(json, device, errors);
+};
+
+std::string VkJsonImageFormatPropertiesToJson(
+    const VkImageFormatProperties& properties) {
+  return VkTypeToJson(properties);
+}
+
+bool VkJsonImageFormatPropertiesFromJson(const std::string& json,
+                                         VkImageFormatProperties* properties,
+                                         std::string* errors) {
+  return VkTypeFromJson(json, properties, errors);
+};
diff --git a/vulkan/vkjson/vkjson.h b/vulkan/vkjson/vkjson.h
new file mode 100644
index 0000000..5e8428a
--- /dev/null
+++ b/vulkan/vkjson/vkjson.h
@@ -0,0 +1,162 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2016 Valve Corporation
+// Copyright (c) 2015-2016 LunarG, Inc.
+// Copyright (c) 2015-2016 Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef VKJSON_H_
+#define VKJSON_H_
+
+#include <vulkan/vulkan.h>
+#include <string.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#ifdef WIN32
+#undef min
+#undef max
+#endif
+
+#ifndef VK_API_VERSION_1_0
+#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0)
+#endif
+
+#ifndef VK_API_VERSION_1_1
+#define VK_API_VERSION_1_1 VK_MAKE_VERSION(1, 1, 0)
+#endif
+
+struct VkJsonLayer {
+  VkLayerProperties properties;
+  std::vector<VkExtensionProperties> extensions;
+};
+
+struct VkJsonExtVariablePointerFeatures {
+  VkJsonExtVariablePointerFeatures() {
+    memset(&variable_pointer_features_khr, 0,
+           sizeof(VkPhysicalDeviceVariablePointerFeaturesKHR));
+  }
+  VkPhysicalDeviceVariablePointerFeaturesKHR variable_pointer_features_khr;
+};
+
+struct VkJsonDevice {
+  VkJsonDevice() {
+    memset(&properties, 0, sizeof(VkPhysicalDeviceProperties));
+    memset(&features, 0, sizeof(VkPhysicalDeviceFeatures));
+    memset(&memory, 0, sizeof(VkPhysicalDeviceMemoryProperties));
+    memset(&subgroup_properties, 0, sizeof(VkPhysicalDeviceSubgroupProperties));
+    memset(&point_clipping_properties, 0,
+           sizeof(VkPhysicalDevicePointClippingProperties));
+    memset(&multiview_properties, 0,
+           sizeof(VkPhysicalDeviceMultiviewProperties));
+    memset(&id_properties, 0, sizeof(VkPhysicalDeviceIDProperties));
+    memset(&maintenance3_properties, 0,
+           sizeof(VkPhysicalDeviceMaintenance3Properties));
+    memset(&bit16_storage_features, 0,
+           sizeof(VkPhysicalDevice16BitStorageFeatures));
+    memset(&multiview_features, 0, sizeof(VkPhysicalDeviceMultiviewFeatures));
+    memset(&variable_pointer_features, 0,
+           sizeof(VkPhysicalDeviceVariablePointerFeatures));
+    memset(&protected_memory_features, 0,
+           sizeof(VkPhysicalDeviceProtectedMemoryFeatures));
+    memset(&sampler_ycbcr_conversion_features, 0,
+           sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures));
+    memset(&shader_draw_parameter_features, 0,
+           sizeof(VkPhysicalDeviceShaderDrawParameterFeatures));
+  }
+  VkPhysicalDeviceProperties properties;
+  VkPhysicalDeviceFeatures features;
+  VkJsonExtVariablePointerFeatures ext_variable_pointer_features;
+  VkPhysicalDeviceMemoryProperties memory;
+  std::vector<VkQueueFamilyProperties> queues;
+  std::vector<VkExtensionProperties> extensions;
+  std::vector<VkLayerProperties> layers;
+  std::map<VkFormat, VkFormatProperties> formats;
+  VkPhysicalDeviceSubgroupProperties subgroup_properties;
+  VkPhysicalDevicePointClippingProperties point_clipping_properties;
+  VkPhysicalDeviceMultiviewProperties multiview_properties;
+  VkPhysicalDeviceIDProperties id_properties;
+  VkPhysicalDeviceMaintenance3Properties maintenance3_properties;
+  VkPhysicalDevice16BitStorageFeatures bit16_storage_features;
+  VkPhysicalDeviceMultiviewFeatures multiview_features;
+  VkPhysicalDeviceVariablePointerFeatures variable_pointer_features;
+  VkPhysicalDeviceProtectedMemoryFeatures protected_memory_features;
+  VkPhysicalDeviceSamplerYcbcrConversionFeatures
+      sampler_ycbcr_conversion_features;
+  VkPhysicalDeviceShaderDrawParameterFeatures shader_draw_parameter_features;
+  std::map<VkExternalFenceHandleTypeFlagBits, VkExternalFenceProperties>
+      external_fence_properties;
+  std::map<VkExternalSemaphoreHandleTypeFlagBits, VkExternalSemaphoreProperties>
+      external_semaphore_properties;
+};
+
+struct VkJsonDeviceGroup {
+  VkJsonDeviceGroup() {
+    memset(&properties, 0, sizeof(VkPhysicalDeviceGroupProperties));
+  }
+  VkPhysicalDeviceGroupProperties properties;
+  std::vector<uint32_t> device_inds;
+};
+
+struct VkJsonInstance {
+  VkJsonInstance() : api_version(0) {}
+  uint32_t api_version;
+  std::vector<VkJsonLayer> layers;
+  std::vector<VkExtensionProperties> extensions;
+  std::vector<VkJsonDevice> devices;
+  std::vector<VkJsonDeviceGroup> device_groups;
+};
+
+VkJsonInstance VkJsonGetInstance();
+std::string VkJsonInstanceToJson(const VkJsonInstance& instance);
+bool VkJsonInstanceFromJson(const std::string& json,
+                            VkJsonInstance* instance,
+                            std::string* errors);
+
+VkJsonDevice VkJsonGetDevice(VkInstance instance,
+                             VkPhysicalDevice device,
+                             uint32_t instanceExtensionCount,
+                             const char* const* instanceExtensions);
+std::string VkJsonDeviceToJson(const VkJsonDevice& device);
+bool VkJsonDeviceFromJson(const std::string& json,
+                          VkJsonDevice* device,
+                          std::string* errors);
+
+std::string VkJsonImageFormatPropertiesToJson(
+    const VkImageFormatProperties& properties);
+bool VkJsonImageFormatPropertiesFromJson(const std::string& json,
+                                         VkImageFormatProperties* properties,
+                                         std::string* errors);
+
+// Backward-compatibility aliases
+typedef VkJsonDevice VkJsonAllProperties;
+inline VkJsonAllProperties VkJsonGetAllProperties(
+    VkPhysicalDevice physicalDevice) {
+  return VkJsonGetDevice(VK_NULL_HANDLE, physicalDevice, 0, nullptr);
+}
+inline std::string VkJsonAllPropertiesToJson(
+    const VkJsonAllProperties& properties) {
+  return VkJsonDeviceToJson(properties);
+}
+inline bool VkJsonAllPropertiesFromJson(const std::string& json,
+                                        VkJsonAllProperties* properties,
+                                        std::string* errors) {
+  return VkJsonDeviceFromJson(json, properties, errors);
+}
+
+#endif  // VKJSON_H_
diff --git a/vulkan/vkjson/vkjson_info.cc b/vulkan/vkjson/vkjson_info.cc
new file mode 100644
index 0000000..3c4b08b
--- /dev/null
+++ b/vulkan/vkjson/vkjson_info.cc
@@ -0,0 +1,184 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2016 Valve Corporation
+// Copyright (c) 2015-2016 LunarG, Inc.
+// Copyright (c) 2015-2016 Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef VK_PROTOTYPES
+#define VK_PROTOTYPES
+#endif
+
+#include "vkjson.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <iostream>
+#include <vector>
+
+const uint32_t unsignedNegOne = (uint32_t)(-1);
+
+struct Options {
+  bool instance = false;
+  uint32_t device_index = unsignedNegOne;
+  std::string device_name;
+  std::string output_file;
+};
+
+bool ParseOptions(int argc, char* argv[], Options* options) {
+  for (int i = 1; i < argc; ++i) {
+    std::string arg(argv[i]);
+    if (arg == "--instance" || arg == "-i") {
+      options->instance = true;
+    } else if (arg == "--first" || arg == "-f") {
+      options->device_index = 0;
+    } else {
+      ++i;
+      if (i >= argc) {
+        std::cerr << "Missing parameter after: " << arg << std::endl;
+        return false;
+      }
+      std::string arg2(argv[i]);
+      if (arg == "--device-index" || arg == "-d") {
+        int result = sscanf(arg2.c_str(), "%u", &options->device_index);
+        if (result != 1) {
+          options->device_index = static_cast<uint32_t>(-1);
+          std::cerr << "Unable to parse index: " << arg2 << std::endl;
+          return false;
+        }
+      } else if (arg == "--device-name" || arg == "-n") {
+        options->device_name = arg2;
+      } else if (arg == "--output" || arg == "-o") {
+        options->output_file = arg2;
+      } else {
+        std::cerr << "Unknown argument: " << arg << std::endl;
+        return false;
+      }
+    }
+  }
+  if (options->instance && (options->device_index != unsignedNegOne ||
+                            !options->device_name.empty())) {
+    std::cerr << "Specifying a specific device is incompatible with dumping "
+                 "the whole instance." << std::endl;
+    return false;
+  }
+  if (options->device_index != unsignedNegOne && !options->device_name.empty()) {
+    std::cerr << "Must specify only one of device index and device name."
+              << std::endl;
+    return false;
+  }
+  if (options->instance && options->output_file.empty()) {
+    std::cerr << "Must specify an output file when dumping the whole instance."
+              << std::endl;
+    return false;
+  }
+  if (!options->output_file.empty() && !options->instance &&
+      options->device_index == unsignedNegOne && options->device_name.empty()) {
+    std::cerr << "Must specify instance, device index, or device name when "
+                 "specifying "
+                 "output file." << std::endl;
+    return false;
+  }
+  return true;
+}
+
+bool Dump(const VkJsonInstance& instance, const Options& options) {
+  const VkJsonDevice* out_device = nullptr;
+  if (options.device_index != unsignedNegOne) {
+    if (static_cast<uint32_t>(options.device_index) >=
+        instance.devices.size()) {
+      std::cerr << "Error: device " << options.device_index
+                << " requested but only " << instance.devices.size()
+                << " devices found." << std::endl;
+      return false;
+    }
+    out_device = &instance.devices[options.device_index];
+  } else if (!options.device_name.empty()) {
+    for (const auto& device : instance.devices) {
+      if (device.properties.deviceName == options.device_name) {
+        out_device = &device;
+      }
+    }
+    if (!out_device) {
+      std::cerr << "Error: device '" << options.device_name
+                << "' requested but not found." << std::endl;
+      return false;
+    }
+  }
+
+  std::string output_file;
+  if (options.output_file.empty()) {
+    assert(out_device);
+#if defined(ANDROID)
+    output_file.assign("/sdcard/Android/" + std::string(out_device->properties.deviceName));
+#else
+    output_file.assign(out_device->properties.deviceName);
+#endif
+    output_file.append(".json");
+  } else {
+    output_file = options.output_file;
+  }
+  FILE* file = nullptr;
+  if (output_file == "-") {
+    file = stdout;
+  } else {
+    file = fopen(output_file.c_str(), "w");
+    if (!file) {
+      std::cerr << "Unable to open file " << output_file << "." << std::endl;
+      return false;
+    }
+  }
+
+  std::string json = out_device ? VkJsonDeviceToJson(*out_device)
+                                : VkJsonInstanceToJson(instance);
+  fwrite(json.data(), 1, json.size(), file);
+  fputc('\n', file);
+
+  if (output_file != "-") {
+    fclose(file);
+    std::cout << "Wrote file " << output_file;
+    if (out_device)
+      std::cout << " for device " << out_device->properties.deviceName;
+    std::cout << "." << std::endl;
+  }
+  return true;
+}
+
+int main(int argc, char* argv[]) {
+#if defined(ANDROID)
+  int vulkanSupport = InitVulkan();
+  if (vulkanSupport == 0)
+    return 1;
+#endif
+  Options options;
+  if (!ParseOptions(argc, argv, &options))
+    return 1;
+
+  VkJsonInstance instance = VkJsonGetInstance();
+  if (options.instance || options.device_index != unsignedNegOne ||
+      !options.device_name.empty()) {
+    Dump(instance, options);
+  } else {
+    for (uint32_t i = 0, n = static_cast<uint32_t>(instance.devices.size()); i < n; i++) {
+      options.device_index = i;
+      Dump(instance, options);
+    }
+  }
+
+  return 0;
+}
diff --git a/vulkan/vkjson/vkjson_instance.cc b/vulkan/vkjson/vkjson_instance.cc
new file mode 100644
index 0000000..db0450d
--- /dev/null
+++ b/vulkan/vkjson/vkjson_instance.cc
@@ -0,0 +1,417 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2016 Valve Corporation
+// Copyright (c) 2015-2016 LunarG, Inc.
+// Copyright (c) 2015-2016 Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef VK_PROTOTYPES
+#define VK_PROTOTYPES
+#endif
+
+#include "vkjson.h"
+
+#include <algorithm>
+#include <utility>
+
+namespace {
+const char* kSupportedInstanceExtensions[] = {
+    "VK_KHR_get_physical_device_properties2"};
+
+bool EnumerateExtensions(const char* layer_name,
+                         std::vector<VkExtensionProperties>* extensions) {
+  VkResult result;
+  uint32_t count = 0;
+  result = vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr);
+  if (result != VK_SUCCESS)
+    return false;
+  extensions->resize(count);
+  result = vkEnumerateInstanceExtensionProperties(layer_name, &count,
+                                                  extensions->data());
+  if (result != VK_SUCCESS)
+    return false;
+  return true;
+}
+
+bool HasExtension(const char* extension_name,
+                  uint32_t count,
+                  const char* const* extensions) {
+  return std::find_if(extensions, extensions + count,
+                      [extension_name](const char* extension) {
+                        return strcmp(extension, extension_name) == 0;
+                      }) != extensions + count;
+}
+
+bool HasExtension(const char* extension_name,
+                  const std::vector<VkExtensionProperties>& extensions) {
+  return std::find_if(extensions.cbegin(), extensions.cend(),
+                      [extension_name](const VkExtensionProperties& extension) {
+                        return strcmp(extension.extensionName,
+                                      extension_name) == 0;
+                      }) != extensions.cend();
+}
+}  // anonymous namespace
+
+VkJsonDevice VkJsonGetDevice(VkInstance instance,
+                             VkPhysicalDevice physical_device,
+                             uint32_t instance_extension_count,
+                             const char* const* instance_extensions) {
+  VkJsonDevice device;
+
+  PFN_vkGetPhysicalDeviceFeatures2KHR vkpGetPhysicalDeviceFeatures2KHR =
+      nullptr;
+  if (instance != VK_NULL_HANDLE &&
+      HasExtension("VK_KHR_get_physical_device_properties2",
+                   instance_extension_count, instance_extensions)) {
+    vkpGetPhysicalDeviceFeatures2KHR =
+        reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2KHR>(
+            vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2KHR"));
+  }
+
+  uint32_t extension_count = 0;
+  vkEnumerateDeviceExtensionProperties(physical_device, nullptr,
+                                       &extension_count, nullptr);
+  if (extension_count > 0) {
+    device.extensions.resize(extension_count);
+    vkEnumerateDeviceExtensionProperties(
+        physical_device, nullptr, &extension_count, device.extensions.data());
+  }
+
+  uint32_t layer_count = 0;
+  vkEnumerateDeviceLayerProperties(physical_device, &layer_count, nullptr);
+  if (layer_count > 0) {
+    device.layers.resize(layer_count);
+    vkEnumerateDeviceLayerProperties(physical_device, &layer_count,
+                                     device.layers.data());
+  }
+
+  vkGetPhysicalDeviceProperties(physical_device, &device.properties);
+  if (HasExtension("VK_KHR_get_physical_device_properties2",
+                   instance_extension_count, instance_extensions)) {
+    VkPhysicalDeviceFeatures2KHR features = {
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+        nullptr,
+        {}  // features
+    };
+    if (HasExtension("VK_KHR_variable_pointers", device.extensions)) {
+      device.ext_variable_pointer_features.variable_pointer_features_khr.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES_KHR;
+      device.ext_variable_pointer_features.variable_pointer_features_khr.pNext =
+          features.pNext;
+      features.pNext =
+          &device.ext_variable_pointer_features.variable_pointer_features_khr;
+    }
+    vkpGetPhysicalDeviceFeatures2KHR(physical_device, &features);
+    device.features = features.features;
+  } else {
+    vkGetPhysicalDeviceFeatures(physical_device, &device.features);
+  }
+  vkGetPhysicalDeviceMemoryProperties(physical_device, &device.memory);
+
+  uint32_t queue_family_count = 0;
+  vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count,
+                                           nullptr);
+  if (queue_family_count > 0) {
+    device.queues.resize(queue_family_count);
+    vkGetPhysicalDeviceQueueFamilyProperties(
+        physical_device, &queue_family_count, device.queues.data());
+  }
+
+  VkFormatProperties format_properties = {};
+  for (VkFormat format = VK_FORMAT_R4G4_UNORM_PACK8;
+       format <= VK_FORMAT_END_RANGE;
+       format = static_cast<VkFormat>(format + 1)) {
+    vkGetPhysicalDeviceFormatProperties(physical_device, format,
+                                        &format_properties);
+    if (format_properties.linearTilingFeatures ||
+        format_properties.optimalTilingFeatures ||
+        format_properties.bufferFeatures) {
+      device.formats.insert(std::make_pair(format, format_properties));
+    }
+  }
+
+  if (device.properties.apiVersion >= VK_API_VERSION_1_1) {
+    for (VkFormat format = VK_FORMAT_G8B8G8R8_422_UNORM;
+         format <= VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM;
+         format = static_cast<VkFormat>(format + 1)) {
+      vkGetPhysicalDeviceFormatProperties(physical_device, format,
+                                          &format_properties);
+      if (format_properties.linearTilingFeatures ||
+          format_properties.optimalTilingFeatures ||
+          format_properties.bufferFeatures) {
+        device.formats.insert(std::make_pair(format, format_properties));
+      }
+    }
+
+    PFN_vkGetPhysicalDeviceProperties2 vkpGetPhysicalDeviceProperties2 =
+        reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
+            vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2"));
+    if (vkpGetPhysicalDeviceProperties2) {
+      VkPhysicalDeviceProperties2 properties2 = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, nullptr, {}};
+
+      device.subgroup_properties.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
+      device.subgroup_properties.pNext = properties2.pNext;
+      properties2.pNext = &device.subgroup_properties;
+
+      device.point_clipping_properties.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES;
+      device.point_clipping_properties.pNext = properties2.pNext;
+      properties2.pNext = &device.point_clipping_properties;
+
+      device.multiview_properties.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
+      device.multiview_properties.pNext = properties2.pNext;
+      properties2.pNext = &device.multiview_properties;
+
+      device.id_properties.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
+      device.id_properties.pNext = properties2.pNext;
+      properties2.pNext = &device.id_properties;
+
+      device.maintenance3_properties.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
+      device.maintenance3_properties.pNext = properties2.pNext;
+      properties2.pNext = &device.maintenance3_properties;
+
+      (*vkpGetPhysicalDeviceProperties2)(physical_device, &properties2);
+    }
+
+    PFN_vkGetPhysicalDeviceFeatures2 vkpGetPhysicalDeviceFeatures2 =
+        reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
+            vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2"));
+    if (vkpGetPhysicalDeviceFeatures2) {
+      VkPhysicalDeviceFeatures2 features2 = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, nullptr, {}};
+
+      device.bit16_storage_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
+      device.bit16_storage_features.pNext = features2.pNext;
+      features2.pNext = &device.bit16_storage_features;
+
+      device.multiview_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
+      device.multiview_features.pNext = features2.pNext;
+      features2.pNext = &device.multiview_features;
+
+      device.variable_pointer_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES;
+      device.variable_pointer_features.pNext = features2.pNext;
+      features2.pNext = &device.variable_pointer_features;
+
+      device.protected_memory_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
+      device.protected_memory_features.pNext = features2.pNext;
+      features2.pNext = &device.protected_memory_features;
+
+      device.sampler_ycbcr_conversion_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+      device.sampler_ycbcr_conversion_features.pNext = features2.pNext;
+      features2.pNext = &device.sampler_ycbcr_conversion_features;
+
+      device.shader_draw_parameter_features.sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES;
+      device.shader_draw_parameter_features.pNext = features2.pNext;
+      features2.pNext = &device.shader_draw_parameter_features;
+
+      (*vkpGetPhysicalDeviceFeatures2)(physical_device, &features2);
+    }
+
+    PFN_vkGetPhysicalDeviceExternalFenceProperties
+        vkpGetPhysicalDeviceExternalFenceProperties =
+            reinterpret_cast<PFN_vkGetPhysicalDeviceExternalFenceProperties>(
+                vkGetInstanceProcAddr(
+                    instance, "vkGetPhysicalDeviceExternalFenceProperties"));
+    if (vkpGetPhysicalDeviceExternalFenceProperties) {
+      VkPhysicalDeviceExternalFenceInfo external_fence_info = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, nullptr,
+          VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT};
+      VkExternalFenceProperties external_fence_properties = {};
+
+      for (VkExternalFenceHandleTypeFlagBits handle_type =
+               VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT;
+           handle_type <= VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT;
+           handle_type = static_cast<VkExternalFenceHandleTypeFlagBits>(
+               handle_type << 1)) {
+        external_fence_info.handleType = handle_type;
+        (*vkpGetPhysicalDeviceExternalFenceProperties)(
+            physical_device, &external_fence_info, &external_fence_properties);
+        if (external_fence_properties.exportFromImportedHandleTypes ||
+            external_fence_properties.compatibleHandleTypes ||
+            external_fence_properties.externalFenceFeatures) {
+          device.external_fence_properties.insert(
+              std::make_pair(handle_type, external_fence_properties));
+        }
+      }
+    }
+
+    PFN_vkGetPhysicalDeviceExternalSemaphoreProperties
+        vkpGetPhysicalDeviceExternalSemaphoreProperties = reinterpret_cast<
+            PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
+            vkGetInstanceProcAddr(
+                instance, "vkGetPhysicalDeviceExternalSemaphoreProperties"));
+    if (vkpGetPhysicalDeviceExternalSemaphoreProperties) {
+      VkPhysicalDeviceExternalSemaphoreInfo external_semaphore_info = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, nullptr,
+          VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT};
+      VkExternalSemaphoreProperties external_semaphore_properties = {};
+
+      for (VkExternalSemaphoreHandleTypeFlagBits handle_type =
+               VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
+           handle_type <= VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+           handle_type = static_cast<VkExternalSemaphoreHandleTypeFlagBits>(
+               handle_type << 1)) {
+        external_semaphore_info.handleType = handle_type;
+        (*vkpGetPhysicalDeviceExternalSemaphoreProperties)(
+            physical_device, &external_semaphore_info,
+            &external_semaphore_properties);
+        if (external_semaphore_properties.exportFromImportedHandleTypes ||
+            external_semaphore_properties.compatibleHandleTypes ||
+            external_semaphore_properties.externalSemaphoreFeatures) {
+          device.external_semaphore_properties.insert(
+              std::make_pair(handle_type, external_semaphore_properties));
+        }
+      }
+    }
+  }
+
+  return device;
+}
+
+VkJsonInstance VkJsonGetInstance() {
+  VkJsonInstance instance;
+  VkResult result;
+  uint32_t count;
+
+  count = 0;
+  result = vkEnumerateInstanceLayerProperties(&count, nullptr);
+  if (result != VK_SUCCESS)
+    return VkJsonInstance();
+  if (count > 0) {
+    std::vector<VkLayerProperties> layers(count);
+    result = vkEnumerateInstanceLayerProperties(&count, layers.data());
+    if (result != VK_SUCCESS)
+      return VkJsonInstance();
+    instance.layers.reserve(count);
+    for (auto& layer : layers) {
+      instance.layers.push_back(VkJsonLayer{layer, std::vector<VkExtensionProperties>()});
+      if (!EnumerateExtensions(layer.layerName,
+                               &instance.layers.back().extensions))
+        return VkJsonInstance();
+    }
+  }
+
+  if (!EnumerateExtensions(nullptr, &instance.extensions))
+    return VkJsonInstance();
+
+  std::vector<const char*> instance_extensions;
+  for (const auto extension : kSupportedInstanceExtensions) {
+    if (HasExtension(extension, instance.extensions))
+      instance_extensions.push_back(extension);
+  }
+
+  const VkApplicationInfo app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO,
+                                      nullptr,
+                                      "vkjson_info",
+                                      1,
+                                      "",
+                                      0,
+                                      VK_API_VERSION_1_0};
+  VkInstanceCreateInfo instance_info = {
+      VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+      nullptr,
+      0,
+      &app_info,
+      0,
+      nullptr,
+      static_cast<uint32_t>(instance_extensions.size()),
+      instance_extensions.data()};
+  VkInstance vkinstance;
+  result = vkCreateInstance(&instance_info, nullptr, &vkinstance);
+  if (result != VK_SUCCESS)
+    return VkJsonInstance();
+
+  count = 0;
+  result = vkEnumeratePhysicalDevices(vkinstance, &count, nullptr);
+  if (result != VK_SUCCESS) {
+    vkDestroyInstance(vkinstance, nullptr);
+    return VkJsonInstance();
+  }
+
+  std::vector<VkPhysicalDevice> devices(count, VK_NULL_HANDLE);
+  result = vkEnumeratePhysicalDevices(vkinstance, &count, devices.data());
+  if (result != VK_SUCCESS) {
+    vkDestroyInstance(vkinstance, nullptr);
+    return VkJsonInstance();
+  }
+
+  std::map<VkPhysicalDevice, uint32_t> device_map;
+  const uint32_t sz = devices.size();
+  instance.devices.reserve(sz);
+  for (uint32_t i = 0; i < sz; ++i) {
+    device_map.insert(std::make_pair(devices[i], i));
+    instance.devices.emplace_back(VkJsonGetDevice(vkinstance, devices[i],
+                                                  instance_extensions.size(),
+                                                  instance_extensions.data()));
+  }
+
+  PFN_vkEnumerateInstanceVersion vkpEnumerateInstanceVersion =
+      reinterpret_cast<PFN_vkEnumerateInstanceVersion>(
+          vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"));
+  if (!vkpEnumerateInstanceVersion) {
+    instance.api_version = VK_API_VERSION_1_0;
+  } else {
+    result = (*vkpEnumerateInstanceVersion)(&instance.api_version);
+    if (result != VK_SUCCESS) {
+      vkDestroyInstance(vkinstance, nullptr);
+      return VkJsonInstance();
+    }
+  }
+
+  PFN_vkEnumeratePhysicalDeviceGroups vkpEnumeratePhysicalDeviceGroups =
+      reinterpret_cast<PFN_vkEnumeratePhysicalDeviceGroups>(
+          vkGetInstanceProcAddr(vkinstance, "vkEnumeratePhysicalDeviceGroups"));
+  if (vkpEnumeratePhysicalDeviceGroups) {
+    count = 0;
+    result = (*vkpEnumeratePhysicalDeviceGroups)(vkinstance, &count, nullptr);
+    if (result != VK_SUCCESS) {
+      vkDestroyInstance(vkinstance, nullptr);
+      return VkJsonInstance();
+    }
+
+    VkJsonDeviceGroup device_group;
+    std::vector<VkPhysicalDeviceGroupProperties> group_properties;
+    group_properties.resize(count);
+    result = (*vkpEnumeratePhysicalDeviceGroups)(vkinstance, &count,
+                                                 group_properties.data());
+    if (result != VK_SUCCESS) {
+      vkDestroyInstance(vkinstance, nullptr);
+      return VkJsonInstance();
+    }
+    for (auto properties : group_properties) {
+      device_group.properties = properties;
+      for (uint32_t i = 0; i < properties.physicalDeviceCount; ++i) {
+        device_group.device_inds.push_back(
+            device_map[properties.physicalDevices[i]]);
+      }
+      instance.device_groups.push_back(device_group);
+    }
+  }
+
+  vkDestroyInstance(vkinstance, nullptr);
+  return instance;
+}
diff --git a/vulkan/vkjson/vkjson_unittest.cc b/vulkan/vkjson/vkjson_unittest.cc
new file mode 100644
index 0000000..de765cd
--- /dev/null
+++ b/vulkan/vkjson/vkjson_unittest.cc
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+// Copyright (c) 2015-2016 Valve Corporation
+// Copyright (c) 2015-2016 LunarG, Inc.
+// Copyright (c) 2015-2016 Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "vkjson.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <iostream>
+
+#define EXPECT(X) if (!(X)) \
+  ReportFailure(__FILE__, __LINE__, #X);
+
+#define ASSERT(X) if (!(X)) { \
+  ReportFailure(__FILE__, __LINE__, #X); \
+  return 2; \
+}
+
+int g_failures;
+
+void ReportFailure(const char* file, int line, const char* assertion) {
+  std::cout << file << ":" << line << ": \"" << assertion << "\" failed."
+            << std::endl;
+  ++g_failures;
+}
+
+int main(int argc, char* argv[]) {
+  std::string errors;
+  bool result = false;
+
+  VkJsonInstance instance;
+  instance.devices.resize(1);
+  VkJsonDevice& device = instance.devices[0];
+
+  const char name[] = "Test device";
+  memcpy(device.properties.deviceName, name, sizeof(name));
+  device.properties.limits.maxImageDimension1D = 3;
+  device.properties.limits.maxSamplerLodBias = 3.5f;
+  device.properties.limits.bufferImageGranularity = 0x1ffffffffull;
+  device.properties.limits.maxViewportDimensions[0] = 1;
+  device.properties.limits.maxViewportDimensions[1] = 2;
+  VkFormatProperties format_props = {
+      VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,
+      VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
+      VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT};
+  device.formats.insert(std::make_pair(VK_FORMAT_R8_UNORM, format_props));
+  device.formats.insert(std::make_pair(VK_FORMAT_R8G8_UNORM, format_props));
+
+  std::string json = VkJsonInstanceToJson(instance);
+  std::cout << json << std::endl;
+
+  VkJsonInstance instance2;
+  result = VkJsonInstanceFromJson(json, &instance2, &errors);
+  EXPECT(result);
+  if (!result)
+    std::cout << "Error: " << errors << std::endl;
+  const VkJsonDevice& device2 = instance2.devices.at(0);
+
+  EXPECT(!memcmp(&device.properties, &device2.properties,
+                 sizeof(device.properties)));
+  for (auto& kv : device.formats) {
+    auto it = device2.formats.find(kv.first);
+    EXPECT(it != device2.formats.end());
+    EXPECT(!memcmp(&kv.second, &it->second, sizeof(kv.second)));
+  }
+
+  VkImageFormatProperties props = {};
+  json = VkJsonImageFormatPropertiesToJson(props);
+  VkImageFormatProperties props2 = {};
+  result = VkJsonImageFormatPropertiesFromJson(json, &props2, &errors);
+  EXPECT(result);
+  if (!result)
+    std::cout << "Error: " << errors << std::endl;
+
+  EXPECT(!memcmp(&props, &props2, sizeof(props)));
+
+  if (g_failures) {
+    std::cout << g_failures << " failures." << std::endl;
+    return 1;
+  } else {
+    std::cout << "Success." << std::endl;
+    return 0;
+  }
+}