Emulate store and multisample resolve on Metal

This patch implements "store and MSAA resolve" store operation on Metal
drivers that don't support MTLStoreActionStoreAndMultisampleResolve with
a workaround that does MSAA resolve in another render pass.

Driver workaround is one type of Dawn Toggles. Dawn Toggles will include
other optional optimizations and features that can be configured to use
or not when we create Dawn Devices.

As all Metal try bots don't need this toggle, to better test this
patch on the try bots:
1. We add the support of forcing enabling a workaround when starting an
   Dawn end2end test so that we can test the workaround on the platforms
   where the workaround is disabled.
2. We add an optional parameter DeviceDescriptor to CreateDevice() so
   that we can custom the toggles the Dawn device should use.

This patch also adds the support of querying toggle details from Instance
and the names of the toggles in use from Device. These APIs are tested in
the Dawn unittests added in this patch.

BUG=dawn:56
TEST=dawn_end2end_tests
TEST=dawn_unittests

Change-Id: Iae31d2ded6057eee638b6099d3061e9d78b04d55
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/6620
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/BUILD.gn b/BUILD.gn
index b7b6a07..87f5f20 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -153,6 +153,7 @@
     "src/dawn_native/Texture.cpp",
     "src/dawn_native/Texture.h",
     "src/dawn_native/ToBackend.h",
+    "src/dawn_native/Toggles.h",
     "src/dawn_native/dawn_platform.h",
   ]
 
@@ -581,6 +582,7 @@
     "src/tests/unittests/validation/ShaderModuleValidationTests.cpp",
     "src/tests/unittests/validation/TextureValidationTests.cpp",
     "src/tests/unittests/validation/TextureViewValidationTests.cpp",
+    "src/tests/unittests/validation/ToggleValidationTests.cpp",
     "src/tests/unittests/validation/ValidationTest.cpp",
     "src/tests/unittests/validation/ValidationTest.h",
     "src/tests/unittests/validation/VertexBufferValidationTests.cpp",
diff --git a/src/dawn_native/Adapter.cpp b/src/dawn_native/Adapter.cpp
index e108acc..6e51f17 100644
--- a/src/dawn_native/Adapter.cpp
+++ b/src/dawn_native/Adapter.cpp
@@ -38,20 +38,21 @@
         return mInstance;
     }
 
-    DeviceBase* AdapterBase::CreateDevice() {
+    DeviceBase* AdapterBase::CreateDevice(const DeviceDescriptor* descriptor) {
         DeviceBase* result = nullptr;
 
-        if (mInstance->ConsumedError(CreateDeviceInternal(&result))) {
+        if (mInstance->ConsumedError(CreateDeviceInternal(&result, descriptor))) {
             return nullptr;
         }
 
         return result;
     }
 
-    MaybeError AdapterBase::CreateDeviceInternal(DeviceBase** result) {
+    MaybeError AdapterBase::CreateDeviceInternal(DeviceBase** result,
+                                                 const DeviceDescriptor* descriptor) {
         // 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());
+        DAWN_TRY_ASSIGN(*result, CreateDeviceImpl(descriptor));
         return {};
     }
 
diff --git a/src/dawn_native/Adapter.h b/src/dawn_native/Adapter.h
index 6f1c0b2..6c9d3f1 100644
--- a/src/dawn_native/Adapter.h
+++ b/src/dawn_native/Adapter.h
@@ -33,16 +33,16 @@
         const PCIInfo& GetPCIInfo() const;
         InstanceBase* GetInstance() const;
 
-        DeviceBase* CreateDevice();
+        DeviceBase* CreateDevice(const DeviceDescriptor* descriptor = nullptr);
 
       protected:
         PCIInfo mPCIInfo = {};
         DeviceType mDeviceType = DeviceType::Unknown;
 
       private:
-        virtual ResultOrError<DeviceBase*> CreateDeviceImpl() = 0;
+        virtual ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) = 0;
 
-        MaybeError CreateDeviceInternal(DeviceBase** result);
+        MaybeError CreateDeviceInternal(DeviceBase** result, const DeviceDescriptor* descriptor);
 
         InstanceBase* mInstance = nullptr;
         BackendType mBackend;
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index 7330f84..e73cfe4 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -26,6 +26,12 @@
         return GetProcsAutogen();
     }
 
+    std::vector<const char*> GetTogglesUsed(DawnDevice device) {
+        const dawn_native::DeviceBase* deviceBase =
+            reinterpret_cast<const dawn_native::DeviceBase*>(device);
+        return deviceBase->GetTogglesUsed();
+    }
+
     // Adapter
 
     Adapter::Adapter() = default;
@@ -53,8 +59,8 @@
         return mImpl != nullptr;
     }
 
-    DawnDevice Adapter::CreateDevice() {
-        return reinterpret_cast<DawnDevice>(mImpl->CreateDevice());
+    DawnDevice Adapter::CreateDevice(const DeviceDescriptor* deviceDescriptor) {
+        return reinterpret_cast<DawnDevice>(mImpl->CreateDevice(deviceDescriptor));
     }
 
     // AdapterDiscoverOptionsBase
@@ -89,4 +95,8 @@
         return adapters;
     }
 
+    const ToggleInfo* Instance::GetToggleInfo(const char* toggleName) {
+        return mImpl->GetToggleInfo(toggleName);
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 44be88a..b7aa559 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -25,6 +25,7 @@
 #include "dawn_native/ErrorData.h"
 #include "dawn_native/Fence.h"
 #include "dawn_native/FenceSignalTracker.h"
+#include "dawn_native/Instance.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/Queue.h"
 #include "dawn_native/RenderPipeline.h"
@@ -50,7 +51,8 @@
 
     // DeviceBase
 
-    DeviceBase::DeviceBase(AdapterBase* adapter) : mAdapter(adapter) {
+    DeviceBase::DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor)
+        : mAdapter(adapter) {
         mCaches = std::make_unique<DeviceBase::Caches>();
         mFenceSignalTracker = std::make_unique<FenceSignalTracker>(this);
         mDynamicUploader = std::make_unique<DynamicUploader>(this);
@@ -257,6 +259,42 @@
         }
     }
 
