Populate supported Vulkan limits from the backend

This commit also unifies the initialization process for Adapters.
InitializeImpl() initializes the actual backend adapter.
InitializeSupportedFeaturesImpl() checks base WebGPU features and
discovers additional supported features.
InitializeSupportedLimitsImpl() checks base WebGPU limits and
queries the adapter's maximum supported limits.

Some of these limits from the backend are still overriden in the
frontend because they are limited by internal Dawn constants.

Bug: dawn:685
Change-Id: I43efb0b678dd45f8f89cd62d13104dd00b197da1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64980
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Brandon Jones <bajones@chromium.org>
diff --git a/src/dawn_native/Adapter.cpp b/src/dawn_native/Adapter.cpp
index cb16e99..d83871b 100644
--- a/src/dawn_native/Adapter.cpp
+++ b/src/dawn_native/Adapter.cpp
@@ -14,16 +14,51 @@
 
 #include "dawn_native/Adapter.h"
 
+#include "common/Constants.h"
 #include "dawn_native/Instance.h"
 
 namespace dawn_native {
 
     AdapterBase::AdapterBase(InstanceBase* instance, wgpu::BackendType backend)
         : mInstance(instance), mBackend(backend) {
-        GetDefaultLimits(&mLimits.v1);
         mSupportedFeatures.EnableFeature(Feature::DawnInternalUsages);
     }
 
+    MaybeError AdapterBase::Initialize() {
+        DAWN_TRY(InitializeImpl());
+        DAWN_TRY(InitializeSupportedFeaturesImpl());
+        DAWN_TRY(InitializeSupportedLimitsImpl(&mLimits));
+
+        // Enforce internal Dawn constants.
+        mLimits.v1.maxVertexBufferArrayStride =
+            std::min(mLimits.v1.maxVertexBufferArrayStride, kMaxVertexBufferArrayStride);
+        mLimits.v1.maxBindGroups = std::min(mLimits.v1.maxBindGroups, kMaxBindGroups);
+        mLimits.v1.maxVertexAttributes =
+            std::min(mLimits.v1.maxVertexAttributes, uint32_t(kMaxVertexAttributes));
+        mLimits.v1.maxVertexBuffers =
+            std::min(mLimits.v1.maxVertexBuffers, uint32_t(kMaxVertexBuffers));
+        mLimits.v1.maxInterStageShaderComponents =
+            std::min(mLimits.v1.maxInterStageShaderComponents, kMaxInterStageShaderComponents);
+        mLimits.v1.maxSampledTexturesPerShaderStage = std::min(
+            mLimits.v1.maxSampledTexturesPerShaderStage, kMaxSampledTexturesPerShaderStage);
+        mLimits.v1.maxSamplersPerShaderStage =
+            std::min(mLimits.v1.maxSamplersPerShaderStage, kMaxSamplersPerShaderStage);
+        mLimits.v1.maxStorageBuffersPerShaderStage =
+            std::min(mLimits.v1.maxStorageBuffersPerShaderStage, kMaxStorageBuffersPerShaderStage);
+        mLimits.v1.maxStorageTexturesPerShaderStage = std::min(
+            mLimits.v1.maxStorageTexturesPerShaderStage, kMaxStorageTexturesPerShaderStage);
+        mLimits.v1.maxUniformBuffersPerShaderStage =
+            std::min(mLimits.v1.maxUniformBuffersPerShaderStage, kMaxUniformBuffersPerShaderStage);
+        mLimits.v1.maxDynamicUniformBuffersPerPipelineLayout =
+            std::min(mLimits.v1.maxDynamicUniformBuffersPerPipelineLayout,
+                     kMaxDynamicUniformBuffersPerPipelineLayout);
+        mLimits.v1.maxDynamicStorageBuffersPerPipelineLayout =
+            std::min(mLimits.v1.maxDynamicStorageBuffersPerPipelineLayout,
+                     kMaxDynamicStorageBuffersPerPipelineLayout);
+
+        return {};
+    }
+
     wgpu::BackendType AdapterBase::GetBackendType() const {
         return mBackend;
     }
diff --git a/src/dawn_native/Adapter.h b/src/dawn_native/Adapter.h
index adf230b..d2bd139 100644
--- a/src/dawn_native/Adapter.h
+++ b/src/dawn_native/Adapter.h
@@ -33,6 +33,8 @@
         AdapterBase(InstanceBase* instance, wgpu::BackendType backend);
         virtual ~AdapterBase() = default;
 
+        MaybeError Initialize();
+
         wgpu::BackendType GetBackendType() const;
         wgpu::AdapterType GetAdapterType() const;
         const std::string& GetDriverDescription() const;
@@ -66,6 +68,14 @@
       private:
         virtual ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) = 0;
 
