[dawn][emscripten] Adds Future implementation for device creation.

- Adds EventSource to allow for passing through event handling.
- Implements RequestDevice and DeviceLost events together since
  they are very intertwined.
- Adds handling for UncapturedError.

Bug: 358445329
Change-Id: Iea02cccafdb5dd1609498d6e131d7cfbe361045f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/203421
Reviewed-by: Shrek Shao <shrekshao@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/samples/SampleUtils.cpp b/src/dawn/samples/SampleUtils.cpp
index 400fba1..18cee4b 100644
--- a/src/dawn/samples/SampleUtils.cpp
+++ b/src/dawn/samples/SampleUtils.cpp
@@ -146,8 +146,6 @@
             break;
     }
 
-        // TODO(crbug.com/42241221): Once the headers are more stable and implemented in Emscripten,
-        // we could probably unify branched code below a bit more.
 #ifndef __EMSCRIPTEN__
     dawnProcSetProcs(&dawn::native::GetProcs());
 
@@ -171,6 +169,10 @@
     instanceDescriptor.nextInChain = &toggles;
     instanceDescriptor.features.timedWaitAnyEnable = true;
     sample->instance = wgpu::CreateInstance(&instanceDescriptor);
+#else
+    // Create the instance
+    sample->instance = wgpu::CreateInstance(nullptr);
+#endif  // __EMSCRIPTEN__
 
     // Synchronously create the adapter
     sample->instance.WaitAny(
@@ -254,6 +256,7 @@
         return 1;
     }
 
+#ifndef __EMSCRIPTEN__
     if (!sample->Setup()) {
         dawn::ErrorLog() << "Failed to perform sample setup";
         return 1;
@@ -268,46 +271,11 @@
         }
     }
 #else
-    // Create the instance
-    sample->instance = wgpu::CreateInstance(nullptr);
-
-    // 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;
+    if (sample->Setup()) {
+        emscripten_set_main_loop([]() { sample->FrameImpl(); }, 0, false);
+    } else {
+        dawn::ErrorLog() << "Failed to setup sample";
     }
-
-    // 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.
-    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);
 #endif  // __EMSCRIPTEN__
 
     return 0;
diff --git a/third_party/emdawnwebgpu/library_webgpu.js b/third_party/emdawnwebgpu/library_webgpu.js
index 0d05bbb..fb72053 100644
--- a/third_party/emdawnwebgpu/library_webgpu.js
+++ b/third_party/emdawnwebgpu/library_webgpu.js
@@ -480,13 +480,14 @@
   // Extra helper that allow for directly inserting Devices (and their
   // corresponding Queue) that is called from the HTML5 library since there
   // isn't access to the C++ in webgpu.cpp there.
-  emwgpuTableInsertDevice__deps: ['emwgpuCreateDevice', 'emwgpuCreateQueue'],
+  emwgpuTableInsertDevice__deps: ['emwgpuCreateDevice', 'emwgpuCreateQueue', 'wgpuCreateInstance'],
   emwgpuTableInsertDevice: (device) => {
+    var instancePtr = _wgpuCreateInstance();
     var queuePtr = _emwgpuCreateQueue();
     WebGPU._tableInsert(queuePtr, device.queue);
-    var devicePtr = _emwgpuCreateDevice(queuePtr);
+    var devicePtr = _emwgpuCreateDevice(instancePtr, queuePtr);
     WebGPU._tableInsert(devicePtr, device);
-    return devicePtr;
+    return { instancePtr, devicePtr };
   },
 
 #if ASYNCIFY
@@ -612,8 +613,14 @@
     return adapter.features.has(WebGPU.FeatureName[featureEnumValue]);
   },
 