+    void DeviceBase::ApplyToggleOverrides(const DeviceDescriptor* deviceDescriptor) {
+        ASSERT(deviceDescriptor);
+
+        for (const char* toggleName : deviceDescriptor->forceEnabledToggles) {
+            Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum(toggleName);
+            if (toggle != Toggle::InvalidEnum) {
+                mTogglesSet.SetToggle(toggle, true);
+            }
+        }
+
+        for (const char* toggleName : deviceDescriptor->forceDisabledToggles) {
+            Toggle toggle = GetAdapter()->GetInstance()->ToggleNameToEnum(toggleName);
+            if (toggle != Toggle::InvalidEnum) {
+                mTogglesSet.SetToggle(toggle, false);
+            }
+        }
+    }
+
+    std::vector<const char*> DeviceBase::GetTogglesUsed() const {
+        std::vector<const char*> togglesNameInUse(mTogglesSet.toggleBitset.count());
+
+        uint32_t index = 0;
+        for (uint32_t i : IterateBitSet(mTogglesSet.toggleBitset)) {
+            const char* toggleName =
+                GetAdapter()->GetInstance()->ToggleEnumToName(static_cast<Toggle>(i));
+            togglesNameInUse[index] = toggleName;
+            ++index;
+        }
+
+        return togglesNameInUse;
+    }
+
+    bool DeviceBase::IsToggleEnabled(Toggle toggle) const {
+        return mTogglesSet.IsEnabled(toggle);
+    }
+
     // Implementation details of object creation
 
     MaybeError DeviceBase::CreateBindGroupInternal(BindGroupBase** result,
@@ -361,4 +399,8 @@
         return mDynamicUploader.get();
     }
 
