Support BC formats as the first extension in Dawn

This patch refactors the current implementation of BC formats to treat
it as the first extension in Dawn and adds all the related tests.

Note that in Dawn all the extensions are disabled unless we enable them
when we create the device, which means the BC formats can only be used
when we enable the related extension on the creation of the device, and
the creation of the device will fail if the adapter does not support the
extension

BUG=dawn:42
TEST=dawn_end2end_tests
TEST=dawn_unittests

Change-Id: I04d818b0218ebb3b1b7a70a4fea71779f308f85f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9520
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 7d33abb..2b53fd3 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -140,6 +140,8 @@
     "src/dawn_native/Error.h",
     "src/dawn_native/ErrorData.cpp",
     "src/dawn_native/ErrorData.h",
+    "src/dawn_native/Extensions.cpp",
+    "src/dawn_native/Extensions.h",
     "src/dawn_native/Fence.cpp",
     "src/dawn_native/Fence.h",
     "src/dawn_native/FenceSignalTracker.cpp",
@@ -605,6 +607,7 @@
     "src/tests/unittests/CommandAllocatorTests.cpp",
     "src/tests/unittests/EnumClassBitmasksTests.cpp",
     "src/tests/unittests/ErrorTests.cpp",
+    "src/tests/unittests/ExtensionTests.cpp",
     "src/tests/unittests/MathTests.cpp",
     "src/tests/unittests/ObjectBaseTests.cpp",
     "src/tests/unittests/PerStageTests.cpp",
diff --git a/src/dawn_native/Adapter.cpp b/src/dawn_native/Adapter.cpp
index 6e51f17..362b540 100644
--- a/src/dawn_native/Adapter.cpp
+++ b/src/dawn_native/Adapter.cpp
@@ -38,6 +38,24 @@
         return mInstance;
     }
 
+    ExtensionsSet AdapterBase::GetSupportedExtensions() const {
+        return mSupportedExtensions;
+    }
+
+    bool AdapterBase::SupportsAllRequestedExtensions(
+        const std::vector<const char*>& requestedExtensions) const {
+        for (const char* extensionStr : requestedExtensions) {
+            Extension extensionEnum = mInstance->ExtensionNameToEnum(extensionStr);
+            if (extensionEnum == Extension::InvalidEnum) {
+                return false;
+            }
+            if (!mSupportedExtensions.IsEnabled(extensionEnum)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     DeviceBase* AdapterBase::CreateDevice(const DeviceDescriptor* descriptor) {
         DeviceBase* result = nullptr;
 
@@ -50,6 +68,12 @@
 
     MaybeError AdapterBase::CreateDeviceInternal(DeviceBase** result,
                                                  const DeviceDescriptor* descriptor) {
+        if (descriptor != nullptr) {
+            if (!SupportsAllRequestedExtensions(descriptor->requiredExtensions)) {
+                return DAWN_VALIDATION_ERROR("One or more requested extensions are not supported");
+            }
+        }
+
         // TODO(cwallez@chromium.org): This will eventually have validation that the device
         // descriptor is valid and is a subset what's allowed on this adapter.
         DAWN_TRY_ASSIGN(*result, CreateDeviceImpl(descriptor));
diff --git a/src/dawn_native/Adapter.h b/src/dawn_native/Adapter.h
index 6c9d3f1..f4cb28a 100644
--- a/src/dawn_native/Adapter.h
+++ b/src/dawn_native/Adapter.h
@@ -18,6 +18,7 @@
 #include "dawn_native/DawnNative.h"
 
 #include "dawn_native/Error.h"
+#include "dawn_native/Extensions.h"
 
 namespace dawn_native {
 
@@ -35,9 +36,14 @@
 
         DeviceBase* CreateDevice(const DeviceDescriptor* descriptor = nullptr);
 
+        ExtensionsSet GetSupportedExtensions() const;
+        bool SupportsAllRequestedExtensions(
+            const std::vector<const char*>& requestedExtensions) const;
+
       protected:
         PCIInfo mPCIInfo = {};
         DeviceType mDeviceType = DeviceType::Unknown;
+        ExtensionsSet mSupportedExtensions;
 
       private:
         virtual ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) = 0;
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index f5909eb..33969aa 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -55,6 +55,11 @@
         return mImpl->GetPCIInfo();
     }
 
+    std::vector<const char*> Adapter::GetSupportedExtensions() const {
+        ExtensionsSet supportedExtensionsSet = mImpl->GetSupportedExtensions();
+        return supportedExtensionsSet.GetEnabledExtensionNames();
+    }
+
     Adapter::operator bool() const {
         return mImpl != nullptr;
     }
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 2306cf0..8fe4800 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -66,6 +66,10 @@
         mDynamicUploader = std::make_unique<DynamicUploader>(this);
         SetDefaultToggles();
 
+        if (descriptor != nullptr) {
+            ApplyExtensions(descriptor);
+        }
+
         mFormatTable = BuildFormatTable(this);
     }
 
@@ -505,10 +509,26 @@
         }
     }
 
+    void DeviceBase::ApplyExtensions(const DeviceDescriptor* deviceDescriptor) {
+        ASSERT(deviceDescriptor);
+        ASSERT(GetAdapter()->SupportsAllRequestedExtensions(deviceDescriptor->requiredExtensions));
+
+        mEnabledExtensions = GetAdapter()->GetInstance()->ExtensionNamesToExtensionsSet(
+            deviceDescriptor->requiredExtensions);
+    }
+
+    std::vector<const char*> DeviceBase::GetEnabledExtensions() const {
+        return mEnabledExtensions.GetEnabledExtensionNames();
+    }
+
     std::vector<const char*> DeviceBase::GetTogglesUsed() const {
         return mTogglesSet.GetEnabledToggleNames();
     }
 
+    bool DeviceBase::IsExtensionEnabled(Extension extension) const {
+        return mEnabledExtensions.IsEnabled(extension);
+    }
+
     bool DeviceBase::IsToggleEnabled(Toggle toggle) const {
         return mTogglesSet.IsEnabled(toggle);
     }
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 3c3d6c7..89353b6 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -17,6 +17,7 @@
 
 #include "common/Serial.h"
 #include "dawn_native/Error.h"
+#include "dawn_native/Extensions.h"
 #include "dawn_native/Format.h"
 #include "dawn_native/Forward.h"
 #include "dawn_native/ObjectBase.h"
@@ -154,7 +155,9 @@
 
         ResultOrError<DynamicUploader*> GetDynamicUploader() const;
 
+        std::vector<const char*> GetEnabledExtensions() const;
         std::vector<const char*> GetTogglesUsed() const;
+        bool IsExtensionEnabled(Extension extension) const;
         bool IsToggleEnabled(Toggle toggle) const;
         size_t GetLazyClearCountForTesting();
         void IncrementLazyClearCountForTesting();
@@ -212,6 +215,8 @@
                                              TextureBase* texture,
                                              const TextureViewDescriptor* descriptor);
 
