Vulkan: handle Device extensions more programmatically

Similarly to the CL for Instance extensions, it makes each device
extension linked to an enum class and a bitset. Logic surrounding device
extensions is changed to take advantage of this to be more programmatic
and less error prone when adding support for a new extension.

Bug: dawn:457
Change-Id: Iecf623c40b890b7e00ba972d5eac0712866692b5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22941
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/vulkan/AdapterVk.cpp b/src/dawn_native/vulkan/AdapterVk.cpp
index c220a8e..43ed6ac 100644
--- a/src/dawn_native/vulkan/AdapterVk.cpp
+++ b/src/dawn_native/vulkan/AdapterVk.cpp
@@ -39,7 +39,7 @@
 
     MaybeError Adapter::Initialize() {
         DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(*this));
-        if (!mDeviceInfo.maintenance1) {
+        if (!mDeviceInfo.HasExt(DeviceExt::Maintenance1)) {
             return DAWN_INTERNAL_ERROR(
                 "Dawn requires Vulkan 1.1 or Vulkan 1.0 with KHR_Maintenance1 in order to support "
                 "viewport flipY");
@@ -74,9 +74,9 @@
             mSupportedExtensions.EnableExtension(Extension::TextureCompressionBC);
         }
 
-        if (mDeviceInfo.shaderFloat16Int8 &&
+        if (mDeviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
             mDeviceInfo.shaderFloat16Int8Features.shaderFloat16 == VK_TRUE &&
-            mDeviceInfo._16BitStorage &&
+            mDeviceInfo.HasExt(DeviceExt::_16BitStorage) &&
             mDeviceInfo._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE) {
             mSupportedExtensions.EnableExtension(Extension::ShaderFloat16);
         }
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index ccd04ea..f1e3e46 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -665,7 +665,7 @@
                 }
 
                 case Command::InsertDebugMarker: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>();
                         const char* label = mCommands.NextData<char>(cmd->length + 1);
                         VkDebugMarkerMarkerInfoEXT markerInfo;
@@ -685,7 +685,7 @@
                 }
 
                 case Command::PopDebugGroup: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         mCommands.NextCommand<PopDebugGroupCmd>();
                         device->fn.CmdDebugMarkerEndEXT(commands);
                     } else {
@@ -695,7 +695,7 @@
                 }
 
                 case Command::PushDebugGroup: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>();
                         const char* label = mCommands.NextData<char>(cmd->length + 1);
                         VkDebugMarkerMarkerInfoEXT markerInfo;
@@ -812,7 +812,7 @@
                 }
 
                 case Command::InsertDebugMarker: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         InsertDebugMarkerCmd* cmd = iter->NextCommand<InsertDebugMarkerCmd>();
                         const char* label = iter->NextData<char>(cmd->length + 1);
                         VkDebugMarkerMarkerInfoEXT markerInfo;
