Add WGPUFuture, and implement for WorkDone in Metal + wire
This implements the WGPUFuture version of OnSubmittedWorkDone for the
Metal backend, as well as in the wire (over the old implementation).
This is a partial implementation of WGPUFuture. It sits alongside the
old API until it's complete and has all of the breaking API changes
we're going to make. (See bug dawn:2021.)
For more on WGPUFuture, see the design doc in bug dawn:1987.
Bug: dawn:1987, dawn:2021
Change-Id: If0c82279addf419ef16cc0280416ce2bfb71895f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/137502
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/CPPLINT.cfg b/CPPLINT.cfg
index 407dec8..c19a658 100644
--- a/CPPLINT.cfg
+++ b/CPPLINT.cfg
@@ -1,5 +1,8 @@
set noparent
+# Dawn has something called CHECK, but it doesn't have everything Chromium has (like CHECK_GE).
+filter=-readability/check
+
# This set of removals is set to match the set of
# OFF_UNLESS_MANUALLY_ENABLED_LINT_FEATURES from the depot_tools
# presubmit_canned_checks.py file.
diff --git a/dawn.json b/dawn.json
index 2b2685f..df50b2d 100644
--- a/dawn.json
+++ b/dawn.json
@@ -29,6 +29,7 @@
"create instance": {
"category": "function",
"returns": "instance",
+ "_comment": "TODO(crbug.com/dawn/1987): The return type should be nullable; null is returned in error cases.",
"args": [
{"name": "descriptor", "type": "instance descriptor", "annotation": "const*", "optional": true}
]
@@ -42,7 +43,7 @@
"category": "function",
"returns": "proc",
"args": [
- {"name": "device", "type": "device"},
+ {"name": "device", "type": "device", "optional": true},
{"name": "proc name", "type": "char", "annotation": "const*"}
]
},
@@ -1951,6 +1952,15 @@
"name": "process events"
},
{
+ "name": "wait any",
+ "returns": "wait status",
+ "args": [
+ {"name": "future count", "type": "size_t"},
+ {"name": "futures", "type": "future wait info", "annotation": "*", "length": "future count"},
+ {"name": "timeout NS", "type": "uint64_t"}
+ ]
+ },
+ {
"name": "request adapter",
"args": [
{"name": "options", "type": "request adapter options", "annotation": "const*", "optional": true, "no_default": true},
@@ -1960,10 +1970,63 @@
}
]
},
+ "callback mode": {
+ "_comment": "TODO(crbug.com/dawn/1987): Change this to an enum, and always return a future (https://github.com/webgpu-native/webgpu-headers/issues/199#issuecomment-1710784711).",
+ "category": "bitmask",
+ "values": [
+ {"name": "future", "value": 1},
+ {"name": "process events", "value": 2},
+ {"name": "spontaneous", "value": 4}
+ ]
+ },
+ "future": {
+ "category": "structure",
+ "members": [
+ {"name": "id", "type": "uint64_t"}
+ ]
+ },
+ "wait status": {
+ "category": "enum",
+ "_comment": "TODO(crbug.com/dawn/1987): This could be possibly be [[nodiscard]].",
+ "emscripten_no_enum_table": true,
+ "values": [
+ {"name": "success", "value": 0},
+ {"name": "timed out", "value": 1},
+ {"name": "unsupported timeout", "value": 2},
+ {"name": "unsupported count", "value": 3},
+ {"name": "unsupported mixed sources", "value": 4},
+ {"name": "unknown", "value": 5}
+ ]
+ },
+ "future wait info": {
+ "category": "structure",
+ "members": [
+ {"name": "future", "type": "future"},
+ {"name": "completed", "type": "bool", "default": "false"}
+ ]
+ },
+ "instance features": {
+ "category": "structure",
+ "extensible": "in",
+ "members": [
+ {"name": "timed wait any enable", "type": "bool", "default": "false"},
+ {"name": "timed wait any max count", "type": "size_t", "default": "0"}
+ ]
+ },
"instance descriptor": {
"category": "structure",
"extensible": "in",
- "members": []
+ "members": [
+ {"name": "features", "type": "instance features"}
+ ]
+ },
+ "get instance features": {
+ "category": "function",
+ "_comment": "TODO(crbug.com/dawn/1987): Figure out how to return error codes for functions like this (https://github.com/webgpu-native/webgpu-headers/issues/115).",
+ "returns": "bool",
+ "args": [
+ {"name": "features", "type": "instance features", "annotation": "*"}
+ ]
},
"vertex attribute": {
"category": "structure",
@@ -2198,6 +2261,15 @@
]
},
{
+ "name": "on submitted work done f",
+ "_comment": "TODO(crbug.com/dawn/2021): This is dawn/emscripten-only until we rename it to replace the old API. See bug for details.",
+ "tags": ["dawn", "emscripten"],
+ "returns": "future",
+ "args": [
+ {"name": "callback info", "type": "queue work done callback info"}
+ ]
+ },
+ {
"name": "write buffer",
"args": [
{"name": "buffer", "type": "buffer"},
@@ -2261,6 +2333,15 @@
{"name": "userdata", "type": "void *"}
]
},
+ "queue work done callback info": {
+ "category": "structure",
+ "extensible": "in",
+ "members": [
+ {"name": "mode", "type": "callback mode"},
+ {"name": "callback", "type": "queue work done callback"},
+ {"name": "userdata", "type": "void *"}
+ ]
+ },
"queue work done status": {
"category": "enum",
"emscripten_no_enum_table": true,
diff --git a/dawn_wire.json b/dawn_wire.json
index 5b78ea5..9137f2d 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -172,6 +172,7 @@
},
"special items": {
"client_side_structures": [
+ "FutureWaitInfo",
"SurfaceDescriptorFromMetalLayer",
"SurfaceDescriptorFromWindowsHWND",
"SurfaceDescriptorFromXlibWindow",
@@ -207,6 +208,7 @@
"QuerySetGetType",
"QuerySetGetCount",
"QueueOnSubmittedWorkDone",
+ "QueueOnSubmittedWorkDoneF",
"QueueWriteBuffer",
"QueueWriteTexture",
"TextureGetWidth",
@@ -226,6 +228,8 @@
"DeviceGetQueue",
"DeviceGetSupportedSurfaceUsage",
"DeviceInjectError",
+ "InstanceProcessEvents",
+ "InstanceWaitAny",
"SwapChainGetCurrentTexture"
],
"client_special_objects": [
diff --git a/generator/templates/api.h b/generator/templates/api.h
index d3f7735..1f98bac 100644
--- a/generator/templates/api.h
+++ b/generator/templates/api.h
@@ -188,7 +188,9 @@
{% for function in by_category["function"] %}
{{API}}_EXPORT {{as_cType(function.return_type.name)}} {{as_cMethod(None, function.name)}}(
{%- for arg in function.arguments -%}
- {% if not loop.first %}, {% endif %}{{as_annotated_cType(arg)}}
+ {% if not loop.first %}, {% endif -%}
+ {%- if arg.optional %}{{API}}_NULLABLE {% endif -%}
+ {{as_annotated_cType(arg)}}
{%- endfor -%}
) {{API}}_FUNCTION_ATTRIBUTE;
{% endfor %}
diff --git a/generator/templates/api_cpp.cpp b/generator/templates/api_cpp.cpp
index c326363..68caffe 100644
--- a/generator/templates/api_cpp.cpp
+++ b/generator/templates/api_cpp.cpp
@@ -92,6 +92,8 @@
{{as_varName(arg.name)}}.Get()
{%- elif arg.type.category == "enum" or arg.type.category == "bitmask" -%}
static_cast<{{as_cType(arg.type.name)}}>({{as_varName(arg.name)}})
+ {%- elif arg.type.category == "structure" -%}
+ *reinterpret_cast<{{as_cType(arg.type.name)}} const*>(&{{as_varName(arg.name)}})
{%- elif arg.type.category in ["function pointer", "native"] -%}
{{as_varName(arg.name)}}
{%- else -%}
diff --git a/generator/templates/dawn/wire/client/ApiProcs.cpp b/generator/templates/dawn/wire/client/ApiProcs.cpp
index 82bb340..ce227bf 100644
--- a/generator/templates/dawn/wire/client/ApiProcs.cpp
+++ b/generator/templates/dawn/wire/client/ApiProcs.cpp
@@ -14,6 +14,7 @@
#include "dawn/wire/client/ApiObjects.h"
#include "dawn/wire/client/Client.h"
+#include "dawn/wire/client/Instance.h"
#include <algorithm>
#include <cstring>
@@ -122,11 +123,6 @@
{% endfor %}
namespace {
- WGPUInstance ClientCreateInstance(WGPUInstanceDescriptor const* descriptor) {
- UNREACHABLE();
- return nullptr;
- }
-
struct ProcEntry {
WGPUProc proc;
const char* name;
@@ -154,15 +150,14 @@
return entry->proc;
}
- // Special case the two free-standing functions of the API.
- if (strcmp(procName, "wgpuGetProcAddress") == 0) {
- return reinterpret_cast<WGPUProc>(ClientGetProcAddress);
- }
+ // Special case the free-standing functions of the API.
+ // TODO(dawn:1238) Checking string one by one is slow, it needs to be optimized.
+ {% for function in by_category["function"] %}
+ if (strcmp(procName, "{{as_cMethod(None, function.name)}}") == 0) {
+ return reinterpret_cast<WGPUProc>(Client{{as_cppType(function.name)}});
+ }
- if (strcmp(procName, "wgpuCreateInstance") == 0) {
- return reinterpret_cast<WGPUProc>(ClientCreateInstance);
- }
-
+ {% endfor %}
return nullptr;
}
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn
index cacfd25..bd5dc19 100644
--- a/src/dawn/common/BUILD.gn
+++ b/src/dawn/common/BUILD.gn
@@ -242,6 +242,8 @@
"CoreFoundationRef.h",
"DynamicLib.cpp",
"DynamicLib.h",
+ "FutureUtils.cpp",
+ "FutureUtils.h",
"GPUInfo.cpp",
"GPUInfo.h",
"HashUtils.h",
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt
index aa20870..6ebe743 100644
--- a/src/dawn/common/CMakeLists.txt
+++ b/src/dawn/common/CMakeLists.txt
@@ -45,6 +45,8 @@
"CoreFoundationRef.h"
"DynamicLib.cpp"
"DynamicLib.h"
+ "FutureUtils.cpp"
+ "FutureUtils.h"
"GPUInfo.cpp"
"GPUInfo.h"
"HashUtils.h"
diff --git a/src/dawn/common/FutureUtils.cpp b/src/dawn/common/FutureUtils.cpp
new file mode 100644
index 0000000..db5d44b
--- /dev/null
+++ b/src/dawn/common/FutureUtils.cpp
@@ -0,0 +1,40 @@
+// Copyright 2023 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 "dawn/common/FutureUtils.h"
+
+#include "dawn/common/Assert.h"
+
+namespace dawn {
+
+CallbackMode ValidateAndFlattenCallbackMode(WGPUCallbackModeFlags mode) {
+ switch (mode) {
+ case WGPUCallbackMode_Spontaneous:
+ return CallbackMode::Spontaneous;
+ case WGPUCallbackMode_Future:
+ return CallbackMode::Future;
+ case WGPUCallbackMode_Future | WGPUCallbackMode_Spontaneous:
+ return CallbackMode::FutureOrSpontaneous;
+ case WGPUCallbackMode_ProcessEvents:
+ return CallbackMode::ProcessEvents;
+ case WGPUCallbackMode_ProcessEvents | WGPUCallbackMode_Spontaneous:
+ return CallbackMode::ProcessEventsOrSpontaneous;
+ default:
+ // These cases are undefined behaviors according to the API contract.
+ ASSERT(false);
+ return CallbackMode::Spontaneous;
+ }
+}
+
+} // namespace dawn
diff --git a/src/dawn/common/FutureUtils.h b/src/dawn/common/FutureUtils.h
new file mode 100644
index 0000000..a062735
--- /dev/null
+++ b/src/dawn/common/FutureUtils.h
@@ -0,0 +1,51 @@
+// Copyright 2023 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 SRC_DAWN_COMMON_FUTUREUTILS_H_
+#define SRC_DAWN_COMMON_FUTUREUTILS_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "dawn/webgpu.h"
+
+namespace dawn {
+
+using FutureID = uint64_t;
+constexpr FutureID kNullFutureID = 0;
+
+constexpr size_t kTimedWaitAnyMaxCountDefault = 64;
+
+enum class EventCompletionType {
+ // The event is completing because it became ready.
+ Ready,
+ // The event is completing because the instance is shutting down.
+ Shutdown,
+};
+
+// Flattened version of the wgpu::CallbackMode flags.
+// (This will disappear when that API changes to use an enum instead of flags.)
+enum class [[nodiscard]] CallbackMode {
+ Spontaneous,
+ Future,
+ FutureOrSpontaneous,
+ ProcessEvents,
+ ProcessEventsOrSpontaneous,
+};
+
+CallbackMode ValidateAndFlattenCallbackMode(WGPUCallbackModeFlags mode);
+
+} // namespace dawn
+
+#endif // SRC_DAWN_COMMON_FUTUREUTILS_H_
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index 2bd4a68..91a37a6 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -266,6 +266,8 @@
"ErrorInjector.h",
"ErrorScope.cpp",
"ErrorScope.h",
+ "EventManager.cpp",
+ "EventManager.h",
"ExecutionQueue.cpp",
"ExecutionQueue.h",
"ExternalTexture.cpp",
@@ -351,6 +353,8 @@
"Surface.h",
"SwapChain.cpp",
"SwapChain.h",
+ "SystemEvent.cpp",
+ "SystemEvent.h",
"Texture.cpp",
"Texture.h",
"TintUtils.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 7547f0f..03b486e 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -116,6 +116,8 @@
"ErrorInjector.h"
"ErrorScope.cpp"
"ErrorScope.h"
+ "EventManager.cpp"
+ "EventManager.h"
"Features.cpp"
"Features.h"
"ExternalTexture.cpp"
@@ -138,6 +140,8 @@
"IntegerTypes.h"
"Limits.cpp"
"Limits.h"
+ "SystemEvent.cpp"
+ "SystemEvent.h"
"ObjectBase.cpp"
"ObjectBase.h"
"PassResourceUsage.cpp"
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 1700cd4..88d20db 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -750,6 +750,10 @@
return &mObjectLists[type];
}
+InstanceBase* DeviceBase::GetInstance() const {
+ return mAdapter->GetPhysicalDevice()->GetInstance();
+}
+
AdapterBase* DeviceBase::GetAdapter() const {
return mAdapter.Get();
}
@@ -2051,6 +2055,13 @@
return 4u;
}
+bool DeviceBase::WaitAnyImpl(size_t futureCount,
+ TrackedFutureWaitInfo* futures,
+ Nanoseconds timeout) {
+ // Default for backends which don't actually need to do anything special in this case.
+ return WaitAnySystemEvent(futureCount, futures, timeout);
+}
+
MaybeError DeviceBase::CopyFromStagingToBuffer(BufferBase* source,
uint64_t sourceOffset,
BufferBase* destination,
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 3081c75..88b02f1 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -59,6 +59,7 @@
struct CallbackTask;
struct InternalPipelineStore;
struct ShaderModuleParseResult;
+struct TrackedFutureWaitInfo;
using WGSLExtensionSet = std::unordered_set<std::string>;
@@ -157,6 +158,7 @@
MaybeError ValidateObject(const ApiObjectBase* object) const;
+ InstanceBase* GetInstance() const;
AdapterBase* GetAdapter() const;
PhysicalDeviceBase* GetPhysicalDevice() const;
virtual dawn::platform::Platform* GetPlatform() const;
@@ -430,6 +432,10 @@
virtual void AppendDebugLayerMessages(ErrorData* error) {}
+ [[nodiscard]] virtual bool WaitAnyImpl(size_t futureCount,
+ TrackedFutureWaitInfo* futures,
+ Nanoseconds timeout);
+
// It is guaranteed that the wrapped mutex will outlive the Device (if the Device is deleted
// before the AutoLockAndHoldRef).
[[nodiscard]] Mutex::AutoLockAndHoldRef GetScopedLockSafeForDelete();
diff --git a/src/dawn/native/EventManager.cpp b/src/dawn/native/EventManager.cpp
new file mode 100644
index 0000000..e03deb3
--- /dev/null
+++ b/src/dawn/native/EventManager.cpp
@@ -0,0 +1,315 @@
+// Copyright 2023 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 "dawn/native/EventManager.h"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+#include <vector>
+
+#include "dawn/common/Assert.h"
+#include "dawn/common/FutureUtils.h"
+#include "dawn/native/Device.h"
+#include "dawn/native/IntegerTypes.h"
+#include "dawn/native/SystemEvent.h"
+
+namespace dawn::native {
+
+namespace {
+
+wgpu::WaitStatus WaitImpl(std::vector<TrackedFutureWaitInfo>& futures, Nanoseconds timeout) {
+ // Sort the futures by how they'll be waited (their GetWaitDevice).
+ // This lets us do each wait on a slice of the array.
+ std::sort(futures.begin(), futures.end(), [](const auto& a, const auto& b) {
+ // operator<() is undefined behavior for arbitrary pointers, but std::less{}() is defined.
+ return std::less<DeviceBase*>{}(a.event->GetWaitDevice(), b.event->GetWaitDevice());
+ });
+
+ if (timeout > Nanoseconds(0)) {
+ ASSERT(futures.size() <= kTimedWaitAnyMaxCountDefault);
+
+ // If there's a timeout, check that there isn't a mix of wait devices.
+ if (futures.front().event->GetWaitDevice() != futures.back().event->GetWaitDevice()) {
+ return wgpu::WaitStatus::UnsupportedMixedSources;
+ }
+ }
+
+ // Actually do the poll or wait to find out if any of the futures became ready.
+ // Here, there's either only one iteration, or timeout is 0, so we know the
+ // timeout won't get stacked multiple times.
+ bool anySuccess = false;
+ // Find each slice of the array (sliced by wait device), and wait on it.
+ for (size_t sliceStart = 0; sliceStart < futures.size();) {
+ DeviceBase* waitDevice = futures[sliceStart].event->GetWaitDevice();
+ size_t sliceLength = 1;
+ while (sliceStart + sliceLength < futures.size() &&
+ (futures[sliceStart + sliceLength].event->GetWaitDevice()) == waitDevice) {
+ sliceLength++;
+ }
+
+ {
+ bool success;
+ if (waitDevice) {
+ success = waitDevice->WaitAnyImpl(sliceLength, &futures[sliceStart], timeout);
+ } else {
+ success = WaitAnySystemEvent(sliceLength, &futures[sliceStart], timeout);
+ }
+ anySuccess |= success;
+ }
+
+ sliceStart += sliceLength;
+ }
+ if (!anySuccess) {
+ return wgpu::WaitStatus::TimedOut;
+ }
+ return wgpu::WaitStatus::Success;
+}
+
+} // namespace
+
+// EventManager
+
+EventManager::EventManager() {
+ mTrackers.emplace(); // Construct the non-movable inner struct.
+}
+
+EventManager::~EventManager() {
+ ASSERT(!mTrackers.has_value());
+}
+
+MaybeError EventManager::Initialize(const InstanceDescriptor* descriptor) {
+ if (descriptor) {
+ if (descriptor->features.timedWaitAnyMaxCount > kTimedWaitAnyMaxCountDefault) {
+ // We don't yet support a higher timedWaitAnyMaxCount because it would be complicated
+ // to implement on Windows, and it isn't that useful to implement only on non-Windows.
+ return DAWN_VALIDATION_ERROR("Requested timedWaitAnyMaxCount is not supported");
+ }
+ mTimedWaitAnyEnable = descriptor->features.timedWaitAnyEnable;
+ mTimedWaitAnyMaxCount =
+ std::max(kTimedWaitAnyMaxCountDefault, descriptor->features.timedWaitAnyMaxCount);
+ }
+
+ return {};
+}
+
+void EventManager::ShutDown() {
+ mTrackers.reset();
+}
+
+FutureID EventManager::TrackEvent(WGPUCallbackModeFlags mode, Ref<TrackedEvent>&& future) {
+ switch (ValidateAndFlattenCallbackMode(mode)) {
+ case CallbackMode::Spontaneous:
+ // We don't need to track the future because some other code is responsible for
+ // completing it, and we aren't returning an ID so we don't need to be able to query it.
+ return kNullFutureID;
+ case CallbackMode::Future:
+ case CallbackMode::FutureOrSpontaneous: {
+ FutureID futureID = mNextFutureID++;
+ if (mTrackers.has_value()) {
+ mTrackers->futures->emplace(futureID, std::move(future));
+ }
+ return futureID;
+ }
+ case CallbackMode::ProcessEvents:
+ case CallbackMode::ProcessEventsOrSpontaneous: {
+ FutureID futureID = mNextFutureID++;
+ if (mTrackers.has_value()) {
+ mTrackers->pollEvents->emplace(futureID, std::move(future));
+ }
+ // Return null future, because the user didn't actually ask for a future.
+ return kNullFutureID;
+ }
+ }
+}
+
+void EventManager::ProcessPollEvents() {
+ ASSERT(mTrackers.has_value());
+
+ std::vector<TrackedFutureWaitInfo> futures;
+ mTrackers->pollEvents.Use([&](auto trackedPollEvents) {
+ futures.reserve(trackedPollEvents->size());
+
+ for (auto& [futureID, event] : *trackedPollEvents) {
+ futures.push_back(
+ TrackedFutureWaitInfo{futureID, TrackedEvent::WaitRef{event.Get()}, 0, false});
+ }
+
+ // The WaitImpl is inside of the lock to prevent any two ProcessEvents calls from
+ // calling competing OS wait syscalls at the same time.
+ wgpu::WaitStatus waitStatus = WaitImpl(futures, Nanoseconds(0));
+ if (waitStatus == wgpu::WaitStatus::TimedOut) {
+ return;
+ }
+ ASSERT(waitStatus == wgpu::WaitStatus::Success);
+
+ for (TrackedFutureWaitInfo& future : futures) {
+ if (future.ready) {
+ trackedPollEvents->erase(future.futureID);
+ }
+ }
+ });
+
+ for (TrackedFutureWaitInfo& future : futures) {
+ if (future.ready) {
+ ASSERT(future.event->mCallbackMode & WGPUCallbackMode_ProcessEvents);
+ future.event->EnsureComplete(EventCompletionType::Ready);
+ }
+ }
+}
+
+wgpu::WaitStatus EventManager::WaitAny(size_t count, FutureWaitInfo* infos, Nanoseconds timeout) {
+ ASSERT(mTrackers.has_value());
+
+ // Validate for feature support.
+ if (timeout > Nanoseconds(0)) {
+ if (!mTimedWaitAnyEnable) {
+ return wgpu::WaitStatus::UnsupportedTimeout;
+ }
+ if (count > mTimedWaitAnyMaxCount) {
+ return wgpu::WaitStatus::UnsupportedCount;
+ }
+ // UnsupportedMixedSources is validated later, in WaitImpl.
+ }
+
+ if (count == 0) {
+ return wgpu::WaitStatus::Success;
+ }
+
+ // Look up all of the futures and build a list of `TrackedFutureWaitInfo`s.
+ std::vector<TrackedFutureWaitInfo> futures;
+ futures.reserve(count);
+ bool anyCompleted = false;
+ mTrackers->futures.Use([&](auto trackedFutures) {
+ FutureID firstInvalidFutureID = mNextFutureID;
+ for (size_t i = 0; i < count; ++i) {
+ FutureID futureID = infos[i].future.id;
+
+ // Check for cases that are undefined behavior in the API contract.
+ ASSERT(futureID != 0);
+ ASSERT(futureID < firstInvalidFutureID);
+ // TakeWaitRef below will catch if the future is waited twice at the
+ // same time (unless it's already completed).
+
+ auto it = trackedFutures->find(futureID);
+ if (it == trackedFutures->end()) {
+ infos[i].completed = true;
+ anyCompleted = true;
+ } else {
+ infos[i].completed = false;
+ TrackedEvent* event = it->second.Get();
+ futures.push_back(
+ TrackedFutureWaitInfo{futureID, TrackedEvent::WaitRef{event}, i, false});
+ }
+ }
+ });
+ // If any completed, return immediately.
+ if (anyCompleted) {
+ return wgpu::WaitStatus::Success;
+ }
+ // Otherwise, we should have successfully looked up all of them.
+ ASSERT(futures.size() == count);
+
+ wgpu::WaitStatus waitStatus = WaitImpl(futures, timeout);
+ if (waitStatus != wgpu::WaitStatus::Success) {
+ return waitStatus;
+ }
+
+ // For any futures that we're about to complete, first ensure they're untracked. It's OK if
+ // something actually isn't tracked anymore (because it completed elsewhere while waiting.)
+ mTrackers->futures.Use([&](auto trackedFutures) {
+ for (const TrackedFutureWaitInfo& future : futures) {
+ if (future.ready) {
+ trackedFutures->erase(future.futureID);
+ }
+ }
+ });
+
+ // Finally, call callbacks and update return values.
+ for (TrackedFutureWaitInfo& future : futures) {
+ if (future.ready) {
+ // Set completed before calling the callback.
+ infos[future.indexInInfos].completed = true;
+ // TODO(crbug.com/dawn/1987): Guarantee the event ordering from the JS spec.
+ ASSERT(future.event->mCallbackMode & WGPUCallbackMode_Future);
+ future.event->EnsureComplete(EventCompletionType::Ready);
+ }
+ }
+
+ return wgpu::WaitStatus::Success;
+}
+
+// EventManager::TrackedEvent
+
+EventManager::TrackedEvent::TrackedEvent(DeviceBase* device,
+ WGPUCallbackModeFlags callbackMode,
+ SystemEventReceiver&& receiver)
+ : mDevice(device), mCallbackMode(callbackMode), mReceiver(std::move(receiver)) {}
+
+EventManager::TrackedEvent::~TrackedEvent() {
+ ASSERT(mCompleted);
+}
+
+const SystemEventReceiver& EventManager::TrackedEvent::GetReceiver() const {
+ return mReceiver;
+}
+
+DeviceBase* EventManager::TrackedEvent::GetWaitDevice() const {
+ return MustWaitUsingDevice() ? mDevice.Get() : nullptr;
+}
+
+void EventManager::TrackedEvent::EnsureComplete(EventCompletionType completionType) {
+ bool alreadyComplete = mCompleted.exchange(true);
+ if (!alreadyComplete) {
+ Complete(completionType);
+ }
+}
+
+void EventManager::TrackedEvent::CompleteIfSpontaneous() {
+ if (mCallbackMode & WGPUCallbackMode_Spontaneous) {
+ bool alreadyComplete = mCompleted.exchange(true);
+ // If it was already complete, but there was an error, we have no place
+ // to report it, so ASSERT. This shouldn't happen.
+ ASSERT(!alreadyComplete);
+ Complete(EventCompletionType::Ready);
+ }
+}
+
+// EventManager::TrackedEvent::WaitRef
+
+EventManager::TrackedEvent::WaitRef::WaitRef(TrackedEvent* event) : mRef(event) {
+#if DAWN_ENABLE_ASSERTS
+ bool wasAlreadyWaited = mRef->mCurrentlyBeingWaited.exchange(true);
+ ASSERT(!wasAlreadyWaited);
+#endif
+}
+
+EventManager::TrackedEvent::WaitRef::~WaitRef() {
+#if DAWN_ENABLE_ASSERTS
+ if (mRef.Get() != nullptr) {
+ bool wasAlreadyWaited = mRef->mCurrentlyBeingWaited.exchange(false);
+ ASSERT(wasAlreadyWaited);
+ }
+#endif
+}
+
+EventManager::TrackedEvent* EventManager::TrackedEvent::WaitRef::operator->() {
+ return mRef.Get();
+}
+
+const EventManager::TrackedEvent* EventManager::TrackedEvent::WaitRef::operator->() const {
+ return mRef.Get();
+}
+
+} // namespace dawn::native
diff --git a/src/dawn/native/EventManager.h b/src/dawn/native/EventManager.h
new file mode 100644
index 0000000..7eba4eb
--- /dev/null
+++ b/src/dawn/native/EventManager.h
@@ -0,0 +1,184 @@
+// Copyright 2023 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 SRC_DAWN_NATIVE_EVENTMANAGER_H_
+#define SRC_DAWN_NATIVE_EVENTMANAGER_H_
+
+#include <atomic>
+#include <cstdint>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include "dawn/common/FutureUtils.h"
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/NonCopyable.h"
+#include "dawn/common/Ref.h"
+#include "dawn/native/Error.h"
+#include "dawn/native/IntegerTypes.h"
+#include "dawn/native/SystemEvent.h"
+
+namespace dawn::native {
+
+struct InstanceDescriptor;
+
+// Subcomponent of the Instance which tracks callback events for the Future-based callback
+// entrypoints. All events from this instance (regardless of whether from an adapter, device, queue,
+// etc.) are tracked here, and used by the instance-wide ProcessEvents and WaitAny entrypoints.
+//
+// TODO(crbug.com/dawn/1987): Can this eventually replace CallbackTaskManager?
+// TODO(crbug.com/dawn/1987): There are various ways to optimize ProcessEvents/WaitAny:
+// - Only pay attention to the earliest serial on each queue.
+// - Spontaneously set events as "early-ready" in other places when we see serials advance, e.g.
+// Submit, or when checking a later wait before an earlier wait.
+// - For thread-driven events (async pipeline compilation and Metal queue events), defer tracking
+// for ProcessEvents until the event is already completed.
+// - Avoid creating OS events until they're actually needed (see the todo in TrackedEvent).
+class EventManager final : NonMovable {
+ public:
+ EventManager();
+ ~EventManager();
+
+ MaybeError Initialize(const InstanceDescriptor*);
+ // Called by WillDropLastExternalRef. Once shut down, the EventManager stops tracking anything.
+ // It drops any refs to TrackedEvents, to break reference cycles. If doing so frees the last ref
+ // of any uncompleted TrackedEvents, they'll get completed with EventCompletionType::Shutdown.
+ void ShutDown();
+
+ class TrackedEvent;
+ // Track a TrackedEvent and give it a FutureID.
+ [[nodiscard]] FutureID TrackEvent(WGPUCallbackModeFlags mode, Ref<TrackedEvent>&&);
+ void ProcessPollEvents();
+ [[nodiscard]] wgpu::WaitStatus WaitAny(size_t count,
+ FutureWaitInfo* infos,
+ Nanoseconds timeout);
+
+ private:
+ struct Trackers : dawn::NonMovable {
+ // Tracks Futures (used by WaitAny).
+ MutexProtected<std::unordered_map<FutureID, Ref<TrackedEvent>>> futures;
+ // Tracks events polled by ProcessEvents.
+ MutexProtected<std::unordered_map<FutureID, Ref<TrackedEvent>>> pollEvents;
+ };
+
+ bool mTimedWaitAnyEnable = false;
+ size_t mTimedWaitAnyMaxCount = kTimedWaitAnyMaxCountDefault;
+ std::atomic<FutureID> mNextFutureID = 1;
+
+ // Freed once the user has dropped their last ref to the Instance, so can't call WaitAny or
+ // ProcessEvents anymore. This breaks reference cycles.
+ std::optional<Trackers> mTrackers;
+};
+
+// Base class for the objects that back WGPUFutures. TrackedEvent is responsible for the lifetime
+// the callback it contains. If TrackedEvent gets destroyed before it completes, it's responsible
+// for cleaning up (by calling the callback with an "Unknown" status).
+//
+// For Future-based and ProcessEvents-based TrackedEvents, the EventManager will track them for
+// completion in WaitAny or ProcessEvents. However, once the Instance has lost all its external
+// refs, the user can't call either of those methods anymore, so EventManager will stop holding refs
+// to any TrackedEvents. Any which are not ref'd elsewhere (in order to be `Spontaneous`ly
+// completed) will be cleaned up at that time.
+class EventManager::TrackedEvent : public RefCounted {
+ protected:
+ // Note: TrackedEvents are (currently) only for Device events. Events like RequestAdapter and
+ // RequestDevice complete immediately in dawn native, so should never need to be tracked.
+ TrackedEvent(DeviceBase* device,
+ WGPUCallbackModeFlags callbackMode,
+ SystemEventReceiver&& receiver);
+
+ public:
+ // Subclasses must implement this to complete the event (if not completed) with
+ // EventCompletionType::Shutdown.
+ ~TrackedEvent() override;
+
+ class WaitRef;
+
+ const SystemEventReceiver& GetReceiver() const;
+ DeviceBase* GetWaitDevice() const;
+
+ protected:
+ void EnsureComplete(EventCompletionType);
+ void CompleteIfSpontaneous();
+
+ // True if the event can only be waited using its device (e.g. with vkWaitForFences).
+ // False if it can be waited using OS-level wait primitives (WaitAnySystemEvent).
+ virtual bool MustWaitUsingDevice() const = 0;
+ virtual void Complete(EventCompletionType) = 0;
+
+ // This creates a temporary ref cycle (Device->Instance->EventManager->TrackedEvent).
+ // This is OK because the instance will clear out the EventManager on shutdown.
+ // TODO(crbug.com/dawn/1987): This is a bit fragile. Is it possible to remove the ref cycle?
+ Ref<DeviceBase> mDevice;
+ WGPUCallbackModeFlags mCallbackMode;
+
+#if DAWN_ENABLE_ASSERTS
+ std::atomic<bool> mCurrentlyBeingWaited;
+#endif
+
+ private:
+ friend class EventManager;
+
+ // TODO(crbug.com/dawn/1987): Optimize by creating an SystemEventReceiver only once actually
+ // needed (the user asks for a timed wait or an OS event handle). This should be generally
+ // achievable:
+ // - For thread-driven events (async pipeline compilation and Metal queue events), use a mutex
+ // or atomics to atomically:
+ // - On wait: { check if mKnownReady. if not, create the SystemEventPipe }
+ // - On signal: { check if there's an SystemEventPipe. if not, set mKnownReady }
+ // - For D3D12/Vulkan fences, on timed waits, first use GetCompletedValue/GetFenceStatus, then
+ // create an OS event if it's not ready yet (and we don't have one yet).
+ //
+ // This abstraction should probably be hidden from TrackedEvent - previous attempts to do
+ // something similar in TrackedEvent turned out to be quite confusing. It can instead be an
+ // "optimization" to the SystemEvent* or a layer between TrackedEvent and SystemEventReceiver.
+ SystemEventReceiver mReceiver;
+ // Callback has been called.
+ std::atomic<bool> mCompleted = false;
+};
+
+// A Ref<TrackedEvent>, but ASSERTing that a future isn't used concurrently in multiple
+// WaitAny/ProcessEvents call (by checking that there's never more than one WaitRef for a
+// TrackedEvent). For WaitAny, this checks the embedder's behavior, but for ProcessEvents this is
+// only an internal ASSERT (it's supposed to be synchronized so that this never happens).
+class EventManager::TrackedEvent::WaitRef : dawn::NonCopyable {
+ public:
+ WaitRef(WaitRef&& rhs) = default;
+ WaitRef& operator=(WaitRef&& rhs) = default;
+
+ explicit WaitRef(TrackedEvent* future);
+ ~WaitRef();
+
+ TrackedEvent* operator->();
+ const TrackedEvent* operator->() const;
+
+ private:
+ Ref<TrackedEvent> mRef;
+};
+
+// TrackedEvent::WaitRef plus a few extra fields needed for some implementations.
+// Sometimes they'll be unused, but that's OK; it simplifies code reuse.
+struct TrackedFutureWaitInfo {
+ FutureID futureID;
+ EventManager::TrackedEvent::WaitRef event;
+ // Used by EventManager::ProcessPollEvents
+ size_t indexInInfos;
+ // Used by EventManager::ProcessPollEvents and ::WaitAny
+ bool ready;
+};
+
+} // namespace dawn::native
+
+#endif // SRC_DAWN_NATIVE_EVENTMANAGER_H_
diff --git a/src/dawn/native/Instance.cpp b/src/dawn/native/Instance.cpp
index 8434971..e658369 100644
--- a/src/dawn/native/Instance.cpp
+++ b/src/dawn/native/Instance.cpp
@@ -17,6 +17,7 @@
#include <utility>
#include "dawn/common/Assert.h"
+#include "dawn/common/FutureUtils.h"
#include "dawn/common/GPUInfo.h"
#include "dawn/common/Log.h"
#include "dawn/common/SystemUtils.h"
@@ -48,8 +49,6 @@
#include "dawn/native/X11Functions.h"
#endif // defined(DAWN_USE_X11)
-#include <optional>
-
namespace dawn::native {
// Forward definitions of each backend's "Connect" function that creates new BackendConnection.
@@ -96,6 +95,16 @@
} // anonymous namespace
+wgpu::Bool APIGetInstanceFeatures(InstanceFeatures* features) {
+ if (features->nextInChain != nullptr) {
+ return false;
+ }
+
+ features->timedWaitAnyEnable = true;
+ features->timedWaitAnyMaxCount = kTimedWaitAnyMaxCountDefault;
+ return true;
+}
+
InstanceBase* APICreateInstance(const InstanceDescriptor* descriptor) {
return InstanceBase::Create(descriptor).Detach();
}
@@ -132,7 +141,10 @@
void InstanceBase::WillDropLastExternalRef() {
// InstanceBase uses RefCountedWithExternalCount to break refcycles.
- //
+
+ // Stop tracking events. See comment on ShutDown.
+ mEventManager.ShutDown();
+
// InstanceBase holds backends which hold Refs to PhysicalDeviceBases discovered, which hold
// Refs back to the InstanceBase.
// In order to break this cycle and prevent leaks, when the application drops the last external
@@ -174,6 +186,8 @@
mDefaultPlatform = std::make_unique<dawn::platform::Platform>();
SetPlatform(dawnDesc != nullptr ? dawnDesc->platform : mDefaultPlatform.get());
+ DAWN_TRY(mEventManager.Initialize(descriptor));
+
return {};
}
@@ -560,6 +574,13 @@
}
mCallbackTaskManager->Flush();
+ mEventManager.ProcessPollEvents();
+}
+
+wgpu::WaitStatus InstanceBase::APIWaitAny(size_t count,
+ FutureWaitInfo* futures,
+ uint64_t timeoutNS) {
+ return mEventManager.WaitAny(count, futures, Nanoseconds(timeoutNS));
}
const std::vector<std::string>& InstanceBase::GetRuntimeSearchPaths() const {
@@ -570,6 +591,10 @@
return mCallbackTaskManager;
}
+EventManager* InstanceBase::GetEventManager() {
+ return &mEventManager;
+}
+
void InstanceBase::ConsumeError(std::unique_ptr<ErrorData> error) {
ASSERT(error != nullptr);
dawn::ErrorLog() << error->GetFormattedMessage();
diff --git a/src/dawn/native/Instance.h b/src/dawn/native/Instance.h
index ccf6b9d..1d7f09e 100644
--- a/src/dawn/native/Instance.h
+++ b/src/dawn/native/Instance.h
@@ -20,7 +20,6 @@
#include <mutex>
#include <set>
#include <string>
-#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -30,6 +29,7 @@
#include "dawn/native/Adapter.h"
#include "dawn/native/BackendConnection.h"
#include "dawn/native/BlobCache.h"
+#include "dawn/native/EventManager.h"
#include "dawn/native/Features.h"
#include "dawn/native/RefCountedWithExternalCount.h"
#include "dawn/native/Toggles.h"
@@ -50,6 +50,7 @@
using BackendsArray = ityp::
array<wgpu::BackendType, std::unique_ptr<BackendConnection>, kEnumCount<wgpu::BackendType>>;
+wgpu::Bool APIGetInstanceFeatures(InstanceFeatures* features);
InstanceBase* APICreateInstance(const InstanceDescriptor* descriptor);
// This is called InstanceBase for consistency across the frontend, even if the backends don't
@@ -139,6 +140,7 @@
const std::vector<std::string>& GetRuntimeSearchPaths() const;
const Ref<CallbackTaskManager>& GetCallbackTaskManager() const;
+ EventManager* GetEventManager();
// Get backend-independent libraries that need to be loaded dynamically.
const X11Functions* GetOrLoadX11Functions();
@@ -146,6 +148,9 @@
// Dawn API
Surface* APICreateSurface(const SurfaceDescriptor* descriptor);
void APIProcessEvents();
+ [[nodiscard]] wgpu::WaitStatus APIWaitAny(size_t count,
+ FutureWaitInfo* futures,
+ uint64_t timeoutNS);
private:
explicit InstanceBase(const TogglesState& instanceToggles);
@@ -205,6 +210,7 @@
#endif // defined(DAWN_USE_X11)
Ref<CallbackTaskManager> mCallbackTaskManager;
+ EventManager mEventManager;
std::set<DeviceBase*> mDevicesList;
mutable std::mutex mDevicesListMutex;
diff --git a/src/dawn/native/IntegerTypes.h b/src/dawn/native/IntegerTypes.h
index b4645fd..8c7cc1d 100644
--- a/src/dawn/native/IntegerTypes.h
+++ b/src/dawn/native/IntegerTypes.h
@@ -21,6 +21,7 @@
#include "dawn/common/TypedInteger.h"
namespace dawn::native {
+
// Binding numbers in the shader and BindGroup/BindGroupLayoutDescriptors
using BindingNumber = TypedInteger<struct BindingNumberT, uint32_t>;
constexpr BindingNumber kMaxBindingsPerBindGroupTyped = BindingNumber(kMaxBindingsPerBindGroup);
@@ -72,6 +73,8 @@
// other pipelines.
using PipelineCompatibilityToken = TypedInteger<struct PipelineCompatibilityTokenT, uint64_t>;
+using Nanoseconds = TypedInteger<struct NanosecondsT, uint64_t>;
+
} // namespace dawn::native
#endif // SRC_DAWN_NATIVE_INTEGERTYPES_H_
diff --git a/src/dawn/native/ObjectBase.cpp b/src/dawn/native/ObjectBase.cpp
index 62fc893..b575e7e 100644
--- a/src/dawn/native/ObjectBase.cpp
+++ b/src/dawn/native/ObjectBase.cpp
@@ -16,6 +16,7 @@
#include <utility>
#include "absl/strings/str_format.h"
+#include "dawn/native/Adapter.h"
#include "dawn/native/Device.h"
#include "dawn/native/ObjectBase.h"
#include "dawn/native/ObjectType_autogen.h"
@@ -36,6 +37,10 @@
ObjectBase::ObjectBase(DeviceBase* device, ErrorTag) : ErrorMonad(kError), mDevice(device) {}
+InstanceBase* ObjectBase::GetInstance() const {
+ return mDevice->GetAdapter()->GetPhysicalDevice()->GetInstance();
+}
+
DeviceBase* ObjectBase::GetDevice() const {
return mDevice.Get();
}
diff --git a/src/dawn/native/ObjectBase.h b/src/dawn/native/ObjectBase.h
index e3eb0bd..e29d2ac 100644
--- a/src/dawn/native/ObjectBase.h
+++ b/src/dawn/native/ObjectBase.h
@@ -48,6 +48,7 @@
explicit ObjectBase(DeviceBase* device);
ObjectBase(DeviceBase* device, ErrorTag tag);
+ InstanceBase* GetInstance() const;
DeviceBase* GetDevice() const;
private:
diff --git a/src/dawn/native/Queue.cpp b/src/dawn/native/Queue.cpp
index c31e92d..6d86b2b 100644
--- a/src/dawn/native/Queue.cpp
+++ b/src/dawn/native/Queue.cpp
@@ -20,6 +20,7 @@
#include <vector>
#include "dawn/common/Constants.h"
+#include "dawn/common/FutureUtils.h"
#include "dawn/common/ityp_span.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/CommandBuffer.h"
@@ -29,14 +30,18 @@
#include "dawn/native/CopyTextureForBrowserHelper.h"
#include "dawn/native/Device.h"
#include "dawn/native/DynamicUploader.h"
+#include "dawn/native/EventManager.h"
#include "dawn/native/ExternalTexture.h"
+#include "dawn/native/Instance.h"
#include "dawn/native/ObjectType_autogen.h"
#include "dawn/native/QuerySet.h"
#include "dawn/native/RenderPassEncoder.h"
#include "dawn/native/RenderPipeline.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/Texture.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
+#include "dawn/webgpu.h"
namespace dawn::native {
@@ -172,8 +177,51 @@
void ForceEventualFlushOfCommands() override { UNREACHABLE(); }
MaybeError WaitForIdleForDestruction() override { UNREACHABLE(); }
};
+
+struct WorkDoneEvent final : public EventManager::TrackedEvent {
+ std::optional<WGPUQueueWorkDoneStatus> mEarlyStatus;
+ WGPUQueueWorkDoneCallback mCallback;
+ void* mUserdata;
+
+ // Create an event backed by the given SystemEventReceiver.
+ WorkDoneEvent(DeviceBase* device,
+ const WGPUQueueWorkDoneCallbackInfo& callbackInfo,
+ SystemEventReceiver&& receiver)
+ : TrackedEvent(device, callbackInfo.mode, std::move(receiver)),
+ mCallback(callbackInfo.callback),
+ mUserdata(callbackInfo.userdata) {}
+
+ // Create an event that's ready at creation (for errors, etc.)
+ WorkDoneEvent(DeviceBase* device,
+ const WGPUQueueWorkDoneCallbackInfo& callbackInfo,
+ WGPUQueueWorkDoneStatus earlyStatus)
+ : WorkDoneEvent(device, callbackInfo, SystemEventReceiver::CreateAlreadySignaled()) {
+ CompleteIfSpontaneous();
+ }
+
+ ~WorkDoneEvent() override { EnsureComplete(EventCompletionType::Shutdown); }
+
+ // TODO(crbug.com/dawn/1987): When adding support for mixed sources, return false here when
+ // the device has the mixed sources feature enabled, and so can expose the fence as an OS event.
+ bool MustWaitUsingDevice() const override { return true; }
+
+ void Complete(EventCompletionType completionType) override {
+ // WorkDoneEvent has no error cases other than the mEarlyStatus ones.
+ WGPUQueueWorkDoneStatus status = WGPUQueueWorkDoneStatus_Success;
+ if (completionType == EventCompletionType::Shutdown) {
+ status = WGPUQueueWorkDoneStatus_Unknown;
+ } else if (mEarlyStatus) {
+ status = mEarlyStatus.value();
+ }
+
+ mCallback(status, mUserdata);
+ }
+};
+
} // namespace
+// TrackTaskCallback
+
void TrackTaskCallback::SetFinishedSerial(ExecutionSerial serial) {
mSerial = serial;
}
@@ -238,6 +286,39 @@
uint64_t(GetDevice()->GetPendingCommandSerial()));
}
+WGPUFuture QueueBase::APIOnSubmittedWorkDoneF(const WGPUQueueWorkDoneCallbackInfo& callbackInfo) {
+ // TODO(crbug.com/dawn/1987): Once we always return a future, change this to log to the instance
+ // (note, not raise a validation error to the device) and return the null future.
+ ASSERT(callbackInfo.nextInChain == nullptr);
+
+ Ref<EventManager::TrackedEvent> event;
+
+ WGPUQueueWorkDoneStatus validationEarlyStatus;
+ if (GetDevice()->ConsumedError(ValidateOnSubmittedWorkDone(0, &validationEarlyStatus))) {
+ // TODO(crbug.com/dawn/1987): This is here to pretend that things succeed when the device is
+ // lost. When the old OnSubmittedWorkDone is removed then we can update
+ // ValidateOnSubmittedWorkDone to just return the correct thing here.
+ if (validationEarlyStatus == WGPUQueueWorkDoneStatus_DeviceLost) {
+ validationEarlyStatus = WGPUQueueWorkDoneStatus_Success;
+ }
+
+ // Note: if the callback is spontaneous, it'll get called in here.
+ event = AcquireRef(new WorkDoneEvent(GetDevice(), callbackInfo, validationEarlyStatus));
+ } else {
+ event = AcquireRef(new WorkDoneEvent(GetDevice(), callbackInfo, InsertWorkDoneEvent()));
+ }
+
+ FutureID futureID =
+ GetInstance()->GetEventManager()->TrackEvent(callbackInfo.mode, std::move(event));
+
+ return WGPUFuture{futureID};
+}
+
+SystemEventReceiver QueueBase::InsertWorkDoneEvent() {
+ // TODO(crbug.com/dawn/1987): Implement this in all backends and remove this default impl
+ CHECK(false);
+}
+
void QueueBase::TrackTask(std::unique_ptr<TrackTaskCallback> task, ExecutionSerial serial) {
// If the task depends on a serial which is not submitted yet, force a flush.
if (serial > GetLastSubmittedCommandSerial()) {
diff --git a/src/dawn/native/Queue.h b/src/dawn/native/Queue.h
index d7aa81f..1c03a9f 100644
--- a/src/dawn/native/Queue.h
+++ b/src/dawn/native/Queue.h
@@ -24,6 +24,7 @@
#include "dawn/native/Forward.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/ObjectBase.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/DawnNative.h"
#include "dawn/native/dawn_platform.h"
@@ -59,6 +60,7 @@
void APIOnSubmittedWorkDone(uint64_t signalValue,
WGPUQueueWorkDoneCallback callback,
void* userdata);
+ WGPUFuture APIOnSubmittedWorkDoneF(const WGPUQueueWorkDoneCallbackInfo& callbackInfo);
void APIWriteBuffer(BufferBase* buffer, uint64_t bufferOffset, const void* data, size_t size);
void APIWriteTexture(const ImageCopyTexture* destination,
const void* data,
@@ -86,7 +88,9 @@
protected:
QueueBase(DeviceBase* device, const QueueDescriptor* descriptor);
QueueBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
+
void DestroyImpl() override;
+ virtual SystemEventReceiver InsertWorkDoneEvent();
private:
MaybeError WriteTextureInternal(const ImageCopyTexture* destination,
diff --git a/src/dawn/native/SystemEvent.cpp b/src/dawn/native/SystemEvent.cpp
new file mode 100644
index 0000000..c1ad4a0
--- /dev/null
+++ b/src/dawn/native/SystemEvent.cpp
@@ -0,0 +1,222 @@
+// Copyright 2023 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 "dawn/native/SystemEvent.h"
+#include <limits>
+#include "dawn/common/Assert.h"
+
+#if DAWN_PLATFORM_IS(WINDOWS)
+#include <windows.h>
+#elif DAWN_PLATFORM_IS(POSIX)
+#include <sys/poll.h>
+#include <unistd.h>
+#endif
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "dawn/native/EventManager.h"
+
+namespace dawn::native {
+
+namespace {
+
+template <typename T, T Infinity>
+T ToMillisecondsGeneric(Nanoseconds timeout) {
+ uint64_t ns = uint64_t{timeout};
+ uint64_t ms = 0;
+ if (ns > 0) {
+ ms = (ns - 1) / 1'000'000 + 1;
+ if (ms > std::numeric_limits<T>::max()) {
+ return Infinity; // Round long timeout up to infinity
+ }
+ }
+ return static_cast<T>(ms);
+}
+
+#if DAWN_PLATFORM_IS(WINDOWS)
+// #define ToMilliseconds ToMillisecondsGeneric<DWORD, INFINITE>
+#elif DAWN_PLATFORM_IS(POSIX)
+#define ToMilliseconds ToMillisecondsGeneric<int, -1>
+#endif
+
+#if DAWN_PLATFORM_IS(WINDOWS)
+HANDLE AsHANDLE(const SystemEventPrimitive& p) {
+ return reinterpret_cast<HANDLE>(p.value);
+}
+#elif DAWN_PLATFORM_IS(POSIX)
+int AsFD(const SystemEventPrimitive& p) {
+ ASSERT(p.value <= std::numeric_limits<int>::max());
+ return p.value;
+}
+#endif
+
+} // namespace
+
+// SystemEventPrimitive
+
+SystemEventPrimitive::SystemEventPrimitive(void* win32Handle)
+ : value(reinterpret_cast<uintptr_t>(win32Handle)) {
+#if DAWN_PLATFORM_IS(WINDOWS)
+ static_assert(std::is_same_v<void*, HANDLE>);
+ ASSERT(win32Handle != nullptr);
+#else
+ ASSERT(false); // Wrong platform.
+#endif
+}
+
+SystemEventPrimitive::SystemEventPrimitive(int posixFd) : value(posixFd) {
+#if DAWN_PLATFORM_IS(POSIX)
+ static_assert(sizeof(uintptr_t) >= sizeof(int));
+ ASSERT(posixFd > 0);
+#else
+ ASSERT(false); // Wrong platform.
+#endif
+}
+
+SystemEventPrimitive::~SystemEventPrimitive() {
+ if (IsValid()) {
+ Close();
+ }
+}
+
+SystemEventPrimitive::SystemEventPrimitive(SystemEventPrimitive&& rhs) {
+ *this = std::move(rhs);
+}
+
+SystemEventPrimitive& SystemEventPrimitive::operator=(SystemEventPrimitive&& rhs) {
+ if (this != &rhs) {
+ if (IsValid()) {
+ Close();
+ }
+ std::swap(value, rhs.value);
+ }
+ return *this;
+}
+
+bool SystemEventPrimitive::IsValid() const {
+ return value != kInvalid;
+}
+
+void SystemEventPrimitive::Close() {
+ ASSERT(IsValid());
+
+#if DAWN_PLATFORM_IS(WINDOWS)
+ CloseHandle(AsHANDLE(*this));
+#elif DAWN_PLATFORM_IS(POSIX)
+ close(AsFD(*this));
+#else
+ CHECK(false); // Not implemented.
+#endif
+
+ value = kInvalid;
+}
+
+// SystemEventReceiver
+
+SystemEventReceiver SystemEventReceiver::CreateAlreadySignaled() {
+ SystemEventPipeSender sender;
+ SystemEventReceiver receiver;
+ std::tie(sender, receiver) = CreateSystemEventPipe();
+ std::move(sender).Signal();
+ return receiver;
+}
+
+// SystemEventPipeSender
+
+SystemEventPipeSender::~SystemEventPipeSender() {
+ // Make sure it's been Signaled (or is empty) before being dropped.
+ // Dropping this would "leak" the receiver (it'll never get signalled).
+ ASSERT(!mPrimitive.IsValid());
+}
+
+void SystemEventPipeSender::Signal() && {
+ ASSERT(mPrimitive.IsValid());
+#if DAWN_PLATFORM_IS(WINDOWS)
+ // This is not needed on Windows yet. It's implementable using SetEvent().
+ UNREACHABLE();
+#elif DAWN_PLATFORM_IS(POSIX)
+ // Send one byte to signal the receiver
+ char zero[1] = {0};
+ int status = write(AsFD(mPrimitive), zero, 1);
+ CHECK(status >= 0);
+#else
+ // Not implemented for this platform.
+ CHECK(false);
+#endif
+
+ mPrimitive.Close();
+}
+
+// standalone functions
+
+bool WaitAnySystemEvent(size_t count, TrackedFutureWaitInfo* futures, Nanoseconds timeout) {
+#if DAWN_PLATFORM_IS(WINDOWS)
+ // TODO(crbug.com/dawn/1987): Implement this.
+ CHECK(false);
+#elif DAWN_PLATFORM_IS(POSIX)
+ std::vector<pollfd> pollfds(count);
+ for (size_t i = 0; i < count; ++i) {
+ int fd = AsFD(futures[i].event->GetReceiver().mPrimitive);
+ pollfds[i] = pollfd{fd, POLLIN, 0};
+ }
+
+ int status = poll(pollfds.data(), pollfds.size(), ToMilliseconds(timeout));
+
+ CHECK(status >= 0);
+ if (status == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < count; ++i) {
+ int revents = pollfds[i].revents;
+ static constexpr int kAllowedEvents = POLLIN | POLLHUP;
+ CHECK((revents & kAllowedEvents) == revents);
+ }
+
+ for (size_t i = 0; i < count; ++i) {
+ bool ready = (pollfds[i].revents & POLLIN) != 0;
+ futures[i].ready = ready;
+ }
+
+ return true;
+#else
+ CHECK(false); // Not implemented.
+#endif
+}
+
+std::pair<SystemEventPipeSender, SystemEventReceiver> CreateSystemEventPipe() {
+#if DAWN_PLATFORM_IS(WINDOWS)
+ // This is not needed on Windows yet. It's implementable using CreateEvent().
+ UNREACHABLE();
+#elif DAWN_PLATFORM_IS(POSIX)
+ int pipeFds[2];
+ int status = pipe(pipeFds);
+ CHECK(status >= 0);
+
+ SystemEventReceiver receiver;
+ receiver.mPrimitive = SystemEventPrimitive{pipeFds[0]};
+
+ SystemEventPipeSender sender;
+ sender.mPrimitive = SystemEventPrimitive{pipeFds[1]};
+
+ return std::make_pair(std::move(sender), std::move(receiver));
+#else
+ // Not implemented for this platform.
+ CHECK(false);
+#endif
+}
+
+} // namespace dawn::native
diff --git a/src/dawn/native/SystemEvent.h b/src/dawn/native/SystemEvent.h
new file mode 100644
index 0000000..0e7f12f
--- /dev/null
+++ b/src/dawn/native/SystemEvent.h
@@ -0,0 +1,115 @@
+// Copyright 2023 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 SRC_DAWN_NATIVE_SYSTEMEVENT_H_
+#define SRC_DAWN_NATIVE_SYSTEMEVENT_H_
+
+#include <utility>
+
+#include "dawn/common/NonCopyable.h"
+#include "dawn/common/Platform.h"
+#include "dawn/native/IntegerTypes.h"
+
+namespace dawn::native {
+
+struct TrackedFutureWaitInfo;
+class SystemEventPipeSender;
+
+// Either a Win32 HANDLE or a POSIX fd (int) depending on OS, represented as a uintptr_t with
+// necessary conversions.
+class SystemEventPrimitive : NonCopyable {
+ public:
+ SystemEventPrimitive() = default;
+ // void* is the typedef of HANDLE in Win32.
+ explicit SystemEventPrimitive(void* win32Handle);
+ explicit SystemEventPrimitive(int posixFd);
+ ~SystemEventPrimitive();
+
+ SystemEventPrimitive(SystemEventPrimitive&&);
+ SystemEventPrimitive& operator=(SystemEventPrimitive&&);
+
+ bool IsValid() const;
+ void Close();
+
+ static constexpr uintptr_t kInvalid = 0;
+ // The underlying primitive, either a Win32 HANDLE (void*) or a POSIX fd (int), cast to
+ // uintptr_t. We treat 0 as the "invalid" value, even for POSIX.
+ uintptr_t value = kInvalid;
+};
+
+// SystemEventReceiver holds an OS event primitive (Win32 Event Object or POSIX file descriptor (fd)
+// that will be signalled by some other thing: either an OS integration like SetEventOnCompletion(),
+// or our own code like SystemEventPipeSender.
+//
+// SystemEventReceiver is one-time-use (to make it easier to use correctly) - once it's been
+// signalled, it won't ever get reset (become unsignalled). Instead, if we want to reuse underlying
+// OS objects, it should be reset and recycled *after* the SystemEventReceiver and
+// SystemEventPipeSender have been destroyed.
+class SystemEventReceiver final : NonCopyable {
+ public:
+ static SystemEventReceiver CreateAlreadySignaled();
+
+ SystemEventReceiver() = default;
+ SystemEventReceiver(SystemEventReceiver&&) = default;
+ SystemEventReceiver& operator=(SystemEventReceiver&&) = default;
+
+ private:
+ friend bool WaitAnySystemEvent(size_t, TrackedFutureWaitInfo*, Nanoseconds);
+ friend std::pair<SystemEventPipeSender, SystemEventReceiver> CreateSystemEventPipe();
+ SystemEventPrimitive mPrimitive;
+};
+
+// See CreateSystemEventPipe.
+class SystemEventPipeSender final : NonCopyable {
+ public:
+ SystemEventPipeSender() = default;
+ SystemEventPipeSender(SystemEventPipeSender&&) = default;
+ SystemEventPipeSender& operator=(SystemEventPipeSender&&) = default;
+ ~SystemEventPipeSender();
+
+ void Signal() &&;
+
+ private:
+ friend std::pair<SystemEventPipeSender, SystemEventReceiver> CreateSystemEventPipe();
+ SystemEventPrimitive mPrimitive;
+};
+
+// Implementation of WaitAny when backed by SystemEventReceiver.
+// Returns true if some future is now ready, false if not (it timed out).
+[[nodiscard]] bool WaitAnySystemEvent(size_t count,
+ TrackedFutureWaitInfo* futures,
+ Nanoseconds timeout);
+
+// CreateSystemEventPipe provides an SystemEventReceiver that can be signalled by Dawn code. This is
+// useful for queue completions on Metal (where Metal signals us by calling a callback) and for
+// async pipeline creations that happen in a worker-thread task.
+//
+// We use OS events even for these because, unlike C++/pthreads primitives (mutexes, atomics,
+// condvars, etc.), it's possible to wait-any on them (wait for any of a list of events to fire).
+// Other use-cases in Dawn that don't require wait-any should generally use C++ primitives, for
+// example for signalling the completion of other types of worker-thread work that don't need to
+// signal a WGPUFuture.
+//
+// SystemEventReceiver is one-time-use (see its docs), so there's no way to reset an
+// SystemEventPipeSender.
+//
+// - On Windows, SystemEventReceiver is a Win32 Event Object, so we can create one with
+// CreateEvent() and signal it with SetEvent().
+// - On POSIX, SystemEventReceiver is a file descriptor (fd), so we can create one with pipe(), and
+// signal it by write()ing into the pipe (to make it become readable, though we won't read() it).
+std::pair<SystemEventPipeSender, SystemEventReceiver> CreateSystemEventPipe();
+
+} // namespace dawn::native
+
+#endif // SRC_DAWN_NATIVE_SYSTEMEVENT_H_
diff --git a/src/dawn/native/metal/QueueMTL.h b/src/dawn/native/metal/QueueMTL.h
index 892d539..49dcc12 100644
--- a/src/dawn/native/metal/QueueMTL.h
+++ b/src/dawn/native/metal/QueueMTL.h
@@ -16,7 +16,11 @@
#define SRC_DAWN_NATIVE_METAL_QUEUEMTL_H_
#import <Metal/Metal.h>
+
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/SerialQueue.h"
#include "dawn/native/Queue.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/metal/CommandRecordingContext.h"
namespace dawn::native::metal {
@@ -39,7 +43,9 @@
~Queue() override;
MaybeError Initialize();
+ void UpdateWaitingEvents(ExecutionSerial completedSerial);
+ SystemEventReceiver InsertWorkDoneEvent() override;
MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
bool HasPendingCommands() const override;
ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
@@ -61,6 +67,12 @@
// A shared event that can be exported for synchronization with other users of Metal.
// MTLSharedEvent is not available until macOS 10.14+ so use just `id`.
NSPRef<id> mMtlSharedEvent = nullptr;
+
+ // This mutex must be held to access mWaitingEvents (which may happen in a Metal driver thread).
+ // TODO(crbug.com/dawn/1987): if we atomically knew a conservative lower bound on the
+ // mWaitingEvents serials, we could avoid taking this lock sometimes. Optimize if needed.
+ // See old draft code: https://dawn-review.googlesource.com/c/dawn/+/137502/29
+ MutexProtected<SerialQueue<ExecutionSerial, SystemEventPipeSender>> mWaitingEvents;
};
} // namespace dawn::native::metal
diff --git a/src/dawn/native/metal/QueueMTL.mm b/src/dawn/native/metal/QueueMTL.mm
index f193516..635fc04 100644
--- a/src/dawn/native/metal/QueueMTL.mm
+++ b/src/dawn/native/metal/QueueMTL.mm
@@ -41,6 +41,7 @@
void Queue::Destroy() {
// Forget all pending commands.
mCommandContext.AcquireCommands();
+ UpdateWaitingEvents(kMaxExecutionSerial);
mCommandQueue = nullptr;
mLastSubmittedCommands = nullptr;
mMtlSharedEvent = nullptr;
@@ -61,6 +62,42 @@
return mCommandContext.PrepareNextCommandBuffer(*mCommandQueue);
}
+void Queue::UpdateWaitingEvents(ExecutionSerial completedSerial) {
+ ASSERT(mCompletedSerial >= uint64_t(completedSerial) || completedSerial == kMaxExecutionSerial);
+ mWaitingEvents.Use([&](auto waitingEvents) {
+ for (auto& waiting : waitingEvents->IterateUpTo(completedSerial)) {
+ std::move(waiting).Signal();
+ }
+ waitingEvents->ClearUpTo(completedSerial);
+ });
+}
+
+SystemEventReceiver Queue::InsertWorkDoneEvent() {
+ ExecutionSerial serial = GetScheduledWorkDoneSerial();
+
+ // TODO(crbug.com/dawn/1987): Optimize to not create a pipe for every WorkDone/MapAsync event.
+ // Possible ways to do this:
+ // - Don't create the pipe until needed (see the todo on TrackedEvent::mReceiver).
+ // - Dedup event pipes when one serial is needed for multiple events (and add a
+ // SystemEventReceiver::Duplicate() method which dup()s its underlying pipe receiver).
+ // - Create a pipe each for each new serial instead of for each requested event (tradeoff).
+ SystemEventPipeSender sender;
+ SystemEventReceiver receiver;
+ std::tie(sender, receiver) = CreateSystemEventPipe();
+
+ mWaitingEvents.Use([&](auto waitingEvents) {
+ // Check for device loss while the lock is held. Otherwise, we could enqueue the event
+ // after mWaitingEvents has been flushed for device loss, and it'll never get cleaned up.
+ if (GetDevice()->IsLost() || mCompletedSerial >= uint64_t(serial)) {
+ std::move(sender).Signal();
+ } else {
+ waitingEvents->Enqueue(std::move(sender), serial);
+ }
+ });
+
+ return receiver;
+}
+
MaybeError Queue::WaitForIdleForDestruction() {
// Forget all pending commands.
mCommandContext.AcquireCommands();
@@ -139,6 +176,8 @@
uint64_t(pendingSerial));
ASSERT(uint64_t(pendingSerial) > mCompletedSerial.load());
this->mCompletedSerial = uint64_t(pendingSerial);
+
+ this->UpdateWaitingEvents(pendingSerial);
}];
TRACE_EVENT_ASYNC_BEGIN0(platform, GPUWork, "DeviceMTL::SubmitPendingCommandBuffer",
diff --git a/src/dawn/samples/SampleUtils.cpp b/src/dawn/samples/SampleUtils.cpp
index 289c33c..e6e8830 100644
--- a/src/dawn/samples/SampleUtils.cpp
+++ b/src/dawn/samples/SampleUtils.cpp
@@ -129,7 +129,9 @@
return wgpu::Device();
}
- instance = std::make_unique<dawn::native::Instance>();
+ WGPUInstanceDescriptor instanceDescriptor{};
+ instanceDescriptor.features.timedWaitAnyEnable = true;
+ instance = std::make_unique<dawn::native::Instance>(&instanceDescriptor);
wgpu::RequestAdapterOptions options = {};
options.backendType = backendType;
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 696e32f..048b3c7 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -536,6 +536,7 @@
"end2end/DualSourceBlendTests.cpp",
"end2end/DynamicBufferOffsetTests.cpp",
"end2end/EntryPointTests.cpp",
+ "end2end/EventTests.cpp",
"end2end/ExperimentalDP4aTests.cpp",
"end2end/ExternalTextureTests.cpp",
"end2end/FirstIndexOffsetTests.cpp",
diff --git a/src/dawn/tests/DawnTest.cpp b/src/dawn/tests/DawnTest.cpp
index 374bb69..70a4c1d 100644
--- a/src/dawn/tests/DawnTest.cpp
+++ b/src/dawn/tests/DawnTest.cpp
@@ -400,8 +400,9 @@
dawnInstanceDesc.platform = platform;
dawnInstanceDesc.nextInChain = &instanceToggles;
- wgpu::InstanceDescriptor instanceDesc;
+ wgpu::InstanceDescriptor instanceDesc{};
instanceDesc.nextInChain = &dawnInstanceDesc;
+ instanceDesc.features.timedWaitAnyEnable = !UsesWire();
auto instance = std::make_unique<native::Instance>(
reinterpret_cast<const WGPUInstanceDescriptor*>(&instanceDesc));
diff --git a/src/dawn/tests/end2end/BasicTests.cpp b/src/dawn/tests/end2end/BasicTests.cpp
index a84d7ed..9d69cca 100644
--- a/src/dawn/tests/end2end/BasicTests.cpp
+++ b/src/dawn/tests/end2end/BasicTests.cpp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "dawn/common/FutureUtils.h"
#include "dawn/tests/DawnTest.h"
-
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
@@ -61,6 +61,20 @@
ASSERT_DEVICE_ERROR(queue.WriteBuffer(buffer, 1000, &value, sizeof(value)));
}
+TEST_P(BasicTests, GetInstanceFeatures) {
+ wgpu::InstanceFeatures instanceFeatures{};
+ bool success = wgpu::GetInstanceFeatures(&instanceFeatures);
+ EXPECT_TRUE(success);
+ EXPECT_EQ(instanceFeatures.timedWaitAnyEnable, !UsesWire());
+ EXPECT_EQ(instanceFeatures.timedWaitAnyMaxCount, kTimedWaitAnyMaxCountDefault);
+ EXPECT_EQ(instanceFeatures.nextInChain, nullptr);
+
+ wgpu::ChainedStruct chained{};
+ instanceFeatures.nextInChain = &chained;
+ success = wgpu::GetInstanceFeatures(&instanceFeatures);
+ EXPECT_FALSE(success);
+}
+
DAWN_INSTANTIATE_TEST(BasicTests,
D3D11Backend(),
D3D12Backend(),
diff --git a/src/dawn/tests/end2end/EventTests.cpp b/src/dawn/tests/end2end/EventTests.cpp
new file mode 100644
index 0000000..1b19de3
--- /dev/null
+++ b/src/dawn/tests/end2end/EventTests.cpp
@@ -0,0 +1,571 @@
+// Copyright 2023 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 <atomic>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include "dawn/common/FutureUtils.h"
+#include "dawn/tests/DawnTest.h"
+#include "dawn/webgpu.h"
+
+namespace dawn {
+namespace {
+
+std::pair<wgpu::Instance, wgpu::Device> CreateExtraInstance(wgpu::InstanceDescriptor* desc) {
+ // IMPORTANT: DawnTest overrides RequestAdapter and RequestDevice and mixes
+ // up the two instances. We use these to bypass the override.
+ auto* requestAdapter = reinterpret_cast<WGPUProcInstanceRequestAdapter>(
+ wgpuGetProcAddress(nullptr, "wgpuInstanceRequestAdapter"));
+ auto* requestDevice = reinterpret_cast<WGPUProcAdapterRequestDevice>(
+ wgpuGetProcAddress(nullptr, "wgpuAdapterRequestDevice"));
+
+ wgpu::Instance instance2 = wgpu::CreateInstance(desc);
+
+ wgpu::Adapter adapter2;
+ requestAdapter(
+ instance2.Get(), nullptr,
+ [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char*, void* userdata) {
+ ASSERT_EQ(status, WGPURequestAdapterStatus_Success);
+ *reinterpret_cast<wgpu::Adapter*>(userdata) = wgpu::Adapter(adapter);
+ },
+ &adapter2);
+ ASSERT(adapter2);
+
+ wgpu::Device device2;
+ requestDevice(
+ adapter2.Get(), nullptr,
+ [](WGPURequestDeviceStatus status, WGPUDevice device, const char*, void* userdata) {
+ ASSERT_EQ(status, WGPURequestDeviceStatus_Success);
+ *reinterpret_cast<wgpu::Device*>(userdata) = wgpu::Device(device);
+ },
+ &device2);
+ ASSERT(device2);
+
+ return std::pair(std::move(instance2), std::move(device2));
+}
+
+// EventCompletionTests
+
+enum class WaitType {
+ TimedWaitAny,
+ SpinWaitAny,
+ SpinProcessEvents,
+};
+
+enum class WaitTypeAndCallbackMode {
+ TimedWaitAny_Future,
+ TimedWaitAny_FutureSpontaneous,
+ SpinWaitAny_Future,
+ SpinWaitAny_FutureSpontaneous,
+ SpinProcessEvents_ProcessEvents,
+ SpinProcessEvents_ProcessEventsSpontaneous,
+};
+
+std::ostream& operator<<(std::ostream& o, WaitTypeAndCallbackMode waitMode) {
+ switch (waitMode) {
+ case WaitTypeAndCallbackMode::TimedWaitAny_Future:
+ return o << "TimedWaitAny_Future";
+ case WaitTypeAndCallbackMode::SpinWaitAny_Future:
+ return o << "SpinWaitAny_Future";
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents:
+ return o << "SpinProcessEvents_ProcessEvents";
+ case WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous:
+ return o << "TimedWaitAny_FutureSpontaneous";
+ case WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous:
+ return o << "SpinWaitAny_FutureSpontaneous";
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous:
+ return o << "SpinProcessEvents_ProcessEventsSpontaneous";
+ }
+}
+
+DAWN_TEST_PARAM_STRUCT(EventCompletionTestParams, WaitTypeAndCallbackMode);
+
+class EventCompletionTests : public DawnTestWithParams<EventCompletionTestParams> {
+ protected:
+ wgpu::Instance testInstance;
+ wgpu::Device testDevice;
+ wgpu::Queue testQueue;
+ std::vector<wgpu::FutureWaitInfo> mFutures;
+ std::atomic<uint64_t> mCallbacksCompletedCount = 0;
+ uint64_t mCallbacksIssuedCount = 0;
+ uint64_t mCallbacksWaitedCount = 0;
+
+ void SetUp() override {
+ DawnTestWithParams::SetUp();
+ WaitTypeAndCallbackMode mode = GetParam().mWaitTypeAndCallbackMode;
+ if (UsesWire()) {
+ DAWN_TEST_UNSUPPORTED_IF(mode == WaitTypeAndCallbackMode::TimedWaitAny_Future ||
+ mode ==
+ WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous);
+ }
+ testInstance = GetInstance();
+ testDevice = device;
+ testQueue = queue;
+ // Make sure these aren't used accidentally (unfortunately can't do the same for instance):
+ device = nullptr;
+ queue = nullptr;
+ }
+
+ void UseSecondInstance() {
+ wgpu::InstanceDescriptor desc;
+ desc.features.timedWaitAnyEnable = !UsesWire();
+ std::tie(testInstance, testDevice) = CreateExtraInstance(&desc);
+ testQueue = testDevice.GetQueue();
+ }
+
+ void LoseTestDevice() {
+ EXPECT_CALL(mDeviceLostCallback,
+ Call(WGPUDeviceLostReason_Undefined, testing::_, testing::_))
+ .Times(1);
+ testDevice.ForceLoss(wgpu::DeviceLostReason::Undefined, "Device lost for testing");
+ testDevice.Tick();
+ }
+
+ void TrivialSubmit() {
+ wgpu::CommandBuffer cb = testDevice.CreateCommandEncoder().Finish();
+ testQueue.Submit(1, &cb);
+ }
+
+ wgpu::CallbackMode GetCallbackMode() {
+ switch (GetParam().mWaitTypeAndCallbackMode) {
+ case WaitTypeAndCallbackMode::TimedWaitAny_Future:
+ case WaitTypeAndCallbackMode::SpinWaitAny_Future:
+ return wgpu::CallbackMode::Future;
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents:
+ return wgpu::CallbackMode::ProcessEvents;
+ case WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous:
+ case WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous:
+ return wgpu::CallbackMode::Future | wgpu::CallbackMode::Spontaneous;
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous:
+ return wgpu::CallbackMode::ProcessEvents | wgpu::CallbackMode::Spontaneous;
+ }
+ }
+
+ bool IsSpontaneous() { return GetCallbackMode() & wgpu::CallbackMode::Spontaneous; }
+
+ void TrackForTest(wgpu::Future future) {
+ mCallbacksIssuedCount++;
+
+ switch (GetParam().mWaitTypeAndCallbackMode) {
+ case WaitTypeAndCallbackMode::TimedWaitAny_Future:
+ case WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous:
+ case WaitTypeAndCallbackMode::SpinWaitAny_Future:
+ case WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous:
+ mFutures.push_back(wgpu::FutureWaitInfo{future, false});
+ break;
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents:
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous:
+ ASSERT_EQ(future.id, 0ull);
+ break;
+ }
+ }
+
+ wgpu::Future OnSubmittedWorkDone(WGPUQueueWorkDoneStatus expectedStatus) {
+ struct Userdata {
+ EventCompletionTests* self;
+ WGPUQueueWorkDoneStatus expectedStatus;
+ };
+ Userdata* userdata = new Userdata{this, expectedStatus};
+
+ return testQueue.OnSubmittedWorkDoneF({
+ nullptr,
+ GetCallbackMode(),
+ [](WGPUQueueWorkDoneStatus status, void* userdata) {
+ Userdata* u = reinterpret_cast<Userdata*>(userdata);
+ u->self->mCallbacksCompletedCount++;
+ ASSERT_EQ(status, u->expectedStatus);
+ delete u;
+ },
+ userdata,
+ });
+ }
+
+ void TestWaitAll(bool loopOnlyOnce = false) {
+ switch (GetParam().mWaitTypeAndCallbackMode) {
+ case WaitTypeAndCallbackMode::TimedWaitAny_Future:
+ case WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous:
+ return TestWaitImpl(WaitType::TimedWaitAny);
+ case WaitTypeAndCallbackMode::SpinWaitAny_Future:
+ case WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous:
+ return TestWaitImpl(WaitType::SpinWaitAny);
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents:
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous:
+ return TestWaitImpl(WaitType::SpinProcessEvents);
+ }
+ }
+
+ void TestWaitIncorrectly() {
+ switch (GetParam().mWaitTypeAndCallbackMode) {
+ case WaitTypeAndCallbackMode::TimedWaitAny_Future:
+ case WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous:
+ case WaitTypeAndCallbackMode::SpinWaitAny_Future:
+ case WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous:
+ return TestWaitImpl(WaitType::SpinProcessEvents);
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents:
+ case WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous:
+ return TestWaitImpl(WaitType::SpinWaitAny);
+ }
+ }
+
+ private:
+ void TestWaitImpl(WaitType waitType, bool loopOnlyOnce = false) {
+ uint64_t oldCompletedCount = mCallbacksCompletedCount;
+
+ const auto start = std::chrono::high_resolution_clock::now();
+ auto testTimeExceeded = [=]() -> bool {
+ return std::chrono::high_resolution_clock::now() - start > std::chrono::seconds(5);
+ };
+
+ switch (waitType) {
+ case WaitType::TimedWaitAny: {
+ bool emptyWait = mFutures.size() == 0;
+ // Loop at least once so we can test it with 0 futures.
+ do {
+ ASSERT_FALSE(testTimeExceeded());
+ ASSERT(!UsesWire());
+ wgpu::WaitStatus status;
+
+ uint64_t oldCompletionCount = mCallbacksCompletedCount;
+ // Any futures should succeed within a few milliseconds at most.
+ status = testInstance.WaitAny(mFutures.size(), mFutures.data(), UINT64_MAX);
+ ASSERT_EQ(status, wgpu::WaitStatus::Success);
+ bool mayHaveCompletedEarly = IsSpontaneous();
+ if (!mayHaveCompletedEarly && !emptyWait) {
+ ASSERT_GT(mCallbacksCompletedCount, oldCompletionCount);
+ }
+
+ // Verify this succeeds instantly because some futures completed already.
+ status = testInstance.WaitAny(mFutures.size(), mFutures.data(), 0);
+ ASSERT_EQ(status, wgpu::WaitStatus::Success);
+
+ RemoveCompletedFutures();
+ if (loopOnlyOnce) {
+ break;
+ }
+ } while (mFutures.size() > 0);
+ } break;
+ case WaitType::SpinWaitAny: {
+ bool emptyWait = mFutures.size() == 0;
+ // Loop at least once so we can test it with 0 futures.
+ do {
+ ASSERT_FALSE(testTimeExceeded());
+
+ uint64_t oldCompletionCount = mCallbacksCompletedCount;
+ FlushWire();
+ testDevice.Tick();
+ auto status = testInstance.WaitAny(mFutures.size(), mFutures.data(), 0);
+ if (status == wgpu::WaitStatus::TimedOut) {
+ continue;
+ }
+ ASSERT_TRUE(status == wgpu::WaitStatus::Success);
+ bool mayHaveCompletedEarly = IsSpontaneous();
+ if (!mayHaveCompletedEarly && !emptyWait) {
+ ASSERT_GT(mCallbacksCompletedCount, oldCompletionCount);
+ }
+
+ RemoveCompletedFutures();
+ if (loopOnlyOnce) {
+ break;
+ }
+ } while (mFutures.size() > 0);
+ } break;
+ case WaitType::SpinProcessEvents: {
+ do {
+ ASSERT_FALSE(testTimeExceeded());
+
+ FlushWire();
+ testDevice.Tick();
+ testInstance.ProcessEvents();
+
+ if (loopOnlyOnce) {
+ break;
+ }
+ } while (mCallbacksCompletedCount < mCallbacksIssuedCount);
+ } break;
+ }
+
+ if (!IsSpontaneous()) {
+ ASSERT_EQ(mCallbacksCompletedCount - oldCompletedCount,
+ mCallbacksIssuedCount - mCallbacksWaitedCount);
+ }
+ ASSERT_EQ(mCallbacksCompletedCount, mCallbacksIssuedCount);
+ mCallbacksWaitedCount = mCallbacksCompletedCount;
+ }
+
+ void RemoveCompletedFutures() {
+ size_t oldSize = mFutures.size();
+ if (oldSize > 0) {
+ mFutures.erase(
+ std::remove_if(mFutures.begin(), mFutures.end(),
+ [](const wgpu::FutureWaitInfo& info) { return info.completed; }),
+ mFutures.end());
+ ASSERT_LT(mFutures.size(), oldSize);
+ }
+ }
+};
+
+// Wait when no events have been requested.
+TEST_P(EventCompletionTests, NoEvents) {
+ TestWaitAll();
+}
+
+// WorkDone event after submitting some trivial work.
+TEST_P(EventCompletionTests, WorkDoneSimple) {
+ TrivialSubmit();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+}
+
+// WorkDone event before device loss, wait afterward.
+TEST_P(EventCompletionTests, WorkDoneAcrossDeviceLoss) {
+ TrivialSubmit();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+}
+
+// WorkDone event after device loss.
+TEST_P(EventCompletionTests, WorkDoneAfterDeviceLoss) {
+ TrivialSubmit();
+ LoseTestDevice();
+ ASSERT_DEVICE_ERROR_ON(testDevice,
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success)));
+ TestWaitAll();
+}
+
+// WorkDone event twice after submitting some trivial work.
+TEST_P(EventCompletionTests, WorkDoneTwice) {
+ TrivialSubmit();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+}
+
+// WorkDone event without ever having submitted any work.
+TEST_P(EventCompletionTests, WorkDoneNoWork) {
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+}
+
+// WorkDone event after all work has completed already.
+TEST_P(EventCompletionTests, WorkDoneAlreadyCompleted) {
+ TrivialSubmit();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+ TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+ TestWaitAll();
+}
+
+// WorkDone events waited in reverse order.
+TEST_P(EventCompletionTests, WorkDoneOutOfOrder) {
+ // With ProcessEvents or Spontaneous we can't control the order of completion.
+ DAWN_TEST_UNSUPPORTED_IF(GetCallbackMode() &
+ (wgpu::CallbackMode::ProcessEvents | wgpu::CallbackMode::Spontaneous));
+
+ TrivialSubmit();
+ wgpu::Future f1 = OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success);
+ TrivialSubmit();
+ wgpu::Future f2 = OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success);
+
+ // When using WaitAny, normally callback ordering guarantees would guarantee f1 completes before
+ // f2. But if we wait on f2 first, then f2 is allowed to complete first because f1 still hasn't
+ // had an opportunity to complete.
+ TrackForTest(f2);
+ TestWaitAll();
+ TrackForTest(f1);
+ TestWaitAll(true);
+}
+
+constexpr WGPUQueueWorkDoneStatus kStatusUninitialized =
+ static_cast<WGPUQueueWorkDoneStatus>(INT32_MAX);
+
+TEST_P(EventCompletionTests, WorkDoneDropInstanceBeforeEvent) {
+ // TODO(crbug.com/dawn/1987): Wire does not implement instance destruction correctly yet.
+ DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+ UseSecondInstance();
+ testInstance = nullptr; // Drop the last external ref to the instance.
+
+ WGPUQueueWorkDoneStatus status = kStatusUninitialized;
+ testQueue.OnSubmittedWorkDoneF({nullptr, GetCallbackMode(),
+ [](WGPUQueueWorkDoneStatus status, void* userdata) {
+ *reinterpret_cast<WGPUQueueWorkDoneStatus*>(userdata) =
+ status;
+ },
+ &status});
+
+ // Callback should have been called immediately because we leaked it since there's no way to
+ // call WaitAny or ProcessEvents anymore.
+ //
+ // TODO(crbug.com/dawn/1987): Once Spontaneous is implemented, this should no longer expect the
+ // callback to be cleaned up immediately (and should expect it to happen on a future Tick).
+ ASSERT_EQ(status, WGPUQueueWorkDoneStatus_Unknown);
+}
+
+TEST_P(EventCompletionTests, WorkDoneDropInstanceAfterEvent) {
+ // TODO(crbug.com/dawn/1987): Wire does not implement instance destruction correctly yet.
+ DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+ UseSecondInstance();
+
+ WGPUQueueWorkDoneStatus status = kStatusUninitialized;
+ testQueue.OnSubmittedWorkDoneF({nullptr, GetCallbackMode(),
+ [](WGPUQueueWorkDoneStatus status, void* userdata) {
+ *reinterpret_cast<WGPUQueueWorkDoneStatus*>(userdata) =
+ status;
+ },
+ &status});
+
+ ASSERT_EQ(status, kStatusUninitialized);
+
+ testInstance = nullptr; // Drop the last external ref to the instance.
+
+ // Callback should have been called immediately because we leaked it since there's no way to
+ // call WaitAny or ProcessEvents anymore.
+ //
+ // TODO(crbug.com/dawn/1987): Once Spontaneous is implemented, this should no longer expect the
+ // callback to be cleaned up immediately (and should expect it to happen on a future Tick).
+ ASSERT_EQ(status, WGPUQueueWorkDoneStatus_Unknown);
+}
+
+// TODO(crbug.com/dawn/1987):
+// - Test any reentrancy guarantees (for ProcessEvents or WaitAny inside a callback),
+// to make sure things don't blow up and we don't attempt to hold locks recursively.
+// - Other tests?
+
+DAWN_INSTANTIATE_TEST_P(EventCompletionTests,
+ // TODO(crbug.com/dawn/1987): Enable tests for the rest of the backends.
+ {MetalBackend()},
+ {
+ WaitTypeAndCallbackMode::TimedWaitAny_Future,
+ WaitTypeAndCallbackMode::TimedWaitAny_FutureSpontaneous,
+ WaitTypeAndCallbackMode::SpinWaitAny_Future,
+ WaitTypeAndCallbackMode::SpinWaitAny_FutureSpontaneous,
+ WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEvents,
+ WaitTypeAndCallbackMode::SpinProcessEvents_ProcessEventsSpontaneous,
+
+ // TODO(crbug.com/dawn/1987): The cases with the Spontaneous flag
+ // enabled were added before we implemented all of the spontaneous
+ // completions. They might accidentally be overly strict.
+
+ // TODO(crbug.com/dawn/1987): Make guarantees that Spontaneous callbacks
+ // get called (as long as you're hitting "checkpoints"), and add the
+ // corresponding tests, for example:
+ // - SpinProcessEvents_Spontaneous,
+ // - SpinSubmit_Spontaneous,
+ // - SpinCheckpoint_Spontaneous (if wgpuDeviceCheckpoint is added).
+ // - (Note we don't want to guarantee Tick will process events - we
+ // could even test that it doesn't, if we make that true.)
+ });
+
+// WaitAnyTests
+
+class WaitAnyTests : public DawnTest {};
+
+TEST_P(WaitAnyTests, UnsupportedTimeout) {
+ wgpu::Instance instance2;
+ wgpu::Device device2;
+
+ if (UsesWire()) {
+ // The wire (currently) never supports timedWaitAnyEnable, so we can run this test on the
+ // default instance/device.
+ instance2 = GetInstance();
+ device2 = device;
+ } else {
+ // When not using the wire, DawnTest will unconditionally set timedWaitAnyEnable since it's
+ // useful for other tests. For this test, we need it to be false to test validation.
+ wgpu::InstanceDescriptor desc;
+ desc.features.timedWaitAnyEnable = false;
+ std::tie(instance2, device2) = CreateExtraInstance(&desc);
+ }
+
+ // UnsupportedTimeout is still validated if no futures are passed.
+ for (uint64_t timeout : {uint64_t(1), uint64_t(0), UINT64_MAX}) {
+ ASSERT_EQ(instance2.WaitAny(0, nullptr, timeout),
+ timeout > 0 ? wgpu::WaitStatus::UnsupportedTimeout : wgpu::WaitStatus::Success);
+ }
+
+ for (uint64_t timeout : {uint64_t(1), uint64_t(0), UINT64_MAX}) {
+ wgpu::FutureWaitInfo info{device2.GetQueue().OnSubmittedWorkDoneF(
+ {nullptr, wgpu::CallbackMode::Future, [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})};
+ wgpu::WaitStatus status = instance2.WaitAny(1, &info, timeout);
+ if (timeout == 0) {
+ ASSERT_TRUE(status == wgpu::WaitStatus::Success ||
+ status == wgpu::WaitStatus::TimedOut);
+ } else {
+ ASSERT_EQ(status, wgpu::WaitStatus::UnsupportedTimeout);
+ }
+ }
+}
+
+TEST_P(WaitAnyTests, UnsupportedCount) {
+ 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::Future,
+ [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})});
+ }
+ wgpu::WaitStatus status = GetInstance().WaitAny(infos.size(), infos.data(), timeout);
+ if (timeout == 0) {
+ ASSERT_TRUE(status == wgpu::WaitStatus::Success ||
+ status == wgpu::WaitStatus::TimedOut);
+ } else if (UsesWire()) {
+ // Wire doesn't support timeouts at all.
+ ASSERT_EQ(status, wgpu::WaitStatus::UnsupportedTimeout);
+ } else if (count <= 64) {
+ ASSERT_EQ(status, wgpu::WaitStatus::Success);
+ } else {
+ ASSERT_EQ(status, wgpu::WaitStatus::UnsupportedCount);
+ }
+ }
+ }
+}
+
+TEST_P(WaitAnyTests, UnsupportedMixedSources) {
+ wgpu::Device device2 = CreateDevice();
+ wgpu::Queue queue2 = device2.GetQueue();
+ for (uint64_t timeout : {uint64_t(0), uint64_t(1)}) {
+ std::vector<wgpu::FutureWaitInfo> infos{{
+ {queue.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::Future,
+ [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})},
+ {queue2.OnSubmittedWorkDoneF({nullptr, wgpu::CallbackMode::Future,
+ [](WGPUQueueWorkDoneStatus, void*) {}, nullptr})},
+ }};
+ wgpu::WaitStatus status = GetInstance().WaitAny(infos.size(), infos.data(), timeout);
+ if (timeout == 0) {
+ ASSERT_TRUE(status == wgpu::WaitStatus::Success ||
+ status == wgpu::WaitStatus::TimedOut);
+ } else if (UsesWire()) {
+ // Wire doesn't support timeouts at all.
+ ASSERT_EQ(status, wgpu::WaitStatus::UnsupportedTimeout);
+ } else {
+ ASSERT_EQ(status, wgpu::WaitStatus::UnsupportedMixedSources);
+ }
+ }
+}
+
+DAWN_INSTANTIATE_TEST(WaitAnyTests,
+ // TODO(crbug.com/dawn/1987): Enable tests for the rest of the backends.
+ MetalBackend());
+
+} // anonymous namespace
+} // namespace dawn
diff --git a/src/dawn/wire/BUILD.gn b/src/dawn/wire/BUILD.gn
index b159113..9d7fa3a 100644
--- a/src/dawn/wire/BUILD.gn
+++ b/src/dawn/wire/BUILD.gn
@@ -86,6 +86,8 @@
"client/ClientInlineMemoryTransferService.cpp",
"client/Device.cpp",
"client/Device.h",
+ "client/EventManager.cpp",
+ "client/EventManager.h",
"client/Instance.cpp",
"client/Instance.h",
"client/LimitsAndFeatures.cpp",
diff --git a/src/dawn/wire/CMakeLists.txt b/src/dawn/wire/CMakeLists.txt
index 7b7df16..d1a1f3c 100644
--- a/src/dawn/wire/CMakeLists.txt
+++ b/src/dawn/wire/CMakeLists.txt
@@ -59,6 +59,8 @@
"client/ClientInlineMemoryTransferService.cpp"
"client/Device.cpp"
"client/Device.h"
+ "client/EventManager.cpp"
+ "client/EventManager.h"
"client/Instance.cpp"
"client/Instance.h"
"client/LimitsAndFeatures.cpp"
diff --git a/src/dawn/wire/ObjectHandle.cpp b/src/dawn/wire/ObjectHandle.cpp
index 62d9f80..db747f8 100644
--- a/src/dawn/wire/ObjectHandle.cpp
+++ b/src/dawn/wire/ObjectHandle.cpp
@@ -14,11 +14,15 @@
#include "dawn/wire/ObjectHandle.h"
+#include "dawn/common/Assert.h"
+
namespace dawn::wire {
ObjectHandle::ObjectHandle() = default;
ObjectHandle::ObjectHandle(ObjectId id, ObjectGeneration generation)
- : id(id), generation(generation) {}
+ : id(id), generation(generation) {
+ ASSERT(id != 0);
+}
ObjectHandle::ObjectHandle(const volatile ObjectHandle& rhs)
: id(rhs.id), generation(rhs.generation) {}
@@ -41,4 +45,9 @@
generation = rhs.generation;
return *this;
}
+
+bool ObjectHandle::IsValid() const {
+ return id > 0;
+}
+
} // namespace dawn::wire
diff --git a/src/dawn/wire/ObjectHandle.h b/src/dawn/wire/ObjectHandle.h
index 3e6cf64..1335efb 100644
--- a/src/dawn/wire/ObjectHandle.h
+++ b/src/dawn/wire/ObjectHandle.h
@@ -21,9 +21,12 @@
using ObjectId = uint32_t;
using ObjectGeneration = uint32_t;
+
+// ObjectHandle identifies some WebGPU object in the wire.
+// An ObjectHandle will never be reused, so can be used to uniquely identify an object forever.
struct ObjectHandle {
- ObjectId id;
- ObjectGeneration generation;
+ ObjectId id = 0;
+ ObjectGeneration generation = 0;
ObjectHandle();
ObjectHandle(ObjectId id, ObjectGeneration generation);
@@ -42,6 +45,8 @@
}
ObjectHandle& AssignFrom(const ObjectHandle& rhs);
ObjectHandle& AssignFrom(const volatile ObjectHandle& rhs);
+
+ bool IsValid() const;
};
} // namespace dawn::wire
diff --git a/src/dawn/wire/client/Client.cpp b/src/dawn/wire/client/Client.cpp
index 5feaca1..27bec54 100644
--- a/src/dawn/wire/client/Client.cpp
+++ b/src/dawn/wire/client/Client.cpp
@@ -38,7 +38,10 @@
} // anonymous namespace
Client::Client(CommandSerializer* serializer, MemoryTransferService* memoryTransferService)
- : ClientBase(), mSerializer(serializer), mMemoryTransferService(memoryTransferService) {
+ : ClientBase(),
+ mSerializer(serializer),
+ mMemoryTransferService(memoryTransferService),
+ mEventManager(this) {
if (mMemoryTransferService == nullptr) {
// If a MemoryTransferService is not provided, fall back to inline memory.
mOwnedMemoryTransferService = CreateInlineMemoryTransferService();
@@ -48,6 +51,7 @@
Client::~Client() {
DestroyAllObjects();
+ mEventManager.ShutDown();
}
void Client::DestroyAllObjects() {
@@ -142,6 +146,10 @@
Free(FromAPI(reservation.instance));
}
+EventManager* Client::GetEventManager() {
+ return &mEventManager;
+}
+
void Client::Disconnect() {
mDisconnected = true;
mSerializer = ChunkedCommandSerializer(NoopCommandSerializer::GetInstance());
@@ -160,6 +168,7 @@
object->value()->CancelCallbacksForDisconnect();
}
}
+ mEventManager.ShutDown();
}
bool Client::IsDisconnected() const {
diff --git a/src/dawn/wire/client/Client.h b/src/dawn/wire/client/Client.h
index 6a5e02f..7828535 100644
--- a/src/dawn/wire/client/Client.h
+++ b/src/dawn/wire/client/Client.h
@@ -27,6 +27,7 @@
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireDeserializeAllocator.h"
#include "dawn/wire/client/ClientBase_autogen.h"
+#include "dawn/wire/client/EventManager.h"
#include "dawn/wire/client/ObjectStore.h"
namespace dawn::wire::client {
@@ -91,6 +92,8 @@
mSerializer.SerializeCommand(cmd, *this, std::forward<Extensions>(es)...);
}
+ EventManager* GetEventManager();
+
void Disconnect();
bool IsDisconnected() const;
@@ -105,6 +108,7 @@
MemoryTransferService* mMemoryTransferService = nullptr;
std::unique_ptr<MemoryTransferService> mOwnedMemoryTransferService = nullptr;
PerObjectType<LinkedList<ObjectBase>> mObjects;
+ EventManager mEventManager;
bool mDisconnected = false;
};
diff --git a/src/dawn/wire/client/EventManager.cpp b/src/dawn/wire/client/EventManager.cpp
new file mode 100644
index 0000000..efa4902
--- /dev/null
+++ b/src/dawn/wire/client/EventManager.cpp
@@ -0,0 +1,172 @@
+// Copyright 2023 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 <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "dawn/wire/ObjectHandle.h"
+#include "dawn/wire/client/Client.h"
+#include "dawn/wire/client/EventManager.h"
+
+namespace dawn::wire::client {
+
+// EventManager
+
+EventManager::EventManager(Client* client) : mClient(client) {}
+
+FutureID EventManager::TrackEvent(WGPUCallbackModeFlags mode, EventCallback&& callback) {
+ DAWN_UNUSED(ValidateAndFlattenCallbackMode(mode));
+
+ FutureID futureID = mNextFutureID++;
+
+ if (mClient->IsDisconnected()) {
+ callback(EventCompletionType::Shutdown);
+ return futureID;
+ }
+
+ mTrackedEvents.Use([&](auto trackedEvents) {
+ auto [it, inserted] =
+ trackedEvents->emplace(futureID, TrackedEvent(mode, std::move(callback)));
+ ASSERT(inserted);
+ });
+
+ return futureID;
+}
+
+void EventManager::ShutDown() {
+ // Call any outstanding callbacks before destruction.
+ while (true) {
+ std::unordered_map<FutureID, TrackedEvent> movedEvents;
+ mTrackedEvents.Use([&](auto trackedEvents) { movedEvents = std::move(*trackedEvents); });
+
+ if (movedEvents.empty()) {
+ break;
+ }
+
+ for (auto& [futureID, trackedEvent] : movedEvents) {
+ // Event should be already marked Ready since events are actually driven by
+ // RequestTrackers (at the time of this writing), which all shut down before this.
+ ASSERT(trackedEvent.mReady);
+ trackedEvent.mCallback(EventCompletionType::Shutdown);
+ trackedEvent.mCallback = nullptr;
+ }
+ }
+}
+
+void EventManager::SetFutureReady(FutureID futureID) {
+ ASSERT(futureID > 0);
+ mTrackedEvents.Use([&](auto trackedEvents) {
+ TrackedEvent& trackedEvent = trackedEvents->at(futureID); // Asserts futureID is in the map
+ trackedEvent.mReady = true;
+ });
+ // TODO(crbug.com/dawn/1987): Handle spontaneous completions.
+}
+
+void EventManager::ProcessPollEvents() {
+ std::vector<TrackedEvent> eventsToCompleteNow;
+
+ // TODO(crbug.com/dawn/1987): EventManager shouldn't bother to track ProcessEvents-type events
+ // until they've completed. We can queue them up when they're received on the wire. (Before that
+ // point, the RequestTracker tracks them. If/when we merge this with RequestTracker, then we'll
+ // track both here but still don't need to queue them for ProcessEvents until they complete.)
+ mTrackedEvents.Use([&](auto trackedEvents) {
+ for (auto it = trackedEvents->begin(); it != trackedEvents->end();) {
+ TrackedEvent& event = it->second;
+ bool shouldRemove = (event.mMode & WGPUCallbackMode_ProcessEvents) && event.mReady;
+ if (!shouldRemove) {
+ ++it;
+ continue;
+ }
+
+ // mCallback may still be null if it's stale (was already spontaneously completed).
+ if (event.mCallback) {
+ eventsToCompleteNow.emplace_back(std::move(event));
+ }
+ it = trackedEvents->erase(it);
+ }
+ });
+
+ for (TrackedEvent& event : eventsToCompleteNow) {
+ ASSERT(event.mReady && event.mCallback);
+ event.mCallback(EventCompletionType::Ready);
+ event.mCallback = nullptr;
+ }
+}
+
+WGPUWaitStatus EventManager::WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS) {
+ // Validate for feature support.
+ if (timeoutNS > 0) {
+ // Wire doesn't support timedWaitEnable (for now). (There's no UnsupportedCount or
+ // UnsupportedMixedSources validation here, because those only apply to timed waits.)
+ //
+ // TODO(crbug.com/dawn/1987): CreateInstance needs to validate timedWaitEnable was false.
+ return WGPUWaitStatus_UnsupportedTimeout;
+ }
+
+ if (count == 0) {
+ return WGPUWaitStatus_Success;
+ }
+
+ std::vector<TrackedEvent> eventsToCompleteNow;
+ bool anyCompleted = false;
+ const FutureID firstInvalidFutureID = mNextFutureID;
+ mTrackedEvents.Use([&](auto trackedEvents) {
+ for (size_t i = 0; i < count; ++i) {
+ FutureID futureID = infos[i].future.id;
+ ASSERT(futureID < firstInvalidFutureID);
+
+ auto it = trackedEvents->find(futureID);
+ if (it == trackedEvents->end()) {
+ infos[i].completed = true;
+ anyCompleted = true;
+ continue;
+ }
+
+ TrackedEvent& event = it->second;
+ ASSERT(event.mMode & WGPUCallbackMode_Future);
+ // Early update .completed, in prep to complete the callback if ready.
+ infos[i].completed = event.mReady;
+ if (event.mReady) {
+ anyCompleted = true;
+ if (event.mCallback) {
+ eventsToCompleteNow.emplace_back(std::move(event));
+ }
+ trackedEvents->erase(it);
+ }
+ }
+ });
+
+ // TODO(crbug.com/dawn/1987): Guarantee the event ordering from the JS spec.
+ for (TrackedEvent& event : eventsToCompleteNow) {
+ ASSERT(event.mReady && event.mCallback);
+ // .completed has already been set to true (before the callback, per API contract).
+ event.mCallback(EventCompletionType::Ready);
+ event.mCallback = nullptr;
+ }
+
+ return anyCompleted ? WGPUWaitStatus_Success : WGPUWaitStatus_TimedOut;
+}
+
+// EventManager::TrackedEvent
+
+EventManager::TrackedEvent::TrackedEvent(WGPUCallbackModeFlags mode, EventCallback&& callback)
+ : mMode(mode), mCallback(callback) {}
+
+EventManager::TrackedEvent::~TrackedEvent() {
+ // Make sure we're not dropping a callback on the floor.
+ ASSERT(!mCallback);
+}
+
+} // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/EventManager.h b/src/dawn/wire/client/EventManager.h
new file mode 100644
index 0000000..0b689d6
--- /dev/null
+++ b/src/dawn/wire/client/EventManager.h
@@ -0,0 +1,77 @@
+// Copyright 2023 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 SRC_DAWN_WIRE_CLIENT_EVENTMANAGER_H_
+#define SRC_DAWN_WIRE_CLIENT_EVENTMANAGER_H_
+
+#include <cstddef>
+#include <functional>
+#include <unordered_map>
+
+#include "dawn/common/FutureUtils.h"
+#include "dawn/common/MutexProtected.h"
+#include "dawn/common/NonCopyable.h"
+#include "dawn/common/Ref.h"
+#include "dawn/webgpu.h"
+#include "dawn/wire/ObjectHandle.h"
+
+namespace dawn::wire::client {
+
+class Client;
+
+// Code to run to complete the event (after receiving a ready notification from the wire).
+using EventCallback = std::function<void(EventCompletionType)>;
+
+// Subcomponent which tracks callback events for the Future-based callback
+// entrypoints. All events from this instance (regardless of whether from an adapter, device, queue,
+// etc.) are tracked here, and used by the instance-wide ProcessEvents and WaitAny entrypoints.
+//
+// TODO(crbug.com/dawn/1987): This should probably be merged together with RequestTracker.
+class EventManager final : NonMovable {
+ public:
+ explicit EventManager(Client*);
+ ~EventManager() = default;
+
+ FutureID TrackEvent(WGPUCallbackModeFlags mode, EventCallback&& callback);
+ void ShutDown();
+ void SetFutureReady(FutureID futureID);
+ void ProcessPollEvents();
+ WGPUWaitStatus WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS);
+
+ private:
+ struct TrackedEvent : dawn::NonCopyable {
+ TrackedEvent(WGPUCallbackModeFlags mode, EventCallback&& callback);
+ ~TrackedEvent();
+
+ TrackedEvent(TrackedEvent&&) = default;
+ TrackedEvent& operator=(TrackedEvent&&) = default;
+
+ WGPUCallbackModeFlags mMode;
+ // Callback. Falsey if already called.
+ EventCallback mCallback;
+ // These states don't need to be atomic because they're always protected by
+ // mTrackedEventsMutex (or moved out to a local variable).
+ bool mReady = false;
+ };
+
+ Client* mClient;
+
+ // Tracks all kinds of events (for both WaitAny and ProcessEvents).
+ MutexProtected<std::unordered_map<FutureID, TrackedEvent>> mTrackedEvents;
+ std::atomic<FutureID> mNextFutureID = 1;
+};
+
+} // namespace dawn::wire::client
+
+#endif // SRC_DAWN_WIRE_CLIENT_EVENTMANAGER_H_
diff --git a/src/dawn/wire/client/Instance.cpp b/src/dawn/wire/client/Instance.cpp
index efd9b74..fe82dbd 100644
--- a/src/dawn/wire/client/Instance.cpp
+++ b/src/dawn/wire/client/Instance.cpp
@@ -18,6 +18,25 @@
namespace dawn::wire::client {
+// Free-standing API functions
+
+WGPUBool ClientGetInstanceFeatures(WGPUInstanceFeatures* features) {
+ if (features->nextInChain != nullptr) {
+ return false;
+ }
+
+ features->timedWaitAnyEnable = false;
+ features->timedWaitAnyMaxCount = kTimedWaitAnyMaxCountDefault;
+ return true;
+}
+
+WGPUInstance ClientCreateInstance(WGPUInstanceDescriptor const* descriptor) {
+ UNREACHABLE();
+ return nullptr;
+}
+
+// Instance
+
Instance::~Instance() {
mRequestAdapterRequests.CloseAll([](RequestAdapterData* request) {
request->callback(WGPURequestAdapterStatus_Unknown, nullptr,
@@ -100,4 +119,35 @@
return true;
}
+void Instance::ProcessEvents() {
+ // TODO(crbug.com/dawn/1987): This should only process events for this Instance, not others
+ // on the same client. When EventManager is moved to Instance, this can be fixed.
+ GetClient()->GetEventManager()->ProcessPollEvents();
+
+ // TODO(crbug.com/dawn/1987): The responsibility of ProcessEvents here is a bit mixed. It both
+ // processes events coming in from the server, and also prompts the server to check for and
+ // forward over new events - which won't be received until *after* this client-side
+ // ProcessEvents completes.
+ //
+ // Fixing this nicely probably requires the server to more self-sufficiently
+ // forward the events, which is half of making the wire fully invisible to use (which we might
+ // like to do, someday, but not soon). This is easy for immediate events (like requestDevice)
+ // and thread-driven events (async pipeline creation), but harder for queue fences where we have
+ // to wait on the backend and then trigger Dawn code to forward the event.
+ //
+ // In the meantime, we could maybe do this on client->server flush to keep this concern in the
+ // wire instead of in the API itself, but otherwise it's not significantly better so we just
+ // keep it here for now for backward compatibility.
+ InstanceProcessEventsCmd cmd;
+ cmd.self = ToAPI(this);
+ GetClient()->SerializeCommand(cmd);
+}
+
+WGPUWaitStatus Instance::WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS) {
+ // In principle the EventManager should be on the Instance, not the Client.
+ // But it's hard to get from an object to its Instance right now, so we can
+ // store it on the Client.
+ return GetClient()->GetEventManager()->WaitAny(count, infos, timeoutNS);
+}
+
} // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/Instance.h b/src/dawn/wire/client/Instance.h
index 1400565..dcb8dc8 100644
--- a/src/dawn/wire/client/Instance.h
+++ b/src/dawn/wire/client/Instance.h
@@ -16,7 +16,6 @@
#define SRC_DAWN_WIRE_CLIENT_INSTANCE_H_
#include "dawn/webgpu.h"
-
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/client/ObjectBase.h"
@@ -24,6 +23,9 @@
namespace dawn::wire::client {
+WGPUBool ClientGetInstanceFeatures(WGPUInstanceFeatures* features);
+WGPUInstance ClientCreateInstance(WGPUInstanceDescriptor const* descriptor);
+
class Instance final : public ObjectBase {
public:
using ObjectBase::ObjectBase;
@@ -42,6 +44,9 @@
uint32_t featuresCount,
const WGPUFeatureName* features);
+ void ProcessEvents();
+ WGPUWaitStatus WaitAny(size_t count, WGPUFutureWaitInfo* infos, uint64_t timeoutNS);
+
private:
struct RequestAdapterData {
WGPURequestAdapterCallback callback = nullptr;
diff --git a/src/dawn/wire/client/Queue.cpp b/src/dawn/wire/client/Queue.cpp
index bc5a4bb..70f505c 100644
--- a/src/dawn/wire/client/Queue.cpp
+++ b/src/dawn/wire/client/Queue.cpp
@@ -15,7 +15,7 @@
#include "dawn/wire/client/Queue.h"
#include "dawn/wire/client/Client.h"
-#include "dawn/wire/client/Device.h"
+#include "dawn/wire/client/EventManager.h"
namespace dawn::wire::client {
@@ -52,6 +52,44 @@
client->SerializeCommand(cmd);
}
+WGPUFuture Queue::OnSubmittedWorkDoneF(const WGPUQueueWorkDoneCallbackInfo& callbackInfo) {
+ // TODO(crbug.com/dawn/1987): Once we always return a future, change this to log to the instance
+ // (note, not raise a validation error to the device) and return the null future.
+ ASSERT(callbackInfo.nextInChain == nullptr);
+
+ Client* client = GetClient();
+ FutureID futureIDInternal = client->GetEventManager()->TrackEvent(
+ callbackInfo.mode, [=](EventCompletionType completionType) {
+ WGPUQueueWorkDoneStatus status = completionType == EventCompletionType::Shutdown
+ ? WGPUQueueWorkDoneStatus_Unknown
+ : WGPUQueueWorkDoneStatus_Success;
+ callbackInfo.callback(status, callbackInfo.userdata);
+ });
+
+ struct Lambda {
+ Client* client;
+ FutureID futureIDInternal;
+ };
+ Lambda* lambda = new Lambda{client, futureIDInternal};
+ uint64_t serial = mOnWorkDoneRequests.Add(
+ {[](WGPUQueueWorkDoneStatus /* ignored */, void* userdata) {
+ auto* lambda = static_cast<Lambda*>(userdata);
+ lambda->client->GetEventManager()->SetFutureReady(lambda->futureIDInternal);
+ delete lambda;
+ },
+ lambda});
+
+ QueueOnSubmittedWorkDoneCmd cmd;
+ cmd.queueId = GetWireId();
+ cmd.signalValue = 0;
+ cmd.requestSerial = serial;
+
+ client->SerializeCommand(cmd);
+
+ FutureID futureID = (callbackInfo.mode & WGPUCallbackMode_Future) ? futureIDInternal : 0;
+ return {futureID};
+}
+
void Queue::WriteBuffer(WGPUBuffer cBuffer, uint64_t bufferOffset, const void* data, size_t size) {
Buffer* buffer = FromAPI(cBuffer);
diff --git a/src/dawn/wire/client/Queue.h b/src/dawn/wire/client/Queue.h
index 28424c0..17edf06 100644
--- a/src/dawn/wire/client/Queue.h
+++ b/src/dawn/wire/client/Queue.h
@@ -34,6 +34,7 @@
void OnSubmittedWorkDone(uint64_t signalValue,
WGPUQueueWorkDoneCallback callback,
void* userdata);
+ WGPUFuture OnSubmittedWorkDoneF(const WGPUQueueWorkDoneCallbackInfo& callbackInfo);
void WriteBuffer(WGPUBuffer cBuffer, uint64_t bufferOffset, const void* data, size_t size);
void WriteTexture(const WGPUImageCopyTexture* destination,
const void* data,