+        void ApplyExtensions(const DeviceDescriptor* deviceDescriptor);
+
         void ConsumeError(ErrorData* error);
         void SetDefaultToggles();
 
@@ -240,6 +245,8 @@
 
         TogglesSet mTogglesSet;
         size_t mLazyClearCountForTesting = 0;
+
+        ExtensionsSet mEnabledExtensions;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Extensions.cpp b/src/dawn_native/Extensions.cpp
new file mode 100644
index 0000000..2de7a85
--- /dev/null
+++ b/src/dawn_native/Extensions.cpp
@@ -0,0 +1,113 @@
+// Copyright 2019 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 <array>
+
+#include "common/Assert.h"
+#include "common/BitSetIterator.h"
+#include "dawn_native/Extensions.h"
+
+namespace dawn_native {
+    namespace {
+
+        struct ExtensionEnumAndInfo {
+            Extension extension;
+            ExtensionInfo info;
+        };
+
+        using ExtensionEnumAndInfoList =
+            std::array<ExtensionEnumAndInfo, static_cast<size_t>(Extension::EnumCount)>;
+
+        static constexpr ExtensionEnumAndInfoList kExtensionNameAndInfoList = {
+            {{Extension::TextureCompressionBC,
+              {"texture_compression_bc", "Support Block Compressed (BC) texture formats",
+               "https://bugs.chromium.org/p/dawn/issues/detail?id=42"}}}};
+
+    }  // anonymous namespace
+
+    void ExtensionsSet::EnableExtension(Extension extension) {
+        ASSERT(extension != Extension::InvalidEnum);
+        const size_t extensionIndex = static_cast<size_t>(extension);
+        extensionsBitSet.set(extensionIndex);
+    }
+
+    bool ExtensionsSet::IsEnabled(Extension extension) const {
+        ASSERT(extension != Extension::InvalidEnum);
+        const size_t extensionIndex = static_cast<size_t>(extension);
+        return extensionsBitSet[extensionIndex];
+    }
+
+    std::vector<const char*> ExtensionsSet::GetEnabledExtensionNames() const {
+        std::vector<const char*> enabledExtensionNames(extensionsBitSet.count());
+
+        uint32_t index = 0;
+        for (uint32_t i : IterateBitSet(extensionsBitSet)) {
+            const char* extensionName = ExtensionEnumToName(static_cast<Extension>(i));
+            enabledExtensionNames[index] = extensionName;
+            ++index;
+        }
+        return enabledExtensionNames;
+    }
+
+    const char* ExtensionEnumToName(Extension extension) {
+        ASSERT(extension != Extension::InvalidEnum);
+
+        const ExtensionEnumAndInfo& extensionNameAndInfo =
+            kExtensionNameAndInfoList[static_cast<size_t>(extension)];
+        ASSERT(extensionNameAndInfo.extension == extension);
+        return extensionNameAndInfo.info.name;
+    }
+
+    ExtensionsInfo::ExtensionsInfo() {
+        for (size_t index = 0; index < kExtensionNameAndInfoList.size(); ++index) {
+            const ExtensionEnumAndInfo& extensionNameAndInfo = kExtensionNameAndInfoList[index];
+            ASSERT(index == static_cast<size_t>(extensionNameAndInfo.extension));
+            mExtensionNameToEnumMap[extensionNameAndInfo.info.name] =
+                extensionNameAndInfo.extension;
+        }
+    }
+
+    const ExtensionInfo* ExtensionsInfo::GetExtensionInfo(const char* extensionName) const {
+        ASSERT(extensionName);
+
+        const auto& iter = mExtensionNameToEnumMap.find(extensionName);
+        if (iter != mExtensionNameToEnumMap.cend()) {
+            return &kExtensionNameAndInfoList[static_cast<size_t>(iter->second)].info;
+        }
+        return nullptr;
+    }
+
+    Extension ExtensionsInfo::ExtensionNameToEnum(const char* extensionName) const {
+        ASSERT(extensionName);
+
+        const auto& iter = mExtensionNameToEnumMap.find(extensionName);
+        if (iter != mExtensionNameToEnumMap.cend()) {
+            return kExtensionNameAndInfoList[static_cast<size_t>(iter->second)].extension;
+        }
+        return Extension::InvalidEnum;
+    }
+
+    ExtensionsSet ExtensionsInfo::ExtensionNamesToExtensionsSet(
+        const std::vector<const char*>& requiredExtensions) const {
+        ExtensionsSet extensionsSet;
+
+        for (const char* extensionName : requiredExtensions) {
+            Extension extensionEnum = ExtensionNameToEnum(extensionName);
+            ASSERT(extensionEnum != Extension::InvalidEnum);
+            extensionsSet.EnableExtension(extensionEnum);
+        }
+        return extensionsSet;
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/Extensions.h b/src/dawn_native/Extensions.h
new file mode 100644
index 0000000..274096f
--- /dev/null
+++ b/src/dawn_native/Extensions.h
@@ -0,0 +1,63 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_EXTENSIONS_H_
+#define DAWNNATIVE_EXTENSIONS_H_
+
+#include <bitset>
+#include <unordered_map>
+#include <vector>
+
+#include "dawn_native/DawnNative.h"
+
+namespace dawn_native {
+
+    enum class Extension {
+        TextureCompressionBC,
+
+        EnumCount,
+        InvalidEnum = EnumCount,
+        ExtensionMin = TextureCompressionBC,
+    };
+
+    // A wrapper of the bitset to store if an extension is enabled or not. This wrapper provides the
+    // convenience to convert the enums of enum class Extension to the indices of a bitset.
+    struct ExtensionsSet {
+        std::bitset<static_cast<size_t>(Extension::EnumCount)> extensionsBitSet;
+
+        void EnableExtension(Extension extension);
+        bool IsEnabled(Extension extension) const;
+        std::vector<const char*> GetEnabledExtensionNames() const;
+    };
+
+    const char* ExtensionEnumToName(Extension extension);
+
+    class ExtensionsInfo {
+      public:
+        ExtensionsInfo();
+
+        // Used to query the details of an extension. Return nullptr if extensionName is not a valid
+        // name of an extension supported in Dawn
+        const ExtensionInfo* GetExtensionInfo(const char* extensionName) const;
+        Extension ExtensionNameToEnum(const char* extensionName) const;
+        ExtensionsSet ExtensionNamesToExtensionsSet(
+            const std::vector<const char*>& requiredExtensions) const;
+
+      private:
+        std::unordered_map<std::string, Extension> mExtensionNameToEnumMap;
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_EXTENSIONS_H_
\ No newline at end of file
diff --git a/src/dawn_native/Format.cpp b/src/dawn_native/Format.cpp
index da2946a..44ae1d8 100644
--- a/src/dawn_native/Format.cpp
+++ b/src/dawn_native/Format.cpp
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "dawn_native/Format.h"
+#include "dawn_native/Device.h"
+#include "dawn_native/Extensions.h"
 
 #include <bitset>
 
@@ -48,7 +50,7 @@
         return static_cast<size_t>(static_cast<uint32_t>(format));
     }
 
-    FormatTable BuildFormatTable(const DeviceBase*) {
+    FormatTable BuildFormatTable(const DeviceBase* device) {
         FormatTable table;
         std::bitset<kKnownFormatCount> formatsSet;
 
@@ -93,12 +95,12 @@
         };
 
         auto AddCompressedFormat = [&AddFormat](dawn::TextureFormat format, uint32_t byteSize,
-                                                uint32_t width, uint32_t height) {
+                                                uint32_t width, uint32_t height, bool isSupported) {
             Format internalFormat;
             internalFormat.format = format;
             internalFormat.isRenderable = false;
             internalFormat.isCompressed = true;
-            internalFormat.isSupported = true;
+            internalFormat.isSupported = isSupported;
             internalFormat.aspect = Format::Aspect::Color;
             internalFormat.blockByteSize = byteSize;
             internalFormat.blockWidth = width;
@@ -168,20 +170,21 @@
         AddDepthStencilFormat(dawn::TextureFormat::Depth24PlusStencil8, Format::Aspect::DepthStencil, 4);
 
         // BC compressed formats
-        AddCompressedFormat(dawn::TextureFormat::BC1RGBAUnorm, 8, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC1RGBAUnormSrgb, 8, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC4RSnorm, 8, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC4RUnorm, 8, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC2RGBAUnorm, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC2RGBAUnormSrgb, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC3RGBAUnorm, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC3RGBAUnormSrgb, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC5RGSnorm, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC5RGUnorm, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC6HRGBSfloat, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC6HRGBUfloat, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC7RGBAUnorm, 16, 4, 4);
-        AddCompressedFormat(dawn::TextureFormat::BC7RGBAUnormSrgb, 16, 4, 4);
+        bool isBCFormatSupported = device->IsExtensionEnabled(Extension::TextureCompressionBC);
+        AddCompressedFormat(dawn::TextureFormat::BC1RGBAUnorm, 8, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC1RGBAUnormSrgb, 8, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC4RSnorm, 8, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC4RUnorm, 8, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC2RGBAUnorm, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC2RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC3RGBAUnorm, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC3RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC5RGSnorm, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC5RGUnorm, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC6HRGBSfloat, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC6HRGBUfloat, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC7RGBAUnorm, 16, 4, 4, isBCFormatSupported);
+        AddCompressedFormat(dawn::TextureFormat::BC7RGBAUnormSrgb, 16, 4, 4, isBCFormatSupported);
 
         // clang-format on
 
diff --git a/src/dawn_native/Instance.cpp b/src/dawn_native/Instance.cpp
index c106ad17..cc3c9cc 100644
--- a/src/dawn_native/Instance.cpp
+++ b/src/dawn_native/Instance.cpp
@@ -86,6 +86,19 @@
         return mTogglesInfo.ToggleNameToEnum(toggleName);
     }
 
+    const ExtensionInfo* InstanceBase::GetExtensionInfo(const char* extensionName) {
+        return mExtensionsInfo.GetExtensionInfo(extensionName);
+    }
+
+    Extension InstanceBase::ExtensionNameToEnum(const char* extensionName) {
+        return mExtensionsInfo.ExtensionNameToEnum(extensionName);
+    }
+
+    ExtensionsSet InstanceBase::ExtensionNamesToExtensionsSet(
+        const std::vector<const char*>& requiredExtensions) {
+        return mExtensionsInfo.ExtensionNamesToExtensionsSet(requiredExtensions);
+    }
+
     const std::vector<std::unique_ptr<AdapterBase>>& InstanceBase::GetAdapters() const {
         return mAdapters;
     }
diff --git a/src/dawn_native/Instance.h b/src/dawn_native/Instance.h
index a3c5ebb..c0f6da7 100644
--- a/src/dawn_native/Instance.h
+++ b/src/dawn_native/Instance.h
@@ -17,6 +17,7 @@
 
 #include "dawn_native/Adapter.h"
 #include "dawn_native/BackendConnection.h"
+#include "dawn_native/Extensions.h"
 #include "dawn_native/Toggles.h"
 
 #include <array>
@@ -47,9 +48,15 @@
         // Used to query the details of a toggle. Return nullptr if toggleName is not a valid name
         // of a toggle supported in Dawn.
         const ToggleInfo* GetToggleInfo(const char* toggleName);
-
         Toggle ToggleNameToEnum(const char* toggleName);
 
+        // Used to query the details of an extension. Return nullptr if extensionName is not a valid
+        // name of an extension supported in Dawn.
+        const ExtensionInfo* GetExtensionInfo(const char* extensionName);
+        Extension ExtensionNameToEnum(const char* extensionName);
+        ExtensionsSet ExtensionNamesToExtensionsSet(
+            const std::vector<const char*>& requiredExtensions);
+
         void EnableBackendValidation(bool enableBackendValidation);
         bool IsBackendValidationEnabled() const;
 
@@ -74,6 +81,7 @@
         std::vector<std::unique_ptr<BackendConnection>> mBackends;
         std::vector<std::unique_ptr<AdapterBase>> mAdapters;
 
+        ExtensionsInfo mExtensionsInfo;
         TogglesInfo mTogglesInfo;
     };
 
diff --git a/src/dawn_native/d3d12/AdapterD3D12.cpp b/src/dawn_native/d3d12/AdapterD3D12.cpp
index e2c92cc..bb14171 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn_native/d3d12/AdapterD3D12.cpp
@@ -84,9 +84,15 @@
             "Error converting");
         mPCIInfo.name = converter.to_bytes(adapterDesc.Description);
 
+        InitializeSupportedExtensions();
+
         return {};
     }
 
+    void Adapter::InitializeSupportedExtensions() {
+        mSupportedExtensions.EnableExtension(Extension::TextureCompressionBC);
+    }
+
     ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
         std::unique_ptr<Device> device = std::make_unique<Device>(this, descriptor);
         DAWN_TRY(device->Initialize());
diff --git a/src/dawn_native/d3d12/AdapterD3D12.h b/src/dawn_native/d3d12/AdapterD3D12.h
index b5a726b..6c085f0 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.h
+++ b/src/dawn_native/d3d12/AdapterD3D12.h
@@ -38,6 +38,7 @@
 
       private:
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+        void InitializeSupportedExtensions();
 
         ComPtr<IDXGIAdapter1> mHardwareAdapter;
         ComPtr<ID3D12Device> mD3d12Device;
diff --git a/src/dawn_native/metal/BackendMTL.mm b/src/dawn_native/metal/BackendMTL.mm
index e09976d..d8516df 100644
--- a/src/dawn_native/metal/BackendMTL.mm
+++ b/src/dawn_native/metal/BackendMTL.mm
@@ -182,6 +182,11 @@
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
             return {new Device(this, mDevice, descriptor)};
         }