@@ -832,7 +832,7 @@
                 }
 
                 case Command::PopDebugGroup: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         iter->NextCommand<PopDebugGroupCmd>();
                         device->fn.CmdDebugMarkerEndEXT(commands);
                     } else {
@@ -842,7 +842,7 @@
                 }
 
                 case Command::PushDebugGroup: {
-                    if (device->GetDeviceInfo().debugMarker) {
+                    if (device->GetDeviceInfo().HasExt(DeviceExt::DebugMarker)) {
                         PushDebugGroupCmd* cmd = iter->NextCommand<PushDebugGroupCmd>();
                         const char* label = iter->NextData<char>(cmd->length + 1);
                         VkDebugMarkerMarkerInfoEXT markerInfo;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index fb116a1..bb16f84 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -268,54 +268,17 @@
     ResultOrError<VulkanDeviceKnobs> Device::CreateDevice(VkPhysicalDevice physicalDevice) {
         VulkanDeviceKnobs usedKnobs = {};
 
-        float zero = 0.0f;
-        std::vector<const char*> layersToRequest;
-        std::vector<const char*> extensionsToRequest;
-        std::vector<VkDeviceQueueCreateInfo> queuesToRequest;
+        // Default to asking for all avilable known extensions.
+        usedKnobs.extensions = mDeviceInfo.extensions;
 
-        if (mDeviceInfo.debugMarker) {
-            extensionsToRequest.push_back(kExtensionNameExtDebugMarker);
-            usedKnobs.debugMarker = true;
-        }
-        if (mDeviceInfo.externalMemory) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalMemory);
-            usedKnobs.externalMemory = true;
-        }
-        if (mDeviceInfo.externalMemoryFD) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalMemoryFD);
-            usedKnobs.externalMemoryFD = true;
-        }
-        if (mDeviceInfo.externalMemoryDmaBuf) {
-            extensionsToRequest.push_back(kExtensionNameExtExternalMemoryDmaBuf);
-            usedKnobs.externalMemoryDmaBuf = true;
-        }
-        if (mDeviceInfo.imageDrmFormatModifier) {
-            extensionsToRequest.push_back(kExtensionNameExtImageDrmFormatModifier);
-            usedKnobs.imageDrmFormatModifier = true;
-        }
-        if (mDeviceInfo.externalMemoryZirconHandle) {
-            extensionsToRequest.push_back(kExtensionNameFuchsiaExternalMemory);
-            usedKnobs.externalMemoryZirconHandle = true;
-        }
-        if (mDeviceInfo.externalSemaphore) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalSemaphore);
-            usedKnobs.externalSemaphore = true;
-        }
-        if (mDeviceInfo.externalSemaphoreFD) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalSemaphoreFD);
-            usedKnobs.externalSemaphoreFD = true;
-        }
-        if (mDeviceInfo.externalSemaphoreZirconHandle) {
-            extensionsToRequest.push_back(kExtensionNameFuchsiaExternalSemaphore);
-            usedKnobs.externalSemaphoreZirconHandle = true;
-        }
-        if (mDeviceInfo.swapchain) {
-            extensionsToRequest.push_back(kExtensionNameKhrSwapchain);
-            usedKnobs.swapchain = true;
-        }
-        if (mDeviceInfo.maintenance1) {
-            extensionsToRequest.push_back(kExtensionNameKhrMaintenance1);
-            usedKnobs.maintenance1 = true;
+        // However only request the extensions that haven't been promoted in the device's apiVersion
+        std::vector<const char*> extensionNames;
+        for (uint32_t ext : IterateBitSet(usedKnobs.extensions.extensionBitSet)) {
+            const DeviceExtInfo& info = GetDeviceExtInfo(static_cast<DeviceExt>(ext));
+
+            if (info.versionPromoted > mDeviceInfo.properties.apiVersion) {
+                extensionNames.push_back(info.name);
+            }
         }
 
         // Always require independentBlend because it is a core Dawn feature
@@ -333,21 +296,13 @@
 
         if (IsExtensionEnabled(Extension::ShaderFloat16)) {
             const VulkanDeviceInfo& deviceInfo = ToBackend(GetAdapter())->GetDeviceInfo();
-            ASSERT(deviceInfo.shaderFloat16Int8 &&
+            ASSERT(deviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
                    deviceInfo.shaderFloat16Int8Features.shaderFloat16 == VK_TRUE &&
-                   deviceInfo._16BitStorage &&
+                   deviceInfo.HasExt(DeviceExt::_16BitStorage) &&
                    deviceInfo._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE);
 
-            usedKnobs.shaderFloat16Int8 = true;
             usedKnobs.shaderFloat16Int8Features.shaderFloat16 = VK_TRUE;
-            extensionsToRequest.push_back(kExtensionNameKhrShaderFloat16Int8);
-
-            usedKnobs._16BitStorage = true;
             usedKnobs._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess = VK_TRUE;
-            // VK_KHR_16bit_storage is promoted to Vulkan 1.1.
-            if (deviceInfo.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
-                extensionsToRequest.push_back(kExtensionNameKhr16BitStorage);
-            }
         }
 
         // Find a universal queue family
@@ -370,6 +325,8 @@
         }
 
         // Choose to create a single universal queue
+        std::vector<VkDeviceQueueCreateInfo> queuesToRequest;
+        float zero = 0.0f;
         {
             VkDeviceQueueCreateInfo queueCreateInfo;
             queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
@@ -388,10 +345,10 @@
         createInfo.flags = 0;
         createInfo.queueCreateInfoCount = static_cast<uint32_t>(queuesToRequest.size());
         createInfo.pQueueCreateInfos = queuesToRequest.data();
-        createInfo.enabledLayerCount = static_cast<uint32_t>(layersToRequest.size());
-        createInfo.ppEnabledLayerNames = layersToRequest.data();
-        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensionsToRequest.size());
-        createInfo.ppEnabledExtensionNames = extensionsToRequest.data();
+        createInfo.enabledLayerCount = 0;
+        createInfo.ppEnabledLayerNames = nullptr;
+        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensionNames.size());
+        createInfo.ppEnabledExtensionNames = extensionNames.data();
         createInfo.pEnabledFeatures = &usedKnobs.features;
 
         DAWN_TRY(CheckVkSuccess(fn.CreateDevice(physicalDevice, &createInfo, nullptr, &mVkDevice),
diff --git a/src/dawn_native/vulkan/VulkanExtensions.cpp b/src/dawn_native/vulkan/VulkanExtensions.cpp
index ea0ccab..353f087 100644
--- a/src/dawn_native/vulkan/VulkanExtensions.cpp
+++ b/src/dawn_native/vulkan/VulkanExtensions.cpp
@@ -23,6 +23,7 @@
 namespace dawn_native { namespace vulkan {
 
     static constexpr uint32_t VulkanVersion_1_1 = VK_MAKE_VERSION(1, 1, 0);
+    static constexpr uint32_t VulkanVersion_1_2 = VK_MAKE_VERSION(1, 2, 0);
     static constexpr uint32_t NeverPromoted = std::numeric_limits<uint32_t>::max();
 
     // A static array for InstanceExtInfo that can be indexed with InstanceExts.
@@ -132,4 +133,154 @@
         }
     }
 
+    static constexpr size_t kDeviceExtCount = static_cast<size_t>(DeviceExt::EnumCount);
+    static constexpr std::array<DeviceExtInfo, kDeviceExtCount> sDeviceExtInfos{{
+        //
+        {DeviceExt::Maintenance1, "VK_KHR_maintenance1", VulkanVersion_1_1},
+        {DeviceExt::ExternalMemory, "VK_KHR_external_memory", VulkanVersion_1_1},
+        {DeviceExt::ExternalSemaphore, "VK_KHR_external_semaphore", VulkanVersion_1_1},
+        {DeviceExt::_16BitStorage, "VK_KHR_16bit_storage", VulkanVersion_1_1},
+
+        {DeviceExt::ShaderFloat16Int8, "VK_KHR_shader_float16_int8", VulkanVersion_1_2},
+
+        {DeviceExt::ExternalMemoryFD, "VK_KHR_external_memory_fd", NeverPromoted},
+        {DeviceExt::ExternalMemoryDmaBuf, "VK_EXT_external_memory_dma_buf", NeverPromoted},
+        {DeviceExt::ExternalMemoryZirconHandle, "VK_FUCHSIA_external_memory", NeverPromoted},
+        {DeviceExt::ExternalSemaphoreFD, "VK_KHR_external_semaphore_fd", NeverPromoted},
+        {DeviceExt::ExternalSemaphoreZirconHandle, "VK_FUCHSIA_external_semaphore", NeverPromoted},
+
+        {DeviceExt::DebugMarker, "VK_EXT_debug_marker", NeverPromoted},
+        {DeviceExt::ImageDrmFormatModifier, "VK_EXT_image_drm_format_modifier", NeverPromoted},
+        {DeviceExt::Swapchain, "VK_KHR_swapchain", NeverPromoted},
+        //
+    }};
+
+    void DeviceExtSet::Set(DeviceExt extension, bool enabled) {
+        extensionBitSet.set(static_cast<uint32_t>(extension), enabled);
+    }
+
+    bool DeviceExtSet::Has(DeviceExt extension) const {
+        return extensionBitSet[static_cast<uint32_t>(extension)];
+    }
+
+    const DeviceExtInfo& GetDeviceExtInfo(DeviceExt ext) {
+        uint32_t index = static_cast<uint32_t>(ext);
+        ASSERT(index < sDeviceExtInfos.size());
+        ASSERT(sDeviceExtInfos[index].index == ext);
+        return sDeviceExtInfos[index];
+    }
+
+    std::unordered_map<std::string, DeviceExt> CreateDeviceExtNameMap() {
+        std::unordered_map<std::string, DeviceExt> result;
+        for (const DeviceExtInfo& info : sDeviceExtInfos) {
+            result[info.name] = info.index;
+        }
+        return result;
+    }
+
+    DeviceExtSet EnsureDependencies(const DeviceExtSet& advertisedExts,
+                                    const InstanceExtSet& instanceExts) {
+        // This is very similar to EnsureDependencies for instanceExtSet. See comment there for
+        // an explanation of what happens.
+        DeviceExtSet visitedSet;
+        DeviceExtSet trimmedSet;
+
+        auto HasDep = [&](DeviceExt ext) -> bool {
+            ASSERT(visitedSet.Has(ext));
+            return trimmedSet.Has(ext);
+        };
+
+        for (uint32_t i = 0; i < sDeviceExtInfos.size(); i++) {
+            DeviceExt ext = static_cast<DeviceExt>(i);
+
+            bool hasDependencies = false;
+            switch (ext) {
+                case DeviceExt::DebugMarker:
+                    // TODO(cwallez@chromium.org): VK_KHR_debug_report is deprecated, switch to
+                    // using VK_KHR_debug_utils instead.
+                    hasDependencies = instanceExts.Has(InstanceExt::DebugReport);
+                    break;
+
+                case DeviceExt::ImageDrmFormatModifier:
+                    // TODO(cwallez@chromium.org): ImageDrmFormatModifier actually requires:
+                    //  - VK_KHR_bind_memory2
+                    //  - VK_KHR_image_format_list
+                    //  - VK_KHR_sampler_ycbcr_conversion
+                    //
+                    // Also switch to using DeviceExt::GetPhysicalDeviceProperties2 when we decouple
+                    // Instance / Device physical device extensions.
+                    hasDependencies = instanceExts.Has(InstanceExt::GetPhysicalDeviceProperties2);
+                    break;
+
+                case DeviceExt::Swapchain:
+                    hasDependencies = instanceExts.Has(InstanceExt::Surface);
+                    break;
+
+                case DeviceExt::Maintenance1:
+                    hasDependencies = true;
+                    break;
+
+                case DeviceExt::ShaderFloat16Int8:
+                    // TODO(cwallez@chromium.org): switch to using
+                    // DeviceExt::GetPhysicalDeviceProperties2 when we decouple Instance / Device
+                    // physical device extensions.
+                    hasDependencies = instanceExts.Has(InstanceExt::GetPhysicalDeviceProperties2);
+                    break;
+
+                case DeviceExt::ExternalMemory:
+                    // TODO(cwallez@chromium.org): switch to using
+                    // DeviceExt::ExternalMemoryCapabilities when we decouple Instance / Device
+                    // physical device extensions.
+                    hasDependencies = instanceExts.Has(InstanceExt::ExternalMemoryCapabilities);
+                    break;
+
+                case DeviceExt::ExternalSemaphore:
+                    // TODO(cwallez@chromium.org): switch to using
+                    // DeviceExt::ExternalSemaphoreCapabilities when we decouple Instance / Device
+                    // physical device extensions.
+                    hasDependencies = instanceExts.Has(InstanceExt::ExternalSemaphoreCapabilities);
+                    break;
+
+                case DeviceExt::_16BitStorage:
+                    // TODO(cwallez@chromium.org): switch to using
+                    // DeviceExt::GetPhysicalDeviceProperties2 when we decouple Instance / Device
+                    // physical device extensions.
+                    // Also depends on VK_KHR_storage_buffer_storage_class
+                    hasDependencies = instanceExts.Has(InstanceExt::GetPhysicalDeviceProperties2);
+                    break;
+
+                case DeviceExt::ExternalMemoryFD:
+                case DeviceExt::ExternalMemoryZirconHandle:
+                    hasDependencies = HasDep(DeviceExt::ExternalMemory);
+                    break;
+
+                case DeviceExt::ExternalMemoryDmaBuf:
+                    hasDependencies = HasDep(DeviceExt::ExternalMemoryFD);
+                    break;
+
+                case DeviceExt::ExternalSemaphoreFD:
+                case DeviceExt::ExternalSemaphoreZirconHandle:
+                    hasDependencies = HasDep(DeviceExt::ExternalSemaphore);
+                    break;
+
+                default:
+                    UNREACHABLE();
+                    break;
+            }
+
+            trimmedSet.Set(ext, hasDependencies && advertisedExts.Has(ext));
+            visitedSet.Set(ext, true);
+        }
+
+        return trimmedSet;
+    }
+
+    void MarkPromotedExtensions(DeviceExtSet* extensions, uint32_t version) {
+        for (const DeviceExtInfo& info : sDeviceExtInfos) {
+            if (info.versionPromoted <= version) {
+                extensions->Set(info.index, true);
+            }
+        }
+    }
+
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/VulkanExtensions.h b/src/dawn_native/vulkan/VulkanExtensions.h
index a604faa..184df0b 100644
--- a/src/dawn_native/vulkan/VulkanExtensions.h
+++ b/src/dawn_native/vulkan/VulkanExtensions.h
@@ -70,6 +70,62 @@
     // extensions that don't have all their transitive dependencies in advertisedExts.
     InstanceExtSet EnsureDependencies(const InstanceExtSet& advertisedExts);
 
+    // The list of known device extensions. They must be in dependency order (this is checked
+    // inside EnsureDependencies)
+    enum class DeviceExt {
+        // Promoted to 1.1
+        Maintenance1,
+        ExternalMemory,
+        ExternalSemaphore,
+        _16BitStorage,
+
+        // Promoted to 1.2
+        ShaderFloat16Int8,
+
+        // External* extensions
+        ExternalMemoryFD,
+        ExternalMemoryDmaBuf,
+        ExternalMemoryZirconHandle,
+        ExternalSemaphoreFD,
+        ExternalSemaphoreZirconHandle,
+
+        // Others
+        DebugMarker,
+        ImageDrmFormatModifier,
+        Swapchain,
+
+        EnumCount,
+    };
+
+    // A bitset wrapper that is indexed with DeviceExt.
+    struct DeviceExtSet {
+        std::bitset<static_cast<size_t>(DeviceExt::EnumCount)> extensionBitSet;
+        void Set(DeviceExt extension, bool enabled);
+        bool Has(DeviceExt extension) const;
+    };
+
+    // A bitset wrapper that is indexed with DeviceExt.
+    struct DeviceExtInfo {
+        DeviceExt index;
+        const char* name;
+        // The version in which this extension was promoted as built with VK_MAKE_VERSION,
+        // or NeverPromoted if it was never promoted.
+        uint32_t versionPromoted;
+    };
+
+    // Returns the information about a known DeviceExt
+    const DeviceExtInfo& GetDeviceExtInfo(DeviceExt ext);
+    // Returns a map that maps a Vulkan extension name to its DeviceExt.
+    std::unordered_map<std::string, DeviceExt> CreateDeviceExtNameMap();
+
+    // Sets entries in `extensions` to true if that entry was promoted in Vulkan version `version`
+    void MarkPromotedExtensions(DeviceExtSet* extensions, uint32_t version);
+    // From a set of extensions advertised as supported by the device (or promoted), remove all
+    // extensions that don't have all their transitive dependencies in advertisedExts or in
+    // instanceExts.
+    DeviceExtSet EnsureDependencies(const DeviceExtSet& advertisedExts,
+                                    const InstanceExtSet& instanceExts);
+
 }}  // namespace dawn_native::vulkan
 
 #endif  // DAWNNATIVE_VULKAN_VULKANEXTENSIONS_H_