+        virtual MaybeError InitializeImpl() = 0;
+
+        // Check base WebGPU features and discover supported featurees.
+        virtual MaybeError InitializeSupportedFeaturesImpl() = 0;
+
+        // Check base WebGPU limits and populate supported limits.
+        virtual MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) = 0;
+
         MaybeError CreateDeviceInternal(DeviceBase** result, const DeviceDescriptor* descriptor);
 
         virtual MaybeError ResetInternalDeviceForTestingImpl();
diff --git a/src/dawn_native/d3d12/AdapterD3D12.cpp b/src/dawn_native/d3d12/AdapterD3D12.cpp
index 67f5066..26ac005 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn_native/d3d12/AdapterD3D12.cpp
@@ -61,7 +61,7 @@
         return mDriverVersion;
     }
 
-    MaybeError Adapter::Initialize() {
+    MaybeError Adapter::InitializeImpl() {
         // D3D12 cannot check for feature support without a device.
         // Create the device to populate the adapter properties then reuse it when needed for actual
         // rendering.
@@ -104,8 +104,6 @@
             mDriverDescription = o.str();
         }
 
-        InitializeSupportedFeatures();
-
         return {};
     }
 
@@ -130,13 +128,19 @@
         return true;
     }
 
-    void Adapter::InitializeSupportedFeatures() {
+    MaybeError Adapter::InitializeSupportedFeaturesImpl() {
         if (AreTimestampQueriesSupported()) {
             mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
         }
         mSupportedFeatures.EnableFeature(Feature::TextureCompressionBC);
         mSupportedFeatures.EnableFeature(Feature::PipelineStatisticsQuery);
         mSupportedFeatures.EnableFeature(Feature::MultiPlanarFormats);
+        return {};
+    }
+
+    MaybeError Adapter::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
+        GetDefaultLimits(&limits->v1);
+        return {};
     }
 
     MaybeError Adapter::InitializeDebugLayerFilters() {
diff --git a/src/dawn_native/d3d12/AdapterD3D12.h b/src/dawn_native/d3d12/AdapterD3D12.h
index 9c3d8f4..f4044d1 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.h
+++ b/src/dawn_native/d3d12/AdapterD3D12.h
@@ -39,15 +39,16 @@
         ComPtr<ID3D12Device> GetDevice() const;
         const gpu_info::D3DDriverVersion& GetDriverVersion() const;
 
-        MaybeError Initialize();
-
       private:
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
         MaybeError ResetInternalDeviceForTestingImpl() override;
 
         bool AreTimestampQueriesSupported() const;
 
-        void InitializeSupportedFeatures();
+        MaybeError InitializeImpl() override;
+        MaybeError InitializeSupportedFeaturesImpl() override;
+        MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
+
         MaybeError InitializeDebugLayerFilters();
         void CleanUpDebugLayerFilters();
 
diff --git a/src/dawn_native/d3d12/BackendD3D12.cpp b/src/dawn_native/d3d12/BackendD3D12.cpp
index 3e5c15b..8ba578f 100644
--- a/src/dawn_native/d3d12/BackendD3D12.cpp
+++ b/src/dawn_native/d3d12/BackendD3D12.cpp
@@ -163,7 +163,7 @@
             ResultOrError<std::unique_ptr<AdapterBase>> adapter =
                 CreateAdapterFromIDXGIAdapter(this, dxgiAdapter);
             if (adapter.IsError()) {
-                adapter.AcquireError();
+                GetInstance()->ConsumedError(adapter.AcquireError());
                 continue;
             }
 
diff --git a/src/dawn_native/metal/BackendMTL.mm b/src/dawn_native/metal/BackendMTL.mm
index 48bbb4b..caea140 100644
--- a/src/dawn_native/metal/BackendMTL.mm
+++ b/src/dawn_native/metal/BackendMTL.mm
@@ -263,8 +263,6 @@
             NSString* osVersion = [[NSProcessInfo processInfo] operatingSystemVersionString];
             mDriverDescription =
                 "Metal driver on " + std::string(systemName) + [osVersion UTF8String];
-
-            InitializeSupportedFeatures();
         }
 
         // AdapterBase Implementation
@@ -278,7 +276,11 @@
             return Device::Create(this, mDevice, descriptor);
         }
 