+        void InitializeSupportedExtensions() {
+            if ([mDevice supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) {
+                mSupportedExtensions.EnableExtension(Extension::TextureCompressionBC);
+            }
+        }
 
         id<MTLDevice> mDevice = nil;
     };
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 693cf09..47a3aec 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -17,6 +17,7 @@
 #include "dawn_native/BackendConnection.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/DynamicUploader.h"
+#include "dawn_native/Instance.h"
 
 #include <spirv-cross/spirv_cross.hpp>
 
@@ -24,19 +25,24 @@
 
     // Implementation of pre-Device objects: the null adapter, null backend connection and Connect()
 
-    class Adapter : public AdapterBase {
-      public:
-        Adapter(InstanceBase* instance) : AdapterBase(instance, BackendType::Null) {
-            mPCIInfo.name = "Null backend";
-            mDeviceType = DeviceType::CPU;
-        }
-        virtual ~Adapter() = default;
+    Adapter::Adapter(InstanceBase* instance) : AdapterBase(instance, BackendType::Null) {
+        mPCIInfo.name = "Null backend";
+        mDeviceType = DeviceType::CPU;
 
-      private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
-            return {new Device(this, descriptor)};
-        }
-    };
+        // Enable all extensions by default for the convenience of tests.
+        mSupportedExtensions.extensionsBitSet.flip();
+    }
+
+    Adapter::~Adapter() = default;
+
+    // Used for the tests that intend to use an adapter without all extensions enabled.
+    void Adapter::SetSupportedExtensions(const std::vector<const char*>& requiredExtensions) {
+        mSupportedExtensions = GetInstance()->ExtensionNamesToExtensionsSet(requiredExtensions);
+    }
+
+    ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
+        return {new Device(this, descriptor)};
+    }
 
     class Backend : public BackendConnection {
       public:
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index cff06a0..36139a5 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_NULL_DEVICENULL_H_
 #define DAWNNATIVE_NULL_DEVICENULL_H_
 
+#include "dawn_native/Adapter.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Buffer.h"
@@ -137,6 +138,18 @@
         size_t mMemoryUsage = 0;
     };
 
