Vulkan: handle Instance extensions more programmatically

At the moment each instance extension needs special handling
in 6+ places when added to the Vulkan backend. This is very
error-prone and makes it difficult to do changes in how they
are extensions are handled.

This CL makes instance extensions linked with an enum class
and a bitset to know which are available (instead of individual
booleans). A table of known extensions with more information like
`versionPromoted` so that they can be handled programmatically.

Bug: dawn:457

Change-Id: I266deb730eb2b7f3ab0ee7ada1b06ff9748a60e4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22940
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index 3f24ba3..f6974c8 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -519,6 +519,8 @@
       "vulkan/UtilsVulkan.h",
       "vulkan/VulkanError.cpp",
       "vulkan/VulkanError.h",
+      "vulkan/VulkanExtensions.cpp",
+      "vulkan/VulkanExtensions.h",
       "vulkan/VulkanFunctions.cpp",
       "vulkan/VulkanFunctions.h",
       "vulkan/VulkanInfo.cpp",
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index 7cee0d0..cabbc1a 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -412,6 +412,8 @@
         "vulkan/UtilsVulkan.h"
         "vulkan/VulkanError.cpp"
         "vulkan/VulkanError.h"
+        "vulkan/VulkanExtensions.cpp"
+        "vulkan/VulkanExtensions.h"
         "vulkan/VulkanFunctions.cpp"
         "vulkan/VulkanFunctions.h"
         "vulkan/VulkanInfo.cpp"
diff --git a/src/dawn_native/vulkan/BackendVk.cpp b/src/dawn_native/vulkan/BackendVk.cpp
index f664cd6..c682b72 100644
--- a/src/dawn_native/vulkan/BackendVk.cpp
+++ b/src/dawn_native/vulkan/BackendVk.cpp
@@ -14,6 +14,7 @@
 
 #include "dawn_native/vulkan/BackendVk.h"
 
+#include "common/BitSetIterator.h"
 #include "common/Log.h"
 #include "common/SystemUtils.h"
 #include "dawn_native/Instance.h"
@@ -149,7 +150,7 @@
 
         DAWN_TRY(mFunctions.LoadInstanceProcs(mInstance, mGlobalInfo));
 
