[dawn][emscripten] Implements initial Future's API
- Implements the base features for Futures along with an implementation
for instance.RequestAdapter.
Bug: 358445329
Change-Id: I8b1a7e30b4be682fa5b9d80d51cf9df5b6bdd834
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/202176
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/samples/SampleUtils.cpp b/src/dawn/samples/SampleUtils.cpp
index d53cf4b..499d90d 100644
--- a/src/dawn/samples/SampleUtils.cpp
+++ b/src/dawn/samples/SampleUtils.cpp
@@ -271,38 +271,41 @@
// Create the instance
sample->instance = wgpu::CreateInstance(nullptr);
- // Create the adapter, device, and set the emscripten loop via callbacks
+ // Synchronously create the adapter
+ sample->instance.WaitAny(
+ sample->instance.RequestAdapter(
+ &adapterOptions, wgpu::CallbackMode::WaitAnyOnly,
+ [](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, const char* message) {
+ if (status != wgpu::RequestAdapterStatus::Success) {
+ dawn::ErrorLog() << "Failed to get an adapter:" << message;
+ return;
+ }
+ sample->adapter = std::move(adapter);
+ }),
+ UINT64_MAX);
+ if (sample->adapter == nullptr) {
+ return 1;
+ }
+
+ // Create the device and set the emscripten loop via callbacks
// TODO(crbug.com/42241221) Update to use the newer APIs once they are implemented in
// Emscripten.
- sample->instance.RequestAdapter(
- &adapterOptions,
- [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message,
- void* userdata) {
- if (status != WGPURequestAdapterStatus_Success) {
- dawn::ErrorLog() << "Failed to get an adapter:" << message;
+ wgpu::DeviceDescriptor deviceDesc = {};
+ sample->adapter.RequestDevice(
+ &deviceDesc,
+ [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* userdata) {
+ if (status != WGPURequestDeviceStatus_Success) {
+ dawn::ErrorLog() << "Failed to get an device:" << message;
return;
}
- sample->adapter = wgpu::Adapter::Acquire(adapter);
+ sample->device = wgpu::Device::Acquire(device);
+ sample->queue = sample->device.GetQueue();
- wgpu::DeviceDescriptor deviceDesc = {};
- sample->adapter.RequestDevice(
- &deviceDesc,
- [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message,
- void* userdata) {
- if (status != WGPURequestDeviceStatus_Success) {
- dawn::ErrorLog() << "Failed to get an device:" << message;
- return;
- }
- sample->device = wgpu::Device::Acquire(device);
- sample->queue = sample->device.GetQueue();
-
- if (sample->Setup()) {
- emscripten_set_main_loop([]() { sample->FrameImpl(); }, 0, false);
- } else {
- dawn::ErrorLog() << "Failed to setup sample";
- }
- },
- nullptr);
+ if (sample->Setup()) {
+ emscripten_set_main_loop([]() { sample->FrameImpl(); }, 0, false);
+ } else {
+ dawn::ErrorLog() << "Failed to setup sample";
+ }
},
nullptr);
#endif // __EMSCRIPTEN__
diff --git a/src/emdawnwebgpu/CMakeLists.txt b/src/emdawnwebgpu/CMakeLists.txt
index 21bcfef..d545432 100644
--- a/src/emdawnwebgpu/CMakeLists.txt
+++ b/src/emdawnwebgpu/CMakeLists.txt
@@ -194,6 +194,8 @@
target_link_options(emdawnwebgpu_config INTERFACE
# We are using Dawn-generated bindings, not built-in ones
"-sUSE_WEBGPU=0"
+ # We need Asyncify for Future implementation.
+ "-sASYNCIFY=1"
# The JS libraries needed for bindings
"--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_enum_tables.js"
"--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_generated_struct_info.js"
diff --git a/third_party/emdawnwebgpu/library_webgpu.js b/third_party/emdawnwebgpu/library_webgpu.js
index f5d4926..ac3b0df 100644
--- a/third_party/emdawnwebgpu/library_webgpu.js
+++ b/third_party/emdawnwebgpu/library_webgpu.js
@@ -83,6 +83,23 @@
WebGPU._table[ptr] = value;
},
+ // Future to promise management, and temporary list allocated up-front for
+ // WaitAny implementation on the promises. Note that all FutureIDs
+ // (uint64_t) are passed either as a low and high value or by pointer
+ // because they need to be passed back and forth between JS and C++, and JS
+ // is currently unable to pass a value to a C++ function as a uint64_t.
+ // This might be possible with -sWASM_BIGINT, but I was unable to get that
+ // to work properly at the time of writing.
+ _futures: [],
+ _futureInsert: (futureIdL, futureIdH, promise) => {
+#if ASYNCIFY
+ var futureId = futureIdH * 0x100000000 + futureIdL;
+ WebGPU._futures[futureId] =
+ new Promise((resolve) => promise.finally(resolve(futureId)));
+#endif
+ },
+ _waitAnyPromisesList: [],
+
errorCallback: (callback, type, message, userdata) => {
var sp = stackSave();
var messagePtr = stringToUTF8OnStack(message);
@@ -452,13 +469,46 @@
},
// ----------------------------------------------------------------------------
- // Definitions for JS emwgpu functions (callable from webgpu.cpp)
+ // Definitions for standalone JS emwgpu functions (callable from webgpu.cpp)
// ----------------------------------------------------------------------------
emwgpuDelete: (id) => {
delete WebGPU._table[id];
},
+#if ASYNCIFY
+ // Returns a FutureID that was resolved, or kNullFutureId if timed out.
+ emwgpuWaitAny__async: true,
+ emwgpuWaitAny: (futurePtr, futureCount, timeoutNSPtr) => {
+ var promises = WebGPU._waitAnyPromisesList;
+ if (timeoutNSPtr) {
+ var timeoutMS = {{{ gpu.makeGetU64('timeoutNSPtr', 0) }}} / 1000000;
+ promises.length = futureCount + 1;
+ promise[futureCount] = new Promise((resolve) => setTimeout(resolve, timeoutMS, 0));
+ } else {
+ promises.length = futureCount;
+ }
+
+ for (var i = 0; i < futureCount; ++i) {
+ // If any of the FutureIDs are not tracked, it means it must be done.
+ var futureId = {{{ gpu.makeGetU64('(futurePtr + i * 8)', 0) }}};
+ if (!(futureId in WebGPU._futures)) {
+ return futureId;
+ }
+ promises[i] = WebGPU._futures[futureId];
+ }
+
+ var result = Asyncify.handleAsync(async () => {
+ return await Promise.race(promises);
+ });
+
+ // Clean up internal futures state.
+ delete WebGPU._futures[result];
+ WebGPU._waitAnyPromisesList.length = 0;
+ return result;
+ },
+#endif
+
// --------------------------------------------------------------------------
// WebGPU function definitions, with methods organized by "class".
//
@@ -1817,15 +1867,9 @@
return navigator["gpu"]["wgslLanguageFeatures"].has(WebGPU.WGSLFeatureName[featureEnumValue]);
},
- wgpuInstanceProcessEvents: (instance) => {
- // TODO: This could probably be emulated with ASYNCIFY.
-#if ASSERTIONS
- abort('wgpuInstanceProcessEvents is unsupported (use requestAnimationFrame via html5.h instead)');
-#endif
- },
-
- wgpuInstanceRequestAdapter__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateAdapter'],
- wgpuInstanceRequestAdapter: (instancePtr, options, callback, userdata) => {
+ emwgpuInstanceRequestAdapter__i53abi: false,
+ emwgpuInstanceRequestAdapter__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateAdapter', 'emwgpuOnRequestAdapterCompleted'],
+ emwgpuInstanceRequestAdapter: (instancePtr, futureIdL, futureIdH, options) => {
var opts;
if (options) {
{{{ gpu.makeCheckDescriptor('options') }}}
@@ -1838,37 +1882,35 @@
}
if (!('gpu' in navigator)) {
- var sp = stackSave();
- var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (navigator.gpu is not available)');
- {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
- stackRestore(sp);
+ withStackSave(() => {
+ var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (navigator.gpu is not available)');
+ _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr);
+ });
return;
}
{{{ runtimeKeepalivePush() }}}
- navigator["gpu"]["requestAdapter"](opts).then((adapter) => {
+ WebGPU._futureInsert(futureIdL, futureIdH, navigator["gpu"]["requestAdapter"](opts).then((adapter) => {
{{{ runtimeKeepalivePop() }}}
- callUserCallback(() => {
- if (adapter) {
- var adapterPtr = _emwgpuCreateAdapter();
- WebGPU._tableInsert(adapterPtr, adapter);
- {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Success }}}, adapterPtr, 0, userdata);
- } else {
- var sp = stackSave();
- var messagePtr = stringToUTF8OnStack('WebGPU not available on this system (requestAdapter returned null)');
- {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr, userdata);
- stackRestore(sp);
- }
- });
+ if (adapter) {
+ var adapterPtr = _emwgpuCreateAdapter();
+ WebGPU._tableInsert(adapterPtr, adapter);
+ _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Success }}}, adapterPtr, 0);
+ } else {
+ withStackSave(() => {
+ var messagePtr = stringToUTF8OnStack('WebGPU not available on this browser (requestAdapter returned null)');
+ _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Unavailable }}}, 0, messagePtr);
+ });
+ }
+ return;
}, (ex) => {
{{{ runtimeKeepalivePop() }}}
- callUserCallback(() => {
- var sp = stackSave();
+ withStackSave(() => {
var messagePtr = stringToUTF8OnStack(ex.message);
- {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr, userdata);
- stackRestore(sp);
+ _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Error }}}, 0, messagePtr);
});
- });
+ return;
+ }));
},
// --------------------------------------------------------------------------
@@ -2485,7 +2527,9 @@
for (const key of Object.keys(LibraryWebGPU)) {
if (typeof LibraryWebGPU[key] === 'function') {
- LibraryWebGPU[key + '__i53abi'] = true;
+ if (!(key + '__i53abi' in LibraryWebGPU)) {
+ LibraryWebGPU[key + '__i53abi'] = true;
+ }
}
}
diff --git a/third_party/emdawnwebgpu/webgpu.cpp b/third_party/emdawnwebgpu/webgpu.cpp
index 6a77b26..4572b67 100644
--- a/third_party/emdawnwebgpu/webgpu.cpp
+++ b/third_party/emdawnwebgpu/webgpu.cpp
@@ -7,18 +7,42 @@
// This file and library_webgpu.js together implement <webgpu/webgpu.h>.
//
+#include <emscripten/emscripten.h>
#include <webgpu/webgpu.h>
#include <array>
#include <atomic>
#include <cassert>
#include <cstdlib>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <set>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+using FutureID = uint64_t;
+static constexpr FutureID kNullFutureId = 0;
+using InstanceID = uint64_t;
// ----------------------------------------------------------------------------
// Declarations for JS emwgpu functions (defined in library_webgpu.js)
// ----------------------------------------------------------------------------
extern "C" {
void emwgpuDelete(void* id);
+
+// Note that for the JS entry points, we pass uint64_t as pointer and decode it
+// on the other side.
+FutureID emwgpuWaitAny(FutureID const* futurePtr,
+ size_t futureCount,
+ uint64_t const* timeoutNSPtr);
+
+// Future/async operation that need to be forwarded to JS.
+void emwgpuInstanceRequestAdapter(WGPUInstance instance,
+ FutureID futureId,
+ const WGPURequestAdapterOptions* options);
} // extern "C"
// ----------------------------------------------------------------------------
@@ -38,7 +62,17 @@
void operator=(const NonCopyable&) = delete;
};
-class RefCounted : public NonCopyable {
+class NonMovable : NonCopyable {
+ protected:
+ constexpr NonMovable() = default;
+ ~NonMovable() = default;
+
+ private:
+ NonMovable(NonMovable&&) = delete;
+ void operator=(NonMovable&&) = delete;
+};
+
+class RefCounted : NonMovable {
public:
RefCounted() = default;
virtual ~RefCounted() = default;
@@ -111,13 +145,354 @@
X(TextureView)
// clang-format on
+// ----------------------------------------------------------------------------
+// Future related structures and helpers.
+// ----------------------------------------------------------------------------
+
+enum class EventCompletionType {
+ Ready,
+ Shutdown,
+};
+enum class EventType {
+ RequestAdapter,
+};
+
+class EventManager;
+class TrackedEvent;
+
+class TrackedEvent : NonMovable {
+ public:
+ virtual ~TrackedEvent() = default;
+ virtual EventType GetType() = 0;
+ virtual void Complete(FutureID futureId, EventCompletionType type) = 0;
+
+ protected:
+ TrackedEvent(InstanceID instance, WGPUCallbackMode mode)
+ : mInstanceId(instance), mMode(mode) {}
+
+ private:
+ friend class EventManager;
+
+ // Events need to keep track of the instance they came from for validation.
+ const InstanceID mInstanceId;
+ const WGPUCallbackMode mMode;
+ bool mIsReady = false;
+};
+
+// Thread-safe EventManager class that tracks all events.
+//
+// Note that there is a single global EventManager that should be accessed via
+// GetEventManager(). The EventManager needs to outlive all WGPUInstances in
+// order to handle Spontaneous events.
+class EventManager : NonMovable {
+ public:
+ void RegisterInstance(InstanceID instance) {
+ assert(instance);
+ std::unique_lock<std::mutex> lock(mMutex);
+ mPerInstanceEvents.try_emplace(instance);
+ }
+
+ void UnregisterInstance(InstanceID instance) {
+ assert(instance);
+ std::unique_lock<std::mutex> lock(mMutex);
+ auto it = mPerInstanceEvents.find(instance);
+ assert(it != mPerInstanceEvents.end());
+
+ // When unregistering the Instance, resolve all non-spontaneous callbacks
+ // with Shutdown.
+ for (const FutureID futureId : it->second) {
+ if (auto it = mEvents.find(futureId); it != mEvents.end()) {
+ it->second->Complete(futureId, EventCompletionType::Shutdown);
+ mEvents.erase(it);
+ }
+ }
+ mPerInstanceEvents.erase(instance);
+ }
+
+ void ProcessEvents(InstanceID instance) {
+ assert(instance);
+ std::vector<std::pair<FutureID, std::unique_ptr<TrackedEvent>>> completable;
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ auto instanceIt = mPerInstanceEvents.find(instance);
+ assert(instanceIt != mPerInstanceEvents.end());
+ auto& instanceFutureIds = instanceIt->second;
+
+ // Note that we are only currently handling AllowProcessEvents events,
+ // i.e. we are not handling AllowSpontaneous events in this loop.
+ for (auto futureIdsIt = instanceFutureIds.begin();
+ futureIdsIt != instanceFutureIds.end();) {
+ FutureID futureId = *futureIdsIt;
+ auto eventIt = mEvents.find(futureId);
+ if (eventIt == mEvents.end()) {
+ ++futureIdsIt;
+ continue;
+ }
+ auto& event = eventIt->second;
+
+ if (event->mMode == WGPUCallbackMode_AllowProcessEvents &&
+ event->mIsReady) {
+ completable.emplace_back(futureId, std::move(event));
+ mEvents.erase(eventIt);
+ futureIdsIt = instanceFutureIds.erase(futureIdsIt);
+ } else {
+ ++futureIdsIt;
+ }
+ }
+ }
+
+ // Since the sets are ordered, the events must already be ordered by
+ // FutureID.
+ for (auto& [futureId, event] : completable) {
+ event->Complete(futureId, EventCompletionType::Ready);
+ }
+ }
+
+ WGPUWaitStatus WaitAny(InstanceID instance,
+ size_t count,
+ WGPUFutureWaitInfo* infos,
+ uint64_t timeoutNS) {
+ assert(instance);
+
+ if (count == 0) {
+ return WGPUWaitStatus_Success;
+ }
+
+ // To handle timeouts, use Asyncify and proxy back into JS.
+ if (timeoutNS > 0) {
+ // Cannot handle timeouts if we are not using Asyncify.
+ if (!emscripten_has_asyncify()) {
+ return WGPUWaitStatus_UnsupportedTimeout;
+ }
+
+ std::vector<FutureID> futures;
+ std::unordered_map<FutureID, WGPUFutureWaitInfo*> futureIdToInfo;
+ for (size_t i = 0; i < count; ++i) {
+ futures.push_back(infos[i].future.id);
+ futureIdToInfo.emplace(infos[i].future.id, &infos[i]);
+ }
+
+ bool hasTimeout = timeoutNS != UINT64_MAX;
+ FutureID completedId = emwgpuWaitAny(futures.data(), count,
+ hasTimeout ? &timeoutNS : nullptr);
+ if (completedId == kNullFutureId) {
+ return WGPUWaitStatus_TimedOut;
+ }
+ futureIdToInfo[completedId]->completed = true;
+
+ std::unique_ptr<TrackedEvent> completed;
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ auto eventIt = mEvents.find(completedId);
+ if (eventIt == mEvents.end()) {
+ return WGPUWaitStatus_Success;
+ }
+
+ completed = std::move(eventIt->second);
+ mEvents.erase(eventIt);
+ if (auto instanceIt = mPerInstanceEvents.find(instance);
+ instanceIt != mPerInstanceEvents.end()) {
+ instanceIt->second.erase(completedId);
+ }
+ }
+
+ if (completed) {
+ completed->Complete(completedId, EventCompletionType::Ready);
+ }
+ return WGPUWaitStatus_Success;
+ }
+
+ std::map<FutureID, std::unique_ptr<TrackedEvent>> completable;
+ bool anyCompleted = false;
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ auto instanceIt = mPerInstanceEvents.find(instance);
+ assert(instanceIt != mPerInstanceEvents.end());
+ auto& instanceFutureIds = instanceIt->second;
+
+ for (size_t i = 0; i < count; ++i) {
+ FutureID futureId = infos[i].future.id;
+ auto eventIt = mEvents.find(futureId);
+ if (eventIt == mEvents.end()) {
+ infos[i].completed = true;
+ continue;
+ }
+
+ auto& event = eventIt->second;
+ assert(event->mInstanceId == instance);
+ infos[i].completed = event->mIsReady;
+ if (event->mIsReady) {
+ anyCompleted = true;
+ completable.emplace(futureId, std::move(event));
+ mEvents.erase(eventIt);
+ instanceFutureIds.erase(futureId);
+ }
+ }
+ }
+
+ // We used an ordered map to collect the events, so they must be ordered.
+ for (auto& [futureId, event] : completable) {
+ event->Complete(futureId, EventCompletionType::Ready);
+ }
+ return anyCompleted ? WGPUWaitStatus_Success : WGPUWaitStatus_TimedOut;
+ }
+
+ std::pair<FutureID, bool> TrackEvent(std::unique_ptr<TrackedEvent> event) {
+ FutureID futureId = mNextFutureId++;
+ InstanceID instance = event->mInstanceId;
+ std::unique_lock<std::mutex> lock(mMutex);
+ switch (event->mMode) {
+ case WGPUCallbackMode_WaitAnyOnly:
+ case WGPUCallbackMode_AllowProcessEvents: {
+ auto it = mPerInstanceEvents.find(instance);
+ if (it == mPerInstanceEvents.end()) {
+ // The instance has already been unregistered so just complete this
+ // event as shutdown now.
+ event->Complete(futureId, EventCompletionType::Shutdown);
+ return {futureId, false};
+ }
+ it->second.insert(futureId);
+ mEvents.try_emplace(futureId, std::move(event));
+ break;
+ }
+ case WGPUCallbackMode_AllowSpontaneous: {
+ mEvents.try_emplace(futureId, std::move(event));
+ break;
+ }
+ default: {
+ // Invalid callback mode, so we just return kNullFutureId.
+ return {kNullFutureId, false};
+ }
+ }
+ return {futureId, true};
+ }
+
+ template <typename Event, typename... ReadyArgs>
+ void SetFutureReady(FutureID futureId, ReadyArgs&&... readyArgs) {
+ std::unique_ptr<TrackedEvent> spontaneousEvent;
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ auto eventIt = mEvents.find(futureId);
+ if (eventIt == mEvents.end()) {
+ return;
+ }
+
+ auto& event = eventIt->second;
+ assert(event->GetType() == Event::kType);
+ static_cast<Event*>(event.get())
+ ->ReadyHook(std::forward<ReadyArgs>(readyArgs)...);
+ event->mIsReady = true;
+
+ // If the event can be spontaneously completed, prepare to do so now.
+ if (event->mMode == WGPUCallbackMode_AllowSpontaneous) {
+ spontaneousEvent = std::move(event);
+ mEvents.erase(futureId);
+ }
+ }
+
+ if (spontaneousEvent) {
+ spontaneousEvent->Complete(futureId, EventCompletionType::Ready);
+ }
+ }
+
+ private:
+ std::mutex mMutex;
+ std::atomic<FutureID> mNextFutureId = 1;
+
+ // The EventManager separates events based on the WGPUInstance that the event
+ // stems from.
+ std::unordered_map<InstanceID, std::set<FutureID>> mPerInstanceEvents;
+ std::unordered_map<FutureID, std::unique_ptr<TrackedEvent>> mEvents;
+};
+
+static EventManager& GetEventManager() {
+ static EventManager kEventManager;
+ return kEventManager;
+}
+
+// ----------------------------------------------------------------------------
+// Future events.
+// ----------------------------------------------------------------------------
+
+class RequestAdapterEvent final : public TrackedEvent {
+ public:
+ static constexpr EventType kType = EventType::RequestAdapter;
+
+ RequestAdapterEvent(InstanceID instance,
+ const WGPURequestAdapterCallbackInfo2& callbackInfo)
+ : TrackedEvent(instance, callbackInfo.mode),
+ mCallback(callbackInfo.callback),
+ mUserdata1(callbackInfo.userdata1),
+ mUserdata2(callbackInfo.userdata2) {}
+
+ EventType GetType() override { return kType; }
+
+ void ReadyHook(WGPURequestAdapterStatus status,
+ WGPUAdapter adapter,
+ const char* message) {
+ mStatus = status;
+ mAdapter = adapter;
+ mMessage = message;
+ }
+
+ void Complete(FutureID futureId, EventCompletionType type) override {
+ if (type == EventCompletionType::Shutdown) {
+ mStatus = WGPURequestAdapterStatus_InstanceDropped;
+ mMessage = "A valid external Instance reference no longer exists.";
+ }
+ if (mCallback) {
+ mCallback(
+ mStatus,
+ mStatus == WGPURequestAdapterStatus_Success ? mAdapter : nullptr,
+ mMessage ? mMessage->c_str() : nullptr, mUserdata1, mUserdata2);
+ }
+ }
+
+ private:
+ WGPURequestAdapterCallback2 mCallback = nullptr;
+ void* mUserdata1 = nullptr;
+ void* mUserdata2 = nullptr;
+
+ WGPURequestAdapterStatus mStatus;
+ WGPUAdapter mAdapter = nullptr;
+ std::optional<std::string> mMessage = std::nullopt;
+};
+
+// ----------------------------------------------------------------------------
+// WGPU struct implementations.
+// ----------------------------------------------------------------------------
+
// Default struct implementations.
#define DEFINE_WGPU_DEFAULT_STRUCT(Name) \
struct WGPU##Name##Impl : public RefCounted {};
WGPU_PASSTHROUGH_OBJECTS(DEFINE_WGPU_DEFAULT_STRUCT)
-// Specialized struct implementations
-struct WGPUInstanceImpl : public RefCounted {};
+// Instance is specially implemented in order to handle Futures implementation.
+struct WGPUInstanceImpl : public RefCounted {
+ public:
+ WGPUInstanceImpl() {
+ mId = GetNextInstanceId();
+ GetEventManager().RegisterInstance(mId);
+ }
+ ~WGPUInstanceImpl() override { GetEventManager().UnregisterInstance(mId); }
+ InstanceID GetId() const { return mId; }
+
+ void ProcessEvents() { GetEventManager().ProcessEvents(mId); }
+
+ WGPUWaitStatus WaitAny(size_t count,
+ WGPUFutureWaitInfo* infos,
+ uint64_t timeoutNS) {
+ return GetEventManager().WaitAny(mId, count, infos, timeoutNS);
+ }
+
+ private:
+ static InstanceID GetNextInstanceId() {
+ static std::atomic<InstanceID> kNextInstanceId = 1;
+ return kNextInstanceId++;
+ }
+
+ InstanceID mId;
+};
// Device is specially implemented in order to handle refcounting the Queue.
struct WGPUDeviceImpl : public RefCounted {
@@ -152,6 +527,15 @@
return new WGPUDeviceImpl(queue);
}
+// Future event callbacks.
+void emwgpuOnRequestAdapterCompleted(FutureID futureId,
+ WGPURequestAdapterStatus status,
+ WGPUAdapter adapter,
+ const char* message) {
+ GetEventManager().SetFutureReady<RequestAdapterEvent>(futureId, status,
+ adapter, message);
+}
+
} // extern "C"
// ----------------------------------------------------------------------------
@@ -241,6 +625,48 @@
// Methods of Instance
// ----------------------------------------------------------------------------
+void wgpuInstanceProcessEvents(WGPUInstance instance) {
+ instance->ProcessEvents();
+}
+
+void wgpuInstanceRequestAdapter(WGPUInstance instance,
+ WGPURequestAdapterOptions const* options,
+ WGPURequestAdapterCallback callback,
+ void* userdata) {
+ WGPURequestAdapterCallbackInfo2 callbackInfo = {};
+ callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
+ callbackInfo.callback = [](WGPURequestAdapterStatus status,
+ WGPUAdapter adapter, char const* message,
+ void* callback, void* userdata) {
+ auto cb = reinterpret_cast<WGPURequestAdapterCallback>(callback);
+ cb(status, adapter, message, userdata);
+ };
+ callbackInfo.userdata1 = reinterpret_cast<void*>(callback);
+ callbackInfo.userdata2 = userdata;
+ wgpuInstanceRequestAdapter2(instance, options, callbackInfo);
+}
+
+WGPUFuture wgpuInstanceRequestAdapter2(
+ WGPUInstance instance,
+ WGPURequestAdapterOptions const* options,
+ WGPURequestAdapterCallbackInfo2 callbackInfo) {
+ auto [futureId, tracked] = GetEventManager().TrackEvent(
+ std::make_unique<RequestAdapterEvent>(instance->GetId(), callbackInfo));
+ if (!tracked) {
+ return WGPUFuture{kNullFutureId};
+ }
+
+ emwgpuInstanceRequestAdapter(instance, futureId, options);
+ return WGPUFuture{futureId};
+}
+
+WGPUWaitStatus wgpuInstanceWaitAny(WGPUInstance instance,
+ size_t futureCount,
+ WGPUFutureWaitInfo* futures,
+ uint64_t timeoutNS) {
+ return instance->WaitAny(futureCount, futures, timeoutNS);
+}
+
// ----------------------------------------------------------------------------
// Methods of PipelineLayout
// ----------------------------------------------------------------------------