dawn: Refactor device creation and add shader-f16 feature

This CL modifies the way adapter creating devices, adds `shader-f16`
feature, and deprecates the `dawn-shader-float16` feature which is no
longer used.
Details:
1. Parse the toggles chained with device descriptor in
`adapter::CreateDeviceInternal`, which are then used to validate
features requirement within `CreateDeviceInternal` and passed to device
constructor as initializer.
2. When creating device, validate features requirement in
`CreateDeviceInternal` with toggles known, make sure to fail the device
creation if a required feature is not supported by adapter or is guarded
by certain toggles which were not enabled/disabled. Feature ShaderF16
and ChromiumExperimentalDp4a are validated in this way. Unittest is
added to check creating devices with toggles-guarded features required.
3. Add `shader-f16` feature, which allow `using f16;` in WGSL code.
End-to-end tests are added to test a trival f16 WGSL shader could be
used if and only if the device has `shader-f16` feature.
4. Deprecate the `dawn-shader-float16` feature, which will be completely
removed after cleaning up Blink code.

Bug: dawn:1510
Change-Id: I6cb2dcbe1ee584fdd6131c62df1ee850b881dbd2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/100802
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/dawn.json b/dawn.json
index c842e79..d168b05 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1384,6 +1384,7 @@
             {"value": 6, "name": "texture compression ETC2"},
             {"value": 7, "name": "texture compression ASTC"},
             {"value": 8, "name": "indirect first instance"},
+            {"value": 9, "name": "shader f16"},
             {"value": 1001, "name": "dawn shader float 16", "tags": ["dawn"]},
             {"value": 1002, "name": "dawn internal usages", "tags": ["dawn"]},
             {"value": 1003, "name": "dawn multi planar formats", "tags": ["dawn"]},
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h
index 1d7bf42..b2ffa5c 100644
--- a/include/dawn/native/DawnNative.h
+++ b/include/dawn/native/DawnNative.h
@@ -61,7 +61,15 @@
 // A struct to record the information of a feature. A feature is a GPU feature that is not
 // required to be supported by all Dawn backends and can only be used when it is enabled on the
 // creation of device.
-using FeatureInfo = ToggleInfo;
+struct FeatureInfo {
+    const char* name;
+    const char* description;
+    const char* url;
+    // The enum of feature state, could be stable or experimental. Using an experimental feature
+    // requires DisallowUnsafeAPIs toggle being disabled.
+    enum class FeatureState { Stable = 0, Experimental };
+    FeatureState featureState;
+};
 
 // An adapter is an object that represent on possibility of creating devices in the system.
 // Most of the time it will represent a combination of a physical GPU and an API. Not that the
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 7415312..0234e2c 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -19,6 +19,7 @@
 
 #include "dawn/common/Constants.h"
 #include "dawn/common/GPUInfo.h"
+#include "dawn/native/ChainUtils_autogen.h"
 #include "dawn/native/Device.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/ValidationUtils_autogen.h"
@@ -189,15 +190,40 @@
     return true;
 }
 
+MaybeError AdapterBase::ValidateFeatureSupportedWithToggles(
+    wgpu::FeatureName feature,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    DAWN_TRY(ValidateFeatureName(feature));
+    DAWN_INVALID_IF(!mSupportedFeatures.IsEnabled(feature),
+                    "Requested feature %s is not supported.", feature);
+
+    const FeatureInfo* featureInfo = GetInstance()->GetFeatureInfo(feature);
+    // Experimental features are guarded by toggle DisallowUnsafeAPIs.
+    if (featureInfo->featureState == FeatureInfo::FeatureState::Experimental) {
+        DAWN_INVALID_IF(!userProvidedToggles.IsDisabled(Toggle::DisallowUnsafeAPIs),
+                        "Feature %s is guarded by toggle disallow_unsafe_apis.", featureInfo->name);
+    }
+
+    // Do backend-specific validation.
+    return ValidateFeatureSupportedWithTogglesImpl(feature, userProvidedToggles);
+}
+
 ResultOrError<Ref<DeviceBase>> AdapterBase::CreateDeviceInternal(
     const DeviceDescriptor* descriptor) {
     ASSERT(descriptor != nullptr);
 
+    // Check overriden toggles before creating device, as some device features may be guarded by
+    // toggles, and requiring such features without using corresponding toggles should fails the
+    // device creating.
+    const DawnTogglesDeviceDescriptor* togglesDesc = nullptr;
+    FindInChain(descriptor->nextInChain, &togglesDesc);
+    TripleStateTogglesSet userProvidedToggles =
+        TripleStateTogglesSet::CreateFromTogglesDeviceDescriptor(togglesDesc);
+
+    // Validate all required features are supported by the adapter and suitable under given toggles.
     for (uint32_t i = 0; i < descriptor->requiredFeaturesCount; ++i) {
-        wgpu::FeatureName f = descriptor->requiredFeatures[i];
-        DAWN_TRY(ValidateFeatureName(f));
-        DAWN_INVALID_IF(!mSupportedFeatures.IsEnabled(f), "Requested feature %s is not supported.",
-                        f);
+        wgpu::FeatureName feature = descriptor->requiredFeatures[i];
+        DAWN_TRY(ValidateFeatureSupportedWithToggles(feature, userProvidedToggles));
     }
 
     if (descriptor->requiredLimits != nullptr) {
@@ -208,7 +234,7 @@
         DAWN_INVALID_IF(descriptor->requiredLimits->nextInChain != nullptr,
                         "nextInChain is not nullptr.");
     }
-    return CreateDeviceImpl(descriptor);
+    return CreateDeviceImpl(descriptor, userProvidedToggles);
 }
 
 void AdapterBase::SetUseTieredLimits(bool useTieredLimits) {
diff --git a/src/dawn/native/Adapter.h b/src/dawn/native/Adapter.h
index 6b6448f..8bef321 100644
--- a/src/dawn/native/Adapter.h
+++ b/src/dawn/native/Adapter.h
@@ -24,6 +24,7 @@
 #include "dawn/native/Error.h"
 #include "dawn/native/Features.h"
 #include "dawn/native/Limits.h"
+#include "dawn/native/Toggles.h"
 #include "dawn/native/dawn_platform.h"
 
 namespace dawn::native {
@@ -72,14 +73,24 @@
     std::string mName;
     wgpu::AdapterType mAdapterType = wgpu::AdapterType::Unknown;
     std::string mDriverDescription;
+
+    // Features set that CAN be supported by devices of this adapter. Some features in this set may
+    // be guarded by toggles, and creating a device with these features required may result in a
+    // validation error if proper toggles are not enabled/disabled.
     FeaturesSet mSupportedFeatures;
+    // Check if a feature os supported by this adapter AND suitable with given toggles.
+    MaybeError ValidateFeatureSupportedWithToggles(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles);
 
   private:
-    virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) = 0;
+    virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) = 0;
 
     virtual MaybeError InitializeImpl() = 0;
 
-    // Check base WebGPU features and discover supported featurees.
+    // Check base WebGPU features and discover supported features.
     virtual MaybeError InitializeSupportedFeaturesImpl() = 0;
 
     // Check base WebGPU limits and populate supported limits.
@@ -87,6 +98,10 @@
 
     virtual void InitializeVendorArchitectureImpl();
 
+    virtual MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) = 0;
+
     ResultOrError<Ref<DeviceBase>> CreateDeviceInternal(const DeviceDescriptor* descriptor);
 
     virtual MaybeError ResetInternalDeviceForTestingImpl();
diff --git a/src/dawn/native/CopyTextureForBrowserHelper.cpp b/src/dawn/native/CopyTextureForBrowserHelper.cpp
index d7890ed..6f89842 100644
--- a/src/dawn/native/CopyTextureForBrowserHelper.cpp
+++ b/src/dawn/native/CopyTextureForBrowserHelper.cpp
@@ -364,7 +364,7 @@
         source->texture->GetSampleCount(), destination->texture->GetSampleCount());
 
     DAWN_INVALID_IF(
-        options->internalUsage && !device->IsFeatureEnabled(Feature::DawnInternalUsages),
+        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
         "The internalUsage is true while the dawn-internal-usages feature is not enabled.");
     UsageValidationMode mode =
         options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 65e79c1..8112f64 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -170,20 +170,19 @@
 
 // DeviceBase
 
-DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor)
-    : mAdapter(adapter), mNextPipelineCompatibilityToken(1) {
+DeviceBase::DeviceBase(AdapterBase* adapter,
+                       const DeviceDescriptor* descriptor,
+                       const TripleStateTogglesSet& userProvidedToggles)
+    : mAdapter(adapter),
+      mEnabledToggles(userProvidedToggles.providedTogglesEnabled),
+      mOverridenToggles(userProvidedToggles.togglesIsProvided),
+      mNextPipelineCompatibilityToken(1) {
     mAdapter->GetInstance()->IncrementDeviceCountForTesting();
     ASSERT(descriptor != nullptr);
 
     AdapterProperties adapterProperties;
     adapter->APIGetProperties(&adapterProperties);
 
-    const DawnTogglesDeviceDescriptor* togglesDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &togglesDesc);
-    if (togglesDesc != nullptr) {
-        ApplyToggleOverrides(togglesDesc);
-    }
-
     SetDefaultToggles();
     ApplyFeatures(descriptor);
 
@@ -1323,17 +1322,19 @@
     }
 }
 
