Implement queue-related futures on D3D

Also modifies EventTests to not leave incomplete futures on the
shared test instance. Instead, these tests make their own instance.
DawnTest ASSERTs that there are no dangling objects at the end of a
test. These futures would be dangling because the shared test instance
is never dropped.

Bug: dawn:2055, dawn:2054
Change-Id: Idf1b8f87352eed07b08f53bcf97517d8a7202674
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/156805
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/common/SerialMap.h b/src/dawn/common/SerialMap.h
index 412ae06..0ec4d96 100644
--- a/src/dawn/common/SerialMap.h
+++ b/src/dawn/common/SerialMap.h
@@ -29,6 +29,7 @@
 #define SRC_DAWN_COMMON_SERIALMAP_H_
 
 #include <map>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -59,6 +60,8 @@
     void Enqueue(Value&& value, Serial serial);
     void Enqueue(const std::vector<Value>& values, Serial serial);
     void Enqueue(std::vector<Value>&& values, Serial serial);
+
+    std::optional<Value> TakeOne(Serial serial);
 };
 
 // SerialMap
@@ -89,6 +92,24 @@
     }
 }
 
+template <typename Serial, typename Value>
+std::optional<Value> SerialMap<Serial, Value>::TakeOne(Serial serial) {
+    auto it = this->mStorage.find(serial);
+    if (it == this->mStorage.end()) {
+        return std::nullopt;
+    }
+    auto& vec = it->second;
+    if (vec.empty()) {
+        return std::nullopt;
+    }
+    Value value = std::move(vec.back());
+    vec.pop_back();
+    if (vec.empty()) {
+        this->mStorage.erase(it);
+    }
+    return value;
+}
+
 }  // namespace dawn
 
 #endif  // SRC_DAWN_COMMON_SERIALMAP_H_
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index 890dad5..86dc55e 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -440,6 +440,8 @@
       "d3d/PhysicalDeviceD3D.h",
       "d3d/PlatformFunctions.cpp",
       "d3d/PlatformFunctions.h",
+      "d3d/QueueD3D.cpp",
+      "d3d/QueueD3D.h",
       "d3d/ShaderUtils.cpp",
       "d3d/ShaderUtils.h",
       "d3d/SharedFenceD3D.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index e6ac5dd..6090a17 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -307,6 +307,8 @@
         "d3d/PhysicalDeviceD3D.h"
         "d3d/PlatformFunctions.cpp"
         "d3d/PlatformFunctions.h"
+        "d3d/QueueD3D.cpp"
+        "d3d/QueueD3D.h"
         "d3d/ShaderUtils.cpp"
         "d3d/ShaderUtils.h"
         "d3d/SharedFenceD3D.cpp"
diff --git a/src/dawn/native/EventManager.cpp b/src/dawn/native/EventManager.cpp
index 35be9ed..5bf71a4 100644
--- a/src/dawn/native/EventManager.cpp
+++ b/src/dawn/native/EventManager.cpp
@@ -75,6 +75,10 @@
         return mWrappedIt - rhs.mWrappedIt;
     }
 