+    class Adapter : public AdapterBase {
+      public:
+        Adapter(InstanceBase* instance);
+        virtual ~Adapter();
+
+        // Used for the tests that intend to use an adapter without all extensions enabled.
+        void SetSupportedExtensions(const std::vector<const char*>& requiredExtensions);
+
+      private:
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+    };
+
     class Buffer : public BufferBase {
       public:
         Buffer(Device* device, const BufferDescriptor* descriptor);
diff --git a/src/dawn_native/vulkan/AdapterVk.cpp b/src/dawn_native/vulkan/AdapterVk.cpp
index 614eeda..d86a3bb 100644
--- a/src/dawn_native/vulkan/AdapterVk.cpp
+++ b/src/dawn_native/vulkan/AdapterVk.cpp
@@ -40,6 +40,8 @@
     MaybeError Adapter::Initialize() {
         DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(*this));
 
+        InitializeSupportedExtensions();
+
         mPCIInfo.deviceId = mDeviceInfo.properties.deviceID;
         mPCIInfo.vendorId = mDeviceInfo.properties.vendorID;
         mPCIInfo.name = mDeviceInfo.properties.deviceName;
@@ -62,6 +64,12 @@
         return {};
     }
 
+    void Adapter::InitializeSupportedExtensions() {
+        if (mDeviceInfo.features.textureCompressionBC == VK_TRUE) {
+            mSupportedExtensions.EnableExtension(Extension::TextureCompressionBC);
+        }
+    }
+
     ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
         std::unique_ptr<Device> device = std::make_unique<Device>(this, descriptor);
         DAWN_TRY(device->Initialize());