-  wgpuAdapterRequestDevice__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateDevice', 'emwgpuCreateQueue'],
-  wgpuAdapterRequestDevice: (adapterPtr, descriptor, callback, userdata) => {
+  emwgpuAdapterRequestDevice__i53abi: false,
+  emwgpuAdapterRequestDevice__deps: ['$callUserCallback', '$stringToUTF8OnStack', 'emwgpuCreateQueue', 'emwgpuOnDeviceLostCompleted', 'emwgpuOnRequestDeviceCompleted', 'emwgpuOnUncapturedError'],
+  emwgpuAdapterRequestDevice: (
+    adapterPtr,
+    futureIdL, futureIdH,
+    deviceLostFutureIdL, deviceLostFutureIdH,
+    devicePtr, queuePtr, descriptor
+  ) => {
     var adapter = WebGPU._tableGet(adapterPtr);
 
     var desc = {};
@@ -689,39 +696,57 @@
         desc["defaultQueue"] = defaultQueueDesc;
       }
 
-      var deviceLostCallbackPtr = {{{ makeGetValue('descriptor', C_STRUCTS.WGPUDeviceDescriptor.deviceLostCallbackInfo + C_STRUCTS.WGPUDeviceLostCallbackInfo.callback, '*') }}};
-      var deviceLostUserdataPtr = {{{ makeGetValue('descriptor', C_STRUCTS.WGPUDeviceDescriptor.deviceLostCallbackInfo + C_STRUCTS.WGPUDeviceLostCallbackInfo.userdata, '*') }}};
-
       var labelPtr = {{{ makeGetValue('descriptor', C_STRUCTS.WGPUDeviceDescriptor.label, '*') }}};
       if (labelPtr) desc["label"] = UTF8ToString(labelPtr);
     }
 
     {{{ runtimeKeepalivePush() }}}
-    adapter.requestDevice(desc).then((device) => {
+    var hasDeviceLostFutureId = !!deviceLostFutureIdH || !!deviceLostFutureIdL;
+    WebGPU._futureInsert(futureIdL, futureIdH, adapter.requestDevice(desc).then((device) => {
       {{{ runtimeKeepalivePop() }}}
-      callUserCallback(() => {
-        var queuePtr = _emwgpuCreateQueue();
-        WebGPU._tableInsert(queuePtr, device.queue);
+      WebGPU._tableInsert(queuePtr, device.queue);
+      WebGPU._tableInsert(devicePtr, device);
 
-        var devicePtr = _emwgpuCreateDevice(queuePtr);
-        WebGPU._tableInsert(devicePtr, device);
-        if (deviceLostCallbackPtr) {
-          device.lost.then((info) => {
-            callUserCallback(() => WebGPU.errorCallback(deviceLostCallbackPtr,
-              WebGPU.Int_DeviceLostReason[info.reason], info.message, deviceLostUserdataPtr));
+      // Set up device lost promise resolution.
+      if (hasDeviceLostFutureId) {
+        WebGPU._futureInsert(deviceLostFutureIdL, deviceLostFutureIdH, device.lost.then((info) => {
+          // Unset the uncaptured error handler.
+          device.onuncapturederror = (ev) => {};
+          withStackSave(() => {
+            var messagePtr = stringToUTF8OnStack(info.message);
+            _emwgpuOnDeviceLostCompleted(deviceLostFutureIdL, deviceLostFutureIdH, WebGPU.Int_DeviceLostReason[info.reason], messagePtr);
           });
-        }
-        {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestDeviceStatus.Success }}}, devicePtr, 0, userdata);
-      });
-    }, function(ex) {
+        }));
+      }
+
+      // Set up uncaptured error handlers.
+#if ASSERTIONS
+      assert(typeof GPUValidationError != 'undefined');
+      assert(typeof GPUOutOfMemoryError != 'undefined');
+      assert(typeof GPUInternalError != 'undefined');
+#endif
+      device.onuncapturederror = (ev) => {
+          var type = {{{ gpu.ErrorType.Unknown }}};;
+          if (ev.error instanceof GPUValidationError) type = {{{ gpu.ErrorType.Validation }}};
+          else if (ev.error instanceof GPUOutOfMemoryError) type = {{{ gpu.ErrorType.OutOfMemory }}};
+          else if (ev.error instanceof GPUInternalError) type = {{{ gpu.ErrorType.Internal }}};
+          withStackSave(() => {
+            var messagePtr = stringToUTF8OnStack(ev.error.message);
+            _emwgpuOnUncapturedError(devicePtr, type, messagePtr);
+          });
+      };
+
+      _emwgpuOnRequestDeviceCompleted(futureIdL, futureIdH, {{{ gpu.RequestDeviceStatus.Success }}}, devicePtr, 0);
+    }, (ex) => {
       {{{ runtimeKeepalivePop() }}}
-      callUserCallback(() => {
-        var sp = stackSave();
+      withStackSave(() => {
         var messagePtr = stringToUTF8OnStack(ex.message);
-        {{{ makeDynCall('vippp', 'callback') }}}({{{ gpu.RequestDeviceStatus.Error }}}, 0, messagePtr, userdata);
-        stackRestore(sp);
+        _emwgpuOnRequestDeviceCompleted(futureIdL, futureIdH, {{{ gpu.RequestDeviceStatus.Error }}}, devicePtr, messagePtr);
+        if (deviceLostFutureId) {
+          _emwgpuOnDeviceLostCompleted(deviceLostFutureIdL, deviceLostFutureIdH, {{{ gpu.DeviceLostReason.FailedCreation }}}, messagePtr);
+        }
       });
-    });
+    }));
   },
 
   // --------------------------------------------------------------------------