-        if (usedGlobalKnobs.debugReport) {
+        if (usedGlobalKnobs.HasExt(InstanceExt::DebugReport)) {
             DAWN_TRY(RegisterDebugReport());
         }
 
@@ -176,9 +177,7 @@
 
     ResultOrError<VulkanGlobalKnobs> Backend::CreateInstance() {
         VulkanGlobalKnobs usedKnobs = {};
-
-        std::vector<const char*> layersToRequest;
-        std::vector<const char*> extensionsToRequest;
+        std::vector<const char*> layerNames;
 
         // vktrace works by instering a layer, but we hide it behind a macro due to the vktrace
         // layer crashes when used without vktrace server started. See this vktrace issue:
@@ -187,7 +186,7 @@
         // by other layers.
 #if defined(DAWN_USE_VKTRACE)
         if (mGlobalInfo.vktrace) {
-            layersToRequest.push_back(kLayerNameLunargVKTrace);
+            layerNames.push_back(kLayerNameLunargVKTrace);
             usedKnobs.vktrace = true;
         }
 #endif
@@ -195,86 +194,38 @@
         // it unless we are debugging in RenderDoc so we hide it behind a macro.
 #if defined(DAWN_USE_RENDERDOC)
         if (mGlobalInfo.renderDocCapture) {
-            layersToRequest.push_back(kLayerNameRenderDocCapture);
+            layerNames.push_back(kLayerNameRenderDocCapture);
             usedKnobs.renderDocCapture = true;
         }
 #endif
 
         if (GetInstance()->IsBackendValidationEnabled()) {
             if (mGlobalInfo.validation) {
-                layersToRequest.push_back(kLayerNameKhronosValidation);
+                layerNames.push_back(kLayerNameKhronosValidation);
                 usedKnobs.validation = true;
             }
-            if (mGlobalInfo.debugReport) {
-                extensionsToRequest.push_back(kExtensionNameExtDebugReport);
-                usedKnobs.debugReport = true;
-            }
         }
 
-        // Always request all extensions used to create VkSurfaceKHR objects so that they are
-        // always available for embedders looking to create VkSurfaceKHR on our VkInstance.
-        if (mGlobalInfo.fuchsiaImagePipeSwapchain) {
-            layersToRequest.push_back(kLayerNameFuchsiaImagePipeSwapchain);
-            usedKnobs.fuchsiaImagePipeSwapchain = true;
-        }
-        if (mGlobalInfo.metalSurface) {
-            extensionsToRequest.push_back(kExtensionNameExtMetalSurface);
-            usedKnobs.metalSurface = true;
-        }
-        if (mGlobalInfo.surface) {
-            extensionsToRequest.push_back(kExtensionNameKhrSurface);
-            usedKnobs.surface = true;
-        }
-        if (mGlobalInfo.waylandSurface) {
-            extensionsToRequest.push_back(kExtensionNameKhrWaylandSurface);
-            usedKnobs.waylandSurface = true;
-        }
-        if (mGlobalInfo.win32Surface) {
-            extensionsToRequest.push_back(kExtensionNameKhrWin32Surface);
-            usedKnobs.win32Surface = true;
-        }
-        if (mGlobalInfo.xcbSurface) {
-            extensionsToRequest.push_back(kExtensionNameKhrXcbSurface);
-            usedKnobs.xcbSurface = true;
-        }
-        if (mGlobalInfo.xlibSurface) {
-            extensionsToRequest.push_back(kExtensionNameKhrXlibSurface);
-            usedKnobs.xlibSurface = true;
-        }
-        if (mGlobalInfo.fuchsiaImagePipeSurface) {
-            extensionsToRequest.push_back(kExtensionNameFuchsiaImagePipeSurface);
-            usedKnobs.fuchsiaImagePipeSurface = true;
+        // Available and known instance extensions default to being requested, but some special
+        // cases are removed.
+        InstanceExtSet extensionsToRequest = mGlobalInfo.extensions;
+
+        // TODO(cwallez@chromium.org): don't request extensions that have been promoted to Vulkan
+        // 1.1. This can only happen when we correctly detect and handle VkPhysicalDevice instance
+        // extensions that are promoted to be "device" extensions in the core Vulkan. If we don't
+        // do this there is a crash because a Vulkan 1.1 loader instance will not emulate the call
+        // on a Vulkan 1.0 ICD (and call nullptr).
+        // See https://github.com/KhronosGroup/Vulkan-Loader/issues/412.
+
+        if (!GetInstance()->IsBackendValidationEnabled()) {
+            extensionsToRequest.Set(InstanceExt::DebugReport, false);
         }
 
-        // Mark the promoted extensions as present if the core version in which they were promoted
-        // is used. This allows having a single boolean that checks if the functionality from that
-        // extension is available (instead of checking extension || coreVersion).
-        if (mGlobalInfo.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
-            usedKnobs.getPhysicalDeviceProperties2 = true;
-            usedKnobs.externalMemoryCapabilities = true;
-            usedKnobs.externalSemaphoreCapabilities = true;
-        }
+        usedKnobs.extensions = extensionsToRequest;
 
-        // The Vulkan-Loader has emulation of VkPhysicalDevices functions such as
-        // vkGetPhysicalDeviceProperties2 when the ICD doesn't support the extension. However the
-        // loader has a bug where if the instance is created with Vulkan 1.1 and not the promoted
-        // extensions, it will skip emulation and if the ICD doesn't support Vulkan 1.1 nor the
-        // extensions, we will crash on nullptr function pointer when the loader tries to call the
-        // ICD's vkGetPhysicalDeviceProperties2. See
-        // https://github.com/KhronosGroup/Vulkan-Loader/issues/412. We work around this by
-        // specifying we want to enable the promoted extensions, even when we create a Vulkan 1.1
-        // instance.
-        if (mGlobalInfo.externalMemoryCapabilities) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalMemoryCapabilities);
-            usedKnobs.externalMemoryCapabilities = true;
-        }
-        if (mGlobalInfo.externalSemaphoreCapabilities) {
-            extensionsToRequest.push_back(kExtensionNameKhrExternalSemaphoreCapabilities);
-            usedKnobs.externalSemaphoreCapabilities = true;
-        }
-        if (mGlobalInfo.getPhysicalDeviceProperties2) {
-            extensionsToRequest.push_back(kExtensionNameKhrGetPhysicalDeviceProperties2);
-            usedKnobs.getPhysicalDeviceProperties2 = true;
+        std::vector<const char*> extensionNames;
+        for (uint32_t ext : IterateBitSet(extensionsToRequest.extensionBitSet)) {
+            extensionNames.push_back(GetInstanceExtInfo(static_cast<InstanceExt>(ext)).name);
         }
 
         VkApplicationInfo appInfo;
@@ -291,10 +242,10 @@
         createInfo.pNext = nullptr;
         createInfo.flags = 0;
         createInfo.pApplicationInfo = &appInfo;
-        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 = static_cast<uint32_t>(layerNames.size());
+        createInfo.ppEnabledLayerNames = layerNames.data();
+        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensionNames.size());
+        createInfo.ppEnabledExtensionNames = extensionNames.data();
 
         DAWN_TRY(CheckVkSuccess(mFunctions.CreateInstance(&createInfo, nullptr, &mInstance),
                                 "vkCreateInstance"));
diff --git a/src/dawn_native/vulkan/VulkanExtensions.cpp b/src/dawn_native/vulkan/VulkanExtensions.cpp
new file mode 100644
index 0000000..ea0ccab
--- /dev/null
+++ b/src/dawn_native/vulkan/VulkanExtensions.cpp
@@ -0,0 +1,135 @@
+// Copyright 2020 The Dawn Authors
+//
+// 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 "dawn_native/vulkan/VulkanExtensions.h"
+
+#include "common/Assert.h"
+#include "common/vulkan_platform.h"
+
+#include <array>
+#include <limits>
+
+namespace dawn_native { namespace vulkan {
+
+    static constexpr uint32_t VulkanVersion_1_1 = VK_MAKE_VERSION(1, 1, 0);
+    static constexpr uint32_t NeverPromoted = std::numeric_limits<uint32_t>::max();
+
+    // A static array for InstanceExtInfo that can be indexed with InstanceExts.
+    // GetInstanceExtInfo checks that "index" matches the index used to access this array so an
+    // assert will fire if it isn't in the correct order.
+    static constexpr size_t kInstanceExtCount = static_cast<size_t>(InstanceExt::EnumCount);
+    static constexpr std::array<InstanceExtInfo, kInstanceExtCount> sInstanceExtInfos{{
+        //
+        {InstanceExt::GetPhysicalDeviceProperties2, "VK_KHR_get_physical_device_properties2",
+         VulkanVersion_1_1},
+        {InstanceExt::ExternalMemoryCapabilities, "VK_KHR_external_memory_capabilities",
+         VulkanVersion_1_1},
+        {InstanceExt::ExternalSemaphoreCapabilities, "VK_KHR_external_semaphore_capabilities",
+         VulkanVersion_1_1},
+
+        {InstanceExt::Surface, "VK_KHR_surface", NeverPromoted},
+        {InstanceExt::FuchsiaImagePipeSurface, "VK_FUCHSIA_imagepipe_surface", NeverPromoted},
+        {InstanceExt::MetalSurface, "VK_EXT_metal_surface", NeverPromoted},
+        {InstanceExt::WaylandSurface, "VK_KHR_wayland_surface", NeverPromoted},
+        {InstanceExt::Win32Surface, "VK_KHR_win32_surface", NeverPromoted},
+        {InstanceExt::XcbSurface, "VK_KHR_xcb_surface", NeverPromoted},
+        {InstanceExt::XlibSurface, "VK_KHR_xlib_surface", NeverPromoted},
+
+        {InstanceExt::DebugReport, "VK_EXT_debug_report", NeverPromoted}
+        //
+    }};
+
+    void InstanceExtSet::Set(InstanceExt extension, bool enabled) {
+        extensionBitSet.set(static_cast<uint32_t>(extension), enabled);
+    }
+
+    bool InstanceExtSet::Has(InstanceExt extension) const {
+        return extensionBitSet[static_cast<uint32_t>(extension)];
+    }
+
+    const InstanceExtInfo& GetInstanceExtInfo(InstanceExt ext) {
+        uint32_t index = static_cast<uint32_t>(ext);
+        ASSERT(index < sInstanceExtInfos.size());
+        ASSERT(sInstanceExtInfos[index].index == ext);
+        return sInstanceExtInfos[index];
+    }
+
+    std::unordered_map<std::string, InstanceExt> CreateInstanceExtNameMap() {
+        std::unordered_map<std::string, InstanceExt> result;
+        for (const InstanceExtInfo& info : sInstanceExtInfos) {
+            result[info.name] = info.index;
+        }
+        return result;
+    }
+
+    InstanceExtSet EnsureDependencies(const InstanceExtSet& advertisedExts) {
+        // We need to check that all transitive dependencies of extensions are advertised.
+        // To do that in a single pass and no data structures, the extensions are topologically
+        // sorted in the definition of InstanceExt.
+        // To ensure the order is correct, we mark visited extensions in `visitedSet` and each
+        // dependency check will first assert all its dependents have been visited.
+        InstanceExtSet visitedSet;
+        InstanceExtSet trimmedSet;
+
+        auto HasDep = [&](InstanceExt ext) -> bool {
+            ASSERT(visitedSet.Has(ext));
+            return trimmedSet.Has(ext);
+        };
+
+        for (uint32_t i = 0; i < sInstanceExtInfos.size(); i++) {
+            InstanceExt ext = static_cast<InstanceExt>(i);
+
+            bool hasDependencies = false;
+            switch (ext) {
+                case InstanceExt::GetPhysicalDeviceProperties2:
+                case InstanceExt::Surface:
+                case InstanceExt::DebugReport:
+                    hasDependencies = true;
+                    break;
+
+                case InstanceExt::ExternalMemoryCapabilities:
+                case InstanceExt::ExternalSemaphoreCapabilities:
+                    hasDependencies = HasDep(InstanceExt::GetPhysicalDeviceProperties2);
+                    break;
+
+                case InstanceExt::FuchsiaImagePipeSurface:
+                case InstanceExt::MetalSurface:
+                case InstanceExt::WaylandSurface:
+                case InstanceExt::Win32Surface:
+                case InstanceExt::XcbSurface:
+                case InstanceExt::XlibSurface:
+                    hasDependencies = HasDep(InstanceExt::Surface);
+                    break;
+
+                default:
+                    UNREACHABLE();
+                    break;
+            }
+
+            trimmedSet.Set(ext, hasDependencies && advertisedExts.Has(ext));
+            visitedSet.Set(ext, true);
+        }
+
+        return trimmedSet;
+    }
+
+    void MarkPromotedExtensions(InstanceExtSet* extensions, uint32_t version) {
+        for (const InstanceExtInfo& info : sInstanceExtInfos) {
+            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
new file mode 100644
index 0000000..a604faa
--- /dev/null
+++ b/src/dawn_native/vulkan/VulkanExtensions.h
@@ -0,0 +1,75 @@
+// Copyright 2020 The Dawn Authors
+//
+// 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 DAWNNATIVE_VULKAN_VULKANEXTENSIONS_H_
+#define DAWNNATIVE_VULKAN_VULKANEXTENSIONS_H_
+
+#include <bitset>
+#include <unordered_map>
+
+namespace dawn_native { namespace vulkan {
+
+    // The list of known instance extensions. They must be in dependency order (this is checked
+    // inside EnsureDependencies)
+    enum class InstanceExt {
+        // Promoted to 1.1
+        GetPhysicalDeviceProperties2,
+        ExternalMemoryCapabilities,
+        ExternalSemaphoreCapabilities,
+
+        // Surface extensions
+        Surface,
+        FuchsiaImagePipeSurface,
+        MetalSurface,
+        WaylandSurface,
+        Win32Surface,
+        XcbSurface,
+        XlibSurface,
+
+        // Others
+        DebugReport,
+
+        EnumCount,
+    };
+
+    // A bitset wrapper that is indexed with InstanceExt.
+    struct InstanceExtSet {
+        std::bitset<static_cast<size_t>(InstanceExt::EnumCount)> extensionBitSet;
+        void Set(InstanceExt extension, bool enabled);
+        bool Has(InstanceExt extension) const;
+    };
+
+    // Information about a known instance extension.
+    struct InstanceExtInfo {
+        InstanceExt 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 InstanceExt
+    const InstanceExtInfo& GetInstanceExtInfo(InstanceExt ext);
+    // Returns a map that maps a Vulkan extension name to its InstanceExt.
+    std::unordered_map<std::string, InstanceExt> CreateInstanceExtNameMap();
+
+    // Sets entries in `extensions` to true if that entry was promoted in Vulkan version `version`
+    void MarkPromotedExtensions(InstanceExtSet* extensions, uint32_t version);
+    // From a set of extensions advertised as supported by the instance (or promoted), remove all
+    // extensions that don't have all their transitive dependencies in advertisedExts.
+    InstanceExtSet EnsureDependencies(const InstanceExtSet& advertisedExts);
+
+}}  // 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 6099c0a..e31f464 100644
--- a/src/dawn_native/vulkan/VulkanFunctions.cpp
+++ b/src/dawn_native/vulkan/VulkanFunctions.cpp
@@ -74,7 +74,7 @@
         GET_INSTANCE_PROC(GetPhysicalDeviceQueueFamilyProperties);
         GET_INSTANCE_PROC(GetPhysicalDeviceSparseImageFormatProperties);
 
-        if (globalInfo.debugReport) {
+        if (globalInfo.HasExt(InstanceExt::DebugReport)) {
             GET_INSTANCE_PROC(CreateDebugReportCallbackEXT);
             GET_INSTANCE_PROC(DebugReportMessageEXT);
             GET_INSTANCE_PROC(DestroyDebugReportCallbackEXT);
@@ -84,13 +84,13 @@
         // support the vendor entrypoint in GetProcAddress.
         if (globalInfo.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
             GET_INSTANCE_PROC(GetPhysicalDeviceExternalBufferProperties);
-        } else if (globalInfo.externalMemoryCapabilities) {
+        } else if (globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities)) {
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceExternalBufferProperties, KHR);
         }
 
         if (globalInfo.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) {
             GET_INSTANCE_PROC(GetPhysicalDeviceExternalSemaphoreProperties);
-        } else if (globalInfo.externalSemaphoreCapabilities) {
+        } else if (globalInfo.HasExt(InstanceExt::ExternalSemaphoreCapabilities)) {
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceExternalSemaphoreProperties, KHR);
         }
 
@@ -102,7 +102,7 @@
             GET_INSTANCE_PROC(GetPhysicalDeviceQueueFamilyProperties2);
             GET_INSTANCE_PROC(GetPhysicalDeviceMemoryProperties2);
             GET_INSTANCE_PROC(GetPhysicalDeviceSparseImageFormatProperties2);
-        } else if (globalInfo.getPhysicalDeviceProperties2) {
+        } else if (globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2)) {
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceFeatures2, KHR);
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceProperties2, KHR);
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceFormatProperties2, KHR);
@@ -112,7 +112,7 @@
             GET_INSTANCE_PROC_VENDOR(GetPhysicalDeviceSparseImageFormatProperties2, KHR);
         }
 
