gfxstream: Add Vulkan external memory resource import implementation for host.

Developed for QNX use-case, but applies generally to any
platform that supports external memory for import but not for
export.

Some robustness around VK_QNX_external_memory_screen_buffer API
usage added with the updateExternalMemoryInfo() function.

BUG=319510663
TEST=compile && launch_cvd --gpu_mode=gfxstream_guest_angle

Change-Id: I7b10b70da024df60881c8b27ea29bd44101b95f6
diff --git a/host/ColorBuffer.cpp b/host/ColorBuffer.cpp
index 06f2969..6a8f58d 100644
--- a/host/ColorBuffer.cpp
+++ b/host/ColorBuffer.cpp
@@ -375,6 +375,30 @@
     return true;
 }
 
+bool ColorBuffer::importNativeResource(void* nativeResource, uint32_t type, bool preserveContent) {
+    switch (type) {
+        case RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE: {
+            if (mColorBufferGl) {
+                GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                    << "Native resource import type %s is invalid when GL emulation is active. "
+                    << "Use RESOURCE_TYPE_EGL_NATIVE_PIXMAP of RESOURCE_TYPE_EGL_IMAGE imports "
+                       "instead.";
+                return false;
+            } else if (!mColorBufferVk) {
+                GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                    << "Vulkan emulation must be available for RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE "
+                       "import.";
+                return false;
+            }
+            return mColorBufferVk->importExtMemoryHandle(nativeResource, type, preserveContent);
+        }
+        default:
+            GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
+                << "Unrecognized type for ColorBuffer::importNativeResource.";
+            return false;
+    }
+}
+
 bool ColorBuffer::glOpBlitFromCurrentReadBuffer() {
     if (!mColorBufferGl) {
         GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
diff --git a/host/ColorBuffer.h b/host/ColorBuffer.h
index 47bf96c..f519fe7 100644
--- a/host/ColorBuffer.h
+++ b/host/ColorBuffer.h
@@ -84,6 +84,7 @@
     bool flushFromVkBytes(const void* bytes, size_t bytesSize);
     bool invalidateForGl();
     bool invalidateForVk();
+    bool importNativeResource(void* nativeResource, uint32_t type, bool preserveContent);
 
     GLuint glOpGetTexture();
     bool glOpBlitFromCurrentReadBuffer();
diff --git a/host/FrameBuffer.cpp b/host/FrameBuffer.cpp
index d69b7e6..f7f0016 100644
--- a/host/FrameBuffer.cpp
+++ b/host/FrameBuffer.cpp
@@ -3465,6 +3465,11 @@
             return colorBuffer->glOpImportEglNativePixmap(resource, preserveContent);
         case RESOURCE_TYPE_EGL_IMAGE:
             return colorBuffer->glOpImportEglImage(resource, preserveContent);
+
+        // Note: Additional non-EGL resource-types can be added here, and will
+        // be propagated through color-buffer import functionality
+        case RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE:
+            return colorBuffer->importNativeResource(resource, type, preserveContent);
         default:
             ERR("Error: unsupported resource type: %u", type);
             return false;
diff --git a/host/include/gfxstream/virtio-gpu-gfxstream-renderer-unstable.h b/host/include/gfxstream/virtio-gpu-gfxstream-renderer-unstable.h
index 4276f15..429bfa5 100644
--- a/host/include/gfxstream/virtio-gpu-gfxstream-renderer-unstable.h
+++ b/host/include/gfxstream/virtio-gpu-gfxstream-renderer-unstable.h
@@ -75,6 +75,7 @@
 // types
 #define STREAM_RENDERER_PLATFORM_RESOURCE_TYPE_EGL_NATIVE_PIXMAP 0x01
 #define STREAM_RENDERER_PLATFORM_RESOURCE_TYPE_EGL_IMAGE 0x02
+#define STREAM_RENDERER_PLATFORM_RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE 0x03
 
 // uses
 #define STREAM_RENDERER_PLATFORM_RESOURCE_USE_PRESERVE 0x10
diff --git a/host/tests/Vulkan_unittest.cpp b/host/tests/Vulkan_unittest.cpp
index 23d392a..b3cf5f7 100644
--- a/host/tests/Vulkan_unittest.cpp
+++ b/host/tests/Vulkan_unittest.cpp
@@ -472,10 +472,10 @@
 
 TEST_F(VulkanFrameBufferTest, VkColorBufferWithoutMemoryProperties) {
     // Create a color buffer without any memory properties restriction.
-    EXPECT_TRUE(setupVkColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE,
-                                   kArbitraryColorBufferHandle, true, /* vulkanOnly */
-                                   0                                  /* memoryProperty */
-                                   ));
+    EXPECT_TRUE(createVkColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE,
+                                    kArbitraryColorBufferHandle, true, /* vulkanOnly */
+                                    0                                  /* memoryProperty */
+                                    ));
     EXPECT_TRUE(teardownVkColorBuffer(kArbitraryColorBufferHandle));
 }
 
@@ -537,9 +537,9 @@
     }
 
     // Create a color buffer with the target memory property flags.
-    EXPECT_TRUE(setupVkColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE,
-                                   kArbitraryColorBufferHandle, true, /* vulkanOnly */
-                                   static_cast<uint32_t>(kTargetMemoryPropertyFlags)));
+    EXPECT_TRUE(createVkColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE,
+                                    kArbitraryColorBufferHandle, true, /* vulkanOnly */
+                                    static_cast<uint32_t>(kTargetMemoryPropertyFlags)));
 
     uint32_t allocatedTypeIndex = 0u;
     EXPECT_TRUE(getColorBufferAllocationInfo(kArbitraryColorBufferHandle, nullptr,
diff --git a/host/vulkan/ColorBufferVk.cpp b/host/vulkan/ColorBufferVk.cpp
index 72860c4..0d569b3 100644
--- a/host/vulkan/ColorBufferVk.cpp
+++ b/host/vulkan/ColorBufferVk.cpp
@@ -24,8 +24,8 @@
                                                      uint32_t height, GLenum format,
                                                      FrameworkFormat frameworkFormat,
                                                      bool vulkanOnly, uint32_t memoryProperty) {
-    if (!setupVkColorBuffer(width, height, format, frameworkFormat, handle, vulkanOnly,
-                            memoryProperty)) {
+    if (!createVkColorBuffer(width, height, format, frameworkFormat, handle, vulkanOnly,
+                             memoryProperty)) {
         GL_LOG("Failed to create ColorBufferVk:%d", handle);
         return nullptr;
     }
@@ -58,5 +58,14 @@
     return updateColorBufferFromBytes(mHandle, x, y, w, h, bytes);
 }
 
+bool ColorBufferVk::importExtMemoryHandle(void* nativeResource, uint32_t type,
+                                          bool preserveContent) {
+    // TODO: Any need to support preserveContent?
+    assert(!preserveContent);
+    VK_EXT_MEMORY_HANDLE extMemoryHandle =
+        *reinterpret_cast<VK_EXT_MEMORY_HANDLE*>(&nativeResource);
+    return importExtMemoryHandleToVkColorBuffer(mHandle, type, extMemoryHandle);
+}
+
 }  // namespace vk
 }  // namespace gfxstream