diff --git a/src/dawn_native/vulkan/AdapterVk.h b/src/dawn_native/vulkan/AdapterVk.h
index 2d9ed83..a4a3d53 100644
--- a/src/dawn_native/vulkan/AdapterVk.h
+++ b/src/dawn_native/vulkan/AdapterVk.h
@@ -37,6 +37,7 @@
 
       private:
         ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
+        void InitializeSupportedExtensions();
 
         VkPhysicalDevice mPhysicalDevice;
         Backend* mBackend;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 7ff3f3f..3d370e3 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -344,8 +344,11 @@
         // Always require fragmentStoresAndAtomics because it is required by end2end tests.
         usedKnobs.features.fragmentStoresAndAtomics = VK_TRUE;
 
-        // TODO(jiawei.shao@intel.com): support BC formats as extension
-        usedKnobs.features.textureCompressionBC = VK_TRUE;
+        if (IsExtensionEnabled(Extension::TextureCompressionBC)) {
+            ASSERT(ToBackend(GetAdapter())->GetDeviceInfo().features.textureCompressionBC ==
+                   VK_TRUE);
+            usedKnobs.features.textureCompressionBC = VK_TRUE;
+        }
 
         // Find a universal queue family
         {
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index d90abd0..3382409 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -50,6 +50,7 @@
     // An optional parameter of Adapter::CreateDevice() to send additional information when creating
     // a Device. For example, we can use it to enable a workaround, optimization or feature.
     struct DAWN_NATIVE_EXPORT DeviceDescriptor {
+        std::vector<const char*> requiredExtensions;
         std::vector<const char*> forceEnabledToggles;
         std::vector<const char*> forceDisabledToggles;
     };
@@ -63,6 +64,11 @@
         const char* url;
     };
 
+    // A struct to record the information of an extension. An extension 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 ExtensionInfo = ToggleInfo;
+
     // 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
     // same GPU can be represented by multiple adapters but on different APIs.
@@ -78,6 +84,7 @@
         BackendType GetBackendType() const;
         DeviceType GetDeviceType() const;
         const PCIInfo& GetPCIInfo() const;
+        std::vector<const char*> GetSupportedExtensions() const;
 
         explicit operator bool() const;
 
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index c8afc2b..ea2070c 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -333,9 +333,31 @@
     return gTestEnv->GetVendorIdFilter();
 }
 
+std::vector<const char*> DawnTest::GetRequiredExtensions() {
+    return {};
+}
+
+// This function can only be called after SetUp() because it requires mBackendAdapter to be
+// initialized.
+bool DawnTest::SupportsExtensions(const std::vector<const char*>& extensions) {
+    ASSERT(mBackendAdapter);
+
+    std::set<std::string> supportedExtensionsSet;
+    for (const char* supportedExtensionName : mBackendAdapter.GetSupportedExtensions()) {
+        supportedExtensionsSet.insert(supportedExtensionName);
+    }
+
+    for (const char* extensionName : extensions) {
+        if (supportedExtensionsSet.find(extensionName) == supportedExtensionsSet.end()) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 void DawnTest::SetUp() {
-    // Get an adapter for the backend to use, and create the device.
-    dawn_native::Adapter backendAdapter;
+    // Initialize mBackendAdapter, and create the device.
     const dawn_native::BackendType backendType = GetParam().backendType;
     {
         dawn_native::Instance* instance = gTestEnv->GetInstance();
@@ -345,11 +367,11 @@
             if (adapter.GetBackendType() == backendType) {
                 if (HasVendorIdFilter()) {
                     if (adapter.GetPCIInfo().vendorId == GetVendorIdFilter()) {
-                        backendAdapter = adapter;
+                        mBackendAdapter = adapter;
                         break;
                     }
                 } else {
-                    backendAdapter = adapter;
+                    mBackendAdapter = adapter;
 
                     // On Metal, select the last adapter so that the discrete GPU is tested on
                     // multi-GPU systems.
@@ -363,10 +385,10 @@
             }
         }
 
-        ASSERT(backendAdapter);
+        ASSERT(mBackendAdapter);
     }
 
-    mPCIInfo = backendAdapter.GetPCIInfo();
+    mPCIInfo = mBackendAdapter.GetPCIInfo();
 
     for (const char* forceEnabledWorkaround : GetParam().forceEnabledWorkarounds) {
         ASSERT(gTestEnv->GetInstance()->GetToggleInfo(forceEnabledWorkaround) != nullptr);
@@ -377,7 +399,9 @@
     dawn_native::DeviceDescriptor deviceDescriptor;
     deviceDescriptor.forceEnabledToggles = GetParam().forceEnabledWorkarounds;
     deviceDescriptor.forceDisabledToggles = GetParam().forceDisabledWorkarounds;
-    backendDevice = backendAdapter.CreateDevice(&deviceDescriptor);
+    deviceDescriptor.requiredExtensions = GetRequiredExtensions();
+    backendDevice = mBackendAdapter.CreateDevice(&deviceDescriptor);
+    ASSERT_NE(nullptr, backendDevice);
 
     backendProcs = dawn_native::GetProcs();
 
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 3aa685e..bbde83e 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -206,6 +206,14 @@
 
     void SwapBuffersForCapture();
 
+    bool SupportsExtensions(const std::vector<const char*>& extensions);
+
+    // Called in SetUp() to get the extensions required to be enabled in the tests. The tests must
+    // check if the required extensions are supported by the adapter in this function and guarantee
+    // the returned extensions are all supported by the adapter. The tests may provide different
+    // code path to handle the situation when not all extensions are supported.
+    virtual std::vector<const char*> GetRequiredExtensions();
+
   private:
     // Things used to set up testing through the Wire.
     std::unique_ptr<dawn_wire::WireServer> mWireServer;
@@ -263,6 +271,8 @@
     std::unique_ptr<utils::BackendBinding> mBinding;
 
     dawn_native::PCIInfo mPCIInfo;
+
+    dawn_native::Adapter mBackendAdapter;
 };
 
 // Instantiate the test once for each backend provided after the first argument. Use it like this:
diff --git a/src/tests/end2end/CompressedTextureFormatTests.cpp b/src/tests/end2end/CompressedTextureFormatTests.cpp
index 157ae9e..c19b6ad 100644
--- a/src/tests/end2end/CompressedTextureFormatTests.cpp
+++ b/src/tests/end2end/CompressedTextureFormatTests.cpp
@@ -67,9 +67,24 @@
                      {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture}});
     }
 