-        if (globalInfo.surface) {
+        if (globalInfo.HasExt(InstanceExt::Surface)) {
             GET_INSTANCE_PROC(DestroySurfaceKHR);
             GET_INSTANCE_PROC(GetPhysicalDeviceSurfaceSupportKHR);
             GET_INSTANCE_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR);
@@ -121,26 +121,26 @@
         }
 
 #if defined(VK_USE_PLATFORM_FUCHSIA)
-        if (globalInfo.fuchsiaImagePipeSurface) {
+        if (globalInfo.HasExt(InstanceExt::FuchsiaImagePipeSurface)) {
             GET_INSTANCE_PROC(CreateImagePipeSurfaceFUCHSIA);
         }
 #endif  // defined(VK_USE_PLATFORM_FUCHSIA)
 
 #if defined(DAWN_ENABLE_BACKEND_METAL)
-        if (globalInfo.metalSurface) {
+        if (globalInfo.HasExt(InstanceExt::MetalSurface)) {
             GET_INSTANCE_PROC(CreateMetalSurfaceEXT);
         }
 #endif  // defined(DAWN_ENABLE_BACKEND_METAL)
 
 #if defined(DAWN_PLATFORM_WINDOWS)
-        if (globalInfo.win32Surface) {
+        if (globalInfo.HasExt(InstanceExt::Win32Surface)) {
             GET_INSTANCE_PROC(CreateWin32SurfaceKHR);
             GET_INSTANCE_PROC(GetPhysicalDeviceWin32PresentationSupportKHR);
         }
 #endif  // defined(DAWN_PLATFORM_WINDOWS)
 
 #if defined(DAWN_USE_X11)
-        if (globalInfo.xlibSurface) {
+        if (globalInfo.HasExt(InstanceExt::XlibSurface)) {
             GET_INSTANCE_PROC(CreateXlibSurfaceKHR);
             GET_INSTANCE_PROC(GetPhysicalDeviceXlibPresentationSupportKHR);
         }
diff --git a/src/dawn_native/vulkan/VulkanInfo.cpp b/src/dawn_native/vulkan/VulkanInfo.cpp
index b2d9928..e713ba6 100644
--- a/src/dawn_native/vulkan/VulkanInfo.cpp
+++ b/src/dawn_native/vulkan/VulkanInfo.cpp
@@ -55,8 +55,6 @@
     const char kLayerNameFuchsiaImagePipeSwapchain[] = "VK_LAYER_FUCHSIA_imagepipe_swapchain";
 
     const char kExtensionNameExtDebugMarker[] = "VK_EXT_debug_marker";
-    const char kExtensionNameExtDebugReport[] = "VK_EXT_debug_report";
-    const char kExtensionNameExtMetalSurface[] = "VK_EXT_metal_surface";
     const char kExtensionNameKhrExternalMemory[] = "VK_KHR_external_memory";
     const char kExtensionNameKhrExternalMemoryCapabilities[] =
         "VK_KHR_external_memory_capabilities";
@@ -71,21 +69,32 @@
     const char kExtensionNameFuchsiaExternalSemaphore[] = "VK_FUCHSIA_external_semaphore";
     const char kExtensionNameKhrGetPhysicalDeviceProperties2[] =
         "VK_KHR_get_physical_device_properties2";
-    const char kExtensionNameKhrSurface[] = "VK_KHR_surface";
     const char kExtensionNameKhrSwapchain[] = "VK_KHR_swapchain";
-    const char kExtensionNameKhrWaylandSurface[] = "VK_KHR_wayland_surface";
-    const char kExtensionNameKhrWin32Surface[] = "VK_KHR_win32_surface";
-    const char kExtensionNameKhrXcbSurface[] = "VK_KHR_xcb_surface";
-    const char kExtensionNameKhrXlibSurface[] = "VK_KHR_xlib_surface";
-    const char kExtensionNameFuchsiaImagePipeSurface[] = "VK_FUCHSIA_imagepipe_surface";
     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);
+    }
+
     ResultOrError<VulkanGlobalInfo> GatherGlobalInfo(const Backend& backend) {
         VulkanGlobalInfo info = {};
         const VulkanFunctions& vkFunctions = backend.GetFunctions();
 
+        // Gather info on available API version
+        {
+            uint32_t supportedAPIVersion = VK_MAKE_VERSION(1, 0, 0);
+            if (vkFunctions.EnumerateInstanceVersion) {
+                vkFunctions.EnumerateInstanceVersion(&supportedAPIVersion);
+            }
+
+            // Use Vulkan 1.1 if it's available.
+            info.apiVersion = (supportedAPIVersion >= VK_MAKE_VERSION(1, 1, 0))
+                                  ? VK_MAKE_VERSION(1, 1, 0)
+                                  : VK_MAKE_VERSION(1, 0, 0);
+        }
+
         // Gather the info about the instance layers
         {
             uint32_t count = 0;
@@ -124,76 +133,40 @@
 
         // Gather the info about the instance extensions
         {
-            if (!EnumerateInstanceExtensions(nullptr, vkFunctions, &info.extensions)) {
+            std::unordered_map<std::string, InstanceExt> knownExts = CreateInstanceExtNameMap();
+
+            std::vector<VkExtensionProperties> extensionsProperties;
+            if (!EnumerateInstanceExtensions(nullptr, vkFunctions, &extensionsProperties)) {
                 return DAWN_INTERNAL_ERROR("vkEnumerateInstanceExtensionProperties");
             }
 
-            for (const auto& extension : info.extensions) {
-                if (IsExtensionName(extension, kExtensionNameExtDebugReport)) {
-                    info.debugReport = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameExtMetalSurface)) {
-                    info.metalSurface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalMemoryCapabilities)) {
-                    info.externalMemoryCapabilities = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrExternalSemaphoreCapabilities)) {
-                    info.externalSemaphoreCapabilities = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrGetPhysicalDeviceProperties2)) {
-                    info.getPhysicalDeviceProperties2 = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrSurface)) {
-                    info.surface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrWaylandSurface)) {
-                    info.waylandSurface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrWin32Surface)) {
-                    info.win32Surface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrXcbSurface)) {
-                    info.xcbSurface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameKhrXlibSurface)) {
-                    info.xlibSurface = true;
-                }
-                if (IsExtensionName(extension, kExtensionNameFuchsiaImagePipeSurface)) {
-                    info.fuchsiaImagePipeSurface = true;
+            for (const VkExtensionProperties& extension : extensionsProperties) {
+                auto it = knownExts.find(extension.extensionName);
+                if (it != knownExts.end()) {
+                    info.extensions.Set(it->second, true);
                 }
             }
-        }
 