-bool DeviceBase::IsFeatureEnabled(Feature feature) const {
+bool DeviceBase::HasFeature(Feature feature) const {
     return mEnabledFeatures.IsEnabled(feature);
 }
 
 void DeviceBase::SetWGSLExtensionAllowList() {
     // Set the WGSL extensions allow list based on device's enabled features and other
-    // propority. For example:
-    //     mWGSLExtensionAllowList.insert("InternalExtensionForTesting");
-    if (IsFeatureEnabled(Feature::ChromiumExperimentalDp4a)) {
+    // propority.
+    if (mEnabledFeatures.IsEnabled(Feature::ChromiumExperimentalDp4a)) {
         mWGSLExtensionAllowList.insert("chromium_experimental_dp4a");
     }
+    if (mEnabledFeatures.IsEnabled(Feature::ShaderF16)) {
+        mWGSLExtensionAllowList.insert("f16");
+    }
 }
 
 WGSLExtensionSet DeviceBase::GetWGSLExtensionAllowList() const {
@@ -1800,27 +1801,6 @@
     SetToggle(Toggle::DisallowUnsafeAPIs, true);
 }
 
-void DeviceBase::ApplyToggleOverrides(const DawnTogglesDeviceDescriptor* togglesDescriptor) {
-    ASSERT(togglesDescriptor != nullptr);
-
-    for (uint32_t i = 0; i < togglesDescriptor->forceEnabledTogglesCount; ++i) {
-        Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum(
-            togglesDescriptor->forceEnabledToggles[i]);
-        if (toggle != Toggle::InvalidEnum) {
-            mEnabledToggles.Set(toggle, true);
-            mOverridenToggles.Set(toggle, true);
-        }
-    }
-    for (uint32_t i = 0; i < togglesDescriptor->forceDisabledTogglesCount; ++i) {
-        Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum(
-            togglesDescriptor->forceDisabledToggles[i]);
-        if (toggle != Toggle::InvalidEnum) {
-            mEnabledToggles.Set(toggle, false);
-            mOverridenToggles.Set(toggle, true);
-        }
-    }
-}
-
 void DeviceBase::FlushCallbackTaskQueue() {
     if (!mCallbackTaskManager->IsEmpty()) {
         // If a user calls Queue::Submit inside the callback, then the device will be ticked,
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 42bc77b..4fbe874 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -62,7 +62,9 @@
 
 class DeviceBase : public RefCountedWithExternalCount {
   public:
-    DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor);
+    DeviceBase(AdapterBase* adapter,
+               const DeviceDescriptor* descriptor,
+               const TripleStateTogglesSet& userProvidedToggles);
     ~DeviceBase() override;
 
     // Handles the error, causing a device loss if applicable. Almost always when a device loss
@@ -279,11 +281,7 @@
     QueueBase* APIGetQueue();
 
     bool APIGetLimits(SupportedLimits* limits) const;
-    // Note that we should not use this function to query the features which can only be enabled
-    // behind toggles (use IsFeatureEnabled() instead).
     bool APIHasFeature(wgpu::FeatureName feature) const;
-    // Note that we should not use this function to query the features which can only be enabled
-    // behind toggles (use IsFeatureEnabled() instead).
     size_t APIEnumerateFeatures(wgpu::FeatureName* features) const;
     void APIInjectError(wgpu::ErrorType type, const char* message);
     bool APITick();
@@ -381,9 +379,7 @@
     virtual bool ShouldDuplicateParametersForDrawIndirect(
         const RenderPipelineBase* renderPipelineBase) const;
 
-    // TODO(crbug.com/dawn/1434): Make this function non-overridable when we support requesting
-    // Adapter with toggles.
-    virtual bool IsFeatureEnabled(Feature feature) const;
+    bool HasFeature(Feature feature) const;
 
     const CombinedLimits& GetLimits() const;
 
@@ -482,7 +478,6 @@
                                                    WGPUCreateRenderPipelineAsyncCallback callback,
                                                    void* userdata);
 
-    void ApplyToggleOverrides(const DawnTogglesDeviceDescriptor* togglesDescriptor);
     void ApplyFeatures(const DeviceDescriptor* deviceDescriptor);
 
     void SetDefaultToggles();
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index d375b20..4589f1c 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -34,58 +34,64 @@
 static constexpr FeatureEnumAndInfoList kFeatureNameAndInfoList = {{
     {Feature::TextureCompressionBC,
      {"texture-compression-bc", "Support Block Compressed (BC) texture formats",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=42"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=42", FeatureInfo::FeatureState::Stable}},
     {Feature::TextureCompressionETC2,
      {"texture-compression-etc2",
       "Support Ericsson Texture Compressed (ETC2/EAC) texture "
       "formats",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=955"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=955", FeatureInfo::FeatureState::Stable}},
     {Feature::TextureCompressionASTC,
      {"texture-compression-astc",
       "Support Adaptable Scalable Texture Compressed (ASTC) "
       "texture formats",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=955"}},
-    {Feature::ShaderFloat16,
-     {"shader-float16",
-      "Support 16bit float arithmetic and declarations in uniform and storage buffers",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=426"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=955", FeatureInfo::FeatureState::Stable}},
     {Feature::PipelineStatisticsQuery,
      {"pipeline-statistics-query", "Support Pipeline Statistics Query",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=434"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=434", FeatureInfo::FeatureState::Stable}},
     {Feature::TimestampQuery,
      {"timestamp-query", "Support Timestamp Query",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=434"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=434", FeatureInfo::FeatureState::Stable}},
     {Feature::DepthClipControl,
      {"depth-clip-control", "Disable depth clipping of primitives to the clip volume",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=1178"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=1178", FeatureInfo::FeatureState::Stable}},
     {Feature::Depth32FloatStencil8,
      {"depth32float-stencil8", "Support depth32float-stencil8 texture format",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=690"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=690", FeatureInfo::FeatureState::Stable}},
     {Feature::ChromiumExperimentalDp4a,
      {"chromium-experimental-dp4a", "Support experimental DP4a instructions in WGSL",
-      "https://bugs.chromium.org/p/tint/issues/detail?id=1497"}},
+      "https://bugs.chromium.org/p/tint/issues/detail?id=1497",
+      FeatureInfo::FeatureState::Experimental}},
     {Feature::IndirectFirstInstance,
      {"indirect-first-instance", "Support non-zero first instance values on indirect draw calls",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=1197"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=1197", FeatureInfo::FeatureState::Stable}},
+    {Feature::ShaderF16,
+     {"shader-f16", "Supports the \"enable f16;\" directive in WGSL",
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=1510",
+      FeatureInfo::FeatureState::Experimental}},
     {Feature::DawnInternalUsages,
      {"dawn-internal-usages",
       "Add internal usages to resources to affect how the texture is allocated, but not "
       "frontend validation. Other internal commands may access this usage.",
       "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
-      "dawn_internal_usages.md"}},
+      "dawn_internal_usages.md",
+      FeatureInfo::FeatureState::Stable}},
     {Feature::MultiPlanarFormats,
      {"multiplanar-formats", "Import and use multi-planar texture formats with per plane views",
-      "https://bugs.chromium.org/p/dawn/issues/detail?id=551"}},
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=551", FeatureInfo::FeatureState::Stable}},
     {Feature::DawnNative,
      {"dawn-native", "WebGPU is running on top of dawn_native.",
       "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
-      "dawn_native.md"}},
+      "dawn_native.md",
+      FeatureInfo::FeatureState::Stable}},
 }};
 
 Feature FromAPIFeature(wgpu::FeatureName feature) {
     switch (feature) {
         case wgpu::FeatureName::Undefined:
             return Feature::InvalidEnum;
+        case wgpu::FeatureName::DawnShaderFloat16:
+            // Deprecated.
+            return Feature::InvalidEnum;
 
         case wgpu::FeatureName::TimestampQuery:
             return Feature::TimestampQuery;
@@ -103,8 +109,6 @@
             return Feature::Depth32FloatStencil8;
         case wgpu::FeatureName::IndirectFirstInstance:
             return Feature::IndirectFirstInstance;
-        case wgpu::FeatureName::DawnShaderFloat16:
-            return Feature::ShaderFloat16;
         case wgpu::FeatureName::DawnInternalUsages:
             return Feature::DawnInternalUsages;
         case wgpu::FeatureName::DawnMultiPlanarFormats:
@@ -113,6 +117,8 @@
             return Feature::DawnNative;
         case wgpu::FeatureName::ChromiumExperimentalDp4a:
             return Feature::ChromiumExperimentalDp4a;
+        case wgpu::FeatureName::ShaderF16:
+            return Feature::ShaderF16;
     }
     return Feature::InvalidEnum;
 }
@@ -135,8 +141,6 @@
             return wgpu::FeatureName::Depth32FloatStencil8;
         case Feature::IndirectFirstInstance:
             return wgpu::FeatureName::IndirectFirstInstance;
-        case Feature::ShaderFloat16:
-            return wgpu::FeatureName::DawnShaderFloat16;
         case Feature::DawnInternalUsages:
             return wgpu::FeatureName::DawnInternalUsages;
         case Feature::MultiPlanarFormats:
@@ -145,6 +149,8 @@
             return wgpu::FeatureName::DawnNative;
         case Feature::ChromiumExperimentalDp4a:
             return wgpu::FeatureName::ChromiumExperimentalDp4a;
+        case Feature::ShaderF16:
+            return wgpu::FeatureName::ShaderF16;
 
         case Feature::EnumCount:
             break;
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h
index f97fd9d..9413da6 100644
--- a/src/dawn/native/Features.h
+++ b/src/dawn/native/Features.h
@@ -30,13 +30,13 @@
     TextureCompressionBC,
     TextureCompressionETC2,
     TextureCompressionASTC,
-    ShaderFloat16,
     PipelineStatisticsQuery,
     TimestampQuery,
     DepthClipControl,
     Depth32FloatStencil8,
     ChromiumExperimentalDp4a,
     IndirectFirstInstance,
+    ShaderF16,
 
     // Dawn-specific
     DawnInternalUsages,
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index 11de9ac..ef9a7d2 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -389,12 +389,12 @@
         AddMultiAspectFormat(wgpu::TextureFormat::Depth24PlusStencil8,
                               Aspect::Depth | Aspect::Stencil, wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Stencil8, true, true, true, 2);
         AddDepthFormat(wgpu::TextureFormat::Depth32Float, 4, true);