+    SystemEventAndReadyStateIterator operator+(difference_type rhs) {
+        return SystemEventAndReadyStateIterator{mWrappedIt + rhs};
+    }
+
     SystemEventAndReadyStateIterator& operator++() {
         ++mWrappedIt;
         return *this;
diff --git a/src/dawn/native/SystemEvent.cpp b/src/dawn/native/SystemEvent.cpp
index c330870..e08ff7f 100644
--- a/src/dawn/native/SystemEvent.cpp
+++ b/src/dawn/native/SystemEvent.cpp
@@ -46,6 +46,9 @@
 
 // SystemEventReceiver
 
+SystemEventReceiver::SystemEventReceiver(SystemHandle primitive)
+    : mPrimitive(std::move(primitive)) {}
+
 SystemEventReceiver SystemEventReceiver::CreateAlreadySignaled() {
     SystemEventPipeSender sender;
     SystemEventReceiver receiver;
@@ -69,8 +72,7 @@
 void SystemEventPipeSender::Signal() && {
     DAWN_ASSERT(mPrimitive.IsValid());
 #if DAWN_PLATFORM_IS(WINDOWS)
-    // This is not needed on Windows yet. It's implementable using SetEvent().
-    DAWN_UNREACHABLE();
+    DAWN_CHECK(SetEvent(mPrimitive.Get()));
 #elif DAWN_PLATFORM_IS(POSIX)
     // Send one byte to signal the receiver
     char zero[1] = {0};
@@ -86,8 +88,21 @@
 
 std::pair<SystemEventPipeSender, SystemEventReceiver> CreateSystemEventPipe() {
 #if DAWN_PLATFORM_IS(WINDOWS)
-    // This is not needed on Windows yet. It's implementable using CreateEvent().
-    DAWN_UNREACHABLE();
+    HANDLE eventDup;
+    HANDLE event = CreateEvent(nullptr, /*bManualReset=*/true, /*bInitialState=*/false, nullptr);
+
+    DAWN_CHECK(event != nullptr);
+    DAWN_CHECK(DuplicateHandle(GetCurrentProcess(), event, GetCurrentProcess(), &eventDup, 0, FALSE,
+                               DUPLICATE_SAME_ACCESS));
+    DAWN_CHECK(eventDup != nullptr);
+
+    SystemEventReceiver receiver;
+    receiver.mPrimitive = SystemHandle::Acquire(event);
+
+    SystemEventPipeSender sender;
+    sender.mPrimitive = SystemHandle::Acquire(eventDup);
+
+    return std::make_pair(std::move(sender), std::move(receiver));
 #elif DAWN_PLATFORM_IS(POSIX)
     int pipeFds[2];
     int status = pipe(pipeFds);
diff --git a/src/dawn/native/SystemEvent.h b/src/dawn/native/SystemEvent.h
index 34c24d0..2a3d0c5 100644
--- a/src/dawn/native/SystemEvent.h
+++ b/src/dawn/native/SystemEvent.h
@@ -56,6 +56,7 @@
     static SystemEventReceiver CreateAlreadySignaled();
 
     SystemEventReceiver() = default;
+    explicit SystemEventReceiver(SystemHandle primitive);
     SystemEventReceiver(SystemEventReceiver&&) = default;
     SystemEventReceiver& operator=(SystemEventReceiver&&) = default;
 
diff --git a/src/dawn/native/WaitAnySystemEvent.h b/src/dawn/native/WaitAnySystemEvent.h
index be2ebe4..0e2336a 100644
--- a/src/dawn/native/WaitAnySystemEvent.h
+++ b/src/dawn/native/WaitAnySystemEvent.h
@@ -59,7 +59,7 @@
 }
 
 #if DAWN_PLATFORM_IS(WINDOWS)
-// #define ToMilliseconds ToMillisecondsGeneric<DWORD, INFINITE>
+#define ToMilliseconds ToMillisecondsGeneric<DWORD, INFINITE>
 #elif DAWN_PLATFORM_IS(POSIX)
 #define ToMilliseconds ToMillisecondsGeneric<int, -1>
 #endif
@@ -75,8 +75,22 @@
         return false;
     }
 #if DAWN_PLATFORM_IS(WINDOWS)
-    // TODO(crbug.com/dawn/2054): Implement this.
-    DAWN_CHECK(false);
+    StackVector<HANDLE, 4 /* avoid heap allocation for small waits */> handles;
+    handles->reserve(count);
+    for (auto it = begin; it != end; ++it) {
+        handles->push_back((*it).first.mPrimitive.Get());
+    }
+    DAWN_ASSERT(handles->size() <= MAXIMUM_WAIT_OBJECTS);
+    DWORD status = WaitForMultipleObjects(handles->size(), handles->data(), /*bWaitAll=*/false,
+                                          ToMilliseconds(timeout));
+    if (status == WAIT_TIMEOUT) {
+        return false;
+    }
+    DAWN_CHECK(WAIT_OBJECT_0 <= status && status < WAIT_OBJECT_0 + count);
+    const size_t completedIndex = status - WAIT_OBJECT_0;
+
+    *(*(begin + completedIndex)).second = true;
+    return true;
 #elif DAWN_PLATFORM_IS(POSIX)
     StackVector<pollfd, 4 /* avoid heap allocation for small waits */> pollfds;
     pollfds->reserve(count);
diff --git a/src/dawn/native/d3d/QueueD3D.cpp b/src/dawn/native/d3d/QueueD3D.cpp
new file mode 100644
index 0000000..f8615dc
--- /dev/null
+++ b/src/dawn/native/d3d/QueueD3D.cpp
@@ -0,0 +1,71 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/d3d/QueueD3D.h"
+
+#include <utility>
+
+#include "dawn/native/WaitAnySystemEvent.h"
+
+namespace dawn::native::d3d {
+
+Queue::~Queue() = default;
+
+ResultOrError<bool> Queue::WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) {
+    ExecutionSerial completedSerial = GetCompletedCommandSerial();
+    if (serial <= completedSerial) {
+        return true;
+    }
+
+    auto receiver = mSystemEventReceivers->TakeOne(serial);
+    if (!receiver) {
+        // Anytime we may create an event, clear out any completed receivers so the list doesn't
+        // grow forever.
+        mSystemEventReceivers->ClearUpTo(completedSerial);
+
+        HANDLE fenceEvent =
+            ::CreateEvent(nullptr, /*bManualReset=*/true, /*bInitialState=*/false, nullptr);
+        DAWN_INVALID_IF(fenceEvent == nullptr, "CreateEvent failed");
+        SetEventOnCompletion(serial, fenceEvent);
+
+        receiver = SystemEventReceiver(SystemHandle::Acquire(fenceEvent));
+    }
+
+    bool ready = false;
+    std::array<std::pair<const dawn::native::SystemEventReceiver&, bool*>, 1> events{
+        {{*receiver, &ready}}};
+    DAWN_ASSERT(serial <= GetLastSubmittedCommandSerial());
+    bool didComplete = WaitAnySystemEvent(events.begin(), events.end(), timeout);
+    if (!didComplete) {
+        // Return the SystemEventReceiver to the pool of receivers so it can be re-waited in the
+        // future.
+        mSystemEventReceivers->Enqueue(std::move(*receiver), serial);
+    }
+    return didComplete;
+}
+
+}  // namespace dawn::native::d3d
diff --git a/src/dawn/native/d3d/QueueD3D.h b/src/dawn/native/d3d/QueueD3D.h
new file mode 100644
index 0000000..0c185d4
--- /dev/null
+++ b/src/dawn/native/d3d/QueueD3D.h
@@ -0,0 +1,54 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_D3D_QUEUED3D_H_
+#define SRC_DAWN_NATIVE_D3D_QUEUED3D_H_
+
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/SerialMap.h"
+#include "dawn/common/windows_with_undefs.h"
+#include "dawn/native/Queue.h"
+#include "dawn/native/SystemEvent.h"
+
+namespace dawn::native::d3d {
+
+class Queue : public QueueBase {
+  public:
+    using QueueBase::QueueBase;
+    ~Queue() override;
+
+  private:
+    virtual void SetEventOnCompletion(ExecutionSerial serial, HANDLE event) = 0;
+
+    ResultOrError<bool> WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) override;
+
+    MutexProtected<SerialMap<ExecutionSerial, SystemEventReceiver>> mSystemEventReceivers;
+};
+
+}  // namespace dawn::native::d3d
+
+#endif  // SRC_DAWN_NATIVE_D3D_QUEUED3D_H_
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index c26bb98..2327804 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -158,6 +158,10 @@
     return mD3d11Device5.Get();
 }
 