@@ -1814,6 +1839,7 @@
     device.label = UTF8ToString(labelPtr);
   },
 
+  // TODO(42241415) Remove this after verifying that it's not used and/or updating users.
   wgpuDeviceSetUncapturedErrorCallback__deps: ['$callUserCallback'],
   wgpuDeviceSetUncapturedErrorCallback: (devicePtr, callback, userdata) => {
     var device = WebGPU._tableGet(devicePtr);
@@ -1906,7 +1932,7 @@
     WebGPU._futureInsert(futureIdL, futureIdH, navigator["gpu"]["requestAdapter"](opts).then((adapter) => {
       {{{ runtimeKeepalivePop() }}}
       if (adapter) {
-        var adapterPtr = _emwgpuCreateAdapter();
+        var adapterPtr = _emwgpuCreateAdapter(instancePtr);
         WebGPU._tableInsert(adapterPtr, adapter);
         _emwgpuOnRequestAdapterCompleted(futureIdL, futureIdH, {{{ gpu.RequestAdapterStatus.Success }}}, adapterPtr, 0);
       } else {
diff --git a/third_party/emdawnwebgpu/webgpu.cpp b/third_party/emdawnwebgpu/webgpu.cpp
index d529881..f1c12c2 100644
--- a/third_party/emdawnwebgpu/webgpu.cpp
+++ b/third_party/emdawnwebgpu/webgpu.cpp
@@ -19,6 +19,7 @@
 #include <mutex>
 #include <optional>
 #include <set>
+#include <tuple>
 #include <unordered_map>
 #include <utility>
 #include <vector>
@@ -40,6 +41,12 @@
                        uint64_t const* timeoutNSPtr);
 
 // Future/async operation that need to be forwarded to JS.
+void emwgpuAdapterRequestDevice(WGPUAdapter adapter,
+                                FutureID futureId,
+                                FutureID deviceLostFutureId,
+                                WGPUDevice device,
+                                WGPUQueue queue,
+                                const WGPUDeviceDescriptor* descriptor);
 void emwgpuInstanceRequestAdapter(WGPUInstance instance,
                                   FutureID futureId,
                                   const WGPURequestAdapterOptions* options);
@@ -245,7 +252,6 @@
 // X Macro to help generate boilerplate code for all passthrough object types.
 // Passthrough objects refer to objects that are implemented via JS objects.
 #define WGPU_PASSTHROUGH_OBJECTS(X) \
-  X(Adapter)             \
   X(BindGroup)           \
   X(BindGroupLayout)     \
   X(Buffer)              \
@@ -277,7 +283,9 @@
   Shutdown,
 };
 enum class EventType {
+  DeviceLost,
   RequestAdapter,
+  RequestDevice,
 };
 
 class EventManager;
@@ -302,6 +310,27 @@
   bool mIsReady = false;
 };
 
+// Compositable class for objects that provide entry point(s) that produce
+// Events, i.e. returns a Future.
+//
+// Note that while it would be nice to make it so that C++ entry points
+// implemented in here, and called from JS could use this abstraction in
+// signatures, pointers passed between JS and C++ in WASM do not cast properly
+// and results in undefined behavior. As an example, given:
+//   (1) WGPUAdapter emwgpuCreateAdapter(const EventSource* source);
+//   (2) WGPUAdapter emwgpuCreateAdapter(WGPUInstance instance);
+// I tried to use (1), but when calling from JS, the pointer is not correctly
+// adjusted so the value we end up getting when calling GetInstanceId() is some
+// garbage.
+class EventSource {
+ public:
+  explicit EventSource(InstanceID instanceId) : mInstanceId(instanceId) {}
+  InstanceID GetInstanceId() const { return mInstanceId; }
+
+ private:
+  const InstanceID mInstanceId = 0;
+};
+
 // Thread-safe EventManager class that tracks all events.
 //
 // Note that there is a single global EventManager that should be accessed via
@@ -542,10 +571,102 @@
   struct WGPU##Name##Impl final : public RefCounted {};
 WGPU_PASSTHROUGH_OBJECTS(DEFINE_WGPU_DEFAULT_STRUCT)
 
+// Instance is specially implemented in order to handle Futures implementation.
+struct WGPUInstanceImpl final : public RefCounted, public EventSource {
+ public:
+  WGPUInstanceImpl();
+  ~WGPUInstanceImpl();
+
+  void ProcessEvents();
+  WGPUWaitStatus WaitAny(size_t count,
+                         WGPUFutureWaitInfo* infos,
+                         uint64_t timeoutNS);
+
+ private:
+  static InstanceID GetNextInstanceId();
+};
+
+struct WGPUAdapterImpl final : public RefCounted, public EventSource {
+ public:
+  WGPUAdapterImpl(const EventSource* source);
+};
+
+// Device is specially implemented in order to handle refcounting the Queue.
+struct WGPUDeviceImpl final : public RefCountedWithExternalCount,
+                              public EventSource {
+ public:
+  // Reservation constructor used when calling RequestDevice.
+  WGPUDeviceImpl(const EventSource* source,
+                 const WGPUDeviceDescriptor* descriptor,
+                 WGPUQueue queue);
+  // Injection constructor used when we already have a backing Device.
+  WGPUDeviceImpl(const EventSource* source, WGPUQueue queue);
+
+  WGPUQueue GetQueue() const;
+
+  void OnDeviceLost(WGPUDeviceLostReason reason, const char* message);
+  void OnUncapturedError(WGPUErrorType type, char const* message);
+
+ private:
+  void WillDropLastExternalRef() override;
+
+  Ref<WGPUQueue> mQueue;
+  WGPUUncapturedErrorCallbackInfo2 mUncapturedErrorCallbackInfo =
+      WGPU_UNCAPTURED_ERROR_CALLBACK_INFO_2_INIT;
+  FutureID mDeviceLostFutureId = kNullFutureId;
+};
+
 // ----------------------------------------------------------------------------
 // Future events.
 // ----------------------------------------------------------------------------
 
+class DeviceLostEvent final : public TrackedEvent {
+ public:
+  static constexpr EventType kType = EventType::DeviceLost;
+
+  DeviceLostEvent(InstanceID instance,
+                  WGPUDevice device,
+                  const WGPUDeviceLostCallbackInfo2& callbackInfo)
+      : TrackedEvent(instance, callbackInfo.mode),
+        mCallback(callbackInfo.callback),
+        mUserdata1(callbackInfo.userdata1),
+        mUserdata2(callbackInfo.userdata2),
+        mDevice(device) {
+    assert(mDevice);
+  }
+
+  EventType GetType() override { return kType; }
+
+  void ReadyHook(WGPUDeviceLostReason reason, const char* message) {
+    mReason = reason;
+    mMessage = message;
+  }
+
+  void Complete(FutureID futureId, EventCompletionType type) override {
+    if (type == EventCompletionType::Shutdown) {
+      mReason = WGPUDeviceLostReason_InstanceDropped;
+      mMessage = "A valid external Instance reference no longer exists.";
+    }
+    if (mCallback) {
+      WGPUDevice device = mReason != WGPUDeviceLostReason_FailedCreation
+                              ? mDevice.Get()
+                              : nullptr;
+      mCallback(&device, mReason, mMessage ? mMessage->c_str() : nullptr,
+                mUserdata1, mUserdata2);
+    }
+  }
+
+ private:
+  WGPUDeviceLostCallback2 mCallback = nullptr;
+  void* mUserdata1 = nullptr;
+  void* mUserdata2 = nullptr;
+
+  Ref<WGPUDevice> mDevice;
+
+  WGPUDeviceLostReason mReason;
+  std::optional<std::string> mMessage;
+};
+
 class RequestAdapterEvent final : public TrackedEvent {
  public:
   static constexpr EventType kType = EventType::RequestAdapter;
@@ -591,50 +712,136 @@
   std::optional<std::string> mMessage = std::nullopt;
 };
 
+class RequestDeviceEvent final : public TrackedEvent {
+ public:
+  static constexpr EventType kType = EventType::RequestDevice;
+
+  RequestDeviceEvent(InstanceID instance,
+                     const WGPURequestDeviceCallbackInfo2& callbackInfo)
+      : TrackedEvent(instance, callbackInfo.mode),
+        mCallback(callbackInfo.callback),
+        mUserdata1(callbackInfo.userdata1),
+        mUserdata2(callbackInfo.userdata2) {}
+
+  EventType GetType() override { return kType; }
+
+  void ReadyHook(WGPURequestDeviceStatus status,
+                 WGPUDevice device,
+                 const char* message) {
+    mStatus = status;
+    mDevice.Acquire(device);
+    mMessage = message;
+  }
+
+  void Complete(FutureID futureId, EventCompletionType type) override {
+    if (type == EventCompletionType::Shutdown) {
+      mStatus = WGPURequestDeviceStatus_InstanceDropped;
+      mMessage = "A valid external Instance reference no longer exists.";
+    }
+    if (mCallback) {
+      mCallback(mStatus,
+                mStatus == WGPURequestDeviceStatus_Success
+                    ? ReturnToAPI(std::move(mDevice))
+                    : nullptr,
+                mMessage ? mMessage->c_str() : nullptr, mUserdata1, mUserdata2);
+    }
+  }
+
+ private:
+  WGPURequestDeviceCallback2 mCallback = nullptr;
+  void* mUserdata1 = nullptr;
+  void* mUserdata2 = nullptr;
+
+  WGPURequestDeviceStatus mStatus;
+  Ref<WGPUDevice> mDevice;
+  std::optional<std::string> mMessage = std::nullopt;
+};
+
 // ----------------------------------------------------------------------------
 // WGPU struct implementations.
 // ----------------------------------------------------------------------------
 
-// Instance is specially implemented in order to handle Futures implementation.
-struct WGPUInstanceImpl : public RefCounted {
- public:
-  WGPUInstanceImpl() {
-    mId = GetNextInstanceId();
-    GetEventManager().RegisterInstance(mId);
+// ----------------------------------------------------------------------------
+// WGPUAdapterImpl implementations.
+// ----------------------------------------------------------------------------
+
+WGPUAdapterImpl::WGPUAdapterImpl(const EventSource* source)
+    : EventSource(source->GetInstanceId()) {}
+
+// ----------------------------------------------------------------------------
+// WGPUInstanceImpl implementations.
+// ----------------------------------------------------------------------------
+
+WGPUInstanceImpl::WGPUInstanceImpl() : EventSource(GetNextInstanceId()) {
+  GetEventManager().RegisterInstance(GetInstanceId());
+}
+WGPUInstanceImpl::~WGPUInstanceImpl() {
+  GetEventManager().UnregisterInstance(GetInstanceId());
+}
+
+void WGPUInstanceImpl::ProcessEvents() {
+  GetEventManager().ProcessEvents(GetInstanceId());
+}
+
+WGPUWaitStatus WGPUInstanceImpl::WaitAny(size_t count,
+                                         WGPUFutureWaitInfo* infos,
+                                         uint64_t timeoutNS) {
+  return GetEventManager().WaitAny(GetInstanceId(), count, infos, timeoutNS);
+}
+
+InstanceID WGPUInstanceImpl::GetNextInstanceId() {
+  static std::atomic<InstanceID> kNextInstanceId = 1;
+  return kNextInstanceId++;
+}
+
+// ----------------------------------------------------------------------------
+// WGPUDeviceImpl implementations.
+// ----------------------------------------------------------------------------
+
+WGPUDeviceImpl::WGPUDeviceImpl(const EventSource* source,
+                               const WGPUDeviceDescriptor* descriptor,
+                               WGPUQueue queue)
+    : EventSource(source->GetInstanceId()),
+      mUncapturedErrorCallbackInfo(descriptor->uncapturedErrorCallbackInfo2) {
+  // Create the DeviceLostEvent now.
+  std::tie(mDeviceLostFutureId, std::ignore) =
+      GetEventManager().TrackEvent(std::make_unique<DeviceLostEvent>(
+          source->GetInstanceId(), this, descriptor->deviceLostCallbackInfo2));
+  mQueue.Acquire(queue);
+}
+
+WGPUDeviceImpl::WGPUDeviceImpl(const EventSource* source, WGPUQueue queue)
+    : EventSource(source->GetInstanceId()) {
+  mQueue.Acquire(queue);
+}
+
+WGPUQueue WGPUDeviceImpl::GetQueue() const {
+  auto queue = mQueue;
+  return ReturnToAPI(std::move(queue));
+}
+
+void WGPUDeviceImpl::OnDeviceLost(WGPUDeviceLostReason reason,
+                                  const char* message) {
+  if (mDeviceLostFutureId != kNullFutureId) {
+    GetEventManager().SetFutureReady<DeviceLostEvent>(mDeviceLostFutureId,
+                                                      reason, message);
   }
-  ~WGPUInstanceImpl() { GetEventManager().UnregisterInstance(mId); }
-  InstanceID GetId() const { return mId; }
+  mDeviceLostFutureId = kNullFutureId;
+}
 
-  void ProcessEvents() { GetEventManager().ProcessEvents(mId); }
-
-  WGPUWaitStatus WaitAny(size_t count,
-                         WGPUFutureWaitInfo* infos,
-                         uint64_t timeoutNS) {
-    return GetEventManager().WaitAny(mId, count, infos, timeoutNS);
+void WGPUDeviceImpl::OnUncapturedError(WGPUErrorType type,
+                                       char const* message) {
+  if (mUncapturedErrorCallbackInfo.callback) {
+    WGPUDeviceImpl* device = this;
+    mUncapturedErrorCallbackInfo.callback(
+        &device, type, message, mUncapturedErrorCallbackInfo.userdata1,
+        mUncapturedErrorCallbackInfo.userdata2);
   }
+}
 
- 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 {
- public:
-  WGPUDeviceImpl(WGPUQueue queue) { mQueue.Acquire(queue); }
-
-  WGPUQueue GetQueue() {
-    auto queue = mQueue;
-    return ReturnToAPI(std::move(queue));
-  }
-
- private:
-  Ref<WGPUQueue> mQueue;
-};
+void WGPUDeviceImpl::WillDropLastExternalRef() {
+  OnDeviceLost(WGPUDeviceLostReason_Destroyed, "Device was destroyed.");
+}
 
 // ----------------------------------------------------------------------------
 // Definitions for C++ emwgpu functions (callable from library_webgpu.js)
@@ -649,11 +856,20 @@
   }
 WGPU_PASSTHROUGH_OBJECTS(DEFINE_EMWGPU_DEFAULT_CREATE)
 
-WGPUDevice emwgpuCreateDevice(WGPUQueue queue) {
-  return new WGPUDeviceImpl(queue);
+WGPUAdapter emwgpuCreateAdapter(WGPUInstance instance) {
+  return new WGPUAdapterImpl(instance);
+}
+
+WGPUDevice emwgpuCreateDevice(WGPUInstance instance, WGPUQueue queue) {
+  return new WGPUDeviceImpl(instance, queue);
 }
 
 // Future event callbacks.
+void emwgpuOnDeviceLostCompleted(FutureID futureId,
+                                 WGPUDeviceLostReason reason,
+                                 const char* message) {
+  GetEventManager().SetFutureReady<DeviceLostEvent>(futureId, reason, message);
+}
 void emwgpuOnRequestAdapterCompleted(FutureID futureId,
                                      WGPURequestAdapterStatus status,
                                      WGPUAdapter adapter,
@@ -661,6 +877,33 @@
   GetEventManager().SetFutureReady<RequestAdapterEvent>(futureId, status,
                                                         adapter, message);
 }
+void emwgpuOnRequestDeviceCompleted(FutureID futureId,
+                                    WGPURequestDeviceStatus status,
+                                    WGPUDevice device,
+                                    const char* message) {
+  // This handler should always have a device since we pre-allocate it before
+  // calling out to JS.
+  assert(device);
+  if (status == WGPURequestDeviceStatus_Success) {
+    GetEventManager().SetFutureReady<RequestDeviceEvent>(futureId, status,
+                                                         device, message);
+  } else {
+    // If the request failed, we need to resolve the DeviceLostEvent.
+    device->OnDeviceLost(WGPUDeviceLostReason_FailedCreation,
+                         "Device failed at creation.");
+    GetEventManager().SetFutureReady<RequestDeviceEvent>(futureId, status,
+                                                         nullptr, message);
+  }
+}
+
+// Uncaptured error handler is similar to the Future event callbacks, but it
+// doesn't go through the EventManager and just calls the callback on the Device
+// immediately.
+void emwgpuOnUncapturedError(WGPUDevice device,
+                             WGPUErrorType type,
+                             char const* message) {
+  device->OnUncapturedError(type, message);
+}
 
 }  // extern "C"
 
@@ -710,6 +953,48 @@
 // Methods of Adapter
 // ----------------------------------------------------------------------------
 
+void wgpuAdapterRequestDevice(WGPUAdapter adapter,
+                              const WGPUDeviceDescriptor* descriptor,
+                              WGPURequestDeviceCallback callback,
+                              void* userdata) {
+  WGPURequestDeviceCallbackInfo2 callbackInfo = {};
+  callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
+  callbackInfo.callback = [](WGPURequestDeviceStatus status, WGPUDevice device,
+                             char const* message, void* callback,
+                             void* userdata) {
+    auto cb = reinterpret_cast<WGPURequestDeviceCallback>(callback);
+    cb(status, device, message, userdata);
+  };
+  callbackInfo.userdata1 = reinterpret_cast<void*>(callback);
+  callbackInfo.userdata2 = userdata;
+  wgpuAdapterRequestDevice2(adapter, descriptor, callbackInfo);
+}
+
+WGPUFuture wgpuAdapterRequestDevice2(
+    WGPUAdapter adapter,
+    const WGPUDeviceDescriptor* descriptor,
+    WGPURequestDeviceCallbackInfo2 callbackInfo) {
+  auto [futureId, tracked] =
+      GetEventManager().TrackEvent(std::make_unique<RequestDeviceEvent>(
+          adapter->GetInstanceId(), callbackInfo));
+  if (!tracked) {
+    return WGPUFuture{kNullFutureId};
+  }
+
+  // For RequestDevice, we always create a Device and Queue up front. The
+  // Device is also immediately associated with the DeviceLostEvent.
+  WGPUQueue queue = new WGPUQueueImpl();
+  WGPUDevice device = new WGPUDeviceImpl(adapter, descriptor, queue);
+
+  auto [deviceLostFutureId, _] = GetEventManager().TrackEvent(
+      std::make_unique<DeviceLostEvent>(adapter->GetInstanceId(), device,
+                                        descriptor->deviceLostCallbackInfo2));
+
+  emwgpuAdapterRequestDevice(adapter, futureId, deviceLostFutureId, device,
+                             queue, descriptor);
+  return WGPUFuture{futureId};
+}
+
 // ----------------------------------------------------------------------------
 // Methods of BindGroup
 // ----------------------------------------------------------------------------
@@ -775,8 +1060,9 @@
     WGPUInstance instance,
     WGPURequestAdapterOptions const* options,
     WGPURequestAdapterCallbackInfo2 callbackInfo) {
-  auto [futureId, tracked] = GetEventManager().TrackEvent(
-      std::make_unique<RequestAdapterEvent>(instance->GetId(), callbackInfo));
+  auto [futureId, tracked] =
+      GetEventManager().TrackEvent(std::make_unique<RequestAdapterEvent>(
+          instance->GetInstanceId(), callbackInfo));
   if (!tracked) {
     return WGPUFuture{kNullFutureId};
   }