-        void InitializeSupportedFeatures() {
+        MaybeError InitializeImpl() override {
+            return {};
+        }
+
+        MaybeError InitializeSupportedFeaturesImpl() override {
 #if defined(DAWN_PLATFORM_MACOS)
             if ([*mDevice supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) {
                 mSupportedFeatures.EnableFeature(Feature::TextureCompressionBC);
@@ -315,6 +317,13 @@
             if (@available(macOS 10.11, iOS 11.0, *)) {
                 mSupportedFeatures.EnableFeature(Feature::DepthClamping);
             }
+
+            return {};
+        }
+
+        MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override {
+            GetDefaultLimits(&limits->v1);
+            return {};
         }
 
         NSPRef<id<MTLDevice>> mDevice;
@@ -339,7 +348,10 @@
             NSRef<NSArray<id<MTLDevice>>> devices = AcquireNSRef(MTLCopyAllDevices());
 
             for (id<MTLDevice> device in devices.Get()) {
-                adapters.push_back(std::make_unique<Adapter>(GetInstance(), device));
+                std::unique_ptr<Adapter> adapter = std::make_unique<Adapter>(GetInstance(), device);
+                if (!GetInstance()->ConsumedError(adapter->Initialize())) {
+                    adapters.push_back(std::move(adapter));
+                }
             }
         }
 #endif
@@ -348,8 +360,10 @@
         if (@available(iOS 8.0, *)) {
             supportedVersion = YES;
             // iOS only has a single device so MTLCopyAllDevices doesn't exist there.
-            adapters.push_back(
-                std::make_unique<Adapter>(GetInstance(), MTLCreateSystemDefaultDevice()));
+            std::unique_ptr<Adapter> adapter = std::make_unique<Adapter>(GetInstance(), MTLCreateSystemDefaultDevice());
+            if (!GetInstance()->ConsumedError(adapter->Initialize())) {
+                adapters.push_back(std::move(adapter));
+            }
         }
 #endif
         if (!supportedVersion) {
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index e9b23eb..6b785a1 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -27,9 +27,8 @@
     Adapter::Adapter(InstanceBase* instance) : AdapterBase(instance, wgpu::BackendType::Null) {
         mPCIInfo.name = "Null backend";
         mAdapterType = wgpu::AdapterType::CPU;
-
-        // Enable all features by default for the convenience of tests.
-        mSupportedFeatures.featuresBitSet.set();
+        MaybeError err = Initialize();
+        ASSERT(err.IsSuccess());
     }
 
     Adapter::~Adapter() = default;
@@ -43,6 +42,21 @@
         mSupportedFeatures = GetInstance()->FeatureNamesToFeaturesSet(requiredFeatures);
     }
 
+    MaybeError Adapter::InitializeImpl() {
+        return {};
+    }
+
+    MaybeError Adapter::InitializeSupportedFeaturesImpl() {
+        // Enable all features by default for the convenience of tests.
+        mSupportedFeatures.featuresBitSet.set();
+        return {};
+    }
+
+    MaybeError Adapter::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
+        GetDefaultLimits(&limits->v1);
+        return {};
+    }
+
     ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
         return Device::Create(this, descriptor);
     }
@@ -56,7 +70,8 @@
             // There is always a single Null adapter because it is purely CPU based and doesn't
             // depend on the system.
             std::vector<std::unique_ptr<AdapterBase>> adapters;
-            adapters.push_back(std::make_unique<Adapter>(GetInstance()));
+            std::unique_ptr<Adapter> adapter = std::make_unique<Adapter>(GetInstance());
+            adapters.push_back(std::move(adapter));
             return adapters;
         }
     };
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index 5192819..cef718d 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -177,6 +177,10 @@
         void SetSupportedFeatures(const std::vector<const char*>& requiredFeatures);
 
       private:
+        MaybeError InitializeImpl() override;
+        MaybeError InitializeSupportedFeaturesImpl() override;
+        MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
+
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
     };
 
diff --git a/src/dawn_native/opengl/BackendGL.cpp b/src/dawn_native/opengl/BackendGL.cpp
index b92599e..9a66779 100644
--- a/src/dawn_native/opengl/BackendGL.cpp
+++ b/src/dawn_native/opengl/BackendGL.cpp
@@ -123,9 +123,21 @@
             : AdapterBase(instance, backendType) {
         }
 