diff --git a/src/dawn_native/vulkan/VulkanFunctions.cpp b/src/dawn_native/vulkan/VulkanFunctions.cpp
index e31f464..cc070f2 100644
--- a/src/dawn_native/vulkan/VulkanFunctions.cpp
+++ b/src/dawn_native/vulkan/VulkanFunctions.cpp
@@ -278,35 +278,35 @@
         GET_DEVICE_PROC(UpdateDescriptorSets);
         GET_DEVICE_PROC(WaitForFences);
 
-        if (deviceInfo.debugMarker) {
+        if (deviceInfo.HasExt(DeviceExt::DebugMarker)) {
             GET_DEVICE_PROC(CmdDebugMarkerBeginEXT);
             GET_DEVICE_PROC(CmdDebugMarkerEndEXT);
             GET_DEVICE_PROC(CmdDebugMarkerInsertEXT);
         }
 
-        if (deviceInfo.externalMemoryFD) {
+        if (deviceInfo.HasExt(DeviceExt::ExternalMemoryFD)) {
             GET_DEVICE_PROC(GetMemoryFdKHR);
             GET_DEVICE_PROC(GetMemoryFdPropertiesKHR);
         }
 
-        if (deviceInfo.externalSemaphoreFD) {
+        if (deviceInfo.HasExt(DeviceExt::ExternalSemaphoreFD)) {
             GET_DEVICE_PROC(ImportSemaphoreFdKHR);
             GET_DEVICE_PROC(GetSemaphoreFdKHR);
         }
 
 #if VK_USE_PLATFORM_FUCHSIA
-        if (deviceInfo.externalMemoryZirconHandle) {
+        if (deviceInfo.HasExt(DeviceExt::ExternalMemoryZirconHandle)) {
             GET_DEVICE_PROC(GetMemoryZirconHandleFUCHSIA);
             GET_DEVICE_PROC(GetMemoryZirconHandlePropertiesFUCHSIA);
         }
 
-        if (deviceInfo.externalSemaphoreZirconHandle) {
+        if (deviceInfo.HasExt(DeviceExt::ExternalSemaphoreZirconHandle)) {
             GET_DEVICE_PROC(ImportSemaphoreZirconHandleFUCHSIA);
             GET_DEVICE_PROC(GetSemaphoreZirconHandleFUCHSIA);
         }
 #endif
 
-        if (deviceInfo.swapchain) {
+        if (deviceInfo.HasExt(DeviceExt::Swapchain)) {
             GET_DEVICE_PROC(CreateSwapchainKHR);
             GET_DEVICE_PROC(DestroySwapchainKHR);
             GET_DEVICE_PROC(GetSwapchainImagesKHR);
diff --git a/src/dawn_native/vulkan/VulkanInfo.cpp b/src/dawn_native/vulkan/VulkanInfo.cpp
index e713ba6..6b2346e 100644
--- a/src/dawn_native/vulkan/VulkanInfo.cpp
+++ b/src/dawn_native/vulkan/VulkanInfo.cpp
@@ -28,10 +28,6 @@
             return strncmp(layer.layerName, name, VK_MAX_EXTENSION_NAME_SIZE) == 0;
         }
 
-        bool IsExtensionName(const VkExtensionProperties& extension, const char* name) {
-            return strncmp(extension.extensionName, name, VK_MAX_EXTENSION_NAME_SIZE) == 0;
-        }
-
         bool EnumerateInstanceExtensions(const char* layerName,
                                          const dawn_native::vulkan::VulkanFunctions& vkFunctions,
                                          std::vector<VkExtensionProperties>* extensions) {
@@ -54,30 +50,14 @@
     const char kLayerNameRenderDocCapture[] = "VK_LAYER_RENDERDOC_Capture";
     const char kLayerNameFuchsiaImagePipeSwapchain[] = "VK_LAYER_FUCHSIA_imagepipe_swapchain";
 
-    const char kExtensionNameExtDebugMarker[] = "VK_EXT_debug_marker";
-    const char kExtensionNameKhrExternalMemory[] = "VK_KHR_external_memory";
-    const char kExtensionNameKhrExternalMemoryCapabilities[] =
-        "VK_KHR_external_memory_capabilities";
-    const char kExtensionNameKhrExternalMemoryFD[] = "VK_KHR_external_memory_fd";
-    const char kExtensionNameExtExternalMemoryDmaBuf[] = "VK_EXT_external_memory_dma_buf";
-    const char kExtensionNameExtImageDrmFormatModifier[] = "VK_EXT_image_drm_format_modifier";
-    const char kExtensionNameFuchsiaExternalMemory[] = "VK_FUCHSIA_external_memory";
-    const char kExtensionNameKhrExternalSemaphore[] = "VK_KHR_external_semaphore";
-    const char kExtensionNameKhrExternalSemaphoreCapabilities[] =
-        "VK_KHR_external_semaphore_capabilities";
-    const char kExtensionNameKhrExternalSemaphoreFD[] = "VK_KHR_external_semaphore_fd";
-    const char kExtensionNameFuchsiaExternalSemaphore[] = "VK_FUCHSIA_external_semaphore";
-    const char kExtensionNameKhrGetPhysicalDeviceProperties2[] =
-        "VK_KHR_get_physical_device_properties2";
-    const char kExtensionNameKhrSwapchain[] = "VK_KHR_swapchain";
-    const char kExtensionNameKhrMaintenance1[] = "VK_KHR_maintenance1";
-    const char kExtensionNameKhrShaderFloat16Int8[] = "VK_KHR_shader_float16_int8";
-    const char kExtensionNameKhr16BitStorage[] = "VK_KHR_16bit_storage";
-
     bool VulkanGlobalKnobs::HasExt(InstanceExt ext) const {
         return extensions.Has(ext);
     }
 
+    bool VulkanDeviceKnobs::HasExt(DeviceExt ext) const {
+        return extensions.Has(ext);
+    }
+
     ResultOrError<VulkanGlobalInfo> GatherGlobalInfo(const Backend& backend) {
         VulkanGlobalInfo info = {};
         const VulkanFunctions& vkFunctions = backend.GetFunctions();
@@ -248,82 +228,47 @@
                 return DAWN_INTERNAL_ERROR("vkEnumerateDeviceExtensionProperties");
             }
 
-            info.extensions.resize(count);
-            DAWN_TRY(CheckVkSuccess(vkFunctions.EnumerateDeviceExtensionProperties(
-                                        physicalDevice, nullptr, &count, info.extensions.data()),
-                                    "vkEnumerateDeviceExtensionProperties"));
+            std::vector<VkExtensionProperties> extensionsProperties;
+            extensionsProperties.resize(count);
+            DAWN_TRY(
+                CheckVkSuccess(vkFunctions.EnumerateDeviceExtensionProperties(
+                                   physicalDevice, nullptr, &count, extensionsProperties.data()),
+                               "vkEnumerateDeviceExtensionProperties"));
 
-            for (const auto& extension : info.extensions) {
-                if (IsExtensionName(extension, kExtensionNameExtDebugMarker)) {
-                    info.debugMarker = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalMemory)) {
-                    info.externalMemory = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalMemoryFD)) {
-                    info.externalMemoryFD = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameExtExternalMemoryDmaBuf)) {
-                    info.externalMemoryDmaBuf = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameExtImageDrmFormatModifier)) {
-                    info.imageDrmFormatModifier = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameFuchsiaExternalMemory)) {
-                    info.externalMemoryZirconHandle = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalSemaphore)) {
-                    info.externalSemaphore = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalSemaphoreFD)) {
-                    info.externalSemaphoreFD = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameFuchsiaExternalSemaphore)) {
-                    info.externalSemaphoreZirconHandle = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrSwapchain)) {
-                    info.swapchain = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrMaintenance1)) {
-                    info.maintenance1 = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrShaderFloat16Int8) &&
-                    globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2)) {
-                    info.shaderFloat16Int8 = true;
-                    info.shaderFloat16Int8Features.sType =
-                        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR;
+            std::unordered_map<std::string, DeviceExt> knownExts = CreateDeviceExtNameMap();
 
-                    VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures2 = {};
-                    physicalDeviceFeatures2.sType =
-                        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
-                    physicalDeviceFeatures2.pNext = &info.shaderFloat16Int8Features;
-                    vkFunctions.GetPhysicalDeviceFeatures2(physicalDevice,
-                                                           &physicalDeviceFeatures2);
-                }
-                if (IsExtensionName(extension, kExtensionNameKhr16BitStorage) &&
-                    globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2)) {
-                    info._16BitStorage = true;
+            for (const VkExtensionProperties& extension : extensionsProperties) {
+                auto it = knownExts.find(extension.extensionName);
+                if (it != knownExts.end()) {
+                    info.extensions.Set(it->second, true);
                 }
             }
+
+            MarkPromotedExtensions(&info.extensions, info.properties.apiVersion);
+            info.extensions = EnsureDependencies(info.extensions, globalInfo.extensions);
         }
 
