summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/Device.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/Device.cpp')
-rw-r--r--dom/webgpu/Device.cpp1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp
new file mode 100644
index 0000000000..a659047af1
--- /dev/null
+++ b/dom/webgpu/Device.cpp
@@ -0,0 +1,1058 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "js/ArrayBuffer.h"
+#include "js/Value.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Console.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "Device.h"
+#include "CommandEncoder.h"
+#include "BindGroup.h"
+
+#include "Adapter.h"
+#include "Buffer.h"
+#include "CompilationInfo.h"
+#include "ComputePipeline.h"
+#include "DeviceLostInfo.h"
+#include "InternalError.h"
+#include "OutOfMemoryError.h"
+#include "PipelineLayout.h"
+#include "Queue.h"
+#include "RenderBundleEncoder.h"
+#include "RenderPipeline.h"
+#include "Sampler.h"
+#include "SupportedFeatures.h"
+#include "SupportedLimits.h"
+#include "Texture.h"
+#include "TextureView.h"
+#include "ValidationError.h"
+#include "ipc/WebGPUChild.h"
+#include "Utility.h"
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla::webgpu {
+
+mozilla::LazyLogModule gWebGPULog("WebGPU");
+
+GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(Device, DOMEventTargetHelper,
+ mBridge, mQueue, mFeatures,
+ mLimits, mLostPromise);
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(Device, DOMEventTargetHelper)
+GPU_IMPL_JS_WRAP(Device)
+
+/* static */ CheckedInt<uint32_t> Device::BufferStrideWithMask(
+ const gfx::IntSize& aSize, const gfx::SurfaceFormat& aFormat) {
+ constexpr uint32_t kBufferAlignmentMask = 0xff;
+ return CheckedInt<uint32_t>(aSize.width) * gfx::BytesPerPixel(aFormat) +
+ kBufferAlignmentMask;
+}
+
+RefPtr<WebGPUChild> Device::GetBridge() { return mBridge; }
+
+Device::Device(Adapter* const aParent, RawId aId,
+ const ffi::WGPULimits& aRawLimits)
+ : DOMEventTargetHelper(aParent->GetParentObject()),
+ mId(aId),
+ // features are filled in Adapter::RequestDevice
+ mFeatures(new SupportedFeatures(aParent)),
+ mLimits(new SupportedLimits(aParent, aRawLimits)),
+ mBridge(aParent->mBridge),
+ mQueue(new class Queue(this, aParent->mBridge, aId)) {
+ mBridge->RegisterDevice(this);
+}
+
+Device::~Device() { Cleanup(); }
+
+void Device::Cleanup() {
+ if (!mValid) {
+ return;
+ }
+
+ mValid = false;
+
+ if (mBridge) {
+ mBridge->UnregisterDevice(mId);
+ }
+}
+
+void Device::CleanupUnregisteredInParent() {
+ if (mBridge) {
+ mBridge->FreeUnregisteredInParentDevice(mId);
+ }
+ mValid = false;
+}
+
+bool Device::IsLost() const {
+ return !mBridge || !mBridge->CanSend() ||
+ (mLostPromise &&
+ (mLostPromise->State() != dom::Promise::PromiseState::Pending));
+}
+
+bool Device::IsBridgeAlive() const { return mBridge && mBridge->CanSend(); }
+
+// Generate an error on the Device timeline for this device.
+//
+// aMessage is interpreted as UTF-8.
+void Device::GenerateValidationError(const nsCString& aMessage) {
+ if (!IsBridgeAlive()) {
+ return; // Just drop it?
+ }
+ mBridge->SendGenerateError(Some(mId), dom::GPUErrorFilter::Validation,
+ aMessage);
+}
+
+void Device::TrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Insert(aBuffer); }
+
+void Device::UntrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Remove(aBuffer); }
+
+void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; }
+void Device::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }
+
+dom::Promise* Device::GetLost(ErrorResult& aRv) {
+ aRv = NS_OK;
+ if (!mLostPromise) {
+ mLostPromise = dom::Promise::Create(GetParentObject(), aRv);
+ if (mLostPromise && !mBridge->CanSend()) {
+ auto info = MakeRefPtr<DeviceLostInfo>(GetParentObject(),
+ u"WebGPUChild destroyed"_ns);
+ mLostPromise->MaybeResolve(info);
+ }
+ }
+ return mLostPromise;
+}
+
+void Device::ResolveLost(Maybe<dom::GPUDeviceLostReason> aReason,
+ const nsAString& aMessage) {
+ IgnoredErrorResult rv;
+ dom::Promise* lostPromise = GetLost(rv);
+ if (!lostPromise) {
+ // Promise doesn't exist? Maybe out of memory.
+ return;
+ }
+ if (!lostPromise->PromiseObj()) {
+ // The underlying JS object is gone.
+ return;
+ }
+ if (lostPromise->State() != dom::Promise::PromiseState::Pending) {
+ // lostPromise was already resolved or rejected.
+ return;
+ }
+ RefPtr<DeviceLostInfo> info;
+ if (aReason.isSome()) {
+ info = MakeRefPtr<DeviceLostInfo>(GetParentObject(), *aReason, aMessage);
+ } else {
+ info = MakeRefPtr<DeviceLostInfo>(GetParentObject(), aMessage);
+ }
+ lostPromise->MaybeResolve(info);
+}
+
+already_AddRefed<Buffer> Device::CreateBuffer(
+ const dom::GPUBufferDescriptor& aDesc, ErrorResult& aRv) {
+ return Buffer::Create(this, mId, aDesc, aRv);
+}
+
+already_AddRefed<Texture> Device::CreateTextureForSwapChain(
+ const dom::GPUCanvasConfiguration* const aConfig,
+ const gfx::IntSize& aCanvasSize, layers::RemoteTextureOwnerId aOwnerId) {
+ MOZ_ASSERT(aConfig);
+
+ dom::GPUTextureDescriptor desc;
+ desc.mDimension = dom::GPUTextureDimension::_2d;
+ auto& sizeDict = desc.mSize.SetAsGPUExtent3DDict();
+ sizeDict.mWidth = aCanvasSize.width;
+ sizeDict.mHeight = aCanvasSize.height;
+ sizeDict.mDepthOrArrayLayers = 1;
+ desc.mFormat = aConfig->mFormat;
+ desc.mMipLevelCount = 1;
+ desc.mSampleCount = 1;
+ desc.mUsage = aConfig->mUsage | dom::GPUTextureUsage_Binding::COPY_SRC;
+ desc.mViewFormats = aConfig->mViewFormats;
+
+ return CreateTexture(desc, Some(aOwnerId));
+}
+
+already_AddRefed<Texture> Device::CreateTexture(
+ const dom::GPUTextureDescriptor& aDesc) {
+ return CreateTexture(aDesc, /* aOwnerId */ Nothing());
+}
+
+already_AddRefed<Texture> Device::CreateTexture(
+ const dom::GPUTextureDescriptor& aDesc,
+ Maybe<layers::RemoteTextureOwnerId> aOwnerId) {
+ ffi::WGPUTextureDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ if (aDesc.mSize.IsRangeEnforcedUnsignedLongSequence()) {
+ const auto& seq = aDesc.mSize.GetAsRangeEnforcedUnsignedLongSequence();
+ desc.size.width = seq.Length() > 0 ? seq[0] : 1;
+ desc.size.height = seq.Length() > 1 ? seq[1] : 1;
+ desc.size.depth_or_array_layers = seq.Length() > 2 ? seq[2] : 1;
+ } else if (aDesc.mSize.IsGPUExtent3DDict()) {
+ const auto& dict = aDesc.mSize.GetAsGPUExtent3DDict();
+ desc.size.width = dict.mWidth;
+ desc.size.height = dict.mHeight;
+ desc.size.depth_or_array_layers = dict.mDepthOrArrayLayers;
+ } else {
+ MOZ_CRASH("Unexpected union");
+ }
+ desc.mip_level_count = aDesc.mMipLevelCount;
+ desc.sample_count = aDesc.mSampleCount;
+ desc.dimension = ffi::WGPUTextureDimension(aDesc.mDimension);
+ desc.format = ConvertTextureFormat(aDesc.mFormat);
+ desc.usage = aDesc.mUsage;
+
+ AutoTArray<ffi::WGPUTextureFormat, 8> viewFormats;
+ for (auto format : aDesc.mViewFormats) {
+ viewFormats.AppendElement(ConvertTextureFormat(format));
+ }
+ desc.view_formats = {viewFormats.Elements(), viewFormats.Length()};
+
+ Maybe<ffi::WGPUSwapChainId> ownerId;
+ if (aOwnerId.isSome()) {
+ ownerId = Some(ffi::WGPUSwapChainId{aOwnerId->mId});
+ }
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_texture(
+ mBridge->GetClient(), mId, &desc, ownerId.ptrOr(nullptr), ToFFI(&bb));
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<Texture> texture = new Texture(this, id, aDesc);
+ return texture.forget();
+}
+
+already_AddRefed<Sampler> Device::CreateSampler(
+ const dom::GPUSamplerDescriptor& aDesc) {
+ ffi::WGPUSamplerDescriptor desc = {};
+ webgpu::StringHelper label(aDesc.mLabel);
+
+ desc.label = label.Get();
+ desc.address_modes[0] = ffi::WGPUAddressMode(aDesc.mAddressModeU);
+ desc.address_modes[1] = ffi::WGPUAddressMode(aDesc.mAddressModeV);
+ desc.address_modes[2] = ffi::WGPUAddressMode(aDesc.mAddressModeW);
+ desc.mag_filter = ffi::WGPUFilterMode(aDesc.mMagFilter);
+ desc.min_filter = ffi::WGPUFilterMode(aDesc.mMinFilter);
+ desc.mipmap_filter = ffi::WGPUFilterMode(aDesc.mMipmapFilter);
+ desc.lod_min_clamp = aDesc.mLodMinClamp;
+ desc.lod_max_clamp = aDesc.mLodMaxClamp;
+
+ ffi::WGPUCompareFunction comparison = ffi::WGPUCompareFunction_Sentinel;
+ if (aDesc.mCompare.WasPassed()) {
+ comparison = ConvertCompareFunction(aDesc.mCompare.Value());
+ desc.compare = &comparison;
+ }
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_sampler(mBridge->GetClient(), mId, &desc,
+ ToFFI(&bb));
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<Sampler> sampler = new Sampler(this, id);
+ return sampler.forget();
+}
+
+already_AddRefed<CommandEncoder> Device::CreateCommandEncoder(
+ const dom::GPUCommandEncoderDescriptor& aDesc) {
+ ffi::WGPUCommandEncoderDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_command_encoder(mBridge->GetClient(), mId,
+ &desc, ToFFI(&bb));
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<CommandEncoder> encoder = new CommandEncoder(this, mBridge, id);
+ return encoder.forget();
+}
+
+already_AddRefed<RenderBundleEncoder> Device::CreateRenderBundleEncoder(
+ const dom::GPURenderBundleEncoderDescriptor& aDesc) {
+ RefPtr<RenderBundleEncoder> encoder =
+ new RenderBundleEncoder(this, mBridge, aDesc);
+ return encoder.forget();
+}
+
+already_AddRefed<BindGroupLayout> Device::CreateBindGroupLayout(
+ const dom::GPUBindGroupLayoutDescriptor& aDesc) {
+ struct OptionalData {
+ ffi::WGPUTextureViewDimension dim;
+ ffi::WGPURawTextureSampleType type;
+ ffi::WGPUTextureFormat format;
+ };
+ nsTArray<OptionalData> optional(aDesc.mEntries.Length());
+ for (const auto& entry : aDesc.mEntries) {
+ OptionalData data = {};
+ if (entry.mTexture.WasPassed()) {
+ const auto& texture = entry.mTexture.Value();
+ data.dim = ffi::WGPUTextureViewDimension(texture.mViewDimension);
+ switch (texture.mSampleType) {
+ case dom::GPUTextureSampleType::Float:
+ data.type = ffi::WGPURawTextureSampleType_Float;
+ break;
+ case dom::GPUTextureSampleType::Unfilterable_float:
+ data.type = ffi::WGPURawTextureSampleType_UnfilterableFloat;
+ break;
+ case dom::GPUTextureSampleType::Uint:
+ data.type = ffi::WGPURawTextureSampleType_Uint;
+ break;
+ case dom::GPUTextureSampleType::Sint:
+ data.type = ffi::WGPURawTextureSampleType_Sint;
+ break;
+ case dom::GPUTextureSampleType::Depth:
+ data.type = ffi::WGPURawTextureSampleType_Depth;
+ break;
+ case dom::GPUTextureSampleType::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+ }
+ if (entry.mStorageTexture.WasPassed()) {
+ const auto& texture = entry.mStorageTexture.Value();
+ data.dim = ffi::WGPUTextureViewDimension(texture.mViewDimension);
+ data.format = ConvertTextureFormat(texture.mFormat);
+ }
+ optional.AppendElement(data);
+ }
+
+ nsTArray<ffi::WGPUBindGroupLayoutEntry> entries(aDesc.mEntries.Length());
+ for (size_t i = 0; i < aDesc.mEntries.Length(); ++i) {
+ const auto& entry = aDesc.mEntries[i];
+ ffi::WGPUBindGroupLayoutEntry e = {};
+ e.binding = entry.mBinding;
+ e.visibility = entry.mVisibility;
+ if (entry.mBuffer.WasPassed()) {
+ switch (entry.mBuffer.Value().mType) {
+ case dom::GPUBufferBindingType::Uniform:
+ e.ty = ffi::WGPURawBindingType_UniformBuffer;
+ break;
+ case dom::GPUBufferBindingType::Storage:
+ e.ty = ffi::WGPURawBindingType_StorageBuffer;
+ break;
+ case dom::GPUBufferBindingType::Read_only_storage:
+ e.ty = ffi::WGPURawBindingType_ReadonlyStorageBuffer;
+ break;
+ case dom::GPUBufferBindingType::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+ e.has_dynamic_offset = entry.mBuffer.Value().mHasDynamicOffset;
+ }
+ if (entry.mTexture.WasPassed()) {
+ e.ty = ffi::WGPURawBindingType_SampledTexture;
+ e.view_dimension = &optional[i].dim;
+ e.texture_sample_type = &optional[i].type;
+ e.multisampled = entry.mTexture.Value().mMultisampled;
+ }
+ if (entry.mStorageTexture.WasPassed()) {
+ e.ty = entry.mStorageTexture.Value().mAccess ==
+ dom::GPUStorageTextureAccess::Write_only
+ ? ffi::WGPURawBindingType_WriteonlyStorageTexture
+ : ffi::WGPURawBindingType_ReadonlyStorageTexture;
+ e.view_dimension = &optional[i].dim;
+ e.storage_texture_format = &optional[i].format;
+ }
+ if (entry.mSampler.WasPassed()) {
+ e.ty = ffi::WGPURawBindingType_Sampler;
+ switch (entry.mSampler.Value().mType) {
+ case dom::GPUSamplerBindingType::Filtering:
+ e.sampler_filter = true;
+ break;
+ case dom::GPUSamplerBindingType::Non_filtering:
+ break;
+ case dom::GPUSamplerBindingType::Comparison:
+ e.sampler_compare = true;
+ break;
+ case dom::GPUSamplerBindingType::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+ }
+ entries.AppendElement(e);
+ }
+
+ ffi::WGPUBindGroupLayoutDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+ desc.entries = entries.Elements();
+ desc.entries_length = entries.Length();
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_bind_group_layout(mBridge->GetClient(),
+ mId, &desc, ToFFI(&bb));
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<BindGroupLayout> object = new BindGroupLayout(this, id, true);
+ return object.forget();
+}
+
+already_AddRefed<PipelineLayout> Device::CreatePipelineLayout(
+ const dom::GPUPipelineLayoutDescriptor& aDesc) {
+ nsTArray<ffi::WGPUBindGroupLayoutId> bindGroupLayouts(
+ aDesc.mBindGroupLayouts.Length());
+
+ for (const auto& layout : aDesc.mBindGroupLayouts) {
+ bindGroupLayouts.AppendElement(layout->mId);
+ }
+
+ ffi::WGPUPipelineLayoutDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+ desc.bind_group_layouts = bindGroupLayouts.Elements();
+ desc.bind_group_layouts_length = bindGroupLayouts.Length();
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_pipeline_layout(mBridge->GetClient(), mId,
+ &desc, ToFFI(&bb));
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<PipelineLayout> object = new PipelineLayout(this, id);
+ return object.forget();
+}
+
+already_AddRefed<BindGroup> Device::CreateBindGroup(
+ const dom::GPUBindGroupDescriptor& aDesc) {
+ nsTArray<ffi::WGPUBindGroupEntry> entries(aDesc.mEntries.Length());
+ for (const auto& entry : aDesc.mEntries) {
+ ffi::WGPUBindGroupEntry e = {};
+ e.binding = entry.mBinding;
+ if (entry.mResource.IsGPUBufferBinding()) {
+ const auto& bufBinding = entry.mResource.GetAsGPUBufferBinding();
+ if (!bufBinding.mBuffer->mId) {
+ NS_WARNING("Buffer binding has no id -- ignoring.");
+ continue;
+ }
+ e.buffer = bufBinding.mBuffer->mId;
+ e.offset = bufBinding.mOffset;
+ e.size = bufBinding.mSize.WasPassed() ? bufBinding.mSize.Value() : 0;
+ } else if (entry.mResource.IsGPUTextureView()) {
+ e.texture_view = entry.mResource.GetAsGPUTextureView()->mId;
+ } else if (entry.mResource.IsGPUSampler()) {
+ e.sampler = entry.mResource.GetAsGPUSampler()->mId;
+ } else {
+ // Not a buffer, nor a texture view, nor a sampler. If we pass
+ // this to wgpu_client, it'll panic. Log a warning instead and
+ // ignore this entry.
+ NS_WARNING("Bind group entry has unknown type.");
+ continue;
+ }
+ entries.AppendElement(e);
+ }
+
+ ffi::WGPUBindGroupDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+ desc.layout = aDesc.mLayout->mId;
+ desc.entries = entries.Elements();
+ desc.entries_length = entries.Length();
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_bind_group(mBridge->GetClient(), mId,
+ &desc, ToFFI(&bb));
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<BindGroup> object = new BindGroup(this, id);
+ return object.forget();
+}
+
+MOZ_CAN_RUN_SCRIPT void reportCompilationMessagesToConsole(
+ const RefPtr<ShaderModule>& aShaderModule,
+ const nsTArray<WebGPUCompilationMessage>& aMessages) {
+ auto* global = aShaderModule->GetParentObject();
+
+ dom::AutoJSAPI api;
+ if (!api.Init(global)) {
+ return;
+ }
+
+ const auto& cx = api.cx();
+
+ ErrorResult rv;
+ RefPtr<dom::Console> console =
+ nsGlobalWindowInner::Cast(global->GetAsInnerWindow())->GetConsole(cx, rv);
+ if (rv.Failed()) {
+ return;
+ }
+
+ dom::GlobalObject globalObj(cx, global->GetGlobalJSObject());
+
+ dom::Sequence<JS::Value> args;
+ dom::SequenceRooter<JS::Value> msgArgsRooter(cx, &args);
+ auto SetSingleStrAsArgs =
+ [&](const nsString& message, dom::Sequence<JS::Value>* args)
+ MOZ_CAN_RUN_SCRIPT {
+ args->Clear();
+ JS::Rooted<JSString*> jsStr(
+ cx, JS_NewUCStringCopyN(cx, message.Data(), message.Length()));
+ if (!jsStr) {
+ return;
+ }
+ JS::Rooted<JS::Value> val(cx, JS::StringValue(jsStr));
+ if (!args->AppendElement(val, fallible)) {
+ return;
+ }
+ };
+
+ nsString label;
+ aShaderModule->GetLabel(label);
+ auto appendNiceLabelIfPresent = [&label](nsString* buf) MOZ_CAN_RUN_SCRIPT {
+ if (!label.IsEmpty()) {
+ buf->AppendLiteral(u" \"");
+ buf->Append(label);
+ buf->AppendLiteral(u"\"");
+ }
+ };
+
+ // We haven't actually inspected a message for severity, but
+ // it doesn't actually matter, since we don't do anything at
+ // this level.
+ auto highestSeveritySeen = WebGPUCompilationMessageType::Info;
+ uint64_t errorCount = 0;
+ uint64_t warningCount = 0;
+ uint64_t infoCount = 0;
+ for (const auto& message : aMessages) {
+ bool higherThanSeen =
+ static_cast<std::underlying_type_t<WebGPUCompilationMessageType>>(
+ message.messageType) <
+ static_cast<std::underlying_type_t<WebGPUCompilationMessageType>>(
+ highestSeveritySeen);
+ if (higherThanSeen) {
+ highestSeveritySeen = message.messageType;
+ }
+ switch (message.messageType) {
+ case WebGPUCompilationMessageType::Error:
+ errorCount += 1;
+ break;
+ case WebGPUCompilationMessageType::Warning:
+ warningCount += 1;
+ break;
+ case WebGPUCompilationMessageType::Info:
+ infoCount += 1;
+ break;
+ }
+ }
+ switch (highestSeveritySeen) {
+ case WebGPUCompilationMessageType::Info:
+ // shouldn't happen, but :shrug:
+ break;
+ case WebGPUCompilationMessageType::Warning: {
+ nsString msg(
+ u"Encountered one or more warnings while creating shader module");
+ appendNiceLabelIfPresent(&msg);
+ SetSingleStrAsArgs(msg, &args);
+ console->Warn(globalObj, args);
+ break;
+ }
+ case WebGPUCompilationMessageType::Error: {
+ nsString msg(
+ u"Encountered one or more errors while creating shader module");
+ appendNiceLabelIfPresent(&msg);
+ SetSingleStrAsArgs(msg, &args);
+ console->Error(globalObj, args);
+ break;
+ }
+ }
+
+ nsString header;
+ header.AppendLiteral(u"WebGPU compilation info for shader module");
+ appendNiceLabelIfPresent(&header);
+ header.AppendLiteral(u" (");
+ header.AppendInt(errorCount);
+ header.AppendLiteral(u" error(s), ");
+ header.AppendInt(warningCount);
+ header.AppendLiteral(u" warning(s), ");
+ header.AppendInt(infoCount);
+ header.AppendLiteral(u" info)");
+ SetSingleStrAsArgs(header, &args);
+ console->GroupCollapsed(globalObj, args);
+
+ for (const auto& message : aMessages) {
+ SetSingleStrAsArgs(message.message, &args);
+ switch (message.messageType) {
+ case WebGPUCompilationMessageType::Error:
+ console->Error(globalObj, args);
+ break;
+ case WebGPUCompilationMessageType::Warning:
+ console->Warn(globalObj, args);
+ break;
+ case WebGPUCompilationMessageType::Info:
+ console->Info(globalObj, args);
+ break;
+ }
+ }
+ console->GroupEnd(globalObj);
+}
+
+already_AddRefed<ShaderModule> Device::CreateShaderModule(
+ JSContext* aCx, const dom::GPUShaderModuleDescriptor& aDesc,
+ ErrorResult& aRv) {
+ Unused << aCx;
+
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RawId moduleId =
+ ffi::wgpu_client_make_shader_module_id(mBridge->GetClient(), mId);
+
+ RefPtr<ShaderModule> shaderModule = new ShaderModule(this, moduleId, promise);
+
+ shaderModule->SetLabel(aDesc.mLabel);
+
+ RefPtr<Device> device = this;
+
+ if (mBridge->CanSend()) {
+ mBridge
+ ->SendDeviceCreateShaderModule(mId, moduleId, aDesc.mLabel, aDesc.mCode)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, device,
+ shaderModule](nsTArray<WebGPUCompilationMessage>&& messages)
+ MOZ_CAN_RUN_SCRIPT {
+ if (!messages.IsEmpty()) {
+ reportCompilationMessagesToConsole(shaderModule,
+ std::cref(messages));
+ }
+ RefPtr<CompilationInfo> infoObject(
+ new CompilationInfo(device));
+ infoObject->SetMessages(messages);
+ promise->MaybeResolve(infoObject);
+ },
+ [promise](const ipc::ResponseRejectReason& aReason) {
+ promise->MaybeRejectWithNotSupportedError("IPC error");
+ });
+ } else {
+ promise->MaybeRejectWithNotSupportedError("IPC error");
+ }
+
+ return shaderModule.forget();
+}
+
+RawId CreateComputePipelineImpl(PipelineCreationContext* const aContext,
+ WebGPUChild* aBridge,
+ const dom::GPUComputePipelineDescriptor& aDesc,
+ ipc::ByteBuf* const aByteBuf) {
+ ffi::WGPUComputePipelineDescriptor desc = {};
+ nsCString entryPoint;
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ if (aDesc.mLayout.IsGPUAutoLayoutMode()) {
+ desc.layout = 0;
+ } else if (aDesc.mLayout.IsGPUPipelineLayout()) {
+ desc.layout = aDesc.mLayout.GetAsGPUPipelineLayout()->mId;
+ } else {
+ MOZ_ASSERT_UNREACHABLE();
+ }
+ desc.stage.module = aDesc.mCompute.mModule->mId;
+ CopyUTF16toUTF8(aDesc.mCompute.mEntryPoint, entryPoint);
+ desc.stage.entry_point = entryPoint.get();
+
+ RawId implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {};
+ RawId id = ffi::wgpu_client_create_compute_pipeline(
+ aBridge->GetClient(), aContext->mParentId, &desc, ToFFI(aByteBuf),
+ &aContext->mImplicitPipelineLayoutId, implicit_bgl_ids);
+
+ for (const auto& cur : implicit_bgl_ids) {
+ if (!cur) break;
+ aContext->mImplicitBindGroupLayoutIds.AppendElement(cur);
+ }
+
+ return id;
+}
+
+RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext,
+ WebGPUChild* aBridge,
+ const dom::GPURenderPipelineDescriptor& aDesc,
+ ipc::ByteBuf* const aByteBuf) {
+ // A bunch of stack locals that we can have pointers into
+ nsTArray<ffi::WGPUVertexBufferLayout> vertexBuffers;
+ nsTArray<ffi::WGPUVertexAttribute> vertexAttributes;
+ ffi::WGPURenderPipelineDescriptor desc = {};
+ nsCString vsEntry, fsEntry;
+ ffi::WGPUIndexFormat stripIndexFormat = ffi::WGPUIndexFormat_Uint16;
+ ffi::WGPUFace cullFace = ffi::WGPUFace_Front;
+ ffi::WGPUVertexState vertexState = {};
+ ffi::WGPUFragmentState fragmentState = {};
+ nsTArray<ffi::WGPUColorTargetState> colorStates;
+ nsTArray<ffi::WGPUBlendState> blendStates;
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ if (aDesc.mLayout.IsGPUAutoLayoutMode()) {
+ desc.layout = 0;
+ } else if (aDesc.mLayout.IsGPUPipelineLayout()) {
+ desc.layout = aDesc.mLayout.GetAsGPUPipelineLayout()->mId;
+ } else {
+ MOZ_ASSERT_UNREACHABLE();
+ }
+
+ {
+ const auto& stage = aDesc.mVertex;
+ vertexState.stage.module = stage.mModule->mId;
+ CopyUTF16toUTF8(stage.mEntryPoint, vsEntry);
+ vertexState.stage.entry_point = vsEntry.get();
+
+ for (const auto& vertex_desc : stage.mBuffers) {
+ ffi::WGPUVertexBufferLayout vb_desc = {};
+ if (!vertex_desc.IsNull()) {
+ const auto& vd = vertex_desc.Value();
+ vb_desc.array_stride = vd.mArrayStride;
+ vb_desc.step_mode = ffi::WGPUVertexStepMode(vd.mStepMode);
+ // Note: we are setting the length but not the pointer
+ vb_desc.attributes_length = vd.mAttributes.Length();
+ for (const auto& vat : vd.mAttributes) {
+ ffi::WGPUVertexAttribute ad = {};
+ ad.offset = vat.mOffset;
+ ad.format = ffi::WGPUVertexFormat(vat.mFormat);
+ ad.shader_location = vat.mShaderLocation;
+ vertexAttributes.AppendElement(ad);
+ }
+ }
+ vertexBuffers.AppendElement(vb_desc);
+ }
+ // Now patch up all the pointers to attribute lists.
+ size_t numAttributes = 0;
+ for (auto& vb_desc : vertexBuffers) {
+ vb_desc.attributes = vertexAttributes.Elements() + numAttributes;
+ numAttributes += vb_desc.attributes_length;
+ }
+
+ vertexState.buffers = vertexBuffers.Elements();
+ vertexState.buffers_length = vertexBuffers.Length();
+ desc.vertex = &vertexState;
+ }
+
+ if (aDesc.mFragment.WasPassed()) {
+ const auto& stage = aDesc.mFragment.Value();
+ fragmentState.stage.module = stage.mModule->mId;
+ CopyUTF16toUTF8(stage.mEntryPoint, fsEntry);
+ fragmentState.stage.entry_point = fsEntry.get();
+
+ // Note: we pre-collect the blend states into a different array
+ // so that we can have non-stale pointers into it.
+ for (const auto& colorState : stage.mTargets) {
+ ffi::WGPUColorTargetState desc = {};
+ desc.format = ConvertTextureFormat(colorState.mFormat);
+ desc.write_mask = colorState.mWriteMask;
+ colorStates.AppendElement(desc);
+ ffi::WGPUBlendState bs = {};
+ if (colorState.mBlend.WasPassed()) {
+ const auto& blend = colorState.mBlend.Value();
+ bs.alpha = ConvertBlendComponent(blend.mAlpha);
+ bs.color = ConvertBlendComponent(blend.mColor);
+ }
+ blendStates.AppendElement(bs);
+ }
+ for (size_t i = 0; i < colorStates.Length(); ++i) {
+ if (stage.mTargets[i].mBlend.WasPassed()) {
+ colorStates[i].blend = &blendStates[i];
+ }
+ }
+
+ fragmentState.targets = colorStates.Elements();
+ fragmentState.targets_length = colorStates.Length();
+ desc.fragment = &fragmentState;
+ }
+
+ {
+ const auto& prim = aDesc.mPrimitive;
+ desc.primitive.topology = ffi::WGPUPrimitiveTopology(prim.mTopology);
+ if (prim.mStripIndexFormat.WasPassed()) {
+ stripIndexFormat = ffi::WGPUIndexFormat(prim.mStripIndexFormat.Value());
+ desc.primitive.strip_index_format = &stripIndexFormat;
+ }
+ desc.primitive.front_face = ffi::WGPUFrontFace(prim.mFrontFace);
+ if (prim.mCullMode != dom::GPUCullMode::None) {
+ cullFace = prim.mCullMode == dom::GPUCullMode::Front ? ffi::WGPUFace_Front
+ : ffi::WGPUFace_Back;
+ desc.primitive.cull_mode = &cullFace;
+ }
+ desc.primitive.unclipped_depth = prim.mUnclippedDepth;
+ }
+ desc.multisample = ConvertMultisampleState(aDesc.mMultisample);
+
+ ffi::WGPUDepthStencilState depthStencilState = {};
+ if (aDesc.mDepthStencil.WasPassed()) {
+ depthStencilState = ConvertDepthStencilState(aDesc.mDepthStencil.Value());
+ desc.depth_stencil = &depthStencilState;
+ }
+
+ RawId implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {};
+ RawId id = ffi::wgpu_client_create_render_pipeline(
+ aBridge->GetClient(), aContext->mParentId, &desc, ToFFI(aByteBuf),
+ &aContext->mImplicitPipelineLayoutId, implicit_bgl_ids);
+
+ for (const auto& cur : implicit_bgl_ids) {
+ if (!cur) break;
+ aContext->mImplicitBindGroupLayoutIds.AppendElement(cur);
+ }
+
+ return id;
+}
+
+already_AddRefed<ComputePipeline> Device::CreateComputePipeline(
+ const dom::GPUComputePipelineDescriptor& aDesc) {
+ PipelineCreationContext context = {mId};
+ ipc::ByteBuf bb;
+ RawId id = CreateComputePipelineImpl(&context, mBridge, aDesc, &bb);
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<ComputePipeline> object =
+ new ComputePipeline(this, id, context.mImplicitPipelineLayoutId,
+ std::move(context.mImplicitBindGroupLayoutIds));
+ return object.forget();
+}
+
+already_AddRefed<RenderPipeline> Device::CreateRenderPipeline(
+ const dom::GPURenderPipelineDescriptor& aDesc) {
+ PipelineCreationContext context = {mId};
+ ipc::ByteBuf bb;
+ RawId id = CreateRenderPipelineImpl(&context, mBridge, aDesc, &bb);
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceAction(mId, std::move(bb));
+ }
+
+ RefPtr<RenderPipeline> object =
+ new RenderPipeline(this, id, context.mImplicitPipelineLayoutId,
+ std::move(context.mImplicitBindGroupLayoutIds));
+ return object.forget();
+}
+
+already_AddRefed<dom::Promise> Device::CreateComputePipelineAsync(
+ const dom::GPUComputePipelineDescriptor& aDesc, ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ std::shared_ptr<PipelineCreationContext> context(
+ new PipelineCreationContext());
+ context->mParentId = mId;
+
+ ipc::ByteBuf bb;
+ RawId pipelineId =
+ CreateComputePipelineImpl(context.get(), mBridge, aDesc, &bb);
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceActionWithAck(mId, std::move(bb))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, context, pipelineId, promise](bool aDummy) {
+ Unused << aDummy;
+ RefPtr<ComputePipeline> object = new ComputePipeline(
+ self, pipelineId, context->mImplicitPipelineLayoutId,
+ std::move(context->mImplicitBindGroupLayoutIds));
+ promise->MaybeResolve(object);
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithOperationError(
+ "Internal communication error");
+ });
+ } else {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<dom::Promise> Device::CreateRenderPipelineAsync(
+ const dom::GPURenderPipelineDescriptor& aDesc, ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ std::shared_ptr<PipelineCreationContext> context(
+ new PipelineCreationContext());
+ context->mParentId = mId;
+
+ ipc::ByteBuf bb;
+ RawId pipelineId =
+ CreateRenderPipelineImpl(context.get(), mBridge, aDesc, &bb);
+
+ if (mBridge->CanSend()) {
+ mBridge->SendDeviceActionWithAck(mId, std::move(bb))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, context, promise, pipelineId](bool aDummy) {
+ Unused << aDummy;
+ RefPtr<RenderPipeline> object = new RenderPipeline(
+ self, pipelineId, context->mImplicitPipelineLayoutId,
+ std::move(context->mImplicitBindGroupLayoutIds));
+ promise->MaybeResolve(object);
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithOperationError(
+ "Internal communication error");
+ });
+ } else {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<Texture> Device::InitSwapChain(
+ const dom::GPUCanvasConfiguration* const aConfig,
+ const layers::RemoteTextureOwnerId aOwnerId,
+ bool aUseExternalTextureInSwapChain, gfx::SurfaceFormat aFormat,
+ gfx::IntSize aCanvasSize) {
+ MOZ_ASSERT(aConfig);
+
+ if (!mBridge->CanSend()) {
+ return nullptr;
+ }
+
+ // Check that aCanvasSize and aFormat will generate a texture stride
+ // within limits.
+ const auto bufferStrideWithMask = BufferStrideWithMask(aCanvasSize, aFormat);
+ if (!bufferStrideWithMask.isValid()) {
+ return nullptr;
+ }
+
+ const layers::RGBDescriptor rgbDesc(aCanvasSize, aFormat);
+ // buffer count doesn't matter much, will be created on demand
+ const size_t maxBufferCount = 10;
+ mBridge->DeviceCreateSwapChain(mId, rgbDesc, maxBufferCount, aOwnerId,
+ aUseExternalTextureInSwapChain);
+
+ // TODO: `mColorSpace`: <https://bugzilla.mozilla.org/show_bug.cgi?id=1846608>
+ // TODO: `mAlphaMode`: <https://bugzilla.mozilla.org/show_bug.cgi?id=1846605>
+ return CreateTextureForSwapChain(aConfig, aCanvasSize, aOwnerId);
+}
+
+bool Device::CheckNewWarning(const nsACString& aMessage) {
+ return mKnownWarnings.EnsureInserted(aMessage);
+}
+
+void Device::Destroy() {
+ if (IsLost()) {
+ return;
+ }
+
+ // Unmap all buffers from this device, as specified by
+ // https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy.
+ dom::AutoJSAPI jsapi;
+ if (jsapi.Init(GetOwnerGlobal())) {
+ IgnoredErrorResult rv;
+ for (const auto& buffer : mTrackedBuffers) {
+ buffer->Unmap(jsapi.cx(), rv);
+ }
+
+ mTrackedBuffers.Clear();
+ }
+
+ mBridge->SendDeviceDestroy(mId);
+}
+
+void Device::PushErrorScope(const dom::GPUErrorFilter& aFilter) {
+ if (!IsBridgeAlive()) {
+ return;
+ }
+ mBridge->SendDevicePushErrorScope(mId, aFilter);
+}
+
+already_AddRefed<dom::Promise> Device::PopErrorScope(ErrorResult& aRv) {
+ /*
+ https://www.w3.org/TR/webgpu/#errors-and-debugging:
+ > After a device is lost (described below), errors are no longer surfaced.
+ > At this point, implementations do not need to run validation or error
+ tracking: > popErrorScope() and uncapturederror stop reporting errors, > and
+ the validity of objects on the device becomes unobservable.
+ */
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!IsBridgeAlive()) {
+ WebGPUChild::JsWarning(
+ GetOwnerGlobal(),
+ "popErrorScope resolving to null because device is already lost."_ns);
+ promise->MaybeResolve(JS::NullHandleValue);
+ return promise.forget();
+ }
+
+ auto errorPromise = mBridge->SendDevicePopErrorScope(mId);
+
+ errorPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, promise](const PopErrorScopeResult& aResult) {
+ RefPtr<Error> error;
+
+ switch (aResult.resultType) {
+ case PopErrorScopeResultType::NoError:
+ promise->MaybeResolve(JS::NullHandleValue);
+ return;
+
+ case PopErrorScopeResultType::DeviceLost:
+ WebGPUChild::JsWarning(
+ self->GetOwnerGlobal(),
+ "popErrorScope resolving to null because device was lost."_ns);
+ promise->MaybeResolve(JS::NullHandleValue);
+ return;
+
+ case PopErrorScopeResultType::ThrowOperationError:
+ promise->MaybeRejectWithOperationError(aResult.message);
+ return;
+
+ case PopErrorScopeResultType::OutOfMemory:
+ error =
+ new OutOfMemoryError(self->GetParentObject(), aResult.message);
+ break;
+
+ case PopErrorScopeResultType::ValidationError:
+ error =
+ new ValidationError(self->GetParentObject(), aResult.message);
+ break;
+
+ case PopErrorScopeResultType::InternalError:
+ error = new InternalError(self->GetParentObject(), aResult.message);
+ break;
+ }
+ promise->MaybeResolve(std::move(error));
+ },
+ [self = RefPtr{this}, promise](const ipc::ResponseRejectReason&) {
+ // Device was lost.
+ WebGPUChild::JsWarning(
+ self->GetOwnerGlobal(),
+ "popErrorScope resolving to null because device was just lost."_ns);
+ promise->MaybeResolve(JS::NullHandleValue);
+ });
+
+ return promise.forget();
+}
+
+} // namespace mozilla::webgpu