-        MaybeError Initialize(const AdapterDiscoveryOptions* options) {
+        MaybeError InitializeGLFunctions(void* (*getProc)(const char*)) {
             // Use getProc to populate the dispatch table
-            DAWN_TRY(mFunctions.Initialize(options->getProc));
+            return mFunctions.Initialize(getProc);
+        }
+
+        ~Adapter() override = default;
+
+        // AdapterBase Implementation
+        bool SupportsExternalImages() const override {
+            // Via dawn_native::opengl::WrapExternalEGLImage
+            return GetBackendType() == wgpu::BackendType::OpenGLES;
+        }
+
+      private:
+        MaybeError InitializeImpl() override {
             if (mFunctions.GetVersion().IsES()) {
                 ASSERT(GetBackendType() == wgpu::BackendType::OpenGLES);
             } else {
@@ -187,29 +199,10 @@
                 mAdapterType = wgpu::AdapterType::CPU;
             }
 
-            InitializeSupportedFeatures();
-
             return {};
         }
 
-        ~Adapter() override = default;
-
-        // AdapterBase Implementation
-        bool SupportsExternalImages() const override {
-            // Via dawn_native::opengl::WrapExternalEGLImage
-            return GetBackendType() == wgpu::BackendType::OpenGLES;
-        }
-
-      private:
-        OpenGLFunctions mFunctions;
-
-        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
-            // There is no limit on the number of devices created from this adapter because they can
-            // all share the same backing OpenGL context.
-            return Device::Create(this, descriptor, mFunctions);
-        }
-
-        void InitializeSupportedFeatures() {
+        MaybeError InitializeSupportedFeaturesImpl() override {
             // TextureCompressionBC
             {
                 // BC1, BC2 and BC3 are not supported in OpenGL or OpenGL ES core features.
@@ -252,7 +245,22 @@
                     mSupportedFeatures.EnableFeature(dawn_native::Feature::TextureCompressionBC);
                 }
             }
+
+            return {};
         }
+
+        MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override {
+            GetDefaultLimits(&limits->v1);
+            return {};
+        }
+
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
+            // There is no limit on the number of devices created from this adapter because they can
+            // all share the same backing OpenGL context.
+            return Device::Create(this, descriptor, mFunctions);
+        }
+
+        OpenGLFunctions mFunctions;
     };
 
     // Implementation of the OpenGL backend's BackendConnection
@@ -284,7 +292,8 @@
 
         std::unique_ptr<Adapter> adapter = std::make_unique<Adapter>(
             GetInstance(), static_cast<wgpu::BackendType>(optionsBase->backendType));
-        DAWN_TRY(adapter->Initialize(options));
+        DAWN_TRY(adapter->InitializeGLFunctions(options->getProc));
+        DAWN_TRY(adapter->Initialize());
 
         mCreatedAdapter = true;
         std::vector<std::unique_ptr<AdapterBase>> adapters;
diff --git a/src/dawn_native/vulkan/AdapterVk.cpp b/src/dawn_native/vulkan/AdapterVk.cpp
index df488b3..bf2aa02 100644
--- a/src/dawn_native/vulkan/AdapterVk.cpp
+++ b/src/dawn_native/vulkan/AdapterVk.cpp
@@ -40,9 +40,8 @@
         return mBackend;
     }
 
-    MaybeError Adapter::Initialize() {
+    MaybeError Adapter::InitializeImpl() {
         DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(*this));
-        DAWN_TRY(CheckCoreWebGPUSupport());
 
         if (mDeviceInfo.HasExt(DeviceExt::DriverProperties)) {
             mDriverDescription = mDeviceInfo.driverProperties.driverName;
@@ -54,8 +53,6 @@
                 "Vulkan driver version: " + std::to_string(mDeviceInfo.properties.driverVersion);
         }
 
-        InitializeSupportedFeatures();
-
         mPCIInfo.deviceId = mDeviceInfo.properties.deviceID;
         mPCIInfo.vendorId = mDeviceInfo.properties.vendorID;
         mPCIInfo.name = mDeviceInfo.properties.deviceName;
@@ -78,10 +75,7 @@
         return {};
     }
 