-        // Specific handling for the Fuchsia swapchain surface creation extension
-        // which is normally part of the Fuchsia-specific swapchain layer.
-        if (info.fuchsiaImagePipeSwapchain && !info.fuchsiaImagePipeSurface) {
-            std::vector<VkExtensionProperties> layer_extensions;
-            if (!EnumerateInstanceExtensions(kLayerNameFuchsiaImagePipeSwapchain, vkFunctions,
-                                             &layer_extensions)) {
-                return DAWN_INTERNAL_ERROR("vkEnumerateInstanceExtensionProperties");
-            }
+            // Specific handling for the Fuchsia swapchain surface creation extension
+            // which is normally part of the Fuchsia-specific swapchain layer.
+            if (info.fuchsiaImagePipeSwapchain &&
+                !info.HasExt(InstanceExt::FuchsiaImagePipeSurface)) {
+                if (!EnumerateInstanceExtensions(kLayerNameFuchsiaImagePipeSwapchain, vkFunctions,
+                                                 &extensionsProperties)) {
+                    return DAWN_INTERNAL_ERROR("vkEnumerateInstanceExtensionProperties");
+                }
 
-            for (const auto& extension : layer_extensions) {
-                if (IsExtensionName(extension, kExtensionNameFuchsiaImagePipeSurface)) {
-                    info.fuchsiaImagePipeSurface = true;
-                    // For now, copy this to the global extension list.
-                    info.extensions.push_back(extension);
+                for (const VkExtensionProperties& extension : extensionsProperties) {
+                    auto it = knownExts.find(extension.extensionName);
+                    if (it != knownExts.end() &&
+                        it->second == InstanceExt::FuchsiaImagePipeSurface) {
+                        info.extensions.Set(InstanceExt::FuchsiaImagePipeSurface, true);
+                    }
                 }
             }
-        }
 
-        // Gather info on available API version
-        {
-            uint32_t supportedAPIVersion = VK_MAKE_VERSION(1, 0, 0);
-            if (vkFunctions.EnumerateInstanceVersion) {
-                vkFunctions.EnumerateInstanceVersion(&supportedAPIVersion);
-            }
-
-            // Use Vulkan 1.1 if it's available.
-            info.apiVersion = (supportedAPIVersion >= VK_MAKE_VERSION(1, 1, 0))
-                                  ? VK_MAKE_VERSION(1, 1, 0)
-                                  : VK_MAKE_VERSION(1, 0, 0);
+            MarkPromotedExtensions(&info.extensions, info.apiVersion);
+            info.extensions = EnsureDependencies(info.extensions);
         }
 
         // TODO(cwallez@chromium:org): Each layer can expose additional extensions, query them?
@@ -315,7 +288,7 @@
                     info.maintenance1 = true;
                 }
                 if (IsExtensionName(extension, kExtensionNameKhrShaderFloat16Int8) &&
-                    globalInfo.getPhysicalDeviceProperties2) {
+                    globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2)) {
                     info.shaderFloat16Int8 = true;
                     info.shaderFloat16Int8Features.sType =
                         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR;
@@ -328,7 +301,7 @@
                                                            &physicalDeviceFeatures2);
                 }
                 if (IsExtensionName(extension, kExtensionNameKhr16BitStorage) &&
-                    globalInfo.getPhysicalDeviceProperties2) {
+                    globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2)) {
                     info._16BitStorage = true;
                 }
             }