-        bool isD32S8Supported = device->IsFeatureEnabled(Feature::Depth32FloatStencil8);
+        bool isD32S8Supported = device->HasFeature(Feature::Depth32FloatStencil8);
         AddMultiAspectFormat(wgpu::TextureFormat::Depth32FloatStencil8,
                               Aspect::Depth | Aspect::Stencil, wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Stencil8, true, isD32S8Supported, true, 2);
 
         // BC compressed formats
-        bool isBCFormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionBC);
+        bool isBCFormatSupported = device->HasFeature(Feature::TextureCompressionBC);
         AddCompressedFormat(wgpu::TextureFormat::BC1RGBAUnorm, 8, 4, 4, isBCFormatSupported, 4);
         AddCompressedFormat(wgpu::TextureFormat::BC1RGBAUnormSrgb, 8, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC1RGBAUnorm);
         AddCompressedFormat(wgpu::TextureFormat::BC4RSnorm, 8, 4, 4, isBCFormatSupported, 1);
@@ -411,7 +411,7 @@
         AddCompressedFormat(wgpu::TextureFormat::BC7RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported, 4, wgpu::TextureFormat::BC7RGBAUnorm);
 
         // ETC2/EAC compressed formats
-        bool isETC2FormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionETC2);
+        bool isETC2FormatSupported = device->HasFeature(Feature::TextureCompressionETC2);
         AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8Unorm, 8, 4, 4, isETC2FormatSupported, 3);
         AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8UnormSrgb, 8, 4, 4, isETC2FormatSupported, 3, wgpu::TextureFormat::ETC2RGB8Unorm);
         AddCompressedFormat(wgpu::TextureFormat::ETC2RGB8A1Unorm, 8, 4, 4, isETC2FormatSupported, 4);
@@ -424,7 +424,7 @@
         AddCompressedFormat(wgpu::TextureFormat::EACRG11Snorm, 16, 4, 4, isETC2FormatSupported, 2);
 
         // ASTC compressed formats
-        bool isASTCFormatSupported = device->IsFeatureEnabled(Feature::TextureCompressionASTC);
+        bool isASTCFormatSupported = device->HasFeature(Feature::TextureCompressionASTC);
         AddCompressedFormat(wgpu::TextureFormat::ASTC4x4Unorm, 16, 4, 4, isASTCFormatSupported, 4);
         AddCompressedFormat(wgpu::TextureFormat::ASTC4x4UnormSrgb, 16, 4, 4, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC4x4Unorm);
         AddCompressedFormat(wgpu::TextureFormat::ASTC5x4Unorm, 16, 5, 4, isASTCFormatSupported, 4);
@@ -455,7 +455,7 @@
         AddCompressedFormat(wgpu::TextureFormat::ASTC12x12UnormSrgb, 16, 12, 12, isASTCFormatSupported, 4, wgpu::TextureFormat::ASTC12x12Unorm);
 
         // multi-planar formats
-        const bool isMultiPlanarFormatSupported = device->IsFeatureEnabled(Feature::MultiPlanarFormats);
+        const bool isMultiPlanarFormatSupported = device->HasFeature(Feature::MultiPlanarFormats);
         AddMultiAspectFormat(wgpu::TextureFormat::R8BG8Biplanar420Unorm, Aspect::Plane0 | Aspect::Plane1,
             wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::RG8Unorm, false, isMultiPlanarFormatSupported, false, 3);
 
diff --git a/src/dawn/native/IndirectDrawValidationEncoder.cpp b/src/dawn/native/IndirectDrawValidationEncoder.cpp
index abd09cd..db940e7 100644
--- a/src/dawn/native/IndirectDrawValidationEncoder.cpp
+++ b/src/dawn/native/IndirectDrawValidationEncoder.cpp
@@ -338,7 +338,7 @@
             if (device->IsValidationEnabled()) {
                 newPass.flags |= kValidationEnabled;
             }
-            if (device->IsFeatureEnabled(Feature::IndirectFirstInstance)) {
+            if (device->HasFeature(Feature::IndirectFirstInstance)) {
                 newPass.flags |= kIndirectFirstInstanceEnabled;
             }
             passes.push_back(std::move(newPass));
diff --git a/src/dawn/native/QuerySet.cpp b/src/dawn/native/QuerySet.cpp
index a503165..ad75bed 100644
--- a/src/dawn/native/QuerySet.cpp
+++ b/src/dawn/native/QuerySet.cpp
@@ -60,7 +60,7 @@
                             "fully implemented");
 
             DAWN_INVALID_IF(
-                !device->IsFeatureEnabled(Feature::PipelineStatisticsQuery),
+                !device->HasFeature(Feature::PipelineStatisticsQuery),
                 "Pipeline statistics query set created without the feature being enabled.");
 
             DAWN_INVALID_IF(descriptor->pipelineStatisticsCount == 0,
@@ -82,7 +82,7 @@
                             "Timestamp queries are disallowed because they may expose precise "
                             "timing information.");
 
-            DAWN_INVALID_IF(!device->IsFeatureEnabled(Feature::TimestampQuery),
+            DAWN_INVALID_IF(!device->HasFeature(Feature::TimestampQuery),
                             "Timestamp query set created without the feature being enabled.");
 
             DAWN_INVALID_IF(descriptor->pipelineStatisticsCount != 0,
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 8a3d82a..03b90bb 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -156,7 +156,7 @@
     DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, wgpu::SType::PrimitiveDepthClipControl));
     const PrimitiveDepthClipControl* depthClipControl = nullptr;
     FindInChain(descriptor->nextInChain, &depthClipControl);
