/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebGPUChild.h" #include #include "Adapter.h" #include "CompilationInfo.h" #include "DeviceLostInfo.h" #include "PipelineLayout.h" #include "Sampler.h" #include "Utility.h" #include "js/RootingAPI.h" #include "js/String.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "js/Warnings.h" // JS::WarnUTF8 #include "mozilla/Assertions.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/dom/GPUUncapturedErrorEvent.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/WebGPUBinding.h" #include "mozilla/webgpu/ComputePipeline.h" #include "mozilla/webgpu/InternalError.h" #include "mozilla/webgpu/OutOfMemoryError.h" #include "mozilla/webgpu/PipelineError.h" #include "mozilla/webgpu/PromiseHelpers.h" #include "mozilla/webgpu/RenderPipeline.h" #include "mozilla/webgpu/ValidationError.h" #include "mozilla/webgpu/WebGPUTypes.h" #include "mozilla/webgpu/ffi/wgpu.h" namespace mozilla::webgpu { void WebGPUChild::JsWarning(nsIGlobalObject* aGlobal, const nsACString& aMessage) { const auto& flatString = PromiseFlatCString(aMessage); if (aGlobal) { dom::AutoJSAPI api; if (api.Init(aGlobal)) { JS::WarnUTF8(api.cx(), "Uncaptured WebGPU error: %s", flatString.get()); } } else { printf_stderr("Uncaptured WebGPU error without device target: %s\n", flatString.get()); } } void on_message_queued(ffi::WGPUWebGPUChildPtr child) { auto* c = static_cast(child); c->ScheduleFlushQueuedMessages(); } WebGPUChild::WebGPUChild() : mClient(ffi::wgpu_client_new(this, on_message_queued)) {} WebGPUChild::~WebGPUChild() = default; RawId WebGPUChild::RenderBundleEncoderFinish( ffi::WGPURenderBundleEncoder& aEncoder, RawId aDeviceId, const dom::GPURenderBundleDescriptor& aDesc) { ffi::WGPURenderBundleDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); RawId id = ffi::wgpu_client_create_render_bundle(GetClient(), aDeviceId, &aEncoder, &desc); return id; } RawId WebGPUChild::RenderBundleEncoderFinishError(RawId aDeviceId, const nsString& aLabel) { webgpu::StringHelper label(aLabel); RawId id = ffi::wgpu_client_create_render_bundle_error(GetClient(), aDeviceId, label.Get()); return id; } namespace ffi { void wgpu_child_send_messages(WGPUWebGPUChildPtr aChild, uint32_t aNrOfMessages, struct WGPUByteBuf aSerializedMessages) { auto* c = static_cast(aChild); auto messages = ipc::ByteBuf(aSerializedMessages.data, aSerializedMessages.len, aSerializedMessages.capacity); c->SendSerializedMessages(aNrOfMessages, std::move(messages)); } void wgpu_child_resolve_request_adapter_promise( WGPUWebGPUChildPtr aChild, RawId aAdapterId, const struct WGPUAdapterInformation* aAdapterInfo) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueRequestAdapterPromise(); MOZ_RELEASE_ASSERT(pending_promise.adapter_id == aAdapterId); if (aAdapterInfo == nullptr) { promise::MaybeResolveWithNull(std::move(pending_promise.promise)); } else { auto info = std::make_shared(*aAdapterInfo); RefPtr adapter = new Adapter(pending_promise.instance, c, info); promise::MaybeResolve(std::move(pending_promise.promise), std::move(adapter)); } } void wgpu_child_resolve_request_device_promise(WGPUWebGPUChildPtr aChild, RawId aDeviceId, RawId aQueueId, const nsCString* aError) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueRequestDevicePromise(); MOZ_RELEASE_ASSERT(pending_promise.device_id == aDeviceId); MOZ_RELEASE_ASSERT(pending_promise.queue_id == aQueueId); if (aError == nullptr) { RefPtr device = new Device(pending_promise.adapter, pending_promise.device_id, pending_promise.queue_id, pending_promise.features, pending_promise.limits, pending_promise.adapter_info, pending_promise.lost_promise); device->SetLabel(pending_promise.label); promise::MaybeResolve(std::move(pending_promise.promise), std::move(device)); } else { promise::MaybeRejectWithOperationError(std::move(pending_promise.promise), nsCString(*aError)); } } void wgpu_child_resolve_pop_error_scope_promise(WGPUWebGPUChildPtr aChild, RawId aDeviceId, uint8_t aTy, const nsCString* aMessage) { auto* c = static_cast(aChild); auto pending_promise = c->DequeuePopErrorScopePromise(); MOZ_RELEASE_ASSERT(pending_promise.device->GetId() == aDeviceId); RefPtr error; switch ((PopErrorScopeResultType)aTy) { case PopErrorScopeResultType::NoError: promise::MaybeResolveWithNull(std::move(pending_promise.promise)); return; case PopErrorScopeResultType::DeviceLost: promise::MaybeResolveWithNull(std::move(pending_promise.promise)); return; case PopErrorScopeResultType::ThrowOperationError: promise::MaybeRejectWithOperationError(std::move(pending_promise.promise), nsCString(*aMessage)); return; case PopErrorScopeResultType::OutOfMemory: error = new OutOfMemoryError(pending_promise.device->GetParentObject(), *aMessage); break; case PopErrorScopeResultType::ValidationError: error = new ValidationError(pending_promise.device->GetParentObject(), *aMessage); break; case PopErrorScopeResultType::InternalError: error = new InternalError(pending_promise.device->GetParentObject(), *aMessage); break; } promise::MaybeResolve(std::move(pending_promise.promise), std::move(error)); } void wgpu_child_resolve_create_pipeline_promise(WGPUWebGPUChildPtr aChild, RawId aPipelineId, bool aIsRenderPipeline, bool aIsValidationError, const nsCString* aError) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueCreatePipelinePromise(); MOZ_RELEASE_ASSERT(pending_promise.pipeline_id == aPipelineId); MOZ_RELEASE_ASSERT(pending_promise.is_render_pipeline == aIsRenderPipeline); if (aError == nullptr) { if (pending_promise.is_render_pipeline) { RefPtr object = new RenderPipeline( pending_promise.device, pending_promise.pipeline_id); object->SetLabel(pending_promise.label); promise::MaybeResolve(std::move(pending_promise.promise), std::move(object)); } else { RefPtr object = new ComputePipeline( pending_promise.device, pending_promise.pipeline_id); object->SetLabel(pending_promise.label); promise::MaybeResolve(std::move(pending_promise.promise), std::move(object)); } } else { dom::GPUPipelineErrorReason reason; if (aIsValidationError) { reason = dom::GPUPipelineErrorReason::Validation; } else { reason = dom::GPUPipelineErrorReason::Internal; } RefPtr e = new PipelineError(*aError, reason); promise::MaybeRejectWithPipelineError(std::move(pending_promise.promise), std::move(e)); } } void wgpu_child_resolve_create_shader_module_promise( WGPUWebGPUChildPtr aChild, RawId aShaderModuleId, struct WGPUFfiSlice_FfiShaderModuleCompilationMessage aMessages) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueCreateShaderModulePromise(); MOZ_RELEASE_ASSERT(pending_promise.shader_module->GetId() == aShaderModuleId); auto ffi_messages = Span(aMessages.data, aMessages.length); auto messages = nsTArray(aMessages.length); for (const auto& message : ffi_messages) { WebGPUCompilationMessage msg; msg.lineNum = message.line_number; msg.linePos = message.line_pos; msg.offset = message.utf16_offset; msg.length = message.utf16_length; msg.message = message.message; // wgpu currently only returns errors. msg.messageType = WebGPUCompilationMessageType::Error; messages.AppendElement(std::move(msg)); } if (!messages.IsEmpty()) { auto shader_module = pending_promise.shader_module; reportCompilationMessagesToConsole(shader_module, std::cref(messages)); } RefPtr infoObject( new CompilationInfo(pending_promise.device)); infoObject->SetMessages(messages); promise::MaybeResolve(std::move(pending_promise.promise), std::move(infoObject)); }; void wgpu_child_resolve_buffer_map_promise(WGPUWebGPUChildPtr aChild, WGPUBufferId aBufferId, bool aIsWritable, uint64_t aOffset, uint64_t aSize, const nsCString* aError) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueBufferMapPromise(aBufferId); if (aError == nullptr) { pending_promise.buffer->ResolveMapRequest(pending_promise.promise, aOffset, aSize, aIsWritable); } else { pending_promise.buffer->RejectMapRequest(pending_promise.promise, *aError); } } void wgpu_child_resolve_on_submitted_work_done_promise( WGPUWebGPUChildPtr aChild, WGPUQueueId aQueueId) { auto* c = static_cast(aChild); auto pending_promise = c->DequeueOnSubmittedWorkDonePromise(aQueueId); promise::MaybeResolveWithUndefined(std::move(pending_promise)); }; } // namespace ffi ipc::IPCResult WebGPUChild::RecvServerMessage(const ipc::ByteBuf& aByteBuf) { ffi::wgpu_client_receive_server_message(GetClient(), ToFFI(&aByteBuf)); return IPC_OK(); } void WebGPUChild::ScheduleFlushQueuedMessages() { if (mScheduledFlushQueuedMessages) { return; } mScheduledFlushQueuedMessages = true; nsContentUtils::RunInStableState( NewRunnableMethod("dom::WebGPUChild::ScheduledFlushQueuedMessages", this, &WebGPUChild::ScheduledFlushQueuedMessages)); } size_t WebGPUChild::QueueDataBuffer(ipc::ByteBuf&& bb) { auto buffer_index = mQueuedDataBuffers.Length(); mQueuedDataBuffers.AppendElement(std::move(bb)); return buffer_index; } size_t WebGPUChild::QueueShmemHandle(ipc::MutableSharedMemoryHandle&& handle) { auto shmem_handle_index = mQueuedHandles.Length(); mQueuedHandles.AppendElement(std::move(handle)); return shmem_handle_index; } void WebGPUChild::ScheduledFlushQueuedMessages() { MOZ_ASSERT(mScheduledFlushQueuedMessages); mScheduledFlushQueuedMessages = false; PROFILER_MARKER_UNTYPED("WebGPU: ScheduledFlushQueuedMessages", GRAPHICS_WebGPU); FlushQueuedMessages(); } void WebGPUChild::FlushQueuedMessages() { ipc::ByteBuf serialized_messages; auto nr_of_messages = ffi::wgpu_client_get_queued_messages( GetClient(), ToFFI(&serialized_messages)); if (nr_of_messages == 0) { return; } SendSerializedMessages(nr_of_messages, std::move(serialized_messages)); } void WebGPUChild::SendSerializedMessages(uint32_t aNrOfMessages, ipc::ByteBuf aSerializedMessages) { PROFILER_MARKER_FMT("WebGPU: SendSerializedMessages", GRAPHICS_WebGPU, {}, "messages: {}", aNrOfMessages); bool sent = SendMessages(aNrOfMessages, std::move(aSerializedMessages), std::move(mQueuedDataBuffers), std::move(mQueuedHandles)); mQueuedDataBuffers.Clear(); mQueuedHandles.Clear(); if (!sent) { ClearActorState(); } } ipc::IPCResult WebGPUChild::RecvUncapturedError(RawId aDeviceId, const dom::GPUErrorFilter aType, const nsACString& aMessage) { MOZ_RELEASE_ASSERT(aDeviceId); RefPtr device; const auto itr = mDeviceMap.find(aDeviceId); if (itr != mDeviceMap.end()) { device = itr->second.get(); } if (!device) { return IPC_OK(); } // We don't want to spam the errors to the console indefinitely if (device->CheckNewWarning(aMessage)) { JsWarning(device->GetOwnerGlobal(), aMessage); dom::GPUUncapturedErrorEventInit init; switch (aType) { case dom::GPUErrorFilter::Validation: init.mError = new ValidationError(device->GetParentObject(), aMessage); break; case dom::GPUErrorFilter::Out_of_memory: init.mError = new OutOfMemoryError(device->GetParentObject(), aMessage); break; case dom::GPUErrorFilter::Internal: init.mError = new InternalError(device->GetParentObject(), aMessage); break; } RefPtr event = dom::GPUUncapturedErrorEvent::Constructor(device, u"uncapturederror"_ns, init); device->DispatchEvent(*event); } return IPC_OK(); } ipc::IPCResult WebGPUChild::RecvDeviceLost(RawId aDeviceId, uint8_t aReason, const nsACString& aMessage) { // There might have been a race between getting back the response to a // `device.destroy()` call and actual device loss. If that was the case, // set the lost reason to "destroyed". auto device_lost_promise_entry = mPendingDeviceLostPromises.extract(aDeviceId); if (!device_lost_promise_entry.empty()) { auto promise = std::move(device_lost_promise_entry.mapped()); RefPtr info = new DeviceLostInfo( promise->GetParentObject(), dom::GPUDeviceLostReason::Destroyed, u"Device destroyed"_ns); promise::MaybeResolve(std::move(promise), std::move(info)); } else { auto message = NS_ConvertUTF8toUTF16(aMessage); const auto itr = mDeviceMap.find(aDeviceId); if (itr != mDeviceMap.end()) { RefPtr device = itr->second.get(); if (!device) { return IPC_OK(); } dom::GPUDeviceLostReason reason = static_cast(aReason); device->ResolveLost(reason, message); } } return IPC_OK(); } void WebGPUChild::SwapChainPresent(RawId aTextureId, const RemoteTextureId& aRemoteTextureId, const RemoteTextureOwnerId& aOwnerId) { // The parent side needs to create a command encoder which will be submitted // and dropped right away so we create and release an encoder ID here. RawId commandEncoderId = ffi::wgpu_client_make_command_encoder_id(GetClient()); RawId commandBufferId = ffi::wgpu_client_make_command_buffer_id(GetClient()); ffi::wgpu_client_swap_chain_present(GetClient(), aTextureId, commandEncoderId, commandBufferId, aRemoteTextureId.mId, aOwnerId.mId); ffi::wgpu_client_free_command_encoder_id(GetClient(), commandEncoderId); ffi::wgpu_client_free_command_buffer_id(GetClient(), commandBufferId); } void WebGPUChild::RegisterDevice(Device* const aDevice) { mDeviceMap.insert({aDevice->GetId(), aDevice}); } void WebGPUChild::UnregisterDevice(RawId aDeviceId) { mDeviceMap.erase(aDeviceId); } void WebGPUChild::EnqueueRequestAdapterPromise( PendingRequestAdapterPromise&& promise) { mPendingRequestAdapterPromises.push_back(std::move(promise)); } void WebGPUChild::EnqueueRequestDevicePromise( PendingRequestDevicePromise&& promise) { mPendingRequestDevicePromises.push_back(std::move(promise)); } void WebGPUChild::RegisterDeviceLostPromise(RawId id, RefPtr& promise) { mPendingDeviceLostPromises.insert({id, promise}); } void WebGPUChild::EnqueuePopErrorScopePromise( PendingPopErrorScopePromise&& promise) { mPendingPopErrorScopePromises.push_back(std::move(promise)); } void WebGPUChild::EnqueueCreatePipelinePromise( PendingCreatePipelinePromise&& promise) { mPendingCreatePipelinePromises.push_back(std::move(promise)); } void WebGPUChild::EnqueueCreateShaderModulePromise( PendingCreateShaderModulePromise&& promise) { mPendingCreateShaderModulePromises.push_back(std::move(promise)); } void WebGPUChild::EnqueueBufferMapPromise(RawId id, PendingBufferMapPromise&& promise) { mPendingBufferMapPromises[id].push_back(std::move(promise)); } void WebGPUChild::EnqueueOnSubmittedWorkDonePromise( RawId id, RefPtr& promise) { mPendingOnSubmittedWorkDonePromises[id].push_back(promise); } PendingRequestAdapterPromise WebGPUChild::DequeueRequestAdapterPromise() { auto& promises = mPendingRequestAdapterPromises; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); return promise; } PendingRequestDevicePromise WebGPUChild::DequeueRequestDevicePromise() { auto& promises = mPendingRequestDevicePromises; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); return promise; } PendingPopErrorScopePromise WebGPUChild::DequeuePopErrorScopePromise() { auto& promises = mPendingPopErrorScopePromises; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); return promise; } PendingCreatePipelinePromise WebGPUChild::DequeueCreatePipelinePromise() { auto& promises = mPendingCreatePipelinePromises; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); return promise; } PendingCreateShaderModulePromise WebGPUChild::DequeueCreateShaderModulePromise() { auto& promises = mPendingCreateShaderModulePromises; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); return promise; } PendingBufferMapPromise WebGPUChild::DequeueBufferMapPromise(RawId id) { auto& promises_map = mPendingBufferMapPromises; const auto& it = promises_map.find(id); MOZ_RELEASE_ASSERT(it != promises_map.end()); auto& promises = it->second; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); if (promises.empty()) { promises_map.erase(it); } return promise; } RefPtr WebGPUChild::DequeueOnSubmittedWorkDonePromise(RawId id) { auto& promises_map = mPendingOnSubmittedWorkDonePromises; const auto& it = promises_map.find(id); MOZ_RELEASE_ASSERT(it != promises_map.end()); auto& promises = it->second; MOZ_RELEASE_ASSERT(!promises.empty()); auto promise = std::move(promises.front()); promises.pop_front(); if (promises.empty()) { promises_map.erase(it); } return promise; } void WebGPUChild::ActorDestroy(ActorDestroyReason) { ClearActorState(); } void WebGPUChild::ClearActorState() { // The following code sections dispatch runnables that resolve/reject // promises. // // There are a few reasons why we defer promise resolution: // // - this function can be indirectly called by `ScheduledFlushQueuedMessages` // which is a runnable that should not enter the JS runtime; see docs of // `nsContentUtils::RunInStableState`, // - if the thread is shutting down we can no longer resolve the promises; // `NS_DispatchToCurrentThread` will return an error in this case that we // ignore, // - it avoids reentrancy issues (ex: invalidating the iterators below). // Resolve the promise with null since the WebGPUChild has been destroyed. { for (auto& pending_promise : mPendingRequestAdapterPromises) { promise::MaybeResolveWithNull(std::move(pending_promise.promise)); } mPendingRequestAdapterPromises.clear(); } // Pretend this worked but return a lost device, per spec. { for (auto& pending_promise : mPendingRequestDevicePromises) { RefPtr device = new Device(pending_promise.adapter, pending_promise.device_id, pending_promise.queue_id, pending_promise.features, pending_promise.limits, pending_promise.adapter_info, pending_promise.lost_promise); device->SetLabel(pending_promise.label); device->ResolveLost(dom::GPUDeviceLostReason::Unknown, u"WebGPUChild destroyed"_ns); promise::MaybeResolve(std::move(pending_promise.promise), std::move(device)); } mPendingRequestDevicePromises.clear(); } // Resolve all promises that were pending due to `device.destroy()` being // called. { for (auto& pending_promise_entry : mPendingDeviceLostPromises) { auto pending_promise = std::move(pending_promise_entry.second); RefPtr info = new DeviceLostInfo( pending_promise->GetParentObject(), dom::GPUDeviceLostReason::Destroyed, u"Device destroyed"_ns); promise::MaybeResolve(std::move(pending_promise), std::move(info)); } mPendingDeviceLostPromises.clear(); } // Empty device map and resolve all lost promises with an "unknown" reason. { for (auto& device_map_entry : mDeviceMap) { RefPtr device = device_map_entry.second.get(); if (device) { device->ResolveLost(dom::GPUDeviceLostReason::Unknown, u"WebGPUChild destroyed"_ns); } } mDeviceMap.clear(); } // Pretend this worked and there is no error, per spec. { for (auto& pending_promise : mPendingPopErrorScopePromises) { promise::MaybeResolveWithNull(std::move(pending_promise.promise)); } mPendingPopErrorScopePromises.clear(); } // Pretend this worked, per spec; see "Listen for timeline event". { for (auto& pending_promise : mPendingCreatePipelinePromises) { if (pending_promise.is_render_pipeline) { RefPtr object = new RenderPipeline( pending_promise.device, pending_promise.pipeline_id); object->SetLabel(pending_promise.label); promise::MaybeResolve(std::move(pending_promise.promise), std::move(object)); } else { RefPtr object = new ComputePipeline( pending_promise.device, pending_promise.pipeline_id); object->SetLabel(pending_promise.label); promise::MaybeResolve(std::move(pending_promise.promise), std::move(object)); } } mPendingCreatePipelinePromises.clear(); } // Pretend this worked, per spec; see "Listen for timeline event". { for (auto& pending_promise : mPendingCreateShaderModulePromises) { nsTArray messages; RefPtr infoObject( new CompilationInfo(pending_promise.device)); infoObject->SetMessages(messages); promise::MaybeResolve(std::move(pending_promise.promise), std::move(infoObject)); } mPendingCreateShaderModulePromises.clear(); } // Reject the promise as if unmap() has been called, per spec. { for (auto& pending_promises : mPendingBufferMapPromises) { for (auto& pending_promise : pending_promises.second) { pending_promise.buffer->RejectMapRequestWithAbortError( pending_promise.promise); } } mPendingBufferMapPromises.clear(); } // Pretend this worked, per spec; see "Listen for timeline event". { for (auto& pending_promises : mPendingOnSubmittedWorkDonePromises) { for (auto& pending_promise : pending_promises.second) { promise::MaybeResolveWithUndefined(std::move(pending_promise)); } } mPendingOnSubmittedWorkDonePromises.clear(); } } void WebGPUChild::QueueSubmit( RawId aSelfId, RawId aDeviceId, nsTArray& aCommandBuffers, const nsTArray& aUsedExternalTextureSources) { ffi::wgpu_client_queue_submit( GetClient(), aDeviceId, aSelfId, {aCommandBuffers.Elements(), aCommandBuffers.Length()}, {mSwapChainTexturesWaitingForSubmit.Elements(), mSwapChainTexturesWaitingForSubmit.Length()}, {aUsedExternalTextureSources.Elements(), aUsedExternalTextureSources.Length()}); mSwapChainTexturesWaitingForSubmit.Clear(); PROFILER_MARKER_UNTYPED("WebGPU: QueueSubmit", GRAPHICS_WebGPU); FlushQueuedMessages(); } void WebGPUChild::NotifyWaitForSubmit(RawId aTextureId) { mSwapChainTexturesWaitingForSubmit.AppendElement(aTextureId); } } // namespace mozilla::webgpu