-    MaybeError Adapter::CheckCoreWebGPUSupport() {
-        Limits baseLimits;
-        GetDefaultLimits(&baseLimits);
-
+    MaybeError Adapter::InitializeSupportedFeaturesImpl() {
         // Needed for viewport Y-flip.
         if (!mDeviceInfo.HasExt(DeviceExt::Maintenance1)) {
             return DAWN_INTERNAL_ERROR("Vulkan 1.1 or Vulkan 1.0 with KHR_Maintenance1 required.");
@@ -120,152 +114,7 @@
             return DAWN_INTERNAL_ERROR("Vulkan sampleRateShading feature required.");
         }
 
-        // Check base WebGPU limits are supported.
-        const VkPhysicalDeviceLimits& limits = mDeviceInfo.properties.limits;
-        if (limits.maxImageDimension1D < baseLimits.maxTextureDimension1D) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxTextureDimension1D");
-        }
-        if (limits.maxImageDimension2D < baseLimits.maxTextureDimension2D ||
-            limits.maxImageDimensionCube < baseLimits.maxTextureDimension2D ||
-            limits.maxFramebufferWidth < baseLimits.maxTextureDimension2D ||
-            limits.maxFramebufferHeight < baseLimits.maxTextureDimension2D ||
-            limits.maxViewportDimensions[0] < baseLimits.maxTextureDimension2D ||
-            limits.maxViewportDimensions[1] < baseLimits.maxTextureDimension2D ||
-            limits.viewportBoundsRange[1] < baseLimits.maxTextureDimension2D) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxTextureDimension2D");
-        }
-        if (limits.maxImageDimension3D < baseLimits.maxTextureDimension3D) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxTextureDimension3D");
-        }
-        if (limits.maxImageArrayLayers < baseLimits.maxTextureArrayLayers) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxTextureArrayLayers");
-        }
-        if (limits.maxBoundDescriptorSets < baseLimits.maxBindGroups) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxBindGroups");
-        }
-        if (limits.maxDescriptorSetUniformBuffersDynamic <
-            baseLimits.maxDynamicUniformBuffersPerPipelineLayout) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxDynamicUniformBuffersPerPipelineLayout");
-        }
-        if (limits.maxDescriptorSetStorageBuffersDynamic <
-            baseLimits.maxDynamicStorageBuffersPerPipelineLayout) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxDynamicStorageBuffersPerPipelineLayout");
-        }
-        if (limits.maxPerStageDescriptorSampledImages <
-            baseLimits.maxSampledTexturesPerShaderStage) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxSampledTexturesPerShaderStage");
-        }
-        if (limits.maxPerStageDescriptorSamplers < baseLimits.maxSamplersPerShaderStage) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxSamplersPerShaderStage");
-        }
-        if (limits.maxPerStageDescriptorStorageBuffers <
-            baseLimits.maxStorageBuffersPerShaderStage) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxStorageBuffersPerShaderStage");
-        }
-        if (limits.maxPerStageDescriptorStorageImages <
-            baseLimits.maxStorageTexturesPerShaderStage) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxStorageTexturesPerShaderStage");
-        }
-        if (limits.maxPerStageDescriptorUniformBuffers <
-            baseLimits.maxUniformBuffersPerShaderStage) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxUniformBuffersPerShaderStage");
-        }
-        if (limits.maxUniformBufferRange < baseLimits.maxUniformBufferBindingSize) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxUniformBufferBindingSize");
-        }
-        if (limits.maxStorageBufferRange < baseLimits.maxStorageBufferBindingSize) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxStorageBufferBindingSize");
-        }
-        if (limits.minUniformBufferOffsetAlignment > baseLimits.minUniformBufferOffsetAlignment) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for minUniformBufferOffsetAlignment");
-        }
-        if (limits.minStorageBufferOffsetAlignment > baseLimits.minStorageBufferOffsetAlignment) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for minStorageBufferOffsetAlignment");
-        }
-        if (limits.maxVertexInputBindings < baseLimits.maxVertexBuffers) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxVertexBuffers");
-        }
-        if (limits.maxVertexInputAttributes < baseLimits.maxVertexAttributes) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxVertexAttributes");
-        }
-        if (limits.maxVertexInputBindingStride < baseLimits.maxVertexBufferArrayStride ||
-            limits.maxVertexInputAttributeOffset < baseLimits.maxVertexBufferArrayStride - 1) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxVertexBufferArrayStride");
-        }
-        if (limits.maxVertexOutputComponents < baseLimits.maxInterStageShaderComponents ||
-            limits.maxFragmentInputComponents < baseLimits.maxInterStageShaderComponents) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxInterStageShaderComponents");
-        }
-        if (limits.maxComputeSharedMemorySize < baseLimits.maxComputeWorkgroupStorageSize) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxComputeWorkgroupStorageSize");
-        }
-        if (limits.maxComputeWorkGroupInvocations < baseLimits.maxComputeInvocationsPerWorkgroup) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxComputeInvocationsPerWorkgroup");
-        }
-        if (limits.maxComputeWorkGroupSize[0] < baseLimits.maxComputeWorkgroupSizeX ||
-            limits.maxComputeWorkGroupSize[1] < baseLimits.maxComputeWorkgroupSizeY ||
-            limits.maxComputeWorkGroupSize[2] < baseLimits.maxComputeWorkgroupSizeZ) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxComputeWorkgroupSize");
-        }
-        if (limits.maxComputeWorkGroupCount[0] < baseLimits.maxComputeWorkgroupsPerDimension ||
-            limits.maxComputeWorkGroupCount[1] < baseLimits.maxComputeWorkgroupsPerDimension ||
-            limits.maxComputeWorkGroupCount[2] < baseLimits.maxComputeWorkgroupsPerDimension) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for maxComputeWorkgroupsPerDimension");
-        }
-        if (limits.maxColorAttachments < kMaxColorAttachments) {
-            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxColorAttachments");
-        }
-        if (!IsSubset(VkSampleCountFlags(VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT),
-                      limits.framebufferColorSampleCounts)) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for framebufferColorSampleCounts");
-        }
-        if (!IsSubset(VkSampleCountFlags(VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT),
-                      limits.framebufferDepthSampleCounts)) {
-            return DAWN_INTERNAL_ERROR(
-                "Insufficient Vulkan limits for framebufferDepthSampleCounts");
-        }
-
-        // Only check maxFragmentCombinedOutputResources on mobile GPUs. Desktop GPUs drivers seem
-        // to put incorrect values for this limit with things like 8 or 16 when they can do bindless
-        // storage buffers.
-        uint32_t vendorId = mDeviceInfo.properties.vendorID;
-        if (!gpu_info::IsAMD(vendorId) && !gpu_info::IsIntel(vendorId) &&
-            !gpu_info::IsNvidia(vendorId)) {
-            if (limits.maxFragmentCombinedOutputResources <
-                kMaxColorAttachments + baseLimits.maxStorageTexturesPerShaderStage +
-                    baseLimits.maxStorageBuffersPerShaderStage) {
-                return DAWN_INTERNAL_ERROR(
-                    "Insufficient Vulkan maxFragmentCombinedOutputResources limit");
-            }
-        }
-
-        return {};
-    }
-
-    bool Adapter::SupportsExternalImages() const {
-        // Via dawn_native::vulkan::WrapVulkanImage
-        return external_memory::Service::CheckSupport(mDeviceInfo) &&
-               external_semaphore::Service::CheckSupport(mDeviceInfo, mPhysicalDevice,
-                                                         mBackend->GetFunctions());
-    }
-
-    void Adapter::InitializeSupportedFeatures() {
+        // Initialize supported extensions
         if (mDeviceInfo.features.textureCompressionBC == VK_TRUE) {
             mSupportedFeatures.EnableFeature(Feature::TextureCompressionBC);
         }
@@ -289,6 +138,186 @@
         if (mDeviceInfo.properties.limits.timestampComputeAndGraphics == VK_TRUE) {
             mSupportedFeatures.EnableFeature(Feature::TimestampQuery);
         }
+
+        return {};
+    }
+
+    MaybeError Adapter::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
+        GetDefaultLimits(&limits->v1);
+        CombinedLimits baseLimits = *limits;
+
+        const VkPhysicalDeviceLimits& vkLimits = mDeviceInfo.properties.limits;
+
+#define CHECK_AND_SET_V1_LIMIT_IMPL(vulkanName, webgpuName, compareOp, msgSegment)   \
+    do {                                                                             \
+        if (vkLimits.vulkanName compareOp baseLimits.v1.webgpuName) {                \
+            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for " #webgpuName \
+                                       "."                                           \
+                                       " VkPhysicalDeviceLimits::" #vulkanName       \
+                                       " must be at " msgSegment " " +               \
+                                       std::to_string(baseLimits.v1.webgpuName));    \
+        }                                                                            \
+        limits->v1.webgpuName = vkLimits.vulkanName;                                 \
+    } while (false)
+
+#define CHECK_AND_SET_V1_MAX_LIMIT(vulkanName, webgpuName) \
+    CHECK_AND_SET_V1_LIMIT_IMPL(vulkanName, webgpuName, <, "least")
+#define CHECK_AND_SET_V1_MIN_LIMIT(vulkanName, webgpuName) \
+    CHECK_AND_SET_V1_LIMIT_IMPL(vulkanName, webgpuName, >, "most")
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxImageDimension1D, maxTextureDimension1D);
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxImageDimension2D, maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxImageDimensionCube, maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxFramebufferWidth, maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxFramebufferHeight, maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxViewportDimensions[0], maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxViewportDimensions[1], maxTextureDimension2D);
+        CHECK_AND_SET_V1_MAX_LIMIT(viewportBoundsRange[1], maxTextureDimension2D);
+        limits->v1.maxTextureDimension2D = std::min({
+            static_cast<uint32_t>(vkLimits.maxImageDimension2D),
+            static_cast<uint32_t>(vkLimits.maxImageDimensionCube),
+            static_cast<uint32_t>(vkLimits.maxFramebufferWidth),
+            static_cast<uint32_t>(vkLimits.maxFramebufferHeight),
+            static_cast<uint32_t>(vkLimits.maxViewportDimensions[0]),
+            static_cast<uint32_t>(vkLimits.maxViewportDimensions[1]),
+            static_cast<uint32_t>(vkLimits.viewportBoundsRange[1]),
+        });
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxImageDimension3D, maxTextureDimension3D);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxImageArrayLayers, maxTextureArrayLayers);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxBoundDescriptorSets, maxBindGroups);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxDescriptorSetUniformBuffersDynamic,
+                                   maxDynamicUniformBuffersPerPipelineLayout);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxDescriptorSetStorageBuffersDynamic,
+                                   maxDynamicStorageBuffersPerPipelineLayout);
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxPerStageDescriptorSampledImages,
+                                   maxSampledTexturesPerShaderStage);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxPerStageDescriptorSamplers, maxSamplersPerShaderStage);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxPerStageDescriptorStorageBuffers,
+                                   maxStorageBuffersPerShaderStage);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxPerStageDescriptorStorageImages,
+                                   maxStorageTexturesPerShaderStage);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxPerStageDescriptorUniformBuffers,
+                                   maxUniformBuffersPerShaderStage);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxUniformBufferRange, maxUniformBufferBindingSize);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxStorageBufferRange, maxStorageBufferBindingSize);
+
+        CHECK_AND_SET_V1_MIN_LIMIT(minUniformBufferOffsetAlignment,
+                                   minUniformBufferOffsetAlignment);
+        CHECK_AND_SET_V1_MIN_LIMIT(minStorageBufferOffsetAlignment,
+                                   minStorageBufferOffsetAlignment);
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxVertexInputBindings, maxVertexBuffers);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxVertexInputAttributes, maxVertexAttributes);
+
+        if (vkLimits.maxVertexInputBindingStride < baseLimits.v1.maxVertexBufferArrayStride ||
+            vkLimits.maxVertexInputAttributeOffset < baseLimits.v1.maxVertexBufferArrayStride - 1) {
+            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxVertexBufferArrayStride");
+        }
+        limits->v1.maxVertexBufferArrayStride = std::min(
+            vkLimits.maxVertexInputBindingStride, vkLimits.maxVertexInputAttributeOffset + 1);
+
+        if (vkLimits.maxVertexOutputComponents < baseLimits.v1.maxInterStageShaderComponents ||
+            vkLimits.maxFragmentInputComponents < baseLimits.v1.maxInterStageShaderComponents) {
+            return DAWN_INTERNAL_ERROR(
+                "Insufficient Vulkan limits for maxInterStageShaderComponents");
+        }
+        limits->v1.maxInterStageShaderComponents =
+            std::min(vkLimits.maxVertexOutputComponents, vkLimits.maxFragmentInputComponents);
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeSharedMemorySize, maxComputeWorkgroupStorageSize);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupInvocations,
+                                   maxComputeInvocationsPerWorkgroup);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupSize[0], maxComputeWorkgroupSizeX);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupSize[1], maxComputeWorkgroupSizeY);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupSize[2], maxComputeWorkgroupSizeZ);
+
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupCount[0], maxComputeWorkgroupsPerDimension);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupCount[1], maxComputeWorkgroupsPerDimension);
+        CHECK_AND_SET_V1_MAX_LIMIT(maxComputeWorkGroupCount[2], maxComputeWorkgroupsPerDimension);
+        limits->v1.maxComputeWorkgroupsPerDimension = std::min({
+            vkLimits.maxComputeWorkGroupCount[0],
+            vkLimits.maxComputeWorkGroupCount[1],
+            vkLimits.maxComputeWorkGroupCount[2],
+        });
+
+        if (vkLimits.maxColorAttachments < kMaxColorAttachments) {
+            return DAWN_INTERNAL_ERROR("Insufficient Vulkan limits for maxColorAttachments");
+        }
+        if (!IsSubset(VkSampleCountFlags(VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT),
+                      vkLimits.framebufferColorSampleCounts)) {
+            return DAWN_INTERNAL_ERROR(
+                "Insufficient Vulkan limits for framebufferColorSampleCounts");
+        }
+        if (!IsSubset(VkSampleCountFlags(VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT),
+                      vkLimits.framebufferDepthSampleCounts)) {
+            return DAWN_INTERNAL_ERROR(
+                "Insufficient Vulkan limits for framebufferDepthSampleCounts");
+        }
+
+        // Only check maxFragmentCombinedOutputResources on mobile GPUs. Desktop GPUs drivers seem
+        // to put incorrect values for this limit with things like 8 or 16 when they can do bindless
+        // storage buffers.
+        uint32_t vendorId = mDeviceInfo.properties.vendorID;
+        if (!gpu_info::IsAMD(vendorId) && !gpu_info::IsIntel(vendorId) &&
+            !gpu_info::IsNvidia(vendorId)) {
+            if (vkLimits.maxFragmentCombinedOutputResources <
+                kMaxColorAttachments + baseLimits.v1.maxStorageTexturesPerShaderStage +
+                    baseLimits.v1.maxStorageBuffersPerShaderStage) {
+                return DAWN_INTERNAL_ERROR(
+                    "Insufficient Vulkan maxFragmentCombinedOutputResources limit");
+            }
+
+            uint32_t maxFragmentCombinedOutputResources =
+                kMaxColorAttachments + limits->v1.maxStorageTexturesPerShaderStage +
+                limits->v1.maxStorageBuffersPerShaderStage;
+
+            if (maxFragmentCombinedOutputResources > vkLimits.maxFragmentCombinedOutputResources) {
+                // WebGPU's maxFragmentCombinedOutputResources exceeds the Vulkan limit.
+                // Decrease |maxStorageTexturesPerShaderStage| and |maxStorageBuffersPerShaderStage|
+                // to fit within the Vulkan limit.
+                uint32_t countOverLimit = maxFragmentCombinedOutputResources -
+                                          vkLimits.maxFragmentCombinedOutputResources;
+
+                uint32_t maxStorageTexturesOverBase =
+                    limits->v1.maxStorageTexturesPerShaderStage -
+                    baseLimits.v1.maxStorageTexturesPerShaderStage;
+                uint32_t maxStorageBuffersOverBase = limits->v1.maxStorageBuffersPerShaderStage -
+                                                     baseLimits.v1.maxStorageBuffersPerShaderStage;
+
+                // Reduce the number of resources by half the overage count, but clamp to
+                // to ensure we don't go below the base limits.
+                uint32_t numFewerStorageTextures =
+                    std::min(countOverLimit / 2, maxStorageTexturesOverBase);
+                uint32_t numFewerStorageBuffers =
+                    std::min((countOverLimit + 1) / 2, maxStorageBuffersOverBase);
+
+                if (numFewerStorageTextures == maxStorageTexturesOverBase) {
+                    // If |numFewerStorageTextures| was clamped, subtract the remaining
+                    // from the storage buffers.
+                    numFewerStorageBuffers = countOverLimit - numFewerStorageTextures;
+                    ASSERT(numFewerStorageBuffers <= maxStorageBuffersOverBase);
+                } else if (numFewerStorageBuffers == maxStorageBuffersOverBase) {
+                    // If |numFewerStorageBuffers| was clamped, subtract the remaining
+                    // from the storage textures.
+                    numFewerStorageTextures = countOverLimit - numFewerStorageBuffers;
+                    ASSERT(numFewerStorageTextures <= maxStorageTexturesOverBase);
+                }
+                limits->v1.maxStorageTexturesPerShaderStage -= numFewerStorageTextures;
+                limits->v1.maxStorageBuffersPerShaderStage -= numFewerStorageBuffers;
+            }
+        }
+
+        return {};
+    }
+
+    bool Adapter::SupportsExternalImages() const {
+        // Via dawn_native::vulkan::WrapVulkanImage
+        return external_memory::Service::CheckSupport(mDeviceInfo) &&
+               external_semaphore::Service::CheckSupport(mDeviceInfo, mPhysicalDevice,
+                                                         mBackend->GetFunctions());
     }
 
     ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
diff --git a/src/dawn_native/vulkan/AdapterVk.h b/src/dawn_native/vulkan/AdapterVk.h
index 47679e8..9d98d2c 100644
--- a/src/dawn_native/vulkan/AdapterVk.h
+++ b/src/dawn_native/vulkan/AdapterVk.h
@@ -36,12 +36,12 @@
         VkPhysicalDevice GetPhysicalDevice() const;
         Backend* GetBackend() const;
 
-        MaybeError Initialize();
-
       private:
+        MaybeError InitializeImpl() override;
+        MaybeError InitializeSupportedFeaturesImpl() override;
+        MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
+
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
-        MaybeError CheckCoreWebGPUSupport();
-        void InitializeSupportedFeatures();
 
         VkPhysicalDevice mPhysicalDevice;
         Backend* mBackend;