@@ -342,7 +315,7 @@
         // 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.getPhysicalDeviceProperties2);
+            ASSERT(globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2));
             info._16BitStorage = true;
             info._16BitStorageFeatures.sType =
                 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
diff --git a/src/dawn_native/vulkan/VulkanInfo.h b/src/dawn_native/vulkan/VulkanInfo.h
index 81ef054..a9d0818 100644
--- a/src/dawn_native/vulkan/VulkanInfo.h
+++ b/src/dawn_native/vulkan/VulkanInfo.h
@@ -17,6 +17,7 @@
 
 #include "common/vulkan_platform.h"
 #include "dawn_native/Error.h"
+#include "dawn_native/vulkan/VulkanExtensions.h"
 
 #include <vector>
 
@@ -31,8 +32,6 @@
     extern const char kLayerNameFuchsiaImagePipeSwapchain[];
 
     extern const char kExtensionNameExtDebugMarker[];
-    extern const char kExtensionNameExtDebugReport[];
-    extern const char kExtensionNameExtMetalSurface[];
     extern const char kExtensionNameKhrExternalMemory[];
     extern const char kExtensionNameKhrExternalMemoryCapabilities[];
     extern const char kExtensionNameKhrExternalMemoryFD[];
@@ -44,13 +43,7 @@
     extern const char kExtensionNameKhrExternalSemaphoreFD[];
     extern const char kExtensionNameFuchsiaExternalSemaphore[];
     extern const char kExtensionNameKhrGetPhysicalDeviceProperties2[];