-    DAWN_INVALID_IF(depthClipControl && !device->IsFeatureEnabled(Feature::DepthClipControl),
+    DAWN_INVALID_IF(depthClipControl && !device->HasFeature(Feature::DepthClipControl),
                     "%s is not supported", wgpu::FeatureName::DepthClipControl);
     DAWN_TRY(ValidatePrimitiveTopology(descriptor->topology));
     DAWN_TRY(ValidateIndexFormat(descriptor->stripIndexFormat));
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 7efbbf8..5ddbbc3 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -339,7 +339,7 @@
     FindInChain(descriptor->nextInChain, &internalUsageDesc);
 
     DAWN_INVALID_IF(
-        internalUsageDesc != nullptr && !device->IsFeatureEnabled(Feature::DawnInternalUsages),
+        internalUsageDesc != nullptr && !device->HasFeature(Feature::DawnInternalUsages),
         "The internalUsageDesc is not empty while the dawn-internal-usages feature is not enabled");
 
     const Format* format;
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index 8fbf0e3..3816f20 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -17,6 +17,7 @@
 #include "dawn/common/Assert.h"
 #include "dawn/common/BitSetIterator.h"
 #include "dawn/native/Toggles.h"
+#include "dawn/native/dawn_platform.h"
 
 namespace dawn::native {
 namespace {
@@ -334,6 +335,81 @@
     return togglesNameInUse;
 }
 
+TripleStateTogglesSet TripleStateTogglesSet::CreateFromTogglesDeviceDescriptor(
+    const DawnTogglesDeviceDescriptor* togglesDesc) {
+    TripleStateTogglesSet userToggles;
+    if (togglesDesc != nullptr) {
+        TogglesInfo togglesInfo;
+        for (uint32_t i = 0; i < togglesDesc->forceEnabledTogglesCount; ++i) {
+            Toggle toggle = togglesInfo.ToggleNameToEnum(togglesDesc->forceEnabledToggles[i]);
+            if (toggle != Toggle::InvalidEnum) {
+                userToggles.togglesIsProvided.Set(toggle, true);
+                userToggles.providedTogglesEnabled.Set(toggle, true);
+            }
+        }
+        for (uint32_t i = 0; i < togglesDesc->forceDisabledTogglesCount; ++i) {
+            Toggle toggle = togglesInfo.ToggleNameToEnum(togglesDesc->forceDisabledToggles[i]);
+            if (toggle != Toggle::InvalidEnum) {
+                userToggles.togglesIsProvided.Set(toggle, true);
+                userToggles.providedTogglesEnabled.Set(toggle, false);
+            }
+        }
+    }
+    return userToggles;
+}
+
+void TripleStateTogglesSet::Set(Toggle toggle, bool enabled) {
+    ASSERT(toggle != Toggle::InvalidEnum);
+    togglesIsProvided.Set(toggle, true);
+    providedTogglesEnabled.Set(toggle, enabled);
+}
+
+bool TripleStateTogglesSet::IsProvided(Toggle toggle) const {
+    return togglesIsProvided.Has(toggle);
+}
+// Return true if the toggle is provided in enable list, and false otherwise.
+bool TripleStateTogglesSet::IsEnabled(Toggle toggle) const {
+    return togglesIsProvided.Has(toggle) && providedTogglesEnabled.Has(toggle);
+}
+// Return true if the toggle is provided in disable list, and false otherwise.
+bool TripleStateTogglesSet::IsDisabled(Toggle toggle) const {
+    return togglesIsProvided.Has(toggle) && !providedTogglesEnabled.Has(toggle);
+}
+
+std::vector<const char*> TripleStateTogglesSet::GetEnabledToggleNames() const {
+    std::vector<const char*> enabledTogglesName(providedTogglesEnabled.toggleBitset.count());
+
+    uint32_t index = 0;
+    for (uint32_t i : IterateBitSet(providedTogglesEnabled.toggleBitset)) {
+        const Toggle& toggle = static_cast<Toggle>(i);
+        // All enabled toggles must be provided.
+        ASSERT(togglesIsProvided.Has(toggle));
+        const char* toggleName = ToggleEnumToName(toggle);
+        enabledTogglesName[index] = toggleName;
+        ++index;
+    }
+
+    return enabledTogglesName;
+}
+
+std::vector<const char*> TripleStateTogglesSet::GetDisabledToggleNames() const {
+    std::vector<const char*> enabledTogglesName(togglesIsProvided.toggleBitset.count() -
+                                                providedTogglesEnabled.toggleBitset.count());
+
+    uint32_t index = 0;
+    for (uint32_t i : IterateBitSet(togglesIsProvided.toggleBitset)) {
+        const Toggle& toggle = static_cast<Toggle>(i);
+        // Disabled toggles are those provided but not enabled.
+        if (!providedTogglesEnabled.Has(toggle)) {
+            const char* toggleName = ToggleEnumToName(toggle);
+            enabledTogglesName[index] = toggleName;
+            ++index;
+        }
+    }
+
+    return enabledTogglesName;
+}
+
 const char* ToggleEnumToName(Toggle toggle) {
     ASSERT(toggle != Toggle::InvalidEnum);
 
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index 5e000f9..981aa94 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -24,6 +24,8 @@
 
 namespace dawn::native {
 
+struct DawnTogglesDeviceDescriptor;
+
 enum class Toggle {
     EmulateStoreAndMSAAResolve,
     NonzeroClearResourcesOnCreationForTesting,
@@ -92,6 +94,27 @@
     std::vector<const char*> GetContainedToggleNames() const;
 };
 
+// TripleStateTogglesSet track each toggle with three posible states, i.e. "Not provided" (default),
+// "Provided as enabled", and "Provided as disabled". This struct can be used to record the
+// user-provided toggles, where some toggles are explicitly enabled or disabled while the other
+// toggles are left as default.
+struct TripleStateTogglesSet {
+    TogglesSet togglesIsProvided;
+    TogglesSet providedTogglesEnabled;
+
+    static TripleStateTogglesSet CreateFromTogglesDeviceDescriptor(
+        const DawnTogglesDeviceDescriptor* togglesDesc);
+    // Provide a single toggle with given state.
+    void Set(Toggle toggle, bool enabled);
+    bool IsProvided(Toggle toggle) const;
+    // Return true if the toggle is provided in enable list, and false otherwise.
+    bool IsEnabled(Toggle toggle) const;
+    // Return true if the toggle is provided in disable list, and false otherwise.
+    bool IsDisabled(Toggle toggle) const;
+    std::vector<const char*> GetEnabledToggleNames() const;
+    std::vector<const char*> GetDisabledToggleNames() const;
+};
+
 const char* ToggleEnumToName(Toggle toggle);
 
 class TogglesInfo {
diff --git a/src/dawn/native/d3d12/AdapterD3D12.cpp b/src/dawn/native/d3d12/AdapterD3D12.cpp
index e23830e..bcd40fc 100644
--- a/src/dawn/native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn/native/d3d12/AdapterD3D12.cpp
@@ -147,6 +147,9 @@
             dxcVersion >= MakeDXCVersion(kLeastMajorVersionForDP4a, kLeastMinorVersionForDP4a)) {
             mSupportedFeatures.EnableFeature(Feature::ChromiumExperimentalDp4a);
         }
+        if (mDeviceInfo.supportsShaderF16) {
+            mSupportedFeatures.EnableFeature(Feature::ShaderF16);
+        }
     }
 
     return {};
@@ -312,6 +315,20 @@
     return {};
 }
 
+MaybeError Adapter::ValidateFeatureSupportedWithTogglesImpl(
+    wgpu::FeatureName feature,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    // shader-f16 feature and chromium-experimental-dp4a feature require DXC for D3D12.
+    if (feature == wgpu::FeatureName::ShaderF16 ||
+        feature == wgpu::FeatureName::ChromiumExperimentalDp4a) {
+        DAWN_INVALID_IF(!(userProvidedToggles.IsEnabled(Toggle::UseDXC) &&
+                          mBackend->GetFunctions()->IsDXCAvailable()),
+                        "Feature %s requires DXC for D3D12.",
+                        GetInstance()->GetFeatureInfo(feature)->name);
+    }
+    return {};
+}
+
 MaybeError Adapter::InitializeDebugLayerFilters() {
     if (!GetInstance()->IsBackendValidationEnabled()) {
         return {};
@@ -418,8 +435,10 @@
     infoQueue->PopStorageFilter();
 }
 
-ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
-    return Device::Create(this, descriptor);
+ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(
+    const DeviceDescriptor* descriptor,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return Device::Create(this, descriptor, userProvidedToggles);
 }
 
 // Resets the backend device and creates a new one. If any D3D12 objects belonging to the
diff --git a/src/dawn/native/d3d12/AdapterD3D12.h b/src/dawn/native/d3d12/AdapterD3D12.h
index 035e291..2c8a377 100644
--- a/src/dawn/native/d3d12/AdapterD3D12.h
+++ b/src/dawn/native/d3d12/AdapterD3D12.h
@@ -40,7 +40,9 @@
     const gpu_info::D3DDriverVersion& GetDriverVersion() const;
 
   private:
-    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) override;
     MaybeError ResetInternalDeviceForTestingImpl() override;
 
     bool AreTimestampQueriesSupported() const;
@@ -49,6 +51,10 @@
     MaybeError InitializeSupportedFeaturesImpl() override;
     MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
 
+    MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) override;
+
     MaybeError InitializeDebugLayerFilters();
     void CleanUpDebugLayerFilters();
 
diff --git a/src/dawn/native/d3d12/D3D12Info.cpp b/src/dawn/native/d3d12/D3D12Info.cpp
index 3d3470c..d6d8dde 100644
--- a/src/dawn/native/d3d12/D3D12Info.cpp
+++ b/src/dawn/native/d3d12/D3D12Info.cpp
@@ -121,7 +121,7 @@
     info.shaderProfiles[SingleShaderStage::Fragment] = L"p" + profileSuffix;
     info.shaderProfiles[SingleShaderStage::Compute] = L"c" + profileSuffix;
 
-    info.supportsShaderFloat16 =
+    info.supportsShaderF16 =
         driverShaderModel >= D3D_SHADER_MODEL_6_2 && featureOptions4.Native16BitShaderOpsSupported;
 
     info.supportsDP4a = driverShaderModel >= D3D_SHADER_MODEL_6_4;
diff --git a/src/dawn/native/d3d12/D3D12Info.h b/src/dawn/native/d3d12/D3D12Info.h
index f81e28d..af0b633 100644
--- a/src/dawn/native/d3d12/D3D12Info.h
+++ b/src/dawn/native/d3d12/D3D12Info.h
@@ -27,7 +27,7 @@
     bool isUMA;
     uint32_t resourceHeapTier;
     bool supportsRenderPass;
-    bool supportsShaderFloat16;
+    bool supportsShaderF16;
     // shaderModel indicates the maximum supported shader model, for example, the value 62
     // indicates that current driver supports the maximum shader model is shader model 6.2.
     uint32_t shaderModel;
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 2a6c97f..4a6a1bd 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -63,8 +63,10 @@
 static constexpr uint64_t kMaxDebugMessagesToPrint = 5;
 
 // static
-ResultOrError<Ref<Device>> Device::Create(Adapter* adapter, const DeviceDescriptor* descriptor) {
-    Ref<Device> device = AcquireRef(new Device(adapter, descriptor));
+ResultOrError<Ref<Device>> Device::Create(Adapter* adapter,
+                                          const DeviceDescriptor* descriptor,
+                                          const TripleStateTogglesSet& userProvidedToggles) {
+    Ref<Device> device = AcquireRef(new Device(adapter, descriptor, userProvidedToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
@@ -84,7 +86,7 @@
         CheckHRESULT(mD3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)),
                      "D3D12 create command queue"));
 
-    if (IsFeatureEnabled(Feature::TimestampQuery) &&
+    if (HasFeature(Feature::TimestampQuery) &&
         !IsToggleEnabled(Toggle::DisableTimestampQueryConversion)) {
         // Get GPU timestamp counter frequency (in ticks/second). This fails if the specified
         // command queue doesn't support timestamps. D3D12_COMMAND_LIST_TYPE_DIRECT queues
@@ -876,17 +878,6 @@
     return ToBackend(computePipeline)->UsesNumWorkgroups();
 }
 
-bool Device::IsFeatureEnabled(Feature feature) const {
-    // Currently we can only use DXC to compile HLSL shaders using float16, and
-    // ChromiumExperimentalDp4a is an experimental feature which can only be enabled with toggle
-    // "use_dxc".
-    if ((feature == Feature::ChromiumExperimentalDp4a || feature == Feature::ShaderFloat16) &&
-        !IsToggleEnabled(Toggle::UseDXC)) {
-        return false;
-    }
-    return DeviceBase::IsFeatureEnabled(feature);
-}
-
 void Device::SetLabelImpl() {
     SetDebugName(this, mD3d12Device.Get(), "Dawn_Device", GetLabel());
 }
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index 49bc301..6b56d1a 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -46,7 +46,9 @@
 // Definition of backend types
 class Device final : public DeviceBase {
   public:
-    static ResultOrError<Ref<Device>> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
+    static ResultOrError<Ref<Device>> Create(Adapter* adapter,
+                                             const DeviceDescriptor* descriptor,
+                                             const TripleStateTogglesSet& userProvidedToggles);
     ~Device() override;
 
     MaybeError Initialize(const DeviceDescriptor* descriptor);
@@ -160,8 +162,6 @@
     bool ShouldDuplicateParametersForDrawIndirect(
         const RenderPipelineBase* renderPipelineBase) const override;
 
-    bool IsFeatureEnabled(Feature feature) const override;
-
     uint64_t GetBufferCopyOffsetAlignmentForDepthStencil() const override;
 
     // Dawn APIs
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
index bb0077a..f67167e 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
@@ -97,7 +97,7 @@
     X(bool, dumpShaders)
 
 #define D3D_BYTECODE_COMPILATION_REQUEST_MEMBERS(X) \
-    X(bool, hasShaderFloat16Feature)                \
+    X(bool, hasShaderF16Feature)                    \
     X(uint32_t, compileFlags)                       \
     X(Compiler, compiler)                           \
     X(uint64_t, compilerVersion)                    \
@@ -186,8 +186,7 @@
     std::wstring entryPointW;
     DAWN_TRY_ASSIGN(entryPointW, ConvertStringToWstring(entryPointName));
 
-    std::vector<const wchar_t*> arguments =
-        GetDXCArguments(r.compileFlags, r.hasShaderFloat16Feature);
+    std::vector<const wchar_t*> arguments = GetDXCArguments(r.compileFlags, r.hasShaderF16Feature);
 
     ComPtr<IDxcOperationResult> result;
     DAWN_TRY(CheckHRESULT(r.dxcCompiler->Compile(sourceBlob.Get(), nullptr, entryPointW.c_str(),
@@ -475,7 +474,7 @@
     req.hlsl.disableWorkgroupInit = device->IsToggleEnabled(Toggle::DisableWorkgroupInit);
     req.hlsl.dumpShaders = device->IsToggleEnabled(Toggle::DumpShaders);
 
-    req.bytecode.hasShaderFloat16Feature = device->IsFeatureEnabled(Feature::ShaderFloat16);
+    req.bytecode.hasShaderF16Feature = device->HasFeature(Feature::ShaderF16);
     req.bytecode.compileFlags = compileFlags;
 
     if (device->IsToggleEnabled(Toggle::UseDXC)) {
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index acba1a9..3ef6f3f 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -299,8 +299,10 @@
     }
 
   private:
-    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
-        return Device::Create(this, mDevice, descriptor);
+    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) override {
+        return Device::Create(this, mDevice, descriptor, userProvidedToggles);
     }
 
     MaybeError InitializeImpl() override { return {}; }
@@ -378,6 +380,8 @@
 
         mSupportedFeatures.EnableFeature(Feature::IndirectFirstInstance);
 
+        mSupportedFeatures.EnableFeature(Feature::ShaderF16);
+
         return {};
     }
 
@@ -620,6 +624,12 @@
         return {};
     }
 
+    MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) override {
+        return {};
+    }
+
     NSPRef<id<MTLDevice>> mDevice;
 };
 
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index 074140b..62d4f90 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -38,7 +38,8 @@
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
                                              NSPRef<id<MTLDevice>> mtlDevice,
-                                             const DeviceDescriptor* descriptor);
+                                             const DeviceDescriptor* descriptor,
+                                             const TripleStateTogglesSet& userProvidedToggles);
     ~Device() override;
 
     MaybeError Initialize(const DeviceDescriptor* descriptor);