diff --git a/host/vulkan/ColorBufferVk.h b/host/vulkan/ColorBufferVk.h
index c049bf3..8364c93 100644
--- a/host/vulkan/ColorBufferVk.h
+++ b/host/vulkan/ColorBufferVk.h
@@ -36,6 +36,8 @@
     bool updateFromBytes(const std::vector<uint8_t>& bytes);
     bool updateFromBytes(uint32_t x, uint32_t y, uint32_t w, uint32_t h, const void* bytes);
 
+    bool importExtMemoryHandle(void* nativeResource, uint32_t type, bool preserveContent);
+
    private:
     ColorBufferVk(uint32_t handle);
 
diff --git a/host/vulkan/VkAndroidNativeBuffer.cpp b/host/vulkan/VkAndroidNativeBuffer.cpp
index 26435d1..095d387 100644
--- a/host/vulkan/VkAndroidNativeBuffer.cpp
+++ b/host/vulkan/VkAndroidNativeBuffer.cpp
@@ -141,7 +141,8 @@
     auto emu = getGlobalVkEmulation();
 
     if (emu && emu->live) {
-        externalMemoryCompatible = emu->deviceInfo.supportsExternalMemory;
+        externalMemoryCompatible = emu->deviceInfo.supportsExternalMemoryImport &&
+                                   emu->deviceInfo.supportsExternalMemoryExport;
     }
 
     bool colorBufferExportedToGl = false;
diff --git a/host/vulkan/VkCommonOperations.cpp b/host/vulkan/VkCommonOperations.cpp
index 3a9fc12..a48d4e2 100644
--- a/host/vulkan/VkCommonOperations.cpp
+++ b/host/vulkan/VkCommonOperations.cpp
@@ -96,6 +96,7 @@
 
 static android::base::StaticLock sVkEmulationLock;
 
+#if !defined(__QNX__)
 VK_EXT_MEMORY_HANDLE dupExternalMemory(VK_EXT_MEMORY_HANDLE h) {
 #ifdef _WIN32
     auto myProcessHandle = GetCurrentProcess();
@@ -109,6 +110,7 @@
     return dup(h);
 #endif
 }
+#endif
 
 bool getStagingMemoryTypeIndex(VulkanDispatch* vk, VkDevice device,
                                const VkPhysicalDeviceMemoryProperties* memProps,
@@ -545,6 +547,8 @@
         VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
 #ifdef _WIN32
         VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
+#elif defined(__QNX__)
+        VK_QNX_EXTERNAL_MEMORY_SCREEN_BUFFER_EXTENSION_NAME,
 #else
         VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
 #endif
@@ -767,12 +771,18 @@
         ivk->vkEnumerateDeviceExtensionProperties(physdevs[i], nullptr, &deviceExtensionCount,
                                                   deviceExts.data());
 
-        deviceInfos[i].supportsExternalMemory = false;
+        deviceInfos[i].supportsExternalMemoryImport = false;
+        deviceInfos[i].supportsExternalMemoryExport = false;
         deviceInfos[i].glInteropSupported = 0;  // set later
 
         if (sVkEmulation->instanceSupportsExternalMemoryCapabilities) {
-            deviceInfos[i].supportsExternalMemory =
-                extensionsSupported(deviceExts, externalMemoryDeviceExtNames);
+            deviceInfos[i].supportsExternalMemoryExport =
+                deviceInfos[i].supportsExternalMemoryImport =
+                    extensionsSupported(deviceExts, externalMemoryDeviceExtNames);
+#if defined(__QNX__)
+            // External memory export not supported on QNX
+            deviceInfos[i].supportsExternalMemoryExport = false;
+#endif
             deviceInfos[i].supportsIdProperties =
                 sVkEmulation->getPhysicalDeviceProperties2Func != nullptr;
             deviceInfos[i].supportsDriverProperties =
@@ -847,10 +857,23 @@
                 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES,
             };
             vk_append_struct(&features2Chain, &samplerYcbcrConversionFeatures);
+#if defined(__QNX__)
+            VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX extMemScreenBufferFeatures = {
+                .sType =
+                    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_SCREEN_BUFFER_FEATURES_QNX,
+            };
+            vk_append_struct(&features2Chain, &extMemScreenBufferFeatures);
+#endif
             sVkEmulation->getPhysicalDeviceFeatures2Func(physdevs[i], &features2);
 
             deviceInfos[i].supportsSamplerYcbcrConversion =
                 samplerYcbcrConversionFeatures.samplerYcbcrConversion == VK_TRUE;
+#if defined(__QNX__)
+            deviceInfos[i].supportsExternalMemoryImport =
+                extMemScreenBufferFeatures.screenBufferImport == VK_TRUE;
+        } else {
+            deviceInfos[i].supportsExternalMemoryImport = false;
+#endif
         }
 
         uint32_t queueFamilyCount = 0;
@@ -911,7 +934,9 @@
     for (uint32_t i = 0; i < physdevCount; ++i) {
         uint32_t deviceScore = 0;
         if (deviceInfos[i].hasGraphicsQueueFamily) deviceScore += 10000;
-        if (deviceInfos[i].supportsExternalMemory) deviceScore += 1000;
+        if (deviceInfos[i].supportsExternalMemoryImport ||
+            deviceInfos[i].supportsExternalMemoryExport)
+            deviceScore += 1000;
         if (deviceInfos[i].physdevProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ||
             deviceInfos[i].physdevProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) {
             deviceScore += 100;
@@ -987,7 +1012,8 @@
 
     std::unordered_set<const char*> selectedDeviceExtensionNames_;
 
-    if (sVkEmulation->deviceInfo.supportsExternalMemory) {
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryImport ||
+        sVkEmulation->deviceInfo.supportsExternalMemoryExport) {
         for (auto extension : externalMemoryDeviceExtNames) {
             selectedDeviceExtensionNames_.emplace(extension);
         }
@@ -1027,6 +1053,20 @@
                 });
         vk_append_struct(&deviceCiChain, samplerYcbcrConversionFeatures.get());
     }
+#if defined(__QNX__)
+    std::unique_ptr<VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX>
+        extMemScreenBufferFeaturesQNX = nullptr;
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryImport) {
+        extMemScreenBufferFeaturesQNX = std::make_unique<
+            VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX>(
+            VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX{
+                .sType =
+                    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_SCREEN_BUFFER_FEATURES_QNX,
+                .screenBufferImport = VK_TRUE,
+            });
+        vk_append_struct(&deviceCiChain, extMemScreenBufferFeaturesQNX.get());
+    }
+#endif
 
     ivk->vkCreateDevice(sVkEmulation->physdev, &dCi, nullptr, &sVkEmulation->device);
 
@@ -1051,7 +1091,7 @@
         }
     }
 