-    extern const char kExtensionNameKhrSurface[];
     extern const char kExtensionNameKhrSwapchain[];
-    extern const char kExtensionNameKhrWaylandSurface[];
-    extern const char kExtensionNameKhrWin32Surface[];
-    extern const char kExtensionNameKhrXcbSurface[];
-    extern const char kExtensionNameKhrXlibSurface[];
-    extern const char kExtensionNameFuchsiaImagePipeSurface[];
     extern const char kExtensionNameKhrMaintenance1[];
     extern const char kExtensionNameKhrShaderFloat16Int8[];
     extern const char kExtensionNameKhr16BitStorage[];
@@ -63,23 +56,12 @@
         bool renderDocCapture = false;
         bool fuchsiaImagePipeSwapchain = false;
 
-        // Extensions
-        bool debugReport = false;
-        bool externalMemoryCapabilities = false;
-        bool externalSemaphoreCapabilities = false;
-        bool getPhysicalDeviceProperties2 = false;
-        bool metalSurface = false;
-        bool surface = false;
-        bool waylandSurface = false;
-        bool win32Surface = false;
-        bool xcbSurface = false;
-        bool xlibSurface = false;
-        bool fuchsiaImagePipeSurface = false;
+        bool HasExt(InstanceExt ext) const;
+        InstanceExtSet extensions;
     };
 
     struct VulkanGlobalInfo : VulkanGlobalKnobs {
         std::vector<VkLayerProperties> layers;
-        std::vector<VkExtensionProperties> extensions;
         uint32_t apiVersion;
         // 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 35c7a07..c521ed1 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -64,8 +64,8 @@
         const VulkanGlobalInfo& globalInfo =
             ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.getPhysicalDeviceProperties2 &&
-                     globalInfo.externalMemoryCapabilities && deviceInfo.externalMemory &&
+        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2 &&
+                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) && deviceInfo.externalMemory &&
                      deviceInfo.externalMemoryFD && deviceInfo.externalMemoryDmaBuf &&
                      deviceInfo.imageDrmFormatModifier;
     }
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index 8724324..57b74af 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -27,9 +27,9 @@
         const VulkanGlobalInfo& globalInfo =
             ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.getPhysicalDeviceProperties2 &&