+ID3D11Fence* Device::GetD3D11Fence() const {
+    return mFence.Get();
+}
+
 ScopedCommandRecordingContext Device::GetScopedPendingCommandContext(SubmitMode submitMode) {
     // Callers of GetPendingCommandList do so to record commands. Only reserve a command
     // allocator when it is needed so we don't submit empty command lists
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index ea61d94..f52be03 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -53,6 +53,7 @@
 
     ID3D11Device* GetD3D11Device() const;
     ID3D11Device5* GetD3D11Device5() const;
+    ID3D11Fence* GetD3D11Fence() const;
 
     ScopedCommandRecordingContext GetScopedPendingCommandContext(SubmitMode submitMode);
     ScopedSwapStateCommandRecordingContext GetScopedSwapStatePendingCommandContext(
diff --git a/src/dawn/native/d3d11/QueueD3D11.cpp b/src/dawn/native/d3d11/QueueD3D11.cpp
index fae81cd..1426bf4 100644
--- a/src/dawn/native/d3d11/QueueD3D11.cpp
+++ b/src/dawn/native/d3d11/QueueD3D11.cpp
@@ -27,6 +27,8 @@
 
 #include "dawn/native/d3d11/QueueD3D11.h"
 
+#include <utility>
+
 #include "dawn/native/d3d11/BufferD3D11.h"
 #include "dawn/native/d3d11/CommandBufferD3D11.h"
 #include "dawn/native/d3d11/DeviceD3D11.h"
@@ -119,4 +121,10 @@
     return ToBackend(GetDevice())->WaitForIdleForDestruction();
 }
 
+void Queue::SetEventOnCompletion(ExecutionSerial serial, HANDLE event) {
+    ToBackend(GetDevice())
+        ->GetD3D11Fence()
+        ->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
+}
+
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/QueueD3D11.h b/src/dawn/native/d3d11/QueueD3D11.h
index 32538b5..ca76404 100644
--- a/src/dawn/native/d3d11/QueueD3D11.h
+++ b/src/dawn/native/d3d11/QueueD3D11.h
@@ -28,18 +28,21 @@
 #ifndef SRC_DAWN_NATIVE_D3D11_QUEUED3D11_H_
 #define SRC_DAWN_NATIVE_D3D11_QUEUED3D11_H_
 
-#include "dawn/native/Queue.h"
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/SerialMap.h"
+#include "dawn/native/SystemEvent.h"
+#include "dawn/native/d3d/QueueD3D.h"
 
 namespace dawn::native::d3d11 {
 
 class Device;
 
-class Queue final : public QueueBase {
+class Queue final : public d3d::Queue {
   public:
     static Ref<Queue> Create(Device* device, const QueueDescriptor* descriptor);
 
   private:
-    using QueueBase::QueueBase;
+    using d3d::Queue::Queue;
 
     ~Queue() override = default;
 
@@ -57,6 +60,8 @@
     ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
     void ForceEventualFlushOfCommands() override;
     MaybeError WaitForIdleForDestruction() override;
+
+    void SetEventOnCompletion(ExecutionSerial serial, HANDLE event) override;
 };
 
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 61edb53..75c5ac5 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -217,6 +217,10 @@
     return mD3d12Device.Get();
 }
 
+ID3D12Fence* Device::GetD3D12Fence() const {
+    return mFence.Get();
+}
+
 ComPtr<ID3D12CommandQueue> Device::GetCommandQueue() const {
     return mCommandQueue;
 }
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index 05ba3c4..6fe7dcf 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -76,6 +76,7 @@
     MaybeError TickImpl() override;
 
     ID3D12Device* GetD3D12Device() const;
+    ID3D12Fence* GetD3D12Fence() const;
     ComPtr<ID3D12CommandQueue> GetCommandQueue() const;
     ID3D12SharingContract* GetSharingContract() const;
 
diff --git a/src/dawn/native/d3d12/QueueD3D12.cpp b/src/dawn/native/d3d12/QueueD3D12.cpp
index 215164a..4d2dc9f 100644
--- a/src/dawn/native/d3d12/QueueD3D12.cpp
+++ b/src/dawn/native/d3d12/QueueD3D12.cpp
@@ -27,6 +27,8 @@
 
 #include "dawn/native/d3d12/QueueD3D12.h"
 
+#include <utility>
+
 #include "dawn/common/Math.h"
 #include "dawn/native/CommandValidation.h"
 #include "dawn/native/Commands.h"
@@ -47,8 +49,6 @@
     return queue;
 }
 
-Queue::Queue(Device* device, const QueueDescriptor* descriptor) : QueueBase(device, descriptor) {}
-
 void Queue::Initialize() {
     SetLabelImpl();
 }
@@ -97,4 +97,10 @@
     SetDebugName(device, device->GetCommandQueue().Get(), "Dawn_Queue", GetLabel());
 }
 
