blob: e30dc54f4694ceb9c0ac0de4652a6530905132cd [file] [log] [blame]
// Copyright 2024 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
//
// 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 <set>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
using FutureID = uint64_t;
static constexpr FutureID kNullFutureId = 0;
using InstanceID = uint64_t;
static constexpr InstanceID kNullInstanceId = 0;
// ----------------------------------------------------------------------------
// Declarations for JS emwgpu functions (defined in library_webgpu.js)
// ----------------------------------------------------------------------------
extern "C" {
void emwgpuDelete(void* ptr);
void emwgpuSetLabel(void* ptr, const char* data, size_t length);
// 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);
WGPUTextureFormat emwgpuGetPreferredFormat();
// Device functions, i.e. creation functions to create JS backing objects given
// a pre-allocated handle, and destruction implementations.
void emwgpuDeviceCreateBuffer(WGPUDevice device,
const WGPUBufferDescriptor* descriptor,
WGPUBuffer buffer);
void emwgpuDeviceCreateShaderModule(
WGPUDevice device,
const WGPUShaderModuleDescriptor* descriptor,
WGPUShaderModule shader);
void emwgpuDeviceDestroy(WGPUDevice device);
// Buffer mapping operations that has work that needs to be done on the JS side.
void emwgpuBufferDestroy(WGPUBuffer buffer);
const void* emwgpuBufferGetConstMappedRange(WGPUBuffer buffer,
size_t offset,
size_t size);
void* emwgpuBufferGetMappedRange(WGPUBuffer buffer, size_t offset, size_t size);
void emwgpuBufferUnmap(WGPUBuffer buffer);
// 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 emwgpuBufferMapAsync(WGPUBuffer buffer,
FutureID futureID,
WGPUMapMode mode,
size_t offset,
size_t size);
void emwgpuDeviceCreateComputePipelineAsync(
WGPUDevice device,
FutureID futureId,
const WGPUComputePipelineDescriptor* descriptor);
void emwgpuDeviceCreateRenderPipelineAsync(
WGPUDevice device,
FutureID futureId,
const WGPURenderPipelineDescriptor* descriptor);
void emwgpuDevicePopErrorScope(WGPUDevice device, FutureID futureId);
void emwgpuInstanceRequestAdapter(WGPUInstance instance,
FutureID futureId,
const WGPURequestAdapterOptions* options);
void emwgpuQueueOnSubmittedWorkDone(WGPUQueue queue, FutureID futureId);
void emwgpuShaderModuleGetCompilationInfo(WGPUShaderModule shader,
FutureID futureId,
WGPUCompilationInfo* compilationInfo);
} // extern "C"
// ----------------------------------------------------------------------------
// Implementation details that are not exposed upwards in the API.
// ----------------------------------------------------------------------------
class NonCopyable {
protected:
constexpr NonCopyable() = default;
~NonCopyable() = default;
NonCopyable(NonCopyable&&) = default;
NonCopyable& operator=(NonCopyable&&) = default;
private:
NonCopyable(const NonCopyable&) = delete;
void operator=(const NonCopyable&) = delete;
};
class NonMovable : NonCopyable {
protected:
constexpr NonMovable() = default;
~NonMovable() = default;
private:
NonMovable(NonMovable&&) = delete;
void operator=(NonMovable&&) = delete;
};
class RefCounted : NonMovable {
public:
static constexpr bool HasExternalRefCount = false;
void AddRef() {
[[maybe_unused]] uint64_t oldRefCount =
mRefCount.fetch_add(1u, std::memory_order_relaxed);
assert(oldRefCount >= 1);
}
bool Release() {
if (mRefCount.fetch_sub(1u, std::memory_order_release) == 1u) {
std::atomic_thread_fence(std::memory_order_acquire);
emwgpuDelete(this);
return true;
}
return false;
}
private:
std::atomic<uint64_t> mRefCount = 1;
};
class RefCountedWithExternalCount : public RefCounted {
public:
static constexpr bool HasExternalRefCount = true;
virtual ~RefCountedWithExternalCount() = default;
void AddRef() {
AddExternalRef();
RefCounted::AddRef();
}
bool Release() {
if (mExternalRefCount.fetch_sub(1u, std::memory_order_release) == 1u) {
std::atomic_thread_fence(std::memory_order_acquire);
WillDropLastExternalRef();
}
return RefCounted::Release();
}
void AddExternalRef() {
mExternalRefCount.fetch_add(1u, std::memory_order_relaxed);
}
private:
virtual void WillDropLastExternalRef() = 0;
std::atomic<uint64_t> mExternalRefCount = 0;
};
template <typename T>
class Ref {
public:
static_assert(std::is_convertible_v<T, RefCounted*>,
"Cannot make a Ref<T> when T is not a Refcounted type.");
Ref() : mValue(nullptr) {}
~Ref() { Release(mValue); }
// Constructors from nullptr.
// NOLINTNEXTLINE(runtime/explicit)
constexpr Ref(std::nullptr_t) : Ref() {}
// Constructors from T.
// NOLINTNEXTLINE(runtime/explicit)
Ref(T value) : mValue(value) { AddRef(value); }
Ref<T>& operator=(const T& value) {
Set(value);
return *this;
}
// Constructors from a Ref<T>.
Ref(const Ref<T>& other) : mValue(other.mValue) { AddRef(other.mValue); }
Ref<T>& operator=(const Ref<T>& other) {
Set(other.mValue);
return *this;
}
Ref(Ref<T>&& other) { mValue = other.Detach(); }
Ref<T>& operator=(Ref<T>&& other) {
if (&other != this) {
Release(mValue);
mValue = other.Detach();
}
return *this;
}
explicit operator bool() const { return !!mValue; }
// Smart pointer methods.
const T& Get() const { return mValue; }
T& Get() { return mValue; }
const T operator->() const { return mValue; }
T operator->() { return mValue; }
[[nodiscard]] T Detach() {
T value = mValue;
mValue = nullptr;
return value;
}
void Acquire(T value) {
Release(mValue);
mValue = value;
}
private:
static void AddRef(T value) {
if (value != nullptr) {
value->RefCounted::AddRef();
}
}
static void Release(T value) {
if (value != nullptr && value->RefCounted::Release()) {
delete value;
}
}
void Set(T value) {
if (mValue != value) {
// Ensure that the new value is referenced before the old is released to
// prevent any transitive frees that may affect the new value.
AddRef(value);
Release(mValue);
mValue = value;
}
}
T mValue;
};
template <typename T>
auto ReturnToAPI(Ref<T*>&& object) {
if constexpr (T::HasExternalRefCount) {
// For an object which has external ref count, just need to increase the
// external ref count, and keep the total ref count unchanged.
object->AddExternalRef();
}
return object.Detach();
}
// StringView utilities.
WGPUStringView ToOutputStringView(const std::string& s) {
return {s.data(), s.size()};
}
// clang-format off
// X Macro to help generate boilerplate code for all refcounted object types.
#define WGPU_OBJECTS(X) \
X(Adapter) \
X(BindGroup) \
X(BindGroupLayout) \
X(Buffer) \
X(CommandBuffer) \
X(CommandEncoder) \
X(ComputePassEncoder) \
X(ComputePipeline) \
X(Device) \
X(Instance) \
X(PipelineLayout) \
X(QuerySet) \
X(Queue) \
X(RenderBundle) \
X(RenderBundleEncoder) \
X(RenderPassEncoder) \
X(RenderPipeline) \
X(Sampler) \
X(ShaderModule) \
X(Surface) \
X(Texture) \
X(TextureView)
// 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(BindGroup) \
X(BindGroupLayout) \
X(CommandBuffer) \
X(CommandEncoder) \
X(ComputePassEncoder) \
X(ComputePipeline) \
X(PipelineLayout) \
X(QuerySet) \
X(RenderBundle) \
X(RenderBundleEncoder) \
X(RenderPassEncoder) \
X(RenderPipeline) \
X(Sampler) \
X(Surface) \
X(Texture) \
X(TextureView)
// clang-format on
// ----------------------------------------------------------------------------
// Future related structures and helpers.
// ----------------------------------------------------------------------------
enum class EventCompletionType {
Ready,
Shutdown,
};
enum class EventType {
CompilationInfo,
CreateComputePipeline,
CreateRenderPipeline,
DeviceLost,
MapAsync,
PopErrorScope,
RequestAdapter,
RequestDevice,
WorkDone,
};
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;
};
// Compositable class for objects that provide entry point(s) that produce
// Events, i.e. returns a Future.
//
// Note that if passing pointers between JS and C++, to ensure that EventSource
// inheritance is handled properly, EventSource must be the first class
// inherited by subclasses. Otherwise the pointer is not cast properly and
// results in corrupted data. As an example, given:
// (1) WGPUAdapter emwgpuCreateAdapter(const EventSource* source);
// (2) WGPUAdapter emwgpuCreateAdapter(WGPUInstance instance);
// WGPUInstance **must** list EventSource as it's first inherited class for (1)
// to work.
class EventSource {
public:
explicit EventSource(InstanceID instanceId) : mInstanceId(instanceId) {}
explicit EventSource(const EventSource* source)
: mInstanceId(source ? source->GetInstanceId() : kNullInstanceId) {}
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
// 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;
}
// ----------------------------------------------------------------------------
// WGPU struct declarations.
// ----------------------------------------------------------------------------
// Default struct declarations.
#define DEFINE_WGPU_DEFAULT_STRUCT(Name) \
struct WGPU##Name##Impl final : public RefCounted { \
WGPU##Name##Impl(const EventSource* source = nullptr) {} \
};
WGPU_PASSTHROUGH_OBJECTS(DEFINE_WGPU_DEFAULT_STRUCT)
struct WGPUAdapterImpl final : public EventSource, public RefCounted {
public:
WGPUAdapterImpl(const EventSource* source);
};
struct WGPUBufferImpl final : public EventSource,
public RefCountedWithExternalCount {
public:
WGPUBufferImpl(const EventSource* source, bool mappedAtCreation);
void Destroy();
const void* GetConstMappedRange(size_t offset, size_t size);
WGPUBufferMapState GetMapState() const;
void* GetMappedRange(size_t offset, size_t size);
WGPUFuture MapAsync(WGPUMapMode mode,
size_t offset,
size_t size,
WGPUBufferMapCallbackInfo2 callbackInfo);
void Unmap();
private:
friend class MapAsyncEvent;
void WillDropLastExternalRef() override;
bool IsPendingMapRequest(FutureID futureID) const;
void AbortPendingMap(const char* message);
// Encapsulates information about a map request. Note that when
// futureID == kNullFutureId, there are no pending map requests, however, it
// is still possible that we are still "mapped" because of mappedAtCreation
// which is not associated with a particular async map / future.
struct MapRequest {
FutureID futureID = kNullFutureId;
WGPUMapMode mode = WGPUMapMode_None;
};
MapRequest mPendingMapRequest;
WGPUBufferMapState mMapState;
};
struct WGPUQueueImpl final : public EventSource, public RefCounted {
public:
WGPUQueueImpl(const EventSource* source);
};
// Device is specially implemented in order to handle refcounting the Queue.
struct WGPUDeviceImpl final : public EventSource,
public RefCountedWithExternalCount {
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);
void Destroy();
WGPUQueue GetQueue() const;
WGPUFuture GetDeviceLostFuture() 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;
};
// Instance is specially implemented in order to handle Futures implementation.
struct WGPUInstanceImpl final : public EventSource, public RefCounted {
public:
WGPUInstanceImpl();
~WGPUInstanceImpl();
void ProcessEvents();
WGPUWaitStatus WaitAny(size_t count,
WGPUFutureWaitInfo* infos,
uint64_t timeoutNS);
private:
static InstanceID GetNextInstanceId();
};
struct WGPUShaderModuleImpl final : public EventSource, public RefCounted {
public:
WGPUShaderModuleImpl(const EventSource* source);
WGPUFuture GetCompilationInfo(WGPUCompilationInfoCallbackInfo2 callbackInfo);
private:
friend class CompilationInfoEvent;
struct WGPUCompilationInfoDeleter {
void operator()(WGPUCompilationInfo* compilationInfo) {
if (!compilationInfo) {
return;
}
if (compilationInfo->messageCount) {
// Since we allocate all the messages in a single block, we only need to
// free the first pointer.
free(const_cast<char*>(compilationInfo->messages[0].message.data));
}
if (compilationInfo->messages) {
free(const_cast<WGPUCompilationMessage*>(compilationInfo->messages));
}
delete compilationInfo;
}
};
using CompilationInfo =
std::unique_ptr<WGPUCompilationInfo, WGPUCompilationInfoDeleter>;
CompilationInfo mCompilationInfo = nullptr;
};
// ----------------------------------------------------------------------------
// Future events.
// ----------------------------------------------------------------------------
class CompilationInfoEvent final : public TrackedEvent {
public:
static constexpr EventType kType = EventType::CompilationInfo;
CompilationInfoEvent(InstanceID instance,
WGPUShaderModule shader,
const WGPUCompilationInfoCallbackInfo2& callbackInfo)
: TrackedEvent(instance, callbackInfo.mode), mShader(shader) {}
EventType GetType() override { return kType; }
void ReadyHook(WGPUCompilationInfoRequestStatus status,
WGPUCompilationInfo* compilationInfo) {
WGPUShaderModuleImpl::CompilationInfo info(compilationInfo);
mStatus = status;
if (mStatus != WGPUCompilationInfoRequestStatus_Success) {
return;
}
if (!mShader->mCompilationInfo.get()) {
// If there wasn't already a cached version of the info, set it now.
mShader->mCompilationInfo = std::move(info);
}
assert(mShader->mCompilationInfo.get());
}
void Complete(FutureID, EventCompletionType type) override {
if (type == EventCompletionType::Shutdown) {
mStatus = WGPUCompilationInfoRequestStatus_InstanceDropped;
}
if (mCallback) {
mCallback(mStatus,
mStatus == WGPUCompilationInfoRequestStatus_Success
? mShader->mCompilationInfo.get()
: nullptr,
mUserdata1, mUserdata2);
}
}
private:
WGPUCompilationInfoCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
Ref<WGPUShaderModule> mShader;
WGPUCompilationInfoRequestStatus mStatus =
WGPUCompilationInfoRequestStatus_Success;
};
template <typename Pipeline, EventType Type, typename CallbackInfo>
class CreatePipelineEventBase final : public TrackedEvent {
public:
static constexpr EventType kType = Type;
CreatePipelineEventBase(InstanceID instance, const CallbackInfo& callbackInfo)
: TrackedEvent(instance, callbackInfo.mode),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
mUserdata2(callbackInfo.userdata2) {}
EventType GetType() override { return kType; }
void ReadyHook(WGPUCreatePipelineAsyncStatus status,
Pipeline pipeline,
const char* message) {
mStatus = status;
mPipeline.Acquire(pipeline);
if (message) {
mMessage = message;
}
}
void Complete(FutureID, EventCompletionType type) override {
if (type == EventCompletionType::Shutdown) {
mStatus = WGPUCreatePipelineAsyncStatus_InstanceDropped;
mMessage = "A valid external Instance reference no longer exists.";
}
if (mCallback) {
mCallback(mStatus,
mStatus == WGPUCreatePipelineAsyncStatus_Success
? ReturnToAPI(std::move(mPipeline))
: nullptr,
ToOutputStringView(mMessage), mUserdata1, mUserdata2);
}
}
private:
using Callback = decltype(std::declval<CallbackInfo>().callback);
Callback mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
WGPUCreatePipelineAsyncStatus mStatus = WGPUCreatePipelineAsyncStatus_Success;
Ref<Pipeline> mPipeline;
std::string mMessage;
};
using CreateComputePipelineEvent =
CreatePipelineEventBase<WGPUComputePipeline,
EventType::CreateComputePipeline,
WGPUCreateComputePipelineAsyncCallbackInfo2>;
using CreateRenderPipelineEvent =
CreatePipelineEventBase<WGPURenderPipeline,
EventType::CreateRenderPipeline,
WGPUCreateRenderPipelineAsyncCallbackInfo2>;
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;
if (message) {
mMessage = message;
}
}
void Complete(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, ToOutputStringView(mMessage), mUserdata1,
mUserdata2);
}
}
private:
WGPUDeviceLostCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
Ref<WGPUDevice> mDevice;
WGPUDeviceLostReason mReason;
std::string mMessage;
};
class PopErrorScopeEvent final : public TrackedEvent {
public:
static constexpr EventType kType = EventType::PopErrorScope;
PopErrorScopeEvent(InstanceID instance,
const WGPUPopErrorScopeCallbackInfo2& callbackInfo)
: TrackedEvent(instance, callbackInfo.mode),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
mUserdata2(callbackInfo.userdata2) {}
EventType GetType() override { return kType; }
void ReadyHook(WGPUPopErrorScopeStatus status,
WGPUErrorType errorType,
const char* message) {
mStatus = status;
mErrorType = errorType;
if (message) {
mMessage = message;
}
}
void Complete(FutureID, EventCompletionType type) override {
if (type == EventCompletionType::Shutdown) {
mStatus = WGPUPopErrorScopeStatus_InstanceDropped;
mErrorType = WGPUErrorType_NoError;
mMessage = "A valid external Instance reference no longer exists.";
}
if (mCallback) {
mCallback(mStatus, mErrorType, ToOutputStringView(mMessage), mUserdata1,
mUserdata2);
}
}
private:
WGPUPopErrorScopeCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
WGPUPopErrorScopeStatus mStatus = WGPUPopErrorScopeStatus_Success;
WGPUErrorType mErrorType = WGPUErrorType_Unknown;
std::string mMessage;
};
class MapAsyncEvent final : public TrackedEvent {
public:
static constexpr EventType kType = EventType::MapAsync;
MapAsyncEvent(InstanceID instance,
WGPUBuffer buffer,
const WGPUBufferMapCallbackInfo2& callbackInfo)
: TrackedEvent(instance, callbackInfo.mode), mBuffer(buffer) {}
EventType GetType() override { return kType; }
void ReadyHook(WGPUMapAsyncStatus status, const char* message) {
// For mapping, this hook may be called more than once if we are not in
// Spontaneous mode. The precedence of which status should follow
// Success < Error < Aborted. Luckily, the enum is defined such that the
// precedence holds true already, so we can exploit that here.
static_assert(WGPUMapAsyncStatus_Success < WGPUMapAsyncStatus_Error);
static_assert(WGPUMapAsyncStatus_Error < WGPUMapAsyncStatus_Aborted);
if (status > mStatus) {
mStatus = status;
if (message) {
mMessage = message;
}
}
}
void Complete(FutureID futureID, EventCompletionType type) override {
if (type == EventCompletionType::Shutdown) {
mStatus = WGPUMapAsyncStatus_InstanceDropped;
mMessage = "A valid external Instance reference no longer exists.";
}
if (mBuffer->IsPendingMapRequest(futureID)) {
if (mStatus == WGPUMapAsyncStatus_Success) {
mBuffer->mMapState = WGPUBufferMapState_Mapped;
} else {
mBuffer->mMapState = WGPUBufferMapState_Unmapped;
mBuffer->mPendingMapRequest = {};
}
} else {
assert(mStatus != WGPUMapAsyncStatus_Success);
}
if (mCallback) {
mCallback(mStatus, ToOutputStringView(mMessage), mUserdata1, mUserdata2);
}
}
private:
WGPUBufferMapCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
Ref<WGPUBuffer> mBuffer;
WGPUMapAsyncStatus mStatus = WGPUMapAsyncStatus_Success;
std::string mMessage;
};
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.Acquire(adapter);
if (message) {
mMessage = message;
}
}
void Complete(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
? ReturnToAPI(std::move(mAdapter))
: nullptr,
ToOutputStringView(mMessage), mUserdata1, mUserdata2);
}
}
private:
WGPURequestAdapterCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
WGPURequestAdapterStatus mStatus;
Ref<WGPUAdapter> mAdapter;
std::string mMessage;
};
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);
if (message) {
mMessage = message;
}
}
void Complete(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,
ToOutputStringView(mMessage), mUserdata1, mUserdata2);
}
}
private:
WGPURequestDeviceCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
WGPURequestDeviceStatus mStatus;
Ref<WGPUDevice> mDevice;
std::string mMessage;
};
class WorkDoneEvent final : public TrackedEvent {
public:
static constexpr EventType kType = EventType::WorkDone;
WorkDoneEvent(InstanceID instance,
const WGPUQueueWorkDoneCallbackInfo2& callbackInfo)
: TrackedEvent(instance, callbackInfo.mode),
mCallback(callbackInfo.callback),
mUserdata1(callbackInfo.userdata1),
mUserdata2(callbackInfo.userdata2) {}
EventType GetType() override { return kType; }
void ReadyHook(WGPUQueueWorkDoneStatus status) { mStatus = status; }
void Complete(FutureID, EventCompletionType type) override {
if (type == EventCompletionType::Shutdown) {
mStatus = WGPUQueueWorkDoneStatus_InstanceDropped;
}
if (mCallback) {
mCallback(mStatus, mUserdata1, mUserdata2);
}
}
private:
WGPUQueueWorkDoneCallback2 mCallback = nullptr;
void* mUserdata1 = nullptr;
void* mUserdata2 = nullptr;
WGPUQueueWorkDoneStatus mStatus;
};
// ----------------------------------------------------------------------------
// Definitions for C++ emwgpu functions (callable from library_webgpu.js)
// ----------------------------------------------------------------------------
extern "C" {
// Object creation helpers that all return a pointer which is used as a key
// in the JS object table in library_webgpu.js.
#define DEFINE_EMWGPU_DEFAULT_CREATE(Name) \
WGPU##Name emwgpuCreate##Name(const EventSource* source = nullptr) { \
return new WGPU##Name##Impl(source); \
}
WGPU_PASSTHROUGH_OBJECTS(DEFINE_EMWGPU_DEFAULT_CREATE)
WGPUAdapter emwgpuCreateAdapter(const EventSource* source) {
return new WGPUAdapterImpl(source);
}
WGPUBuffer emwgpuCreateBuffer(const EventSource* source,
bool mappedAtCreation = false) {
return new WGPUBufferImpl(source, mappedAtCreation);
}
WGPUDevice emwgpuCreateDevice(const EventSource* source, WGPUQueue queue) {
return new WGPUDeviceImpl(source, queue);
}
WGPUQueue emwgpuCreateQueue(const EventSource* source) {
return new WGPUQueueImpl(source);
}
WGPUShaderModule emwgpuCreateShaderModule(const EventSource* source) {
return new WGPUShaderModuleImpl(source);
}
// Future event callbacks.
void emwgpuOnCompilationInfoCompleted(FutureID futureId,
WGPUCompilationInfoRequestStatus status,
WGPUCompilationInfo* compilationInfo) {
GetEventManager().SetFutureReady<CompilationInfoEvent>(futureId, status,
compilationInfo);
}
void emwgpuOnCreateComputePipelineCompleted(
FutureID futureId,
WGPUCreatePipelineAsyncStatus status,
WGPUComputePipeline pipeline,
const char* message) {
GetEventManager().SetFutureReady<CreateComputePipelineEvent>(
futureId, status, pipeline, message);
}
void emwgpuOnCreateRenderPipelineCompleted(FutureID futureId,
WGPUCreatePipelineAsyncStatus status,
WGPURenderPipeline pipeline,
const char* message) {
GetEventManager().SetFutureReady<CreateRenderPipelineEvent>(
futureId, status, pipeline, message);
}
void emwgpuOnDeviceLostCompleted(FutureID futureId,
WGPUDeviceLostReason reason,
const char* message) {
GetEventManager().SetFutureReady<DeviceLostEvent>(futureId, reason, message);
}
void emwgpuOnMapAsyncCompleted(FutureID futureId,
WGPUMapAsyncStatus status,
const char* message) {
GetEventManager().SetFutureReady<MapAsyncEvent>(futureId, status, message);
}
void emwgpuOnPopErrorScopeCompleted(FutureID futureId,
WGPUPopErrorScopeStatus status,
WGPUErrorType errorType,
const char* message) {
GetEventManager().SetFutureReady<PopErrorScopeEvent>(futureId, status,
errorType, message);
}
void emwgpuOnRequestAdapterCompleted(FutureID futureId,
WGPURequestAdapterStatus status,
WGPUAdapter adapter,
const char* message) {
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);
}
}
void emwgpuOnWorkDoneCompleted(FutureID futureId,
WGPUQueueWorkDoneStatus status) {
GetEventManager().SetFutureReady<WorkDoneEvent>(futureId, status);
}
// 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"
// ----------------------------------------------------------------------------
// WGPU struct implementations.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// WGPUAdapterImpl implementations.
// ----------------------------------------------------------------------------
WGPUAdapterImpl::WGPUAdapterImpl(const EventSource* source)
: EventSource(source) {}
// ----------------------------------------------------------------------------
// WGPUBuffer implementations.
// ----------------------------------------------------------------------------
WGPUBufferImpl::WGPUBufferImpl(const EventSource* source, bool mappedAtCreation)
: EventSource(source),
mMapState(mappedAtCreation ? WGPUBufferMapState_Mapped
: WGPUBufferMapState_Unmapped) {
if (mappedAtCreation) {
mPendingMapRequest = {kNullFutureId, WGPUMapMode_Write};
}
}
void WGPUBufferImpl::Destroy() {
emwgpuBufferDestroy(this);
AbortPendingMap("Buffer was destroyed before mapping was resolved.");
}
const void* WGPUBufferImpl::GetConstMappedRange(size_t offset, size_t size) {
if (mMapState != WGPUBufferMapState_Mapped) {
return nullptr;
}
return emwgpuBufferGetConstMappedRange(this, offset, size);
}
WGPUBufferMapState WGPUBufferImpl::GetMapState() const {
return mMapState;
}
void* WGPUBufferImpl::GetMappedRange(size_t offset, size_t size) {
if (mMapState != WGPUBufferMapState_Mapped) {
return nullptr;
}
if (mPendingMapRequest.mode != WGPUMapMode_Write) {
assert(false);
return nullptr;
}
return emwgpuBufferGetMappedRange(this, offset, size);
}
WGPUFuture WGPUBufferImpl::MapAsync(WGPUMapMode mode,
size_t offset,
size_t size,
WGPUBufferMapCallbackInfo2 callbackInfo) {
auto [futureId, tracked] = GetEventManager().TrackEvent(
std::make_unique<MapAsyncEvent>(GetInstanceId(), this, callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
if (mMapState == WGPUBufferMapState_Pending) {
GetEventManager().SetFutureReady<MapAsyncEvent>(
futureId, WGPUMapAsyncStatus_Error,
"Buffer already has an outstanding map pending.");
return WGPUFuture{futureId};
}
assert(mPendingMapRequest.mode == WGPUMapMode_None);
mMapState = WGPUBufferMapState_Pending;
mPendingMapRequest = {futureId, mode};
emwgpuBufferMapAsync(this, futureId, mode, offset, size);
return WGPUFuture{futureId};
}
void WGPUBufferImpl::Unmap() {
emwgpuBufferUnmap(this);
AbortPendingMap("Buffer was unmapped before mapping was resolved.");
}
bool WGPUBufferImpl::IsPendingMapRequest(FutureID futureID) const {
assert(futureID != kNullFutureId);
return mPendingMapRequest.futureID == futureID;
}
void WGPUBufferImpl::AbortPendingMap(const char* message) {
if (mMapState == WGPUBufferMapState_Unmapped) {
return;
}
mMapState = WGPUBufferMapState_Unmapped;
FutureID futureId = mPendingMapRequest.futureID;
if (futureId == kNullFutureId) {
// If we were mappedAtCreation, then there is no pending map request so we
// don't need to resolve any futures.
return;
}
mPendingMapRequest = {};
GetEventManager().SetFutureReady<MapAsyncEvent>(
futureId, WGPUMapAsyncStatus_Aborted, message);
}
void WGPUBufferImpl::WillDropLastExternalRef() {
AbortPendingMap("Buffer was destroyed before mapping was resolved.");
}
// ----------------------------------------------------------------------------
// WGPUDeviceImpl implementations.
// ----------------------------------------------------------------------------
WGPUDeviceImpl::WGPUDeviceImpl(const EventSource* source,
const WGPUDeviceDescriptor* descriptor,
WGPUQueue queue)
: EventSource(source),
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) {
mQueue.Acquire(queue);
}
void WGPUDeviceImpl::Destroy() {
emwgpuDeviceDestroy(this);
// TODO(374803367): Remove this when we can get the device lost future.
OnDeviceLost(WGPUDeviceLostReason_Destroyed, "Device was destroyed.");
}
WGPUQueue WGPUDeviceImpl::GetQueue() const {
auto queue = mQueue;
return ReturnToAPI(std::move(queue));
}
WGPUFuture WGPUDeviceImpl::GetDeviceLostFuture() const {
return WGPUFuture{mDeviceLostFutureId};
}
void WGPUDeviceImpl::OnDeviceLost(WGPUDeviceLostReason reason,
const char* message) {
if (mDeviceLostFutureId != kNullFutureId) {
GetEventManager().SetFutureReady<DeviceLostEvent>(mDeviceLostFutureId,
reason, message);
}
mDeviceLostFutureId = kNullFutureId;
}
void WGPUDeviceImpl::OnUncapturedError(WGPUErrorType type,
char const* message) {
if (mUncapturedErrorCallbackInfo.callback) {
WGPUDeviceImpl* device = this;
mUncapturedErrorCallbackInfo.callback(
&device, type,
WGPUStringView{.data = message, .length = std::strlen(message)},
mUncapturedErrorCallbackInfo.userdata1,
mUncapturedErrorCallbackInfo.userdata2);
}
}
void WGPUDeviceImpl::WillDropLastExternalRef() {
Destroy();
}
// ----------------------------------------------------------------------------
// 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++;
}
// ----------------------------------------------------------------------------
// WGPUQueueImpl implementations.
// ----------------------------------------------------------------------------
WGPUQueueImpl::WGPUQueueImpl(const EventSource* source) : EventSource(source) {}
// ----------------------------------------------------------------------------
// WGPUShaderModuleImpl implementations.
// ----------------------------------------------------------------------------
WGPUShaderModuleImpl::WGPUShaderModuleImpl(const EventSource* source)
: EventSource(source) {}
WGPUFuture WGPUShaderModuleImpl::GetCompilationInfo(
WGPUCompilationInfoCallbackInfo2 callbackInfo) {
auto [futureId, tracked] =
GetEventManager().TrackEvent(std::make_unique<CompilationInfoEvent>(
GetInstanceId(), this, callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
// If we already have the compilation info cached, we don't need to call into
// JS.
if (mCompilationInfo) {
emwgpuOnCompilationInfoCompleted(futureId,
WGPUCompilationInfoRequestStatus_Success,
mCompilationInfo.get());
} else {
WGPUCompilationInfo* compilationInfo = new WGPUCompilationInfo{
.nextInChain = nullptr, .messageCount = 0, .messages = nullptr};
emwgpuShaderModuleGetCompilationInfo(this, futureId, compilationInfo);
}
return WGPUFuture{futureId};
}
// ----------------------------------------------------------------------------
// WebGPU function definitions, with methods organized by "class". Note these
// don't need to be extern "C" because they are already declared in webgpu.h.
//
// Also note that the full set of functions declared in webgpu.h are only
// partially implemeted here. The remaining ones are implemented via
// library_webgpu.js.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Common APIs are batch generated via X macros for all objects, including:
// - AddRef
// - Release
// - SetLabel
// ----------------------------------------------------------------------------
#define DEFINE_WGPU_DEFAULT_ADDREF_RELEASE(Name) \
void wgpu##Name##AddRef(WGPU##Name o) { \
o->AddRef(); \
} \
void wgpu##Name##Release(WGPU##Name o) { \
if (o->Release()) { \
delete o; \
} \
}
WGPU_OBJECTS(DEFINE_WGPU_DEFAULT_ADDREF_RELEASE)
#define DEFINE_WGPU_DEFAULT_SETLABEL(Name) \
void wgpu##Name##SetLabel(WGPU##Name o, WGPUStringView label) { \
emwgpuSetLabel(o, label.data, label.length); \
}
WGPU_OBJECTS(DEFINE_WGPU_DEFAULT_SETLABEL)
// ----------------------------------------------------------------------------
// Standalone (non-method) functions
// ----------------------------------------------------------------------------
void wgpuAdapterInfoFreeMembers(WGPUAdapterInfo value) {
// The strings are allocated via a single malloc, so freeing the first pointer
// frees all of the strings in the struct.
free(const_cast<char*>(value.vendor.data));
}
WGPUInstance wgpuCreateInstance([[maybe_unused]] const WGPUInstanceDescriptor* descriptor) {
assert(descriptor == nullptr); // descriptor not implemented yet
return new WGPUInstanceImpl();
}
void wgpuSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities) {
// wgpuSurfaceCapabilities doesn't currently allocate anything.
}
// ----------------------------------------------------------------------------
// 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,
WGPUStringView 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};
}
static const WGPUDeviceDescriptor kDefaultDescriptor =
WGPU_DEVICE_DESCRIPTOR_INIT;
if (descriptor == nullptr) {
descriptor = &kDefaultDescriptor;
}
// For RequestDevice, we always create a Device and Queue up front. The
// Device is also immediately associated with the DeviceLostEvent.
WGPUQueue queue = new WGPUQueueImpl(adapter);
WGPUDevice device = new WGPUDeviceImpl(adapter, descriptor, queue);
auto deviceLostFutureId = device->GetDeviceLostFuture().id;
emwgpuAdapterRequestDevice(adapter, futureId, deviceLostFutureId, device,
queue, descriptor);
return WGPUFuture{futureId};
}
// ----------------------------------------------------------------------------
// Methods of BindGroup
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of BindGroupLayout
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of Buffer
// ----------------------------------------------------------------------------
void wgpuBufferDestroy(WGPUBuffer buffer) {
buffer->Destroy();
}
const void* wgpuBufferGetConstMappedRange(WGPUBuffer buffer,
size_t offset,
size_t size) {
return buffer->GetConstMappedRange(offset, size);
}
WGPUBufferMapState wgpuBufferGetMapState(WGPUBuffer buffer) {
return buffer->GetMapState();
}
void* wgpuBufferGetMappedRange(WGPUBuffer buffer, size_t offset, size_t size) {
return buffer->GetMappedRange(offset, size);
}
WGPUFuture wgpuBufferMapAsync2(WGPUBuffer buffer,
WGPUMapMode mode,
size_t offset,
size_t size,
WGPUBufferMapCallbackInfo2 callbackInfo) {
return buffer->MapAsync(mode, offset, size, callbackInfo);
}
void wgpuBufferUnmap(WGPUBuffer buffer) {
buffer->Unmap();
}
// ----------------------------------------------------------------------------
// Methods of CommandBuffer
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of CommandEncoder
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of ComputePassEncoder
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of ComputePipeline
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of Device
// ----------------------------------------------------------------------------
WGPUBuffer wgpuDeviceCreateBuffer(WGPUDevice device,
const WGPUBufferDescriptor* descriptor) {
WGPUBuffer buffer = new WGPUBufferImpl(device, descriptor->mappedAtCreation);
emwgpuDeviceCreateBuffer(device, descriptor, buffer);
return buffer;
}
void wgpuDeviceCreateComputePipelineAsync(
WGPUDevice device,
const WGPUComputePipelineDescriptor* descriptor,
WGPUCreateComputePipelineAsyncCallback callback,
void* userdata) {
WGPUCreateComputePipelineAsyncCallbackInfo2 callbackInfo = {};
callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
callbackInfo.callback = [](WGPUCreatePipelineAsyncStatus status,
WGPUComputePipeline pipeline,
WGPUStringView message, void* callback,
void* userdata) {
auto cb =
reinterpret_cast<WGPUCreateComputePipelineAsyncCallback>(callback);
cb(status, pipeline, message, userdata);
};
callbackInfo.userdata1 = reinterpret_cast<void*>(callback);
callbackInfo.userdata2 = userdata;
wgpuDeviceCreateComputePipelineAsync2(device, descriptor, callbackInfo);
}
WGPUFuture wgpuDeviceCreateComputePipelineAsync2(
WGPUDevice device,
const WGPUComputePipelineDescriptor* descriptor,
WGPUCreateComputePipelineAsyncCallbackInfo2 callbackInfo) {
auto [futureId, tracked] =
GetEventManager().TrackEvent(std::make_unique<CreateComputePipelineEvent>(
device->GetInstanceId(), callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
emwgpuDeviceCreateComputePipelineAsync(device, futureId, descriptor);
return WGPUFuture{futureId};
}
void wgpuDeviceCreateRenderPipelineAsync(
WGPUDevice device,
const WGPURenderPipelineDescriptor* descriptor,
WGPUCreateRenderPipelineAsyncCallback callback,
void* userdata) {
WGPUCreateRenderPipelineAsyncCallbackInfo2 callbackInfo = {};
callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
callbackInfo.callback = [](WGPUCreatePipelineAsyncStatus status,
WGPURenderPipeline pipeline,
WGPUStringView message, void* callback,
void* userdata) {
auto cb = reinterpret_cast<WGPUCreateRenderPipelineAsyncCallback>(callback);
cb(status, pipeline, message, userdata);
};
callbackInfo.userdata1 = reinterpret_cast<void*>(callback);
callbackInfo.userdata2 = userdata;
wgpuDeviceCreateRenderPipelineAsync2(device, descriptor, callbackInfo);
}
WGPUFuture wgpuDeviceCreateRenderPipelineAsync2(
WGPUDevice device,
const WGPURenderPipelineDescriptor* descriptor,
WGPUCreateRenderPipelineAsyncCallbackInfo2 callbackInfo) {
auto [futureId, tracked] =
GetEventManager().TrackEvent(std::make_unique<CreateRenderPipelineEvent>(
device->GetInstanceId(), callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
emwgpuDeviceCreateRenderPipelineAsync(device, futureId, descriptor);
return WGPUFuture{futureId};
}
WGPUShaderModule wgpuDeviceCreateShaderModule(
WGPUDevice device,
const WGPUShaderModuleDescriptor* descriptor) {
WGPUShaderModule shader = new WGPUShaderModuleImpl(device);
emwgpuDeviceCreateShaderModule(device, descriptor, shader);
return shader;
}
void wgpuDeviceDestroy(WGPUDevice device) {
device->Destroy();
}
WGPUQueue wgpuDeviceGetQueue(WGPUDevice device) {
return device->GetQueue();
}
WGPUFuture wgpuDevicePopErrorScope2(
WGPUDevice device,
WGPUPopErrorScopeCallbackInfo2 callbackInfo) {
auto [futureId, tracked] =
GetEventManager().TrackEvent(std::make_unique<PopErrorScopeEvent>(
device->GetInstanceId(), callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
emwgpuDevicePopErrorScope(device, futureId);
return WGPUFuture{futureId};
}
// ----------------------------------------------------------------------------
// 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, WGPUStringView 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->GetInstanceId(), 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
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of QuerySet
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of Queue
// ----------------------------------------------------------------------------
WGPUFuture wgpuQueueOnSubmittedWorkDone2(
WGPUQueue queue,
WGPUQueueWorkDoneCallbackInfo2 callbackInfo) {
auto [futureId, tracked] = GetEventManager().TrackEvent(
std::make_unique<WorkDoneEvent>(queue->GetInstanceId(), callbackInfo));
if (!tracked) {
return WGPUFuture{kNullFutureId};
}
emwgpuQueueOnSubmittedWorkDone(queue, futureId);
return WGPUFuture{futureId};
}
// ----------------------------------------------------------------------------
// Methods of RenderBundle
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of RenderBundleEncoder
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of RenderPassEncoder
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of RenderPipeline
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of Sampler
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of ShaderModule
// ----------------------------------------------------------------------------
WGPUFuture wgpuShaderModuleGetCompilationInfo2(
WGPUShaderModule shader,
WGPUCompilationInfoCallbackInfo2 callbackInfo) {
return shader->GetCompilationInfo(callbackInfo);
}
// ----------------------------------------------------------------------------
// Methods of Surface
// ----------------------------------------------------------------------------
WGPUStatus wgpuSurfaceGetCapabilities(WGPUSurface surface,
WGPUAdapter adapter,
WGPUSurfaceCapabilities* capabilities) {
assert(capabilities->nextInChain == nullptr); // TODO: Return WGPUStatus_Error
static constexpr std::array<WGPUTextureFormat, 3> kSurfaceFormatsRGBAFirst = {
WGPUTextureFormat_RGBA8Unorm,
WGPUTextureFormat_BGRA8Unorm,
WGPUTextureFormat_RGBA16Float,
};
static constexpr std::array<WGPUTextureFormat, 3> kSurfaceFormatsBGRAFirst = {
WGPUTextureFormat_BGRA8Unorm,
WGPUTextureFormat_RGBA8Unorm,
WGPUTextureFormat_RGBA16Float,
};
WGPUTextureFormat preferredFormat = emwgpuGetPreferredFormat();
switch (preferredFormat) {
case WGPUTextureFormat_RGBA8Unorm:
capabilities->formatCount = kSurfaceFormatsRGBAFirst.size();
capabilities->formats = kSurfaceFormatsRGBAFirst.data();
break;
case WGPUTextureFormat_BGRA8Unorm:
capabilities->formatCount = kSurfaceFormatsBGRAFirst.size();
capabilities->formats = kSurfaceFormatsBGRAFirst.data();
break;
default:
assert(false);
return WGPUStatus_Error;
}
{
static constexpr WGPUPresentMode kPresentMode = WGPUPresentMode_Fifo;
capabilities->presentModeCount = 1;
capabilities->presentModes = &kPresentMode;
}
{
static constexpr std::array<WGPUCompositeAlphaMode, 2> kAlphaModes = {
WGPUCompositeAlphaMode_Opaque,
WGPUCompositeAlphaMode_Premultiplied,
};
capabilities->alphaModeCount = kAlphaModes.size();
capabilities->alphaModes = kAlphaModes.data();
}
return WGPUStatus_Success;
}
// ----------------------------------------------------------------------------
// Methods of Texture
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Methods of TextureView
// ----------------------------------------------------------------------------