@@ -74,7 +75,8 @@
   private:
     Device(AdapterBase* adapter,
            NSPRef<id<MTLDevice>> mtlDevice,
-           const DeviceDescriptor* descriptor);
+           const DeviceDescriptor* descriptor,
+           const TripleStateTogglesSet& userProvidedToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 4b589ea..a3fdac1 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -107,16 +107,21 @@
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
                                           NSPRef<id<MTLDevice>> mtlDevice,
-                                          const DeviceDescriptor* descriptor) {
-    Ref<Device> device = AcquireRef(new Device(adapter, std::move(mtlDevice), descriptor));
+                                          const DeviceDescriptor* descriptor,
+                                          const TripleStateTogglesSet& userProvidedToggles) {
+    Ref<Device> device =
+        AcquireRef(new Device(adapter, std::move(mtlDevice), descriptor, userProvidedToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
 
 Device::Device(AdapterBase* adapter,
                NSPRef<id<MTLDevice>> mtlDevice,
-               const DeviceDescriptor* descriptor)
-    : DeviceBase(adapter, descriptor), mMtlDevice(std::move(mtlDevice)), mCompletedSerial(0) {}
+               const DeviceDescriptor* descriptor,
+               const TripleStateTogglesSet& userProvidedToggles)
+    : DeviceBase(adapter, descriptor, userProvidedToggles),
+      mMtlDevice(std::move(mtlDevice)),
+      mCompletedSerial(0) {}
 
 Device::~Device() {
     Destroy();
@@ -132,7 +137,7 @@
 
     DAWN_TRY(mCommandContext.PrepareNextCommandBuffer(*mCommandQueue));
 
-    if (IsFeatureEnabled(Feature::TimestampQuery) &&
+    if (HasFeature(Feature::TimestampQuery) &&
         !IsToggleEnabled(Toggle::DisableTimestampQueryConversion)) {
         // Make a best guess of timestamp period based on device vendor info, and converge it to
         // an accurate value by the following calculations.
@@ -322,7 +327,7 @@
     DAWN_TRY(SubmitPendingCommandBuffer());
 
     // Just run timestamp period calculation when timestamp feature is enabled.
-    if (IsFeatureEnabled(Feature::TimestampQuery)) {
+    if (HasFeature(Feature::TimestampQuery)) {
         if (@available(macos 10.15, iOS 14.0, *)) {
             UpdateTimestampPeriod(GetMTLDevice(), mKalmanInfo.get(), &mCpuTimestamp, &mGpuTimestamp,
                                   &mTimestampPeriod);
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index a00515d..79d5f66 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -65,8 +65,16 @@
     return {};
 }
 
-ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
-    return Device::Create(this, descriptor);
+ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(
+    const DeviceDescriptor* descriptor,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return Device::Create(this, descriptor, userProvidedToggles);
+}
+
+MaybeError Adapter::ValidateFeatureSupportedWithTogglesImpl(
+    wgpu::FeatureName feature,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return {};
 }
 
 class Backend : public BackendConnection {
@@ -103,8 +111,10 @@
 // Device
 
 // static
-ResultOrError<Ref<Device>> Device::Create(Adapter* adapter, const DeviceDescriptor* descriptor) {
-    Ref<Device> device = AcquireRef(new Device(adapter, descriptor));
+ResultOrError<Ref<Device>> Device::Create(Adapter* adapter,
+                                          const DeviceDescriptor* descriptor,
+                                          const TripleStateTogglesSet& userProvidedToggles) {
+    Ref<Device> device = AcquireRef(new Device(adapter, descriptor, userProvidedToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 74da890..7c90c4e 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -89,7 +89,9 @@
 
 class Device final : public DeviceBase {
   public:
-    static ResultOrError<Ref<Device>> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
+    static ResultOrError<Ref<Device>> Create(Adapter* adapter,
+                                             const DeviceDescriptor* descriptor,
+                                             const TripleStateTogglesSet& userProvidedToggles);
     ~Device() override;
 
     MaybeError Initialize(const DeviceDescriptor* descriptor);
@@ -182,7 +184,13 @@
     MaybeError InitializeSupportedFeaturesImpl() override;
     MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
 
-    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) override;
+
+    MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) override;
 };
 
 // Helper class so |BindGroup| can allocate memory for its binding data,
diff --git a/src/dawn/native/opengl/AdapterGL.cpp b/src/dawn/native/opengl/AdapterGL.cpp
index 7a4f336..f309c61 100644
--- a/src/dawn/native/opengl/AdapterGL.cpp
+++ b/src/dawn/native/opengl/AdapterGL.cpp
@@ -141,6 +141,11 @@
         mSupportedFeatures.EnableFeature(Feature::IndirectFirstInstance);
     }
 
+    // ShaderF16
+    if (mFunctions.IsGLExtensionSupported("GL_AMD_gpu_shader_half_float")) {
+        mSupportedFeatures.EnableFeature(Feature::ShaderF16);
+    }
+
     return {};
 }
 
@@ -149,12 +154,20 @@
     return {};
 }
 
-ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
+ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(
+    const DeviceDescriptor* descriptor,
+    const TripleStateTogglesSet& userProvidedToggles) {
     EGLenum api =
         GetBackendType() == wgpu::BackendType::OpenGL ? EGL_OPENGL_API : EGL_OPENGL_ES_API;
     std::unique_ptr<Device::Context> context;
     DAWN_TRY_ASSIGN(context, ContextEGL::Create(mEGLFunctions, api));
-    return Device::Create(this, descriptor, mFunctions, std::move(context));
+    return Device::Create(this, descriptor, mFunctions, std::move(context), userProvidedToggles);
+}
+
+MaybeError Adapter::ValidateFeatureSupportedWithTogglesImpl(
+    wgpu::FeatureName feature,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return {};
 }
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/AdapterGL.h b/src/dawn/native/opengl/AdapterGL.h
index 6e354b2..4d6b0c1 100644
--- a/src/dawn/native/opengl/AdapterGL.h
+++ b/src/dawn/native/opengl/AdapterGL.h
@@ -36,7 +36,13 @@
     MaybeError InitializeImpl() override;
     MaybeError InitializeSupportedFeaturesImpl() override;
     MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
-    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) override;
+
+    MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) override;
 
     OpenGLFunctions mFunctions;
     EGLFunctions mEGLFunctions;
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index 4246d5f..8297573 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -108,8 +108,10 @@
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
                                           const DeviceDescriptor* descriptor,
                                           const OpenGLFunctions& functions,
-                                          std::unique_ptr<Context> context) {
-    Ref<Device> device = AcquireRef(new Device(adapter, descriptor, functions, std::move(context)));
+                                          std::unique_ptr<Context> context,
+                                          const TripleStateTogglesSet& userProvidedToggles) {
+    Ref<Device> device = AcquireRef(
+        new Device(adapter, descriptor, functions, std::move(context), userProvidedToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
@@ -117,8 +119,11 @@
 Device::Device(AdapterBase* adapter,
                const DeviceDescriptor* descriptor,
                const OpenGLFunctions& functions,
-               std::unique_ptr<Context> context)
-    : DeviceBase(adapter, descriptor), mGL(functions), mContext(std::move(context)) {}
+               std::unique_ptr<Context> context,
+               const TripleStateTogglesSet& userProvidedToggles)
+    : DeviceBase(adapter, descriptor, userProvidedToggles),
+      mGL(functions),
+      mContext(std::move(context)) {}
 
 Device::~Device() {
     Destroy();
diff --git a/src/dawn/native/opengl/DeviceGL.h b/src/dawn/native/opengl/DeviceGL.h
index 78abf11..08c776b 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -43,7 +43,8 @@
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
                                              const DeviceDescriptor* descriptor,
                                              const OpenGLFunctions& functions,
-                                             std::unique_ptr<Context> context);
+                                             std::unique_ptr<Context> context,
+                                             const TripleStateTogglesSet& userProvidedToggles);
     ~Device() override;
 
     MaybeError Initialize(const DeviceDescriptor* descriptor);
@@ -93,7 +94,8 @@
     Device(AdapterBase* adapter,
            const DeviceDescriptor* descriptor,
            const OpenGLFunctions& functions,
-           std::unique_ptr<Context> context);
+           std::unique_ptr<Context> context,
+           const TripleStateTogglesSet& userProvidedToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
diff --git a/src/dawn/native/vulkan/AdapterVk.cpp b/src/dawn/native/vulkan/AdapterVk.cpp
index 9ccc4d6..ab0342b 100644
--- a/src/dawn/native/vulkan/AdapterVk.cpp
+++ b/src/dawn/native/vulkan/AdapterVk.cpp
@@ -159,6 +159,15 @@
         mSupportedFeatures.EnableFeature(Feature::IndirectFirstInstance);
     }
 
+    if (mDeviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
+        mDeviceInfo.HasExt(DeviceExt::_16BitStorage) &&
+        mDeviceInfo.shaderFloat16Int8Features.shaderFloat16 == VK_TRUE &&
+        mDeviceInfo._16BitStorageFeatures.storageBuffer16BitAccess == VK_TRUE &&
+        mDeviceInfo._16BitStorageFeatures.storageInputOutput16 == VK_TRUE &&
+        mDeviceInfo._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE) {
+        mSupportedFeatures.EnableFeature(Feature::ShaderF16);
+    }
+
     if (mDeviceInfo.HasExt(DeviceExt::ShaderIntegerDotProduct) &&
         mDeviceInfo.shaderIntegerDotProductProperties
                 .integerDotProduct4x8BitPackedSignedAccelerated == VK_TRUE &&
@@ -354,8 +363,16 @@
                                                      mVulkanInstance->GetFunctions());
 }
 
-ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
-    return Device::Create(this, descriptor);
+ResultOrError<Ref<DeviceBase>> Adapter::CreateDeviceImpl(
+    const DeviceDescriptor* descriptor,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return Device::Create(this, descriptor, userProvidedToggles);
+}
+
+MaybeError Adapter::ValidateFeatureSupportedWithTogglesImpl(
+    wgpu::FeatureName feature,
+    const TripleStateTogglesSet& userProvidedToggles) {
+    return {};
 }
 
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/AdapterVk.h b/src/dawn/native/vulkan/AdapterVk.h
index 9cb5234..a7232fb 100644
--- a/src/dawn/native/vulkan/AdapterVk.h
+++ b/src/dawn/native/vulkan/AdapterVk.h
@@ -46,7 +46,13 @@
     MaybeError InitializeSupportedFeaturesImpl() override;
     MaybeError InitializeSupportedLimitsImpl(CombinedLimits* limits) override;
 
-    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+    ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        const DeviceDescriptor* descriptor,
+        const TripleStateTogglesSet& userProvidedToggles) override;
+
+    MaybeError ValidateFeatureSupportedWithTogglesImpl(
+        wgpu::FeatureName feature,
+        const TripleStateTogglesSet& userProvidedToggles) override;
 
     VkPhysicalDevice mPhysicalDevice;
     Ref<VulkanInstance> mVulkanInstance;
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 662fa5a..b9902ab 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -78,14 +78,19 @@
 }  // namespace
 
 // static
-ResultOrError<Ref<Device>> Device::Create(Adapter* adapter, const DeviceDescriptor* descriptor) {
-    Ref<Device> device = AcquireRef(new Device(adapter, descriptor));
+ResultOrError<Ref<Device>> Device::Create(Adapter* adapter,
+                                          const DeviceDescriptor* descriptor,
+                                          const TripleStateTogglesSet& userProvidedToggles) {
+    Ref<Device> device = AcquireRef(new Device(adapter, descriptor, userProvidedToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
 
-Device::Device(Adapter* adapter, const DeviceDescriptor* descriptor)
-    : DeviceBase(adapter, descriptor), mDebugPrefix(GetNextDeviceDebugPrefix()) {
+Device::Device(Adapter* adapter,
+               const DeviceDescriptor* descriptor,
+               const TripleStateTogglesSet& userProvidedToggles)
+    : DeviceBase(adapter, descriptor, userProvidedToggles),
+      mDebugPrefix(GetNextDeviceDebugPrefix()) {
     InitTogglesFromDriver();
 }
 
@@ -449,29 +454,29 @@
         usedKnobs.features.samplerAnisotropy = VK_TRUE;
     }
 
-    if (IsFeatureEnabled(Feature::TextureCompressionBC)) {
+    if (HasFeature(Feature::TextureCompressionBC)) {
         ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionBC == VK_TRUE);
         usedKnobs.features.textureCompressionBC = VK_TRUE;
     }
 
-    if (IsFeatureEnabled(Feature::TextureCompressionETC2)) {
+    if (HasFeature(Feature::TextureCompressionETC2)) {
         ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionETC2 == VK_TRUE);
         usedKnobs.features.textureCompressionETC2 = VK_TRUE;
     }
 
-    if (IsFeatureEnabled(Feature::TextureCompressionASTC)) {
+    if (HasFeature(Feature::TextureCompressionASTC)) {
         ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionASTC_LDR ==
                VK_TRUE);
         usedKnobs.features.textureCompressionASTC_LDR = VK_TRUE;
     }
 
-    if (IsFeatureEnabled(Feature::PipelineStatisticsQuery)) {
+    if (HasFeature(Feature::PipelineStatisticsQuery)) {
         ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.pipelineStatisticsQuery ==
                VK_TRUE);
         usedKnobs.features.pipelineStatisticsQuery = VK_TRUE;
     }
 
-    if (IsFeatureEnabled(Feature::DepthClipControl)) {
+    if (HasFeature(Feature::DepthClipControl)) {
         const VulkanDeviceInfo& deviceInfo = ToBackend(GetAdapter())->GetDeviceInfo();
         ASSERT(deviceInfo.HasExt(DeviceExt::DepthClipEnable) &&
                deviceInfo.depthClipEnableFeatures.depthClipEnable == VK_TRUE);
@@ -481,16 +486,20 @@
                           VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT);
     }
 
-    if (IsFeatureEnabled(Feature::ShaderFloat16)) {
+    // TODO(dawn:1510, tint:1473): After implementing a transform to handle the pipeline input /
+    // output if necessary, relax the requirement of storageInputOutput16.
+    if (HasFeature(Feature::ShaderF16)) {
         const VulkanDeviceInfo& deviceInfo = ToBackend(GetAdapter())->GetDeviceInfo();
         ASSERT(deviceInfo.HasExt(DeviceExt::ShaderFloat16Int8) &&
                deviceInfo.shaderFloat16Int8Features.shaderFloat16 == VK_TRUE &&
                deviceInfo.HasExt(DeviceExt::_16BitStorage) &&
                deviceInfo._16BitStorageFeatures.storageBuffer16BitAccess == VK_TRUE &&
+               deviceInfo._16BitStorageFeatures.storageInputOutput16 == VK_TRUE &&
                deviceInfo._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess == VK_TRUE);
 
         usedKnobs.shaderFloat16Int8Features.shaderFloat16 = VK_TRUE;
         usedKnobs._16BitStorageFeatures.storageBuffer16BitAccess = VK_TRUE;
+        usedKnobs._16BitStorageFeatures.storageInputOutput16 = VK_TRUE;
         usedKnobs._16BitStorageFeatures.uniformAndStorageBuffer16BitAccess = VK_TRUE;
 
         featuresChain.Add(&usedKnobs.shaderFloat16Int8Features,
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 6e88d4c..fa27f82 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -43,7 +43,9 @@
 
 class Device final : public DeviceBase {
   public:
-    static ResultOrError<Ref<Device>> Create(Adapter* adapter, const DeviceDescriptor* descriptor);
+    static ResultOrError<Ref<Device>> Create(Adapter* adapter,
+                                             const DeviceDescriptor* descriptor,
+                                             const TripleStateTogglesSet& userProvidedToggles);
     ~Device() override;
 
     MaybeError Initialize(const DeviceDescriptor* descriptor);
@@ -113,7 +115,9 @@
     const char* GetDebugPrefix() { return mDebugPrefix.c_str(); }
 
   private:
-    Device(Adapter* adapter, const DeviceDescriptor* descriptor);
+    Device(Adapter* adapter,
+           const DeviceDescriptor* descriptor,
+           const TripleStateTogglesSet& userProvidedToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 830e012..df61ac7 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -436,7 +436,7 @@
     PNextChainBuilder rasterizationChain(&rasterization);
     VkPipelineRasterizationDepthClipStateCreateInfoEXT depthClipState;
     if (HasUnclippedDepth()) {
-        ASSERT(device->IsFeatureEnabled(Feature::DepthClipControl));
+        ASSERT(device->HasFeature(Feature::DepthClipControl));
         depthClipState.pNext = nullptr;
         depthClipState.depthClipEnable = VK_FALSE;
         depthClipState.flags = 0;
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 2955a8b..6f5ba74 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -488,7 +488,7 @@
     "end2end/SamplerFilterAnisotropicTests.cpp",
     "end2end/SamplerTests.cpp",
     "end2end/ScissorTests.cpp",
-    "end2end/ShaderFloat16Tests.cpp",
+    "end2end/ShaderF16Tests.cpp",
     "end2end/ShaderTests.cpp",
     "end2end/ShaderValidationTests.cpp",
     "end2end/StorageTextureTests.cpp",
diff --git a/src/dawn/tests/end2end/ExperimentalDP4aTests.cpp b/src/dawn/tests/end2end/ExperimentalDP4aTests.cpp
index e6ecf3a..d77de5b 100644
--- a/src/dawn/tests/end2end/ExperimentalDP4aTests.cpp
+++ b/src/dawn/tests/end2end/ExperimentalDP4aTests.cpp
@@ -31,7 +31,18 @@
             return {};
         }
 
-        if (GetParam().mRequestDP4aExtension) {
+        if (!IsD3D12()) {
+            mUseDxcEnabledOrNonD3D12 = true;
+        } else {
+            for (auto* enabledToggle : GetParam().forceEnabledWorkarounds) {
+                if (strncmp(enabledToggle, "use_dxc", 7) == 0) {
+                    mUseDxcEnabledOrNonD3D12 = true;
+                    break;
+                }
+            }
+        }
+
+        if (GetParam().mRequestDP4aExtension && mUseDxcEnabledOrNonD3D12) {
             return {wgpu::FeatureName::ChromiumExperimentalDp4a};
         }
 
@@ -39,9 +50,11 @@
     }
 
     bool IsDP4aSupportedOnAdapter() const { return mIsDP4aSupportedOnAdapter; }
+    bool UseDxcEnabledOrNonD3D12() const { return mUseDxcEnabledOrNonD3D12; }
 
   private:
     bool mIsDP4aSupportedOnAdapter = false;
+    bool mUseDxcEnabledOrNonD3D12 = false;
 };
 
 TEST_P(ExperimentalDP4aTests, BasicDP4aFeaturesTest) {
@@ -67,12 +80,25 @@
             buf.data4 = dot4U8Packed(a, c);
         }
 )";
-    if (!GetParam().mRequestDP4aExtension || !IsDP4aSupportedOnAdapter() ||
-        (IsD3D12() && !HasToggleEnabled("use_dxc"))) {
+    const bool shouldDP4AFeatureSupportedByDevice =
+        // Required when creating device
+        GetParam().mRequestDP4aExtension &&
+        // Adapter support the feature
+        IsDP4aSupportedOnAdapter() &&
+        // Proper toggle, disallow_unsafe_apis and use_dxc if d3d12
+        // Note that "disallow_unsafe_apis" is always disabled in DawnTestBase::CreateDeviceImpl.
+        !HasToggleEnabled("disallow_unsafe_apis") && UseDxcEnabledOrNonD3D12();
+    const bool deviceSupportDP4AFeature =
+        device.HasFeature(wgpu::FeatureName::ChromiumExperimentalDp4a);
+    EXPECT_EQ(deviceSupportDP4AFeature, shouldDP4AFeatureSupportedByDevice);
+
+    if (!deviceSupportDP4AFeature) {
         ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader));
         return;
     }
 
+    utils::CreateShaderModule(device, computeShader);
+
     wgpu::BufferDescriptor bufferDesc;
     bufferDesc.size = 4 * sizeof(uint32_t);
     bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
@@ -101,6 +127,11 @@
     EXPECT_BUFFER_U32_RANGE_EQ(expected, bufferOut, 0, 4);
 }
 
+// DawnTestBase::CreateDeviceImpl always disable disallow_unsafe_apis toggle.
 DAWN_INSTANTIATE_TEST_P(ExperimentalDP4aTests,
-                        {D3D12Backend(), D3D12Backend({"use_dxc"}), VulkanBackend()},
+                        {
+                            D3D12Backend(),
+                            D3D12Backend({"use_dxc"}, {}),
+                            VulkanBackend(),
+                        },
                         {true, false});
diff --git a/src/dawn/tests/end2end/ShaderF16Tests.cpp b/src/dawn/tests/end2end/ShaderF16Tests.cpp
new file mode 100644
index 0000000..42c881b
--- /dev/null
+++ b/src/dawn/tests/end2end/ShaderF16Tests.cpp
@@ -0,0 +1,131 @@
+// Copyright 2022 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 <vector>
+
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+namespace {
+using RequireShaderF16Feature = bool;
+DAWN_TEST_PARAM_STRUCT(ShaderF16TestsParams, RequireShaderF16Feature);
+
+}  // anonymous namespace
+
+class ShaderF16Tests : public DawnTestWithParams<ShaderF16TestsParams> {
+  protected:
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        mIsShaderF16SupportedOnAdapter = SupportsFeatures({wgpu::FeatureName::ShaderF16});
+        if (!mIsShaderF16SupportedOnAdapter) {
+            return {};
+        }
+
+        if (!IsD3D12()) {
+            mUseDxcEnabledOrNonD3D12 = true;
+        } else {
+            for (auto* enabledToggle : GetParam().forceEnabledWorkarounds) {
+                if (strncmp(enabledToggle, "use_dxc", 7) == 0) {
+                    mUseDxcEnabledOrNonD3D12 = true;
+                    break;
+                }
+            }
+        }
+
+        if (GetParam().mRequireShaderF16Feature && mUseDxcEnabledOrNonD3D12) {
+            return {wgpu::FeatureName::ShaderF16};
+        }
+
+        return {};
+    }
+
+    bool IsShaderF16SupportedOnAdapter() const { return mIsShaderF16SupportedOnAdapter; }
+    bool UseDxcEnabledOrNonD3D12() const { return mUseDxcEnabledOrNonD3D12; }
+
+  private:
+    bool mIsShaderF16SupportedOnAdapter = false;
+    bool mUseDxcEnabledOrNonD3D12 = false;
+};
+
+TEST_P(ShaderF16Tests, BasicShaderF16FeaturesTest) {
+    const char* computeShader = R"(
+        enable f16;
+
+        struct Buf {
+            v : f32,
+        }
+        @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+        @compute @workgroup_size(1)
+        fn CSMain() {
+            let a : f16 = f16(buf.v) + 1.0h;
+            buf.v = f32(a);
+        }
+    )";
+
+    const bool shouldShaderF16FeatureSupportedByDevice =
+        // Required when creating device
+        GetParam().mRequireShaderF16Feature &&
+        // Adapter support the feature
+        IsShaderF16SupportedOnAdapter() &&
+        // Proper toggle, disallow_unsafe_apis and use_dxc if d3d12
+        // Note that "disallow_unsafe_apis" is always disabled in DawnTestBase::CreateDeviceImpl.
+        !HasToggleEnabled("disallow_unsafe_apis") && UseDxcEnabledOrNonD3D12();
+    const bool deviceSupportShaderF16Feature = device.HasFeature(wgpu::FeatureName::ShaderF16);
+    EXPECT_EQ(deviceSupportShaderF16Feature, shouldShaderF16FeatureSupportedByDevice);
+
+    if (!deviceSupportShaderF16Feature) {
+        ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, computeShader));
+        return;
+    }
+
+    wgpu::BufferDescriptor bufferDesc;
+    bufferDesc.size = 4u;
+    bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
+    wgpu::Buffer bufferOut = device.CreateBuffer(&bufferDesc);
+
+    wgpu::ComputePipelineDescriptor csDesc;
+    csDesc.compute.module = utils::CreateShaderModule(device, computeShader);
+    csDesc.compute.entryPoint = "CSMain";
+    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
+
+    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+                                                     {
+                                                         {0, bufferOut},
+                                                     });
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+    pass.SetPipeline(pipeline);
+    pass.SetBindGroup(0, bindGroup);
+    pass.DispatchWorkgroups(1);
+    pass.End();
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    uint32_t expected[] = {0x3f800000};  // 1.0f
+    EXPECT_BUFFER_U32_RANGE_EQ(expected, bufferOut, 0, 1);
+}
+
+// DawnTestBase::CreateDeviceImpl always disable disallow_unsafe_apis toggle.
+DAWN_INSTANTIATE_TEST_P(ShaderF16Tests,
+                        {
+                            D3D12Backend(),
+                            D3D12Backend({"use_dxc"}),
+                            VulkanBackend(),
+                            MetalBackend(),
+                            OpenGLBackend(),
+                            OpenGLESBackend(),
+                        },
+                        {true, false});
diff --git a/src/dawn/tests/end2end/ShaderFloat16Tests.cpp b/src/dawn/tests/end2end/ShaderFloat16Tests.cpp
deleted file mode 100644
index 81c7ed6..0000000
--- a/src/dawn/tests/end2end/ShaderFloat16Tests.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-// 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 <vector>
-
-#include "dawn/common/Math.h"
-#include "dawn/tests/DawnTest.h"
-#include "dawn/utils/WGPUHelpers.h"
-
-class ShaderFloat16Tests : public DawnTest {
-  protected:
-    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
-        mIsShaderFloat16Supported = SupportsFeatures({wgpu::FeatureName::DawnShaderFloat16});
-        if (!mIsShaderFloat16Supported) {
-            return {};
-        }
-
-        return {wgpu::FeatureName::DawnShaderFloat16};
-    }
-
-    bool IsShaderFloat16Supported() const { return mIsShaderFloat16Supported; }
-
-    bool mIsShaderFloat16Supported = false;
-};
-
-// Test basic 16bit float arithmetic and 16bit storage features.
-// TODO(crbug.com/tint/404): Implement float16 in Tint.
-TEST_P(ShaderFloat16Tests, DISABLED_Basic16BitFloatFeaturesTest) {
-    DAWN_TEST_UNSUPPORTED_IF(!IsShaderFloat16Supported());
-    DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsIntel());  // Flaky crashes. crbug.com/dawn/586
-
-    uint16_t uniformData[] = {Float32ToFloat16(1.23), Float32ToFloat16(0.0)};  // 0.0 is a padding.
-    wgpu::Buffer uniformBuffer = utils::CreateBufferFromData(
-        device, &uniformData, sizeof(uniformData), wgpu::BufferUsage::Uniform);
-
-    uint16_t bufferInData[] = {Float32ToFloat16(2.34), Float32ToFloat16(0.0)};  // 0.0 is a padding.
-    wgpu::Buffer bufferIn = utils::CreateBufferFromData(device, &bufferInData, sizeof(bufferInData),
-                                                        wgpu::BufferUsage::Storage);
-
-    wgpu::BufferDescriptor bufferDesc;
-    bufferDesc.size = 2 * sizeof(uint16_t);
-    bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
-    wgpu::Buffer bufferOut = device.CreateBuffer(&bufferDesc);
-
-    // SPIR-V ASM produced by glslang for the following fragment shader:
-    //
-    //   #version 450
-    //   #extension GL_AMD_gpu_shader_half_float : require
-    //
-    //   struct S {
-    //       float16_t f;
-    //       float16_t padding;
-    //   };
-    //   layout(std140, set = 0, binding = 0) uniform uniformBuf { S c; };
-    //   layout(std140, set = 0, binding = 1) readonly buffer bufA { S a; };
-    //   layout(std140, set = 0, binding = 2) buffer bufB { S b; };
-    //
-    //   void main() {
-    //       b.f = a.f + c.f;
-    //   }
-
-    wgpu::ShaderModule module = utils::CreateShaderModuleFromASM(device, R"(
-; SPIR-V
-; Version: 1.0
-; Generator: Khronos Glslang Reference Front End; 10
-; Bound: 26
-; Schema: 0
-               OpCapability Shader
-               OpCapability Float16
-               OpCapability StorageBuffer16BitAccess
-               OpCapability UniformAndStorageBuffer16BitAccess
-               OpExtension "SPV_KHR_16bit_storage"
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint GLCompute %main "main"
-               OpExecutionMode %main LocalSize 1 1 1
-               OpSource GLSL 450
-               OpSourceExtension "GL_AMD_gpu_shader_half_float"
-               OpName %main "main"
-               OpName %S "S"
-               OpMemberName %S 0 "f"
-               OpMemberName %S 1 "padding"
-               OpName %bufB "bufB"
-               OpMemberName %bufB 0 "b"
-               OpName %_ ""
-               OpName %bufA "bufA"
-               OpMemberName %bufA 0 "a"
-               OpName %__0 ""
-               OpName %uniformBuf "uniformBuf"
-               OpMemberName %uniformBuf 0 "c"
-               OpName %__1 ""
-               OpMemberDecorate %S 0 Offset 0
-               OpMemberDecorate %S 1 Offset 2
-               OpMemberDecorate %bufB 0 Offset 0
-               OpDecorate %bufB BufferBlock
-               OpDecorate %_ DescriptorSet 0
-               OpDecorate %_ Binding 2
-               OpMemberDecorate %bufA 0 NonWritable
-               OpMemberDecorate %bufA 0 Offset 0
-               OpDecorate %bufA BufferBlock
-               OpDecorate %__0 DescriptorSet 0
-               OpDecorate %__0 Binding 1
-               OpMemberDecorate %uniformBuf 0 Offset 0
-               OpDecorate %uniformBuf Block
-               OpDecorate %__1 DescriptorSet 0
-               OpDecorate %__1 Binding 0
-       %void = OpTypeVoid
-          %3 = OpTypeFunction %void
-       %half = OpTypeFloat 16
-          %S = OpTypeStruct %half %half
-       %bufB = OpTypeStruct %S
-%_ptr_Uniform_bufB = OpTypePointer Uniform %bufB
-          %_ = OpVariable %_ptr_Uniform_bufB Uniform
-        %int = OpTypeInt 32 1
-      %int_0 = OpConstant %int 0
-       %bufA = OpTypeStruct %S
-%_ptr_Uniform_bufA = OpTypePointer Uniform %bufA
-        %__0 = OpVariable %_ptr_Uniform_bufA Uniform
-%_ptr_Uniform_half = OpTypePointer Uniform %half
- %uniformBuf = OpTypeStruct %S
-%_ptr_Uniform_uniformBuf = OpTypePointer Uniform %uniformBuf
-        %__1 = OpVariable %_ptr_Uniform_uniformBuf Uniform
-       %main = OpFunction %void None %3
-          %5 = OpLabel
-         %17 = OpAccessChain %_ptr_Uniform_half %__0 %int_0 %int_0
-         %18 = OpLoad %half %17
-         %22 = OpAccessChain %_ptr_Uniform_half %__1 %int_0 %int_0
-         %23 = OpLoad %half %22
-         %24 = OpFAdd %half %18 %23
-         %25 = OpAccessChain %_ptr_Uniform_half %_ %int_0 %int_0
-               OpStore %25 %24
-               OpReturn
-               OpFunctionEnd
-    )");
-
-    wgpu::ComputePipelineDescriptor csDesc;
-    csDesc.compute.module = module;
-    csDesc.compute.entryPoint = "main";
-    wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
-
-    wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
-                                                     {
-                                                         {0, uniformBuffer, 0, sizeof(uniformData)},
-                                                         {1, bufferIn, 0, sizeof(bufferInData)},
-                                                         {2, bufferOut},
-                                                     });
-
-    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
-    wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
-    pass.SetPipeline(pipeline);
-    pass.SetBindGroup(0, bindGroup);
-    pass.DispatchWorkgroups(1);
-    pass.End();
-    wgpu::CommandBuffer commands = encoder.Finish();
-    queue.Submit(1, &commands);
-
-    uint16_t expected[] = {Float32ToFloat16(3.57), Float32ToFloat16(0.0)};  // 0.0 is a padding.
-
-    EXPECT_BUFFER_U16_RANGE_EQ(expected, bufferOut, 0, 2);
-}
-
-DAWN_INSTANTIATE_TEST(ShaderFloat16Tests,
-                      D3D12Backend(),
-                      MetalBackend(),
-                      OpenGLBackend(),
-                      OpenGLESBackend(),
-                      VulkanBackend());
diff --git a/src/dawn/tests/unittests/FeatureTests.cpp b/src/dawn/tests/unittests/FeatureTests.cpp
index cb7d701..b4c7fced 100644
--- a/src/dawn/tests/unittests/FeatureTests.cpp
+++ b/src/dawn/tests/unittests/FeatureTests.cpp
@@ -77,6 +77,14 @@
         deviceDescriptor.requiredFeatures = &featureName;
         deviceDescriptor.requiredFeaturesCount = 1;
 