+void Queue::SetEventOnCompletion(ExecutionSerial serial, HANDLE event) {
+    ToBackend(GetDevice())
+        ->GetD3D12Fence()
+        ->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
+}
+
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/QueueD3D12.h b/src/dawn/native/d3d12/QueueD3D12.h
index 3460216..2d00586 100644
--- a/src/dawn/native/d3d12/QueueD3D12.h
+++ b/src/dawn/native/d3d12/QueueD3D12.h
@@ -28,8 +28,10 @@
 #ifndef SRC_DAWN_NATIVE_D3D12_QUEUED3D12_H_
 #define SRC_DAWN_NATIVE_D3D12_QUEUED3D12_H_
 
-#include "dawn/native/Queue.h"
-
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/SerialMap.h"
+#include "dawn/native/SystemEvent.h"
+#include "dawn/native/d3d/QueueD3D.h"
 #include "dawn/native/d3d12/CommandRecordingContext.h"
 #include "dawn/native/d3d12/d3d12_platform.h"
 
@@ -37,12 +39,12 @@
 
 class Device;
 
-class Queue final : public QueueBase {
+class Queue final : public d3d::Queue {
   public:
     static Ref<Queue> Create(Device* device, const QueueDescriptor* descriptor);
 
   private:
-    Queue(Device* device, const QueueDescriptor* descriptor);
+    using d3d::Queue::Queue;
 
     void Initialize();
 
@@ -52,6 +54,8 @@
     void ForceEventualFlushOfCommands() override;
     MaybeError WaitForIdleForDestruction() override;
 
+    void SetEventOnCompletion(ExecutionSerial serial, HANDLE event) override;
+
     // Dawn API
     void SetLabelImpl() override;
 };
diff --git a/src/dawn/tests/end2end/BufferTests.cpp b/src/dawn/tests/end2end/BufferTests.cpp
index 40bfa69..f89b5b7 100644
--- a/src/dawn/tests/end2end/BufferTests.cpp
+++ b/src/dawn/tests/end2end/BufferTests.cpp
@@ -668,7 +668,7 @@
 
 DAWN_INSTANTIATE_PREFIXED_TEST_P(Future,
                                  BufferMappingTests,
-                                 {MetalBackend(), VulkanBackend()},
+                                 {D3D11Backend(), D3D12Backend(), MetalBackend(), VulkanBackend()},
                                  std::initializer_list<std::optional<wgpu::CallbackMode>>{
                                      wgpu::CallbackMode::WaitAnyOnly,
                                      wgpu::CallbackMode::AllowProcessEvents,
@@ -880,7 +880,7 @@
 
 DAWN_INSTANTIATE_PREFIXED_TEST_P(Future,
                                  BufferMappingCallbackTests,
-                                 {MetalBackend(), VulkanBackend()},
+                                 {D3D11Backend(), D3D12Backend(), MetalBackend(), VulkanBackend()},
                                  std::initializer_list<std::optional<wgpu::CallbackMode>>{
                                      wgpu::CallbackMode::WaitAnyOnly,
                                      wgpu::CallbackMode::AllowProcessEvents,
diff --git a/src/dawn/tests/end2end/EventTests.cpp b/src/dawn/tests/end2end/EventTests.cpp
index b4e5141..389512a 100644
--- a/src/dawn/tests/end2end/EventTests.cpp
+++ b/src/dawn/tests/end2end/EventTests.cpp
@@ -37,7 +37,7 @@
 namespace dawn {
 namespace {
 
-std::pair<wgpu::Instance, wgpu::Device> CreateExtraInstance(wgpu::InstanceDescriptor* desc) {
+wgpu::Device CreateExtraDevice(wgpu::Instance instance) {
     // IMPORTANT: DawnTest overrides RequestAdapter and RequestDevice and mixes
     // up the two instances. We use these to bypass the override.
     auto* requestAdapter = reinterpret_cast<WGPUProcInstanceRequestAdapter>(
@@ -45,11 +45,9 @@
     auto* requestDevice = reinterpret_cast<WGPUProcAdapterRequestDevice>(
         wgpuGetProcAddress(nullptr, "wgpuAdapterRequestDevice"));
 
-    wgpu::Instance instance2 = wgpu::CreateInstance(desc);
-
     wgpu::Adapter adapter2;
     requestAdapter(
-        instance2.Get(), nullptr,
+        instance.Get(), nullptr,
         [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char*, void* userdata) {
             ASSERT_EQ(status, WGPURequestAdapterStatus_Success);
             *reinterpret_cast<wgpu::Adapter*>(userdata) = wgpu::Adapter::Acquire(adapter);
@@ -67,6 +65,15 @@
         &device2);
     DAWN_ASSERT(device2);
 
+    return device2;
+}
+
+std::pair<wgpu::Instance, wgpu::Device> CreateExtraInstance(wgpu::InstanceDescriptor* desc) {
+    wgpu::Instance instance2 = wgpu::CreateInstance(desc);
+
+    wgpu::Device device2 = CreateExtraDevice(instance2);
+    DAWN_ASSERT(device2);
+
     return std::pair(std::move(instance2), std::move(device2));
 }
 
@@ -460,7 +467,7 @@
 
 DAWN_INSTANTIATE_TEST_P(EventCompletionTests,
                         // TODO(crbug.com/dawn/2058): Enable tests for the rest of the backends.
-                        {MetalBackend(), VulkanBackend()},
+                        {D3D11Backend(), D3D12Backend(), MetalBackend(), VulkanBackend()},
                         {
                             WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly,
                             WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous,
@@ -525,17 +532,34 @@
 }
 
 TEST_P(WaitAnyTests, UnsupportedCount) {
+    wgpu::Instance instance2;
+    wgpu::Device device2;
+    wgpu::Queue queue2;
+
+    if (UsesWire()) {
+        // The wire (currently) never supports timedWaitAnyEnable, so we can run this test on the
+        // default instance/device.
+        instance2 = GetInstance();
+        device2 = device;
+        queue2 = queue;
+    } else {
+        wgpu::InstanceDescriptor desc;
+        desc.features.timedWaitAnyEnable = true;
+        std::tie(instance2, device2) = CreateExtraInstance(&desc);
+        queue2 = device2.GetQueue();
+    }
+
     for (uint64_t timeout : {uint64_t(0), uint64_t(1)}) {
         // We don't support values higher than the default (64), and if you ask for lower than 64
         // you still get 64. DawnTest doesn't request anything (so requests 0) so gets 64.
         for (size_t count : {kTimedWaitAnyMaxCountDefault, kTimedWaitAnyMaxCountDefault + 1}) {
             std::vector<wgpu::FutureWaitInfo> infos;
             for (size_t i = 0; i < count; ++i) {
-                infos.push_back(
-                    {queue.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::WaitAnyOnly,
-                                                 [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})});
+                infos.push_back({queue2.OnSubmittedWorkDoneF(
+                    {nullptr, wgpu::CallbackMode::WaitAnyOnly,
+                     [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})});
             }
-            wgpu::WaitStatus status = GetInstance().WaitAny(infos.size(), infos.data(), timeout);
+            wgpu::WaitStatus status = instance2.WaitAny(infos.size(), infos.data(), timeout);
             if (timeout == 0) {
                 ASSERT_TRUE(status == wgpu::WaitStatus::Success ||
                             status == wgpu::WaitStatus::TimedOut);
@@ -552,16 +576,37 @@
 }
 
 TEST_P(WaitAnyTests, UnsupportedMixedSources) {
-    wgpu::Device device2 = CreateDevice();
-    wgpu::Queue queue2 = device2.GetQueue();
+    wgpu::Instance instance2;
+    wgpu::Device device2;
+    wgpu::Queue queue2;
+    wgpu::Device device3;
+    wgpu::Queue queue3;
+
+    if (UsesWire()) {
+        // The wire (currently) never supports timedWaitAnyEnable, so we can run this test on the
+        // default instance/device.
+        instance2 = GetInstance();
+        device2 = device;
+        queue2 = queue;
+        device3 = CreateDevice();
+        queue3 = device3.GetQueue();
+    } else {
+        wgpu::InstanceDescriptor desc;
+        desc.features.timedWaitAnyEnable = true;
+        std::tie(instance2, device2) = CreateExtraInstance(&desc);
+        queue2 = device2.GetQueue();
+        device3 = CreateExtraDevice(instance2);
+        queue3 = device3.GetQueue();
+    }
+
     for (uint64_t timeout : {uint64_t(0), uint64_t(1)}) {
         std::vector<wgpu::FutureWaitInfo> infos{{
-            {queue.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::WaitAnyOnly,
-                                         [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})},
             {queue2.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::WaitAnyOnly,
                                           [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})},
+            {queue3.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::WaitAnyOnly,
+                                          [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})},
         }};
-        wgpu::WaitStatus status = GetInstance().WaitAny(infos.size(), infos.data(), timeout);
+        wgpu::WaitStatus status = instance2.WaitAny(infos.size(), infos.data(), timeout);
         if (timeout == 0) {
             ASSERT_TRUE(status == wgpu::WaitStatus::Success ||
                         status == wgpu::WaitStatus::TimedOut);
@@ -576,6 +621,8 @@
 
 DAWN_INSTANTIATE_TEST(WaitAnyTests,
                       // TODO(crbug.com/dawn/2058): Enable tests for the rest of the backends.
+                      D3D11Backend(),
+                      D3D12Backend(),
                       MetalBackend(),
                       VulkanBackend());