+    void DeviceBase::SetToggle(Toggle toggle, bool isEnabled) {
+        mTogglesSet.SetToggle(toggle, isEnabled);
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 2561073..583488f 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -19,6 +19,7 @@
 #include "dawn_native/Error.h"
 #include "dawn_native/Forward.h"
 #include "dawn_native/ObjectBase.h"
+#include "dawn_native/Toggles.h"
 
 #include "dawn_native/DawnNative.h"
 #include "dawn_native/dawn_platform.h"
@@ -36,7 +37,7 @@
 
     class DeviceBase {
       public:
-        DeviceBase(AdapterBase* adapter);
+        DeviceBase(AdapterBase* adapter, const DeviceDescriptor* descriptor);
         virtual ~DeviceBase();
 
         void HandleError(const char* message);
@@ -115,7 +116,13 @@
 
         ResultOrError<DynamicUploader*> GetDynamicUploader() const;
 
+        std::vector<const char*> GetTogglesUsed() const;
+        bool IsToggleEnabled(Toggle toggle) const;
+
       protected:
+        void SetToggle(Toggle toggle, bool isEnabled);
+        void ApplyToggleOverrides(const DeviceDescriptor* deviceDescriptor);
+
         std::unique_ptr<DynamicUploader> mDynamicUploader;
 
       private:
@@ -179,6 +186,8 @@
         dawn::DeviceErrorCallback mErrorCallback = nullptr;
         dawn::CallbackUserdata mErrorUserdata = 0;
         uint32_t mRefCount = 1;
+
+        TogglesSet mTogglesSet;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Instance.cpp b/src/dawn_native/Instance.cpp
index 9877264..0eedeaa 100644
--- a/src/dawn_native/Instance.cpp
+++ b/src/dawn_native/Instance.cpp
@@ -49,6 +49,28 @@
     }
 #endif  // defined(DAWN_ENABLE_BACKEND_VULKAN)
 
+    namespace {
+
+        struct ToggleEnumAndInfo {
+            Toggle toggle;
+            ToggleInfo info;
+        };
+
+        using ToggleEnumAndInfoList =
+            std::array<ToggleEnumAndInfo, static_cast<size_t>(Toggle::EnumCount)>;
+
+        static constexpr ToggleEnumAndInfoList kToggleNameAndInfoList = {
+            {{Toggle::EmulateStoreAndMSAAResolve,
+              {"emulate_store_and_msaa_resolve",
+               "Emulate storing into multisampled color attachments and doing MSAA resolve "
+               "simultaneously. This workaround is enabled by default on the Metal drivers that do "
+               "not support MTLStoreActionStoreAndMultisampleResolve. To support StoreOp::Store on "
+               "those platforms, we should do MSAA resolve in another render pass after ending the "
+               "previous one.",
+               "https://bugs.chromium.org/p/dawn/issues/detail?id=56"}}}};
+
+    }  // anonymous namespace
+
     // InstanceBase
 
     void InstanceBase::DiscoverDefaultAdapters() {
@@ -78,6 +100,53 @@
         return !ConsumedError(DiscoverAdaptersInternal(options));
     }
 
+    const char* InstanceBase::ToggleEnumToName(Toggle toggle) {
+        ASSERT(toggle != Toggle::InvalidEnum);
+
+        const ToggleEnumAndInfo& toggleNameAndInfo =
+            kToggleNameAndInfoList[static_cast<size_t>(toggle)];
+        ASSERT(toggleNameAndInfo.toggle == toggle);
+        return toggleNameAndInfo.info.name;
+    }
+
+    const ToggleInfo* InstanceBase::GetToggleInfo(const char* toggleName) {
+        ASSERT(toggleName);
+
+        EnsureToggleNameToEnumMapInitialized();
+
+        const auto& iter = mToggleNameToEnumMap.find(toggleName);
+        if (iter != mToggleNameToEnumMap.cend()) {
+            return &kToggleNameAndInfoList[static_cast<size_t>(iter->second)].info;
+        }
+        return nullptr;
+    }
+
+    Toggle InstanceBase::ToggleNameToEnum(const char* toggleName) {
+        ASSERT(toggleName);
+
+        EnsureToggleNameToEnumMapInitialized();
+
+        const auto& iter = mToggleNameToEnumMap.find(toggleName);
+        if (iter != mToggleNameToEnumMap.cend()) {
+            return kToggleNameAndInfoList[static_cast<size_t>(iter->second)].toggle;
+        }
+        return Toggle::InvalidEnum;
+    }
+
+    void InstanceBase::EnsureToggleNameToEnumMapInitialized() {
+        if (mToggleNameToEnumMapInitialized) {
+            return;
+        }
+
+        for (size_t index = 0; index < kToggleNameAndInfoList.size(); ++index) {
+            const ToggleEnumAndInfo& toggleNameAndInfo = kToggleNameAndInfoList[index];
+            ASSERT(index == static_cast<size_t>(toggleNameAndInfo.toggle));
+            mToggleNameToEnumMap[toggleNameAndInfo.info.name] = toggleNameAndInfo.toggle;
+        }
+
+        mToggleNameToEnumMapInitialized = true;
+    }
+
     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 895f710..5896f1b 100644
--- a/src/dawn_native/Instance.h
+++ b/src/dawn_native/Instance.h
@@ -17,8 +17,11 @@
 
 #include "dawn_native/Adapter.h"
 #include "dawn_native/BackendConnection.h"
+#include "dawn_native/Toggles.h"
 
+#include <array>
 #include <memory>
+#include <unordered_map>
 #include <vector>
 
 namespace dawn_native {
@@ -41,19 +44,33 @@
         // Used to handle error that happen up to device creation.
         bool ConsumedError(MaybeError maybeError);
 
+        // 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);
+        const char* ToggleEnumToName(Toggle toggle);
+
       private:
         // Lazily creates connections to all backends that have been compiled.
         void EnsureBackendConnections();
+
         // Finds the BackendConnection for `type` or returns an error.
         ResultOrError<BackendConnection*> FindBackend(BackendType type);
 
         MaybeError DiscoverAdaptersInternal(const AdapterDiscoveryOptionsBase* options);
 
+        void EnsureToggleNameToEnumMapInitialized();
+
         bool mBackendsConnected = false;
         bool mDiscoveredDefaultAdapters = false;
 
+        bool mToggleNameToEnumMapInitialized = false;
+
         std::vector<std::unique_ptr<BackendConnection>> mBackends;
         std::vector<std::unique_ptr<AdapterBase>> mAdapters;
+
+        std::unordered_map<std::string, Toggle> mToggleNameToEnumMap;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h
new file mode 100644
index 0000000..5bcda4d
--- /dev/null
+++ b/src/dawn_native/Toggles.h
@@ -0,0 +1,51 @@
+// 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_TOGGLES_H_
+#define DAWNNATIVE_TOGGLES_H_
+
+#include <bitset>
+
+#include "dawn_native/DawnNative.h"
+
+namespace dawn_native {
+
+    enum class Toggle {
+        EmulateStoreAndMSAAResolve = 0,
+
+        EnumCount = 1,
+        InvalidEnum = EnumCount,
+    };
+
+    // A wrapper of the bitset to store if a toggle is enabled or not. This wrapper provides the
+    // convenience to convert the enums of enum class Toggle to the indices of a bitset.
+    struct TogglesSet {
+        std::bitset<static_cast<size_t>(Toggle::EnumCount)> toggleBitset;
+
+        void SetToggle(Toggle toggle, bool enabled) {
+            ASSERT(toggle != Toggle::InvalidEnum);
+            const size_t toggleIndex = static_cast<size_t>(toggle);
+            toggleBitset.set(toggleIndex, enabled);
+        }
+
+        bool IsEnabled(Toggle toggle) const {
+            ASSERT(toggle != Toggle::InvalidEnum);
+            const size_t toggleIndex = static_cast<size_t>(toggle);
+            return toggleBitset.test(toggleIndex);
+        }
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_TOGGLES_H_
diff --git a/src/dawn_native/d3d12/AdapterD3D12.cpp b/src/dawn_native/d3d12/AdapterD3D12.cpp
index 61750c5..a5df7e4 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn_native/d3d12/AdapterD3D12.cpp
@@ -59,7 +59,7 @@
         return mBackend;
     }
 
-    ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl() {
+    ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
         ComPtr<ID3D12Device> d3d12Device;
         if (FAILED(mBackend->GetFunctions()->d3d12CreateDevice(
                 mHardwareAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12Device)))) {
@@ -67,7 +67,7 @@
         }
 
         ASSERT(d3d12Device != nullptr);
-        return new Device(this, d3d12Device);
+        return new Device(this, d3d12Device, descriptor);
     }
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/AdapterD3D12.h b/src/dawn_native/d3d12/AdapterD3D12.h
index 1ff8b6d..24f3dfc 100644
--- a/src/dawn_native/d3d12/AdapterD3D12.h
+++ b/src/dawn_native/d3d12/AdapterD3D12.h
@@ -31,7 +31,7 @@
         Backend* GetBackend() const;
 
       private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl() override;
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
 
         ComPtr<IDXGIAdapter1> mHardwareAdapter;
         Backend* mBackend;
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index bd30f25..a972903 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -43,8 +43,10 @@
         ASSERT(SUCCEEDED(hr));
     }
 
-    Device::Device(Adapter* adapter, ComPtr<ID3D12Device> d3d12Device)
-        : DeviceBase(adapter), mD3d12Device(d3d12Device) {
+    Device::Device(Adapter* adapter,
+                   ComPtr<ID3D12Device> d3d12Device,
+                   const DeviceDescriptor* descriptor)
+        : DeviceBase(adapter, descriptor), mD3d12Device(d3d12Device) {
         // Create device-global objects
         D3D12_COMMAND_QUEUE_DESC queueDesc = {};
         queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index a73ca6b..5287588 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -37,7 +37,9 @@
     // Definition of backend types
     class Device : public DeviceBase {
       public:
-        Device(Adapter* adapter, ComPtr<ID3D12Device> d3d12Device);
+        Device(Adapter* adapter,
+               ComPtr<ID3D12Device> d3d12Device,
+               const DeviceDescriptor* descriptor);
         ~Device();
 
         CommandBufferBase* CreateCommandBuffer(CommandEncoderBase* encoder) override;
diff --git a/src/dawn_native/metal/BackendMTL.mm b/src/dawn_native/metal/BackendMTL.mm
index 2b2f6e2..a605b1d 100644
--- a/src/dawn_native/metal/BackendMTL.mm
+++ b/src/dawn_native/metal/BackendMTL.mm
@@ -142,8 +142,8 @@
         }
 
       private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl() override {
-            return {new Device(this, mDevice)};
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
+            return {new Device(this, mDevice, descriptor)};
         }
 
         id<MTLDevice> mDevice = nil;
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 7d95ae9..b979963 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -47,7 +47,9 @@
         };
 
         // Creates an autoreleased MTLRenderPassDescriptor matching desc
-        MTLRenderPassDescriptor* CreateMTLRenderPassDescriptor(BeginRenderPassCmd* renderPass) {
+        MTLRenderPassDescriptor* CreateMTLRenderPassDescriptor(
+            BeginRenderPassCmd* renderPass,
+            bool shouldEmulateStoreAndMSAAResolve) {
             MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
 
             for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
@@ -67,20 +69,20 @@
                 descriptor.colorAttachments[i].level = attachmentInfo.view->GetBaseMipLevel();
                 descriptor.colorAttachments[i].slice = attachmentInfo.view->GetBaseArrayLayer();
 
-                ASSERT(attachmentInfo.storeOp == dawn::StoreOp::Store);
-                // TODO(jiawei.shao@intel.com): emulate MTLStoreActionStoreAndMultisampleResolve on
-                // the platforms that do not support this store action.
-                if (attachmentInfo.resolveTarget.Get() != nullptr) {
-                    descriptor.colorAttachments[i].resolveTexture =
-                        ToBackend(attachmentInfo.resolveTarget->GetTexture())->GetMTLTexture();
-                    descriptor.colorAttachments[i].resolveLevel =
-                        attachmentInfo.resolveTarget->GetBaseMipLevel();
-                    descriptor.colorAttachments[i].resolveSlice =
-                        attachmentInfo.resolveTarget->GetBaseArrayLayer();
-                    descriptor.colorAttachments[i].storeAction =
-                        MTLStoreActionStoreAndMultisampleResolve;
-                } else {
-                    descriptor.colorAttachments[i].storeAction = MTLStoreActionStore;
+                if (attachmentInfo.storeOp == dawn::StoreOp::Store) {
+                    if (attachmentInfo.resolveTarget.Get() != nullptr &&
+                        !shouldEmulateStoreAndMSAAResolve) {
+                        descriptor.colorAttachments[i].resolveTexture =
+                            ToBackend(attachmentInfo.resolveTarget->GetTexture())->GetMTLTexture();
+                        descriptor.colorAttachments[i].resolveLevel =
+                            attachmentInfo.resolveTarget->GetBaseMipLevel();
+                        descriptor.colorAttachments[i].resolveSlice =
+                            attachmentInfo.resolveTarget->GetBaseArrayLayer();
+                        descriptor.colorAttachments[i].storeAction =
+                            MTLStoreActionStoreAndMultisampleResolve;
+                    } else {
+                        descriptor.colorAttachments[i].storeAction = MTLStoreActionStore;
+                    }
                 }
             }
 
@@ -120,6 +122,43 @@
             return descriptor;
         }
 
+        // Do MSAA resolve in another render pass.
+        void ResolveInAnotherRenderPass(id<MTLCommandBuffer> commandBuffer,
+                                        BeginRenderPassCmd* renderPass) {
+            ASSERT(renderPass->sampleCount > 1);
+            MTLRenderPassDescriptor* renderPassForResolve = nil;
+            for (uint32_t i : IterateBitSet(renderPass->colorAttachmentsSet)) {
+                auto& attachmentInfo = renderPass->colorAttachments[i];
+                if (attachmentInfo.resolveTarget.Get() == nil ||
+                    attachmentInfo.storeOp != dawn::StoreOp::Store) {
+                    continue;
+                }
+
+                if (renderPassForResolve == nil) {
+                    renderPassForResolve = [MTLRenderPassDescriptor renderPassDescriptor];
+                }
+                renderPassForResolve.colorAttachments[i].texture =
+                    ToBackend(attachmentInfo.view->GetTexture())->GetMTLTexture();
+                renderPassForResolve.colorAttachments[i].level = 0;
+                renderPassForResolve.colorAttachments[i].slice = 0;
+
+                renderPassForResolve.colorAttachments[i].storeAction =
+                    MTLStoreActionMultisampleResolve;
+                renderPassForResolve.colorAttachments[i].resolveTexture =
+                    ToBackend(attachmentInfo.resolveTarget->GetTexture())->GetMTLTexture();
+                renderPassForResolve.colorAttachments[i].resolveLevel =
+                    attachmentInfo.resolveTarget->GetBaseMipLevel();
+                renderPassForResolve.colorAttachments[i].resolveSlice =
+                    attachmentInfo.resolveTarget->GetBaseArrayLayer();
+            }
+
+            if (renderPassForResolve != nil) {
+                id<MTLRenderCommandEncoder> encoder =
+                    [commandBuffer renderCommandEncoderWithDescriptor:renderPassForResolve];
+                [encoder endEncoding];
+            }
+        }
+
         // Handles a call to SetBindGroup, directing the commands to the correct encoder.
         // There is a single function that takes both encoders to factor code. Other approaches like
         // templates wouldn't work because the name of methods are different between the two encoder
@@ -599,9 +638,13 @@
         std::array<uint32_t, kMaxPushConstants> vertexPushConstants;
         std::array<uint32_t, kMaxPushConstants> fragmentPushConstants;
 
+        bool shouldEmulateStoreAndMSAAResolve =
+            GetDevice()->IsToggleEnabled(Toggle::EmulateStoreAndMSAAResolve);
         // This will be autoreleased
         id<MTLRenderCommandEncoder> encoder = [commandBuffer
-            renderCommandEncoderWithDescriptor:CreateMTLRenderPassDescriptor(renderPassCmd)];
+            renderCommandEncoderWithDescriptor:CreateMTLRenderPassDescriptor(
+                                                   renderPassCmd,
+                                                   shouldEmulateStoreAndMSAAResolve)];
 
         // Set default values for push constants
         vertexPushConstants.fill(0);
@@ -620,6 +663,9 @@
                 case Command::EndRenderPass: {
                     mCommands.NextCommand<EndRenderPassCmd>();
                     [encoder endEncoding];
+                    if (renderPassCmd->sampleCount > 1 && shouldEmulateStoreAndMSAAResolve) {
+                        ResolveInAnotherRenderPass(commandBuffer, renderPassCmd);
+                    }
                     return;
                 } break;
 
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index 87eec00..841f18b 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -34,7 +34,7 @@
 
     class Device : public DeviceBase {
       public:
-        Device(AdapterBase* adapter, id<MTLDevice> mtlDevice);
+        Device(AdapterBase* adapter, id<MTLDevice> mtlDevice, const DeviceDescriptor* descriptor);
         ~Device();
 
         CommandBufferBase* CreateCommandBuffer(CommandEncoderBase* encoder) override;
@@ -86,6 +86,8 @@
             TextureBase* texture,
             const TextureViewDescriptor* descriptor) override;
 
+        void InitTogglesFromDriver();
+
         id<MTLDevice> mMtlDevice = nil;
         id<MTLCommandQueue> mCommandQueue = nil;
         std::unique_ptr<MapRequestTracker> mMapTracker;
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index c53e662..e38d423 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -34,13 +34,20 @@
 
 namespace dawn_native { namespace metal {
 
-    Device::Device(AdapterBase* adapter, id<MTLDevice> mtlDevice)
-        : DeviceBase(adapter),
+    Device::Device(AdapterBase* adapter,
+                   id<MTLDevice> mtlDevice,
+                   const DeviceDescriptor* descriptor)
+        : DeviceBase(adapter, descriptor),
           mMtlDevice([mtlDevice retain]),
           mMapTracker(new MapRequestTracker(this)),
           mCompletedSerial(0) {
         [mMtlDevice retain];
         mCommandQueue = [mMtlDevice newCommandQueue];
+
+        InitTogglesFromDriver();
+        if (descriptor != nil) {
+            ApplyToggleOverrides(descriptor);
+        }
     }
 
     Device::~Device() {
@@ -67,6 +74,13 @@
         mMtlDevice = nil;
     }
 
+    void Device::InitTogglesFromDriver() {
+        // TODO(jiawei.shao@intel.com): check iOS feature sets
+        bool emulateStoreAndMSAAResolve =
+            ![mMtlDevice supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v2];
+        SetToggle(Toggle::EmulateStoreAndMSAAResolve, emulateStoreAndMSAAResolve);
+    }
+
     ResultOrError<BindGroupBase*> Device::CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) {
         return new BindGroup(this, descriptor);
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 3476f1a..15db5c2 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -33,8 +33,8 @@
         virtual ~Adapter() = default;
 
       private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl() override {
-            return {new Device(this)};
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
+            return {new Device(this, descriptor)};
         }
     };
 
@@ -58,7 +58,12 @@
 
     // Device
 
-    Device::Device(Adapter* adapter) : DeviceBase(adapter) {
+    Device::Device(Adapter* adapter, const DeviceDescriptor* descriptor)
+        : DeviceBase(adapter, descriptor) {
+        // Apply toggle overrides if necessary for test
+        if (descriptor != nullptr) {
+            ApplyToggleOverrides(descriptor);
+        }
     }
 
     Device::~Device() {
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index 5450b4b..a523f3d 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -82,7 +82,7 @@
 
     class Device : public DeviceBase {
       public:
-        Device(Adapter* adapter);
+        Device(Adapter* adapter, const DeviceDescriptor* descriptor);
         ~Device();
 
         CommandBufferBase* CreateCommandBuffer(CommandEncoderBase* encoder) override;
diff --git a/src/dawn_native/opengl/BackendGL.cpp b/src/dawn_native/opengl/BackendGL.cpp
index 1d69a4a..a74fef2 100644
--- a/src/dawn_native/opengl/BackendGL.cpp
+++ b/src/dawn_native/opengl/BackendGL.cpp
@@ -39,10 +39,10 @@
         virtual ~Adapter() = default;
 
       private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl() override {
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override {
             // There is no limit on the number of devices created from this adapter because they can
             // all share the same backing OpenGL context.
-            return {new Device(this)};
+            return {new Device(this, descriptor)};
         }
     };
 
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index 7dd761b..dcaf916 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -31,7 +31,8 @@
 
 namespace dawn_native { namespace opengl {
 
-    Device::Device(AdapterBase* adapter) : DeviceBase(adapter) {
+    Device::Device(AdapterBase* adapter, const DeviceDescriptor* descriptor)
+        : DeviceBase(adapter, descriptor) {
     }
 
     Device::~Device() {
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index 5725a8e..1f888d8 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -34,7 +34,7 @@
 
     class Device : public DeviceBase {
       public:
-        Device(AdapterBase* adapter);
+        Device(AdapterBase* adapter, const DeviceDescriptor* descriptor);
         ~Device();
 
         void SubmitFenceSync();
diff --git a/src/dawn_native/vulkan/AdapterVk.cpp b/src/dawn_native/vulkan/AdapterVk.cpp
index d87fdcc..614eeda 100644
--- a/src/dawn_native/vulkan/AdapterVk.cpp
+++ b/src/dawn_native/vulkan/AdapterVk.cpp
@@ -62,8 +62,8 @@
         return {};
     }
 
-    ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl() {
-        std::unique_ptr<Device> device = std::make_unique<Device>(this);
+    ResultOrError<DeviceBase*> Adapter::CreateDeviceImpl(const DeviceDescriptor* descriptor) {
+        std::unique_ptr<Device> device = std::make_unique<Device>(this, descriptor);
         DAWN_TRY(device->Initialize());
         return device.release();
     }
diff --git a/src/dawn_native/vulkan/AdapterVk.h b/src/dawn_native/vulkan/AdapterVk.h
index acc4f51..2d9ed83 100644
--- a/src/dawn_native/vulkan/AdapterVk.h
+++ b/src/dawn_native/vulkan/AdapterVk.h
@@ -36,7 +36,7 @@
         MaybeError Initialize();
 
       private:
-        ResultOrError<DeviceBase*> CreateDeviceImpl() override;
+        ResultOrError<DeviceBase*> CreateDeviceImpl(const DeviceDescriptor* descriptor) override;
 
         VkPhysicalDevice mPhysicalDevice;
         Backend* mBackend;
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 212adb0..7b91f59 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -40,7 +40,8 @@
 
 namespace dawn_native { namespace vulkan {
 
-    Device::Device(Adapter* adapter) : DeviceBase(adapter) {
+    Device::Device(Adapter* adapter, const DeviceDescriptor* descriptor)
+        : DeviceBase(adapter, descriptor) {
     }
 
     MaybeError Device::Initialize() {
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index 699aefd..f241eaa 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -38,7 +38,7 @@
 
     class Device : public DeviceBase {
       public:
-        Device(Adapter* adapter);
+        Device(Adapter* adapter, const DeviceDescriptor* descriptor);
         ~Device();
 
         MaybeError Initialize();
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index 7d9fa72..e159089 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -47,6 +47,22 @@
     class InstanceBase;
     class AdapterBase;
 
+    // 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*> forceEnabledToggles;
+        std::vector<const char*> forceDisabledToggles;
+    };
+
+    // A struct to record the information of a toggle. A toggle is a code path in Dawn device that
+    // can be manually configured to run or not outside Dawn, including workarounds, special
+    // features and optimizations.
+    struct ToggleInfo {
+        const char* name;
+        const char* description;
+        const char* url;
+    };
+
     // 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.
@@ -68,7 +84,7 @@
         // Create a device on this adapter, note that the interface will change to include at least
         // a device descriptor and a pointer to backend specific options.
         // On an error, nullptr is returned.
-        DawnDevice CreateDevice();
+        DawnDevice CreateDevice(const DeviceDescriptor* deviceDescriptor = nullptr);
 
       private:
         AdapterBase* mImpl = nullptr;
@@ -107,6 +123,8 @@
         // Returns all the adapters that the instance knows about.
         std::vector<Adapter> GetAdapters() const;
 
+        const ToggleInfo* GetToggleInfo(const char* toggleName);
+
       private:
         InstanceBase* mImpl = nullptr;
     };
@@ -114,6 +132,9 @@
     // Backend-agnostic API for dawn_native
     DAWN_NATIVE_EXPORT DawnProcTable GetProcs();
 
+    // Query the names of all the toggles that are enabled in device
+    DAWN_NATIVE_EXPORT std::vector<const char*> GetTogglesUsed(DawnDevice device);
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_DAWNNATIVE_H_
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 098bcee..af6e716 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -67,6 +67,12 @@
         }
     }
 
+    dawn_native::DeviceDescriptor InitWorkaround(const char* forceEnabledWorkaround) {
+        dawn_native::DeviceDescriptor deviceDescriptor;
+        deviceDescriptor.forceEnabledToggles.push_back(forceEnabledWorkaround);
+        return deviceDescriptor;
+    }
+
     struct MapReadUserdata {
         DawnTest* test;
         size_t slot;
@@ -83,6 +89,12 @@
 
 }  // namespace
 
+DawnTestParam ForceWorkaround(const DawnTestParam& originParam, const char* workaround) {
+    DawnTestParam newTestParam = originParam;
+    newTestParam.forceEnabledWorkaround = workaround;
+    return newTestParam;
+}
+
 // Implementation of DawnTestEnvironment
 
 void InitDawnEnd2EndTestEnvironment(int argc, char** argv) {
@@ -112,10 +124,10 @@
     mInstance = std::make_unique<dawn_native::Instance>();
 
     static constexpr dawn_native::BackendType kAllBackends[] = {
-        D3D12Backend,
-        MetalBackend,
-        OpenGLBackend,
-        VulkanBackend,
+        dawn_native::BackendType::D3D12,
+        dawn_native::BackendType::Metal,
+        dawn_native::BackendType::OpenGL,
+        dawn_native::BackendType::Vulkan,
     };
 
     // Create a test window for each backend and discover an adapter using it.
@@ -191,19 +203,19 @@
 }
 
 bool DawnTest::IsD3D12() const {
-    return GetParam() == D3D12Backend;
+    return GetParam().backendType == dawn_native::BackendType::D3D12;
 }
 
 bool DawnTest::IsMetal() const {
-    return GetParam() == MetalBackend;
+    return GetParam().backendType == dawn_native::BackendType::Metal;
 }
 
 bool DawnTest::IsOpenGL() const {
-    return GetParam() == OpenGLBackend;
+    return GetParam().backendType == dawn_native::BackendType::OpenGL;
 }
 
 bool DawnTest::IsVulkan() const {
-    return GetParam() == VulkanBackend;
+    return GetParam().backendType == dawn_native::BackendType::Vulkan;
 }
 
 bool DawnTest::IsAMD() const {
@@ -257,19 +269,20 @@
 void DawnTest::SetUp() {
     // Get an adapter for the backend to use, and create the device.
     dawn_native::Adapter backendAdapter;
+    const dawn_native::BackendType backendType = GetParam().backendType;
     {
         dawn_native::Instance* instance = gTestEnv->GetInstance();
         std::vector<dawn_native::Adapter> adapters = instance->GetAdapters();
 
         for (const dawn_native::Adapter& adapter : adapters) {
-            if (adapter.GetBackendType() == GetParam()) {
+            if (adapter.GetBackendType() == backendType) {
                 backendAdapter = adapter;
                 // On Metal, select the last adapter so that the discrete GPU is tested on
                 // multi-GPU systems.
                 // TODO(cwallez@chromium.org): Replace this with command line arguments requesting
                 // a specific device / vendor ID once the macOS 10.13 SDK is rolled and correct
                 // PCI info collection is implemented on Metal.
-                if (GetParam() != MetalBackend) {
+                if (backendType != dawn_native::BackendType::Metal) {
                     break;
                 }
             }
@@ -279,13 +292,22 @@
     }
 
     mPCIInfo = backendAdapter.GetPCIInfo();
-    DawnDevice backendDevice = backendAdapter.CreateDevice();
+
+    DawnDevice backendDevice;
+    const char* forceEnabledWorkaround = GetParam().forceEnabledWorkaround;
+    if (forceEnabledWorkaround != nullptr) {
+        dawn_native::DeviceDescriptor deviceDescriptor = InitWorkaround(forceEnabledWorkaround);
+        backendDevice = backendAdapter.CreateDevice(&deviceDescriptor);
+    } else {
+        backendDevice = backendAdapter.CreateDevice(nullptr);
+    }
+
     DawnProcTable backendProcs = dawn_native::GetProcs();
 
     // Get the test window and create the device using it (esp. for OpenGL)
-    GLFWwindow* testWindow = gTestEnv->GetWindowForBackend(GetParam());
+    GLFWwindow* testWindow = gTestEnv->GetWindowForBackend(backendType);
     DAWN_ASSERT(testWindow != nullptr);
-    mBinding.reset(utils::CreateBinding(GetParam(), testWindow, backendDevice));
+    mBinding.reset(utils::CreateBinding(backendType, testWindow, backendDevice));
     DAWN_ASSERT(mBinding != nullptr);
 
     // Choose whether to use the backend procs and devices directly, or set up the wire.
@@ -586,20 +608,26 @@
         }
     }
 
-    std::vector<dawn_native::BackendType> FilterBackends(const dawn_native::BackendType* types,
-                                                         size_t numParams) {
-        std::vector<dawn_native::BackendType> backends;
+    std::vector<DawnTestParam> FilterBackends(const DawnTestParam* params, size_t numParams) {
+        std::vector<DawnTestParam> backends;
 
         for (size_t i = 0; i < numParams; ++i) {
-            if (IsBackendAvailable(types[i])) {
-                backends.push_back(types[i]);
+            if (IsBackendAvailable(params[i].backendType)) {
+                backends.push_back(params[i]);
             }
         }
         return backends;
     }
 
-    std::string GetParamName(const testing::TestParamInfo<dawn_native::BackendType>& info) {
-        return ParamName(info.param);
+    std::string GetParamName(const testing::TestParamInfo<DawnTestParam>& info) {
+        std::ostringstream ostream;
+        ostream << ParamName(info.param.backendType);
+
+        if (info.param.forceEnabledWorkaround != nullptr) {
+            ostream << "_" << info.param.forceEnabledWorkaround;
+        }
+
+        return ostream.str();
     }
 
     // Helper classes to set expectations
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 6b2428d..a098a14 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -16,6 +16,7 @@
 #include "dawn_native/DawnNative.h"
 
 #include <gtest/gtest.h>
+
 #include <memory>
 #include <unordered_map>
 
@@ -60,11 +61,24 @@
 };
 std::ostream& operator<<(std::ostream& stream, const RGBA8& color);
 
+struct DawnTestParam {
+    constexpr explicit DawnTestParam(dawn_native::BackendType backendType)
+        : backendType(backendType) {
+    }
+
+    dawn_native::BackendType backendType;
+
+    // TODO(jiawei.shao@intel.com): support enabling and disabling multiple workarounds.
+    const char* forceEnabledWorkaround = nullptr;
+};
+
 // Shorthands for backend types used in the DAWN_INSTANTIATE_TEST
-static constexpr dawn_native::BackendType D3D12Backend = dawn_native::BackendType::D3D12;
-static constexpr dawn_native::BackendType MetalBackend = dawn_native::BackendType::Metal;
-static constexpr dawn_native::BackendType OpenGLBackend = dawn_native::BackendType::OpenGL;
-static constexpr dawn_native::BackendType VulkanBackend = dawn_native::BackendType::Vulkan;
+static constexpr DawnTestParam D3D12Backend(dawn_native::BackendType::D3D12);
+static constexpr DawnTestParam MetalBackend(dawn_native::BackendType::Metal);
+static constexpr DawnTestParam OpenGLBackend(dawn_native::BackendType::OpenGL);
+static constexpr DawnTestParam VulkanBackend(dawn_native::BackendType::Vulkan);
+
+DawnTestParam ForceWorkaround(const DawnTestParam& originParam, const char* workaround);
 
 struct GLFWwindow;
 
@@ -107,7 +121,7 @@
     std::unordered_map<dawn_native::BackendType, GLFWwindow*> mWindows;
 };
 
-class DawnTest : public ::testing::TestWithParam<dawn_native::BackendType> {
+class DawnTest : public ::testing::TestWithParam<DawnTestParam> {
   public:
     DawnTest();
     ~DawnTest();
@@ -242,9 +256,8 @@
 namespace detail {
     // Helper functions used for DAWN_INSTANTIATE_TEST
     bool IsBackendAvailable(dawn_native::BackendType type);
-    std::vector<dawn_native::BackendType> FilterBackends(const dawn_native::BackendType* types,
-                                                         size_t numParams);
-    std::string GetParamName(const testing::TestParamInfo<dawn_native::BackendType>& info);
+    std::vector<DawnTestParam> FilterBackends(const DawnTestParam* params, size_t numParams);
+    std::string GetParamName(const testing::TestParamInfo<DawnTestParam>& info);
 
     // All classes used to implement the deferred expectations should inherit from this.
     class Expectation {
diff --git a/src/tests/end2end/MultisampledRenderingTests.cpp b/src/tests/end2end/MultisampledRenderingTests.cpp
index ee8127b..ff66ed0 100644
--- a/src/tests/end2end/MultisampledRenderingTests.cpp
+++ b/src/tests/end2end/MultisampledRenderingTests.cpp
@@ -510,4 +510,9 @@
     VerifyResolveTarget(kGreen, resolveTexture2, kBaseMipLevel2, kBaseArrayLayer2);
 }
 
-DAWN_INSTANTIATE_TEST(MultisampledRenderingTest, D3D12Backend, OpenGLBackend, MetalBackend, VulkanBackend);
+DAWN_INSTANTIATE_TEST(MultisampledRenderingTest,
+                      D3D12Backend,
+                      MetalBackend,
+                      OpenGLBackend,
+                      VulkanBackend,
+                      ForceWorkaround(MetalBackend, "emulate_store_and_msaa_resolve"));
diff --git a/src/tests/unittests/RingBufferTests.cpp b/src/tests/unittests/RingBufferTests.cpp
index 9e903a6..92ad835 100644
--- a/src/tests/unittests/RingBufferTests.cpp
+++ b/src/tests/unittests/RingBufferTests.cpp
@@ -34,7 +34,7 @@
   protected:
     void SetUp() override {
         // TODO(bryan.bernhart@intel.com): Create this device through the adapter.
-        mDevice = std::make_unique<null::Device>(/*adapter*/ nullptr);
+        mDevice = std::make_unique<null::Device>(/*adapter*/ nullptr, /*deviceDescriptor*/ nullptr);
     }
 
     null::Device* GetDevice() const {
diff --git a/src/tests/unittests/validation/ToggleValidationTests.cpp b/src/tests/unittests/validation/ToggleValidationTests.cpp
new file mode 100644
index 0000000..1dfd545
--- /dev/null
+++ b/src/tests/unittests/validation/ToggleValidationTests.cpp
@@ -0,0 +1,72 @@
+// 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 "tests/unittests/validation/ValidationTest.h"
+
+namespace {
+
+class ToggleValidationTest : public ValidationTest {
+};
+
+// Tests querying the detail of a toggle from dawn_native::InstanceBase works correctly.
+TEST_F(ToggleValidationTest, QueryToggleInfo) {
+    // Query with a valid toggle name
+    {
+        const char* kValidToggleName = "emulate_store_and_msaa_resolve";
+        const dawn_native::ToggleInfo* toggleInfo = instance->GetToggleInfo(kValidToggleName);
+        ASSERT_NE(nullptr, toggleInfo);
+        ASSERT_NE(nullptr, toggleInfo->name);
+        ASSERT_NE(nullptr, toggleInfo->description);
+        ASSERT_NE(nullptr, toggleInfo->url);
+    }
+
+    // Query with an invalid toggle name
+    {
+        const char* kInvalidToggleName = "!@#$%^&*";
+        const dawn_native::ToggleInfo* toggleInfo = instance->GetToggleInfo(kInvalidToggleName);
+        ASSERT_EQ(nullptr, toggleInfo);
+    }
+}
+
+// Tests overriding toggles when creating a device works correctly.
+TEST_F(ToggleValidationTest, OverrideToggleUsage) {
+    // Dawn unittests use null Adapters, so there should be no toggles that are enabled by default
+    {
+        std::vector<const char*> toggleNames = dawn_native::GetTogglesUsed(device.Get());
+        ASSERT_EQ(0u, toggleNames.size());
+    }
+
+    // Create device with a valid name of a toggle
+    {
+        const char* kValidToggleName = "emulate_store_and_msaa_resolve";
+        dawn_native::DeviceDescriptor descriptor;
+        descriptor.forceEnabledToggles.push_back(kValidToggleName);
+
+        DawnDevice deviceWithToggle = adapter.CreateDevice(&descriptor);
+        std::vector<const char*> toggleNames = dawn_native::GetTogglesUsed(deviceWithToggle);
+        ASSERT_EQ(1u, toggleNames.size());
+        ASSERT_EQ(0, strcmp(kValidToggleName, toggleNames[0]));
+    }
+
+    // Create device with an invalid toggle name
+    {
+        dawn_native::DeviceDescriptor descriptor;
+        descriptor.forceEnabledToggles.push_back("!@#$%^&*");
+
+        DawnDevice deviceWithToggle = adapter.CreateDevice(&descriptor);
+        std::vector<const char*> toggleNames = dawn_native::GetTogglesUsed(deviceWithToggle);
+        ASSERT_EQ(0u, toggleNames.size());
+    }
+}
+}  // anonymous namespace
diff --git a/src/tests/unittests/validation/ValidationTest.cpp b/src/tests/unittests/validation/ValidationTest.cpp
index c673629..76b331a 100644
--- a/src/tests/unittests/validation/ValidationTest.cpp
+++ b/src/tests/unittests/validation/ValidationTest.cpp
@@ -16,28 +16,26 @@
 
 #include "common/Assert.h"
 #include "dawn/dawn.h"
-#include "dawn_native/DawnNative.h"
 #include "dawn_native/NullBackend.h"
 
 ValidationTest::ValidationTest() {
-    mInstance = std::make_unique<dawn_native::Instance>();
-    mInstance->DiscoverDefaultAdapters();
+    instance = std::make_unique<dawn_native::Instance>();
+    instance->DiscoverDefaultAdapters();
 
-    std::vector<dawn_native::Adapter> adapters = mInstance->GetAdapters();
+    std::vector<dawn_native::Adapter> adapters = instance->GetAdapters();
 
     // Validation tests run against the null backend, find the corresponding adapter
     bool foundNullAdapter = false;
-    dawn_native::Adapter nullAdapter;
-    for (auto adapter : adapters) {
-        if (adapter.GetBackendType() == dawn_native::BackendType::Null) {
-            nullAdapter = adapter;
+    for (auto &currentAdapter : adapters) {
+        if (currentAdapter.GetBackendType() == dawn_native::BackendType::Null) {
+            adapter = currentAdapter;
             foundNullAdapter = true;
             break;
         }
     }
 
     ASSERT(foundNullAdapter);
-    device = dawn::Device::Acquire(nullAdapter.CreateDevice());
+    device = dawn::Device::Acquire(adapter.CreateDevice());
 
     DawnProcTable procs = dawn_native::GetProcs();
     dawnSetProcs(&procs);
diff --git a/src/tests/unittests/validation/ValidationTest.h b/src/tests/unittests/validation/ValidationTest.h
index 72534c1..65b4fad 100644
--- a/src/tests/unittests/validation/ValidationTest.h
+++ b/src/tests/unittests/validation/ValidationTest.h
@@ -17,10 +17,7 @@
 
 #include "gtest/gtest.h"
 #include "dawn/dawncpp.h"
-
-namespace dawn_native {
-    class Instance;
-}
+#include "dawn_native/DawnNative.h"
 
 #define ASSERT_DEVICE_ERROR(statement) \
     StartExpectDeviceError(); \
@@ -55,10 +52,10 @@
 
     protected:
         dawn::Device device;
+        dawn_native::Adapter adapter;
+        std::unique_ptr<dawn_native::Instance> instance;
 
     private:
-        std::unique_ptr<dawn_native::Instance> mInstance;
-
         static void OnDeviceError(const char* message, DawnCallbackUserdata userdata);
         std::string mDeviceErrorMessage;
         bool mExpectError = false;