-        // Mark the extensions promoted to Vulkan 1.1 as available.
-        if (info.properties.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
-            info.maintenance1 = true;
-        }
+        // Gather additional information for some of the extensions
+        {
+            if (info.extensions.Has(DeviceExt::ShaderFloat16Int8)) {
+                info.shaderFloat16Int8Features.sType =
+                    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR;
 
-        // VK_KHR_16bit_storage is promoted to Vulkan 1.1, so gather information if either is
-        // present, and mark the extension as available.
-        if (info._16BitStorage || info.properties.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
-            ASSERT(globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2));
-            info._16BitStorage = true;
-            info._16BitStorageFeatures.sType =
-                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
+                VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures2 = {};
+                physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
+                physicalDeviceFeatures2.pNext = &info.shaderFloat16Int8Features;
+                vkFunctions.GetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures2);
+            }
 
-            VkPhysicalDeviceFeatures2 physicalDeviceFeatures2 = {};
-            physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-            physicalDeviceFeatures2.pNext = &info._16BitStorageFeatures;
-            vkFunctions.GetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures2);
+            if (info.extensions.Has(DeviceExt::_16BitStorage)) {
+                info._16BitStorageFeatures.sType =
+                    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
+
+                VkPhysicalDeviceFeatures2 physicalDeviceFeatures2 = {};
+                physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+                physicalDeviceFeatures2.pNext = &info._16BitStorageFeatures;
+                vkFunctions.GetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures2);
+            }
         }
 
         // TODO(cwallez@chromium.org): gather info about formats