-    if (sVkEmulation->deviceInfo.supportsExternalMemory) {
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryImport) {
         sVkEmulation->deviceInfo.getImageMemoryRequirements2Func =
             reinterpret_cast<PFN_vkGetImageMemoryRequirements2KHR>(
                 dvk->vkGetDeviceProcAddr(sVkEmulation->device, "vkGetImageMemoryRequirements2KHR"));
@@ -1066,6 +1106,8 @@
             VK_EMU_INIT_RETURN_OR_ABORT_ON_ERROR(ABORT_REASON_OTHER,
                                                  "Cannot find vkGetBufferMemoryRequirements2KHR");
         }
+    }
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryExport) {
 #ifdef _WIN32
         sVkEmulation->deviceInfo.getMemoryHandleFunc =
             reinterpret_cast<PFN_vkGetMemoryWin32HandleKHR>(
@@ -1359,7 +1401,7 @@
 
     auto allocInfoChain = vk_make_chain_iterator(&allocInfo);
 
-    if (sVkEmulation->deviceInfo.supportsExternalMemory && actuallyExternal) {
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryExport && actuallyExternal) {
         vk_append_struct(&allocInfoChain, &exportAi);
     }
 
@@ -1441,10 +1483,11 @@
         return false;
     }
 
-    if (!sVkEmulation->deviceInfo.supportsExternalMemory || !actuallyExternal) {
+    if (!sVkEmulation->deviceInfo.supportsExternalMemoryExport || !actuallyExternal) {
         return true;
     }
 
+    VkResult exportRes = VK_SUCCESS;
 #ifdef _WIN32
     VkMemoryGetWin32HandleInfoKHR getWin32HandleInfo = {
         VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
@@ -1452,28 +1495,26 @@
         info->memory,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
     };
-    VkResult exportRes = sVkEmulation->deviceInfo.getMemoryHandleFunc(
-        sVkEmulation->device, &getWin32HandleInfo, &info->exportedHandle);
-#else
+    exportRes = sVkEmulation->deviceInfo.getMemoryHandleFunc(
+        sVkEmulation->device, &getWin32HandleInfo, &info->externalHandle);
+#elif !defined(__QNX__)
     VkMemoryGetFdInfoKHR getFdInfo = {
         VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
         0,
         info->memory,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
     };
-    VkResult exportRes = sVkEmulation->deviceInfo.getMemoryHandleFunc(
-        sVkEmulation->device, &getFdInfo, &info->exportedHandle);
+    exportRes = sVkEmulation->deviceInfo.getMemoryHandleFunc(sVkEmulation->device, &getFdInfo,
+                                                             &info->externalHandle);
 #endif
 
-    if (exportRes != VK_SUCCESS) {
+    if (exportRes != VK_SUCCESS || VK_EXT_MEMORY_HANDLE_INVALID == info->externalHandle) {
         // LOG(VERBOSE) << "allocExternalMemory: Failed to get external memory "
         //                 "native handle: "
         //              << exportRes;
         return false;
     }
 
-    info->actuallyExternal = true;
-
     return true;
 }
 
@@ -1497,13 +1538,13 @@
 
     info->memory = VK_NULL_HANDLE;
 
-    if (info->exportedHandle != VK_EXT_MEMORY_HANDLE_INVALID) {
+    if (info->externalHandle != VK_EXT_MEMORY_HANDLE_INVALID) {
 #ifdef _WIN32
-        CloseHandle(info->exportedHandle);
-#else
-        close(info->exportedHandle);
+        CloseHandle(info->externalHandle);
+#elif !defined(__QNX__)
+        close(info->externalHandle);
 #endif
-        info->exportedHandle = VK_EXT_MEMORY_HANDLE_INVALID;
+        info->externalHandle = VK_EXT_MEMORY_HANDLE_INVALID;
     }
 }
 
@@ -1514,15 +1555,20 @@
         VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
         0,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
-        info->exportedHandle,
+        info->externalHandle,
         0,
     };
+#elif defined(__QNX__)
+    VkImportScreenBufferInfoQNX importInfo = {
+        VK_STRUCTURE_TYPE_IMPORT_SCREEN_BUFFER_INFO_QNX,
+        info->externalHandle,
+    };
 #else
     VkImportMemoryFdInfoKHR importInfo = {
         VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
         0,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
-        dupExternalMemory(info->exportedHandle),
+        dupExternalMemory(info->externalHandle),
     };
 #endif
     VkMemoryAllocateInfo allocInfo = {
@@ -1557,15 +1603,21 @@
         VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
         &dedicatedInfo,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
-        info->exportedHandle,
+        info->externalHandle,
         0,
     };
+#elif defined(__QNX__)
+    VkImportScreenBufferInfoQNX importInfo = {
+        VK_STRUCTURE_TYPE_IMPORT_SCREEN_BUFFER_INFO_QNX,
+        &dedicatedInfo,
+        info->externalHandle,
+    };
 #else
     VkImportMemoryFdInfoKHR importInfo = {
         VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
         &dedicatedInfo,
         VK_EXT_MEMORY_HANDLE_TYPE_BIT,
-        info->exportedHandle,
+        info->externalHandle,
     };
 #endif
     VkMemoryAllocateInfo allocInfo = {
@@ -1794,6 +1846,43 @@
     return generateColorBufferVkImageCreateInfo_locked(format, width, height, tiling);
 }
 
+static bool updateExternalMemoryInfo(VK_EXT_MEMORY_HANDLE extMemHandle,
+                                     const VkMemoryRequirements* pMemReqs,
+                                     VkEmulation::ExternalMemoryInfo* pInfo) {
+    // Set externalHandle on the output info
+    pInfo->externalHandle = extMemHandle;
+
+#if defined(__QNX__)
+    VkScreenBufferPropertiesQNX screenBufferProps = {
+        VK_STRUCTURE_TYPE_SCREEN_BUFFER_PROPERTIES_QNX,
+        0,
+    };
+    auto vk = sVkEmulation->dvk;
+    VkResult queryRes =
+        vk->vkGetScreenBufferPropertiesQNX(sVkEmulation->device, extMemHandle, &screenBufferProps);
+    if (VK_SUCCESS != queryRes) {
+        VK_COMMON_ERROR("Failed to get QNX Screen Buffer properties, VK error: %d", queryRes);
+        return false;
+    }
+    if (!((1 << pInfo->typeIndex) & screenBufferProps.memoryTypeBits)) {
+        VK_COMMON_ERROR("QNX Screen buffer can not be imported to memory (typeIndex=%d): %d",
+                        pInfo->typeIndex);
+        return false;
+    }
+    if (screenBufferProps.allocationSize < pMemReqs->size) {
+        VK_COMMON_ERROR(
+            "QNX Screen buffer allocationSize (0x%lx) is not large enough for ColorBuffer image "
+            "size requirements (0x%lx)",
+            screenBufferProps.allocationSize, pMemReqs->size);
+        return false;
+    }
+    // Use the actual allocationSize for VkDeviceMemory object creation
+    pInfo->size = screenBufferProps.allocationSize;
+#endif
+
+    return true;
+}
+
 // TODO(liyl): Currently we can only specify required memoryProperty
 // for a color buffer.
 //
@@ -1810,27 +1899,37 @@
 // buffers of one type index for image and one type index for buffer
 // to begin with, via filtering from the host.
 
-bool setupVkColorBufferLocked(uint32_t width, uint32_t height, GLenum internalFormat,
-                              FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
-                              bool vulkanOnly, uint32_t memoryProperty) {
-    if (!isFormatVulkanCompatible(internalFormat)) {
-        VK_COMMON_VERBOSE("Failed to create Vk ColorBuffer: format:%d not compatible.",
-                          internalFormat);
+bool initializeVkColorBufferLocked(
+    uint32_t colorBufferHandle, VK_EXT_MEMORY_HANDLE extMemHandle = VK_EXT_MEMORY_HANDLE_INVALID) {
+    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    // Not initialized
+    if (!infoPtr) {
         return false;
     }
-
-    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-
-    // Already setup
-    if (infoPtr) {
+    // Already initialized Vulkan memory and other related Vulkan objects
+    if (infoPtr->initialized) {
         return true;
     }
 
+    if (!isFormatVulkanCompatible(infoPtr->internalFormat)) {
+        VK_COMMON_VERBOSE("Failed to create Vk ColorBuffer: format:%d not compatible.",
+                          infoPtr->internalFormat);
+        return false;
+    }
+
+    if ((VK_EXT_MEMORY_HANDLE_INVALID != extMemHandle) &&
+        (!sVkEmulation->deviceInfo.supportsExternalMemoryImport)) {
+        VK_COMMON_ERROR(
+            "Failed to initialize Vk ColorBuffer -- extMemHandle provided, but device does "
+            "not support externalMemoryImport");
+        return false;
+    }
+
     VkFormat vkFormat;
-    bool glCompatible = (frameworkFormat == FRAMEWORK_FORMAT_GL_COMPATIBLE);
-    switch (frameworkFormat) {
+    bool glCompatible = (infoPtr->frameworkFormat == FRAMEWORK_FORMAT_GL_COMPATIBLE);
+    switch (infoPtr->frameworkFormat) {
         case FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE:
-            vkFormat = glFormat2VkFormat(internalFormat);
+            vkFormat = glFormat2VkFormat(infoPtr->internalFormat);
             break;
         case FrameworkFormat::FRAMEWORK_FORMAT_NV12:
             vkFormat = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
@@ -1843,24 +1942,16 @@
             vkFormat = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
             break;
         default:
-            VK_COMMON_ERROR("WARNING: unhandled framework format %d\n", frameworkFormat);
-            vkFormat = glFormat2VkFormat(internalFormat);
+            VK_COMMON_ERROR("WARNING: unhandled framework format %d\n", infoPtr->frameworkFormat);
+            vkFormat = glFormat2VkFormat(infoPtr->internalFormat);
             break;
     }
 
-    VkEmulation::ColorBufferInfo res;
-
-    res.handle = colorBufferHandle;
-
-    // TODO
-    res.frameworkFormat = frameworkFormat;
-    res.frameworkStride = 0;
-
-    VkImageTiling tiling = (memoryProperty & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
+    VkImageTiling tiling = (infoPtr->memoryProperty & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
                                ? VK_IMAGE_TILING_LINEAR
                                : VK_IMAGE_TILING_OPTIMAL;
-    std::unique_ptr<VkImageCreateInfo> imageCi =
-        generateColorBufferVkImageCreateInfo_locked(vkFormat, width, height, tiling);
+    std::unique_ptr<VkImageCreateInfo> imageCi = generateColorBufferVkImageCreateInfo_locked(
+        vkFormat, infoPtr->width, infoPtr->height, tiling);
     // pNext will be filled later.
     if (imageCi == nullptr) {
         // it can happen if the format is not supported
@@ -1880,7 +1971,8 @@
 
     VkExternalMemoryImageCreateInfo* extImageCiPtr = nullptr;
 
-    if (sVkEmulation->deviceInfo.supportsExternalMemory) {
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryImport ||
+        sVkEmulation->deviceInfo.supportsExternalMemoryExport) {
         extImageCiPtr = &extImageCi;
     }
 
@@ -1889,18 +1981,19 @@
     auto vk = sVkEmulation->dvk;
 
     VkResult createRes =
-        vk->vkCreateImage(sVkEmulation->device, imageCi.get(), nullptr, &res.image);
+        vk->vkCreateImage(sVkEmulation->device, imageCi.get(), nullptr, &infoPtr->image);
     if (createRes != VK_SUCCESS) {
         // LOG(VERBOSE) << "Failed to create Vulkan image for ColorBuffer "
         //              << colorBufferHandle;
         return false;
     }
 
-    bool useDedicated = sVkEmulation->useDedicatedAllocations;
+    bool useDedicated =
+        sVkEmulation->useDedicatedAllocations || (VK_EXT_MEMORY_HANDLE_INVALID != extMemHandle);
 
-    res.imageCreateInfoShallow = vk_make_orphan_copy(*imageCi);
-    res.currentLayout = res.imageCreateInfoShallow.initialLayout;
-    res.currentQueueFamilyIndex = sVkEmulation->queueFamilyIndex;
+    infoPtr->imageCreateInfoShallow = vk_make_orphan_copy(*imageCi);
+    infoPtr->currentLayout = infoPtr->imageCreateInfoShallow.initialLayout;
+    infoPtr->currentQueueFamilyIndex = sVkEmulation->queueFamilyIndex;
 
     if (!useDedicated && vk->vkGetImageMemoryRequirements2KHR) {
         VkMemoryDedicatedRequirements dedicated_reqs{
@@ -1908,28 +2001,28 @@
         VkMemoryRequirements2 reqs{VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, &dedicated_reqs};
 
         VkImageMemoryRequirementsInfo2 info{VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
-                                            nullptr, res.image};
+                                            nullptr, infoPtr->image};
         vk->vkGetImageMemoryRequirements2KHR(sVkEmulation->device, &info, &reqs);
         useDedicated = dedicated_reqs.requiresDedicatedAllocation;
-        res.memReqs = reqs.memoryRequirements;
+        infoPtr->memReqs = reqs.memoryRequirements;
     } else {
-        vk->vkGetImageMemoryRequirements(sVkEmulation->device, res.image, &res.memReqs);
+        vk->vkGetImageMemoryRequirements(sVkEmulation->device, infoPtr->image, &infoPtr->memReqs);
     }
 
     // Currently we only care about two memory properties: DEVICE_LOCAL
     // and HOST_VISIBLE; other memory properties specified in
     // rcSetColorBufferVulkanMode2() call will be ignored for now.
-    memoryProperty = memoryProperty &
-                     (VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
+    infoPtr->memoryProperty = infoPtr->memoryProperty & (VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+                                                         VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
 
-    res.memory.size = res.memReqs.size;
+    infoPtr->memory.size = infoPtr->memReqs.size;
 
     // Determine memory type.
-    if (memoryProperty) {
-        res.memory.typeIndex =
-            lastGoodTypeIndexWithMemoryProperties(res.memReqs.memoryTypeBits, memoryProperty);
+    if (infoPtr->memoryProperty) {
+        infoPtr->memory.typeIndex = lastGoodTypeIndexWithMemoryProperties(
+            infoPtr->memReqs.memoryTypeBits, infoPtr->memoryProperty);
     } else {
-        res.memory.typeIndex = lastGoodTypeIndex(res.memReqs.memoryTypeBits);
+        infoPtr->memory.typeIndex = lastGoodTypeIndex(infoPtr->memReqs.memoryTypeBits);
     }
 
     // LOG(VERBOSE) << "ColorBuffer " << colorBufferHandle
@@ -1941,23 +2034,39 @@
     //                         .propertyFlags
     //              << ", requested memory property: " << memoryProperty;
 
-    bool isHostVisible = memoryProperty & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
-    Optional<uint64_t> deviceAlignment =
-        isHostVisible ? Optional<uint64_t>(res.memReqs.alignment) : kNullopt;
-    Optional<VkImage> dedicatedImage = useDedicated ? Optional<VkImage>(res.image) : kNullopt;
-    bool allocRes = allocExternalMemory(vk, &res.memory, true /*actuallyExternal*/, deviceAlignment,
-                                        kNullopt, dedicatedImage);
-
-    if (!allocRes) {
-        // LOG(VERBOSE) << "Failed to allocate ColorBuffer with Vulkan backing.";
-        return false;
+    Optional<VkImage> dedicatedImage = useDedicated ? Optional<VkImage>(infoPtr->image) : kNullopt;
+    if (VK_EXT_MEMORY_HANDLE_INVALID != extMemHandle) {
+        if (!updateExternalMemoryInfo(extMemHandle, &infoPtr->memReqs, &infoPtr->memory)) {
+            VK_COMMON_ERROR(
+                "Failed to update external memory info for ColorBuffer: %d\n",
+                colorBufferHandle);
+            return false;
+        }
+        if (!importExternalMemoryDedicatedImage(vk, sVkEmulation->device, &infoPtr->memory,
+                                                *dedicatedImage, &infoPtr->memory.memory)) {
+            VK_COMMON_ERROR(
+                "Failed to import external memory with dedicated Image for colorBuffer: %d\n",
+                colorBufferHandle);
+            return false;
+        }
+    } else {
+        bool isHostVisible = infoPtr->memoryProperty & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+        Optional<uint64_t> deviceAlignment =
+            isHostVisible ? Optional<uint64_t>(infoPtr->memReqs.alignment) : kNullopt;
+        bool allocRes = allocExternalMemory(vk, &infoPtr->memory, true /*actuallyExternal*/,
+                                            deviceAlignment, kNullopt, dedicatedImage);
+        if (!allocRes) {
+            // LOG(VERBOSE) << "Failed to allocate ColorBuffer with Vulkan backing.";
+            return false;
+        }
     }
 
-    res.memory.pageOffset = reinterpret_cast<uint64_t>(res.memory.mappedPtr) % kPageSize;
-    res.memory.bindOffset = res.memory.pageOffset ? kPageSize - res.memory.pageOffset : 0u;
+    infoPtr->memory.pageOffset = reinterpret_cast<uint64_t>(infoPtr->memory.mappedPtr) % kPageSize;
+    infoPtr->memory.bindOffset =
+        infoPtr->memory.pageOffset ? kPageSize - infoPtr->memory.pageOffset : 0u;
 
-    VkResult bindImageMemoryRes = vk->vkBindImageMemory(sVkEmulation->device, res.image,
-                                                        res.memory.memory, res.memory.bindOffset);
+    VkResult bindImageMemoryRes = vk->vkBindImageMemory(
+        sVkEmulation->device, infoPtr->image, infoPtr->memory.memory, infoPtr->memory.bindOffset);
 
     if (bindImageMemoryRes != VK_SUCCESS) {
         fprintf(stderr, "%s: Failed to bind image memory. %d\n", __func__, bindImageMemoryRes);
@@ -1968,9 +2077,9 @@
         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
         .pNext = nullptr,
         .flags = 0,
-        .image = res.image,
+        .image = infoPtr->image,
         .viewType = VK_IMAGE_VIEW_TYPE_2D,
-        .format = res.imageCreateInfoShallow.format,
+        .format = infoPtr->imageCreateInfoShallow.format,
         .components =
             {
                 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1987,7 +2096,8 @@
                 .layerCount = 1,
             },
     };
-    createRes = vk->vkCreateImageView(sVkEmulation->device, &imageViewCi, nullptr, &res.imageView);
+    createRes =
+        vk->vkCreateImageView(sVkEmulation->device, &imageViewCi, nullptr, &infoPtr->imageView);
     if (createRes != VK_SUCCESS) {
         // LOG(VERBOSE) << "Failed to create Vulkan image for ColorBuffer "
         //              << colorBufferHandle;
@@ -1996,15 +2106,39 @@
 
 #if defined(VK_MVK_moltenvk) && defined(__APPLE__)
     if (sVkEmulation->instanceSupportsMoltenVK) {
-        sVkEmulation->getMTLTextureFunc(res.image, &res.mtlTexture);
-        if (!res.mtlTexture) {
+        sVkEmulation->getMTLTextureFunc(infoPtr->image, &infoPtr->mtlTexture);
+        if (!infoPtr->mtlTexture) {
             fprintf(stderr, "%s: Failed to get MTLTexture.\n", __func__);
         }
 
-        CFRetain(res.mtlTexture);
+        CFRetain(infoPtr->mtlTexture);
     }
 #endif
 
+    infoPtr->initialized = true;
+
+    return true;
+}
+
+bool createVkColorBufferLocked(uint32_t width, uint32_t height, GLenum internalFormat,
+                               FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
+                               bool vulkanOnly, uint32_t memoryProperty) {
+    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    // Already initialized
+    if (infoPtr) {
+        return true;
+    }
+
+    VkEmulation::ColorBufferInfo res;
+
+    res.handle = colorBufferHandle;
+    res.width = width;
+    res.height = height;
+    res.memoryProperty = memoryProperty;
+    res.internalFormat = internalFormat;
+    res.frameworkFormat = frameworkFormat;
+    res.frameworkStride = 0;
+
     if (vulkanOnly) {
         res.vulkanMode = VkEmulation::VulkanMode::VulkanOnly;
     }
@@ -2013,16 +2147,30 @@
     return true;
 }
 
-bool setupVkColorBuffer(uint32_t width, uint32_t height, GLenum internalFormat,
-                        FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
-                        bool vulkanOnly, uint32_t memoryProperty) {
+bool createVkColorBuffer(uint32_t width, uint32_t height, GLenum internalFormat,
+                         FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
+                         bool vulkanOnly, uint32_t memoryProperty) {
     if (!sVkEmulation || !sVkEmulation->live) {
         GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "VkEmulation not available.";
     }
 
     AutoLock lock(sVkEmulationLock);
-    return setupVkColorBufferLocked(width, height, internalFormat, frameworkFormat,
-                                    colorBufferHandle, vulkanOnly, memoryProperty);
+    if (!createVkColorBufferLocked(width, height, internalFormat, frameworkFormat,
+                                   colorBufferHandle, vulkanOnly, memoryProperty)) {
+        return false;
+    }
+
+    const auto& deviceInfo = sVkEmulation->deviceInfo;
+    if (!deviceInfo.supportsExternalMemoryExport && deviceInfo.supportsExternalMemoryImport) {
+        /* Returns, deferring initialization of the Vulkan components themselves.
+         * Platforms that support import but not export of external memory must
+         * use importExtMemoryHandleToVkColorBuffer(). Otherwise, the colorBuffer
+         * memory can not be externalized.
+         */
+        return true;
+    }
+
+    return initializeVkColorBufferLocked(colorBufferHandle);
 }
 
 std::optional<VkColorBufferMemoryExport> exportColorBufferMemory(uint32_t colorBufferHandle) {
@@ -2033,7 +2181,8 @@
     AutoLock lock(sVkEmulationLock);
 
     const auto& deviceInfo = sVkEmulation->deviceInfo;
-    if (!deviceInfo.supportsExternalMemory || !deviceInfo.glInteropSupported) {
+    if ((!(deviceInfo.supportsExternalMemoryExport || !deviceInfo.supportsExternalMemoryImport)) ||
+        (!deviceInfo.glInteropSupported)) {
         return std::nullopt;
     }
 
@@ -2046,7 +2195,8 @@
         return std::nullopt;
     }
 
-    ManagedDescriptor descriptor(dupExternalMemory(info->memory.exportedHandle));
+#if !defined(__QNX__)
+    ManagedDescriptor descriptor(dupExternalMemory(info->memory.externalHandle));
 
     info->glExported = true;
 
@@ -2056,6 +2206,9 @@
         .linearTiling = info->imageCreateInfoShallow.tiling == VK_IMAGE_TILING_LINEAR,
         .dedicatedAllocation = info->memory.dedicatedAllocation,
     };
+#else
+    return std::nullopt;
+#endif
 }
 
 bool teardownVkColorBufferLocked(uint32_t colorBufferHandle) {
@@ -2067,20 +2220,22 @@
 
     if (!infoPtr) return false;
 
-    auto& info = *infoPtr;
-    {
-        android::base::AutoLock lock(*sVkEmulation->queueLock);
-        VK_CHECK(vk->vkQueueWaitIdle(sVkEmulation->queue));
-    }
-    vk->vkDestroyImageView(sVkEmulation->device, info.imageView, nullptr);
-    vk->vkDestroyImage(sVkEmulation->device, info.image, nullptr);
-    freeExternalMemoryLocked(vk, &info.memory);
+    if (infoPtr->initialized) {
+        auto& info = *infoPtr;
+        {
+            android::base::AutoLock lock(*sVkEmulation->queueLock);
+            VK_CHECK(vk->vkQueueWaitIdle(sVkEmulation->queue));
+        }
+        vk->vkDestroyImageView(sVkEmulation->device, info.imageView, nullptr);
+        vk->vkDestroyImage(sVkEmulation->device, info.image, nullptr);
+        freeExternalMemoryLocked(vk, &info.memory);
 
 #ifdef __APPLE__
-    if (info.mtlTexture) {
-        CFRelease(info.mtlTexture);
-    }
+        if (info.mtlTexture) {
+            CFRelease(info.mtlTexture);
+        }
 #endif
+    }
 
     sVkEmulation->colorBuffers.erase(colorBufferHandle);
 
@@ -2094,6 +2249,21 @@
     return teardownVkColorBufferLocked(colorBufferHandle);
 }
 
+bool importExtMemoryHandleToVkColorBuffer(uint32_t colorBufferHandle, uint32_t type,
+                                          VK_EXT_MEMORY_HANDLE extMemHandle) {
+    if (!sVkEmulation || !sVkEmulation->live) {
+        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "VkEmulation not available.";
+    }
+    if (VK_EXT_MEMORY_HANDLE_INVALID == extMemHandle) {
+        return false;
+    }
+
+    AutoLock lock(sVkEmulationLock);
+    // Initialize the colorBuffer with the external memory handle
+    // Note that this will fail if the colorBuffer memory was previously initialized.
+    return initializeVkColorBufferLocked(colorBufferHandle, extMemHandle);
+}
+
 VkEmulation::ColorBufferInfo getColorBufferInfo(uint32_t colorBufferHandle) {
     VkEmulation::ColorBufferInfo res;
 
@@ -2528,7 +2698,7 @@
         return VK_EXT_MEMORY_HANDLE_INVALID;
     }
 
-    return infoPtr->memory.exportedHandle;
+    return infoPtr->memory.externalHandle;
 }
 
 bool setColorBufferVulkanMode(uint32_t colorBuffer, uint32_t vulkanMode) {
@@ -2684,7 +2854,8 @@
     };
 
     VkExternalMemoryBufferCreateInfo* extBufferCiPtr = nullptr;
-    if (sVkEmulation->deviceInfo.supportsExternalMemory) {
+    if (sVkEmulation->deviceInfo.supportsExternalMemoryImport ||
+        sVkEmulation->deviceInfo.supportsExternalMemoryExport) {
         extBufferCiPtr = &extBufferCi;
     }
 
@@ -2818,7 +2989,7 @@
         return VK_EXT_MEMORY_HANDLE_INVALID;
     }
 
-    return infoPtr->memory.exportedHandle;
+    return infoPtr->memory.externalHandle;
 }
 
 bool readBufferToBytes(uint32_t bufferHandle, uint64_t offset, uint64_t size, void* outBytes) {
@@ -3021,6 +3192,15 @@
         res &= ~VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA;
         res |= VK_EXT_MEMORY_HANDLE_TYPE_BIT;
     }
+
+#if defined(__QNX__)
+    // QNX only: Replace DMA_BUF_BIT_EXT with SCREEN_BUFFER_BIT_QNX for host calls
+    if (bits & VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) {
+        res &= ~VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
+        res |= VK_EXT_MEMORY_HANDLE_TYPE_BIT;
+    }
+#endif
+
     return res;
 }
 
@@ -3316,12 +3496,16 @@
     constexpr const uint32_t kArbitraryWidth = 64;
     constexpr const uint32_t kArbitraryHeight = 64;
     constexpr const uint32_t kArbitraryHandle = std::numeric_limits<uint32_t>::max();
-    if (!setupVkColorBufferLocked(kArbitraryWidth, kArbitraryHeight, GL_RGBA8,
-                                  FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE, kArbitraryHandle,
-                                  true, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
+    if (!createVkColorBufferLocked(kArbitraryWidth, kArbitraryHeight, GL_RGBA8,
+                                   FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE,
+                                   kArbitraryHandle, true, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
         ERR("Failed to setup memory type index test ColorBuffer.");
         return std::nullopt;
     }
+    if (!initializeVkColorBufferLocked(kArbitraryHandle)) {
+        ERR("Failed to initialize memory type index test ColorBuffer.");
+        return std::nullopt;
+    }
 
     uint32_t memoryTypeIndex = 0;
     if (!getColorBufferAllocationInfoLocked(kArbitraryHandle, nullptr, &memoryTypeIndex, nullptr,
diff --git a/host/vulkan/VkCommonOperations.h b/host/vulkan/VkCommonOperations.h
index 958a57f..ae81350 100644
--- a/host/vulkan/VkCommonOperations.h
+++ b/host/vulkan/VkCommonOperations.h
@@ -52,13 +52,20 @@
 typedef void* HANDLE;
 #endif
 
-// External memory objects are HANDLE on Windows and fd's on POSIX systems.
-#ifdef _WIN32
+#if defined(_WIN32)
+// External memory objects are HANDLE on Windows
 typedef HANDLE VK_EXT_MEMORY_HANDLE;
 // corresponds to INVALID_HANDLE_VALUE
 #define VK_EXT_MEMORY_HANDLE_INVALID (VK_EXT_MEMORY_HANDLE)(uintptr_t)(-1)
 #define VK_EXT_MEMORY_HANDLE_TYPE_BIT VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT
+#elif defined(__QNX__)
+#include <screen/screen.h>
+// External memory objects are screen_buffer_t handles on QNX
+typedef screen_buffer_t VK_EXT_MEMORY_HANDLE;
+#define VK_EXT_MEMORY_HANDLE_INVALID (VK_EXT_MEMORY_HANDLE) nullptr
+#define VK_EXT_MEMORY_HANDLE_TYPE_BIT VK_EXTERNAL_MEMORY_HANDLE_TYPE_SCREEN_BUFFER_BIT_QNX
 #else
+// External memory objects are fd's on other POSIX systems
 typedef int VK_EXT_MEMORY_HANDLE;
 #define VK_EXT_MEMORY_HANDLE_INVALID (-1)
 #define VK_EXT_MEMORY_HANDLE_TYPE_BIT VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT
@@ -174,7 +181,8 @@
     struct DeviceSupportInfo {
         bool hasGraphicsQueueFamily = false;
         bool hasComputeQueueFamily = false;
-        bool supportsExternalMemory = false;
+        bool supportsExternalMemoryImport = false;
+        bool supportsExternalMemoryExport = false;
         bool supportsIdProperties = false;
         bool supportsDriverProperties = false;
         bool hasSamplerYcbcrConversionExtension = false;
@@ -226,8 +234,8 @@
         // guest physical address.
         uintptr_t gpa = 0u;
 
-        VK_EXT_MEMORY_HANDLE exportedHandle = VK_EXT_MEMORY_HANDLE_INVALID;
-        bool actuallyExternal = false;
+        VK_EXT_MEMORY_HANDLE externalHandle = VK_EXT_MEMORY_HANDLE_INVALID;
+
         bool dedicatedAllocation = false;
     };
 
@@ -286,8 +294,14 @@
 
         uint32_t handle;
 
+        /* Set in create(), before initialize() */
+        uint32_t width;
+        uint32_t height;
+        GLenum internalFormat;
+        uint32_t memoryProperty;
         int frameworkFormat;
         int frameworkStride;
+        bool initialized = false;
 
         VkImage image = VK_NULL_HANDLE;
         VkImageView imageView = VK_NULL_HANDLE;
@@ -435,12 +449,15 @@
                                                                         uint32_t height,
                                                                         VkImageTiling tiling);
 
-bool setupVkColorBuffer(uint32_t width, uint32_t height, GLenum format,
-                        FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
-                        bool vulkanOnly, uint32_t memoryProperty);
+bool createVkColorBuffer(uint32_t width, uint32_t height, GLenum format,
+                         FrameworkFormat frameworkFormat, uint32_t colorBufferHandle,
+                         bool vulkanOnly, uint32_t memoryProperty);
 
 bool teardownVkColorBuffer(uint32_t colorBufferHandle);
 
+bool importExtMemoryHandleToVkColorBuffer(uint32_t colorBufferHandle, uint32_t type,
+                                          VK_EXT_MEMORY_HANDLE extMemHandle);
+
 VkEmulation::ColorBufferInfo getColorBufferInfo(uint32_t colorBufferHandle);
 VK_EXT_MEMORY_HANDLE getColorBufferExtMemoryHandle(uint32_t colorBufferHandle);
 
diff --git a/host/vulkan/VkDecoderGlobalState.cpp b/host/vulkan/VkDecoderGlobalState.cpp
index 065c9b6..765e17b 100644
--- a/host/vulkan/VkDecoderGlobalState.cpp
+++ b/host/vulkan/VkDecoderGlobalState.cpp
@@ -127,6 +127,31 @@
     }
 }
 
+#if defined(_WIN32)
+// External sync objects are HANDLE on Windows
+typedef HANDLE VK_EXT_SYNC_HANDLE;
+// corresponds to INVALID_HANDLE_VALUE
+#define VK_EXT_SYNC_HANDLE_INVALID (VK_EXT_SYNC_HANDLE)(uintptr_t)(-1)
+#else
+// External sync objects are fd's on other POSIX systems
+typedef int VK_EXT_SYNC_HANDLE;
+#define VK_EXT_SYNC_HANDLE_INVALID (-1)
+#endif
+
+VK_EXT_SYNC_HANDLE dupExternalSync(VK_EXT_SYNC_HANDLE h) {
+#ifdef _WIN32
+    auto myProcessHandle = GetCurrentProcess();
+    VK_EXT_SYNC_HANDLE res;
+    DuplicateHandle(myProcessHandle, h,     // source process and handle
+                    myProcessHandle, &res,  // target process and pointer to handle
+                    0 /* desired access (ignored) */, true /* inherit */,
+                    DUPLICATE_SAME_ACCESS /* same access option */);
+    return res;
+#else
+    return dup(h);
+#endif
+}
+
 // A list of device extensions that should not be passed to the host driver.
 // These will mainly include Vulkan features that we emulate ourselves.
 static constexpr const char* const kEmulatedDeviceExtensions[] = {
@@ -142,6 +167,10 @@
     VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME,
     VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME,
     VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
+#if defined(__QNX__)
+    VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
+    VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME,
+#endif
 };
 
 // A list of instance extensions that should not be passed to the host driver.
@@ -2086,7 +2115,7 @@
             return VK_ERROR_INVALID_EXTERNAL_HANDLE;
         }
 
-        VK_EXT_MEMORY_HANDLE handle = dupExternalMemory(infoPtr->externalHandle);
+        VK_EXT_SYNC_HANDLE handle = dupExternalSync(infoPtr->externalHandle);
 
         VkImportSemaphoreWin32HandleInfoKHR win32ImportInfo = {
             VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR,
@@ -2117,7 +2146,7 @@
             pGetFdInfo->semaphore,
             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT,
         };
-        VK_EXT_MEMORY_HANDLE handle;
+        VK_EXT_SYNC_HANDLE handle;
         VkResult result = vk->vkGetSemaphoreWin32HandleKHR(device, &getWin32, &handle);
         if (result != VK_SUCCESS) {
             return result;
@@ -2146,7 +2175,7 @@
 #ifndef _WIN32
         const auto& ite = mSemaphoreInfo.find(semaphore);
         if (ite != mSemaphoreInfo.end() &&
-            (ite->second.externalHandle != VK_EXT_MEMORY_HANDLE_INVALID)) {
+            (ite->second.externalHandle != VK_EXT_SYNC_HANDLE_INVALID)) {
             close(ite->second.externalHandle);
         }
 #endif
@@ -3409,6 +3438,12 @@
             VK_EXT_MEMORY_HANDLE_INVALID,
             L"",
         };
+#elif defined(__QNX__)
+        VkImportScreenBufferInfoQNX importInfo{
+            VK_STRUCTURE_TYPE_IMPORT_SCREEN_BUFFER_INFO_QNX,
+            0,
+            VK_EXT_MEMORY_HANDLE_INVALID,
+        };
 #else
         VkImportMemoryFdInfoKHR importInfo{
             VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
@@ -3454,6 +3489,9 @@
                     return VK_ERROR_OUT_OF_DEVICE_MEMORY;
                 }
 
+#if defined(__QNX__)
+                importInfo.buffer = cbExtMemoryHandle;
+#else
                 externalMemoryHandle = ManagedDescriptor(dupExternalMemory(cbExtMemoryHandle));
 
 #ifdef _WIN32
@@ -3461,6 +3499,7 @@
 #else
                 importInfo.fd = externalMemoryHandle.get().value_or(-1);
 #endif
+#endif
                 vk_append_struct(&structChainIter, &importInfo);
             }
         }
@@ -3489,6 +3528,9 @@
                     return VK_ERROR_OUT_OF_DEVICE_MEMORY;
                 }
 
+#if defined(__QNX__)
+                importInfo.buffer = bufferExtMemoryHandle;
+#else
                 bufferExtMemoryHandle = dupExternalMemory(bufferExtMemoryHandle);
 
 #ifdef _WIN32
@@ -3496,6 +3538,7 @@
 #else
                 importInfo.fd = bufferExtMemoryHandle;
 #endif
+#endif
                 vk_append_struct(&structChainIter, &importInfo);
             }
         }
@@ -3577,7 +3620,7 @@
                 ERR("Failed vkAllocateMemory: missing descriptor info.");
                 return VK_ERROR_OUT_OF_DEVICE_MEMORY;
             }
-#if defined(__linux__) || defined(__QNX__)
+#if defined(__linux__)
             importInfo.fd = rawDescriptor;
 #endif
 
@@ -3832,7 +3875,6 @@
         const auto& props = emu->deviceInfo.physdevProps;
 
         res.supportsVulkan1_1 = props.apiVersion >= VK_API_VERSION_1_1;
-        res.supportsExternalMemory = emu->deviceInfo.supportsExternalMemory;
         res.useDeferredCommands = emu->useDeferredCommands;
         res.useCreateResourcesWithRequirements = emu->useCreateResourcesWithRequirements;
 
@@ -5795,6 +5837,12 @@
         if (hasDeviceExtension(properties, VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME)) {
             res.push_back(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME);
         }
+#elif defined(__QNX__)
+        // Note: VK_QNX_external_memory_screen_buffer is not supported in API translation,
+        // decoding, etc. However, push name to indicate external memory support to guest
+        if (hasDeviceExtension(properties, VK_QNX_EXTERNAL_MEMORY_SCREEN_BUFFER_EXTENSION_NAME)) {
+            res.push_back(VK_QNX_EXTERNAL_MEMORY_SCREEN_BUFFER_EXTENSION_NAME);
+        }
 #elif __unix__
         if (hasDeviceExtension(properties, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)) {
             res.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
@@ -6532,7 +6580,7 @@
     struct SemaphoreInfo {
         VkDevice device;
         int externalHandleId = 0;
-        VK_EXT_MEMORY_HANDLE externalHandle = VK_EXT_MEMORY_HANDLE_INVALID;
+        VK_EXT_SYNC_HANDLE externalHandle = VK_EXT_SYNC_HANDLE_INVALID;
     };
 
     struct DescriptorSetLayoutInfo {
diff --git a/host/vulkan/VkDecoderGlobalState.h b/host/vulkan/VkDecoderGlobalState.h
index b8cb5be..4b2d0dc 100644
--- a/host/vulkan/VkDecoderGlobalState.h
+++ b/host/vulkan/VkDecoderGlobalState.h
@@ -406,7 +406,6 @@
     struct HostFeatureSupport {
         bool supportsVulkan = false;
         bool supportsVulkan1_1 = false;
-        bool supportsExternalMemory = false;
         bool useDeferredCommands = false;
         bool useCreateResourcesWithRequirements = false;
         uint32_t apiVersion = 0;
diff --git a/host/vulkan/cereal/common/vk_struct_id.h b/host/vulkan/cereal/common/vk_struct_id.h
index fc321ec..0953a43 100644
--- a/host/vulkan/cereal/common/vk_struct_id.h
+++ b/host/vulkan/cereal/common/vk_struct_id.h
@@ -71,4 +71,10 @@
 REGISTER_VK_STRUCT_ID(VkMemoryOpaqueCaptureAddressAllocateInfo, VK_STRUCTURE_TYPE_MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO);
 REGISTER_VK_STRUCT_ID(VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT)
 
+#if defined(VK_USE_PLATFORM_SCREEN_QNX)
+REGISTER_VK_STRUCT_ID(VkPhysicalDeviceExternalMemoryScreenBufferFeaturesQNX,
+                      VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_SCREEN_BUFFER_FEATURES_QNX);
+REGISTER_VK_STRUCT_ID(VkImportScreenBufferInfoQNX, VK_STRUCTURE_TYPE_IMPORT_SCREEN_BUFFER_INFO_QNX);
+#endif
+
 #undef REGISTER_VK_STRUCT_ID
diff --git a/host/vulkan/cereal/meson.build b/host/vulkan/cereal/meson.build
index d9f917d..b59d839 100644
--- a/host/vulkan/cereal/meson.build
+++ b/host/vulkan/cereal/meson.build
@@ -15,6 +15,10 @@
   '-DVK_GOOGLE_address_space',
 ]
 
+if host_machine.system() == 'qnx'
+  cereal_cpp_args += '-DVK_USE_PLATFORM_SCREEN_QNX'
+endif
+
 lib_vulkan_cereal = static_library(
   'cereal',
   files_lib_cereal,