| // 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 <cinttypes> | |
| #include <cstdlib> | |
| #include <map> | |
| #include <memory> | |
| #include <mutex> | |
| #include <set> | |
| #include <span> | |
| #include <tuple> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| // Used for "implementation-defined logging" per webgpu.h spec. | |
| // This is a no-op in NDEBUG builds, to reduce code-size. | |
| #ifndef NDEBUG | |
| #include <cstdio> | |
| #define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__) | |
| #define DCHECK_PRINTF(condition, ...) \ | |
| do { \ | |
| if (!(condition)) { \ | |
| fprintf(stderr, __VA_ARGS__); \ | |
| assert(false); \ | |
| } \ | |
| } while (0) | |
| #else | |
| #define DEBUG_PRINTF(...) | |
| #define DCHECK_PRINTF(...) | |
| #endif | |
| using FutureID = uint64_t; | |
| static constexpr FutureID kNullFutureId = 0; | |
| using InstanceID = uint64_t; | |
| static constexpr InstanceID kNullInstanceId = 0; | |
| static constexpr size_t kTimedWaitAnyMaxCountMax = INT32_MAX; | |
| static constexpr size_t kTimedWaitAnyMaxCountDefault = 64; | |
| // ---------------------------------------------------------------------------- | |
| // 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 the FutureIDs as uint64_t pointer | |
| // and deref it in JS on the other side. The timeout, however, is converted to | |
| // a int32_t because it will be used with JS setTimeout which actually takes a | |
| // int32_t. | |
| double emwgpuWaitAny(FutureID const* futurePtr, | |
| size_t futureCount, | |
| int32_t const* timeoutMSPtr); | |
| WGPUTextureFormat emwgpuGetPreferredFormat(); | |
| // Device functions, i.e. creation functions to create JS backing objects given | |
| // a pre-allocated handle, and destruction implementations. | |
| [[nodiscard]] bool 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); | |
| WGPUStatus emwgpuBufferWriteMappedRange(WGPUBuffer buffer, | |
| size_t offset, | |
| void const* data, | |
| size_t size); | |
| WGPUStatus emwgpuBufferReadMappedRange(WGPUBuffer buffer, | |
| size_t offset, | |
| void* data, | |
| 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, | |
| WGPUComputePipeline pipeline); | |
| void emwgpuDeviceCreateRenderPipelineAsync( | |
| WGPUDevice device, | |
| FutureID futureId, | |
| const WGPURenderPipelineDescriptor* descriptor, | |
| WGPURenderPipeline pipeline); | |
| void emwgpuDevicePopErrorScope(WGPUDevice device, FutureID futureId); | |
| void emwgpuInstanceRequestAdapter(WGPUInstance instance, | |
| FutureID futureId, | |
| const WGPURequestAdapterOptions* options, | |
| WGPUAdapter adapter); | |
| void emwgpuQueueOnSubmittedWorkDone(WGPUQueue queue, FutureID futureId); | |
| void emwgpuShaderModuleGetCompilationInfo(WGPUShaderModule shader, | |
| FutureID futureId, | |
| WGPUCompilationInfo* compilationInfo); | |
| } // extern "C" | |
| namespace { | |
| // ---------------------------------------------------------------------------- | |
| // 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; | |
| }; | |
| // For some objects we may do additional cleanup routines, i.e. Destroy if the | |
| // object was natively created via the API. However, if the object was imported | |
| // from JS, we don't do the additional cleanup because they may still be used | |
| // outside of the Wasm API. | |
| struct ImportedFromJSTag {}; | |
| static constexpr ImportedFromJSTag kImportedFromJS; | |
| class RefCounted : NonMovable { | |
| public: | |
| static constexpr bool HasExternalRefCount = false; | |
| explicit RefCounted(ImportedFromJSTag) : mIsImportedFromJS(true) {} | |
| RefCounted() = default; | |
| 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); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool IsImported() const { return mIsImportedFromJS; } | |
| private: | |
| std::atomic<uint64_t> mRefCount = 1; | |
| bool mIsImportedFromJS = false; | |
| }; | |
| class RefCountedWithExternalCount : public RefCounted { | |
| public: | |
| static constexpr bool HasExternalRefCount = true; | |
| explicit RefCountedWithExternalCount(ImportedFromJSTag tag) | |
| : RefCounted(tag) {} | |
| RefCountedWithExternalCount() = default; | |
| 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: | |
| Ref() : mValue(nullptr) { | |
| static_assert(std::is_convertible_v<T*, RefCounted*>, | |
| "Cannot make a Ref<T> when T is not a Refcounted type."); | |
| } | |
| ~Ref() { Release(mValue); } | |
| // Constructors from nullptr. | |
| explicit(false) constexpr Ref(std::nullptr_t) : Ref() {} | |
| // Constructors from T*. | |
| explicit(false) Ref(T* value) : mValue(value) { AddRef(value); } | |
| Ref<T>& operator=(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; | |
| // emwgpuDelete() removes the pointer from the jsObjects mapping. | |
| // Considering some class implementation may need to use it in | |
| // destructor, we call emwgpuDelete() after the pointer delete. | |
| // This also applies to implementation of `wgpu{Type}Release`. | |
| emwgpuDelete(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> | |
| Ref<T> AcquireRef(T* pointee) { | |
| Ref<T> ref; | |
| ref.Acquire(pointee); | |
| return ref; | |
| } | |
| 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(ExternalTexture) \ | |
| 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(ExternalTexture) \ | |
| 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, | |
| }; | |
| bool ValidateCallbackMode(WGPUCallbackMode mode, bool hasCallback) { | |
| if (mode == WGPUCallbackMode_WaitAnyOnly || | |
| mode == WGPUCallbackMode_AllowProcessEvents || | |
| mode == WGPUCallbackMode_AllowSpontaneous || | |
| (int(mode) == 0 && !hasCallback)) { | |
| return true; | |
| } else { | |
| DEBUG_PRINTF("Invalid WGPUCallbackMode %d\n", mode); | |
| return false; | |
| } | |
| } | |
| template <typename CallbackInfo> | |
| bool ValidateCallbackMode(const CallbackInfo& info) { | |
| // This small templated function delegates to the larger non-templated | |
| // function to avoid unnecessary monomorphization. | |
| return ValidateCallbackMode(info.mode, info.callback); | |
| } | |
| class EventManager; | |
| 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), | |
| // Mode can only be 0 if there's no callback, so just pick any default. | |
| mMode(int(mode) == 0 ? WGPUCallbackMode_AllowSpontaneous : mode) { | |
| assert(mMode == WGPUCallbackMode_WaitAnyOnly || | |
| mMode == WGPUCallbackMode_AllowProcessEvents || | |
| mMode == WGPUCallbackMode_AllowSpontaneous); | |
| } | |
| private: | |
| friend 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: | |
| EventManager() { | |
| // We set up a tracker for events that are registered against a null | |
| // Instance because devices may have been created and injected before the | |
| // Instance was created. | |
| // TODO(crbug.com/388914937): Remove this once users are updated. | |
| std::unique_lock<std::mutex> lock(mMutex); | |
| mPerInstanceEvents.try_emplace(kNullInstanceId); | |
| } | |
| 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 instanceIt = mPerInstanceEvents.find(instance); | |
| assert(instanceIt != mPerInstanceEvents.end()); | |
| // When unregistering the Instance, resolve all non-spontaneous callbacks | |
| // with Shutdown. | |
| for (const FutureID futureId : instanceIt->second) { | |
| if (auto futureIdsIt = mEvents.find(futureId); | |
| futureIdsIt != mEvents.end()) { | |
| futureIdsIt->second->Complete(futureId, EventCompletionType::Shutdown); | |
| mEvents.erase(futureIdsIt); | |
| } | |
| } | |
| 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) { | |
| // Should have already been validated in WGPUInstanceImpl::WaitAny. | |
| assert(emscripten_has_asyncify()); | |
| std::vector<FutureID> futures; | |
| std::unordered_map<FutureID, WGPUFutureWaitInfo*> futureIdToInfo; | |
| for (size_t i = 0; i < count; ++i) { | |
| FutureID id = infos[i].future.id; | |
| DCHECK_PRINTF(id != kNullFutureId && id < mNextFutureId, | |
| "Invalid future id %" PRIu64 "\n", id); | |
| futures.push_back(id); | |
| futureIdToInfo.emplace(id, &infos[i]); | |
| } | |
| // We need to clamp and convert the timeout to verify that the timeout in | |
| // ms is less than the maximum value that JS setTimeout can take. If it | |
| // would exceed the timeout, just assume that we are waiting until | |
| // completion. | |
| static constexpr uint64_t kNsInMs = 1000000; | |
| uint64_t timeoutMS64 = timeoutNS / kNsInMs; | |
| bool hasTimeout = timeoutMS64 <= INT32_MAX; | |
| int32_t timeoutMS = static_cast<int32_t>(timeoutMS64); | |
| FutureID completedId = static_cast<FutureID>(emwgpuWaitAny( | |
| futures.data(), count, hasTimeout ? &timeoutMS : 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; | |
| anyCompleted = 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; | |
| } | |
| FutureID 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; | |
| } | |
| it->second.insert(futureId); | |
| break; | |
| } | |
| // Any invalid callback mode should already have been validated by | |
| // ValidateCallbackMode, but it still may be 0 if there's no callback. | |
| default: | |
| case WGPUCallbackMode_AllowSpontaneous: | |
| break; | |
| } | |
| mEvents.try_emplace(futureId, std::move(event)); | |
| return futureId; | |
| } | |
| template <typename Event, typename... ReadyArgs> | |
| void SetFutureReady(FutureID futureId, ReadyArgs&&... readyArgs) { | |
| assert(futureId != kNullFutureId); | |
| 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); | |
| } | |
| } | |
| template <typename Event, typename... ReadyArgs> | |
| void SetFutureReady(double futureId, ReadyArgs&&... readyArgs) { | |
| SetFutureReady<Event>(static_cast<uint64_t>(futureId), | |
| std::forward<ReadyArgs>(readyArgs)...); | |
| } | |
| 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; | |
| } | |
| } // namespace | |
| // ---------------------------------------------------------------------------- | |
| // WGPU struct declarations. | |
| // ---------------------------------------------------------------------------- | |
| // Default struct declarations. | |
| #define DEFINE_WGPU_DEFAULT_STRUCT(Name) \ | |
| struct WGPU##Name##Impl final : public RefCounted { \ | |
| WGPU##Name##Impl(const EventSource* = nullptr) {} \ | |
| }; | |
| WGPU_PASSTHROUGH_OBJECTS(DEFINE_WGPU_DEFAULT_STRUCT) | |
| struct WGPUAdapterImpl final : public EventSource, public RefCounted { | |
| public: | |
| WGPUAdapterImpl(const EventSource* source); | |
| }; | |
| namespace { | |
| // Forward declare so this can be used as a friend below. | |
| class MapAsyncEvent; | |
| } // namespace | |
| struct WGPUBufferImpl final : public EventSource, | |
| public RefCountedWithExternalCount { | |
| public: | |
| WGPUBufferImpl(const EventSource* source, bool mappedAtCreation); | |
| // Injection constructor used when we already have a backing Buffer. | |
| WGPUBufferImpl(const EventSource* source, ImportedFromJSTag tag); | |
| void Destroy(); | |
| const void* GetConstMappedRange(size_t offset, size_t size); | |
| WGPUBufferMapState GetMapState() const; | |
| void* GetMappedRange(size_t offset, size_t size); | |
| WGPUStatus WriteMappedRange(size_t offset, void const* data, size_t size); | |
| WGPUStatus ReadMappedRange(size_t offset, void* data, size_t size); | |
| WGPUFuture MapAsync(WGPUMapMode mode, | |
| size_t offset, | |
| size_t size, | |
| WGPUBufferMapCallbackInfo callbackInfo); | |
| void Unmap(); | |
| private: | |
| friend 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); | |
| }; | |
| enum class DropDeviceFromDeviceLostEvent : bool { No, Yes }; | |
| // 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(DropDeviceFromDeviceLostEvent dropDevice); | |
| WGPUQueue GetQueue() const; | |
| WGPUFuture GetLostFuture() const; | |
| void OnUncapturedError(WGPUErrorType type, char const* message); | |
| private: | |
| void WillDropLastExternalRef() override; | |
| Ref<WGPUQueueImpl> mQueue; | |
| WGPUUncapturedErrorCallbackInfo mUncapturedErrorCallbackInfo = | |
| WGPU_UNCAPTURED_ERROR_CALLBACK_INFO_INIT; | |
| FutureID mDeviceLostFutureId = kNullFutureId; | |
| }; | |
| // Instance is specially implemented in order to handle Futures implementation. | |
| struct WGPUInstanceImpl final : public EventSource, public RefCounted { | |
| public: | |
| static Ref<WGPUInstanceImpl> Create(const WGPUInstanceDescriptor* desc); | |
| ~WGPUInstanceImpl(); | |
| void ProcessEvents(); | |
| WGPUWaitStatus WaitAny(size_t count, | |
| WGPUFutureWaitInfo* infos, | |
| uint64_t timeoutNS); | |
| private: | |
| WGPUInstanceImpl(); | |
| static InstanceID GetNextInstanceId(); | |
| // Zero means TimedWaitAny is not supported at all. | |
| size_t mTimedWaitAnyMaxCount = 0; | |
| }; | |
| namespace { | |
| // Forward declare so this can be used as a friend below. | |
| class CompilationInfoEvent; | |
| } // namespace | |
| struct WGPUShaderModuleImpl final : public EventSource, public RefCounted { | |
| public: | |
| WGPUShaderModuleImpl(const EventSource* source); | |
| WGPUFuture GetCompilationInfo(WGPUCompilationInfoCallbackInfo callbackInfo); | |
| private: | |
| friend CompilationInfoEvent; | |
| struct WGPUCompilationInfoDeleter { | |
| void operator()(WGPUCompilationInfo* compilationInfo) { | |
| if (!compilationInfo) { | |
| return; | |
| } | |
| // Free allocations iff there were any. | |
| 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)); | |
| // We also allocate all the Utf16 structs in a single array. | |
| free(reinterpret_cast<WGPUDawnCompilationMessageUtf16*>( | |
| compilationInfo->messages[0].nextInChain)); | |
| // Finally, free the array of messages. | |
| free(const_cast<WGPUCompilationMessage*>(compilationInfo->messages)); | |
| } | |
| delete compilationInfo; | |
| } | |
| }; | |
| using CompilationInfo = | |
| std::unique_ptr<WGPUCompilationInfo, WGPUCompilationInfoDeleter>; | |
| CompilationInfo mCompilationInfo = nullptr; | |
| }; | |
| // ---------------------------------------------------------------------------- | |
| // Future events. | |
| // ---------------------------------------------------------------------------- | |
| namespace { | |
| class CompilationInfoEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::CompilationInfo; | |
| CompilationInfoEvent(InstanceID instance, | |
| WGPUShaderModule shader, | |
| const WGPUCompilationInfoCallbackInfo& callbackInfo) | |
| : TrackedEvent(instance, callbackInfo.mode), | |
| mCallback(callbackInfo.callback), | |
| mUserdata1(callbackInfo.userdata1), | |
| mUserdata2(callbackInfo.userdata2), | |
| 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_CallbackCancelled; | |
| } | |
| if (mCallback) { | |
| mCallback(mStatus, | |
| mStatus == WGPUCompilationInfoRequestStatus_Success | |
| ? mShader->mCompilationInfo.get() | |
| : nullptr, | |
| mUserdata1, mUserdata2); | |
| } | |
| } | |
| private: | |
| WGPUCompilationInfoCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| Ref<WGPUShaderModuleImpl> mShader; | |
| WGPUCompilationInfoRequestStatus mStatus = | |
| WGPUCompilationInfoRequestStatus_Success; | |
| }; | |
| template <typename PipelineImpl, 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, | |
| PipelineImpl* 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_CallbackCancelled; | |
| 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<PipelineImpl> mPipeline; | |
| std::string mMessage; | |
| }; | |
| using CreateComputePipelineEvent = | |
| CreatePipelineEventBase<WGPUComputePipelineImpl, | |
| EventType::CreateComputePipeline, | |
| WGPUCreateComputePipelineAsyncCallbackInfo>; | |
| using CreateRenderPipelineEvent = | |
| CreatePipelineEventBase<WGPURenderPipelineImpl, | |
| EventType::CreateRenderPipeline, | |
| WGPUCreateRenderPipelineAsyncCallbackInfo>; | |
| class DeviceLostEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::DeviceLost; | |
| DeviceLostEvent(InstanceID instance, | |
| WGPUDevice device, | |
| const WGPUDeviceLostCallbackInfo& callbackInfo) | |
| : TrackedEvent(instance, callbackInfo.mode), | |
| mCallback(callbackInfo.callback), | |
| mUserdata1(callbackInfo.userdata1), | |
| mUserdata2(callbackInfo.userdata2), | |
| mDevice(device) { | |
| assert(mDevice); | |
| } | |
| EventType GetType() override { return kType; } | |
| // If dropDevice=Yes, drops the DeviceLostEvent->WGPUDeviceImpl ref so the | |
| // device won't be passed to the callback, in preparation to free the device. | |
| void ReadyHook(DropDeviceFromDeviceLostEvent dropDevice, | |
| WGPUDeviceLostReason reason, | |
| const char* message) { | |
| if (dropDevice == DropDeviceFromDeviceLostEvent::Yes) { | |
| mDevice = nullptr; | |
| } | |
| mReason = reason; | |
| if (message) { | |
| mMessage = message; | |
| } | |
| } | |
| void Complete(FutureID, EventCompletionType type) override { | |
| if (type == EventCompletionType::Shutdown) { | |
| mReason = WGPUDeviceLostReason_CallbackCancelled; | |
| mMessage = "A valid external Instance reference no longer exists."; | |
| } | |
| if (mCallback) { | |
| WGPUDevice device = mDevice.Get(); | |
| mCallback(&device, mReason, ToOutputStringView(mMessage), mUserdata1, | |
| mUserdata2); | |
| } | |
| } | |
| private: | |
| WGPUDeviceLostCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| Ref<WGPUDeviceImpl> mDevice; | |
| WGPUDeviceLostReason mReason; | |
| std::string mMessage; | |
| }; | |
| class PopErrorScopeEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::PopErrorScope; | |
| PopErrorScopeEvent(InstanceID instance, | |
| const WGPUPopErrorScopeCallbackInfo& 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_CallbackCancelled; | |
| mErrorType = WGPUErrorType_NoError; | |
| mMessage = "A valid external Instance reference no longer exists."; | |
| } | |
| if (mCallback) { | |
| mCallback(mStatus, mErrorType, ToOutputStringView(mMessage), mUserdata1, | |
| mUserdata2); | |
| } | |
| } | |
| private: | |
| WGPUPopErrorScopeCallback 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 WGPUBufferMapCallbackInfo& callbackInfo) | |
| : TrackedEvent(instance, callbackInfo.mode), | |
| mCallback(callbackInfo.callback), | |
| mUserdata1(callbackInfo.userdata1), | |
| mUserdata2(callbackInfo.userdata2), | |
| 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_CallbackCancelled; | |
| 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: | |
| WGPUBufferMapCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| Ref<WGPUBufferImpl> mBuffer; | |
| WGPUMapAsyncStatus mStatus = WGPUMapAsyncStatus_Success; | |
| std::string mMessage; | |
| }; | |
| class RequestAdapterEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::RequestAdapter; | |
| RequestAdapterEvent(InstanceID instance, | |
| const WGPURequestAdapterCallbackInfo& 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_CallbackCancelled; | |
| 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: | |
| WGPURequestAdapterCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| WGPURequestAdapterStatus mStatus; | |
| Ref<WGPUAdapterImpl> mAdapter; | |
| std::string mMessage; | |
| }; | |
| class RequestDeviceEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::RequestDevice; | |
| RequestDeviceEvent(InstanceID instance, | |
| const WGPURequestDeviceCallbackInfo& 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_CallbackCancelled; | |
| 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: | |
| WGPURequestDeviceCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| WGPURequestDeviceStatus mStatus; | |
| Ref<WGPUDeviceImpl> mDevice; | |
| std::string mMessage; | |
| }; | |
| class WorkDoneEvent final : public TrackedEvent { | |
| public: | |
| static constexpr EventType kType = EventType::WorkDone; | |
| WorkDoneEvent(InstanceID instance, | |
| const WGPUQueueWorkDoneCallbackInfo& 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 { | |
| std::string message; | |
| if (type == EventCompletionType::Shutdown) { | |
| mStatus = WGPUQueueWorkDoneStatus_CallbackCancelled; | |
| message = "A valid external Instance reference no longer exists."; | |
| } | |
| if (mCallback) { | |
| mCallback(mStatus, ToOutputStringView(message), mUserdata1, mUserdata2); | |
| } | |
| } | |
| private: | |
| WGPUQueueWorkDoneCallback mCallback = nullptr; | |
| void* mUserdata1 = nullptr; | |
| void* mUserdata2 = nullptr; | |
| WGPUQueueWorkDoneStatus mStatus; | |
| }; | |
| } // namespace | |
| // ---------------------------------------------------------------------------- | |
| // 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 ReturnToAPI(AcquireRef(new WGPU##Name##Impl(source))); \ | |
| } | |
| WGPU_PASSTHROUGH_OBJECTS(DEFINE_EMWGPU_DEFAULT_CREATE) | |
| WGPUAdapter emwgpuCreateAdapter(const EventSource* source) { | |
| return ReturnToAPI(AcquireRef(new WGPUAdapterImpl(source))); | |
| } | |
| // We don't need emwgpuCreateBuffer, as this is only used for importing. | |
| WGPUBuffer emwgpuImportBuffer(const EventSource* source) { | |
| return ReturnToAPI(AcquireRef(new WGPUBufferImpl(source, kImportedFromJS))); | |
| } | |
| WGPUDevice emwgpuCreateDevice(const EventSource* source, WGPUQueue queue) { | |
| return ReturnToAPI(AcquireRef(new WGPUDeviceImpl(source, queue))); | |
| } | |
| WGPUQueue emwgpuCreateQueue(const EventSource* source) { | |
| return ReturnToAPI(AcquireRef(new WGPUQueueImpl(source))); | |
| } | |
| WGPUShaderModule emwgpuCreateShaderModule(const EventSource* source) { | |
| return ReturnToAPI(AcquireRef(new WGPUShaderModuleImpl(source))); | |
| } | |
| // Future event callbacks. | |
| void emwgpuOnCompilationInfoCompleted(double futureId, | |
| WGPUCompilationInfoRequestStatus status, | |
| WGPUCompilationInfo* compilationInfo) { | |
| GetEventManager().SetFutureReady<CompilationInfoEvent>(futureId, status, | |
| compilationInfo); | |
| } | |
| void emwgpuOnCreateComputePipelineCompleted( | |
| double futureId, | |
| WGPUCreatePipelineAsyncStatus status, | |
| WGPUComputePipeline pipeline, | |
| const char* message) { | |
| assert(pipeline); | |
| if (status != WGPUCreatePipelineAsyncStatus_Success) { | |
| delete pipeline; | |
| pipeline = nullptr; | |
| } | |
| GetEventManager().SetFutureReady<CreateComputePipelineEvent>( | |
| futureId, status, pipeline, message); | |
| } | |
| void emwgpuOnCreateRenderPipelineCompleted(double futureId, | |
| WGPUCreatePipelineAsyncStatus status, | |
| WGPURenderPipeline pipeline, | |
| const char* message) { | |
| assert(pipeline); | |
| if (status != WGPUCreatePipelineAsyncStatus_Success) { | |
| delete pipeline; | |
| pipeline = nullptr; | |
| } | |
| GetEventManager().SetFutureReady<CreateRenderPipelineEvent>( | |
| futureId, status, pipeline, message); | |
| } | |
| void emwgpuOnDeviceLostCompleted(double futureId, | |
| WGPUDeviceLostReason reason, | |
| const char* message) { | |
| GetEventManager().SetFutureReady<DeviceLostEvent>( | |
| futureId, DropDeviceFromDeviceLostEvent::No, reason, message); | |
| } | |
| void emwgpuOnMapAsyncCompleted(double futureId, | |
| WGPUMapAsyncStatus status, | |
| const char* message) { | |
| GetEventManager().SetFutureReady<MapAsyncEvent>(futureId, status, message); | |
| } | |
| void emwgpuOnPopErrorScopeCompleted(double futureId, | |
| WGPUPopErrorScopeStatus status, | |
| WGPUErrorType errorType, | |
| const char* message) { | |
| GetEventManager().SetFutureReady<PopErrorScopeEvent>(futureId, status, | |
| errorType, message); | |
| } | |
| void emwgpuOnRequestAdapterCompleted(double futureId, | |
| WGPURequestAdapterStatus status, | |
| WGPUAdapter adapter, | |
| const char* message) { | |
| assert(adapter); | |
| if (status != WGPURequestAdapterStatus_Success) { | |
| delete adapter; | |
| adapter = nullptr; | |
| } | |
| GetEventManager().SetFutureReady<RequestAdapterEvent>(futureId, status, | |
| adapter, message); | |
| } | |
| void emwgpuOnRequestDeviceCompleted(double 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. | |
| GetEventManager().SetFutureReady<RequestDeviceEvent>(futureId, status, | |
| nullptr, message); | |
| GetEventManager().SetFutureReady<DeviceLostEvent>( | |
| device->GetLostFuture().id, DropDeviceFromDeviceLostEvent::Yes, | |
| WGPUDeviceLostReason_FailedCreation, "Device creation failed."); | |
| // Free the device now that there should be no pointers to it. | |
| [[maybe_unused]] bool deviceFreed = device->Release(); | |
| assert(deviceFreed); | |
| } | |
| } | |
| void emwgpuOnWorkDoneCompleted(double 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}; | |
| } | |
| } | |
| WGPUBufferImpl::WGPUBufferImpl(const EventSource* source, ImportedFromJSTag tag) | |
| : EventSource(source), | |
| RefCountedWithExternalCount(tag), | |
| mMapState(WGPUBufferMapState_Unmapped) {} | |
| 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) { | |
| DEBUG_PRINTF( | |
| "GetMappedRange: Mapping is read-only. Use GetConstMappedRange " | |
| "instead.\n"); | |
| return nullptr; | |
| } | |
| return emwgpuBufferGetMappedRange(this, offset, size); | |
| } | |
| WGPUStatus WGPUBufferImpl::WriteMappedRange(size_t offset, | |
| void const* data, | |
| size_t size) { | |
| if (mMapState != WGPUBufferMapState_Mapped) { | |
| return WGPUStatus_Error; | |
| } | |
| if (mPendingMapRequest.mode != WGPUMapMode_Write) { | |
| DEBUG_PRINTF( | |
| "GetMappedRange: Mapping is read-only. Use GetConstMappedRange " | |
| "instead.\n"); | |
| return WGPUStatus_Error; | |
| } | |
| return emwgpuBufferWriteMappedRange(this, offset, data, size); | |
| } | |
| WGPUStatus WGPUBufferImpl::ReadMappedRange(size_t offset, | |
| void* data, | |
| size_t size) { | |
| if (mMapState != WGPUBufferMapState_Mapped) { | |
| return WGPUStatus_Error; | |
| } | |
| return emwgpuBufferReadMappedRange(this, offset, data, size); | |
| } | |
| WGPUFuture WGPUBufferImpl::MapAsync(WGPUMapMode mode, | |
| size_t offset, | |
| size_t size, | |
| WGPUBufferMapCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = GetEventManager().TrackEvent( | |
| std::make_unique<MapAsyncEvent>(GetInstanceId(), this, callbackInfo)); | |
| 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; | |
| mPendingMapRequest = {}; | |
| if (futureId == kNullFutureId) { | |
| // If we were mappedAtCreation, then there is no pending map request so we | |
| // don't need to resolve any futures. | |
| return; | |
| } | |
| 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.uncapturedErrorCallbackInfo) { | |
| // Create the DeviceLostEvent now. | |
| mDeviceLostFutureId = | |
| GetEventManager().TrackEvent(std::make_unique<DeviceLostEvent>( | |
| source->GetInstanceId(), this, descriptor.deviceLostCallbackInfo)); | |
| mQueue.Acquire(queue); | |
| } | |
| WGPUDeviceImpl::WGPUDeviceImpl(const EventSource* source, WGPUQueue queue) | |
| : EventSource(source), RefCountedWithExternalCount(kImportedFromJS) { | |
| mQueue.Acquire(queue); | |
| } | |
| void WGPUDeviceImpl::Destroy(DropDeviceFromDeviceLostEvent dropDevice) { | |
| // Ready the DeviceLostEvent now. dropDevice=Yes ensures we can't later | |
| // try to pass an invalid device pointer to the callback. | |
| GetEventManager().SetFutureReady<DeviceLostEvent>( | |
| mDeviceLostFutureId, dropDevice, WGPUDeviceLostReason_Destroyed, | |
| "Device was destroyed."); | |
| emwgpuDeviceDestroy(this); | |
| } | |
| WGPUQueue WGPUDeviceImpl::GetQueue() const { | |
| auto queue = mQueue; | |
| return ReturnToAPI(std::move(queue)); | |
| } | |
| WGPUFuture WGPUDeviceImpl::GetLostFuture() const { | |
| if (IsImported()) { | |
| DEBUG_PRINTF("GetLostFuture cannot be called on an imported device.\n"); | |
| assert(false); | |
| } | |
| assert(mDeviceLostFutureId != kNullFutureId); | |
| return WGPUFuture{mDeviceLostFutureId}; | |
| } | |
| 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() { | |
| if (IsImported()) { | |
| // Note Destroy is responsible for readying the DeviceLostEvent. If the | |
| // device was imported, that doesn't happen, which is fine because no | |
| // callback can have been set. | |
| assert(mDeviceLostFutureId == kNullFutureId); | |
| } else { | |
| Destroy(DropDeviceFromDeviceLostEvent::Yes); | |
| } | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // WGPUInstanceImpl implementations. | |
| // ---------------------------------------------------------------------------- | |
| Ref<WGPUInstanceImpl> WGPUInstanceImpl::Create( | |
| const WGPUInstanceDescriptor* desc) { | |
| auto instance = AcquireRef(new WGPUInstanceImpl()); | |
| // Validate and apply the descriptor | |
| if (desc) { | |
| // Features | |
| auto features = | |
| std::span(desc->requiredFeatures, desc->requiredFeatureCount); | |
| for (auto feature : features) { | |
| switch (feature) { | |
| case WGPUInstanceFeatureName_TimedWaitAny: | |
| if (!emscripten_has_asyncify()) { | |
| DEBUG_PRINTF( | |
| "timedWaitAnyEnable requested, but requires Asyncify or JSPI " | |
| "which is not available.\n"); | |
| return {}; | |
| } | |
| if (desc->requiredLimits) { | |
| if (desc->requiredLimits->timedWaitAnyMaxCount > | |
| kTimedWaitAnyMaxCountMax) { | |
| DEBUG_PRINTF("timedWaitAnyMaxCount %zu not supported (max %zu)\n", | |
| desc->requiredLimits->timedWaitAnyMaxCount, | |
| kTimedWaitAnyMaxCountMax); | |
| return {}; | |
| } | |
| instance->mTimedWaitAnyMaxCount = | |
| desc->requiredLimits->timedWaitAnyMaxCount; | |
| } | |
| if (instance->mTimedWaitAnyMaxCount < kTimedWaitAnyMaxCountDefault) { | |
| instance->mTimedWaitAnyMaxCount = kTimedWaitAnyMaxCountDefault; | |
| } | |
| continue; | |
| case WGPUInstanceFeatureName_MultipleDevicesPerAdapter: | |
| DEBUG_PRINTF( | |
| "MultipleDevicesPerAdapter requested, but not supported in " | |
| "Wasm.\n"); | |
| return {}; | |
| case WGPUInstanceFeatureName_ShaderSourceSPIRV: | |
| DEBUG_PRINTF( | |
| "ShaderSourceSPIRV requested, but not supported in Wasm.\n"); | |
| return {}; | |
| case WGPUInstanceFeatureName_Force32: | |
| return {}; | |
| } | |
| // Reject any unrecognized feature name. | |
| return {}; | |
| } | |
| // Limits | |
| if (desc->requiredLimits && desc->requiredLimits->nextInChain != nullptr) { | |
| DEBUG_PRINTF("No WGPULimits extensions are supported.\n"); | |
| return {}; | |
| } | |
| } | |
| return instance; | |
| } | |
| 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) { | |
| if (timeoutNS > 0) { | |
| if (mTimedWaitAnyMaxCount == 0) { | |
| // Timed wait not valid unless enabled on the instance. | |
| DEBUG_PRINTF( | |
| "WaitAny timeoutNS (%" PRIu64 | |
| ") > 0, but TimedWaitAny not enabled at wgpuCreateInstance\n", | |
| timeoutNS); | |
| return WGPUWaitStatus_Error; | |
| } | |
| // Cannot handle timeouts without Asyncify. Assert should never fail, | |
| // because wgpuCreateInstance should disallow timedWaitAnyEnable. | |
| // TODO(crbug.com/377760848): Use the preprocessor to remove all this code | |
| // (and emwgpuWaitAny) when building without Asyncify. | |
| assert(emscripten_has_asyncify()); | |
| if (count > mTimedWaitAnyMaxCount) { | |
| DEBUG_PRINTF( | |
| "WaitAny count (%zu) > the timedWaitAnyMaxCount (%zu) which was " | |
| "enabled at wgpuCreateInstance\n", | |
| count, mTimedWaitAnyMaxCount); | |
| return WGPUWaitStatus_Error; | |
| } | |
| } | |
| 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( | |
| WGPUCompilationInfoCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<CompilationInfoEvent>( | |
| GetInstanceId(), this, callbackInfo)); | |
| // If we already have the compilation info cached, we don't need to call into | |
| // JS. | |
| if (mCompilationInfo) { | |
| emwgpuOnCompilationInfoCompleted(double(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; \ | |
| emwgpuDelete(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) | |
| // ---------------------------------------------------------------------------- | |
| // FreeMember 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)); | |
| } | |
| void wgpuAdapterPropertiesSubgroupMatrixConfigsFreeMembers( | |
| WGPUAdapterPropertiesSubgroupMatrixConfigs ext) { | |
| free(const_cast<WGPUSubgroupMatrixConfig*>(ext.configs)); | |
| } | |
| void wgpuSupportedFeaturesFreeMembers(WGPUSupportedFeatures value) { | |
| free(const_cast<WGPUFeatureName*>(value.features)); | |
| } | |
| void wgpuSupportedWGSLLanguageFeaturesFreeMembers( | |
| WGPUSupportedWGSLLanguageFeatures value) { | |
| free(const_cast<WGPUWGSLLanguageFeatureName*>(value.features)); | |
| } | |
| void wgpuSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities) { | |
| // wgpuSurfaceCapabilities doesn't currently allocate anything. | |
| } | |
| void wgpuSupportedInstanceFeaturesFreeMembers(WGPUSupportedInstanceFeatures) { | |
| // Nothing to do, .features is statically allocated. | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // Standalone (non-method) functions | |
| // ---------------------------------------------------------------------------- | |
| namespace { | |
| const std::span<WGPUInstanceFeatureName> GetSupportedFeatures() { | |
| static std::optional<std::vector<WGPUInstanceFeatureName>> sSupportedFeatures; | |
| if (!sSupportedFeatures) { | |
| sSupportedFeatures = std::vector<WGPUInstanceFeatureName>(); | |
| if (emscripten_has_asyncify()) { | |
| sSupportedFeatures->push_back(WGPUInstanceFeatureName_TimedWaitAny); | |
| } | |
| } | |
| return *sSupportedFeatures; | |
| } | |
| } // namespace | |
| WGPUBool wgpuHasInstanceFeature(WGPUInstanceFeatureName feature) { | |
| auto supportedFeatures = GetSupportedFeatures(); | |
| return std::find(supportedFeatures.begin(), supportedFeatures.end(), | |
| feature) != supportedFeatures.end(); | |
| } | |
| void wgpuGetInstanceFeatures(WGPUSupportedInstanceFeatures* features) { | |
| assert(features != nullptr); | |
| auto supportedFeatures = GetSupportedFeatures(); | |
| features->featureCount = supportedFeatures.size(); | |
| features->features = supportedFeatures.data(); | |
| } | |
| WGPUStatus wgpuGetInstanceLimits(WGPUInstanceLimits* limits) { | |
| assert(limits != nullptr); | |
| if (limits->nextInChain != nullptr) { | |
| DEBUG_PRINTF("No WGPULimits extensions are supported.\n"); | |
| return WGPUStatus_Error; | |
| } | |
| limits->timedWaitAnyMaxCount = kTimedWaitAnyMaxCountMax; | |
| return WGPUStatus_Success; | |
| } | |
| WGPUInstance wgpuCreateInstance( | |
| [[maybe_unused]] const WGPUInstanceDescriptor* descriptor) { | |
| return ReturnToAPI(WGPUInstanceImpl::Create(descriptor)); | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // Methods of Adapter | |
| // ---------------------------------------------------------------------------- | |
| WGPUFuture wgpuAdapterRequestDevice( | |
| WGPUAdapter adapter, | |
| const WGPUDeviceDescriptor* descriptor, | |
| WGPURequestDeviceCallbackInfo callbackInfo) { | |
| static const WGPUDeviceDescriptor kDefaultDescriptor = | |
| WGPU_DEVICE_DESCRIPTOR_INIT; | |
| if (descriptor == nullptr) { | |
| descriptor = &kDefaultDescriptor; | |
| } | |
| if (!ValidateCallbackMode(callbackInfo) || | |
| !ValidateCallbackMode(descriptor->deviceLostCallbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<RequestDeviceEvent>( | |
| adapter->GetInstanceId(), callbackInfo)); | |
| // 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->GetLostFuture().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); | |
| } | |
| WGPUStatus wgpuBufferWriteMappedRange(WGPUBuffer buffer, | |
| size_t offset, | |
| void const* data, | |
| size_t size) { | |
| return buffer->WriteMappedRange(offset, data, size); | |
| } | |
| WGPUStatus wgpuBufferReadMappedRange(WGPUBuffer buffer, | |
| size_t offset, | |
| void* data, | |
| size_t size) { | |
| return buffer->ReadMappedRange(offset, data, size); | |
| } | |
| WGPUFuture wgpuBufferMapAsync(WGPUBuffer buffer, | |
| WGPUMapMode mode, | |
| size_t offset, | |
| size_t size, | |
| WGPUBufferMapCallbackInfo 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) { | |
| Ref<WGPUBufferImpl> buffer = | |
| AcquireRef(new WGPUBufferImpl(device, descriptor->mappedAtCreation)); | |
| if (!emwgpuDeviceCreateBuffer(device, descriptor, buffer.Get())) { | |
| return nullptr; | |
| } | |
| return ReturnToAPI(std::move(buffer)); | |
| } | |
| WGPUFuture wgpuDeviceCreateComputePipelineAsync( | |
| WGPUDevice device, | |
| const WGPUComputePipelineDescriptor* descriptor, | |
| WGPUCreateComputePipelineAsyncCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<CreateComputePipelineEvent>( | |
| device->GetInstanceId(), callbackInfo)); | |
| WGPUComputePipeline pipeline = emwgpuCreateComputePipeline(device); | |
| emwgpuDeviceCreateComputePipelineAsync(device, futureId, descriptor, | |
| pipeline); | |
| return WGPUFuture{futureId}; | |
| } | |
| WGPUFuture wgpuDeviceCreateRenderPipelineAsync( | |
| WGPUDevice device, | |
| const WGPURenderPipelineDescriptor* descriptor, | |
| WGPUCreateRenderPipelineAsyncCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<CreateRenderPipelineEvent>( | |
| device->GetInstanceId(), callbackInfo)); | |
| WGPURenderPipeline pipeline = emwgpuCreateRenderPipeline(device); | |
| emwgpuDeviceCreateRenderPipelineAsync(device, futureId, descriptor, pipeline); | |
| 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(DropDeviceFromDeviceLostEvent::No); | |
| } | |
| WGPUFuture wgpuDeviceGetLostFuture(WGPUDevice device) { | |
| return device->GetLostFuture(); | |
| } | |
| WGPUQueue wgpuDeviceGetQueue(WGPUDevice device) { | |
| return device->GetQueue(); | |
| } | |
| WGPUFuture wgpuDevicePopErrorScope(WGPUDevice device, | |
| WGPUPopErrorScopeCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<PopErrorScopeEvent>( | |
| device->GetInstanceId(), callbackInfo)); | |
| emwgpuDevicePopErrorScope(device, futureId); | |
| return WGPUFuture{futureId}; | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // Methods of Instance | |
| // ---------------------------------------------------------------------------- | |
| void wgpuInstanceProcessEvents(WGPUInstance instance) { | |
| instance->ProcessEvents(); | |
| } | |
| WGPUFuture wgpuInstanceRequestAdapter( | |
| WGPUInstance instance, | |
| WGPURequestAdapterOptions const* options, | |
| WGPURequestAdapterCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = | |
| GetEventManager().TrackEvent(std::make_unique<RequestAdapterEvent>( | |
| instance->GetInstanceId(), callbackInfo)); | |
| WGPUAdapter adapter = emwgpuCreateAdapter(instance); | |
| emwgpuInstanceRequestAdapter(instance, futureId, options, adapter); | |
| 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 wgpuQueueOnSubmittedWorkDone( | |
| WGPUQueue queue, | |
| WGPUQueueWorkDoneCallbackInfo callbackInfo) { | |
| if (!ValidateCallbackMode(callbackInfo)) { | |
| return WGPUFuture{kNullFutureId}; | |
| } | |
| FutureID futureId = GetEventManager().TrackEvent( | |
| std::make_unique<WorkDoneEvent>(queue->GetInstanceId(), callbackInfo)); | |
| 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 wgpuShaderModuleGetCompilationInfo( | |
| WGPUShaderModule shader, | |
| WGPUCompilationInfoCallbackInfo callbackInfo) { | |
| return shader->GetCompilationInfo(callbackInfo); | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // Methods of Surface | |
| // ---------------------------------------------------------------------------- | |
| WGPUStatus wgpuSurfaceGetCapabilities(WGPUSurface, | |
| WGPUAdapter, | |
| 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 | |
| // ---------------------------------------------------------------------------- |