diff --git a/src/dawn_native/vulkan/VulkanInfo.h b/src/dawn_native/vulkan/VulkanInfo.h
index a9d0818..6d0a756f 100644
--- a/src/dawn_native/vulkan/VulkanInfo.h
+++ b/src/dawn_native/vulkan/VulkanInfo.h
@@ -31,23 +31,6 @@
     extern const char kLayerNameRenderDocCapture[];
     extern const char kLayerNameFuchsiaImagePipeSwapchain[];
 
-    extern const char kExtensionNameExtDebugMarker[];
-    extern const char kExtensionNameKhrExternalMemory[];
-    extern const char kExtensionNameKhrExternalMemoryCapabilities[];
-    extern const char kExtensionNameKhrExternalMemoryFD[];
-    extern const char kExtensionNameExtExternalMemoryDmaBuf[];
-    extern const char kExtensionNameExtImageDrmFormatModifier[];
-    extern const char kExtensionNameFuchsiaExternalMemory[];
-    extern const char kExtensionNameKhrExternalSemaphore[];
-    extern const char kExtensionNameKhrExternalSemaphoreCapabilities[];
-    extern const char kExtensionNameKhrExternalSemaphoreFD[];
-    extern const char kExtensionNameFuchsiaExternalSemaphore[];
-    extern const char kExtensionNameKhrGetPhysicalDeviceProperties2[];
-    extern const char kExtensionNameKhrSwapchain[];
-    extern const char kExtensionNameKhrMaintenance1[];
-    extern const char kExtensionNameKhrShaderFloat16Int8[];
-    extern const char kExtensionNameKhr16BitStorage[];
-
     // Global information - gathered before the instance is created
     struct VulkanGlobalKnobs {
         // Layers
@@ -72,20 +55,8 @@
         VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shaderFloat16Int8Features;
         VkPhysicalDevice16BitStorageFeaturesKHR _16BitStorageFeatures;
 
-        // Extensions, promoted extensions are set to true if their core version is supported.
-        bool debugMarker = false;
-        bool externalMemory = false;
-        bool externalMemoryFD = false;
-        bool externalMemoryDmaBuf = false;
-        bool imageDrmFormatModifier = false;
-        bool externalMemoryZirconHandle = false;
-        bool externalSemaphore = false;
-        bool externalSemaphoreFD = false;
-        bool externalSemaphoreZirconHandle = false;
-        bool swapchain = false;
-        bool maintenance1 = false;
-        bool shaderFloat16Int8 = false;
-        bool _16BitStorage = false;
+        bool HasExt(DeviceExt ext) const;
+        DeviceExtSet extensions;
     };
 
     struct VulkanDeviceInfo : VulkanDeviceKnobs {
@@ -96,7 +67,6 @@
         std::vector<VkMemoryHeap> memoryHeaps;
 
         std::vector<VkLayerProperties> layers;
-        std::vector<VkExtensionProperties> extensions;
         // TODO(cwallez@chromium.org): layer instance extensions
     };
 
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
index 240c6fe..4129745 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -61,13 +61,9 @@
 
     Service::Service(Device* device) : mDevice(device) {
         const VulkanDeviceInfo& deviceInfo = mDevice->GetDeviceInfo();
-        const VulkanGlobalInfo& globalInfo =
-            ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
-                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) &&
-                     deviceInfo.externalMemory && deviceInfo.externalMemoryFD &&
-                     deviceInfo.externalMemoryDmaBuf && deviceInfo.imageDrmFormatModifier;
+        mSupported = deviceInfo.HasExt(DeviceExt::ExternalMemoryFD) &&
+                     deviceInfo.HasExt(DeviceExt::ImageDrmFormatModifier);
     }
 
     Service::~Service() = default;
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index 57b74af..9905777 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -23,13 +23,7 @@
 namespace dawn_native { namespace vulkan { namespace external_memory {
 
     Service::Service(Device* device) : mDevice(device) {
-        const VulkanDeviceInfo& deviceInfo = mDevice->GetDeviceInfo();
-        const VulkanGlobalInfo& globalInfo =
-            ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
-
-        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
-                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) &&
-                     deviceInfo.externalMemory && deviceInfo.externalMemoryFD;
+        mSupported = device->GetDeviceInfo().HasExt(DeviceExt::ExternalMemoryFD);
     }
 
     Service::~Service() = default;
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
index ab87a7f..08d8d63 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -23,13 +23,7 @@
 namespace dawn_native { namespace vulkan { namespace external_memory {
 
     Service::Service(Device* device) : mDevice(device) {
-        const VulkanDeviceInfo& deviceInfo = mDevice->GetDeviceInfo();
-        const VulkanGlobalInfo& globalInfo =
-            ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
-
-        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
-                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) &&
-                     deviceInfo.externalMemory && deviceInfo.externalMemoryFD;
+        mSupported = device->GetDeviceInfo().HasExt(DeviceExt::ExternalMemoryZirconHandle);
     }
 
     Service::~Service() = default;
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
index 222fcbb..aecc893 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
@@ -21,13 +21,7 @@
 namespace dawn_native { namespace vulkan { namespace external_semaphore {
 
     Service::Service(Device* device) : mDevice(device) {
-        const VulkanDeviceInfo& deviceInfo = mDevice->GetDeviceInfo();
-        const VulkanGlobalInfo& globalInfo =
-            ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
-
-        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
-                     globalInfo.HasExt(InstanceExt::ExternalSemaphoreCapabilities) &&
-                     deviceInfo.externalSemaphore && deviceInfo.externalSemaphoreFD;
+        mSupported = device->GetDeviceInfo().HasExt(DeviceExt::ExternalSemaphoreFD);
 
         // Early out before we try using extension functions
         if (!mSupported) {
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
index 985cf4f..b4e3a62 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
@@ -21,13 +21,7 @@
 namespace dawn_native { namespace vulkan { namespace external_semaphore {
 
     Service::Service(Device* device) : mDevice(device) {
-        const VulkanDeviceInfo& deviceInfo = mDevice->GetDeviceInfo();
-        const VulkanGlobalInfo& globalInfo =
-            ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
-
-        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
-                     globalInfo.HasExt(InstanceExt::ExternalSemaphoreCapabilities) &&
-                     deviceInfo.externalSemaphore && deviceInfo.externalSemaphoreFD;
+        mSupported = device->GetDeviceInfo().hasExt(DeviceExt::ExternalSemaphoreZirconHandle);
 
         // Early out before we try using extension functions
         if (!mSupported) {