+        // Some features may require DisallowUnsafeApis toggle disabled, otherwise CreateDevice may
+        // failed.
+        const char* const disableToggles[] = {"disallow_unsafe_apis"};
+        wgpu::DawnTogglesDeviceDescriptor toggleDesc;
+        toggleDesc.forceDisabledToggles = disableToggles;
+        toggleDesc.forceDisabledTogglesCount = 1;
+        deviceDescriptor.nextInChain = &toggleDesc;
+
         dawn::native::DeviceBase* deviceBase = dawn::native::FromAPI(
             adapter.CreateDevice(reinterpret_cast<const WGPUDeviceDescriptor*>(&deviceDescriptor)));
 
diff --git a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
index 09fe994..3bf15d9 100644
--- a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
+++ b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <memory>
+#include <vector>
 
 #include "dawn/dawn_proc.h"
 #include "dawn/native/DawnNative.h"
@@ -90,6 +91,42 @@
     EXPECT_THAT(toggles, Contains(StrEq(toggle)));
 }
 
+// Test features guarded by toggles are validated when creating devices.
+TEST_F(DeviceCreationTest, CreateDeviceRequiringFeaturesGuardedByToggle) {
+    std::vector<wgpu::FeatureName> featuresGuardedByToggle = {
+        wgpu::FeatureName::ShaderF16, wgpu::FeatureName::ChromiumExperimentalDp4a};
+
+    for (auto feature : featuresGuardedByToggle) {
+        wgpu::DeviceDescriptor deviceDescriptor;
+        deviceDescriptor.requiredFeatures = &feature;
+        deviceDescriptor.requiredFeaturesCount = 1;
+
+        // Test creating device without toggle would fail.
+        {
+            wgpu::Device device = adapter.CreateDevice(&deviceDescriptor);
+            EXPECT_EQ(device, nullptr);
+        }
+
+        // Test creating device without DisallowUnsafeApis toggle disabled.
+        {
+            const char* const disableToggles[] = {"disallow_unsafe_apis"};
+            wgpu::DawnTogglesDeviceDescriptor toggleDesc;
+            toggleDesc.forceDisabledToggles = disableToggles;
+            toggleDesc.forceDisabledTogglesCount = 1;
+            deviceDescriptor.nextInChain = &toggleDesc;
+
+            wgpu::Device device = adapter.CreateDevice(&deviceDescriptor);
+            EXPECT_NE(device, nullptr);
+
+            ASSERT_EQ(1u, device.EnumerateFeatures(nullptr));
+            wgpu::FeatureName enabledFeature;
+            device.EnumerateFeatures(&enabledFeature);
+            EXPECT_EQ(enabledFeature, feature);
+            device.Release();
+        }
+    }
+}
+
 TEST_F(DeviceCreationTest, CreateDeviceWithCacheSuccess) {
     // Default device descriptor should have the same cache key as a device descriptor with a
     // default cache descriptor.
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index aca064f..6358405 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -23,6 +23,7 @@
         case WGPUFeatureName_Undefined:
         case WGPUFeatureName_Force32:
         case WGPUFeatureName_DawnNative:
+        case WGPUFeatureName_DawnShaderFloat16:  // Deprecated
             return false;
         case WGPUFeatureName_Depth32FloatStencil8:
         case WGPUFeatureName_TimestampQuery:
@@ -32,10 +33,10 @@
         case WGPUFeatureName_TextureCompressionASTC:
         case WGPUFeatureName_IndirectFirstInstance:
         case WGPUFeatureName_DepthClipControl:
-        case WGPUFeatureName_DawnShaderFloat16:
         case WGPUFeatureName_DawnInternalUsages:
         case WGPUFeatureName_DawnMultiPlanarFormats:
         case WGPUFeatureName_ChromiumExperimentalDp4a:
+        case WGPUFeatureName_ShaderF16:
             return true;
     }