+    std::vector<const char*> GetRequiredExtensions() override {
+        mIsBCFormatSupported = SupportsExtensions({"texture_compression_bc"});
+        if (!mIsBCFormatSupported) {
+            return {};
+        }
+
+        return {"texture_compression_bc"};
+    }
+
+    bool IsBCFormatSupported() const {
+        return mIsBCFormatSupported;
+    }
+
     // Copy the compressed texture data into the destination texture as is specified in copyConfig.
     void CopyDataIntoCompressedTexture(dawn::Texture bcCompressedTexture,
                                        const CopyConfig& copyConfig) {
+        ASSERT(IsBCFormatSupported());
+
         // Compute the upload buffer size with rowPitchAlignment and the copy region.
         uint32_t actualWidthAtLevel = copyConfig.textureWidthLevel0 >> copyConfig.baseMipmapLevel;
         uint32_t actualHeightAtLevel = copyConfig.textureHeightLevel0 >> copyConfig.baseMipmapLevel;
@@ -121,6 +136,8 @@
                                            dawn::TextureFormat bcFormat,
                                            uint32_t baseArrayLayer = 0,
                                            uint32_t baseMipLevel = 0) {
+        ASSERT(IsBCFormatSupported());
+
         dawn::SamplerDescriptor samplerDesc = utils::GetDefaultSamplerDescriptor();
         samplerDesc.minFilter = dawn::FilterMode::Nearest;
         samplerDesc.magFilter = dawn::FilterMode::Nearest;
@@ -140,6 +157,8 @@
 
     // Create a render pipeline for sampling from a BC texture and rendering into the render target.
     dawn::RenderPipeline CreateRenderPipelineForTest() {
+        ASSERT(IsBCFormatSupported());
+
         dawn::PipelineLayout pipelineLayout =
             utils::MakeBasicPipelineLayout(device, &mBindGroupLayout);
 
@@ -183,6 +202,8 @@
                                             const dawn::Origin3D& expectedOrigin,
                                             const dawn::Extent3D& expectedExtent,
                                             const std::vector<RGBA8>& expected) {
+        ASSERT(IsBCFormatSupported());
+
         ASSERT(expected.size() == renderTargetSize.width * renderTargetSize.height);
         utils::BasicRenderPass renderPass =
             utils::CreateBasicRenderPass(device, renderTargetSize.width, renderTargetSize.height);
@@ -207,6 +228,8 @@
     // Run the tests that copies pre-prepared BC format data into a BC texture and verifies if we
     // can render correctly with the pixel values sampled from the BC texture.
     void TestCopyRegionIntoBCFormatTextures(const CopyConfig& config) {
+        ASSERT(IsBCFormatSupported());
+
         dawn::Texture bcTexture = Create2DTexture(device, config.format, config.textureWidthLevel0,
                                                   config.textureHeightLevel0,
                                                   config.arrayLayerCount, config.mipmapLevelCount);
@@ -416,10 +439,14 @@
     static constexpr uint32_t kBCBlockHeightInTexels = 4;
 
     dawn::BindGroupLayout mBindGroupLayout;
+
+    bool mIsBCFormatSupported = false;
 };
 
 // Test copying into the whole BC texture with 2x2 blocks and sampling from it.
 TEST_P(CompressedTextureBCFormatTest, Basic) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = 8;
@@ -433,6 +460,8 @@
 
 // Test copying into a sub-region of a texture with BC formats works correctly.
 TEST_P(CompressedTextureBCFormatTest, CopyIntoSubRegion) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureHeightLevel0 = 8;
     config.textureWidthLevel0 = 8;
@@ -451,6 +480,8 @@
 
 // Test using rowPitch == 0 in the copies with BC formats works correctly.
 TEST_P(CompressedTextureBCFormatTest, CopyWithZeroRowPitch) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureHeightLevel0 = 8;
 
@@ -468,6 +499,8 @@
 
 // Test copying into the non-zero layer of a 2D array texture with BC formats works correctly.
 TEST_P(CompressedTextureBCFormatTest, CopyIntoNonZeroArrayLayer) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureHeightLevel0 = 8;
     config.textureWidthLevel0 = 8;
@@ -486,6 +519,8 @@
 
 // Test copying into a non-zero mipmap level of a texture with BC texture formats.
 TEST_P(CompressedTextureBCFormatTest, CopyBufferIntoNonZeroMipmapLevel) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureHeightLevel0 = 60;
     config.textureWidthLevel0 = 60;
@@ -517,6 +552,8 @@
 
 // Test texture-to-texture whole-size copies with BC formats.
 TEST_P(CompressedTextureBCFormatTest, CopyWholeTextureSubResourceIntoNonZeroMipmapLevel) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     // TODO(cwallez@chromium.org): This consistently fails on with the 12th pixel being opaque black
     // instead of opaque red on Win10 FYI Release (NVIDIA GeForce GTX 1660). See
     // https://bugs.chromium.org/p/chromium/issues/detail?id=981393
@@ -576,6 +613,8 @@
 
 // Test BC format texture-to-texture partial copies.
 TEST_P(CompressedTextureBCFormatTest, CopyPartofTextureSubResourceIntoNonZeroMipmapLevel) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     // TODO(jiawei.shao@intel.com): add workaround on the T2T copies where Extent3D fits in one
     // subresource and does not fit in another one on Vulkan. Currently this test causes an error if
     // Vulkan validation layer is enabled.
@@ -652,6 +691,8 @@
 // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture
 // extent exactly fit the RowPitch.
 TEST_P(CompressedTextureBCFormatTest, BufferOffsetAndExtentFitRowPitch) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = 8;
@@ -677,6 +718,8 @@
 // backend the texelOffset.y will be greater than 0 after calcuting the texelOffset in the function
 // ComputeTexelOffsets().
 TEST_P(CompressedTextureBCFormatTest, BufferOffsetExceedsSlicePitch) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = 8;
@@ -703,6 +746,8 @@
 // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture
 // extent exceed the RowPitch. On D3D12 backend two copies are required for this case.
 TEST_P(CompressedTextureBCFormatTest, CopyWithBufferOffsetAndExtentExceedRowPitch) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = 8;
@@ -729,6 +774,8 @@
 // rowPitch. On D3D12 backend the texelOffset.z will be greater than 0 after calcuting the
 // texelOffset in the function ComputeTexelOffsets().
 TEST_P(CompressedTextureBCFormatTest, RowPitchEqualToSlicePitch) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = kBCBlockHeightInTexels;
@@ -755,6 +802,8 @@
 // copyExtent.depth) on Metal backends. As copyExtent.depth can only be 1 for BC formats, on Metal
 // backend we will use two copies to implement such copy.
 TEST_P(CompressedTextureBCFormatTest, LargeImageHeight) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureWidthLevel0 = 8;
     config.textureHeightLevel0 = 8;
@@ -771,6 +820,8 @@
 // Test the workaround in the B2T copies when (bufferSize - bufferOffset < bytesPerImage *
 // copyExtent.depth) and copyExtent needs to be clamped.
 TEST_P(CompressedTextureBCFormatTest, LargeImageHeightAndClampedCopyExtent) {
+    DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+
     CopyConfig config;
     config.textureHeightLevel0 = 56;
     config.textureWidthLevel0 = 56;
@@ -803,4 +854,8 @@
 }
 
 // TODO(jiawei.shao@intel.com): support BC formats on OpenGL backend
-DAWN_INSTANTIATE_TEST(CompressedTextureBCFormatTest, D3D12Backend, MetalBackend, VulkanBackend);
+DAWN_INSTANTIATE_TEST(CompressedTextureBCFormatTest,
+                      D3D12Backend,
+                      MetalBackend,
+                      OpenGLBackend,
+                      VulkanBackend);
diff --git a/src/tests/unittests/ExtensionTests.cpp b/src/tests/unittests/ExtensionTests.cpp
new file mode 100644
index 0000000..cef2752
--- /dev/null
+++ b/src/tests/unittests/ExtensionTests.cpp
@@ -0,0 +1,78 @@
+// Copyright 2019 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 <gtest/gtest.h>
+
+#include "dawn_native/Extensions.h"
+#include "dawn_native/Instance.h"
+#include "dawn_native/null/DeviceNull.h"
+
+class ExtensionTests : public testing::Test {
+  public:
+    ExtensionTests() : testing::Test(), mInstanceBase(), mAdapterBase(&mInstanceBase) {
+    }
+
+    std::vector<const char*> GetAllExtensionNames() {
+        std::vector<const char*> allExtensionNames(kTotalExtensionsCount);
+        for (size_t i = 0; i < kTotalExtensionsCount; ++i) {
+            allExtensionNames[i] = ExtensionEnumToName(static_cast<dawn_native::Extension>(i));
+        }
+        return allExtensionNames;
+    }
+
+    static constexpr size_t kTotalExtensionsCount =
+        static_cast<size_t>(dawn_native::Extension::EnumCount);
+
+  protected:
+    dawn_native::InstanceBase mInstanceBase;
+    dawn_native::null::Adapter mAdapterBase;
+};
+
+// Test the creation of a device will fail if the requested extension is not supported on the
+// Adapter.
+TEST_F(ExtensionTests, AdapterWithRequiredExtensionDisabled) {
+    const std::vector<const char*> kAllExtensionNames = GetAllExtensionNames();
+    for (size_t i = 0; i < kTotalExtensionsCount; ++i) {
+        dawn_native::Extension notSupportedExtension = static_cast<dawn_native::Extension>(i);
+
+        std::vector<const char*> extensionNamesWithoutOne = kAllExtensionNames;
+        extensionNamesWithoutOne.erase(extensionNamesWithoutOne.begin() + i);
+
+        mAdapterBase.SetSupportedExtensions(extensionNamesWithoutOne);
+        dawn_native::Adapter adapterWithoutExtension(&mAdapterBase);
+
+        dawn_native::DeviceDescriptor deviceDescriptor;
+        const char* extensionName = ExtensionEnumToName(notSupportedExtension);
+        deviceDescriptor.requiredExtensions = std::vector<const char*>(1, extensionName);
+        DawnDevice deviceWithExtension = adapterWithoutExtension.CreateDevice(&deviceDescriptor);
+        ASSERT_EQ(nullptr, deviceWithExtension);
+    }
+}
+
+// Test Device.GetEnabledExtensions() can return the names of the enabled extensions correctly.
+TEST_F(ExtensionTests, GetEnabledExtensions) {
+    dawn_native::Adapter adapter(&mAdapterBase);
+    for (size_t i = 0; i < kTotalExtensionsCount; ++i) {
+        dawn_native::Extension extension = static_cast<dawn_native::Extension>(i);
+        const char* extensionName = ExtensionEnumToName(extension);
+
+        dawn_native::DeviceDescriptor deviceDescriptor;
+        deviceDescriptor.requiredExtensions = {extensionName};
+        dawn_native::DeviceBase* deviceBase =
+            reinterpret_cast<dawn_native::DeviceBase*>(adapter.CreateDevice(&deviceDescriptor));
+        std::vector<const char*> enabledExtensions = deviceBase->GetEnabledExtensions();
+        ASSERT_EQ(1u, enabledExtensions.size());
+        ASSERT_EQ(0, std::strcmp(extensionName, enabledExtensions[0]));
+    }
+}
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index b2ca88e..0ed925c 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -1155,6 +1155,11 @@
 }
 
 class CopyCommandTest_CompressedTextureFormats : public CopyCommandTest {
+  public:
+    CopyCommandTest_CompressedTextureFormats() : CopyCommandTest() {
+        device = CreateDeviceFromAdapter(adapter, {"texture_compression_bc"});
+    }
+
   protected:
     dawn::Texture Create2DTexture(dawn::TextureFormat format,
                                   uint32_t mipmapLevels = 1,
diff --git a/src/tests/unittests/validation/TextureValidationTests.cpp b/src/tests/unittests/validation/TextureValidationTests.cpp
index bef154e..75874f8 100644
--- a/src/tests/unittests/validation/TextureValidationTests.cpp
+++ b/src/tests/unittests/validation/TextureValidationTests.cpp
@@ -250,10 +250,14 @@
     ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
 }
 
-// TODO(jiawei.shao@intel.com): use compressed texture formats as extensions.
 // TODO(jiawei.shao@intel.com): add tests to verify we cannot create 1D or 3D textures with
 // compressed texture formats.
 class CompressedTextureFormatsValidationTests : public TextureValidationTest {
+  public:
+    CompressedTextureFormatsValidationTests() : TextureValidationTest() {
+        device = CreateDeviceFromAdapter(adapter, {"texture_compression_bc"});
+    }
+
   protected:
     dawn::TextureDescriptor CreateDefaultTextureDescriptor() {
         dawn::TextureDescriptor descriptor =
@@ -309,6 +313,18 @@
     }
 }
 
+// Test the creation of a texture with BC format will fail when the extension textureCompressionBC
+// is not enabled.
+TEST_F(CompressedTextureFormatsValidationTests, UseBCFormatWithoutEnablingExtension) {
+    const std::vector<const char*> kEmptyVector;
+    dawn::Device deviceWithoutExtension = CreateDeviceFromAdapter(adapter, kEmptyVector);
+    for (dawn::TextureFormat format : kBCFormats) {
+        dawn::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
+        descriptor.format = format;
+        ASSERT_DEVICE_ERROR(deviceWithoutExtension.CreateTexture(&descriptor));
+    }
+}
+
 // Test the validation of texture usages when creating textures in compressed texture formats.
 TEST_F(CompressedTextureFormatsValidationTests, TextureUsage) {
     // Test that only CopySrc, CopyDst and Sampled are accepted as the texture usage of the
diff --git a/src/tests/unittests/validation/ValidationTest.cpp b/src/tests/unittests/validation/ValidationTest.cpp
index 09f6e87..af02a0b 100644
--- a/src/tests/unittests/validation/ValidationTest.cpp
+++ b/src/tests/unittests/validation/ValidationTest.cpp
@@ -35,12 +35,29 @@
     }
 
     ASSERT(foundNullAdapter);
-    device = dawn::Device::Acquire(adapter.CreateDevice());
 
     DawnProcTable procs = dawn_native::GetProcs();
     dawnSetProcs(&procs);
 
-    device.SetErrorCallback(ValidationTest::OnDeviceError, this);
+    device = CreateDeviceFromAdapter(adapter, std::vector<const char*>());
+}
+
+dawn::Device ValidationTest::CreateDeviceFromAdapter(
+    dawn_native::Adapter adapterToTest,
+    const std::vector<const char*>& requiredExtensions) {
+    dawn::Device deviceToTest;
+
+    // Always keep the code path to test creating a device without a device descriptor.
+    if (requiredExtensions.empty()) {
+        deviceToTest = dawn::Device::Acquire(adapterToTest.CreateDevice());
+    } else {
+        dawn_native::DeviceDescriptor descriptor;
+        descriptor.requiredExtensions = requiredExtensions;
+        deviceToTest = dawn::Device::Acquire(adapterToTest.CreateDevice(&descriptor));
+    }
+
+    deviceToTest.SetErrorCallback(ValidationTest::OnDeviceError, this);
+    return deviceToTest;
 }
 
 ValidationTest::~ValidationTest() {
diff --git a/src/tests/unittests/validation/ValidationTest.h b/src/tests/unittests/validation/ValidationTest.h
index 92aef16..5deb019 100644
--- a/src/tests/unittests/validation/ValidationTest.h
+++ b/src/tests/unittests/validation/ValidationTest.h
@@ -29,6 +29,9 @@
     ValidationTest();
     ~ValidationTest();
 
+    dawn::Device CreateDeviceFromAdapter(dawn_native::Adapter adapter,
+                                         const std::vector<const char*>& requiredExtensions);
+
     void TearDown() override;
 
     void StartExpectDeviceError();