-                     globalInfo.externalMemoryCapabilities && deviceInfo.externalMemory &&
-                     deviceInfo.externalMemoryFD;
+        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
+                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) &&
+                     deviceInfo.externalMemory && deviceInfo.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 10b9955..ab87a7f 100644
--- a/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -27,9 +27,9 @@
         const VulkanGlobalInfo& globalInfo =
             ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.getPhysicalDeviceProperties2 &&
-                     globalInfo.externalMemoryCapabilities && deviceInfo.externalMemory &&
-                     deviceInfo.externalMemoryFD;
+        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
+                     globalInfo.HasExt(InstanceExt::ExternalMemoryCapabilities) &&
+                     deviceInfo.externalMemory && deviceInfo.externalMemoryFD;
     }
 
     Service::~Service() = default;
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
index e79288a..222fcbb 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
@@ -25,9 +25,9 @@
         const VulkanGlobalInfo& globalInfo =
             ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.getPhysicalDeviceProperties2 &&
-                     globalInfo.externalSemaphoreCapabilities && deviceInfo.externalSemaphore &&
-                     deviceInfo.externalSemaphoreFD;
+        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
+                     globalInfo.HasExt(InstanceExt::ExternalSemaphoreCapabilities) &&
+                     deviceInfo.externalSemaphore && deviceInfo.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 fd10076..985cf4f 100644
--- a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp
@@ -25,9 +25,9 @@
         const VulkanGlobalInfo& globalInfo =
             ToBackend(mDevice->GetAdapter())->GetBackend()->GetGlobalInfo();
 
-        mSupported = globalInfo.getPhysicalDeviceProperties2 &&
-                     globalInfo.externalSemaphoreCapabilities && deviceInfo.externalSemaphore &&
-                     deviceInfo.externalSemaphoreFD;
+        mSupported = globalInfo.HasExt(InstanceExt::GetPhysicalDeviceProperties2) &&
+                     globalInfo.HasExt(InstanceExt::ExternalSemaphoreCapabilities) &&
+                     deviceInfo.externalSemaphore && deviceInfo.externalSemaphoreFD;
 
         // Early out before we try using extension functions
         if (!mSupported) {