summaryrefslogtreecommitdiffstats
path: root/dom/webgpu
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/webgpu
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/webgpu/Adapter.cpp134
-rw-r--r--dom/webgpu/Adapter.h73
-rw-r--r--dom/webgpu/BindGroup.cpp36
-rw-r--r--dom/webgpu/BindGroup.h33
-rw-r--r--dom/webgpu/BindGroupLayout.cpp36
-rw-r--r--dom/webgpu/BindGroupLayout.h34
-rw-r--r--dom/webgpu/Buffer.cpp335
-rw-r--r--dom/webgpu/Buffer.h90
-rw-r--r--dom/webgpu/CanvasContext.cpp230
-rw-r--r--dom/webgpu/CanvasContext.h109
-rw-r--r--dom/webgpu/CommandBuffer.cpp51
-rw-r--r--dom/webgpu/CommandBuffer.h40
-rw-r--r--dom/webgpu/CommandEncoder.cpp252
-rw-r--r--dom/webgpu/CommandEncoder.h109
-rw-r--r--dom/webgpu/CompilationInfo.cpp34
-rw-r--r--dom/webgpu/CompilationInfo.h39
-rw-r--r--dom/webgpu/CompilationMessage.cpp24
-rw-r--r--dom/webgpu/CompilationMessage.h54
-rw-r--r--dom/webgpu/ComputePassEncoder.cpp108
-rw-r--r--dom/webgpu/ComputePassEncoder.h75
-rw-r--r--dom/webgpu/ComputePipeline.cpp50
-rw-r--r--dom/webgpu/ComputePipeline.h41
-rw-r--r--dom/webgpu/Device.cpp385
-rw-r--r--dom/webgpu/Device.h171
-rw-r--r--dom/webgpu/DeviceLostInfo.cpp13
-rw-r--r--dom/webgpu/DeviceLostInfo.h51
-rw-r--r--dom/webgpu/Instance.cpp85
-rw-r--r--dom/webgpu/Instance.h51
-rw-r--r--dom/webgpu/ObjectModel.cpp40
-rw-r--r--dom/webgpu/ObjectModel.h131
-rw-r--r--dom/webgpu/OutOfMemoryError.cpp17
-rw-r--r--dom/webgpu/OutOfMemoryError.h35
-rw-r--r--dom/webgpu/PipelineLayout.cpp36
-rw-r--r--dom/webgpu/PipelineLayout.h33
-rw-r--r--dom/webgpu/QuerySet.cpp22
-rw-r--r--dom/webgpu/QuerySet.h31
-rw-r--r--dom/webgpu/Queue.cpp416
-rw-r--r--dom/webgpu/Queue.h74
-rw-r--r--dom/webgpu/RenderBundle.cpp38
-rw-r--r--dom/webgpu/RenderBundle.h32
-rw-r--r--dom/webgpu/RenderBundleEncoder.cpp196
-rw-r--r--dom/webgpu/RenderBundleEncoder.h77
-rw-r--r--dom/webgpu/RenderPassEncoder.cpp306
-rw-r--r--dom/webgpu/RenderPassEncoder.h104
-rw-r--r--dom/webgpu/RenderPipeline.cpp50
-rw-r--r--dom/webgpu/RenderPipeline.h41
-rw-r--r--dom/webgpu/Sampler.cpp32
-rw-r--r--dom/webgpu/Sampler.h33
-rw-r--r--dom/webgpu/ShaderModule.cpp40
-rw-r--r--dom/webgpu/ShaderModule.h40
-rw-r--r--dom/webgpu/SupportedFeatures.cpp18
-rw-r--r--dom/webgpu/SupportedFeatures.h29
-rw-r--r--dom/webgpu/SupportedLimits.cpp101
-rw-r--r--dom/webgpu/SupportedLimits.h61
-rw-r--r--dom/webgpu/Texture.cpp123
-rw-r--r--dom/webgpu/Texture.h57
-rw-r--r--dom/webgpu/TextureView.cpp37
-rw-r--r--dom/webgpu/TextureView.h35
-rw-r--r--dom/webgpu/ValidationError.cpp41
-rw-r--r--dom/webgpu/ValidationError.h47
-rw-r--r--dom/webgpu/ipc/PWebGPU.ipdl93
-rw-r--r--dom/webgpu/ipc/PWebGPUTypes.ipdlh26
-rw-r--r--dom/webgpu/ipc/WebGPUChild.cpp1080
-rw-r--r--dom/webgpu/ipc/WebGPUChild.h146
-rw-r--r--dom/webgpu/ipc/WebGPUParent.cpp1116
-rw-r--r--dom/webgpu/ipc/WebGPUParent.h156
-rw-r--r--dom/webgpu/ipc/WebGPUSerialize.h50
-rw-r--r--dom/webgpu/ipc/WebGPUTypes.h69
-rw-r--r--dom/webgpu/mochitest/mochitest-no-pref.ini4
-rw-r--r--dom/webgpu/mochitest/mochitest.ini32
-rw-r--r--dom/webgpu/mochitest/test_basic_canvas.worker.html20
-rw-r--r--dom/webgpu/mochitest/test_basic_canvas.worker.js32
-rw-r--r--dom/webgpu/mochitest/test_buffer_mapping.html59
-rw-r--r--dom/webgpu/mochitest/test_command_buffer_creation.html28
-rw-r--r--dom/webgpu/mochitest/test_device_creation.html28
-rw-r--r--dom/webgpu/mochitest/test_disabled.html16
-rw-r--r--dom/webgpu/mochitest/test_enabled.html16
-rw-r--r--dom/webgpu/mochitest/test_error_scope.html38
-rw-r--r--dom/webgpu/mochitest/test_queue_copyExternalImageToTexture.html181
-rw-r--r--dom/webgpu/mochitest/test_queue_write.html33
-rw-r--r--dom/webgpu/mochitest/test_submit_compute_empty.html31
-rw-r--r--dom/webgpu/mochitest/test_submit_render_empty.html53
-rw-r--r--dom/webgpu/mochitest/test_submit_render_empty.worker.html16
-rw-r--r--dom/webgpu/mochitest/test_submit_render_empty.worker.js48
-rw-r--r--dom/webgpu/mochitest/worker_wrapper.js33
-rw-r--r--dom/webgpu/moz.build74
86 files changed, 8594 insertions, 0 deletions
diff --git a/dom/webgpu/Adapter.cpp b/dom/webgpu/Adapter.cpp
new file mode 100644
index 0000000000..ef857b70e8
--- /dev/null
+++ b/dom/webgpu/Adapter.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "Adapter.h"
+
+#include "Device.h"
+#include "Instance.h"
+#include "SupportedFeatures.h"
+#include "SupportedLimits.h"
+#include "ipc/WebGPUChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(Adapter, mParent, mBridge, mFeatures, mLimits)
+GPU_IMPL_JS_WRAP(Adapter)
+
+Maybe<uint32_t> Adapter::MakeFeatureBits(
+ const dom::Sequence<dom::GPUFeatureName>& aFeatures) {
+ uint32_t bits = 0;
+ for (const auto& feature : aFeatures) {
+ if (feature == dom::GPUFeatureName::Depth_clip_control) {
+ bits |= WGPUFeatures_DEPTH_CLIP_CONTROL;
+ } else if (feature == dom::GPUFeatureName::Texture_compression_bc) {
+ bits |= WGPUFeatures_TEXTURE_COMPRESSION_BC;
+ } else if (feature == dom::GPUFeatureName::Indirect_first_instance) {
+ bits |= WGPUFeatures_INDIRECT_FIRST_INSTANCE;
+ } else {
+ NS_WARNING(
+ nsPrintfCString("Requested feature bit '%d' is not recognized.",
+ static_cast<int>(feature))
+ .get());
+ return Nothing();
+ }
+ }
+ return Some(bits);
+}
+
+Adapter::Adapter(Instance* const aParent, WebGPUChild* const aBridge,
+ const ffi::WGPUAdapterInformation& aInfo)
+ : ChildOf(aParent),
+ mBridge(aBridge),
+ mId(aInfo.id),
+ mFeatures(new SupportedFeatures(this)),
+ mLimits(
+ new SupportedLimits(this, MakeUnique<ffi::WGPULimits>(aInfo.limits))),
+ mIsFallbackAdapter(aInfo.ty == ffi::WGPUDeviceType_Cpu) {
+ ErrorResult result; // TODO: should this come from outside
+ // This list needs to match `AdapterRequestDevice`
+ if (aInfo.features & WGPUFeatures_DEPTH_CLIP_CONTROL) {
+ dom::GPUSupportedFeatures_Binding::SetlikeHelpers::Add(
+ mFeatures, u"depth-clip-control"_ns, result);
+ }
+ if (aInfo.features & WGPUFeatures_TEXTURE_COMPRESSION_BC) {
+ dom::GPUSupportedFeatures_Binding::SetlikeHelpers::Add(
+ mFeatures, u"texture-compression-bc"_ns, result);
+ }
+ if (aInfo.features & WGPUFeatures_INDIRECT_FIRST_INSTANCE) {
+ dom::GPUSupportedFeatures_Binding::SetlikeHelpers::Add(
+ mFeatures, u"indirect-first-instance"_ns, result);
+ }
+}
+
+Adapter::~Adapter() { Cleanup(); }
+
+void Adapter::Cleanup() {
+ if (mValid && mBridge && mBridge->CanSend()) {
+ mValid = false;
+ mBridge->SendAdapterDestroy(mId);
+ }
+}
+
+const RefPtr<SupportedFeatures>& Adapter::Features() const { return mFeatures; }
+const RefPtr<SupportedLimits>& Adapter::Limits() const { return mLimits; }
+
+already_AddRefed<dom::Promise> Adapter::RequestDevice(
+ const dom::GPUDeviceDescriptor& aDesc, ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!mBridge->CanSend()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "WebGPUChild cannot send, must recreate Adapter");
+ return promise.forget();
+ }
+
+ ffi::WGPULimits limits = {};
+ auto request = mBridge->AdapterRequestDevice(mId, aDesc, &limits);
+ if (request) {
+ RefPtr<Device> device =
+ new Device(this, request->mId, MakeUnique<ffi::WGPULimits>(limits));
+ // copy over the features
+ for (const auto& feature : aDesc.mRequiredFeatures) {
+ NS_ConvertASCIItoUTF16 string(
+ dom::GPUFeatureNameValues::GetString(feature));
+ dom::GPUSupportedFeatures_Binding::SetlikeHelpers::Add(device->mFeatures,
+ string, aRv);
+ }
+
+ request->mPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, device](bool aSuccess) {
+ if (aSuccess) {
+ promise->MaybeResolve(device);
+ } else {
+ // In this path, request->mId has an error entry in the wgpu
+ // registry, so let Device::~Device clean things up on both the
+ // child and parent side.
+ promise->MaybeRejectWithInvalidStateError(
+ "Unable to fulfill requested features and limits");
+ }
+ },
+ [promise, device](const ipc::ResponseRejectReason& aReason) {
+ // We can't be sure how far along the WebGPUParent got in handling
+ // our AdapterRequestDevice message, but we can't communicate with it,
+ // so clear up our client state for this Device without trying to
+ // communicate with the parent about it.
+ device->CleanupUnregisteredInParent();
+ promise->MaybeRejectWithNotSupportedError("IPC error");
+ });
+ } else {
+ promise->MaybeRejectWithNotSupportedError("Unable to instantiate a Device");
+ }
+
+ return promise.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Adapter.h b/dom/webgpu/Adapter.h
new file mode 100644
index 0000000000..c2477b8c89
--- /dev/null
+++ b/dom/webgpu/Adapter.h
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+#ifndef GPU_Adapter_H_
+#define GPU_Adapter_H_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsString.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class Promise;
+struct GPUDeviceDescriptor;
+struct GPUExtensions;
+struct GPUFeatures;
+enum class GPUFeatureName : uint8_t;
+template <typename T>
+class Sequence;
+} // namespace dom
+
+namespace webgpu {
+class Device;
+class Instance;
+class SupportedFeatures;
+class SupportedLimits;
+class WebGPUChild;
+namespace ffi {
+struct WGPUAdapterInformation;
+} // namespace ffi
+
+class Adapter final : public ObjectBase, public ChildOf<Instance> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(Adapter)
+ GPU_DECL_JS_WRAP(Adapter)
+
+ RefPtr<WebGPUChild> mBridge;
+
+ static Maybe<uint32_t> MakeFeatureBits(
+ const dom::Sequence<dom::GPUFeatureName>& aFeatures);
+
+ private:
+ ~Adapter();
+ void Cleanup();
+
+ const RawId mId;
+ const nsString mName;
+ // Cant have them as `const` right now, since we wouldn't be able
+ // to unlink them in CC unlink.
+ RefPtr<SupportedFeatures> mFeatures;
+ RefPtr<SupportedLimits> mLimits;
+ const bool mIsFallbackAdapter = false;
+
+ public:
+ Adapter(Instance* const aParent, WebGPUChild* const aBridge,
+ const ffi::WGPUAdapterInformation& aInfo);
+ void GetName(nsString& out) const { out = mName; }
+ const RefPtr<SupportedFeatures>& Features() const;
+ const RefPtr<SupportedLimits>& Limits() const;
+ bool IsFallbackAdapter() const { return mIsFallbackAdapter; }
+
+ already_AddRefed<dom::Promise> RequestDevice(
+ const dom::GPUDeviceDescriptor& aDesc, ErrorResult& aRv);
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_Adapter_H_
diff --git a/dom/webgpu/BindGroup.cpp b/dom/webgpu/BindGroup.cpp
new file mode 100644
index 0000000000..d03aaefee9
--- /dev/null
+++ b/dom/webgpu/BindGroup.cpp
@@ -0,0 +1,36 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "BindGroup.h"
+#include "ipc/WebGPUChild.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(BindGroup, mParent)
+GPU_IMPL_JS_WRAP(BindGroup)
+
+BindGroup::BindGroup(Device* const aParent, RawId aId)
+ : ChildOf(aParent), mId(aId) {
+ if (!aId) {
+ mValid = false;
+ }
+}
+
+BindGroup::~BindGroup() { Cleanup(); }
+
+void BindGroup::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendBindGroupDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/BindGroup.h b/dom/webgpu/BindGroup.h
new file mode 100644
index 0000000000..4f67c906f3
--- /dev/null
+++ b/dom/webgpu/BindGroup.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef GPU_BindGroup_H_
+#define GPU_BindGroup_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class BindGroup final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(BindGroup)
+ GPU_DECL_JS_WRAP(BindGroup)
+
+ BindGroup(Device* const aParent, RawId aId);
+
+ const RawId mId;
+
+ private:
+ ~BindGroup();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_BindGroup_H_
diff --git a/dom/webgpu/BindGroupLayout.cpp b/dom/webgpu/BindGroupLayout.cpp
new file mode 100644
index 0000000000..27ecbd3684
--- /dev/null
+++ b/dom/webgpu/BindGroupLayout.cpp
@@ -0,0 +1,36 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "BindGroupLayout.h"
+#include "ipc/WebGPUChild.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(BindGroupLayout, mParent)
+GPU_IMPL_JS_WRAP(BindGroupLayout)
+
+BindGroupLayout::BindGroupLayout(Device* const aParent, RawId aId, bool aOwning)
+ : ChildOf(aParent), mId(aId), mOwning(aOwning) {
+ if (!aId) {
+ mValid = false;
+ }
+}
+
+BindGroupLayout::~BindGroupLayout() { Cleanup(); }
+
+void BindGroupLayout::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (mOwning && bridge && bridge->IsOpen()) {
+ bridge->SendBindGroupLayoutDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/BindGroupLayout.h b/dom/webgpu/BindGroupLayout.h
new file mode 100644
index 0000000000..fcd721ab5f
--- /dev/null
+++ b/dom/webgpu/BindGroupLayout.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef GPU_BindGroupLayout_H_
+#define GPU_BindGroupLayout_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class BindGroupLayout final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(BindGroupLayout)
+ GPU_DECL_JS_WRAP(BindGroupLayout)
+
+ BindGroupLayout(Device* const aParent, RawId aId, bool aOwning);
+
+ const RawId mId;
+ const bool mOwning;
+
+ private:
+ ~BindGroupLayout();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_BindGroupLayout_H_
diff --git a/dom/webgpu/Buffer.cpp b/dom/webgpu/Buffer.cpp
new file mode 100644
index 0000000000..4222ec9da9
--- /dev/null
+++ b/dom/webgpu/Buffer.cpp
@@ -0,0 +1,335 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "Buffer.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/ipc/Shmem.h"
+#include "ipc/WebGPUChild.h"
+#include "js/ArrayBuffer.h"
+#include "js/RootingAPI.h"
+#include "nsContentUtils.h"
+#include "nsWrapperCache.h"
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_JS_WRAP(Buffer)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Buffer)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Buffer)
+ tmp->Drop();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Buffer)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Buffer)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ if (tmp->mMapped) {
+ for (uint32_t i = 0; i < tmp->mMapped->mArrayBuffers.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(
+ mMapped->mArrayBuffers[i])
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+Buffer::Buffer(Device* const aParent, RawId aId, BufferAddress aSize,
+ uint32_t aUsage, ipc::WritableSharedMemoryMapping&& aShmem)
+ : ChildOf(aParent), mId(aId), mSize(aSize), mUsage(aUsage) {
+ mozilla::HoldJSObjects(this);
+ mShmem =
+ std::make_shared<ipc::WritableSharedMemoryMapping>(std::move(aShmem));
+ MOZ_ASSERT(mParent);
+}
+
+Buffer::~Buffer() {
+ Drop();
+ mozilla::DropJSObjects(this);
+}
+
+already_AddRefed<Buffer> Buffer::Create(Device* aDevice, RawId aDeviceId,
+ const dom::GPUBufferDescriptor& aDesc,
+ ErrorResult& aRv) {
+ if (aDevice->IsLost()) {
+ RefPtr<Buffer> buffer = new Buffer(aDevice, 0, aDesc.mSize, 0,
+ ipc::WritableSharedMemoryMapping());
+ return buffer.forget();
+ }
+
+ RefPtr<WebGPUChild> actor = aDevice->GetBridge();
+
+ auto handle = ipc::UnsafeSharedMemoryHandle();
+ auto mapping = ipc::WritableSharedMemoryMapping();
+
+ bool hasMapFlags = aDesc.mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
+ dom::GPUBufferUsage_Binding::MAP_READ);
+ if (hasMapFlags || aDesc.mMappedAtCreation) {
+ const auto checked = CheckedInt<size_t>(aDesc.mSize);
+ if (!checked.isValid()) {
+ aRv.ThrowRangeError("Mappable size is too large");
+ return nullptr;
+ }
+ size_t size = checked.value();
+
+ auto maybeShmem = ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
+
+ if (maybeShmem.isNothing()) {
+ aRv.ThrowAbortError(
+ nsPrintfCString("Unable to allocate shmem of size %" PRIuPTR, size));
+ return nullptr;
+ }
+
+ handle = std::move(maybeShmem.ref().first);
+ mapping = std::move(maybeShmem.ref().second);
+
+ MOZ_RELEASE_ASSERT(mapping.Size() >= size);
+
+ // zero out memory
+ memset(mapping.Bytes().data(), 0, size);
+ }
+
+ RawId id = actor->DeviceCreateBuffer(aDeviceId, aDesc, std::move(handle));
+
+ RefPtr<Buffer> buffer =
+ new Buffer(aDevice, id, aDesc.mSize, aDesc.mUsage, std::move(mapping));
+ if (aDesc.mMappedAtCreation) {
+ // Mapped at creation's raison d'être is write access, since the buffer is
+ // being created and there isn't anything interesting to read in it yet.
+ bool writable = true;
+ buffer->SetMapped(0, aDesc.mSize, writable);
+ }
+
+ return buffer.forget();
+}
+
+void Buffer::Drop() {
+ AbortMapRequest();
+
+ if (mMapped && !mMapped->mArrayBuffers.IsEmpty()) {
+ // The array buffers could live longer than us and our shmem, so make sure
+ // we clear the external buffer bindings.
+ dom::AutoJSAPI jsapi;
+ if (jsapi.Init(GetDevice().GetOwnerGlobal())) {
+ IgnoredErrorResult rv;
+ UnmapArrayBuffers(jsapi.cx(), rv);
+ }
+ }
+ mMapped.reset();
+
+ if (mValid && !GetDevice().IsLost()) {
+ GetDevice().GetBridge()->SendBufferDrop(mId);
+ }
+ mValid = false;
+}
+
+void Buffer::SetMapped(BufferAddress aOffset, BufferAddress aSize,
+ bool aWritable) {
+ MOZ_ASSERT(!mMapped);
+ MOZ_RELEASE_ASSERT(aOffset <= mSize);
+ MOZ_RELEASE_ASSERT(aSize <= mSize - aOffset);
+
+ mMapped.emplace();
+ mMapped->mWritable = aWritable;
+ mMapped->mOffset = aOffset;
+ mMapped->mSize = aSize;
+}
+
+already_AddRefed<dom::Promise> Buffer::MapAsync(
+ uint32_t aMode, uint64_t aOffset, const dom::Optional<uint64_t>& aSize,
+ ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (GetDevice().IsLost()) {
+ promise->MaybeRejectWithOperationError("Device Lost");
+ return promise.forget();
+ }
+
+ if (mMapRequest) {
+ promise->MaybeRejectWithOperationError("Buffer mapping is already pending");
+ return promise.forget();
+ }
+
+ BufferAddress size = 0;
+ if (aSize.WasPassed()) {
+ size = aSize.Value();
+ } else if (aOffset <= mSize) {
+ // Default to passing the reminder of the buffer after the provided offset.
+ size = mSize - aOffset;
+ } else {
+ // The provided offset is larger than the buffer size.
+ // The parent side will handle the error, we can let the requested size be
+ // zero.
+ }
+
+ RefPtr<Buffer> self(this);
+
+ auto mappingPromise =
+ GetDevice().GetBridge()->SendBufferMap(mId, aMode, aOffset, size);
+ MOZ_ASSERT(mappingPromise);
+
+ mMapRequest = promise;
+
+ mappingPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, self](BufferMapResult&& aResult) {
+ // Unmap might have been called while the result was on the way back.
+ if (promise->State() != dom::Promise::PromiseState::Pending) {
+ return;
+ }
+
+ switch (aResult.type()) {
+ case BufferMapResult::TBufferMapSuccess: {
+ auto& success = aResult.get_BufferMapSuccess();
+ self->mMapRequest = nullptr;
+ self->SetMapped(success.offset(), success.size(),
+ success.writable());
+ promise->MaybeResolve(0);
+ break;
+ }
+ case BufferMapResult::TBufferMapError: {
+ auto& error = aResult.get_BufferMapError();
+ self->RejectMapRequest(promise, error.message());
+ break;
+ }
+ default: {
+ MOZ_CRASH("unreachable");
+ }
+ }
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithAbortError("Internal communication error!");
+ });
+
+ return promise.forget();
+}
+
+static void ExternalBufferFreeCallback(void* aContents, void* aUserData) {
+ Unused << aContents;
+ auto shm = static_cast<std::shared_ptr<ipc::WritableSharedMemoryMapping>*>(
+ aUserData);
+ delete shm;
+}
+
+void Buffer::GetMappedRange(JSContext* aCx, uint64_t aOffset,
+ const dom::Optional<uint64_t>& aSize,
+ JS::Rooted<JSObject*>* aObject, ErrorResult& aRv) {
+ if (!mMapped) {
+ aRv.ThrowInvalidStateError("Buffer is not mapped");
+ return;
+ }
+
+ const auto checkedOffset = CheckedInt<size_t>(aOffset);
+ const auto checkedSize = aSize.WasPassed()
+ ? CheckedInt<size_t>(aSize.Value())
+ : CheckedInt<size_t>(mSize) - aOffset;
+ const auto checkedMinBufferSize = checkedOffset + checkedSize;
+
+ if (!checkedOffset.isValid() || !checkedSize.isValid() ||
+ !checkedMinBufferSize.isValid() || aOffset < mMapped->mOffset ||
+ checkedMinBufferSize.value() > mMapped->mOffset + mMapped->mSize) {
+ aRv.ThrowRangeError("Invalid range");
+ return;
+ }
+
+ auto offset = checkedOffset.value();
+ auto size = checkedSize.value();
+ auto span = mShmem->Bytes().Subspan(offset, size);
+
+ std::shared_ptr<ipc::WritableSharedMemoryMapping>* userData =
+ new std::shared_ptr<ipc::WritableSharedMemoryMapping>(mShmem);
+ auto* const arrayBuffer = JS::NewExternalArrayBuffer(
+ aCx, size, span.data(), &ExternalBufferFreeCallback, userData);
+
+ if (!arrayBuffer) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ aObject->set(arrayBuffer);
+ mMapped->mArrayBuffers.AppendElement(*aObject);
+}
+
+void Buffer::UnmapArrayBuffers(JSContext* aCx, ErrorResult& aRv) {
+ MOZ_ASSERT(mMapped);
+
+ bool detachedArrayBuffers = true;
+ for (const auto& arrayBuffer : mMapped->mArrayBuffers) {
+ JS::Rooted<JSObject*> rooted(aCx, arrayBuffer);
+ if (!JS::DetachArrayBuffer(aCx, rooted)) {
+ detachedArrayBuffers = false;
+ }
+ };
+
+ mMapped->mArrayBuffers.Clear();
+
+ AbortMapRequest();
+
+ if (NS_WARN_IF(!detachedArrayBuffers)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+}
+
+void Buffer::RejectMapRequest(dom::Promise* aPromise, nsACString& message) {
+ if (mMapRequest == aPromise) {
+ mMapRequest = nullptr;
+ }
+
+ aPromise->MaybeRejectWithOperationError(message);
+}
+
+void Buffer::AbortMapRequest() {
+ if (mMapRequest) {
+ mMapRequest->MaybeRejectWithAbortError("Buffer unmapped");
+ }
+ mMapRequest = nullptr;
+}
+
+void Buffer::Unmap(JSContext* aCx, ErrorResult& aRv) {
+ if (!mMapped) {
+ return;
+ }
+
+ UnmapArrayBuffers(aCx, aRv);
+
+ bool hasMapFlags = mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
+ dom::GPUBufferUsage_Binding::MAP_READ);
+
+ if (!hasMapFlags) {
+ // We get here if the buffer was mapped at creation without map flags.
+ // It won't be possible to map the buffer again so we can get rid of
+ // our shmem on this side.
+ mShmem = std::make_shared<ipc::WritableSharedMemoryMapping>();
+ }
+
+ if (!GetDevice().IsLost()) {
+ GetDevice().GetBridge()->SendBufferUnmap(GetDevice().mId, mId,
+ mMapped->mWritable);
+ }
+
+ mMapped.reset();
+}
+
+void Buffer::Destroy(JSContext* aCx, ErrorResult& aRv) {
+ if (mMapped) {
+ Unmap(aCx, aRv);
+ }
+
+ if (!GetDevice().IsLost()) {
+ GetDevice().GetBridge()->SendBufferDestroy(mId);
+ }
+ // TODO: we don't have to implement it right now, but it's used by the
+ // examples
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Buffer.h b/dom/webgpu/Buffer.h
new file mode 100644
index 0000000000..3f77ab8381
--- /dev/null
+++ b/dom/webgpu/Buffer.h
@@ -0,0 +1,90 @@
+/* -*- 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/. */
+
+#ifndef GPU_BUFFER_H_
+#define GPU_BUFFER_H_
+
+#include "js/RootingAPI.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsTArray.h"
+#include "ObjectModel.h"
+#include "mozilla/ipc/RawShmem.h"
+#include <memory>
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+struct GPUBufferDescriptor;
+template <typename T>
+class Optional;
+} // namespace dom
+
+namespace webgpu {
+
+class Device;
+
+struct MappedInfo {
+ // True if mapping is requested for writing.
+ bool mWritable = false;
+ // Populated by `GetMappedRange`.
+ nsTArray<JS::Heap<JSObject*>> mArrayBuffers;
+ BufferAddress mOffset;
+ BufferAddress mSize;
+ MappedInfo() = default;
+ MappedInfo(const MappedInfo&) = delete;
+};
+
+class Buffer final : public ObjectBase, public ChildOf<Device> {
+ public:
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Buffer)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Buffer)
+ GPU_DECL_JS_WRAP(Buffer)
+
+ static already_AddRefed<Buffer> Create(Device* aDevice, RawId aDeviceId,
+ const dom::GPUBufferDescriptor& aDesc,
+ ErrorResult& aRv);
+
+ already_AddRefed<dom::Promise> MapAsync(uint32_t aMode, uint64_t aOffset,
+ const dom::Optional<uint64_t>& aSize,
+ ErrorResult& aRv);
+ void GetMappedRange(JSContext* aCx, uint64_t aOffset,
+ const dom::Optional<uint64_t>& aSize,
+ JS::Rooted<JSObject*>* aObject, ErrorResult& aRv);
+ void Unmap(JSContext* aCx, ErrorResult& aRv);
+ void Destroy(JSContext* aCx, ErrorResult& aRv);
+
+ const RawId mId;
+
+ private:
+ Buffer(Device* const aParent, RawId aId, BufferAddress aSize, uint32_t aUsage,
+ ipc::WritableSharedMemoryMapping&& aShmem);
+ virtual ~Buffer();
+ Device& GetDevice() { return *mParent; }
+ void Drop();
+ void UnmapArrayBuffers(JSContext* aCx, ErrorResult& aRv);
+ void RejectMapRequest(dom::Promise* aPromise, nsACString& message);
+ void AbortMapRequest();
+ void SetMapped(BufferAddress aOffset, BufferAddress aSize, bool aWritable);
+
+ // Note: we can't map a buffer with the size that don't fit into `size_t`
+ // (which may be smaller than `BufferAddress`), but general not all buffers
+ // are mapped.
+ const BufferAddress mSize;
+ const uint32_t mUsage;
+ nsString mLabel;
+ // Information about the currently active mapping.
+ Maybe<MappedInfo> mMapped;
+ RefPtr<dom::Promise> mMapRequest;
+ // mShmem does not point to a shared memory segment if the buffer is not
+ // mappable.
+ std::shared_ptr<ipc::WritableSharedMemoryMapping> mShmem;
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_BUFFER_H_
diff --git a/dom/webgpu/CanvasContext.cpp b/dom/webgpu/CanvasContext.cpp
new file mode 100644
index 0000000000..36b538f58f
--- /dev/null
+++ b/dom/webgpu/CanvasContext.cpp
@@ -0,0 +1,230 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "CanvasContext.h"
+#include "gfxUtils.h"
+#include "LayerUserData.h"
+#include "nsDisplayList.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/gfx/CanvasManagerChild.h"
+#include "mozilla/layers/CanvasRenderer.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderCanvasRenderer.h"
+#include "ipc/WebGPUChild.h"
+
+namespace mozilla::webgpu {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasContext)
+
+GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(CanvasContext, mTexture,
+ mBridge, mCanvasElement,
+ mOffscreenCanvas)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+CanvasContext::CanvasContext() = default;
+
+CanvasContext::~CanvasContext() {
+ Cleanup();
+ RemovePostRefreshObserver();
+}
+
+void CanvasContext::Cleanup() { Unconfigure(); }
+
+JSObject* CanvasContext::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GPUCanvasContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aDesc) {
+ Unconfigure();
+
+ // these formats are guaranteed by the spec
+ switch (aDesc.mFormat) {
+ case dom::GPUTextureFormat::Rgba8unorm:
+ case dom::GPUTextureFormat::Rgba8unorm_srgb:
+ mGfxFormat = gfx::SurfaceFormat::R8G8B8A8;
+ break;
+ case dom::GPUTextureFormat::Bgra8unorm:
+ case dom::GPUTextureFormat::Bgra8unorm_srgb:
+ mGfxFormat = gfx::SurfaceFormat::B8G8R8A8;
+ break;
+ default:
+ NS_WARNING("Specified swap chain format is not supported");
+ return;
+ }
+
+ gfx::IntSize actualSize(mWidth, mHeight);
+ mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
+ mTexture = aDesc.mDevice->InitSwapChain(aDesc, *mRemoteTextureOwnerId,
+ mGfxFormat, &actualSize);
+ if (!mTexture) {
+ Unconfigure();
+ return;
+ }
+
+ mTexture->mTargetContext = this;
+ mBridge = aDesc.mDevice->GetBridge();
+ mGfxSize = actualSize;
+
+ ForceNewFrame();
+}
+
+void CanvasContext::Unconfigure() {
+ if (mBridge && mBridge->IsOpen() && mRemoteTextureOwnerId.isSome()) {
+ mBridge->SendSwapChainDestroy(*mRemoteTextureOwnerId);
+ }
+ mRemoteTextureOwnerId = Nothing();
+ mBridge = nullptr;
+ mTexture = nullptr;
+ mGfxFormat = gfx::SurfaceFormat::UNKNOWN;
+}
+
+dom::GPUTextureFormat CanvasContext::GetPreferredFormat(Adapter&) const {
+ return dom::GPUTextureFormat::Bgra8unorm;
+}
+
+RefPtr<Texture> CanvasContext::GetCurrentTexture(ErrorResult& aRv) {
+ if (!mTexture) {
+ aRv.ThrowOperationError("Canvas not configured");
+ return nullptr;
+ }
+ return mTexture;
+}
+
+void CanvasContext::MaybeQueueSwapChainPresent() {
+ if (mPendingSwapChainPresent) {
+ return;
+ }
+
+ mPendingSwapChainPresent = true;
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(
+ NewCancelableRunnableMethod("CanvasContext::SwapChainPresent", this,
+ &CanvasContext::SwapChainPresent)));
+}
+
+void CanvasContext::SwapChainPresent() {
+ mPendingSwapChainPresent = false;
+ if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing() ||
+ !mTexture) {
+ return;
+ }
+ mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
+ mBridge->SwapChainPresent(mTexture->mId, *mLastRemoteTextureId,
+ *mRemoteTextureOwnerId);
+}
+
+bool CanvasContext::UpdateWebRenderCanvasData(
+ mozilla::nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
+ auto* renderer = aCanvasData->GetCanvasRenderer();
+
+ if (renderer && mRemoteTextureOwnerId.isSome() &&
+ renderer->GetRemoteTextureOwnerIdOfPushCallback() ==
+ mRemoteTextureOwnerId) {
+ return true;
+ }
+
+ renderer = aCanvasData->CreateCanvasRenderer();
+ if (!InitializeCanvasRenderer(aBuilder, renderer)) {
+ // Clear CanvasRenderer of WebRenderCanvasData
+ aCanvasData->ClearCanvasRenderer();
+ return false;
+ }
+ return true;
+}
+
+bool CanvasContext::InitializeCanvasRenderer(
+ nsDisplayListBuilder* aBuilder, layers::CanvasRenderer* aRenderer) {
+ if (mRemoteTextureOwnerId.isNothing()) {
+ return false;
+ }
+
+ layers::CanvasRendererData data;
+ data.mContext = this;
+ data.mSize = mGfxSize;
+ data.mIsOpaque = false;
+ data.mRemoteTextureOwnerIdOfPushCallback = mRemoteTextureOwnerId;
+
+ aRenderer->Initialize(data);
+ aRenderer->SetDirty();
+ return true;
+}
+
+mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(int32_t* aFormat) {
+ gfxAlphaType any;
+ RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
+ if (!snapshot) {
+ *aFormat = 0;
+ return nullptr;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
+ return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
+ aFormat);
+}
+
+NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** aStream) {
+ gfxAlphaType any;
+ RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
+ if (!snapshot) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
+ return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true,
+ aMimeType, aEncoderOptions, aStream);
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface> CanvasContext::GetSurfaceSnapshot(
+ gfxAlphaType* aOutAlphaType) {
+ if (aOutAlphaType) {
+ *aOutAlphaType = gfxAlphaType::Premult;
+ }
+
+ auto* const cm = gfx::CanvasManagerChild::Get();
+ if (!cm) {
+ return nullptr;
+ }
+
+ if (!mBridge || !mBridge->IsOpen() || mRemoteTextureOwnerId.isNothing()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mRemoteTextureOwnerId.isSome());
+ return cm->GetSnapshot(cm->Id(), mBridge->Id(), mRemoteTextureOwnerId,
+ mGfxFormat, /* aPremultiply */ false,
+ /* aYFlip */ false);
+}
+
+void CanvasContext::ForceNewFrame() {
+ if (!mCanvasElement && !mOffscreenCanvas) {
+ return;
+ }
+
+ // Force a new frame to be built, which will execute the
+ // `CanvasContextType::WebGPU` switch case in `CreateWebRenderCommands` and
+ // populate the WR user data.
+ if (mCanvasElement) {
+ mCanvasElement->InvalidateCanvas();
+ } else if (mOffscreenCanvas) {
+ dom::OffscreenCanvasDisplayData data;
+ data.mSize = {mWidth, mHeight};
+ data.mIsOpaque = false;
+ data.mOwnerId = mRemoteTextureOwnerId;
+ mOffscreenCanvas->UpdateDisplayData(data);
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/CanvasContext.h b/dom/webgpu/CanvasContext.h
new file mode 100644
index 0000000000..60743ec47b
--- /dev/null
+++ b/dom/webgpu/CanvasContext.h
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+
+#ifndef GPU_CanvasContext_H_
+#define GPU_CanvasContext_H_
+
+#include "nsICanvasRenderingContextInternal.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+
+namespace mozilla {
+namespace dom {
+class Promise;
+struct GPUCanvasConfiguration;
+enum class GPUTextureFormat : uint8_t;
+} // namespace dom
+namespace webgpu {
+class Adapter;
+class Texture;
+
+class CanvasContext final : public nsICanvasRenderingContextInternal,
+ public nsWrapperCache {
+ private:
+ virtual ~CanvasContext();
+ void Cleanup();
+
+ public:
+ // nsISupports interface + CC
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CanvasContext)
+
+ CanvasContext();
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ public: // nsICanvasRenderingContextInternal
+ int32_t GetWidth() override { return mWidth; }
+ int32_t GetHeight() override { return mHeight; }
+
+ NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override {
+ mWidth = aWidth;
+ mHeight = aHeight;
+ return NS_OK;
+ }
+ NS_IMETHOD InitializeWithDrawTarget(
+ nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) override {
+ return NS_OK;
+ }
+
+ bool UpdateWebRenderCanvasData(mozilla::nsDisplayListBuilder* aBuilder,
+ WebRenderCanvasData* aCanvasData) override;
+
+ bool InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder,
+ layers::CanvasRenderer* aRenderer) override;
+ mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
+ NS_IMETHOD GetInputStream(const char* aMimeType,
+ const nsAString& aEncoderOptions,
+ nsIInputStream** aStream) override;
+ already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
+ gfxAlphaType* aOutAlphaType) override;
+
+ void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) override {}
+ bool GetIsOpaque() override { return true; }
+
+ void ResetBitmap() override { Unconfigure(); }
+
+ void MarkContextClean() override {}
+
+ NS_IMETHOD Redraw(const gfxRect& aDirty) override { return NS_OK; }
+
+ void DidRefresh() override {}
+
+ void MarkContextCleanForFrameCapture() override {}
+ Watchable<FrameCaptureState>* GetFrameCaptureState() override {
+ return nullptr;
+ }
+
+ public:
+ void Configure(const dom::GPUCanvasConfiguration& aDesc);
+ void Unconfigure();
+
+ dom::GPUTextureFormat GetPreferredFormat(Adapter& aAdapter) const;
+ RefPtr<Texture> GetCurrentTexture(ErrorResult& aRv);
+ void MaybeQueueSwapChainPresent();
+ void SwapChainPresent();
+ void ForceNewFrame();
+
+ private:
+ uint32_t mWidth = 0, mHeight = 0;
+ bool mPendingSwapChainPresent = false;
+
+ RefPtr<WebGPUChild> mBridge;
+ RefPtr<Texture> mTexture;
+ gfx::SurfaceFormat mGfxFormat = gfx::SurfaceFormat::R8G8B8A8;
+ gfx::IntSize mGfxSize;
+
+ Maybe<layers::RemoteTextureId> mLastRemoteTextureId;
+ Maybe<layers::RemoteTextureOwnerId> mRemoteTextureOwnerId;
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_CanvasContext_H_
diff --git a/dom/webgpu/CommandBuffer.cpp b/dom/webgpu/CommandBuffer.cpp
new file mode 100644
index 0000000000..2ba8fd0420
--- /dev/null
+++ b/dom/webgpu/CommandBuffer.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "CommandBuffer.h"
+#include "ipc/WebGPUChild.h"
+
+#include "mozilla/webgpu/CanvasContext.h"
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(CommandBuffer, mParent)
+GPU_IMPL_JS_WRAP(CommandBuffer)
+
+CommandBuffer::CommandBuffer(Device* const aParent, RawId aId,
+ nsTArray<WeakPtr<CanvasContext>>&& aTargetContexts)
+ : ChildOf(aParent), mId(aId), mTargetContexts(std::move(aTargetContexts)) {
+ if (!aId) {
+ mValid = false;
+ }
+}
+
+CommandBuffer::~CommandBuffer() { Cleanup(); }
+
+void CommandBuffer::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendCommandBufferDestroy(mId);
+ }
+ }
+}
+
+Maybe<RawId> CommandBuffer::Commit() {
+ if (!mValid) {
+ return Nothing();
+ }
+ mValid = false;
+ for (const auto& targetContext : mTargetContexts) {
+ if (targetContext) {
+ targetContext->MaybeQueueSwapChainPresent();
+ }
+ }
+ return Some(mId);
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/CommandBuffer.h b/dom/webgpu/CommandBuffer.h
new file mode 100644
index 0000000000..be525d98f3
--- /dev/null
+++ b/dom/webgpu/CommandBuffer.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef GPU_CommandBuffer_H_
+#define GPU_CommandBuffer_H_
+
+#include "mozilla/WeakPtr.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+
+class CanvasContext;
+class Device;
+
+class CommandBuffer final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(CommandBuffer)
+ GPU_DECL_JS_WRAP(CommandBuffer)
+
+ CommandBuffer(Device* const aParent, RawId aId,
+ nsTArray<WeakPtr<CanvasContext>>&& aTargetContexts);
+
+ Maybe<RawId> Commit();
+
+ private:
+ CommandBuffer() = delete;
+ ~CommandBuffer();
+ void Cleanup();
+
+ const RawId mId;
+ const nsTArray<WeakPtr<CanvasContext>> mTargetContexts;
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_CommandBuffer_H_
diff --git a/dom/webgpu/CommandEncoder.cpp b/dom/webgpu/CommandEncoder.cpp
new file mode 100644
index 0000000000..fb6d3c07b6
--- /dev/null
+++ b/dom/webgpu/CommandEncoder.cpp
@@ -0,0 +1,252 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "CommandEncoder.h"
+
+#include "CommandBuffer.h"
+#include "Buffer.h"
+#include "ComputePassEncoder.h"
+#include "Device.h"
+#include "RenderPassEncoder.h"
+#include "mozilla/webgpu/CanvasContext.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "ipc/WebGPUChild.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(CommandEncoder, mParent, mBridge)
+GPU_IMPL_JS_WRAP(CommandEncoder)
+
+void CommandEncoder::ConvertTextureDataLayoutToFFI(
+ const dom::GPUImageDataLayout& aLayout,
+ ffi::WGPUImageDataLayout* aLayoutFFI) {
+ *aLayoutFFI = {};
+ aLayoutFFI->offset = aLayout.mOffset;
+ aLayoutFFI->bytes_per_row = aLayout.mBytesPerRow;
+ aLayoutFFI->rows_per_image = aLayout.mRowsPerImage;
+}
+
+void CommandEncoder::ConvertTextureCopyViewToFFI(
+ const dom::GPUImageCopyTexture& aCopy,
+ ffi::WGPUImageCopyTexture* aViewFFI) {
+ *aViewFFI = {};
+ aViewFFI->texture = aCopy.mTexture->mId;
+ aViewFFI->mip_level = aCopy.mMipLevel;
+ if (aCopy.mOrigin.WasPassed()) {
+ const auto& origin = aCopy.mOrigin.Value();
+ if (origin.IsRangeEnforcedUnsignedLongSequence()) {
+ const auto& seq = origin.GetAsRangeEnforcedUnsignedLongSequence();
+ aViewFFI->origin.x = seq.Length() > 0 ? seq[0] : 0;
+ aViewFFI->origin.y = seq.Length() > 1 ? seq[1] : 0;
+ aViewFFI->origin.z = seq.Length() > 2 ? seq[2] : 0;
+ } else if (origin.IsGPUOrigin3DDict()) {
+ const auto& dict = origin.GetAsGPUOrigin3DDict();
+ aViewFFI->origin.x = dict.mX;
+ aViewFFI->origin.y = dict.mY;
+ aViewFFI->origin.z = dict.mZ;
+ } else {
+ MOZ_CRASH("Unexpected origin type");
+ }
+ }
+}
+
+void CommandEncoder::ConvertExtent3DToFFI(const dom::GPUExtent3D& aExtent,
+ ffi::WGPUExtent3d* aExtentFFI) {
+ *aExtentFFI = {};
+ if (aExtent.IsRangeEnforcedUnsignedLongSequence()) {
+ const auto& seq = aExtent.GetAsRangeEnforcedUnsignedLongSequence();
+ aExtentFFI->width = seq.Length() > 0 ? seq[0] : 0;
+ aExtentFFI->height = seq.Length() > 1 ? seq[1] : 0;
+ aExtentFFI->depth_or_array_layers = seq.Length() > 2 ? seq[2] : 0;
+ } else if (aExtent.IsGPUExtent3DDict()) {
+ const auto& dict = aExtent.GetAsGPUExtent3DDict();
+ aExtentFFI->width = dict.mWidth;
+ aExtentFFI->height = dict.mHeight;
+ aExtentFFI->depth_or_array_layers = dict.mDepthOrArrayLayers;
+ } else {
+ MOZ_CRASH("Unexptected extent type");
+ }
+}
+
+static ffi::WGPUImageCopyBuffer ConvertBufferCopyView(
+ const dom::GPUImageCopyBuffer& aCopy) {
+ ffi::WGPUImageCopyBuffer view = {};
+ view.buffer = aCopy.mBuffer->mId;
+ CommandEncoder::ConvertTextureDataLayoutToFFI(aCopy, &view.layout);
+ return view;
+}
+
+static ffi::WGPUImageCopyTexture ConvertTextureCopyView(
+ const dom::GPUImageCopyTexture& aCopy) {
+ ffi::WGPUImageCopyTexture view = {};
+ CommandEncoder::ConvertTextureCopyViewToFFI(aCopy, &view);
+ return view;
+}
+
+static ffi::WGPUExtent3d ConvertExtent(const dom::GPUExtent3D& aExtent) {
+ ffi::WGPUExtent3d extent = {};
+ CommandEncoder::ConvertExtent3DToFFI(aExtent, &extent);
+ return extent;
+}
+
+CommandEncoder::CommandEncoder(Device* const aParent,
+ WebGPUChild* const aBridge, RawId aId)
+ : ChildOf(aParent), mId(aId), mBridge(aBridge) {}
+
+CommandEncoder::~CommandEncoder() { Cleanup(); }
+
+void CommandEncoder::Cleanup() {
+ if (mValid) {
+ mValid = false;
+ if (mBridge->IsOpen()) {
+ mBridge->SendCommandEncoderDestroy(mId);
+ }
+ }
+}
+
+void CommandEncoder::CopyBufferToBuffer(const Buffer& aSource,
+ BufferAddress aSourceOffset,
+ const Buffer& aDestination,
+ BufferAddress aDestinationOffset,
+ BufferAddress aSize) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ ffi::wgpu_command_encoder_copy_buffer_to_buffer(
+ aSource.mId, aSourceOffset, aDestination.mId, aDestinationOffset, aSize,
+ ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+ }
+}
+
+void CommandEncoder::CopyBufferToTexture(
+ const dom::GPUImageCopyBuffer& aSource,
+ const dom::GPUImageCopyTexture& aDestination,
+ const dom::GPUExtent3D& aCopySize) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ ffi::wgpu_command_encoder_copy_buffer_to_texture(
+ ConvertBufferCopyView(aSource), ConvertTextureCopyView(aDestination),
+ ConvertExtent(aCopySize), ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+
+ const auto& targetContext = aDestination.mTexture->mTargetContext;
+ if (targetContext) {
+ mTargetContexts.AppendElement(targetContext);
+ }
+ }
+}
+void CommandEncoder::CopyTextureToBuffer(
+ const dom::GPUImageCopyTexture& aSource,
+ const dom::GPUImageCopyBuffer& aDestination,
+ const dom::GPUExtent3D& aCopySize) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ ffi::wgpu_command_encoder_copy_texture_to_buffer(
+ ConvertTextureCopyView(aSource), ConvertBufferCopyView(aDestination),
+ ConvertExtent(aCopySize), ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+ }
+}
+void CommandEncoder::CopyTextureToTexture(
+ const dom::GPUImageCopyTexture& aSource,
+ const dom::GPUImageCopyTexture& aDestination,
+ const dom::GPUExtent3D& aCopySize) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ ffi::wgpu_command_encoder_copy_texture_to_texture(
+ ConvertTextureCopyView(aSource), ConvertTextureCopyView(aDestination),
+ ConvertExtent(aCopySize), ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+
+ const auto& targetContext = aDestination.mTexture->mTargetContext;
+ if (targetContext) {
+ mTargetContexts.AppendElement(targetContext);
+ }
+ }
+}
+
+void CommandEncoder::PushDebugGroup(const nsAString& aString) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ NS_ConvertUTF16toUTF8 marker(aString);
+ ffi::wgpu_command_encoder_push_debug_group(&marker, ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+ }
+}
+void CommandEncoder::PopDebugGroup() {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ ffi::wgpu_command_encoder_pop_debug_group(ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+ }
+}
+void CommandEncoder::InsertDebugMarker(const nsAString& aString) {
+ if (mValid && mBridge->IsOpen()) {
+ ipc::ByteBuf bb;
+ NS_ConvertUTF16toUTF8 marker(aString);
+ ffi::wgpu_command_encoder_insert_debug_marker(&marker, ToFFI(&bb));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(bb));
+ }
+}
+
+already_AddRefed<ComputePassEncoder> CommandEncoder::BeginComputePass(
+ const dom::GPUComputePassDescriptor& aDesc) {
+ RefPtr<ComputePassEncoder> pass = new ComputePassEncoder(this, aDesc);
+ return pass.forget();
+}
+
+already_AddRefed<RenderPassEncoder> CommandEncoder::BeginRenderPass(
+ const dom::GPURenderPassDescriptor& aDesc) {
+ for (const auto& at : aDesc.mColorAttachments) {
+ auto* targetContext = at.mView->GetTargetContext();
+ if (targetContext) {
+ mTargetContexts.AppendElement(targetContext);
+ }
+ if (at.mResolveTarget.WasPassed()) {
+ targetContext = at.mResolveTarget.Value().GetTargetContext();
+ mTargetContexts.AppendElement(targetContext);
+ }
+ }
+
+ RefPtr<RenderPassEncoder> pass = new RenderPassEncoder(this, aDesc);
+ return pass.forget();
+}
+
+void CommandEncoder::EndComputePass(ffi::WGPUComputePass& aPass,
+ ErrorResult& aRv) {
+ if (!mValid || !mBridge->IsOpen()) {
+ return aRv.ThrowInvalidStateError("Command encoder is not valid");
+ }
+
+ ipc::ByteBuf byteBuf;
+ ffi::wgpu_compute_pass_finish(&aPass, ToFFI(&byteBuf));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(byteBuf));
+}
+
+void CommandEncoder::EndRenderPass(ffi::WGPURenderPass& aPass,
+ ErrorResult& aRv) {
+ if (!mValid || !mBridge->IsOpen()) {
+ return aRv.ThrowInvalidStateError("Command encoder is not valid");
+ }
+
+ ipc::ByteBuf byteBuf;
+ ffi::wgpu_render_pass_finish(&aPass, ToFFI(&byteBuf));
+ mBridge->SendCommandEncoderAction(mId, mParent->mId, std::move(byteBuf));
+}
+
+already_AddRefed<CommandBuffer> CommandEncoder::Finish(
+ const dom::GPUCommandBufferDescriptor& aDesc) {
+ RawId id = 0;
+ if (mValid && mBridge->IsOpen()) {
+ mValid = false;
+ id = mBridge->CommandEncoderFinish(mId, mParent->mId, aDesc);
+ }
+ RefPtr<CommandBuffer> comb =
+ new CommandBuffer(mParent, id, std::move(mTargetContexts));
+ return comb.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/CommandEncoder.h b/dom/webgpu/CommandEncoder.h
new file mode 100644
index 0000000000..5de069984e
--- /dev/null
+++ b/dom/webgpu/CommandEncoder.h
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+
+#ifndef GPU_CommandEncoder_H_
+#define GPU_CommandEncoder_H_
+
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+struct GPUComputePassDescriptor;
+template <typename T>
+class Sequence;
+struct GPUCommandBufferDescriptor;
+class GPUComputePipelineOrGPURenderPipeline;
+class RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict;
+struct GPUImageCopyBuffer;
+struct GPUImageCopyTexture;
+struct GPUImageBitmapCopyView;
+struct GPUImageDataLayout;
+struct GPURenderPassDescriptor;
+using GPUExtent3D = RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict;
+} // namespace dom
+namespace webgpu {
+namespace ffi {
+struct WGPUComputePass;
+struct WGPURenderPass;
+struct WGPUImageDataLayout;
+struct WGPUImageCopyTexture_TextureId;
+struct WGPUExtent3d;
+} // namespace ffi
+
+class BindGroup;
+class Buffer;
+class CanvasContext;
+class CommandBuffer;
+class ComputePassEncoder;
+class Device;
+class RenderPassEncoder;
+
+class CommandEncoder final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(CommandEncoder)
+ GPU_DECL_JS_WRAP(CommandEncoder)
+
+ CommandEncoder(Device* const aParent, WebGPUChild* const aBridge, RawId aId);
+
+ const RawId mId;
+
+ static void ConvertTextureDataLayoutToFFI(
+ const dom::GPUImageDataLayout& aLayout,
+ ffi::WGPUImageDataLayout* aLayoutFFI);
+ static void ConvertTextureCopyViewToFFI(
+ const dom::GPUImageCopyTexture& aCopy,
+ ffi::WGPUImageCopyTexture_TextureId* aViewFFI);
+ static void ConvertExtent3DToFFI(const dom::GPUExtent3D& aExtent,
+ ffi::WGPUExtent3d* aExtentFFI);
+
+ private:
+ ~CommandEncoder();
+ void Cleanup();
+
+ RefPtr<WebGPUChild> mBridge;
+ nsTArray<WeakPtr<CanvasContext>> mTargetContexts;
+
+ public:
+ const auto& GetDevice() const { return mParent; };
+
+ void EndComputePass(ffi::WGPUComputePass& aPass, ErrorResult& aRv);
+ void EndRenderPass(ffi::WGPURenderPass& aPass, ErrorResult& aRv);
+
+ void CopyBufferToBuffer(const Buffer& aSource, BufferAddress aSourceOffset,
+ const Buffer& aDestination,
+ BufferAddress aDestinationOffset,
+ BufferAddress aSize);
+ void CopyBufferToTexture(const dom::GPUImageCopyBuffer& aSource,
+ const dom::GPUImageCopyTexture& aDestination,
+ const dom::GPUExtent3D& aCopySize);
+ void CopyTextureToBuffer(const dom::GPUImageCopyTexture& aSource,
+ const dom::GPUImageCopyBuffer& aDestination,
+ const dom::GPUExtent3D& aCopySize);
+ void CopyTextureToTexture(const dom::GPUImageCopyTexture& aSource,
+ const dom::GPUImageCopyTexture& aDestination,
+ const dom::GPUExtent3D& aCopySize);
+
+ void PushDebugGroup(const nsAString& aString);
+ void PopDebugGroup();
+ void InsertDebugMarker(const nsAString& aString);
+
+ already_AddRefed<ComputePassEncoder> BeginComputePass(
+ const dom::GPUComputePassDescriptor& aDesc);
+ already_AddRefed<RenderPassEncoder> BeginRenderPass(
+ const dom::GPURenderPassDescriptor& aDesc);
+ already_AddRefed<CommandBuffer> Finish(
+ const dom::GPUCommandBufferDescriptor& aDesc);
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_CommandEncoder_H_
diff --git a/dom/webgpu/CompilationInfo.cpp b/dom/webgpu/CompilationInfo.cpp
new file mode 100644
index 0000000000..6f8ebf5490
--- /dev/null
+++ b/dom/webgpu/CompilationInfo.cpp
@@ -0,0 +1,34 @@
+/* -*- 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 "CompilationInfo.h"
+#include "CompilationMessage.h"
+#include "ShaderModule.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(CompilationInfo, mParent)
+GPU_IMPL_JS_WRAP(CompilationInfo)
+
+CompilationInfo::CompilationInfo(ShaderModule* const aParent)
+ : ChildOf(aParent) {}
+
+void CompilationInfo::SetMessages(
+ nsTArray<mozilla::webgpu::WebGPUCompilationMessage>& aMessages) {
+ for (auto& msg : aMessages) {
+ mMessages.AppendElement(MakeAndAddRef<mozilla::webgpu::CompilationMessage>(
+ this, msg.lineNum, msg.linePos, msg.offset, std::move(msg.message)));
+ }
+}
+
+void CompilationInfo::GetMessages(
+ nsTArray<RefPtr<mozilla::webgpu::CompilationMessage>>& aMessages) {
+ for (auto& msg : mMessages) {
+ aMessages.AppendElement(msg);
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/CompilationInfo.h b/dom/webgpu/CompilationInfo.h
new file mode 100644
index 0000000000..38d687cf25
--- /dev/null
+++ b/dom/webgpu/CompilationInfo.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+#ifndef GPU_CompilationInfo_H_
+#define GPU_CompilationInfo_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "CompilationMessage.h"
+
+namespace mozilla::webgpu {
+class ShaderModule;
+
+class CompilationInfo final : public nsWrapperCache,
+ public ChildOf<ShaderModule> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(CompilationInfo)
+ GPU_DECL_JS_WRAP(CompilationInfo)
+
+ explicit CompilationInfo(ShaderModule* const aParent);
+
+ void SetMessages(
+ nsTArray<mozilla::webgpu::WebGPUCompilationMessage>& aMessages);
+
+ void GetMessages(
+ nsTArray<RefPtr<mozilla::webgpu::CompilationMessage>>& aMessages);
+
+ private:
+ ~CompilationInfo() = default;
+ void Cleanup() {}
+
+ nsTArray<RefPtr<mozilla::webgpu::CompilationMessage>> mMessages;
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_CompilationInfo_H_
diff --git a/dom/webgpu/CompilationMessage.cpp b/dom/webgpu/CompilationMessage.cpp
new file mode 100644
index 0000000000..f0df8c1db1
--- /dev/null
+++ b/dom/webgpu/CompilationMessage.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "CompilationMessage.h"
+#include "CompilationInfo.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(CompilationMessage, mParent)
+GPU_IMPL_JS_WRAP(CompilationMessage)
+
+CompilationMessage::CompilationMessage(CompilationInfo* const aParent,
+ uint64_t aLineNum, uint64_t aLinePos,
+ uint64_t aOffset, nsString&& aMessage)
+ : ChildOf(aParent),
+ mLineNum(aLineNum),
+ mLinePos(aLinePos),
+ mOffset(aOffset),
+ mMessage(std::move(aMessage)) {}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/CompilationMessage.h b/dom/webgpu/CompilationMessage.h
new file mode 100644
index 0000000000..685a41f68e
--- /dev/null
+++ b/dom/webgpu/CompilationMessage.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef GPU_CompilationMessage_H_
+#define GPU_CompilationMessage_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla {
+namespace dom {
+class DOMString;
+} // namespace dom
+namespace webgpu {
+class CompilationInfo;
+
+class CompilationMessage final : public nsWrapperCache,
+ public ChildOf<CompilationInfo> {
+ dom::GPUCompilationMessageType mType = dom::GPUCompilationMessageType::Error;
+ uint64_t mLineNum = 0;
+ uint64_t mLinePos = 0;
+ uint64_t mOffset = 0;
+ uint64_t mLength = 0;
+ nsString mMessage;
+
+ public:
+ GPU_DECL_CYCLE_COLLECTION(CompilationMessage)
+ GPU_DECL_JS_WRAP(CompilationMessage)
+
+ explicit CompilationMessage(CompilationInfo* const aParent, uint64_t aLineNum,
+ uint64_t aLinePos, uint64_t aOffset,
+ nsString&& aMessage);
+
+ void GetMessage(dom::DOMString& aMessage) {
+ aMessage.AsAString().Assign(mMessage);
+ }
+ dom::GPUCompilationMessageType Type() const { return mType; }
+ uint64_t LineNum() const { return mLineNum; }
+ uint64_t LinePos() const { return mLinePos; }
+ uint64_t Offset() const { return mOffset; }
+ uint64_t Length() const { return mLength; }
+
+ private:
+ ~CompilationMessage() = default;
+ void Cleanup() {}
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_CompilationMessage_H_
diff --git a/dom/webgpu/ComputePassEncoder.cpp b/dom/webgpu/ComputePassEncoder.cpp
new file mode 100644
index 0000000000..8172874c22
--- /dev/null
+++ b/dom/webgpu/ComputePassEncoder.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "ComputePassEncoder.h"
+#include "BindGroup.h"
+#include "ComputePipeline.h"
+#include "CommandEncoder.h"
+
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(ComputePassEncoder, mParent, mUsedBindGroups,
+ mUsedPipelines)
+GPU_IMPL_JS_WRAP(ComputePassEncoder)
+
+ffi::WGPUComputePass* ScopedFfiComputeTraits::empty() { return nullptr; }
+
+void ScopedFfiComputeTraits::release(ffi::WGPUComputePass* raw) {
+ if (raw) {
+ ffi::wgpu_compute_pass_destroy(raw);
+ }
+}
+
+ffi::WGPUComputePass* BeginComputePass(
+ RawId aEncoderId, const dom::GPUComputePassDescriptor& aDesc) {
+ ffi::WGPUComputePassDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ return ffi::wgpu_command_encoder_begin_compute_pass(aEncoderId, &desc);
+}
+
+ComputePassEncoder::ComputePassEncoder(
+ CommandEncoder* const aParent, const dom::GPUComputePassDescriptor& aDesc)
+ : ChildOf(aParent), mPass(BeginComputePass(aParent->mId, aDesc)) {}
+
+ComputePassEncoder::~ComputePassEncoder() {
+ if (mValid) {
+ mValid = false;
+ }
+}
+
+void ComputePassEncoder::SetBindGroup(
+ uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets) {
+ if (mValid) {
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_compute_pass_set_bind_group(mPass, aSlot, aBindGroup.mId,
+ aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
+ }
+}
+
+void ComputePassEncoder::SetPipeline(const ComputePipeline& aPipeline) {
+ if (mValid) {
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_compute_pass_set_pipeline(mPass, aPipeline.mId);
+ }
+}
+
+void ComputePassEncoder::DispatchWorkgroups(uint32_t x, uint32_t y,
+ uint32_t z) {
+ if (mValid) {
+ ffi::wgpu_compute_pass_dispatch_workgroups(mPass, x, y, z);
+ }
+}
+
+void ComputePassEncoder::DispatchWorkgroupsIndirect(
+ const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) {
+ if (mValid) {
+ ffi::wgpu_compute_pass_dispatch_workgroups_indirect(
+ mPass, aIndirectBuffer.mId, aIndirectOffset);
+ }
+}
+
+void ComputePassEncoder::PushDebugGroup(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_compute_pass_push_debug_group(mPass, utf8.get(), 0);
+ }
+}
+void ComputePassEncoder::PopDebugGroup() {
+ if (mValid) {
+ ffi::wgpu_compute_pass_pop_debug_group(mPass);
+ }
+}
+void ComputePassEncoder::InsertDebugMarker(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_compute_pass_insert_debug_marker(mPass, utf8.get(), 0);
+ }
+}
+
+void ComputePassEncoder::EndPass(ErrorResult& aRv) {
+ if (mValid) {
+ mValid = false;
+ auto* pass = mPass.forget();
+ MOZ_ASSERT(pass);
+ mParent->EndComputePass(*pass, aRv);
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ComputePassEncoder.h b/dom/webgpu/ComputePassEncoder.h
new file mode 100644
index 0000000000..6d00cceac6
--- /dev/null
+++ b/dom/webgpu/ComputePassEncoder.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef GPU_ComputePassEncoder_H_
+#define GPU_ComputePassEncoder_H_
+
+#include "mozilla/Scoped.h"
+#include "mozilla/dom/TypedArray.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+struct GPUComputePassDescriptor;
+}
+
+namespace webgpu {
+namespace ffi {
+struct WGPUComputePass;
+} // namespace ffi
+
+class BindGroup;
+class Buffer;
+class CommandEncoder;
+class ComputePipeline;
+
+struct ScopedFfiComputeTraits {
+ using type = ffi::WGPUComputePass*;
+ static type empty();
+ static void release(type raw);
+};
+
+class ComputePassEncoder final : public ObjectBase,
+ public ChildOf<CommandEncoder> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(ComputePassEncoder)
+ GPU_DECL_JS_WRAP(ComputePassEncoder)
+
+ ComputePassEncoder(CommandEncoder* const aParent,
+ const dom::GPUComputePassDescriptor& aDesc);
+
+ private:
+ virtual ~ComputePassEncoder();
+ void Cleanup() {}
+
+ Scoped<ScopedFfiComputeTraits> mPass;
+ // keep all the used objects alive while the pass is recorded
+ nsTArray<RefPtr<const BindGroup>> mUsedBindGroups;
+ nsTArray<RefPtr<const ComputePipeline>> mUsedPipelines;
+
+ public:
+ // programmable pass encoder
+ void SetBindGroup(uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets);
+ // self
+ void SetPipeline(const ComputePipeline& aPipeline);
+
+ void DispatchWorkgroups(uint32_t x, uint32_t y, uint32_t z);
+ void DispatchWorkgroupsIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset);
+
+ void PushDebugGroup(const nsAString& aString);
+ void PopDebugGroup();
+ void InsertDebugMarker(const nsAString& aString);
+
+ void EndPass(ErrorResult& aRv);
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_ComputePassEncoder_H_
diff --git a/dom/webgpu/ComputePipeline.cpp b/dom/webgpu/ComputePipeline.cpp
new file mode 100644
index 0000000000..53a8031871
--- /dev/null
+++ b/dom/webgpu/ComputePipeline.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "ComputePipeline.h"
+
+#include "Device.h"
+#include "ipc/WebGPUChild.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(ComputePipeline, mParent)
+GPU_IMPL_JS_WRAP(ComputePipeline)
+
+ComputePipeline::ComputePipeline(Device* const aParent, RawId aId,
+ RawId aImplicitPipelineLayoutId,
+ nsTArray<RawId>&& aImplicitBindGroupLayoutIds)
+ : ChildOf(aParent),
+ mImplicitPipelineLayoutId(aImplicitPipelineLayoutId),
+ mImplicitBindGroupLayoutIds(std::move(aImplicitBindGroupLayoutIds)),
+ mId(aId) {}
+
+ComputePipeline::~ComputePipeline() { Cleanup(); }
+
+void ComputePipeline::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendComputePipelineDestroy(mId);
+ if (mImplicitPipelineLayoutId) {
+ bridge->SendImplicitLayoutDestroy(mImplicitPipelineLayoutId,
+ mImplicitBindGroupLayoutIds);
+ }
+ }
+ }
+}
+
+already_AddRefed<BindGroupLayout> ComputePipeline::GetBindGroupLayout(
+ uint32_t index) const {
+ const RawId id = index < mImplicitBindGroupLayoutIds.Length()
+ ? mImplicitBindGroupLayoutIds[index]
+ : 0;
+ RefPtr<BindGroupLayout> object = new BindGroupLayout(mParent, id, false);
+ return object.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ComputePipeline.h b/dom/webgpu/ComputePipeline.h
new file mode 100644
index 0000000000..5dbd972912
--- /dev/null
+++ b/dom/webgpu/ComputePipeline.h
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+#ifndef GPU_ComputePipeline_H_
+#define GPU_ComputePipeline_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsTArray.h"
+
+namespace mozilla::webgpu {
+
+class BindGroupLayout;
+class Device;
+
+class ComputePipeline final : public ObjectBase, public ChildOf<Device> {
+ const RawId mImplicitPipelineLayoutId;
+ const nsTArray<RawId> mImplicitBindGroupLayoutIds;
+
+ public:
+ GPU_DECL_CYCLE_COLLECTION(ComputePipeline)
+ GPU_DECL_JS_WRAP(ComputePipeline)
+
+ const RawId mId;
+
+ ComputePipeline(Device* const aParent, RawId aId,
+ RawId aImplicitPipelineLayoutId,
+ nsTArray<RawId>&& aImplicitBindGroupLayoutIds);
+ already_AddRefed<BindGroupLayout> GetBindGroupLayout(uint32_t index) const;
+
+ private:
+ ~ComputePipeline();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_ComputePipeline_H_
diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp
new file mode 100644
index 0000000000..3ae0f7097f
--- /dev/null
+++ b/dom/webgpu/Device.cpp
@@ -0,0 +1,385 @@
+/* -*- 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/ErrorResult.h"
+#include "mozilla/Logging.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 "ComputePipeline.h"
+#include "DeviceLostInfo.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"
+
+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)
+
+RefPtr<WebGPUChild> Device::GetBridge() { return mBridge; }
+
+Device::Device(Adapter* const aParent, RawId aId,
+ UniquePtr<ffi::WGPULimits> aRawLimits)
+ : DOMEventTargetHelper(aParent->GetParentObject()),
+ mId(aId),
+ // features are filled in Adapter::RequestDevice
+ mFeatures(new SupportedFeatures(aParent)),
+ mLimits(new SupportedLimits(aParent, std::move(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);
+ }
+
+ // Cycle collection may have disconnected the promise object.
+ if (mLostPromise && mLostPromise->PromiseObj() != nullptr) {
+ auto info = MakeRefPtr<DeviceLostInfo>(GetParentObject(),
+ dom::GPUDeviceLostReason::Destroyed,
+ u"Device destroyed"_ns);
+ mLostPromise->MaybeResolve(info);
+ }
+}
+
+void Device::CleanupUnregisteredInParent() {
+ if (mBridge) {
+ mBridge->FreeUnregisteredInParentDevice(mId);
+ }
+ mValid = false;
+}
+
+bool Device::IsLost() const { return !mBridge || !mBridge->CanSend(); }
+
+// Generate an error on the Device timeline for this device.
+//
+// aMessage is interpreted as UTF-8.
+void Device::GenerateError(const nsCString& aMessage) {
+ if (mBridge->CanSend()) {
+ mBridge->SendGenerateError(mId, aMessage);
+ }
+}
+
+void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; }
+void Device::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }
+
+dom::Promise* Device::GetLost(ErrorResult& aRv) {
+ 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;
+}
+
+already_AddRefed<Buffer> Device::CreateBuffer(
+ const dom::GPUBufferDescriptor& aDesc, ErrorResult& aRv) {
+ return Buffer::Create(this, mId, aDesc, aRv);
+}
+
+already_AddRefed<Texture> Device::CreateTexture(
+ const dom::GPUTextureDescriptor& aDesc) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateTexture(mId, aDesc);
+ }
+ RefPtr<Texture> texture = new Texture(this, id, aDesc);
+ return texture.forget();
+}
+
+already_AddRefed<Sampler> Device::CreateSampler(
+ const dom::GPUSamplerDescriptor& aDesc) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateSampler(mId, aDesc);
+ }
+ RefPtr<Sampler> sampler = new Sampler(this, id);
+ return sampler.forget();
+}
+
+already_AddRefed<CommandEncoder> Device::CreateCommandEncoder(
+ const dom::GPUCommandEncoderDescriptor& aDesc) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateCommandEncoder(mId, aDesc);
+ }
+ 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) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateBindGroupLayout(mId, aDesc);
+ }
+ RefPtr<BindGroupLayout> object = new BindGroupLayout(this, id, true);
+ return object.forget();
+}
+already_AddRefed<PipelineLayout> Device::CreatePipelineLayout(
+ const dom::GPUPipelineLayoutDescriptor& aDesc) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreatePipelineLayout(mId, aDesc);
+ }
+ RefPtr<PipelineLayout> object = new PipelineLayout(this, id);
+ return object.forget();
+}
+already_AddRefed<BindGroup> Device::CreateBindGroup(
+ const dom::GPUBindGroupDescriptor& aDesc) {
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateBindGroup(mId, aDesc);
+ }
+ RefPtr<BindGroup> object = new BindGroup(this, id);
+ return object.forget();
+}
+
+already_AddRefed<ShaderModule> Device::CreateShaderModule(
+ JSContext* aCx, const dom::GPUShaderModuleDescriptor& aDesc) {
+ Unused << aCx;
+
+ if (!mBridge->CanSend()) {
+ return nullptr;
+ }
+
+ ErrorResult err;
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), err);
+ if (NS_WARN_IF(err.Failed())) {
+ return nullptr;
+ }
+
+ return mBridge->DeviceCreateShaderModule(this, aDesc, promise);
+}
+
+already_AddRefed<ComputePipeline> Device::CreateComputePipeline(
+ const dom::GPUComputePipelineDescriptor& aDesc) {
+ PipelineCreationContext context = {mId};
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateComputePipeline(&context, aDesc);
+ }
+ 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};
+ RawId id = 0;
+ if (mBridge->CanSend()) {
+ id = mBridge->DeviceCreateRenderPipeline(&context, aDesc);
+ }
+ 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;
+ }
+
+ if (!mBridge->CanSend()) {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ return promise.forget();
+ }
+
+ std::shared_ptr<PipelineCreationContext> context(
+ new PipelineCreationContext());
+ context->mParentId = mId;
+ mBridge->DeviceCreateComputePipelineAsync(context.get(), aDesc)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, context, promise](RawId aId) {
+ RefPtr<ComputePipeline> object = new ComputePipeline(
+ self, aId, context->mImplicitPipelineLayoutId,
+ std::move(context->mImplicitBindGroupLayoutIds));
+ promise->MaybeResolve(object);
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ 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;
+ }
+
+ if (!mBridge->CanSend()) {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ return promise.forget();
+ }
+
+ std::shared_ptr<PipelineCreationContext> context(
+ new PipelineCreationContext());
+ context->mParentId = mId;
+ mBridge->DeviceCreateRenderPipelineAsync(context.get(), aDesc)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, context, promise](RawId aId) {
+ RefPtr<RenderPipeline> object = new RenderPipeline(
+ self, aId, context->mImplicitPipelineLayoutId,
+ std::move(context->mImplicitBindGroupLayoutIds));
+ promise->MaybeResolve(object);
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithOperationError(
+ "Internal communication error");
+ });
+
+ return promise.forget();
+}
+
+already_AddRefed<Texture> Device::InitSwapChain(
+ const dom::GPUCanvasConfiguration& aDesc,
+ const layers::RemoteTextureOwnerId aOwnerId, gfx::SurfaceFormat aFormat,
+ gfx::IntSize* aCanvasSize) {
+ if (!mBridge->CanSend()) {
+ return nullptr;
+ }
+
+ gfx::IntSize size = *aCanvasSize;
+ if (aDesc.mSize.WasPassed()) {
+ const auto& descSize = aDesc.mSize.Value();
+ if (descSize.IsRangeEnforcedUnsignedLongSequence()) {
+ const auto& seq = descSize.GetAsRangeEnforcedUnsignedLongSequence();
+ // TODO: add a check for `seq.Length()`
+ size.width = AssertedCast<int>(seq[0]);
+ size.height = AssertedCast<int>(seq[1]);
+ } else if (descSize.IsGPUExtent3DDict()) {
+ const auto& dict = descSize.GetAsGPUExtent3DDict();
+ size.width = AssertedCast<int>(dict.mWidth);
+ size.height = AssertedCast<int>(dict.mHeight);
+ } else {
+ MOZ_CRASH("Unexpected union");
+ }
+ *aCanvasSize = size;
+ }
+
+ const layers::RGBDescriptor rgbDesc(size, aFormat);
+ // buffer count doesn't matter much, will be created on demand
+ const size_t maxBufferCount = 10;
+ mBridge->DeviceCreateSwapChain(mId, rgbDesc, maxBufferCount, aOwnerId);
+
+ dom::GPUTextureDescriptor desc;
+ desc.mDimension = dom::GPUTextureDimension::_2d;
+ auto& sizeDict = desc.mSize.SetAsGPUExtent3DDict();
+ sizeDict.mWidth = size.width;
+ sizeDict.mHeight = size.height;
+ sizeDict.mDepthOrArrayLayers = 1;
+ desc.mFormat = aDesc.mFormat;
+ desc.mMipLevelCount = 1;
+ desc.mSampleCount = 1;
+ desc.mUsage = aDesc.mUsage | dom::GPUTextureUsage_Binding::COPY_SRC;
+ return CreateTexture(desc);
+}
+
+bool Device::CheckNewWarning(const nsACString& aMessage) {
+ return mKnownWarnings.EnsureInserted(aMessage);
+}
+
+void Device::Destroy() {
+ // TODO
+}
+
+void Device::PushErrorScope(const dom::GPUErrorFilter& aFilter) {
+ if (mBridge->CanSend()) {
+ mBridge->SendDevicePushErrorScope(mId);
+ }
+}
+
+already_AddRefed<dom::Promise> Device::PopErrorScope(ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!mBridge->CanSend()) {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ return promise.forget();
+ }
+
+ auto errorPromise = mBridge->SendDevicePopErrorScope(mId);
+
+ errorPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, promise](const MaybeScopedError& aMaybeError) {
+ if (aMaybeError) {
+ if (aMaybeError->operationError) {
+ promise->MaybeRejectWithOperationError("Stack is empty");
+ } else {
+ dom::OwningGPUOutOfMemoryErrorOrGPUValidationError error;
+ if (aMaybeError->validationMessage.IsEmpty()) {
+ error.SetAsGPUOutOfMemoryError();
+ } else {
+ error.SetAsGPUValidationError() = new ValidationError(
+ self->GetParentObject(), aMaybeError->validationMessage);
+ }
+ promise->MaybeResolve(std::move(error));
+ }
+ } else {
+ promise->MaybeResolveWithUndefined();
+ }
+ },
+ [promise](const ipc::ResponseRejectReason&) {
+ promise->MaybeRejectWithOperationError("Internal communication error");
+ });
+
+ return promise.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Device.h b/dom/webgpu/Device.h
new file mode 100644
index 0000000000..f78426e185
--- /dev/null
+++ b/dom/webgpu/Device.h
@@ -0,0 +1,171 @@
+/* -*- 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/. */
+
+#ifndef GPU_DEVICE_H_
+#define GPU_DEVICE_H_
+
+#include "ObjectModel.h"
+#include "nsTHashSet.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "mozilla/webgpu/PWebGPUTypes.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/DOMEventTargetHelper.h"
+
+namespace mozilla {
+namespace dom {
+struct GPUExtensions;
+struct GPUFeatures;
+struct GPULimits;
+struct GPUExtent3DDict;
+
+struct GPUBufferDescriptor;
+struct GPUTextureDescriptor;
+struct GPUSamplerDescriptor;
+struct GPUBindGroupLayoutDescriptor;
+struct GPUPipelineLayoutDescriptor;
+struct GPUBindGroupDescriptor;
+struct GPUBlendStateDescriptor;
+struct GPUDepthStencilStateDescriptor;
+struct GPUInputStateDescriptor;
+struct GPUShaderModuleDescriptor;
+struct GPUAttachmentStateDescriptor;
+struct GPUComputePipelineDescriptor;
+struct GPURenderBundleEncoderDescriptor;
+struct GPURenderPipelineDescriptor;
+struct GPUCommandEncoderDescriptor;
+struct GPUCanvasConfiguration;
+
+class EventHandlerNonNull;
+class Promise;
+template <typename T>
+class Sequence;
+class GPUBufferOrGPUTexture;
+enum class GPUErrorFilter : uint8_t;
+enum class GPUFeatureName : uint8_t;
+class GPULogCallback;
+} // namespace dom
+namespace ipc {
+enum class ResponseRejectReason;
+} // namespace ipc
+
+namespace webgpu {
+namespace ffi {
+struct WGPULimits;
+}
+class Adapter;
+class BindGroup;
+class BindGroupLayout;
+class Buffer;
+class CommandEncoder;
+class ComputePipeline;
+class Fence;
+class InputState;
+class PipelineLayout;
+class Queue;
+class RenderBundleEncoder;
+class RenderPipeline;
+class Sampler;
+class ShaderModule;
+class SupportedFeatures;
+class SupportedLimits;
+class Texture;
+class WebGPUChild;
+
+using MappingPromise =
+ MozPromise<BufferMapResult, ipc::ResponseRejectReason, true>;
+
+class Device final : public DOMEventTargetHelper, public SupportsWeakPtr {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Device, DOMEventTargetHelper)
+ GPU_DECL_JS_WRAP(Device)
+
+ const RawId mId;
+ RefPtr<SupportedFeatures> mFeatures;
+ RefPtr<SupportedLimits> mLimits;
+
+ explicit Device(Adapter* const aParent, RawId aId,
+ UniquePtr<ffi::WGPULimits> aRawLimits);
+
+ RefPtr<WebGPUChild> GetBridge();
+ already_AddRefed<Texture> InitSwapChain(
+ const dom::GPUCanvasConfiguration& aDesc,
+ const layers::RemoteTextureOwnerId aOwnerId, gfx::SurfaceFormat aFormat,
+ gfx::IntSize* aDefaultSize);
+ bool CheckNewWarning(const nsACString& aMessage);
+
+ void CleanupUnregisteredInParent();
+
+ void GenerateError(const nsCString& aMessage);
+
+ bool IsLost() const;
+
+ private:
+ ~Device();
+ void Cleanup();
+
+ RefPtr<WebGPUChild> mBridge;
+ bool mValid = true;
+ nsString mLabel;
+ RefPtr<dom::Promise> mLostPromise;
+ RefPtr<Queue> mQueue;
+ nsTHashSet<nsCString> mKnownWarnings;
+
+ public:
+ void GetLabel(nsAString& aValue) const;
+ void SetLabel(const nsAString& aLabel);
+ dom::Promise* GetLost(ErrorResult& aRv);
+ dom::Promise* MaybeGetLost() const { return mLostPromise; }
+
+ const RefPtr<SupportedFeatures>& Features() const { return mFeatures; }
+ const RefPtr<SupportedLimits>& Limits() const { return mLimits; }
+ const RefPtr<Queue>& GetQueue() const { return mQueue; }
+
+ already_AddRefed<Buffer> CreateBuffer(const dom::GPUBufferDescriptor& aDesc,
+ ErrorResult& aRv);
+
+ already_AddRefed<Texture> CreateTexture(
+ const dom::GPUTextureDescriptor& aDesc);
+ already_AddRefed<Sampler> CreateSampler(
+ const dom::GPUSamplerDescriptor& aDesc);
+
+ already_AddRefed<CommandEncoder> CreateCommandEncoder(
+ const dom::GPUCommandEncoderDescriptor& aDesc);
+ already_AddRefed<RenderBundleEncoder> CreateRenderBundleEncoder(
+ const dom::GPURenderBundleEncoderDescriptor& aDesc);
+
+ already_AddRefed<BindGroupLayout> CreateBindGroupLayout(
+ const dom::GPUBindGroupLayoutDescriptor& aDesc);
+ already_AddRefed<PipelineLayout> CreatePipelineLayout(
+ const dom::GPUPipelineLayoutDescriptor& aDesc);
+ already_AddRefed<BindGroup> CreateBindGroup(
+ const dom::GPUBindGroupDescriptor& aDesc);
+
+ already_AddRefed<ShaderModule> CreateShaderModule(
+ JSContext* aCx, const dom::GPUShaderModuleDescriptor& aDesc);
+ already_AddRefed<ComputePipeline> CreateComputePipeline(
+ const dom::GPUComputePipelineDescriptor& aDesc);
+ already_AddRefed<RenderPipeline> CreateRenderPipeline(
+ const dom::GPURenderPipelineDescriptor& aDesc);
+ already_AddRefed<dom::Promise> CreateComputePipelineAsync(
+ const dom::GPUComputePipelineDescriptor& aDesc, ErrorResult& aRv);
+ already_AddRefed<dom::Promise> CreateRenderPipelineAsync(
+ const dom::GPURenderPipelineDescriptor& aDesc, ErrorResult& aRv);
+
+ void PushErrorScope(const dom::GPUErrorFilter& aFilter);
+ already_AddRefed<dom::Promise> PopErrorScope(ErrorResult& aRv);
+
+ void Destroy();
+
+ IMPL_EVENT_HANDLER(uncapturederror)
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_DEVICE_H_
diff --git a/dom/webgpu/DeviceLostInfo.cpp b/dom/webgpu/DeviceLostInfo.cpp
new file mode 100644
index 0000000000..4f1153ea60
--- /dev/null
+++ b/dom/webgpu/DeviceLostInfo.cpp
@@ -0,0 +1,13 @@
+/* -*- 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 "DeviceLostInfo.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(DeviceLostInfo, mGlobal)
+GPU_IMPL_JS_WRAP(DeviceLostInfo)
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/DeviceLostInfo.h b/dom/webgpu/DeviceLostInfo.h
new file mode 100644
index 0000000000..1ab77610c7
--- /dev/null
+++ b/dom/webgpu/DeviceLostInfo.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef GPU_DeviceLostInfo_H_
+#define GPU_DeviceLostInfo_H_
+
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/Maybe.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+class Device;
+
+class DeviceLostInfo final : public nsWrapperCache {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(DeviceLostInfo)
+ GPU_DECL_JS_WRAP(DeviceLostInfo)
+
+ explicit DeviceLostInfo(nsIGlobalObject* const aGlobal,
+ const nsAString& aMessage)
+ : mGlobal(aGlobal), mMessage(aMessage) {}
+ DeviceLostInfo(nsIGlobalObject* const aGlobal,
+ dom::GPUDeviceLostReason aReason, const nsAString& aMessage)
+ : mGlobal(aGlobal), mReason(Some(aReason)), mMessage(aMessage) {}
+
+ private:
+ ~DeviceLostInfo() = default;
+ void Cleanup() {}
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ const Maybe<dom::GPUDeviceLostReason> mReason;
+ const nsAutoString mMessage;
+
+ public:
+ void GetReason(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval) {
+ if (!mReason || !dom::ToJSValue(aCx, mReason.value(), aRetval)) {
+ aRetval.setUndefined();
+ }
+ }
+
+ void GetMessage(nsAString& aValue) const { aValue = mMessage; }
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_DeviceLostInfo_H_
diff --git a/dom/webgpu/Instance.cpp b/dom/webgpu/Instance.cpp
new file mode 100644
index 0000000000..a80b850da3
--- /dev/null
+++ b/dom/webgpu/Instance.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "Instance.h"
+
+#include "Adapter.h"
+#include "nsIGlobalObject.h"
+#include "ipc/WebGPUChild.h"
+#include "ipc/WebGPUTypes.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/gfx/CanvasManagerChild.h"
+#include "mozilla/gfx/gfxVars.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(Instance, mOwner)
+
+/*static*/
+already_AddRefed<Instance> Instance::Create(nsIGlobalObject* aOwner) {
+ RefPtr<Instance> result = new Instance(aOwner);
+ return result.forget();
+}
+
+Instance::Instance(nsIGlobalObject* aOwner) : mOwner(aOwner) {}
+
+Instance::~Instance() { Cleanup(); }
+
+void Instance::Cleanup() {}
+
+JSObject* Instance::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> givenProto) {
+ return dom::GPU_Binding::Wrap(cx, this, givenProto);
+}
+
+already_AddRefed<dom::Promise> Instance::RequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions, ErrorResult& aRv) {
+ RefPtr<dom::Promise> promise = dom::Promise::Create(mOwner, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!gfx::gfxVars::AllowWebGPU()) {
+ promise->MaybeRejectWithNotSupportedError("WebGPU is not enabled!");
+ return promise.forget();
+ }
+
+ auto* const canvasManager = gfx::CanvasManagerChild::Get();
+ if (!canvasManager) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Failed to create CanavasManagerChild");
+ return promise.forget();
+ }
+
+ RefPtr<WebGPUChild> bridge = canvasManager->GetWebGPUChild();
+ if (!bridge) {
+ promise->MaybeRejectWithInvalidStateError("Failed to create WebGPUChild");
+ return promise.forget();
+ }
+
+ RefPtr<Instance> instance = this;
+
+ bridge->InstanceRequestAdapter(aOptions)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, instance, bridge](ipc::ByteBuf aInfoBuf) {
+ ffi::WGPUAdapterInformation info = {};
+ ffi::wgpu_client_adapter_extract_info(ToFFI(&aInfoBuf), &info);
+ MOZ_ASSERT(info.id != 0);
+ RefPtr<Adapter> adapter = new Adapter(instance, bridge, info);
+ promise->MaybeResolve(adapter);
+ },
+ [promise](const Maybe<ipc::ResponseRejectReason>& aResponseReason) {
+ if (aResponseReason.isSome()) {
+ promise->MaybeRejectWithAbortError("Internal communication error!");
+ } else {
+ promise->MaybeResolve(JS::NullHandleValue);
+ }
+ });
+
+ return promise.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Instance.h b/dom/webgpu/Instance.h
new file mode 100644
index 0000000000..222a66ad81
--- /dev/null
+++ b/dom/webgpu/Instance.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef GPU_INSTANCE_H_
+#define GPU_INSTANCE_H_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class Promise;
+struct GPURequestAdapterOptions;
+} // namespace dom
+
+namespace webgpu {
+class Adapter;
+class GPUAdapter;
+class WebGPUChild;
+
+class Instance final : public nsWrapperCache {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(Instance)
+ GPU_DECL_JS_WRAP(Instance)
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ static already_AddRefed<Instance> Create(nsIGlobalObject* aOwner);
+
+ already_AddRefed<dom::Promise> RequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions, ErrorResult& aRv);
+
+ private:
+ explicit Instance(nsIGlobalObject* aOwner);
+ virtual ~Instance();
+ void Cleanup();
+
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+ public:
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_INSTANCE_H_
diff --git a/dom/webgpu/ObjectModel.cpp b/dom/webgpu/ObjectModel.cpp
new file mode 100644
index 0000000000..f691a17b66
--- /dev/null
+++ b/dom/webgpu/ObjectModel.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "ObjectModel.h"
+
+#include "Adapter.h"
+#include "ShaderModule.h"
+#include "CompilationInfo.h"
+#include "Device.h"
+#include "CommandEncoder.h"
+#include "Instance.h"
+#include "Texture.h"
+
+namespace mozilla::webgpu {
+
+template <typename T>
+ChildOf<T>::ChildOf(T* const parent) : mParent(parent) {}
+
+template <typename T>
+ChildOf<T>::~ChildOf() = default;
+
+template <typename T>
+nsIGlobalObject* ChildOf<T>::GetParentObject() const {
+ return mParent->GetParentObject();
+}
+
+void ObjectBase::GetLabel(nsAString& aValue) const { aValue = mLabel; }
+void ObjectBase::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }
+
+template class ChildOf<Adapter>;
+template class ChildOf<ShaderModule>;
+template class ChildOf<CompilationInfo>;
+template class ChildOf<CommandEncoder>;
+template class ChildOf<Device>;
+template class ChildOf<Instance>;
+template class ChildOf<Texture>;
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ObjectModel.h b/dom/webgpu/ObjectModel.h
new file mode 100644
index 0000000000..d482ac16f5
--- /dev/null
+++ b/dom/webgpu/ObjectModel.h
@@ -0,0 +1,131 @@
+/* -*- 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/. */
+
+#ifndef GPU_OBJECT_MODEL_H_
+#define GPU_OBJECT_MODEL_H_
+
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::webgpu {
+class WebGPUChild;
+
+template <typename T>
+class ChildOf {
+ protected:
+ explicit ChildOf(T* const parent);
+ virtual ~ChildOf();
+
+ RefPtr<T> mParent;
+
+ public:
+ nsIGlobalObject* GetParentObject() const;
+};
+
+class ObjectBase : public nsWrapperCache {
+ private:
+ nsString mLabel;
+
+ protected:
+ virtual ~ObjectBase() = default;
+
+ // False if this object is definitely invalid.
+ //
+ // See WebGPU §3.2, "Invalid Internal Objects & Contagious Invalidity".
+ //
+ // There could also be state in the GPU process indicating that our
+ // counterpart object there is invalid; certain GPU process operations will
+ // report an error back to use if we try to use it. But if it's useful to know
+ // whether the object is "definitely invalid", this should suffice.
+ bool mValid = true;
+
+ public:
+ // Return true if this WebGPU object may be valid.
+ //
+ // This is used by methods that want to know whether somebody other than
+ // `this` is valid. Generally, WebGPU object methods check `this->mValid`
+ // directly.
+ bool IsValid() const { return mValid; }
+
+ void GetLabel(nsAString& aValue) const;
+ void SetLabel(const nsAString& aLabel);
+};
+
+} // namespace mozilla::webgpu
+
+#define GPU_DECL_JS_WRAP(T) \
+ JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) \
+ override;
+
+#define GPU_DECL_CYCLE_COLLECTION(T) \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(T) \
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(T)
+
+#define GPU_IMPL_JS_WRAP(T) \
+ JSObject* T::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) { \
+ return dom::GPU##T##_Binding::Wrap(cx, this, givenProto); \
+ }
+
+// Note: we don't use `NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE` directly
+// because there is a custom action we need to always do.
+#define GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(T, ...) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(T) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(T) \
+ tmp->Cleanup(); \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(T) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(T, ...) \
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(T) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(T) \
+ tmp->Cleanup(); \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(T) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(T, P, ...) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(T) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(T, P) \
+ tmp->Cleanup(); \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(T, P) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define GPU_IMPL_CYCLE_COLLECTION(T, ...) \
+ GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(T, __VA_ARGS__)
+
+template <typename T>
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+ nsTArray<RefPtr<const T>>& field,
+ const char* name, uint32_t flags) {
+ for (auto& element : field) {
+ CycleCollectionNoteChild(callback, const_cast<T*>(element.get()), name,
+ flags);
+ }
+}
+
+template <typename T>
+void ImplCycleCollectionUnlink(nsTArray<RefPtr<const T>>& field) {
+ for (auto& element : field) {
+ ImplCycleCollectionUnlink(element);
+ }
+ field.Clear();
+}
+
+#endif // GPU_OBJECT_MODEL_H_
diff --git a/dom/webgpu/OutOfMemoryError.cpp b/dom/webgpu/OutOfMemoryError.cpp
new file mode 100644
index 0000000000..edf52369df
--- /dev/null
+++ b/dom/webgpu/OutOfMemoryError.cpp
@@ -0,0 +1,17 @@
+/* -*- 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 "OutOfMemoryError.h"
+#include "Device.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(OutOfMemoryError, mParent)
+GPU_IMPL_JS_WRAP(OutOfMemoryError)
+
+OutOfMemoryError::~OutOfMemoryError() = default;
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/OutOfMemoryError.h b/dom/webgpu/OutOfMemoryError.h
new file mode 100644
index 0000000000..e634e396f1
--- /dev/null
+++ b/dom/webgpu/OutOfMemoryError.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef GPU_OutOfMemoryError_H_
+#define GPU_OutOfMemoryError_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+namespace dom {
+class GlobalObject;
+} // namespace dom
+namespace webgpu {
+class Device;
+
+class OutOfMemoryError final : public nsWrapperCache, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(OutOfMemoryError)
+ GPU_DECL_JS_WRAP(OutOfMemoryError)
+ OutOfMemoryError() = delete;
+
+ private:
+ virtual ~OutOfMemoryError();
+ void Cleanup() {}
+
+ public:
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_OutOfMemoryError_H_
diff --git a/dom/webgpu/PipelineLayout.cpp b/dom/webgpu/PipelineLayout.cpp
new file mode 100644
index 0000000000..21485e78a5
--- /dev/null
+++ b/dom/webgpu/PipelineLayout.cpp
@@ -0,0 +1,36 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "PipelineLayout.h"
+#include "ipc/WebGPUChild.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(PipelineLayout, mParent)
+GPU_IMPL_JS_WRAP(PipelineLayout)
+
+PipelineLayout::PipelineLayout(Device* const aParent, RawId aId)
+ : ChildOf(aParent), mId(aId) {
+ if (!aId) {
+ mValid = false;
+ }
+}
+
+PipelineLayout::~PipelineLayout() { Cleanup(); }
+
+void PipelineLayout::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendPipelineLayoutDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/PipelineLayout.h b/dom/webgpu/PipelineLayout.h
new file mode 100644
index 0000000000..65293d778d
--- /dev/null
+++ b/dom/webgpu/PipelineLayout.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef GPU_PipelineLayout_H_
+#define GPU_PipelineLayout_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class PipelineLayout final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(PipelineLayout)
+ GPU_DECL_JS_WRAP(PipelineLayout)
+
+ PipelineLayout(Device* const aParent, RawId aId);
+
+ const RawId mId;
+
+ private:
+ virtual ~PipelineLayout();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_PipelineLayout_H_
diff --git a/dom/webgpu/QuerySet.cpp b/dom/webgpu/QuerySet.cpp
new file mode 100644
index 0000000000..05f30f6cc8
--- /dev/null
+++ b/dom/webgpu/QuerySet.cpp
@@ -0,0 +1,22 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "QuerySet.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+QuerySet::~QuerySet() = default;
+
+GPU_IMPL_CYCLE_COLLECTION(QuerySet, mParent)
+GPU_IMPL_JS_WRAP(QuerySet)
+
+void QuerySet::Destroy() {
+ // TODO
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/QuerySet.h b/dom/webgpu/QuerySet.h
new file mode 100644
index 0000000000..e7e6f4968b
--- /dev/null
+++ b/dom/webgpu/QuerySet.h
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+#ifndef GPU_QuerySet_H_
+#define GPU_QuerySet_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class QuerySet final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(QuerySet)
+ GPU_DECL_JS_WRAP(QuerySet)
+
+ QuerySet() = delete;
+ void Destroy();
+
+ private:
+ virtual ~QuerySet();
+ void Cleanup() {}
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_QuerySet_H_
diff --git a/dom/webgpu/Queue.cpp b/dom/webgpu/Queue.cpp
new file mode 100644
index 0000000000..b71f220066
--- /dev/null
+++ b/dom/webgpu/Queue.cpp
@@ -0,0 +1,416 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "Queue.h"
+
+#include <algorithm>
+
+#include "CommandBuffer.h"
+#include "CommandEncoder.h"
+#include "ipc/WebGPUChild.h"
+#include "mozilla/Casting.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/dom/WebGLTexelConversions.h"
+#include "mozilla/dom/WebGLTypes.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(Queue, mParent, mBridge)
+GPU_IMPL_JS_WRAP(Queue)
+
+Queue::Queue(Device* const aParent, WebGPUChild* aBridge, RawId aId)
+ : ChildOf(aParent), mBridge(aBridge), mId(aId) {}
+
+Queue::~Queue() { Cleanup(); }
+
+void Queue::Submit(
+ const dom::Sequence<OwningNonNull<CommandBuffer>>& aCommandBuffers) {
+ nsTArray<RawId> list(aCommandBuffers.Length());
+ for (uint32_t i = 0; i < aCommandBuffers.Length(); ++i) {
+ auto idMaybe = aCommandBuffers[i]->Commit();
+ if (idMaybe) {
+ list.AppendElement(*idMaybe);
+ }
+ }
+
+ mBridge->SendQueueSubmit(mId, mParent->mId, list);
+}
+
+void Queue::WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset,
+ const dom::ArrayBufferViewOrArrayBuffer& aData,
+ uint64_t aDataOffset,
+ const dom::Optional<uint64_t>& aSize,
+ ErrorResult& aRv) {
+ uint64_t length = 0;
+ uint8_t* data = nullptr;
+ if (aData.IsArrayBufferView()) {
+ const auto& view = aData.GetAsArrayBufferView();
+ view.ComputeState();
+ length = view.Length();
+ data = view.Data();
+ }
+ if (aData.IsArrayBuffer()) {
+ const auto& ab = aData.GetAsArrayBuffer();
+ ab.ComputeState();
+ length = ab.Length();
+ data = ab.Data();
+ }
+
+ MOZ_ASSERT(data != nullptr);
+
+ const auto checkedSize = aSize.WasPassed()
+ ? CheckedInt<size_t>(aSize.Value())
+ : CheckedInt<size_t>(length) - aDataOffset;
+ if (!checkedSize.isValid()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ const auto& size = checkedSize.value();
+ if (aDataOffset + size > length) {
+ aRv.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR, size));
+ return;
+ }
+
+ if (size % 4 != 0) {
+ aRv.ThrowAbortError("Byte size must be a multiple of 4");
+ return;
+ }
+
+ auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
+ if (alloc.isNothing()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto handle = std::move(alloc.ref().first);
+ auto mapping = std::move(alloc.ref().second);
+
+ memcpy(mapping.Bytes().data(), data + aDataOffset, size);
+ ipc::ByteBuf bb;
+ ffi::wgpu_queue_write_buffer(aBuffer.mId, aBufferOffset, ToFFI(&bb));
+ if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
+ std::move(handle))) {
+ MOZ_CRASH("IPC failure");
+ }
+}
+
+void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
+ const dom::ArrayBufferViewOrArrayBuffer& aData,
+ const dom::GPUImageDataLayout& aDataLayout,
+ const dom::GPUExtent3D& aSize, ErrorResult& aRv) {
+ ffi::WGPUImageCopyTexture copyView = {};
+ CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, &copyView);
+ ffi::WGPUImageDataLayout dataLayout = {};
+ CommandEncoder::ConvertTextureDataLayoutToFFI(aDataLayout, &dataLayout);
+ dataLayout.offset = 0; // our Shmem has the contents starting from 0.
+ ffi::WGPUExtent3d extent = {};
+ CommandEncoder::ConvertExtent3DToFFI(aSize, &extent);
+
+ uint64_t availableSize = 0;
+ uint8_t* data = nullptr;
+ if (aData.IsArrayBufferView()) {
+ const auto& view = aData.GetAsArrayBufferView();
+ view.ComputeState();
+ availableSize = view.Length();
+ data = view.Data();
+ }
+ if (aData.IsArrayBuffer()) {
+ const auto& ab = aData.GetAsArrayBuffer();
+ ab.ComputeState();
+ availableSize = ab.Length();
+ data = ab.Data();
+ }
+
+ if (!availableSize) {
+ aRv.ThrowAbortError("Input size cannot be zero.");
+ return;
+ }
+ MOZ_ASSERT(data != nullptr);
+
+ const auto checkedSize =
+ CheckedInt<size_t>(availableSize) - aDataLayout.mOffset;
+ if (!checkedSize.isValid()) {
+ aRv.ThrowAbortError(nsPrintfCString("Offset is higher than the size"));
+ return;
+ }
+ const auto size = checkedSize.value();
+
+ auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
+ if (alloc.isNothing()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto handle = std::move(alloc.ref().first);
+ auto mapping = std::move(alloc.ref().second);
+
+ memcpy(mapping.Bytes().data(), data + aDataLayout.mOffset, size);
+
+ ipc::ByteBuf bb;
+ ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
+ if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
+ std::move(handle))) {
+ MOZ_CRASH("IPC failure");
+ }
+}
+
+static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) {
+ switch (aFormat) {
+ case gfx::SurfaceFormat::B8G8R8A8:
+ case gfx::SurfaceFormat::B8G8R8X8:
+ return WebGLTexelFormat::BGRA8;
+ case gfx::SurfaceFormat::R8G8B8A8:
+ case gfx::SurfaceFormat::R8G8B8X8:
+ return WebGLTexelFormat::RGBA8;
+ default:
+ return WebGLTexelFormat::FormatNotSupportingAnyConversion;
+ }
+}
+
+static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) {
+ // TODO: We need support for Rbg10a2unorm as well.
+ switch (aFormat) {
+ case dom::GPUTextureFormat::R8unorm:
+ return WebGLTexelFormat::R8;
+ case dom::GPUTextureFormat::R16float:
+ return WebGLTexelFormat::R16F;
+ case dom::GPUTextureFormat::R32float:
+ return WebGLTexelFormat::R32F;
+ case dom::GPUTextureFormat::Rg8unorm:
+ return WebGLTexelFormat::RG8;
+ case dom::GPUTextureFormat::Rg16float:
+ return WebGLTexelFormat::RG16F;
+ case dom::GPUTextureFormat::Rg32float:
+ return WebGLTexelFormat::RG32F;
+ case dom::GPUTextureFormat::Rgba8unorm:
+ case dom::GPUTextureFormat::Rgba8unorm_srgb:
+ return WebGLTexelFormat::RGBA8;
+ case dom::GPUTextureFormat::Bgra8unorm:
+ case dom::GPUTextureFormat::Bgra8unorm_srgb:
+ return WebGLTexelFormat::BGRA8;
+ case dom::GPUTextureFormat::Rgba16float:
+ return WebGLTexelFormat::RGBA16F;
+ case dom::GPUTextureFormat::Rgba32float:
+ return WebGLTexelFormat::RGBA32F;
+ default:
+ return WebGLTexelFormat::FormatNotSupportingAnyConversion;
+ }
+}
+
+void Queue::CopyExternalImageToTexture(
+ const dom::GPUImageCopyExternalImage& aSource,
+ const dom::GPUImageCopyTextureTagged& aDestination,
+ const dom::GPUExtent3D& aCopySize, ErrorResult& aRv) {
+ const auto dstFormat = ToWebGLTexelFormat(aDestination.mTexture->mFormat);
+ if (dstFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
+ aRv.ThrowInvalidStateError("Unsupported destination format");
+ return;
+ }
+
+ const uint32_t surfaceFlags = nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
+ SurfaceFromElementResult sfeResult;
+ if (aSource.mSource.IsImageBitmap()) {
+ const auto& bitmap = aSource.mSource.GetAsImageBitmap();
+ if (bitmap->IsClosed()) {
+ aRv.ThrowInvalidStateError("Detached ImageBitmap");
+ return;
+ }
+
+ sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags);
+ } else if (aSource.mSource.IsHTMLCanvasElement()) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const auto& canvas = aSource.mSource.GetAsHTMLCanvasElement();
+ if (canvas->Width() == 0 || canvas->Height() == 0) {
+ aRv.ThrowInvalidStateError("Zero-sized HTMLCanvasElement");
+ return;
+ }
+
+ sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags);
+ } else if (aSource.mSource.IsOffscreenCanvas()) {
+ const auto& canvas = aSource.mSource.GetAsOffscreenCanvas();
+ if (canvas->Width() == 0 || canvas->Height() == 0) {
+ aRv.ThrowInvalidStateError("Zero-sized OffscreenCanvas");
+ return;
+ }
+
+ sfeResult = nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags);
+ }
+
+ if (!sfeResult.mCORSUsed) {
+ nsIGlobalObject* global = mParent->GetOwnerGlobal();
+ nsIPrincipal* dstPrincipal = global ? global->PrincipalOrNull() : nullptr;
+ if (!sfeResult.mPrincipal || !dstPrincipal ||
+ !dstPrincipal->Subsumes(sfeResult.mPrincipal)) {
+ aRv.ThrowSecurityError("Cross-origin elements require CORS!");
+ return;
+ }
+ }
+
+ if (sfeResult.mIsWriteOnly) {
+ aRv.ThrowSecurityError("Write only source data not supported!");
+ return;
+ }
+
+ RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface();
+ if (!surface) {
+ aRv.ThrowInvalidStateError("No surface available from source");
+ return;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
+ if (!dataSurface) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ bool srcPremultiplied;
+ switch (sfeResult.mAlphaType) {
+ case gfxAlphaType::Premult:
+ srcPremultiplied = true;
+ break;
+ case gfxAlphaType::NonPremult:
+ srcPremultiplied = false;
+ break;
+ case gfxAlphaType::Opaque:
+ // No (un)premultiplication necessary so match the output.
+ srcPremultiplied = aDestination.mPremultipliedAlpha;
+ break;
+ }
+
+ const auto surfaceFormat = dataSurface->GetFormat();
+ const auto srcFormat = ToWebGLTexelFormat(surfaceFormat);
+ if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
+ gfxCriticalError() << "Unsupported surface format from source "
+ << surfaceFormat;
+ MOZ_CRASH();
+ }
+
+ gfx::DataSourceSurface::ScopedMap map(dataSurface,
+ gfx::DataSourceSurface::READ);
+ if (!map.IsMapped()) {
+ aRv.ThrowInvalidStateError("Cannot map surface from source");
+ return;
+ }
+
+ if (!aSource.mOrigin.IsGPUOrigin2DDict()) {
+ aRv.ThrowInvalidStateError("Cannot get origin from source");
+ return;
+ }
+
+ ffi::WGPUExtent3d extent = {};
+ CommandEncoder::ConvertExtent3DToFFI(aCopySize, &extent);
+ if (extent.depth_or_array_layers > 1) {
+ aRv.ThrowOperationError("Depth is greater than 1");
+ return;
+ }
+
+ uint32_t srcOriginX;
+ uint32_t srcOriginY;
+ if (aSource.mOrigin.IsRangeEnforcedUnsignedLongSequence()) {
+ const auto& seq = aSource.mOrigin.GetAsRangeEnforcedUnsignedLongSequence();
+ srcOriginX = seq.Length() > 0 ? seq[0] : 0;
+ srcOriginY = seq.Length() > 1 ? seq[1] : 0;
+ } else if (aSource.mOrigin.IsGPUOrigin2DDict()) {
+ const auto& dict = aSource.mOrigin.GetAsGPUOrigin2DDict();
+ srcOriginX = dict.mX;
+ srcOriginY = dict.mY;
+ } else {
+ MOZ_CRASH("Unexpected origin type!");
+ }
+
+ const auto checkedMaxWidth = CheckedInt<uint32_t>(srcOriginX) + extent.width;
+ const auto checkedMaxHeight =
+ CheckedInt<uint32_t>(srcOriginY) + extent.height;
+ if (!checkedMaxWidth.isValid() || !checkedMaxHeight.isValid()) {
+ aRv.ThrowOperationError("Offset and copy size exceed integer bounds");
+ return;
+ }
+
+ const gfx::IntSize surfaceSize = dataSurface->GetSize();
+ const auto surfaceWidth = AssertedCast<uint32_t>(surfaceSize.width);
+ const auto surfaceHeight = AssertedCast<uint32_t>(surfaceSize.height);
+ if (surfaceWidth < checkedMaxWidth.value() ||
+ surfaceHeight < checkedMaxHeight.value()) {
+ aRv.ThrowOperationError("Offset and copy size exceed surface bounds");
+ return;
+ }
+
+ const auto dstWidth = extent.width;
+ const auto dstHeight = extent.height;
+ if (dstWidth == 0 || dstHeight == 0) {
+ aRv.ThrowOperationError("Destination size is empty");
+ return;
+ }
+
+ if (!aDestination.mTexture->mBytesPerBlock) {
+ // TODO(bug 1781071) This should emmit a GPUValidationError on the device
+ // timeline.
+ aRv.ThrowInvalidStateError("Invalid destination format");
+ return;
+ }
+
+ // Note: This assumes bytes per block == bytes per pixel which is the case
+ // here because the spec only allows non-compressed texture formats for the
+ // destination.
+ const auto dstStride = CheckedInt<uint32_t>(extent.width) *
+ aDestination.mTexture->mBytesPerBlock.value();
+ const auto dstByteLength = dstStride * extent.height;
+ if (!dstStride.isValid() || !dstByteLength.isValid()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
+ dstByteLength.value());
+ if (alloc.isNothing()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto handle = std::move(alloc.ref().first);
+ auto mapping = std::move(alloc.ref().second);
+
+ const int32_t pixelSize = gfx::BytesPerPixel(surfaceFormat);
+ auto* dstBegin = mapping.Bytes().data();
+ const auto* srcBegin =
+ map.GetData() + srcOriginX * pixelSize + srcOriginY * map.GetStride();
+ const auto srcOriginPos = gl::OriginPos::TopLeft;
+ const auto srcStride = AssertedCast<uint32_t>(map.GetStride());
+ const auto dstOriginPos =
+ aSource.mFlipY ? gl::OriginPos::BottomLeft : gl::OriginPos::TopLeft;
+ bool wasTrivial;
+
+ if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos,
+ srcFormat, srcPremultiplied, dstBegin, dstStride.value(),
+ dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha,
+ &wasTrivial)) {
+ MOZ_ASSERT_UNREACHABLE("ConvertImage failed!");
+ aRv.ThrowInvalidStateError(
+ nsPrintfCString("Failed to convert source to destination format "
+ "(%i/%i), please file a bug!",
+ (int)srcFormat, (int)dstFormat));
+ return;
+ }
+
+ ffi::WGPUImageDataLayout dataLayout = {0, dstStride.value(), dstHeight};
+ ffi::WGPUImageCopyTexture copyView = {};
+ CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, &copyView);
+ ipc::ByteBuf bb;
+ ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
+ if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
+ std::move(handle))) {
+ MOZ_CRASH("IPC failure");
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Queue.h b/dom/webgpu/Queue.h
new file mode 100644
index 0000000000..27fb23c780
--- /dev/null
+++ b/dom/webgpu/Queue.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef GPU_Queue_H_
+#define GPU_Queue_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+class RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict;
+class ArrayBufferViewOrArrayBuffer;
+template <typename T>
+class Optional;
+template <typename T>
+class Sequence;
+struct GPUImageCopyTexture;
+struct GPUImageDataLayout;
+struct TextureCopyView;
+struct TextureDataLayout;
+using GPUExtent3D = RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict;
+} // namespace dom
+namespace webgpu {
+
+class Buffer;
+class CommandBuffer;
+class Device;
+class Fence;
+
+class Queue final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(Queue)
+ GPU_DECL_JS_WRAP(Queue)
+
+ Queue(Device* const aParent, WebGPUChild* aBridge, RawId aId);
+
+ void Submit(
+ const dom::Sequence<OwningNonNull<CommandBuffer>>& aCommandBuffers);
+
+ void WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset,
+ const dom::ArrayBufferViewOrArrayBuffer& aData,
+ uint64_t aDataOffset, const dom::Optional<uint64_t>& aSize,
+ ErrorResult& aRv);
+
+ void WriteTexture(const dom::GPUImageCopyTexture& aDestination,
+ const dom::ArrayBufferViewOrArrayBuffer& aData,
+ const dom::GPUImageDataLayout& aDataLayout,
+ const dom::GPUExtent3D& aSize, ErrorResult& aRv);
+
+ void CopyExternalImageToTexture(
+ const dom::GPUImageCopyExternalImage& aSource,
+ const dom::GPUImageCopyTextureTagged& aDestination,
+ const dom::GPUExtent3D& aCopySize, ErrorResult& aRv);
+
+ private:
+ virtual ~Queue();
+ void Cleanup() {}
+
+ RefPtr<WebGPUChild> mBridge;
+ const RawId mId;
+
+ public:
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_Queue_H_
diff --git a/dom/webgpu/RenderBundle.cpp b/dom/webgpu/RenderBundle.cpp
new file mode 100644
index 0000000000..0e6ce87874
--- /dev/null
+++ b/dom/webgpu/RenderBundle.cpp
@@ -0,0 +1,38 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "RenderBundle.h"
+
+#include "Device.h"
+#include "ipc/WebGPUChild.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(RenderBundle, mParent)
+GPU_IMPL_JS_WRAP(RenderBundle)
+
+RenderBundle::RenderBundle(Device* const aParent, RawId aId)
+ : ChildOf(aParent), mId(aId) {
+ // If we happened to finish an encoder twice, the second
+ // bundle should be invalid.
+ if (!mId) {
+ mValid = false;
+ }
+}
+
+RenderBundle::~RenderBundle() { Cleanup(); }
+
+void RenderBundle::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendRenderBundleDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/RenderBundle.h b/dom/webgpu/RenderBundle.h
new file mode 100644
index 0000000000..0fef6af781
--- /dev/null
+++ b/dom/webgpu/RenderBundle.h
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+
+#ifndef GPU_RenderBundle_H_
+#define GPU_RenderBundle_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class RenderBundle final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(RenderBundle)
+ GPU_DECL_JS_WRAP(RenderBundle)
+
+ RenderBundle(Device* const aParent, RawId aId);
+
+ const RawId mId;
+
+ private:
+ virtual ~RenderBundle();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_RenderBundle_H_
diff --git a/dom/webgpu/RenderBundleEncoder.cpp b/dom/webgpu/RenderBundleEncoder.cpp
new file mode 100644
index 0000000000..cb14db1dd0
--- /dev/null
+++ b/dom/webgpu/RenderBundleEncoder.cpp
@@ -0,0 +1,196 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "RenderBundleEncoder.h"
+
+#include "BindGroup.h"
+#include "Buffer.h"
+#include "RenderBundle.h"
+#include "RenderPipeline.h"
+#include "ipc/WebGPUChild.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(RenderBundleEncoder, mParent, mUsedBindGroups,
+ mUsedBuffers, mUsedPipelines, mUsedTextureViews)
+GPU_IMPL_JS_WRAP(RenderBundleEncoder)
+
+ffi::WGPURenderBundleEncoder* ScopedFfiBundleTraits::empty() { return nullptr; }
+
+void ScopedFfiBundleTraits::release(ffi::WGPURenderBundleEncoder* raw) {
+ if (raw) {
+ ffi::wgpu_render_bundle_encoder_destroy(raw);
+ }
+}
+
+ffi::WGPURenderBundleEncoder* CreateRenderBundleEncoder(
+ RawId aDeviceId, const dom::GPURenderBundleEncoderDescriptor& aDesc,
+ WebGPUChild* const aBridge) {
+ if (!aBridge->CanSend()) {
+ return nullptr;
+ }
+
+ ffi::WGPURenderBundleEncoderDescriptor desc = {};
+ desc.sample_count = aDesc.mSampleCount;
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ffi::WGPUTextureFormat depthStencilFormat = {ffi::WGPUTextureFormat_Sentinel};
+ if (aDesc.mDepthStencilFormat.WasPassed()) {
+ WebGPUChild::ConvertTextureFormatRef(aDesc.mDepthStencilFormat.Value(),
+ depthStencilFormat);
+ desc.depth_stencil_format = &depthStencilFormat;
+ }
+
+ std::vector<ffi::WGPUTextureFormat> colorFormats = {};
+ for (const auto i : IntegerRange(aDesc.mColorFormats.Length())) {
+ ffi::WGPUTextureFormat format = {ffi::WGPUTextureFormat_Sentinel};
+ WebGPUChild::ConvertTextureFormatRef(aDesc.mColorFormats[i], format);
+ colorFormats.push_back(format);
+ }
+
+ desc.color_formats = colorFormats.data();
+ desc.color_formats_length = colorFormats.size();
+
+ ipc::ByteBuf failureAction;
+ auto* bundle = ffi::wgpu_device_create_render_bundle_encoder(
+ aDeviceId, &desc, ToFFI(&failureAction));
+ // report an error only if the operation failed
+ if (!bundle &&
+ !aBridge->SendDeviceAction(aDeviceId, std::move(failureAction))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return bundle;
+}
+
+RenderBundleEncoder::RenderBundleEncoder(
+ Device* const aParent, WebGPUChild* const aBridge,
+ const dom::GPURenderBundleEncoderDescriptor& aDesc)
+ : ChildOf(aParent),
+ mEncoder(CreateRenderBundleEncoder(aParent->mId, aDesc, aBridge)) {
+ mValid = mEncoder.get() != nullptr;
+}
+
+RenderBundleEncoder::~RenderBundleEncoder() { Cleanup(); }
+
+void RenderBundleEncoder::Cleanup() {
+ if (mValid) {
+ mValid = false;
+ }
+}
+
+void RenderBundleEncoder::SetBindGroup(
+ uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets) {
+ if (mValid) {
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_render_bundle_set_bind_group(mEncoder, aSlot, aBindGroup.mId,
+ aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
+ }
+}
+
+void RenderBundleEncoder::SetPipeline(const RenderPipeline& aPipeline) {
+ if (mValid) {
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_render_bundle_set_pipeline(mEncoder, aPipeline.mId);
+ }
+}
+
+void RenderBundleEncoder::SetIndexBuffer(
+ const Buffer& aBuffer, const dom::GPUIndexFormat& aIndexFormat,
+ uint64_t aOffset, uint64_t aSize) {
+ if (mValid) {
+ mUsedBuffers.AppendElement(&aBuffer);
+ const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
+ ? ffi::WGPUIndexFormat_Uint32
+ : ffi::WGPUIndexFormat_Uint16;
+ ffi::wgpu_render_bundle_set_index_buffer(mEncoder, aBuffer.mId, iformat,
+ aOffset, aSize);
+ }
+}
+
+void RenderBundleEncoder::SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer,
+ uint64_t aOffset, uint64_t aSize) {
+ if (mValid) {
+ mUsedBuffers.AppendElement(&aBuffer);
+ ffi::wgpu_render_bundle_set_vertex_buffer(mEncoder, aSlot, aBuffer.mId,
+ aOffset, aSize);
+ }
+}
+
+void RenderBundleEncoder::Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
+ uint32_t aFirstVertex, uint32_t aFirstInstance) {
+ if (mValid) {
+ ffi::wgpu_render_bundle_draw(mEncoder, aVertexCount, aInstanceCount,
+ aFirstVertex, aFirstInstance);
+ }
+}
+
+void RenderBundleEncoder::DrawIndexed(uint32_t aIndexCount,
+ uint32_t aInstanceCount,
+ uint32_t aFirstIndex, int32_t aBaseVertex,
+ uint32_t aFirstInstance) {
+ if (mValid) {
+ ffi::wgpu_render_bundle_draw_indexed(mEncoder, aIndexCount, aInstanceCount,
+ aFirstIndex, aBaseVertex,
+ aFirstInstance);
+ }
+}
+
+void RenderBundleEncoder::DrawIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset) {
+ if (mValid) {
+ ffi::wgpu_render_bundle_draw_indirect(mEncoder, aIndirectBuffer.mId,
+ aIndirectOffset);
+ }
+}
+
+void RenderBundleEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset) {
+ if (mValid) {
+ ffi::wgpu_render_bundle_draw_indexed_indirect(mEncoder, aIndirectBuffer.mId,
+ aIndirectOffset);
+ }
+}
+
+void RenderBundleEncoder::PushDebugGroup(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_bundle_push_debug_group(mEncoder, utf8.get());
+ }
+}
+void RenderBundleEncoder::PopDebugGroup() {
+ if (mValid) {
+ ffi::wgpu_render_bundle_pop_debug_group(mEncoder);
+ }
+}
+void RenderBundleEncoder::InsertDebugMarker(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_bundle_insert_debug_marker(mEncoder, utf8.get());
+ }
+}
+
+already_AddRefed<RenderBundle> RenderBundleEncoder::Finish(
+ const dom::GPURenderBundleDescriptor& aDesc) {
+ RawId id = 0;
+ if (mValid) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->CanSend()) {
+ auto* encoder = mEncoder.forget();
+ MOZ_ASSERT(encoder);
+ id = bridge->RenderBundleEncoderFinish(*encoder, mParent->mId, aDesc);
+ }
+ }
+ RefPtr<RenderBundle> bundle = new RenderBundle(mParent, id);
+ return bundle.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/RenderBundleEncoder.h b/dom/webgpu/RenderBundleEncoder.h
new file mode 100644
index 0000000000..f8bfebe21e
--- /dev/null
+++ b/dom/webgpu/RenderBundleEncoder.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#ifndef GPU_RenderBundleEncoder_H_
+#define GPU_RenderBundleEncoder_H_
+
+#include "mozilla/Scoped.h"
+#include "mozilla/dom/TypedArray.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+namespace ffi {
+struct WGPURenderBundleEncoder;
+} // namespace ffi
+
+class Device;
+class RenderBundle;
+
+struct ScopedFfiBundleTraits {
+ using type = ffi::WGPURenderBundleEncoder*;
+ static type empty();
+ static void release(type raw);
+};
+
+class RenderBundleEncoder final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(RenderBundleEncoder)
+ GPU_DECL_JS_WRAP(RenderBundleEncoder)
+
+ RenderBundleEncoder(Device* const aParent, WebGPUChild* const aBridge,
+ const dom::GPURenderBundleEncoderDescriptor& aDesc);
+
+ private:
+ ~RenderBundleEncoder();
+ void Cleanup();
+
+ Scoped<ScopedFfiBundleTraits> mEncoder;
+ // keep all the used objects alive while the encoder is finished
+ nsTArray<RefPtr<const BindGroup>> mUsedBindGroups;
+ nsTArray<RefPtr<const Buffer>> mUsedBuffers;
+ nsTArray<RefPtr<const RenderPipeline>> mUsedPipelines;
+ nsTArray<RefPtr<const TextureView>> mUsedTextureViews;
+
+ public:
+ // programmable pass encoder
+ void SetBindGroup(uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets);
+ // render encoder base
+ void SetPipeline(const RenderPipeline& aPipeline);
+ void SetIndexBuffer(const Buffer& aBuffer,
+ const dom::GPUIndexFormat& aIndexFormat, uint64_t aOffset,
+ uint64_t aSize);
+ void SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer, uint64_t aOffset,
+ uint64_t aSize);
+ void Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
+ uint32_t aFirstVertex, uint32_t aFirstInstance);
+ void DrawIndexed(uint32_t aIndexCount, uint32_t aInstanceCount,
+ uint32_t aFirstIndex, int32_t aBaseVertex,
+ uint32_t aFirstInstance);
+ void DrawIndirect(const Buffer& aIndirectBuffer, uint64_t aIndirectOffset);
+ void DrawIndexedIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset);
+
+ void PushDebugGroup(const nsAString& aString);
+ void PopDebugGroup();
+ void InsertDebugMarker(const nsAString& aString);
+
+ // self
+ already_AddRefed<RenderBundle> Finish(
+ const dom::GPURenderBundleDescriptor& aDesc);
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_RenderBundleEncoder_H_
diff --git a/dom/webgpu/RenderPassEncoder.cpp b/dom/webgpu/RenderPassEncoder.cpp
new file mode 100644
index 0000000000..4cc2b9fbf3
--- /dev/null
+++ b/dom/webgpu/RenderPassEncoder.cpp
@@ -0,0 +1,306 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "RenderPassEncoder.h"
+#include "BindGroup.h"
+#include "CommandEncoder.h"
+#include "RenderBundle.h"
+#include "RenderPipeline.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(RenderPassEncoder, mParent, mUsedBindGroups,
+ mUsedBuffers, mUsedPipelines, mUsedTextureViews,
+ mUsedRenderBundles)
+GPU_IMPL_JS_WRAP(RenderPassEncoder)
+
+ffi::WGPURenderPass* ScopedFfiRenderTraits::empty() { return nullptr; }
+
+void ScopedFfiRenderTraits::release(ffi::WGPURenderPass* raw) {
+ if (raw) {
+ ffi::wgpu_render_pass_destroy(raw);
+ }
+}
+
+ffi::WGPULoadOp ConvertLoadOp(const dom::GPULoadOp& aOp) {
+ switch (aOp) {
+ case dom::GPULoadOp::Load:
+ return ffi::WGPULoadOp_Load;
+ default:
+ MOZ_CRASH("Unexpected load op");
+ }
+}
+
+ffi::WGPUStoreOp ConvertStoreOp(const dom::GPUStoreOp& aOp) {
+ switch (aOp) {
+ case dom::GPUStoreOp::Store:
+ return ffi::WGPUStoreOp_Store;
+ case dom::GPUStoreOp::Discard:
+ return ffi::WGPUStoreOp_Discard;
+ default:
+ MOZ_CRASH("Unexpected load op");
+ }
+}
+
+ffi::WGPUColor ConvertColor(const dom::GPUColorDict& aColor) {
+ ffi::WGPUColor color = {aColor.mR, aColor.mG, aColor.mB, aColor.mA};
+ return color;
+}
+
+ffi::WGPURenderPass* BeginRenderPass(
+ CommandEncoder* const aParent, const dom::GPURenderPassDescriptor& aDesc) {
+ ffi::WGPURenderPassDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ffi::WGPURenderPassDepthStencilAttachment dsDesc = {};
+ if (aDesc.mDepthStencilAttachment.WasPassed()) {
+ const auto& dsa = aDesc.mDepthStencilAttachment.Value();
+ dsDesc.view = dsa.mView->mId;
+
+ if (dsa.mDepthLoadValue.IsFloat()) {
+ dsDesc.depth.load_op = ffi::WGPULoadOp_Clear;
+ dsDesc.depth.clear_value = dsa.mDepthLoadValue.GetAsFloat();
+ }
+ if (dsa.mDepthLoadValue.IsGPULoadOp()) {
+ dsDesc.depth.load_op =
+ ConvertLoadOp(dsa.mDepthLoadValue.GetAsGPULoadOp());
+ }
+ dsDesc.depth.store_op = ConvertStoreOp(dsa.mDepthStoreOp);
+
+ if (dsa.mStencilLoadValue.IsRangeEnforcedUnsignedLong()) {
+ dsDesc.stencil.load_op = ffi::WGPULoadOp_Clear;
+ dsDesc.stencil.clear_value =
+ dsa.mStencilLoadValue.GetAsRangeEnforcedUnsignedLong();
+ }
+ if (dsa.mStencilLoadValue.IsGPULoadOp()) {
+ dsDesc.stencil.load_op =
+ ConvertLoadOp(dsa.mStencilLoadValue.GetAsGPULoadOp());
+ }
+ dsDesc.stencil.store_op = ConvertStoreOp(dsa.mStencilStoreOp);
+
+ desc.depth_stencil_attachment = &dsDesc;
+ }
+
+ if (aDesc.mColorAttachments.Length() > WGPUMAX_COLOR_ATTACHMENTS) {
+ aParent->GetDevice()->GenerateError(nsLiteralCString(
+ "Too many color attachments in GPURenderPassDescriptor"));
+ return nullptr;
+ }
+
+ std::array<ffi::WGPURenderPassColorAttachment, WGPUMAX_COLOR_ATTACHMENTS>
+ colorDescs = {};
+ desc.color_attachments = colorDescs.data();
+ desc.color_attachments_length = aDesc.mColorAttachments.Length();
+
+ for (size_t i = 0; i < aDesc.mColorAttachments.Length(); ++i) {
+ const auto& ca = aDesc.mColorAttachments[i];
+ ffi::WGPURenderPassColorAttachment& cd = colorDescs[i];
+ cd.view = ca.mView->mId;
+ cd.channel.store_op = ConvertStoreOp(ca.mStoreOp);
+
+ if (ca.mResolveTarget.WasPassed()) {
+ cd.resolve_target = ca.mResolveTarget.Value().mId;
+ }
+ if (ca.mLoadValue.IsGPULoadOp()) {
+ cd.channel.load_op = ConvertLoadOp(ca.mLoadValue.GetAsGPULoadOp());
+ } else {
+ cd.channel.load_op = ffi::WGPULoadOp_Clear;
+ if (ca.mLoadValue.IsDoubleSequence()) {
+ const auto& seq = ca.mLoadValue.GetAsDoubleSequence();
+ if (seq.Length() >= 1) {
+ cd.channel.clear_value.r = seq[0];
+ }
+ if (seq.Length() >= 2) {
+ cd.channel.clear_value.g = seq[1];
+ }
+ if (seq.Length() >= 3) {
+ cd.channel.clear_value.b = seq[2];
+ }
+ if (seq.Length() >= 4) {
+ cd.channel.clear_value.a = seq[3];
+ }
+ }
+ if (ca.mLoadValue.IsGPUColorDict()) {
+ cd.channel.clear_value =
+ ConvertColor(ca.mLoadValue.GetAsGPUColorDict());
+ }
+ }
+ }
+
+ return ffi::wgpu_command_encoder_begin_render_pass(aParent->mId, &desc);
+}
+
+RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent,
+ const dom::GPURenderPassDescriptor& aDesc)
+ : ChildOf(aParent), mPass(BeginRenderPass(aParent, aDesc)) {
+ if (!mPass) {
+ mValid = false;
+ return;
+ }
+
+ for (const auto& at : aDesc.mColorAttachments) {
+ mUsedTextureViews.AppendElement(at.mView);
+ }
+ if (aDesc.mDepthStencilAttachment.WasPassed()) {
+ mUsedTextureViews.AppendElement(
+ aDesc.mDepthStencilAttachment.Value().mView);
+ }
+}
+
+RenderPassEncoder::~RenderPassEncoder() {
+ if (mValid) {
+ mValid = false;
+ }
+}
+
+void RenderPassEncoder::SetBindGroup(
+ uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets) {
+ if (mValid) {
+ mUsedBindGroups.AppendElement(&aBindGroup);
+ ffi::wgpu_render_pass_set_bind_group(mPass, aSlot, aBindGroup.mId,
+ aDynamicOffsets.Elements(),
+ aDynamicOffsets.Length());
+ }
+}
+
+void RenderPassEncoder::SetPipeline(const RenderPipeline& aPipeline) {
+ if (mValid) {
+ mUsedPipelines.AppendElement(&aPipeline);
+ ffi::wgpu_render_pass_set_pipeline(mPass, aPipeline.mId);
+ }
+}
+
+void RenderPassEncoder::SetIndexBuffer(const Buffer& aBuffer,
+ const dom::GPUIndexFormat& aIndexFormat,
+ uint64_t aOffset, uint64_t aSize) {
+ if (mValid) {
+ mUsedBuffers.AppendElement(&aBuffer);
+ const auto iformat = aIndexFormat == dom::GPUIndexFormat::Uint32
+ ? ffi::WGPUIndexFormat_Uint32
+ : ffi::WGPUIndexFormat_Uint16;
+ ffi::wgpu_render_pass_set_index_buffer(mPass, aBuffer.mId, iformat, aOffset,
+ aSize);
+ }
+}
+
+void RenderPassEncoder::SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer,
+ uint64_t aOffset, uint64_t aSize) {
+ if (mValid) {
+ mUsedBuffers.AppendElement(&aBuffer);
+ ffi::wgpu_render_pass_set_vertex_buffer(mPass, aSlot, aBuffer.mId, aOffset,
+ aSize);
+ }
+}
+
+void RenderPassEncoder::Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
+ uint32_t aFirstVertex, uint32_t aFirstInstance) {
+ if (mValid) {
+ ffi::wgpu_render_pass_draw(mPass, aVertexCount, aInstanceCount,
+ aFirstVertex, aFirstInstance);
+ }
+}
+
+void RenderPassEncoder::DrawIndexed(uint32_t aIndexCount,
+ uint32_t aInstanceCount,
+ uint32_t aFirstIndex, int32_t aBaseVertex,
+ uint32_t aFirstInstance) {
+ if (mValid) {
+ ffi::wgpu_render_pass_draw_indexed(mPass, aIndexCount, aInstanceCount,
+ aFirstIndex, aBaseVertex,
+ aFirstInstance);
+ }
+}
+
+void RenderPassEncoder::DrawIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset) {
+ if (mValid) {
+ ffi::wgpu_render_pass_draw_indirect(mPass, aIndirectBuffer.mId,
+ aIndirectOffset);
+ }
+}
+
+void RenderPassEncoder::DrawIndexedIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset) {
+ if (mValid) {
+ ffi::wgpu_render_pass_draw_indexed_indirect(mPass, aIndirectBuffer.mId,
+ aIndirectOffset);
+ }
+}
+
+void RenderPassEncoder::SetViewport(float x, float y, float width, float height,
+ float minDepth, float maxDepth) {
+ if (mValid) {
+ ffi::wgpu_render_pass_set_viewport(mPass, x, y, width, height, minDepth,
+ maxDepth);
+ }
+}
+
+void RenderPassEncoder::SetScissorRect(uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height) {
+ if (mValid) {
+ ffi::wgpu_render_pass_set_scissor_rect(mPass, x, y, width, height);
+ }
+}
+
+void RenderPassEncoder::SetBlendConstant(
+ const dom::DoubleSequenceOrGPUColorDict& color) {
+ if (mValid) {
+ ffi::WGPUColor aColor = ConvertColor(color.GetAsGPUColorDict());
+ ffi::wgpu_render_pass_set_blend_constant(mPass, &aColor);
+ }
+}
+
+void RenderPassEncoder::SetStencilReference(uint32_t reference) {
+ if (mValid) {
+ ffi::wgpu_render_pass_set_stencil_reference(mPass, reference);
+ }
+}
+
+void RenderPassEncoder::ExecuteBundles(
+ const dom::Sequence<OwningNonNull<RenderBundle>>& aBundles) {
+ if (mValid) {
+ nsTArray<ffi::WGPURenderBundleId> renderBundles(aBundles.Length());
+ for (const auto& bundle : aBundles) {
+ mUsedRenderBundles.AppendElement(bundle);
+ renderBundles.AppendElement(bundle->mId);
+ }
+ ffi::wgpu_render_pass_execute_bundles(mPass, renderBundles.Elements(),
+ renderBundles.Length());
+ }
+}
+
+void RenderPassEncoder::PushDebugGroup(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_pass_push_debug_group(mPass, utf8.get(), 0);
+ }
+}
+void RenderPassEncoder::PopDebugGroup() {
+ if (mValid) {
+ ffi::wgpu_render_pass_pop_debug_group(mPass);
+ }
+}
+void RenderPassEncoder::InsertDebugMarker(const nsAString& aString) {
+ if (mValid) {
+ const NS_ConvertUTF16toUTF8 utf8(aString);
+ ffi::wgpu_render_pass_insert_debug_marker(mPass, utf8.get(), 0);
+ }
+}
+
+void RenderPassEncoder::EndPass(ErrorResult& aRv) {
+ if (mValid) {
+ mValid = false;
+ auto* pass = mPass.forget();
+ MOZ_ASSERT(pass);
+ mParent->EndRenderPass(*pass, aRv);
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/RenderPassEncoder.h b/dom/webgpu/RenderPassEncoder.h
new file mode 100644
index 0000000000..f94d3c750a
--- /dev/null
+++ b/dom/webgpu/RenderPassEncoder.h
@@ -0,0 +1,104 @@
+/* -*- 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/. */
+
+#ifndef GPU_RenderPassEncoder_H_
+#define GPU_RenderPassEncoder_H_
+
+#include "mozilla/Scoped.h"
+#include "mozilla/dom/TypedArray.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class DoubleSequenceOrGPUColorDict;
+struct GPURenderPassDescriptor;
+template <typename T>
+class Sequence;
+namespace binding_detail {
+template <typename T>
+class AutoSequence;
+} // namespace binding_detail
+} // namespace dom
+namespace webgpu {
+namespace ffi {
+struct WGPURenderPass;
+} // namespace ffi
+
+class BindGroup;
+class Buffer;
+class CommandEncoder;
+class RenderBundle;
+class RenderPipeline;
+class TextureView;
+
+struct ScopedFfiRenderTraits {
+ using type = ffi::WGPURenderPass*;
+ static type empty();
+ static void release(type raw);
+};
+
+class RenderPassEncoder final : public ObjectBase,
+ public ChildOf<CommandEncoder> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(RenderPassEncoder)
+ GPU_DECL_JS_WRAP(RenderPassEncoder)
+
+ RenderPassEncoder(CommandEncoder* const aParent,
+ const dom::GPURenderPassDescriptor& aDesc);
+
+ protected:
+ virtual ~RenderPassEncoder();
+ void Cleanup() {}
+
+ Scoped<ScopedFfiRenderTraits> mPass;
+ // keep all the used objects alive while the pass is recorded
+ nsTArray<RefPtr<const BindGroup>> mUsedBindGroups;
+ nsTArray<RefPtr<const Buffer>> mUsedBuffers;
+ nsTArray<RefPtr<const RenderPipeline>> mUsedPipelines;
+ nsTArray<RefPtr<const TextureView>> mUsedTextureViews;
+ nsTArray<RefPtr<const RenderBundle>> mUsedRenderBundles;
+
+ public:
+ // programmable pass encoder
+ void SetBindGroup(uint32_t aSlot, const BindGroup& aBindGroup,
+ const dom::Sequence<uint32_t>& aDynamicOffsets);
+ // render encoder base
+ void SetPipeline(const RenderPipeline& aPipeline);
+ void SetIndexBuffer(const Buffer& aBuffer,
+ const dom::GPUIndexFormat& aIndexFormat, uint64_t aOffset,
+ uint64_t aSize);
+ void SetVertexBuffer(uint32_t aSlot, const Buffer& aBuffer, uint64_t aOffset,
+ uint64_t aSize);
+ void Draw(uint32_t aVertexCount, uint32_t aInstanceCount,
+ uint32_t aFirstVertex, uint32_t aFirstInstance);
+ void DrawIndexed(uint32_t aIndexCount, uint32_t aInstanceCount,
+ uint32_t aFirstIndex, int32_t aBaseVertex,
+ uint32_t aFirstInstance);
+ void DrawIndirect(const Buffer& aIndirectBuffer, uint64_t aIndirectOffset);
+ void DrawIndexedIndirect(const Buffer& aIndirectBuffer,
+ uint64_t aIndirectOffset);
+ // self
+ void SetViewport(float x, float y, float width, float height, float minDepth,
+ float maxDepth);
+ void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
+ void SetBlendConstant(const dom::DoubleSequenceOrGPUColorDict& color);
+ void SetStencilReference(uint32_t reference);
+
+ void PushDebugGroup(const nsAString& aString);
+ void PopDebugGroup();
+ void InsertDebugMarker(const nsAString& aString);
+
+ void ExecuteBundles(
+ const dom::Sequence<OwningNonNull<RenderBundle>>& aBundles);
+
+ void EndPass(ErrorResult& aRv);
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_RenderPassEncoder_H_
diff --git a/dom/webgpu/RenderPipeline.cpp b/dom/webgpu/RenderPipeline.cpp
new file mode 100644
index 0000000000..d99ac87c4a
--- /dev/null
+++ b/dom/webgpu/RenderPipeline.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "RenderPipeline.h"
+
+#include "Device.h"
+#include "ipc/WebGPUChild.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(RenderPipeline, mParent)
+GPU_IMPL_JS_WRAP(RenderPipeline)
+
+RenderPipeline::RenderPipeline(Device* const aParent, RawId aId,
+ RawId aImplicitPipelineLayoutId,
+ nsTArray<RawId>&& aImplicitBindGroupLayoutIds)
+ : ChildOf(aParent),
+ mImplicitPipelineLayoutId(aImplicitPipelineLayoutId),
+ mImplicitBindGroupLayoutIds(std::move(aImplicitBindGroupLayoutIds)),
+ mId(aId) {}
+
+RenderPipeline::~RenderPipeline() { Cleanup(); }
+
+void RenderPipeline::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendRenderPipelineDestroy(mId);
+ if (mImplicitPipelineLayoutId) {
+ bridge->SendImplicitLayoutDestroy(mImplicitPipelineLayoutId,
+ mImplicitBindGroupLayoutIds);
+ }
+ }
+ }
+}
+
+already_AddRefed<BindGroupLayout> RenderPipeline::GetBindGroupLayout(
+ uint32_t index) const {
+ const RawId id = index < mImplicitBindGroupLayoutIds.Length()
+ ? mImplicitBindGroupLayoutIds[index]
+ : 0;
+ RefPtr<BindGroupLayout> object = new BindGroupLayout(mParent, id, false);
+ return object.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/RenderPipeline.h b/dom/webgpu/RenderPipeline.h
new file mode 100644
index 0000000000..859259da27
--- /dev/null
+++ b/dom/webgpu/RenderPipeline.h
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+#ifndef GPU_RenderPipeline_H_
+#define GPU_RenderPipeline_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsTArray.h"
+
+namespace mozilla::webgpu {
+
+class BindGroupLayout;
+class Device;
+
+class RenderPipeline final : public ObjectBase, public ChildOf<Device> {
+ const RawId mImplicitPipelineLayoutId;
+ const nsTArray<RawId> mImplicitBindGroupLayoutIds;
+
+ public:
+ GPU_DECL_CYCLE_COLLECTION(RenderPipeline)
+ GPU_DECL_JS_WRAP(RenderPipeline)
+
+ const RawId mId;
+
+ RenderPipeline(Device* const aParent, RawId aId,
+ RawId aImplicitPipelineLayoutId,
+ nsTArray<RawId>&& aImplicitBindGroupLayoutIds);
+ already_AddRefed<BindGroupLayout> GetBindGroupLayout(uint32_t index) const;
+
+ private:
+ virtual ~RenderPipeline();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_RenderPipeline_H_
diff --git a/dom/webgpu/Sampler.cpp b/dom/webgpu/Sampler.cpp
new file mode 100644
index 0000000000..9a3b90c4dd
--- /dev/null
+++ b/dom/webgpu/Sampler.cpp
@@ -0,0 +1,32 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "Sampler.h"
+#include "ipc/WebGPUChild.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(Sampler, mParent)
+GPU_IMPL_JS_WRAP(Sampler)
+
+Sampler::Sampler(Device* const aParent, RawId aId)
+ : ChildOf(aParent), mId(aId) {}
+
+Sampler::~Sampler() { Cleanup(); }
+
+void Sampler::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendSamplerDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Sampler.h b/dom/webgpu/Sampler.h
new file mode 100644
index 0000000000..02e01982cd
--- /dev/null
+++ b/dom/webgpu/Sampler.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef GPU_SAMPLER_H_
+#define GPU_SAMPLER_H_
+
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+
+class Device;
+
+class Sampler final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(Sampler)
+ GPU_DECL_JS_WRAP(Sampler)
+
+ Sampler(Device* const aParent, RawId aId);
+
+ const RawId mId;
+
+ private:
+ virtual ~Sampler();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_SAMPLER_H_
diff --git a/dom/webgpu/ShaderModule.cpp b/dom/webgpu/ShaderModule.cpp
new file mode 100644
index 0000000000..c415fa732a
--- /dev/null
+++ b/dom/webgpu/ShaderModule.cpp
@@ -0,0 +1,40 @@
+/* -*- 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 "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "ShaderModule.h"
+#include "CompilationInfo.h"
+#include "ipc/WebGPUChild.h"
+
+#include "Device.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(ShaderModule, mParent)
+GPU_IMPL_JS_WRAP(ShaderModule)
+
+ShaderModule::ShaderModule(Device* const aParent, RawId aId,
+ const RefPtr<dom::Promise>& aCompilationInfo)
+ : ChildOf(aParent), mId(aId), mCompilationInfo(aCompilationInfo) {}
+
+ShaderModule::~ShaderModule() { Cleanup(); }
+
+void ShaderModule::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendShaderModuleDestroy(mId);
+ }
+ }
+}
+
+already_AddRefed<dom::Promise> ShaderModule::CompilationInfo(ErrorResult& aRv) {
+ RefPtr<dom::Promise> tmp = mCompilationInfo;
+ return tmp.forget();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ShaderModule.h b/dom/webgpu/ShaderModule.h
new file mode 100644
index 0000000000..08eaf2b804
--- /dev/null
+++ b/dom/webgpu/ShaderModule.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef GPU_ShaderModule_H_
+#define GPU_ShaderModule_H_
+
+#include "mozilla/webgpu/WebGPUTypes.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+
+class CompilationInfo;
+class Device;
+
+class ShaderModule final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(
+ ShaderModule) // TODO: kvark's WIP patch was passing CompilationInfo as a
+ // second argument here.
+ GPU_DECL_JS_WRAP(ShaderModule)
+
+ ShaderModule(Device* const aParent, RawId aId,
+ const RefPtr<dom::Promise>& aCompilationInfo);
+ already_AddRefed<dom::Promise> CompilationInfo(ErrorResult& aRv);
+
+ const RawId mId;
+
+ private:
+ virtual ~ShaderModule();
+ void Cleanup();
+
+ RefPtr<dom::Promise> mCompilationInfo;
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_ShaderModule_H_
diff --git a/dom/webgpu/SupportedFeatures.cpp b/dom/webgpu/SupportedFeatures.cpp
new file mode 100644
index 0000000000..072705892e
--- /dev/null
+++ b/dom/webgpu/SupportedFeatures.cpp
@@ -0,0 +1,18 @@
+/* -*- 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 "SupportedFeatures.h"
+#include "Adapter.h"
+#include "mozilla/dom/WebGPUBinding.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(SupportedFeatures, mParent)
+GPU_IMPL_JS_WRAP(SupportedFeatures)
+
+SupportedFeatures::SupportedFeatures(Adapter* const aParent)
+ : ChildOf(aParent) {}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/SupportedFeatures.h b/dom/webgpu/SupportedFeatures.h
new file mode 100644
index 0000000000..5c12ac8d3c
--- /dev/null
+++ b/dom/webgpu/SupportedFeatures.h
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+#ifndef GPU_SupportedFeatures_H_
+#define GPU_SupportedFeatures_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+class Adapter;
+
+class SupportedFeatures final : public nsWrapperCache, public ChildOf<Adapter> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(SupportedFeatures)
+ GPU_DECL_JS_WRAP(SupportedFeatures)
+
+ explicit SupportedFeatures(Adapter* const aParent);
+
+ private:
+ ~SupportedFeatures() = default;
+ void Cleanup() {}
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_SupportedFeatures_H_
diff --git a/dom/webgpu/SupportedLimits.cpp b/dom/webgpu/SupportedLimits.cpp
new file mode 100644
index 0000000000..ea37dec206
--- /dev/null
+++ b/dom/webgpu/SupportedLimits.cpp
@@ -0,0 +1,101 @@
+/* -*- 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 "SupportedLimits.h"
+#include "Adapter.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(SupportedLimits, mParent)
+GPU_IMPL_JS_WRAP(SupportedLimits)
+
+SupportedLimits::SupportedLimits(Adapter* const aParent,
+ UniquePtr<ffi::WGPULimits>&& aLimits)
+ : ChildOf(aParent), mLimits(std::move(aLimits)) {}
+
+SupportedLimits::~SupportedLimits() = default;
+
+uint32_t SupportedLimits::MaxTextureDimension1D() const {
+ return mLimits->max_texture_dimension_1d;
+}
+uint32_t SupportedLimits::MaxTextureDimension2D() const {
+ return mLimits->max_texture_dimension_2d;
+}
+uint32_t SupportedLimits::MaxTextureDimension3D() const {
+ return mLimits->max_texture_dimension_3d;
+}
+uint32_t SupportedLimits::MaxTextureArrayLayers() const {
+ return mLimits->max_texture_array_layers;
+}
+uint32_t SupportedLimits::MaxBindGroups() const {
+ return mLimits->max_bind_groups;
+}
+uint32_t SupportedLimits::MaxDynamicUniformBuffersPerPipelineLayout() const {
+ return mLimits->max_dynamic_uniform_buffers_per_pipeline_layout;
+}
+uint32_t SupportedLimits::MaxDynamicStorageBuffersPerPipelineLayout() const {
+ return mLimits->max_dynamic_storage_buffers_per_pipeline_layout;
+}
+uint32_t SupportedLimits::MaxSampledTexturesPerShaderStage() const {
+ return mLimits->max_sampled_textures_per_shader_stage;
+}
+uint32_t SupportedLimits::MaxSamplersPerShaderStage() const {
+ return mLimits->max_samplers_per_shader_stage;
+}
+uint32_t SupportedLimits::MaxStorageBuffersPerShaderStage() const {
+ return mLimits->max_storage_buffers_per_shader_stage;
+}
+uint32_t SupportedLimits::MaxStorageTexturesPerShaderStage() const {
+ return mLimits->max_storage_textures_per_shader_stage;
+}
+uint32_t SupportedLimits::MaxUniformBuffersPerShaderStage() const {
+ return mLimits->max_uniform_buffers_per_shader_stage;
+}
+uint32_t SupportedLimits::MaxUniformBufferBindingSize() const {
+ return mLimits->max_uniform_buffer_binding_size;
+}
+uint32_t SupportedLimits::MaxStorageBufferBindingSize() const {
+ return mLimits->max_storage_buffer_binding_size;
+}
+uint32_t SupportedLimits::MinUniformBufferOffsetAlignment() const {
+ return mLimits->min_uniform_buffer_offset_alignment;
+}
+uint32_t SupportedLimits::MinStorageBufferOffsetAlignment() const {
+ return mLimits->min_storage_buffer_offset_alignment;
+}
+uint32_t SupportedLimits::MaxVertexBuffers() const {
+ return mLimits->max_vertex_buffers;
+}
+uint32_t SupportedLimits::MaxVertexAttributes() const {
+ return mLimits->max_vertex_attributes;
+}
+uint32_t SupportedLimits::MaxVertexBufferArrayStride() const {
+ return mLimits->max_vertex_buffer_array_stride;
+}
+uint32_t SupportedLimits::MaxInterStageShaderComponents() const {
+ return mLimits->max_inter_stage_shader_components;
+}
+uint32_t SupportedLimits::MaxComputeWorkgroupStorageSize() const {
+ return mLimits->max_compute_workgroup_storage_size;
+}
+uint32_t SupportedLimits::MaxComputeInvocationsPerWorkgroup() const {
+ return mLimits->max_compute_invocations_per_workgroup;
+}
+uint32_t SupportedLimits::MaxComputeWorkgroupSizeX() const {
+ return mLimits->max_compute_workgroup_size_x;
+}
+uint32_t SupportedLimits::MaxComputeWorkgroupSizeY() const {
+ return mLimits->max_compute_workgroup_size_y;
+}
+uint32_t SupportedLimits::MaxComputeWorkgroupSizeZ() const {
+ return mLimits->max_compute_workgroup_size_z;
+}
+uint32_t SupportedLimits::MaxComputeWorkgroupsPerDimension() const {
+ return mLimits->max_compute_workgroups_per_dimension;
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/SupportedLimits.h b/dom/webgpu/SupportedLimits.h
new file mode 100644
index 0000000000..3c38ae6343
--- /dev/null
+++ b/dom/webgpu/SupportedLimits.h
@@ -0,0 +1,61 @@
+/* -*- 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/. */
+
+#ifndef GPU_SupportedLimits_H_
+#define GPU_SupportedLimits_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla::webgpu {
+namespace ffi {
+struct WGPULimits;
+}
+class Adapter;
+
+class SupportedLimits final : public nsWrapperCache, public ChildOf<Adapter> {
+ const UniquePtr<ffi::WGPULimits> mLimits;
+
+ public:
+ GPU_DECL_CYCLE_COLLECTION(SupportedLimits)
+ GPU_DECL_JS_WRAP(SupportedLimits)
+
+ uint32_t MaxTextureDimension1D() const;
+ uint32_t MaxTextureDimension2D() const;
+ uint32_t MaxTextureDimension3D() const;
+ uint32_t MaxTextureArrayLayers() const;
+ uint32_t MaxBindGroups() const;
+ uint32_t MaxDynamicUniformBuffersPerPipelineLayout() const;
+ uint32_t MaxDynamicStorageBuffersPerPipelineLayout() const;
+ uint32_t MaxSampledTexturesPerShaderStage() const;
+ uint32_t MaxSamplersPerShaderStage() const;
+ uint32_t MaxStorageBuffersPerShaderStage() const;
+ uint32_t MaxStorageTexturesPerShaderStage() const;
+ uint32_t MaxUniformBuffersPerShaderStage() const;
+ uint32_t MaxUniformBufferBindingSize() const;
+ uint32_t MaxStorageBufferBindingSize() const;
+ uint32_t MinUniformBufferOffsetAlignment() const;
+ uint32_t MinStorageBufferOffsetAlignment() const;
+ uint32_t MaxVertexBuffers() const;
+ uint32_t MaxVertexAttributes() const;
+ uint32_t MaxVertexBufferArrayStride() const;
+ uint32_t MaxInterStageShaderComponents() const;
+ uint32_t MaxComputeWorkgroupStorageSize() const;
+ uint32_t MaxComputeInvocationsPerWorkgroup() const;
+ uint32_t MaxComputeWorkgroupSizeX() const;
+ uint32_t MaxComputeWorkgroupSizeY() const;
+ uint32_t MaxComputeWorkgroupSizeZ() const;
+ uint32_t MaxComputeWorkgroupsPerDimension() const;
+
+ SupportedLimits(Adapter* const aParent, UniquePtr<ffi::WGPULimits>&& aLimits);
+
+ private:
+ ~SupportedLimits();
+ void Cleanup() {}
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_SupportedLimits_H_
diff --git a/dom/webgpu/Texture.cpp b/dom/webgpu/Texture.cpp
new file mode 100644
index 0000000000..d4df4ecc02
--- /dev/null
+++ b/dom/webgpu/Texture.cpp
@@ -0,0 +1,123 @@
+/* -*- 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 "Texture.h"
+
+#include "ipc/WebGPUChild.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "mozilla/webgpu/CanvasContext.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "TextureView.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(Texture, mParent)
+GPU_IMPL_JS_WRAP(Texture)
+
+static Maybe<uint8_t> GetBytesPerBlock(dom::GPUTextureFormat format) {
+ switch (format) {
+ case dom::GPUTextureFormat::R8unorm:
+ case dom::GPUTextureFormat::R8snorm:
+ case dom::GPUTextureFormat::R8uint:
+ case dom::GPUTextureFormat::R8sint:
+ return Some<uint8_t>(1u);
+ case dom::GPUTextureFormat::R16uint:
+ case dom::GPUTextureFormat::R16sint:
+ case dom::GPUTextureFormat::R16float:
+ case dom::GPUTextureFormat::Rg8unorm:
+ case dom::GPUTextureFormat::Rg8snorm:
+ case dom::GPUTextureFormat::Rg8uint:
+ case dom::GPUTextureFormat::Rg8sint:
+ return Some<uint8_t>(2u);
+ case dom::GPUTextureFormat::R32uint:
+ case dom::GPUTextureFormat::R32sint:
+ case dom::GPUTextureFormat::R32float:
+ case dom::GPUTextureFormat::Rg16uint:
+ case dom::GPUTextureFormat::Rg16sint:
+ case dom::GPUTextureFormat::Rg16float:
+ case dom::GPUTextureFormat::Rgba8unorm:
+ case dom::GPUTextureFormat::Rgba8unorm_srgb:
+ case dom::GPUTextureFormat::Rgba8snorm:
+ case dom::GPUTextureFormat::Rgba8uint:
+ case dom::GPUTextureFormat::Rgba8sint:
+ case dom::GPUTextureFormat::Bgra8unorm:
+ case dom::GPUTextureFormat::Bgra8unorm_srgb:
+ case dom::GPUTextureFormat::Rgb10a2unorm:
+ case dom::GPUTextureFormat::Rg11b10float:
+ return Some<uint8_t>(4u);
+ case dom::GPUTextureFormat::Rg32uint:
+ case dom::GPUTextureFormat::Rg32sint:
+ case dom::GPUTextureFormat::Rg32float:
+ case dom::GPUTextureFormat::Rgba16uint:
+ case dom::GPUTextureFormat::Rgba16sint:
+ case dom::GPUTextureFormat::Rgba16float:
+ return Some<uint8_t>(8u);
+ case dom::GPUTextureFormat::Rgba32uint:
+ case dom::GPUTextureFormat::Rgba32sint:
+ case dom::GPUTextureFormat::Rgba32float:
+ return Some<uint8_t>(16u);
+ case dom::GPUTextureFormat::Depth32float:
+ return Some<uint8_t>(4u);
+ case dom::GPUTextureFormat::Bc1_rgba_unorm:
+ case dom::GPUTextureFormat::Bc1_rgba_unorm_srgb:
+ case dom::GPUTextureFormat::Bc4_r_unorm:
+ case dom::GPUTextureFormat::Bc4_r_snorm:
+ return Some<uint8_t>(8u);
+ case dom::GPUTextureFormat::Bc2_rgba_unorm:
+ case dom::GPUTextureFormat::Bc2_rgba_unorm_srgb:
+ case dom::GPUTextureFormat::Bc3_rgba_unorm:
+ case dom::GPUTextureFormat::Bc3_rgba_unorm_srgb:
+ case dom::GPUTextureFormat::Bc5_rg_unorm:
+ case dom::GPUTextureFormat::Bc5_rg_snorm:
+ case dom::GPUTextureFormat::Bc6h_rgb_ufloat:
+ case dom::GPUTextureFormat::Bc6h_rgb_float:
+ case dom::GPUTextureFormat::Bc7_rgba_unorm:
+ case dom::GPUTextureFormat::Bc7_rgba_unorm_srgb:
+ return Some<uint8_t>(16u);
+ case dom::GPUTextureFormat::Depth24plus:
+ case dom::GPUTextureFormat::Depth24plus_stencil8:
+ case dom::GPUTextureFormat::EndGuard_:
+ return Nothing();
+ }
+ return Nothing();
+}
+
+Texture::Texture(Device* const aParent, RawId aId,
+ const dom::GPUTextureDescriptor& aDesc)
+ : ChildOf(aParent),
+ mId(aId),
+ mFormat(aDesc.mFormat),
+ mBytesPerBlock(GetBytesPerBlock(aDesc.mFormat)) {}
+
+Texture::~Texture() { Cleanup(); }
+
+void Texture::Cleanup() {
+ if (mValid && mParent) {
+ mValid = false;
+ auto bridge = mParent->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendTextureDestroy(mId);
+ }
+ }
+}
+
+already_AddRefed<TextureView> Texture::CreateView(
+ const dom::GPUTextureViewDescriptor& aDesc) {
+ auto bridge = mParent->GetBridge();
+ RawId id = 0;
+ if (bridge->IsOpen()) {
+ id = bridge->TextureCreateView(mId, mParent->mId, aDesc);
+ }
+
+ RefPtr<TextureView> view = new TextureView(this, id);
+ return view.forget();
+}
+
+void Texture::Destroy() {
+ // TODO: we don't have to implement it right now, but it's used by the
+ // examples
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/Texture.h b/dom/webgpu/Texture.h
new file mode 100644
index 0000000000..63f87d2bcd
--- /dev/null
+++ b/dom/webgpu/Texture.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#ifndef GPU_Texture_H_
+#define GPU_Texture_H_
+
+#include "mozilla/WeakPtr.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla {
+namespace dom {
+struct GPUTextureDescriptor;
+struct GPUTextureViewDescriptor;
+enum class GPUTextureFormat : uint8_t;
+} // namespace dom
+
+namespace webgpu {
+namespace ffi {
+struct WGPUTextureViewDescriptor;
+} // namespace ffi
+
+class CanvasContext;
+class Device;
+class TextureView;
+
+class Texture final : public ObjectBase, public ChildOf<Device> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(Texture)
+ GPU_DECL_JS_WRAP(Texture)
+
+ Texture(Device* const aParent, RawId aId,
+ const dom::GPUTextureDescriptor& aDesc);
+ Device* GetParentDevice() { return mParent; }
+ const RawId mId;
+ const dom::GPUTextureFormat mFormat;
+ const Maybe<uint8_t> mBytesPerBlock;
+
+ WeakPtr<CanvasContext> mTargetContext;
+
+ private:
+ virtual ~Texture();
+ void Cleanup();
+
+ public:
+ already_AddRefed<TextureView> CreateView(
+ const dom::GPUTextureViewDescriptor& aDesc);
+ void Destroy();
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_Texture_H_
diff --git a/dom/webgpu/TextureView.cpp b/dom/webgpu/TextureView.cpp
new file mode 100644
index 0000000000..4401155b90
--- /dev/null
+++ b/dom/webgpu/TextureView.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "TextureView.h"
+
+#include "Device.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/webgpu/CanvasContext.h"
+#include "ipc/WebGPUChild.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(TextureView, mParent)
+GPU_IMPL_JS_WRAP(TextureView)
+
+TextureView::TextureView(Texture* const aParent, RawId aId)
+ : ChildOf(aParent), mId(aId) {}
+
+TextureView::~TextureView() { Cleanup(); }
+
+CanvasContext* TextureView::GetTargetContext() const {
+ return mParent->mTargetContext;
+} // namespace webgpu
+
+void TextureView::Cleanup() {
+ if (mValid && mParent && mParent->GetParentDevice()) {
+ mValid = false;
+ auto bridge = mParent->GetParentDevice()->GetBridge();
+ if (bridge && bridge->IsOpen()) {
+ bridge->SendTextureViewDestroy(mId);
+ }
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/TextureView.h b/dom/webgpu/TextureView.h
new file mode 100644
index 0000000000..a0c69c106b
--- /dev/null
+++ b/dom/webgpu/TextureView.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef GPU_TextureView_H_
+#define GPU_TextureView_H_
+
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+#include "mozilla/webgpu/WebGPUTypes.h"
+
+namespace mozilla::webgpu {
+
+class CanvasContext;
+class Texture;
+
+class TextureView final : public ObjectBase, public ChildOf<Texture> {
+ public:
+ GPU_DECL_CYCLE_COLLECTION(TextureView)
+ GPU_DECL_JS_WRAP(TextureView)
+
+ TextureView(Texture* const aParent, RawId aId);
+ CanvasContext* GetTargetContext() const;
+
+ const RawId mId;
+
+ private:
+ virtual ~TextureView();
+ void Cleanup();
+};
+
+} // namespace mozilla::webgpu
+
+#endif // GPU_TextureView_H_
diff --git a/dom/webgpu/ValidationError.cpp b/dom/webgpu/ValidationError.cpp
new file mode 100644
index 0000000000..119156713c
--- /dev/null
+++ b/dom/webgpu/ValidationError.cpp
@@ -0,0 +1,41 @@
+/* -*- 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 "ValidationError.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIGlobalObject.h"
+#include "nsReadableUtils.h"
+
+namespace mozilla::webgpu {
+
+GPU_IMPL_CYCLE_COLLECTION(ValidationError, mGlobal)
+GPU_IMPL_JS_WRAP(ValidationError)
+
+ValidationError::ValidationError(nsIGlobalObject* aGlobal,
+ const nsACString& aMessage)
+ : mGlobal(aGlobal) {
+ CopyUTF8toUTF16(aMessage, mMessage);
+}
+
+ValidationError::ValidationError(nsIGlobalObject* aGlobal,
+ const nsAString& aMessage)
+ : mGlobal(aGlobal), mMessage(aMessage) {}
+
+ValidationError::~ValidationError() = default;
+
+already_AddRefed<ValidationError> ValidationError::Constructor(
+ const dom::GlobalObject& aGlobal, const nsAString& aString,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.ThrowInvalidStateError("aGlobal is not nsIGlobalObject");
+ return nullptr;
+ }
+
+ return MakeAndAddRef<ValidationError>(global, aString);
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ValidationError.h b/dom/webgpu/ValidationError.h
new file mode 100644
index 0000000000..bdbc1ce2eb
--- /dev/null
+++ b/dom/webgpu/ValidationError.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#ifndef GPU_ValidationError_H_
+#define GPU_ValidationError_H_
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "ObjectModel.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class GlobalObject;
+} // namespace dom
+namespace webgpu {
+
+class ValidationError final : public nsWrapperCache {
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ nsString mMessage;
+
+ public:
+ GPU_DECL_CYCLE_COLLECTION(ValidationError)
+ GPU_DECL_JS_WRAP(ValidationError)
+ ValidationError(nsIGlobalObject* aGlobal, const nsACString& aMessage);
+ ValidationError(nsIGlobalObject* aGlobal, const nsAString& aMessage);
+
+ private:
+ virtual ~ValidationError();
+ void Cleanup() {}
+
+ public:
+ static already_AddRefed<ValidationError> Constructor(
+ const dom::GlobalObject& aGlobal, const nsAString& aString,
+ ErrorResult& aRv);
+ void GetMessage(nsAString& aMessage) const { aMessage = mMessage; }
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // GPU_ValidationError_H_
diff --git a/dom/webgpu/ipc/PWebGPU.ipdl b/dom/webgpu/ipc/PWebGPU.ipdl
new file mode 100644
index 0000000000..daa3873550
--- /dev/null
+++ b/dom/webgpu/ipc/PWebGPU.ipdl
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+using layers::RGBDescriptor from "mozilla/layers/LayersSurfaces.h";
+using layers::RemoteTextureId from "mozilla/layers/LayersTypes.h";
+using layers::RemoteTextureOwnerId from "mozilla/layers/LayersTypes.h";
+using RawId from "mozilla/webgpu/WebGPUTypes.h";
+using dom::GPURequestAdapterOptions from "mozilla/dom/WebGPUBinding.h";
+using dom::GPUCommandBufferDescriptor from "mozilla/dom/WebGPUBinding.h";
+using dom::GPUBufferDescriptor from "mozilla/dom/WebGPUBinding.h";
+using MaybeScopedError from "mozilla/webgpu/WebGPUTypes.h";
+using WebGPUCompilationMessage from "mozilla/webgpu/WebGPUTypes.h";
+[MoveOnly] using class mozilla::ipc::UnsafeSharedMemoryHandle from "mozilla/ipc/RawShmem.h";
+
+include "mozilla/ipc/ByteBufUtils.h";
+include "mozilla/layers/LayersMessageUtils.h";
+include "mozilla/webgpu/WebGPUSerialize.h";
+include "mozilla/layers/WebRenderMessageUtils.h";
+include protocol PCanvasManager;
+include PWebGPUTypes;
+
+namespace mozilla {
+namespace webgpu {
+
+/**
+ * Represents the connection between a WebGPUChild actor that issues WebGPU
+ * command from the content process, and a WebGPUParent in the compositor
+ * process that runs the commands.
+ */
+async protocol PWebGPU
+{
+ manager PCanvasManager;
+
+parent:
+ async DeviceAction(RawId selfId, ByteBuf buf);
+ async DeviceActionWithAck(RawId selfId, ByteBuf buf) returns (bool dummy);
+ async TextureAction(RawId selfId, RawId aDeviceId, ByteBuf buf);
+ async CommandEncoderAction(RawId selfId, RawId aDeviceId, ByteBuf buf);
+ async BumpImplicitBindGroupLayout(RawId pipelineId, bool isCompute, uint32_t index, RawId assignId);
+
+ async CreateBuffer(RawId deviceId, RawId bufferId, GPUBufferDescriptor desc, UnsafeSharedMemoryHandle shm);
+
+ async InstanceRequestAdapter(GPURequestAdapterOptions options, RawId[] ids) returns (ByteBuf byteBuf);
+ async AdapterRequestDevice(RawId selfId, ByteBuf buf, RawId newId) returns (bool success);
+ async AdapterDestroy(RawId selfId);
+ // TODO: We want to return an array of compilation messages.
+ async DeviceCreateShaderModule(RawId selfId, RawId bufferId, nsString label, nsCString code) returns (WebGPUCompilationMessage[] messages);
+ async BufferMap(RawId selfId, uint32_t aMode, uint64_t offset, uint64_t size) returns (BufferMapResult result);
+ async BufferUnmap(RawId deviceId, RawId bufferId, bool flush);
+ async BufferDestroy(RawId selfId);
+ async BufferDrop(RawId selfId);
+ async TextureDestroy(RawId selfId);
+ async TextureViewDestroy(RawId selfId);
+ async SamplerDestroy(RawId selfId);
+ async DeviceDestroy(RawId selfId);
+
+ async CommandEncoderFinish(RawId selfId, RawId deviceId, GPUCommandBufferDescriptor desc);
+ async CommandEncoderDestroy(RawId selfId);
+ async CommandBufferDestroy(RawId selfId);
+ async RenderBundleDestroy(RawId selfId);
+ async QueueSubmit(RawId selfId, RawId aDeviceId, RawId[] commandBuffers);
+ async QueueWriteAction(RawId selfId, RawId aDeviceId, ByteBuf buf, UnsafeSharedMemoryHandle shmem);
+
+ async BindGroupLayoutDestroy(RawId selfId);
+ async PipelineLayoutDestroy(RawId selfId);
+ async BindGroupDestroy(RawId selfId);
+ async ShaderModuleDestroy(RawId selfId);
+ async ComputePipelineDestroy(RawId selfId);
+ async RenderPipelineDestroy(RawId selfId);
+ async ImplicitLayoutDestroy(RawId implicitPlId, RawId[] implicitBglIds);
+ async DeviceCreateSwapChain(RawId selfId, RawId queueId, RGBDescriptor desc, RawId[] bufferIds, RemoteTextureOwnerId ownerId);
+ async SwapChainPresent(RawId textureId, RawId commandEncoderId, RemoteTextureId remoteTextureId, RemoteTextureOwnerId remoteTextureOwnerId);
+ async SwapChainDestroy(RemoteTextureOwnerId ownerId);
+
+ async DevicePushErrorScope(RawId selfId);
+ async DevicePopErrorScope(RawId selfId) returns (MaybeScopedError maybeError);
+
+ // Generate an error on the Device timeline for `deviceId`.
+ // The `message` parameter is interpreted as UTF-8.
+ async GenerateError(RawId deviceId, nsCString message);
+
+child:
+ async DeviceUncapturedError(RawId aDeviceId, nsCString message);
+ async DropAction(ByteBuf buf);
+ async __delete__();
+};
+
+} // webgpu
+} // mozilla
diff --git a/dom/webgpu/ipc/PWebGPUTypes.ipdlh b/dom/webgpu/ipc/PWebGPUTypes.ipdlh
new file mode 100644
index 0000000000..98f062856c
--- /dev/null
+++ b/dom/webgpu/ipc/PWebGPUTypes.ipdlh
@@ -0,0 +1,26 @@
+/* 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/. */
+
+using struct mozilla::null_t from "mozilla/ipc/IPCCore.h";
+
+namespace mozilla {
+namespace webgpu {
+
+struct BufferMapSuccess {
+ uint64_t offset;
+ uint64_t size;
+ bool writable;
+};
+
+struct BufferMapError {
+ nsCString message;
+};
+
+union BufferMapResult {
+ BufferMapSuccess;
+ BufferMapError;
+};
+
+} // namespace layers
+} // namespace mozilla
diff --git a/dom/webgpu/ipc/WebGPUChild.cpp b/dom/webgpu/ipc/WebGPUChild.cpp
new file mode 100644
index 0000000000..5bc08c4386
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUChild.cpp
@@ -0,0 +1,1080 @@
+/* -*- Mode: C++; tab-width: 20; 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 "WebGPUChild.h"
+#include "js/Warnings.h" // JS::WarnUTF8
+#include "mozilla/EnumTypeTraits.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/dom/GPUUncapturedErrorEvent.h"
+#include "mozilla/webgpu/ValidationError.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "Adapter.h"
+#include "DeviceLostInfo.h"
+#include "Sampler.h"
+#include "CompilationInfo.h"
+#include "mozilla/ipc/RawShmem.h"
+
+namespace mozilla::webgpu {
+
+NS_IMPL_CYCLE_COLLECTION(WebGPUChild)
+
+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(), "%s", flatString.get());
+ }
+ } else {
+ printf_stderr("Validation error without device target: %s\n",
+ flatString.get());
+ }
+}
+
+static ffi::WGPUCompareFunction ConvertCompareFunction(
+ const dom::GPUCompareFunction& aCompare) {
+ // Value of 0 = Undefined is reserved on the C side for "null" semantics.
+ return ffi::WGPUCompareFunction(UnderlyingValue(aCompare) + 1);
+}
+
+static ffi::WGPUTextureFormat ConvertTextureFormat(
+ const dom::GPUTextureFormat& aFormat) {
+ ffi::WGPUTextureFormat result = {ffi::WGPUTextureFormat_Sentinel};
+ switch (aFormat) {
+ case dom::GPUTextureFormat::R8unorm:
+ result.tag = ffi::WGPUTextureFormat_R8Unorm;
+ break;
+ case dom::GPUTextureFormat::R8snorm:
+ result.tag = ffi::WGPUTextureFormat_R8Snorm;
+ break;
+ case dom::GPUTextureFormat::R8uint:
+ result.tag = ffi::WGPUTextureFormat_R8Uint;
+ break;
+ case dom::GPUTextureFormat::R8sint:
+ result.tag = ffi::WGPUTextureFormat_R8Sint;
+ break;
+ case dom::GPUTextureFormat::R16uint:
+ result.tag = ffi::WGPUTextureFormat_R16Uint;
+ break;
+ case dom::GPUTextureFormat::R16sint:
+ result.tag = ffi::WGPUTextureFormat_R16Sint;
+ break;
+ case dom::GPUTextureFormat::R16float:
+ result.tag = ffi::WGPUTextureFormat_R16Float;
+ break;
+ case dom::GPUTextureFormat::Rg8unorm:
+ result.tag = ffi::WGPUTextureFormat_Rg8Unorm;
+ break;
+ case dom::GPUTextureFormat::Rg8snorm:
+ result.tag = ffi::WGPUTextureFormat_Rg8Snorm;
+ break;
+ case dom::GPUTextureFormat::Rg8uint:
+ result.tag = ffi::WGPUTextureFormat_Rg8Uint;
+ break;
+ case dom::GPUTextureFormat::Rg8sint:
+ result.tag = ffi::WGPUTextureFormat_Rg8Sint;
+ break;
+ case dom::GPUTextureFormat::R32uint:
+ result.tag = ffi::WGPUTextureFormat_R32Uint;
+ break;
+ case dom::GPUTextureFormat::R32sint:
+ result.tag = ffi::WGPUTextureFormat_R32Sint;
+ break;
+ case dom::GPUTextureFormat::R32float:
+ result.tag = ffi::WGPUTextureFormat_R32Float;
+ break;
+ case dom::GPUTextureFormat::Rg16uint:
+ result.tag = ffi::WGPUTextureFormat_Rg16Uint;
+ break;
+ case dom::GPUTextureFormat::Rg16sint:
+ result.tag = ffi::WGPUTextureFormat_Rg16Sint;
+ break;
+ case dom::GPUTextureFormat::Rg16float:
+ result.tag = ffi::WGPUTextureFormat_Rg16Float;
+ break;
+ case dom::GPUTextureFormat::Rgba8unorm:
+ result.tag = ffi::WGPUTextureFormat_Rgba8Unorm;
+ break;
+ case dom::GPUTextureFormat::Rgba8unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Rgba8UnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Rgba8snorm:
+ result.tag = ffi::WGPUTextureFormat_Rgba8Snorm;
+ break;
+ case dom::GPUTextureFormat::Rgba8uint:
+ result.tag = ffi::WGPUTextureFormat_Rgba8Uint;
+ break;
+ case dom::GPUTextureFormat::Rgba8sint:
+ result.tag = ffi::WGPUTextureFormat_Rgba8Sint;
+ break;
+ case dom::GPUTextureFormat::Bgra8unorm:
+ result.tag = ffi::WGPUTextureFormat_Bgra8Unorm;
+ break;
+ case dom::GPUTextureFormat::Bgra8unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Bgra8UnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Rgb10a2unorm:
+ result.tag = ffi::WGPUTextureFormat_Rgb10a2Unorm;
+ break;
+ case dom::GPUTextureFormat::Rg11b10float:
+ result.tag = ffi::WGPUTextureFormat_Rg11b10Float;
+ break;
+ case dom::GPUTextureFormat::Rg32uint:
+ result.tag = ffi::WGPUTextureFormat_Rg32Uint;
+ break;
+ case dom::GPUTextureFormat::Rg32sint:
+ result.tag = ffi::WGPUTextureFormat_Rg32Sint;
+ break;
+ case dom::GPUTextureFormat::Rg32float:
+ result.tag = ffi::WGPUTextureFormat_Rg32Float;
+ break;
+ case dom::GPUTextureFormat::Rgba16uint:
+ result.tag = ffi::WGPUTextureFormat_Rgba16Uint;
+ break;
+ case dom::GPUTextureFormat::Rgba16sint:
+ result.tag = ffi::WGPUTextureFormat_Rgba16Sint;
+ break;
+ case dom::GPUTextureFormat::Rgba16float:
+ result.tag = ffi::WGPUTextureFormat_Rgba16Float;
+ break;
+ case dom::GPUTextureFormat::Rgba32uint:
+ result.tag = ffi::WGPUTextureFormat_Rgba32Uint;
+ break;
+ case dom::GPUTextureFormat::Rgba32sint:
+ result.tag = ffi::WGPUTextureFormat_Rgba32Sint;
+ break;
+ case dom::GPUTextureFormat::Rgba32float:
+ result.tag = ffi::WGPUTextureFormat_Rgba32Float;
+ break;
+ case dom::GPUTextureFormat::Depth32float:
+ result.tag = ffi::WGPUTextureFormat_Depth32Float;
+ break;
+ case dom::GPUTextureFormat::Bc1_rgba_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc1RgbaUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc1_rgba_unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Bc1RgbaUnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Bc4_r_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc4RUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc4_r_snorm:
+ result.tag = ffi::WGPUTextureFormat_Bc4RSnorm;
+ break;
+ case dom::GPUTextureFormat::Bc2_rgba_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc2RgbaUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc2_rgba_unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Bc2RgbaUnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Bc3_rgba_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc3RgbaUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc3_rgba_unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Bc3RgbaUnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Bc5_rg_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc5RgUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc5_rg_snorm:
+ result.tag = ffi::WGPUTextureFormat_Bc5RgSnorm;
+ break;
+ case dom::GPUTextureFormat::Bc6h_rgb_ufloat:
+ result.tag = ffi::WGPUTextureFormat_Bc6hRgbUfloat;
+ break;
+ case dom::GPUTextureFormat::Bc6h_rgb_float:
+ result.tag = ffi::WGPUTextureFormat_Bc6hRgbSfloat;
+ break;
+ case dom::GPUTextureFormat::Bc7_rgba_unorm:
+ result.tag = ffi::WGPUTextureFormat_Bc7RgbaUnorm;
+ break;
+ case dom::GPUTextureFormat::Bc7_rgba_unorm_srgb:
+ result.tag = ffi::WGPUTextureFormat_Bc7RgbaUnormSrgb;
+ break;
+ case dom::GPUTextureFormat::Depth24plus:
+ result.tag = ffi::WGPUTextureFormat_Depth24Plus;
+ break;
+ case dom::GPUTextureFormat::Depth24plus_stencil8:
+ result.tag = ffi::WGPUTextureFormat_Depth24PlusStencil8;
+ break;
+ case dom::GPUTextureFormat::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+
+ // Clang will check for us that the switch above is exhaustive,
+ // but not if we add a 'default' case. So, check this here.
+ MOZ_ASSERT(result.tag != ffi::WGPUTextureFormat_Sentinel,
+ "unexpected texture format enum");
+
+ return result;
+}
+
+void WebGPUChild::ConvertTextureFormatRef(const dom::GPUTextureFormat& aInput,
+ ffi::WGPUTextureFormat& aOutput) {
+ aOutput = ConvertTextureFormat(aInput);
+}
+
+static UniquePtr<ffi::WGPUClient> initialize() {
+ ffi::WGPUInfrastructure infra = ffi::wgpu_client_new();
+ return UniquePtr<ffi::WGPUClient>{infra.client};
+}
+
+WebGPUChild::WebGPUChild() : mClient(initialize()) {}
+
+WebGPUChild::~WebGPUChild() = default;
+
+RefPtr<AdapterPromise> WebGPUChild::InstanceRequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions) {
+ const int max_ids = 10;
+ RawId ids[max_ids] = {0};
+ unsigned long count =
+ ffi::wgpu_client_make_adapter_ids(mClient.get(), ids, max_ids);
+
+ nsTArray<RawId> sharedIds(count);
+ for (unsigned long i = 0; i != count; ++i) {
+ sharedIds.AppendElement(ids[i]);
+ }
+
+ return SendInstanceRequestAdapter(aOptions, sharedIds)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](ipc::ByteBuf&& aInfoBuf) {
+ // Ideally, we'd just send an empty ByteBuf, but the IPC code
+ // complains if the capacity is zero...
+ // So for the case where an adapter wasn't found, we just
+ // transfer a single 0u64 in this buffer.
+ return aInfoBuf.mLen > sizeof(uint64_t)
+ ? AdapterPromise::CreateAndResolve(std::move(aInfoBuf),
+ __func__)
+ : AdapterPromise::CreateAndReject(Nothing(), __func__);
+ },
+ [](const ipc::ResponseRejectReason& aReason) {
+ return AdapterPromise::CreateAndReject(Some(aReason), __func__);
+ });
+}
+
+Maybe<DeviceRequest> WebGPUChild::AdapterRequestDevice(
+ RawId aSelfId, const dom::GPUDeviceDescriptor& aDesc,
+ ffi::WGPULimits* aLimits) {
+ ffi::WGPUDeviceDescriptor desc = {};
+ ffi::wgpu_client_fill_default_limits(&desc.limits);
+
+ // webgpu::StringHelper label(aDesc.mLabel);
+ // desc.label = label.Get();
+
+ const auto featureBits = Adapter::MakeFeatureBits(aDesc.mRequiredFeatures);
+ if (!featureBits) {
+ return Nothing();
+ }
+ desc.features = *featureBits;
+
+ if (aDesc.mRequiredLimits.WasPassed()) {
+ for (const auto& entry : aDesc.mRequiredLimits.Value().Entries()) {
+ const uint32_t valueU32 =
+ entry.mValue < std::numeric_limits<uint32_t>::max()
+ ? entry.mValue
+ : std::numeric_limits<uint32_t>::max();
+ if (entry.mKey == u"maxTextureDimension1D"_ns) {
+ desc.limits.max_texture_dimension_1d = valueU32;
+ } else if (entry.mKey == u"maxTextureDimension2D"_ns) {
+ desc.limits.max_texture_dimension_2d = valueU32;
+ } else if (entry.mKey == u"maxTextureDimension3D"_ns) {
+ desc.limits.max_texture_dimension_3d = valueU32;
+ } else if (entry.mKey == u"maxTextureArrayLayers"_ns) {
+ desc.limits.max_texture_array_layers = valueU32;
+ } else if (entry.mKey == u"maxBindGroups"_ns) {
+ desc.limits.max_bind_groups = valueU32;
+ } else if (entry.mKey ==
+ u"maxDynamicUniformBuffersPerPipelineLayout"_ns) {
+ desc.limits.max_dynamic_uniform_buffers_per_pipeline_layout = valueU32;
+ } else if (entry.mKey ==
+ u"maxDynamicStorageBuffersPerPipelineLayout"_ns) {
+ desc.limits.max_dynamic_storage_buffers_per_pipeline_layout = valueU32;
+ } else if (entry.mKey == u"maxSampledTexturesPerShaderStage"_ns) {
+ desc.limits.max_sampled_textures_per_shader_stage = valueU32;
+ } else if (entry.mKey == u"maxSamplersPerShaderStage"_ns) {
+ desc.limits.max_samplers_per_shader_stage = valueU32;
+ } else if (entry.mKey == u"maxStorageBuffersPerShaderStage"_ns) {
+ desc.limits.max_storage_buffers_per_shader_stage = valueU32;
+ } else if (entry.mKey == u"maxStorageTexturesPerShaderStage"_ns) {
+ desc.limits.max_storage_textures_per_shader_stage = valueU32;
+ } else if (entry.mKey == u"maxUniformBuffersPerShaderStage"_ns) {
+ desc.limits.max_uniform_buffers_per_shader_stage = valueU32;
+ } else if (entry.mKey == u"maxUniformBufferBindingSize"_ns) {
+ desc.limits.max_uniform_buffer_binding_size = entry.mValue;
+ } else if (entry.mKey == u"maxStorageBufferBindingSize"_ns) {
+ desc.limits.max_storage_buffer_binding_size = entry.mValue;
+ } else if (entry.mKey == u"minUniformBufferOffsetAlignment"_ns) {
+ desc.limits.min_uniform_buffer_offset_alignment = valueU32;
+ } else if (entry.mKey == u"minStorageBufferOffsetAlignment"_ns) {
+ desc.limits.min_storage_buffer_offset_alignment = valueU32;
+ } else if (entry.mKey == u"maxVertexBuffers"_ns) {
+ desc.limits.max_vertex_buffers = valueU32;
+ } else if (entry.mKey == u"maxVertexAttributes"_ns) {
+ desc.limits.max_vertex_attributes = valueU32;
+ } else if (entry.mKey == u"maxVertexBufferArrayStride"_ns) {
+ desc.limits.max_vertex_buffer_array_stride = valueU32;
+ } else if (entry.mKey == u"maxComputeWorkgroupSizeX"_ns) {
+ desc.limits.max_compute_workgroup_size_x = valueU32;
+ } else if (entry.mKey == u"maxComputeWorkgroupSizeY"_ns) {
+ desc.limits.max_compute_workgroup_size_y = valueU32;
+ } else if (entry.mKey == u"maxComputeWorkgroupSizeZ"_ns) {
+ desc.limits.max_compute_workgroup_size_z = valueU32;
+ } else if (entry.mKey == u"maxComputeWorkgroupsPerDimension"_ns) {
+ desc.limits.max_compute_workgroups_per_dimension = valueU32;
+ } else {
+ NS_WARNING(nsPrintfCString("Requested limit '%s' is not recognized.",
+ NS_ConvertUTF16toUTF8(entry.mKey).get())
+ .get());
+ return Nothing();
+ }
+
+ // TODO: maxInterStageShaderComponents
+ // TODO: maxComputeWorkgroupStorageSize
+ // TODO: maxComputeInvocationsPerWorkgroup
+ }
+ }
+
+ RawId id = ffi::wgpu_client_make_device_id(mClient.get(), aSelfId);
+
+ ByteBuf bb;
+ ffi::wgpu_client_serialize_device_descriptor(&desc, ToFFI(&bb));
+
+ DeviceRequest request;
+ request.mId = id;
+ request.mPromise = SendAdapterRequestDevice(aSelfId, std::move(bb), id);
+ *aLimits = desc.limits;
+
+ return Some(std::move(request));
+}
+
+RawId WebGPUChild::DeviceCreateBuffer(RawId aSelfId,
+ const dom::GPUBufferDescriptor& aDesc,
+ ipc::UnsafeSharedMemoryHandle&& aShmem) {
+ RawId bufferId = ffi::wgpu_client_make_buffer_id(mClient.get(), aSelfId);
+ if (!SendCreateBuffer(aSelfId, bufferId, aDesc, std::move(aShmem))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return bufferId;
+}
+
+RawId WebGPUChild::DeviceCreateTexture(RawId aSelfId,
+ const dom::GPUTextureDescriptor& aDesc) {
+ 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;
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_texture(mClient.get(), aSelfId, &desc,
+ ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::TextureCreateView(
+ RawId aSelfId, RawId aDeviceId,
+ const dom::GPUTextureViewDescriptor& aDesc) {
+ ffi::WGPUTextureViewDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ffi::WGPUTextureFormat format = {ffi::WGPUTextureFormat_Sentinel};
+ if (aDesc.mFormat.WasPassed()) {
+ format = ConvertTextureFormat(aDesc.mFormat.Value());
+ desc.format = &format;
+ }
+ ffi::WGPUTextureViewDimension dimension =
+ ffi::WGPUTextureViewDimension_Sentinel;
+ if (aDesc.mDimension.WasPassed()) {
+ dimension = ffi::WGPUTextureViewDimension(aDesc.mDimension.Value());
+ desc.dimension = &dimension;
+ }
+
+ desc.aspect = ffi::WGPUTextureAspect(aDesc.mAspect);
+ desc.base_mip_level = aDesc.mBaseMipLevel;
+ desc.mip_level_count =
+ aDesc.mMipLevelCount.WasPassed() ? aDesc.mMipLevelCount.Value() : 0;
+ desc.base_array_layer = aDesc.mBaseArrayLayer;
+ desc.array_layer_count =
+ aDesc.mArrayLayerCount.WasPassed() ? aDesc.mArrayLayerCount.Value() : 0;
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_texture_view(mClient.get(), aSelfId, &desc,
+ ToFFI(&bb));
+ if (!SendTextureAction(aSelfId, aDeviceId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::DeviceCreateSampler(RawId aSelfId,
+ 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;
+ }
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_sampler(mClient.get(), aSelfId, &desc,
+ ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::DeviceCreateCommandEncoder(
+ RawId aSelfId, const dom::GPUCommandEncoderDescriptor& aDesc) {
+ ffi::WGPUCommandEncoderDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_command_encoder(mClient.get(), aSelfId,
+ &desc, ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::CommandEncoderFinish(
+ RawId aSelfId, RawId aDeviceId,
+ const dom::GPUCommandBufferDescriptor& aDesc) {
+ if (!SendCommandEncoderFinish(aSelfId, aDeviceId, aDesc)) {
+ MOZ_CRASH("IPC failure");
+ }
+ // We rely on knowledge that `CommandEncoderId` == `CommandBufferId`
+ // TODO: refactor this to truly behave as if the encoder is being finished,
+ // and a new command buffer ID is being created from it. Resolve the ID
+ // type aliasing at the place that introduces it: `wgpu-core`.
+ return aSelfId;
+}
+
+RawId WebGPUChild::RenderBundleEncoderFinish(
+ ffi::WGPURenderBundleEncoder& aEncoder, RawId aDeviceId,
+ const dom::GPURenderBundleDescriptor& aDesc) {
+ ffi::WGPURenderBundleDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ipc::ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_render_bundle(
+ mClient.get(), &aEncoder, aDeviceId, &desc, ToFFI(&bb));
+
+ if (!SendDeviceAction(aDeviceId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+
+ return id;
+}
+
+RawId WebGPUChild::DeviceCreateBindGroupLayout(
+ RawId aSelfId, 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();
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_bind_group_layout(mClient.get(), aSelfId,
+ &desc, ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::DeviceCreatePipelineLayout(
+ RawId aSelfId, const dom::GPUPipelineLayoutDescriptor& aDesc) {
+ nsTArray<ffi::WGPUBindGroupLayoutId> bindGroupLayouts(
+ aDesc.mBindGroupLayouts.Length());
+ for (const auto& layout : aDesc.mBindGroupLayouts) {
+ if (!layout->IsValid()) {
+ return 0;
+ }
+ 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();
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_pipeline_layout(mClient.get(), aSelfId,
+ &desc, ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RawId WebGPUChild::DeviceCreateBindGroup(
+ RawId aSelfId, const dom::GPUBindGroupDescriptor& aDesc) {
+ if (!aDesc.mLayout->IsValid()) {
+ return 0;
+ }
+
+ 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();
+ e.buffer = bufBinding.mBuffer->mId;
+ e.offset = bufBinding.mOffset;
+ e.size = bufBinding.mSize.WasPassed() ? bufBinding.mSize.Value() : 0;
+ }
+ if (entry.mResource.IsGPUTextureView()) {
+ e.texture_view = entry.mResource.GetAsGPUTextureView()->mId;
+ }
+ if (entry.mResource.IsGPUSampler()) {
+ e.sampler = entry.mResource.GetAsGPUSampler()->mId;
+ }
+ 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();
+
+ ByteBuf bb;
+ RawId id = ffi::wgpu_client_create_bind_group(mClient.get(), aSelfId, &desc,
+ ToFFI(&bb));
+ if (!SendDeviceAction(aSelfId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+already_AddRefed<ShaderModule> WebGPUChild::DeviceCreateShaderModule(
+ Device* aDevice, const dom::GPUShaderModuleDescriptor& aDesc,
+ RefPtr<dom::Promise> aPromise) {
+ RawId deviceId = aDevice->mId;
+ RawId moduleId =
+ ffi::wgpu_client_make_shader_module_id(mClient.get(), deviceId);
+
+ RefPtr<ShaderModule> shaderModule =
+ new ShaderModule(aDevice, moduleId, aPromise);
+
+ nsString noLabel;
+ const nsString& label =
+ aDesc.mLabel.WasPassed() ? aDesc.mLabel.Value() : noLabel;
+ SendDeviceCreateShaderModule(deviceId, moduleId, label, aDesc.mCode)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aPromise,
+ shaderModule](nsTArray<WebGPUCompilationMessage>&& messages) {
+ RefPtr<CompilationInfo> infoObject(
+ new CompilationInfo(shaderModule));
+ infoObject->SetMessages(messages);
+ aPromise->MaybeResolve(infoObject);
+ },
+ [aPromise](const ipc::ResponseRejectReason& aReason) {
+ aPromise->MaybeRejectWithNotSupportedError("IPC error");
+ });
+
+ return shaderModule.forget();
+}
+
+RawId WebGPUChild::DeviceCreateComputePipelineImpl(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc, ByteBuf* const aByteBuf) {
+ ffi::WGPUComputePipelineDescriptor desc = {};
+ nsCString label, entryPoint;
+ if (aDesc.mLabel.WasPassed()) {
+ LossyCopyUTF16toASCII(aDesc.mLabel.Value(), label);
+ desc.label = label.get();
+ }
+ if (aDesc.mLayout.WasPassed()) {
+ desc.layout = aDesc.mLayout.Value().mId;
+ }
+ desc.stage.module = aDesc.mCompute.mModule->mId;
+ LossyCopyUTF16toASCII(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(
+ mClient.get(), 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 WebGPUChild::DeviceCreateComputePipeline(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc) {
+ ByteBuf bb;
+ const RawId id = DeviceCreateComputePipelineImpl(aContext, aDesc, &bb);
+
+ if (!SendDeviceAction(aContext->mParentId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RefPtr<PipelinePromise> WebGPUChild::DeviceCreateComputePipelineAsync(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc) {
+ ByteBuf bb;
+ const RawId id = DeviceCreateComputePipelineImpl(aContext, aDesc, &bb);
+
+ return SendDeviceActionWithAck(aContext->mParentId, std::move(bb))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [id](bool aDummy) {
+ Unused << aDummy;
+ return PipelinePromise::CreateAndResolve(id, __func__);
+ },
+ [](const ipc::ResponseRejectReason& aReason) {
+ return PipelinePromise::CreateAndReject(aReason, __func__);
+ });
+}
+
+static ffi::WGPUMultisampleState ConvertMultisampleState(
+ const dom::GPUMultisampleState& aDesc) {
+ ffi::WGPUMultisampleState desc = {};
+ desc.count = aDesc.mCount;
+ desc.mask = aDesc.mMask;
+ desc.alpha_to_coverage_enabled = aDesc.mAlphaToCoverageEnabled;
+ return desc;
+}
+
+static ffi::WGPUBlendComponent ConvertBlendComponent(
+ const dom::GPUBlendComponent& aDesc) {
+ ffi::WGPUBlendComponent desc = {};
+ desc.src_factor = ffi::WGPUBlendFactor(aDesc.mSrcFactor);
+ desc.dst_factor = ffi::WGPUBlendFactor(aDesc.mDstFactor);
+ desc.operation = ffi::WGPUBlendOperation(aDesc.mOperation);
+ return desc;
+}
+
+static ffi::WGPUStencilFaceState ConvertStencilFaceState(
+ const dom::GPUStencilFaceState& aDesc) {
+ ffi::WGPUStencilFaceState desc = {};
+ desc.compare = ConvertCompareFunction(aDesc.mCompare);
+ desc.fail_op = ffi::WGPUStencilOperation(aDesc.mFailOp);
+ desc.depth_fail_op = ffi::WGPUStencilOperation(aDesc.mDepthFailOp);
+ desc.pass_op = ffi::WGPUStencilOperation(aDesc.mPassOp);
+ return desc;
+}
+
+static ffi::WGPUDepthStencilState ConvertDepthStencilState(
+ const dom::GPUDepthStencilState& aDesc) {
+ ffi::WGPUDepthStencilState desc = {};
+ desc.format = ConvertTextureFormat(aDesc.mFormat);
+ desc.depth_write_enabled = aDesc.mDepthWriteEnabled;
+ desc.depth_compare = ConvertCompareFunction(aDesc.mDepthCompare);
+ desc.stencil.front = ConvertStencilFaceState(aDesc.mStencilFront);
+ desc.stencil.back = ConvertStencilFaceState(aDesc.mStencilBack);
+ desc.stencil.read_mask = aDesc.mStencilReadMask;
+ desc.stencil.write_mask = aDesc.mStencilWriteMask;
+ desc.bias.constant = aDesc.mDepthBias;
+ desc.bias.slope_scale = aDesc.mDepthBiasSlopeScale;
+ desc.bias.clamp = aDesc.mDepthBiasClamp;
+ return desc;
+}
+
+RawId WebGPUChild::DeviceCreateRenderPipelineImpl(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc, 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.WasPassed()) {
+ desc.layout = aDesc.mLayout.Value().mId;
+ }
+
+ {
+ const auto& stage = aDesc.mVertex;
+ vertexState.stage.module = stage.mModule->mId;
+ LossyCopyUTF16toASCII(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;
+ LossyCopyUTF16toASCII(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.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(
+ mClient.get(), 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 WebGPUChild::DeviceCreateRenderPipeline(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc) {
+ ByteBuf bb;
+ const RawId id = DeviceCreateRenderPipelineImpl(aContext, aDesc, &bb);
+
+ if (!SendDeviceAction(aContext->mParentId, std::move(bb))) {
+ MOZ_CRASH("IPC failure");
+ }
+ return id;
+}
+
+RefPtr<PipelinePromise> WebGPUChild::DeviceCreateRenderPipelineAsync(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc) {
+ ByteBuf bb;
+ const RawId id = DeviceCreateRenderPipelineImpl(aContext, aDesc, &bb);
+
+ return SendDeviceActionWithAck(aContext->mParentId, std::move(bb))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [id](bool aDummy) {
+ Unused << aDummy;
+ return PipelinePromise::CreateAndResolve(id, __func__);
+ },
+ [](const ipc::ResponseRejectReason& aReason) {
+ return PipelinePromise::CreateAndReject(aReason, __func__);
+ });
+}
+
+ipc::IPCResult WebGPUChild::RecvDeviceUncapturedError(
+ RawId aDeviceId, const nsACString& aMessage) {
+ auto targetIter = mDeviceMap.find(aDeviceId);
+ if (!aDeviceId || targetIter == mDeviceMap.end()) {
+ JsWarning(nullptr, aMessage);
+ } else {
+ auto* target = targetIter->second.get();
+ MOZ_ASSERT(target);
+ // We don't want to spam the errors to the console indefinitely
+ if (target->CheckNewWarning(aMessage)) {
+ JsWarning(target->GetOwnerGlobal(), aMessage);
+
+ dom::GPUUncapturedErrorEventInit init;
+ init.mError.SetAsGPUValidationError() =
+ new ValidationError(target->GetParentObject(), aMessage);
+ RefPtr<mozilla::dom::GPUUncapturedErrorEvent> event =
+ dom::GPUUncapturedErrorEvent::Constructor(
+ target, u"uncapturederror"_ns, init);
+ target->DispatchEvent(*event);
+ }
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUChild::RecvDropAction(const ipc::ByteBuf& aByteBuf) {
+ const auto* byteBuf = ToFFI(&aByteBuf);
+ ffi::wgpu_client_drop_action(mClient.get(), byteBuf);
+ return IPC_OK();
+}
+
+void WebGPUChild::DeviceCreateSwapChain(
+ RawId aSelfId, const RGBDescriptor& aRgbDesc, size_t maxBufferCount,
+ const layers::RemoteTextureOwnerId& aOwnerId) {
+ RawId queueId = aSelfId; // TODO: multiple queues
+ nsTArray<RawId> bufferIds(maxBufferCount);
+ for (size_t i = 0; i < maxBufferCount; ++i) {
+ bufferIds.AppendElement(
+ ffi::wgpu_client_make_buffer_id(mClient.get(), aSelfId));
+ }
+ SendDeviceCreateSwapChain(aSelfId, queueId, aRgbDesc, bufferIds, aOwnerId);
+}
+
+void WebGPUChild::SwapChainPresent(RawId aTextureId,
+ const RemoteTextureId& aRemoteTextureId,
+ const RemoteTextureOwnerId& aOwnerId) {
+ // Hack: the function expects `DeviceId`, but it only uses it for `backend()`
+ // selection.
+ RawId encoderId = ffi::wgpu_client_make_encoder_id(mClient.get(), aTextureId);
+ SendSwapChainPresent(aTextureId, encoderId, aRemoteTextureId, aOwnerId);
+}
+
+void WebGPUChild::RegisterDevice(Device* const aDevice) {
+ mDeviceMap.insert({aDevice->mId, aDevice});
+}
+
+void WebGPUChild::UnregisterDevice(RawId aId) {
+ mDeviceMap.erase(aId);
+ if (IsOpen()) {
+ SendDeviceDestroy(aId);
+ }
+}
+
+void WebGPUChild::FreeUnregisteredInParentDevice(RawId aId) {
+ ffi::wgpu_client_kill_device_id(mClient.get(), aId);
+ mDeviceMap.erase(aId);
+}
+
+void WebGPUChild::ActorDestroy(ActorDestroyReason) {
+ // Resolving the promise could cause us to update the original map if the
+ // callee frees the Device objects immediately. Since any remaining entries
+ // in the map are no longer valid, we can just move the map onto the stack.
+ const auto deviceMap = std::move(mDeviceMap);
+ mDeviceMap.clear();
+
+ for (const auto& targetIter : deviceMap) {
+ RefPtr<Device> device = targetIter.second.get();
+ if (!device) {
+ // The Device may have gotten freed when we resolved the Promise for
+ // another Device in the map.
+ continue;
+ }
+
+ RefPtr<dom::Promise> promise = device->MaybeGetLost();
+ if (!promise) {
+ continue;
+ }
+
+ auto info = MakeRefPtr<DeviceLostInfo>(device->GetParentObject(),
+ u"WebGPUChild destroyed"_ns);
+
+ // We have strong references to both the Device and the DeviceLostInfo and
+ // the Promise objects on the stack which keeps them alive for long enough.
+ promise->MaybeResolve(info);
+ }
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ipc/WebGPUChild.h b/dom/webgpu/ipc/WebGPUChild.h
new file mode 100644
index 0000000000..7ab993239c
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUChild.h
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+#ifndef WEBGPU_CHILD_H_
+#define WEBGPU_CHILD_H_
+
+#include "mozilla/webgpu/PWebGPUChild.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace mozilla {
+namespace ipc {
+class UnsafeSharedMemoryHandle;
+} // namespace ipc
+namespace dom {
+struct GPURequestAdapterOptions;
+} // namespace dom
+namespace layers {
+class CompositorBridgeChild;
+} // namespace layers
+namespace webgpu {
+namespace ffi {
+struct WGPUClient;
+struct WGPULimits;
+struct WGPUTextureViewDescriptor;
+} // namespace ffi
+
+using AdapterPromise =
+ MozPromise<ipc::ByteBuf, Maybe<ipc::ResponseRejectReason>, true>;
+using PipelinePromise = MozPromise<RawId, ipc::ResponseRejectReason, true>;
+using DevicePromise = MozPromise<bool, ipc::ResponseRejectReason, true>;
+
+struct PipelineCreationContext {
+ RawId mParentId = 0;
+ RawId mImplicitPipelineLayoutId = 0;
+ nsTArray<RawId> mImplicitBindGroupLayoutIds;
+};
+
+struct DeviceRequest {
+ RawId mId = 0;
+ RefPtr<DevicePromise> mPromise;
+ // Note: we could put `ffi::WGPULimits` in here as well,
+ // but we don't want to #include ffi stuff in this header
+};
+
+ffi::WGPUByteBuf* ToFFI(ipc::ByteBuf* x);
+
+class WebGPUChild final : public PWebGPUChild, public SupportsWeakPtr {
+ public:
+ friend class layers::CompositorBridgeChild;
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(WebGPUChild)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_INHERITED(WebGPUChild)
+
+ public:
+ explicit WebGPUChild();
+
+ bool IsOpen() const { return CanSend(); }
+
+ RefPtr<AdapterPromise> InstanceRequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions);
+ Maybe<DeviceRequest> AdapterRequestDevice(
+ RawId aSelfId, const dom::GPUDeviceDescriptor& aDesc,
+ ffi::WGPULimits* aLimits);
+ RawId DeviceCreateBuffer(RawId aSelfId, const dom::GPUBufferDescriptor& aDesc,
+ ipc::UnsafeSharedMemoryHandle&& aShmem);
+ RawId DeviceCreateTexture(RawId aSelfId,
+ const dom::GPUTextureDescriptor& aDesc);
+ RawId TextureCreateView(RawId aSelfId, RawId aDeviceId,
+ const dom::GPUTextureViewDescriptor& aDesc);
+ RawId DeviceCreateSampler(RawId aSelfId,
+ const dom::GPUSamplerDescriptor& aDesc);
+ RawId DeviceCreateCommandEncoder(
+ RawId aSelfId, const dom::GPUCommandEncoderDescriptor& aDesc);
+ RawId CommandEncoderFinish(RawId aSelfId, RawId aDeviceId,
+ const dom::GPUCommandBufferDescriptor& aDesc);
+ RawId RenderBundleEncoderFinish(ffi::WGPURenderBundleEncoder& aEncoder,
+ RawId aDeviceId,
+ const dom::GPURenderBundleDescriptor& aDesc);
+ RawId DeviceCreateBindGroupLayout(
+ RawId aSelfId, const dom::GPUBindGroupLayoutDescriptor& aDesc);
+ RawId DeviceCreatePipelineLayout(
+ RawId aSelfId, const dom::GPUPipelineLayoutDescriptor& aDesc);
+ RawId DeviceCreateBindGroup(RawId aSelfId,
+ const dom::GPUBindGroupDescriptor& aDesc);
+ RawId DeviceCreateComputePipeline(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc);
+ RefPtr<PipelinePromise> DeviceCreateComputePipelineAsync(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc);
+ RawId DeviceCreateRenderPipeline(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc);
+ RefPtr<PipelinePromise> DeviceCreateRenderPipelineAsync(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc);
+ already_AddRefed<ShaderModule> DeviceCreateShaderModule(
+ Device* aDevice, const dom::GPUShaderModuleDescriptor& aDesc,
+ RefPtr<dom::Promise> aPromise);
+
+ void DeviceCreateSwapChain(RawId aSelfId, const RGBDescriptor& aRgbDesc,
+ size_t maxBufferCount,
+ const layers::RemoteTextureOwnerId& aOwnerId);
+ void SwapChainPresent(RawId aTextureId,
+ const RemoteTextureId& aRemoteTextureId,
+ const RemoteTextureOwnerId& aOwnerId);
+
+ void RegisterDevice(Device* const aDevice);
+ void UnregisterDevice(RawId aId);
+ void FreeUnregisteredInParentDevice(RawId aId);
+
+ static void ConvertTextureFormatRef(const dom::GPUTextureFormat& aInput,
+ ffi::WGPUTextureFormat& aOutput);
+
+ private:
+ virtual ~WebGPUChild();
+
+ void JsWarning(nsIGlobalObject* aGlobal, const nsACString& aMessage);
+
+ RawId DeviceCreateComputePipelineImpl(
+ PipelineCreationContext* const aContext,
+ const dom::GPUComputePipelineDescriptor& aDesc,
+ ipc::ByteBuf* const aByteBuf);
+ RawId DeviceCreateRenderPipelineImpl(
+ PipelineCreationContext* const aContext,
+ const dom::GPURenderPipelineDescriptor& aDesc,
+ ipc::ByteBuf* const aByteBuf);
+
+ UniquePtr<ffi::WGPUClient> const mClient;
+ std::unordered_map<RawId, WeakPtr<Device>> mDeviceMap;
+
+ public:
+ ipc::IPCResult RecvDeviceUncapturedError(RawId aDeviceId,
+ const nsACString& aMessage);
+ ipc::IPCResult RecvDropAction(const ipc::ByteBuf& aByteBuf);
+ void ActorDestroy(ActorDestroyReason) override;
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // WEBGPU_CHILD_H_
diff --git a/dom/webgpu/ipc/WebGPUParent.cpp b/dom/webgpu/ipc/WebGPUParent.cpp
new file mode 100644
index 0000000000..f3f9515319
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUParent.cpp
@@ -0,0 +1,1116 @@
+/* -*- Mode: C++; tab-width: 20; 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 "WebGPUParent.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/layers/WebRenderImageHost.h"
+#include "mozilla/layers/WebRenderTextureHost.h"
+
+namespace mozilla::webgpu {
+
+const uint64_t POLL_TIME_MS = 100;
+
+static mozilla::LazyLogModule sLogger("WebGPU");
+
+// A fixed-capacity buffer for receiving textual error messages from
+// `wgpu_bindings`.
+//
+// The `ToFFI` method returns an `ffi::WGPUErrorBuffer` pointing to our
+// buffer, for you to pass to fallible FFI-visible `wgpu_bindings`
+// functions. These indicate failure by storing an error message in the
+// buffer, which you can retrieve by calling `GetError`.
+//
+// If you call `ToFFI` on this type, you must also call `GetError` to check for
+// an error. Otherwise, the destructor asserts.
+//
+// TODO: refactor this to avoid stack-allocating the buffer all the time.
+class ErrorBuffer {
+ // if the message doesn't fit, it will be truncated
+ static constexpr unsigned BUFFER_SIZE = 512;
+ char mUtf8[BUFFER_SIZE] = {};
+ bool mGuard = false;
+
+ public:
+ ErrorBuffer() { mUtf8[0] = 0; }
+ ErrorBuffer(const ErrorBuffer&) = delete;
+ ~ErrorBuffer() { MOZ_ASSERT(!mGuard); }
+
+ ffi::WGPUErrorBuffer ToFFI() {
+ mGuard = true;
+ ffi::WGPUErrorBuffer errorBuf = {mUtf8, BUFFER_SIZE};
+ return errorBuf;
+ }
+
+ // If an error message was stored in this buffer, return Some(m)
+ // where m is the message as a UTF-8 nsCString. Otherwise, return Nothing.
+ //
+ // Mark this ErrorBuffer as having been handled, so its destructor
+ // won't assert.
+ Maybe<nsCString> GetError() {
+ mGuard = false;
+ if (!mUtf8[0]) {
+ return Nothing();
+ }
+ return Some(nsCString(mUtf8));
+ }
+};
+
+class PresentationData {
+ NS_INLINE_DECL_REFCOUNTING(PresentationData);
+
+ public:
+ RawId mDeviceId = 0;
+ RawId mQueueId = 0;
+ layers::RGBDescriptor mDesc;
+ uint32_t mSourcePitch = 0;
+ int32_t mNextFrameID = 1;
+ std::vector<RawId> mUnassignedBufferIds;
+ std::vector<RawId> mAvailableBufferIds;
+ std::vector<RawId> mQueuedBufferIds;
+ Mutex mBuffersLock MOZ_UNANNOTATED;
+
+ PresentationData(RawId aDeviceId, RawId aQueueId,
+ const layers::RGBDescriptor& aDesc, uint32_t aSourcePitch,
+ const nsTArray<RawId>& aBufferIds)
+ : mDeviceId(aDeviceId),
+ mQueueId(aQueueId),
+ mDesc(aDesc),
+ mSourcePitch(aSourcePitch),
+ mBuffersLock("WebGPU presentation buffers") {
+ MOZ_COUNT_CTOR(PresentationData);
+
+ for (const RawId id : aBufferIds) {
+ mUnassignedBufferIds.push_back(id);
+ }
+ }
+
+ private:
+ ~PresentationData() { MOZ_COUNT_DTOR(PresentationData); }
+};
+
+static void FreeAdapter(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_adapter_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeAdapter");
+ }
+}
+static void FreeDevice(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_device_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeDevice");
+ }
+}
+static void FreeShaderModule(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_shader_module_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeShaderModule");
+ }
+}
+static void FreePipelineLayout(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_pipeline_layout_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreePipelineLayout");
+ }
+}
+static void FreeBindGroupLayout(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_bind_group_layout_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeBindGroupLayout");
+ }
+}
+static void FreeBindGroup(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_bind_group_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeBindGroup");
+ }
+}
+static void FreeCommandBuffer(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_command_buffer_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeCommandBuffer");
+ }
+}
+static void FreeRenderBundle(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_render_bundle_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeRenderBundle");
+ }
+}
+static void FreeRenderPipeline(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_render_pipeline_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeRenderPipeline");
+ }
+}
+static void FreeComputePipeline(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_compute_pipeline_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeComputePipeline");
+ }
+}
+static void FreeBuffer(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_buffer_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeBuffer");
+ }
+}
+static void FreeTexture(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_texture_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeTexture");
+ }
+}
+static void FreeTextureView(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_texture_view_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeTextureView");
+ }
+}
+static void FreeSampler(RawId id, void* param) {
+ ipc::ByteBuf byteBuf;
+ wgpu_server_sampler_free(id, ToFFI(&byteBuf));
+ if (!static_cast<WebGPUParent*>(param)->SendDropAction(std::move(byteBuf))) {
+ NS_ERROR("Unable FreeSampler");
+ }
+}
+static void FreeSurface(RawId id, void* param) {
+ Unused << id;
+ Unused << param;
+}
+
+static ffi::WGPUIdentityRecyclerFactory MakeFactory(void* param) {
+ ffi::WGPUIdentityRecyclerFactory factory;
+ PodZero(&factory);
+ factory.param = param;
+ factory.free_adapter = FreeAdapter;
+ factory.free_device = FreeDevice;
+ factory.free_pipeline_layout = FreePipelineLayout;
+ factory.free_shader_module = FreeShaderModule;
+ factory.free_bind_group_layout = FreeBindGroupLayout;
+ factory.free_bind_group = FreeBindGroup;
+ factory.free_command_buffer = FreeCommandBuffer;
+ factory.free_render_bundle = FreeRenderBundle;
+ factory.free_render_pipeline = FreeRenderPipeline;
+ factory.free_compute_pipeline = FreeComputePipeline;
+ factory.free_buffer = FreeBuffer;
+ factory.free_texture = FreeTexture;
+ factory.free_texture_view = FreeTextureView;
+ factory.free_sampler = FreeSampler;
+ factory.free_surface = FreeSurface;
+ return factory;
+}
+
+WebGPUParent::WebGPUParent()
+ : mContext(ffi::wgpu_server_new(MakeFactory(this))) {
+ mTimer.Start(base::TimeDelta::FromMilliseconds(POLL_TIME_MS), this,
+ &WebGPUParent::MaintainDevices);
+}
+
+WebGPUParent::~WebGPUParent() = default;
+
+void WebGPUParent::MaintainDevices() {
+ ffi::wgpu_server_poll_all_devices(mContext.get(), false);
+}
+
+bool WebGPUParent::ForwardError(RawId aDeviceId, ErrorBuffer& aError) {
+ // don't do anything if the error is empty
+ auto cString = aError.GetError();
+ if (!cString) {
+ return false;
+ }
+
+ ReportError(aDeviceId, cString.value());
+
+ return true;
+}
+
+// Generate an error on the Device timeline of aDeviceId.
+// aMessage is interpreted as UTF-8.
+void WebGPUParent::ReportError(RawId aDeviceId, const nsCString& aMessage) {
+ // find the appropriate error scope
+ const auto& lookup = mErrorScopeMap.find(aDeviceId);
+ if (lookup != mErrorScopeMap.end() && !lookup->second.mStack.IsEmpty()) {
+ auto& last = lookup->second.mStack.LastElement();
+ if (last.isNothing()) {
+ last.emplace(ScopedError{false, aMessage});
+ }
+ } else {
+ // fall back to the uncaptured error handler
+ if (!SendDeviceUncapturedError(aDeviceId, aMessage)) {
+ NS_ERROR("Unable to SendError");
+ }
+ }
+}
+
+ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions,
+ const nsTArray<RawId>& aTargetIds,
+ InstanceRequestAdapterResolver&& resolver) {
+ ffi::WGPURequestAdapterOptions options = {};
+ if (aOptions.mPowerPreference.WasPassed()) {
+ options.power_preference = static_cast<ffi::WGPUPowerPreference>(
+ aOptions.mPowerPreference.Value());
+ }
+ options.force_fallback_adapter = aOptions.mForceFallbackAdapter;
+ // TODO: make available backends configurable by prefs
+
+ ErrorBuffer error;
+ int8_t index = ffi::wgpu_server_instance_request_adapter(
+ mContext.get(), &options, aTargetIds.Elements(), aTargetIds.Length(),
+ error.ToFFI());
+
+ ByteBuf infoByteBuf;
+ // Rust side expects an `Option`, so 0 maps to `None`.
+ uint64_t adapterId = 0;
+ if (index >= 0) {
+ adapterId = aTargetIds[index];
+ }
+ ffi::wgpu_server_adapter_pack_info(mContext.get(), adapterId,
+ ToFFI(&infoByteBuf));
+ resolver(std::move(infoByteBuf));
+ ForwardError(0, error);
+
+ // free the unused IDs
+ ipc::ByteBuf dropByteBuf;
+ for (size_t i = 0; i < aTargetIds.Length(); ++i) {
+ if (static_cast<int8_t>(i) != index) {
+ wgpu_server_adapter_free(aTargetIds[i], ToFFI(&dropByteBuf));
+ }
+ }
+ if (dropByteBuf.mData && !SendDropAction(std::move(dropByteBuf))) {
+ NS_ERROR("Unable to free free unused adapter IDs");
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvAdapterRequestDevice(
+ RawId aAdapterId, const ipc::ByteBuf& aByteBuf, RawId aDeviceId,
+ AdapterRequestDeviceResolver&& resolver) {
+ ErrorBuffer error;
+ ffi::wgpu_server_adapter_request_device(
+ mContext.get(), aAdapterId, ToFFI(&aByteBuf), aDeviceId, error.ToFFI());
+ if (ForwardError(0, error)) {
+ resolver(false);
+ } else {
+ mErrorScopeMap.insert({aAdapterId, ErrorScopeStack()});
+ resolver(true);
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvAdapterDestroy(RawId aAdapterId) {
+ ffi::wgpu_server_adapter_drop(mContext.get(), aAdapterId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvDeviceDestroy(RawId aDeviceId) {
+ ffi::wgpu_server_device_drop(mContext.get(), aDeviceId);
+ mErrorScopeMap.erase(aDeviceId);
+ return IPC_OK();
+}
+
+WebGPUParent::BufferMapData* WebGPUParent::GetBufferMapData(RawId aBufferId) {
+ const auto iter = mSharedMemoryMap.find(aBufferId);
+ if (iter == mSharedMemoryMap.end()) {
+ return nullptr;
+ }
+
+ return &iter->second;
+}
+
+ipc::IPCResult WebGPUParent::RecvCreateBuffer(
+ RawId aDeviceId, RawId aBufferId, dom::GPUBufferDescriptor&& aDesc,
+ ipc::UnsafeSharedMemoryHandle&& aShmem) {
+ webgpu::StringHelper label(aDesc.mLabel);
+
+ auto shmem =
+ ipc::WritableSharedMemoryMapping::Open(std::move(aShmem)).value();
+
+ bool hasMapFlags = aDesc.mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
+ dom::GPUBufferUsage_Binding::MAP_READ);
+ if (hasMapFlags || aDesc.mMappedAtCreation) {
+ uint64_t offset = 0;
+ uint64_t size = 0;
+ if (aDesc.mMappedAtCreation) {
+ size = aDesc.mSize;
+ MOZ_RELEASE_ASSERT(shmem.Size() >= aDesc.mSize);
+ }
+
+ BufferMapData data = {std::move(shmem), hasMapFlags, offset, size};
+ mSharedMemoryMap.insert({aBufferId, std::move(data)});
+ }
+
+ ErrorBuffer error;
+ ffi::wgpu_server_device_create_buffer(mContext.get(), aDeviceId, aBufferId,
+ label.Get(), aDesc.mSize, aDesc.mUsage,
+ aDesc.mMappedAtCreation, error.ToFFI());
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+struct MapRequest {
+ RefPtr<WebGPUParent> mParent;
+ ffi::WGPUGlobal* mContext;
+ ffi::WGPUBufferId mBufferId;
+ ffi::WGPUHostMap mHostMap;
+ uint64_t mOffset;
+ uint64_t mSize;
+ WebGPUParent::BufferMapResolver mResolver;
+};
+
+nsCString MapStatusString(ffi::WGPUBufferMapAsyncStatus status) {
+ switch (status) {
+ case ffi::WGPUBufferMapAsyncStatus_Success:
+ return nsCString("Success");
+ case ffi::WGPUBufferMapAsyncStatus_AlreadyMapped:
+ return nsCString("Already mapped");
+ case ffi::WGPUBufferMapAsyncStatus_MapAlreadyPending:
+ return nsCString("Map is already pending");
+ case ffi::WGPUBufferMapAsyncStatus_Aborted:
+ return nsCString("Map aborted");
+ case ffi::WGPUBufferMapAsyncStatus_ContextLost:
+ return nsCString("Context lost");
+ case ffi::WGPUBufferMapAsyncStatus_Invalid:
+ return nsCString("Invalid buffer");
+ case ffi::WGPUBufferMapAsyncStatus_InvalidRange:
+ return nsCString("Invalid range");
+ case ffi::WGPUBufferMapAsyncStatus_InvalidAlignment:
+ return nsCString("Invalid alignment");
+ case ffi::WGPUBufferMapAsyncStatus_InvalidUsageFlags:
+ return nsCString("Invalid usage flags");
+ case ffi::WGPUBufferMapAsyncStatus_Error:
+ return nsCString("Map failed");
+ case ffi::WGPUBufferMapAsyncStatus_Sentinel: // For -Wswitch
+ break;
+ }
+
+ MOZ_CRASH("Bad ffi::WGPUBufferMapAsyncStatus");
+}
+
+static void MapCallback(ffi::WGPUBufferMapAsyncStatus status,
+ uint8_t* userdata) {
+ auto* req = reinterpret_cast<MapRequest*>(userdata);
+
+ if (!req->mParent->CanSend()) {
+ delete req;
+ return;
+ }
+
+ BufferMapResult result;
+
+ auto bufferId = req->mBufferId;
+ auto* mapData = req->mParent->GetBufferMapData(bufferId);
+ MOZ_RELEASE_ASSERT(mapData);
+
+ if (status != ffi::WGPUBufferMapAsyncStatus_Success) {
+ result = BufferMapError(MapStatusString(status));
+ } else {
+ auto size = req->mSize;
+ auto offset = req->mOffset;
+
+ if (req->mHostMap == ffi::WGPUHostMap_Read && size > 0) {
+ const auto src = ffi::wgpu_server_buffer_get_mapped_range(
+ req->mContext, req->mBufferId, offset, size);
+
+ MOZ_RELEASE_ASSERT(mapData->mShmem.Size() >= offset + size);
+ if (src.ptr != nullptr && src.length >= size) {
+ auto dst = mapData->mShmem.Bytes().Subspan(offset, size);
+ memcpy(dst.data(), src.ptr, size);
+ }
+ }
+
+ result =
+ BufferMapSuccess(offset, size, req->mHostMap == ffi::WGPUHostMap_Write);
+
+ mapData->mMappedOffset = offset;
+ mapData->mMappedSize = size;
+ }
+
+ req->mResolver(std::move(result));
+ delete req;
+}
+
+ipc::IPCResult WebGPUParent::RecvBufferMap(RawId aBufferId, uint32_t aMode,
+ uint64_t aOffset, uint64_t aSize,
+ BufferMapResolver&& aResolver) {
+ MOZ_LOG(sLogger, LogLevel::Info,
+ ("RecvBufferMap %" PRIu64 " offset=%" PRIu64 " size=%" PRIu64 "\n",
+ aBufferId, aOffset, aSize));
+
+ ffi::WGPUHostMap mode;
+ switch (aMode) {
+ case dom::GPUMapMode_Binding::READ:
+ mode = ffi::WGPUHostMap_Read;
+ break;
+ case dom::GPUMapMode_Binding::WRITE:
+ mode = ffi::WGPUHostMap_Write;
+ break;
+ default: {
+ nsCString errorString(
+ "GPUBuffer.mapAsync 'mode' argument must be either GPUMapMode.READ "
+ "or GPUMapMode.WRITE");
+ aResolver(BufferMapError(errorString));
+ return IPC_OK();
+ }
+ }
+
+ auto* mapData = GetBufferMapData(aBufferId);
+
+ if (!mapData) {
+ nsCString errorString("Buffer is not mappable");
+ aResolver(BufferMapError(errorString));
+ return IPC_OK();
+ }
+
+ auto* request =
+ new MapRequest{this, mContext.get(), aBufferId, mode,
+ aOffset, aSize, std::move(aResolver)};
+
+ ffi::WGPUBufferMapCallbackC callback = {&MapCallback,
+ reinterpret_cast<uint8_t*>(request)};
+ ffi::wgpu_server_buffer_map(mContext.get(), aBufferId, aOffset, aSize, mode,
+ callback);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvBufferUnmap(RawId aDeviceId, RawId aBufferId,
+ bool aFlush) {
+ MOZ_LOG(sLogger, LogLevel::Info,
+ ("RecvBufferUnmap %" PRIu64 " flush=%d\n", aBufferId, aFlush));
+
+ auto* mapData = GetBufferMapData(aBufferId);
+
+ if (mapData && aFlush) {
+ uint64_t offset = mapData->mMappedOffset;
+ uint64_t size = mapData->mMappedSize;
+
+ const auto mapped = ffi::wgpu_server_buffer_get_mapped_range(
+ mContext.get(), aBufferId, offset, size);
+
+ if (mapped.ptr != nullptr && mapped.length >= size) {
+ auto shmSize = mapData->mShmem.Size();
+ MOZ_RELEASE_ASSERT(offset <= shmSize);
+ MOZ_RELEASE_ASSERT(size <= shmSize - offset);
+
+ auto src = mapData->mShmem.Bytes().Subspan(offset, size);
+ memcpy(mapped.ptr, src.data(), size);
+ }
+
+ mapData->mMappedOffset = 0;
+ mapData->mMappedSize = 0;
+ }
+
+ ErrorBuffer error;
+ ffi::wgpu_server_buffer_unmap(mContext.get(), aBufferId, error.ToFFI());
+ ForwardError(aDeviceId, error);
+
+ if (mapData && !mapData->mHasMapFlags) {
+ // We get here if the buffer was mapped at creation without map flags.
+ // We don't need the shared memory anymore.
+ DeallocBufferShmem(aBufferId);
+ }
+
+ return IPC_OK();
+}
+
+void WebGPUParent::DeallocBufferShmem(RawId aBufferId) {
+ const auto iter = mSharedMemoryMap.find(aBufferId);
+ if (iter != mSharedMemoryMap.end()) {
+ mSharedMemoryMap.erase(iter);
+ }
+}
+
+ipc::IPCResult WebGPUParent::RecvBufferDrop(RawId aBufferId) {
+ ffi::wgpu_server_buffer_drop(mContext.get(), aBufferId);
+ MOZ_LOG(sLogger, LogLevel::Info, ("RecvBufferDrop %" PRIu64 "\n", aBufferId));
+
+ DeallocBufferShmem(aBufferId);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvBufferDestroy(RawId aBufferId) {
+ ffi::wgpu_server_buffer_destroy(mContext.get(), aBufferId);
+ MOZ_LOG(sLogger, LogLevel::Info,
+ ("RecvBufferDestroy %" PRIu64 "\n", aBufferId));
+
+ DeallocBufferShmem(aBufferId);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvTextureDestroy(RawId aTextureId) {
+ ffi::wgpu_server_texture_drop(mContext.get(), aTextureId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvTextureViewDestroy(RawId aTextureViewId) {
+ ffi::wgpu_server_texture_view_drop(mContext.get(), aTextureViewId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvSamplerDestroy(RawId aSamplerId) {
+ ffi::wgpu_server_sampler_drop(mContext.get(), aSamplerId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvCommandEncoderFinish(
+ RawId aEncoderId, RawId aDeviceId,
+ const dom::GPUCommandBufferDescriptor& aDesc) {
+ Unused << aDesc;
+ ffi::WGPUCommandBufferDescriptor desc = {};
+
+ webgpu::StringHelper label(aDesc.mLabel);
+ desc.label = label.Get();
+
+ ErrorBuffer error;
+ ffi::wgpu_server_encoder_finish(mContext.get(), aEncoderId, &desc,
+ error.ToFFI());
+
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvCommandEncoderDestroy(RawId aEncoderId) {
+ ffi::wgpu_server_encoder_drop(mContext.get(), aEncoderId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvCommandBufferDestroy(RawId aCommandBufferId) {
+ ffi::wgpu_server_command_buffer_drop(mContext.get(), aCommandBufferId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvRenderBundleDestroy(RawId aBundleId) {
+ ffi::wgpu_server_render_bundle_drop(mContext.get(), aBundleId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvQueueSubmit(
+ RawId aQueueId, RawId aDeviceId, const nsTArray<RawId>& aCommandBuffers) {
+ ErrorBuffer error;
+ ffi::wgpu_server_queue_submit(mContext.get(), aQueueId,
+ aCommandBuffers.Elements(),
+ aCommandBuffers.Length(), error.ToFFI());
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvQueueWriteAction(
+ RawId aQueueId, RawId aDeviceId, const ipc::ByteBuf& aByteBuf,
+ ipc::UnsafeSharedMemoryHandle&& aShmem) {
+ auto mapping =
+ ipc::WritableSharedMemoryMapping::Open(std::move(aShmem)).value();
+
+ ErrorBuffer error;
+ ffi::wgpu_server_queue_write_action(mContext.get(), aQueueId,
+ ToFFI(&aByteBuf), mapping.Bytes().data(),
+ mapping.Size(), error.ToFFI());
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvBindGroupLayoutDestroy(RawId aBindGroupId) {
+ ffi::wgpu_server_bind_group_layout_drop(mContext.get(), aBindGroupId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvPipelineLayoutDestroy(RawId aLayoutId) {
+ ffi::wgpu_server_pipeline_layout_drop(mContext.get(), aLayoutId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvBindGroupDestroy(RawId aBindGroupId) {
+ ffi::wgpu_server_bind_group_drop(mContext.get(), aBindGroupId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvShaderModuleDestroy(RawId aModuleId) {
+ ffi::wgpu_server_shader_module_drop(mContext.get(), aModuleId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvComputePipelineDestroy(RawId aPipelineId) {
+ ffi::wgpu_server_compute_pipeline_drop(mContext.get(), aPipelineId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvRenderPipelineDestroy(RawId aPipelineId) {
+ ffi::wgpu_server_render_pipeline_drop(mContext.get(), aPipelineId);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvImplicitLayoutDestroy(
+ RawId aImplicitPlId, const nsTArray<RawId>& aImplicitBglIds) {
+ ffi::wgpu_server_pipeline_layout_drop(mContext.get(), aImplicitPlId);
+ for (const auto& id : aImplicitBglIds) {
+ ffi::wgpu_server_bind_group_layout_drop(mContext.get(), id);
+ }
+ return IPC_OK();
+}
+
+// TODO: proper destruction
+
+ipc::IPCResult WebGPUParent::RecvDeviceCreateSwapChain(
+ RawId aDeviceId, RawId aQueueId, const RGBDescriptor& aDesc,
+ const nsTArray<RawId>& aBufferIds,
+ const layers::RemoteTextureOwnerId& aOwnerId) {
+ switch (aDesc.format()) {
+ case gfx::SurfaceFormat::R8G8B8A8:
+ case gfx::SurfaceFormat::B8G8R8A8:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid surface format!");
+ return IPC_OK();
+ }
+
+ constexpr uint32_t kBufferAlignmentMask = 0xff;
+ const auto bufferStrideWithMask = CheckedInt<uint32_t>(aDesc.size().width) *
+ gfx::BytesPerPixel(aDesc.format()) +
+ kBufferAlignmentMask;
+ if (!bufferStrideWithMask.isValid()) {
+ MOZ_ASSERT_UNREACHABLE("Invalid width / buffer stride!");
+ return IPC_OK();
+ }
+
+ const uint32_t bufferStride =
+ bufferStrideWithMask.value() & ~kBufferAlignmentMask;
+
+ const auto rows = CheckedInt<uint32_t>(aDesc.size().height);
+ if (!rows.isValid()) {
+ MOZ_ASSERT_UNREACHABLE("Invalid height!");
+ return IPC_OK();
+ }
+
+ if (!mRemoteTextureOwner) {
+ mRemoteTextureOwner =
+ MakeRefPtr<layers::RemoteTextureOwnerClient>(OtherPid());
+ }
+ // RemoteTextureMap::GetRemoteTextureForDisplayList() works synchronously.
+ mRemoteTextureOwner->RegisterTextureOwner(aOwnerId, /* aIsSyncMode */ true);
+
+ auto data = MakeRefPtr<PresentationData>(aDeviceId, aQueueId, aDesc,
+ bufferStride, aBufferIds);
+ if (!mCanvasMap.emplace(aOwnerId, data).second) {
+ NS_ERROR("External image is already registered as WebGPU canvas!");
+ }
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvDeviceCreateShaderModule(
+ RawId aDeviceId, RawId aModuleId, const nsString& aLabel,
+ const nsCString& aCode, DeviceCreateShaderModuleResolver&& aOutMessage) {
+ // TODO: this should probably be an optional label in the IPC message.
+ const nsACString* label = nullptr;
+ NS_ConvertUTF16toUTF8 utf8Label(aLabel);
+ if (!utf8Label.IsEmpty()) {
+ label = &utf8Label;
+ }
+
+ ffi::WGPUShaderModuleCompilationMessage message;
+
+ bool ok = ffi::wgpu_server_device_create_shader_module(
+ mContext.get(), aDeviceId, aModuleId, label, &aCode, &message);
+
+ nsTArray<WebGPUCompilationMessage> messages;
+
+ if (!ok) {
+ 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(msg);
+ }
+
+ aOutMessage(messages);
+
+ return IPC_OK();
+}
+
+struct PresentRequest {
+ PresentRequest(const ffi::WGPUGlobal* aContext,
+ RefPtr<PresentationData>& aData,
+ RefPtr<layers::RemoteTextureOwnerClient>& aRemoteTextureOwner,
+ const layers::RemoteTextureId aTextureId,
+ const layers::RemoteTextureOwnerId aOwnerId)
+ : mContext(aContext),
+ mData(aData),
+ mRemoteTextureOwner(aRemoteTextureOwner),
+ mTextureId(aTextureId),
+ mOwnerId(aOwnerId) {}
+
+ const ffi::WGPUGlobal* mContext;
+ RefPtr<PresentationData> mData;
+ RefPtr<layers::RemoteTextureOwnerClient> mRemoteTextureOwner;
+ const layers::RemoteTextureId mTextureId;
+ const layers::RemoteTextureOwnerId mOwnerId;
+};
+
+static void PresentCallback(ffi::WGPUBufferMapAsyncStatus status,
+ uint8_t* userdata) {
+ UniquePtr<PresentRequest> req(reinterpret_cast<PresentRequest*>(userdata));
+
+ PresentationData* data = req->mData.get();
+ // get the buffer ID
+ RawId bufferId;
+ {
+ MutexAutoLock lock(data->mBuffersLock);
+ bufferId = data->mQueuedBufferIds.back();
+ data->mQueuedBufferIds.pop_back();
+ data->mAvailableBufferIds.push_back(bufferId);
+ }
+ MOZ_LOG(
+ sLogger, LogLevel::Info,
+ ("PresentCallback for buffer %" PRIu64 " status=%d\n", bufferId, status));
+ // copy the data
+ if (status == ffi::WGPUBufferMapAsyncStatus_Success) {
+ const auto bufferSize = data->mDesc.size().height * data->mSourcePitch;
+ const auto mapped = ffi::wgpu_server_buffer_get_mapped_range(
+ req->mContext, bufferId, 0, bufferSize);
+ MOZ_ASSERT(mapped.length >= bufferSize);
+ auto textureData =
+ req->mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
+ req->mOwnerId, data->mDesc.size(), data->mDesc.format());
+ if (!textureData) {
+ gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
+ return;
+ }
+ layers::MappedTextureData mappedData;
+ if (textureData && textureData->BorrowMappedData(mappedData)) {
+ uint8_t* src = mapped.ptr;
+ uint8_t* dst = mappedData.data;
+ for (auto row = 0; row < data->mDesc.size().height; ++row) {
+ memcpy(dst, src, mappedData.stride);
+ dst += mappedData.stride;
+ src += data->mSourcePitch;
+ }
+ req->mRemoteTextureOwner->PushTexture(req->mTextureId, req->mOwnerId,
+ std::move(textureData),
+ /* aSharedSurface */ nullptr);
+ } else {
+ NS_WARNING("WebGPU present skipped: the swapchain is resized!");
+ }
+ ErrorBuffer error;
+ wgpu_server_buffer_unmap(req->mContext, bufferId, error.ToFFI());
+ if (auto errorString = error.GetError()) {
+ MOZ_LOG(
+ sLogger, LogLevel::Info,
+ ("WebGPU present: buffer unmap failed: %s\n", errorString->get()));
+ }
+ } else {
+ // TODO: better handle errors
+ NS_WARNING("WebGPU frame mapping failed!");
+ }
+}
+
+ipc::IPCResult WebGPUParent::GetFrontBufferSnapshot(
+ IProtocol* aProtocol, const layers::RemoteTextureOwnerId& aOwnerId,
+ Maybe<Shmem>& aShmem, gfx::IntSize& aSize) {
+ const auto& lookup = mCanvasMap.find(aOwnerId);
+ if (lookup == mCanvasMap.end() || !mRemoteTextureOwner) {
+ return IPC_OK();
+ }
+
+ RefPtr<PresentationData> data = lookup->second.get();
+ aSize = data->mDesc.size();
+ uint32_t stride = layers::ImageDataSerializer::ComputeRGBStride(
+ data->mDesc.format(), aSize.width);
+ uint32_t len = data->mDesc.size().height * stride;
+ Shmem shmem;
+ if (!AllocShmem(len, &shmem)) {
+ return IPC_OK();
+ }
+
+ mRemoteTextureOwner->GetLatestBufferSnapshot(aOwnerId, shmem, aSize);
+ aShmem.emplace(std::move(shmem));
+
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
+ RawId aTextureId, RawId aCommandEncoderId,
+ const layers::RemoteTextureId& aRemoteTextureId,
+ const layers::RemoteTextureOwnerId& aOwnerId) {
+ // step 0: get the data associated with the swapchain
+ const auto& lookup = mCanvasMap.find(aOwnerId);
+ if (lookup == mCanvasMap.end() || !mRemoteTextureOwner ||
+ !mRemoteTextureOwner->IsRegistered(aOwnerId)) {
+ NS_WARNING("WebGPU presenting on a destroyed swap chain!");
+ return IPC_OK();
+ }
+
+ RefPtr<PresentationData> data = lookup->second.get();
+ RawId bufferId = 0;
+ const auto& size = data->mDesc.size();
+ const auto bufferSize = data->mDesc.size().height * data->mSourcePitch;
+
+ // step 1: find an available staging buffer, or create one
+ {
+ MutexAutoLock lock(data->mBuffersLock);
+ if (!data->mAvailableBufferIds.empty()) {
+ bufferId = data->mAvailableBufferIds.back();
+ data->mAvailableBufferIds.pop_back();
+ } else if (!data->mUnassignedBufferIds.empty()) {
+ bufferId = data->mUnassignedBufferIds.back();
+ data->mUnassignedBufferIds.pop_back();
+
+ ffi::WGPUBufferUsages usage =
+ WGPUBufferUsages_COPY_DST | WGPUBufferUsages_MAP_READ;
+
+ ErrorBuffer error;
+ ffi::wgpu_server_device_create_buffer(mContext.get(), data->mDeviceId,
+ bufferId, nullptr, bufferSize,
+ usage, false, error.ToFFI());
+ if (ForwardError(data->mDeviceId, error)) {
+ return IPC_OK();
+ }
+ } else {
+ bufferId = 0;
+ }
+
+ if (bufferId) {
+ data->mQueuedBufferIds.insert(data->mQueuedBufferIds.begin(), bufferId);
+ }
+ }
+
+ MOZ_LOG(sLogger, LogLevel::Info,
+ ("RecvSwapChainPresent with buffer %" PRIu64 "\n", bufferId));
+ if (!bufferId) {
+ // TODO: add a warning - no buffer are available!
+ return IPC_OK();
+ }
+
+ // step 3: submit a copy command for the frame
+ ffi::WGPUCommandEncoderDescriptor encoderDesc = {};
+ {
+ ErrorBuffer error;
+ ffi::wgpu_server_device_create_encoder(mContext.get(), data->mDeviceId,
+ &encoderDesc, aCommandEncoderId,
+ error.ToFFI());
+ if (ForwardError(data->mDeviceId, error)) {
+ return IPC_OK();
+ }
+ }
+
+ const ffi::WGPUImageCopyTexture texView = {
+ aTextureId,
+ };
+ const ffi::WGPUImageDataLayout bufLayout = {
+ 0,
+ data->mSourcePitch,
+ 0,
+ };
+ const ffi::WGPUImageCopyBuffer bufView = {
+ bufferId,
+ bufLayout,
+ };
+ const ffi::WGPUExtent3d extent = {
+ static_cast<uint32_t>(size.width),
+ static_cast<uint32_t>(size.height),
+ 1,
+ };
+ ffi::wgpu_server_encoder_copy_texture_to_buffer(
+ mContext.get(), aCommandEncoderId, &texView, &bufView, &extent);
+ ffi::WGPUCommandBufferDescriptor commandDesc = {};
+ {
+ ErrorBuffer error;
+ ffi::wgpu_server_encoder_finish(mContext.get(), aCommandEncoderId,
+ &commandDesc, error.ToFFI());
+ if (ForwardError(data->mDeviceId, error)) {
+ return IPC_OK();
+ }
+ }
+
+ {
+ ErrorBuffer error;
+ ffi::wgpu_server_queue_submit(mContext.get(), data->mQueueId,
+ &aCommandEncoderId, 1, error.ToFFI());
+ if (ForwardError(data->mDeviceId, error)) {
+ return IPC_OK();
+ }
+ }
+
+ // step 4: request the pixels to be copied into the external texture
+ // TODO: this isn't strictly necessary. When WR wants to Lock() the external
+ // texture,
+ // we can just give it the contents of the last mapped buffer instead of the
+ // copy.
+ auto presentRequest = MakeUnique<PresentRequest>(
+ mContext.get(), data, mRemoteTextureOwner, aRemoteTextureId, aOwnerId);
+
+ ffi::WGPUBufferMapCallbackC callback = {
+ &PresentCallback, reinterpret_cast<uint8_t*>(presentRequest.release())};
+ ffi::wgpu_server_buffer_map(mContext.get(), bufferId, 0, bufferSize,
+ ffi::WGPUHostMap_Read, callback);
+
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvSwapChainDestroy(
+ const layers::RemoteTextureOwnerId& aOwnerId) {
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->UnregisterTextureOwner(aOwnerId);
+ }
+ const auto& lookup = mCanvasMap.find(aOwnerId);
+ MOZ_ASSERT(lookup != mCanvasMap.end());
+ if (lookup == mCanvasMap.end()) {
+ NS_WARNING("WebGPU presenting on a destroyed swap chain!");
+ return IPC_OK();
+ }
+
+ RefPtr<PresentationData> data = lookup->second.get();
+ mCanvasMap.erase(lookup);
+
+ MutexAutoLock lock(data->mBuffersLock);
+ ipc::ByteBuf dropByteBuf;
+ for (const auto bid : data->mUnassignedBufferIds) {
+ wgpu_server_buffer_free(bid, ToFFI(&dropByteBuf));
+ }
+ if (dropByteBuf.mData && !SendDropAction(std::move(dropByteBuf))) {
+ NS_WARNING("Unable to free an ID for non-assigned buffer");
+ }
+ for (const auto bid : data->mAvailableBufferIds) {
+ ffi::wgpu_server_buffer_drop(mContext.get(), bid);
+ }
+ for (const auto bid : data->mQueuedBufferIds) {
+ ffi::wgpu_server_buffer_drop(mContext.get(), bid);
+ }
+ return IPC_OK();
+}
+
+void WebGPUParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mTimer.Stop();
+ mCanvasMap.clear();
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->UnregisterAllTextureOwners();
+ mRemoteTextureOwner = nullptr;
+ }
+ ffi::wgpu_server_poll_all_devices(mContext.get(), true);
+ mContext = nullptr;
+}
+
+ipc::IPCResult WebGPUParent::RecvDeviceAction(RawId aDeviceId,
+ const ipc::ByteBuf& aByteBuf) {
+ ErrorBuffer error;
+ ffi::wgpu_server_device_action(mContext.get(), aDeviceId, ToFFI(&aByteBuf),
+ error.ToFFI());
+
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvDeviceActionWithAck(
+ RawId aDeviceId, const ipc::ByteBuf& aByteBuf,
+ DeviceActionWithAckResolver&& aResolver) {
+ ErrorBuffer error;
+ ffi::wgpu_server_device_action(mContext.get(), aDeviceId, ToFFI(&aByteBuf),
+ error.ToFFI());
+
+ ForwardError(aDeviceId, error);
+ aResolver(true);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvTextureAction(RawId aTextureId,
+ RawId aDeviceId,
+ const ipc::ByteBuf& aByteBuf) {
+ ErrorBuffer error;
+ ffi::wgpu_server_texture_action(mContext.get(), aTextureId, ToFFI(&aByteBuf),
+ error.ToFFI());
+
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvCommandEncoderAction(
+ RawId aEncoderId, RawId aDeviceId, const ipc::ByteBuf& aByteBuf) {
+ ErrorBuffer error;
+ ffi::wgpu_server_command_encoder_action(mContext.get(), aEncoderId,
+ ToFFI(&aByteBuf), error.ToFFI());
+ ForwardError(aDeviceId, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvBumpImplicitBindGroupLayout(RawId aPipelineId,
+ bool aIsCompute,
+ uint32_t aIndex,
+ RawId aAssignId) {
+ ErrorBuffer error;
+ if (aIsCompute) {
+ ffi::wgpu_server_compute_pipeline_get_bind_group_layout(
+ mContext.get(), aPipelineId, aIndex, aAssignId, error.ToFFI());
+ } else {
+ ffi::wgpu_server_render_pipeline_get_bind_group_layout(
+ mContext.get(), aPipelineId, aIndex, aAssignId, error.ToFFI());
+ }
+
+ ForwardError(0, error);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvDevicePushErrorScope(RawId aDeviceId) {
+ const auto& lookup = mErrorScopeMap.find(aDeviceId);
+ if (lookup == mErrorScopeMap.end()) {
+ NS_WARNING("WebGPU error scopes on a destroyed device!");
+ return IPC_OK();
+ }
+
+ lookup->second.mStack.EmplaceBack();
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvDevicePopErrorScope(
+ RawId aDeviceId, DevicePopErrorScopeResolver&& aResolver) {
+ const auto& lookup = mErrorScopeMap.find(aDeviceId);
+ if (lookup == mErrorScopeMap.end()) {
+ NS_WARNING("WebGPU error scopes on a destroyed device!");
+ ScopedError error = {true};
+ aResolver(Some(error));
+ return IPC_OK();
+ }
+
+ if (lookup->second.mStack.IsEmpty()) {
+ NS_WARNING("WebGPU no error scope to pop!");
+ ScopedError error = {true};
+ aResolver(Some(error));
+ return IPC_OK();
+ }
+
+ auto scope = lookup->second.mStack.PopLastElement();
+ aResolver(scope);
+ return IPC_OK();
+}
+
+ipc::IPCResult WebGPUParent::RecvGenerateError(RawId aDeviceId,
+ const nsCString& aMessage) {
+ ReportError(aDeviceId, aMessage);
+ return IPC_OK();
+}
+
+} // namespace mozilla::webgpu
diff --git a/dom/webgpu/ipc/WebGPUParent.h b/dom/webgpu/ipc/WebGPUParent.h
new file mode 100644
index 0000000000..384d560003
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUParent.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+#ifndef WEBGPU_PARENT_H_
+#define WEBGPU_PARENT_H_
+
+#include "mozilla/webgpu/ffi/wgpu.h"
+#include "mozilla/webgpu/PWebGPUParent.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/ipc/RawShmem.h"
+#include "WebGPUTypes.h"
+#include "base/timer.h"
+
+namespace mozilla {
+
+namespace layers {
+class RemoteTextureOwnerClient;
+} // namespace layers
+
+namespace webgpu {
+
+class ErrorBuffer;
+class PresentationData;
+
+struct ErrorScopeStack {
+ nsTArray<MaybeScopedError> mStack;
+};
+
+class WebGPUParent final : public PWebGPUParent {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGPUParent, override)
+
+ public:
+ explicit WebGPUParent();
+
+ ipc::IPCResult RecvInstanceRequestAdapter(
+ const dom::GPURequestAdapterOptions& aOptions,
+ const nsTArray<RawId>& aTargetIds,
+ InstanceRequestAdapterResolver&& resolver);
+ ipc::IPCResult RecvAdapterRequestDevice(
+ RawId aAdapterId, const ipc::ByteBuf& aByteBuf, RawId aDeviceId,
+ AdapterRequestDeviceResolver&& resolver);
+ ipc::IPCResult RecvAdapterDestroy(RawId aAdapterId);
+ ipc::IPCResult RecvDeviceDestroy(RawId aDeviceId);
+ ipc::IPCResult RecvCreateBuffer(RawId aDeviceId, RawId aBufferId,
+ dom::GPUBufferDescriptor&& aDesc,
+ ipc::UnsafeSharedMemoryHandle&& aShmem);
+ ipc::IPCResult RecvBufferMap(RawId aBufferId, uint32_t aMode,
+ uint64_t aOffset, uint64_t size,
+ BufferMapResolver&& aResolver);
+ ipc::IPCResult RecvBufferUnmap(RawId aDeviceId, RawId aBufferId, bool aFlush);
+ ipc::IPCResult RecvBufferDestroy(RawId aBufferId);
+ ipc::IPCResult RecvBufferDrop(RawId aBufferId);
+ ipc::IPCResult RecvTextureDestroy(RawId aTextureId);
+ ipc::IPCResult RecvTextureViewDestroy(RawId aTextureViewId);
+ ipc::IPCResult RecvSamplerDestroy(RawId aSamplerId);
+ ipc::IPCResult RecvCommandEncoderFinish(
+ RawId aEncoderId, RawId aDeviceId,
+ const dom::GPUCommandBufferDescriptor& aDesc);
+ ipc::IPCResult RecvCommandEncoderDestroy(RawId aEncoderId);
+ ipc::IPCResult RecvCommandBufferDestroy(RawId aCommandBufferId);
+ ipc::IPCResult RecvRenderBundleDestroy(RawId aBundleId);
+ ipc::IPCResult RecvQueueSubmit(RawId aQueueId, RawId aDeviceId,
+ const nsTArray<RawId>& aCommandBuffers);
+ ipc::IPCResult RecvQueueWriteAction(RawId aQueueId, RawId aDeviceId,
+ const ipc::ByteBuf& aByteBuf,
+ ipc::UnsafeSharedMemoryHandle&& aShmem);
+ ipc::IPCResult RecvBindGroupLayoutDestroy(RawId aBindGroupLayoutId);
+ ipc::IPCResult RecvPipelineLayoutDestroy(RawId aPipelineLayoutId);
+ ipc::IPCResult RecvBindGroupDestroy(RawId aBindGroupId);
+ ipc::IPCResult RecvShaderModuleDestroy(RawId aModuleId);
+ ipc::IPCResult RecvComputePipelineDestroy(RawId aPipelineId);
+ ipc::IPCResult RecvRenderPipelineDestroy(RawId aPipelineId);
+ ipc::IPCResult RecvImplicitLayoutDestroy(
+ RawId aImplicitPlId, const nsTArray<RawId>& aImplicitBglIds);
+ ipc::IPCResult RecvDeviceCreateSwapChain(
+ RawId aDeviceId, RawId aQueueId, const layers::RGBDescriptor& aDesc,
+ const nsTArray<RawId>& aBufferIds,
+ const layers::RemoteTextureOwnerId& aOwnerId);
+ ipc::IPCResult RecvDeviceCreateShaderModule(
+ RawId aDeviceId, RawId aModuleId, const nsString& aLabel,
+ const nsCString& aCode, DeviceCreateShaderModuleResolver&& aOutMessage);
+
+ ipc::IPCResult RecvSwapChainPresent(
+ RawId aTextureId, RawId aCommandEncoderId,
+ const layers::RemoteTextureId& aRemoteTextureId,
+ const layers::RemoteTextureOwnerId& aOwnerId);
+ ipc::IPCResult RecvSwapChainDestroy(
+ const layers::RemoteTextureOwnerId& aOwnerId);
+
+ ipc::IPCResult RecvDeviceAction(RawId aDeviceId,
+ const ipc::ByteBuf& aByteBuf);
+ ipc::IPCResult RecvDeviceActionWithAck(
+ RawId aDeviceId, const ipc::ByteBuf& aByteBuf,
+ DeviceActionWithAckResolver&& aResolver);
+ ipc::IPCResult RecvTextureAction(RawId aTextureId, RawId aDevice,
+ const ipc::ByteBuf& aByteBuf);
+ ipc::IPCResult RecvCommandEncoderAction(RawId aEncoderId, RawId aDeviceId,
+ const ipc::ByteBuf& aByteBuf);
+ ipc::IPCResult RecvBumpImplicitBindGroupLayout(RawId aPipelineId,
+ bool aIsCompute,
+ uint32_t aIndex,
+ RawId aAssignId);
+
+ ipc::IPCResult RecvDevicePushErrorScope(RawId aDeviceId);
+ ipc::IPCResult RecvDevicePopErrorScope(
+ RawId aDeviceId, DevicePopErrorScopeResolver&& aResolver);
+ ipc::IPCResult RecvGenerateError(RawId aDeviceId, const nsCString& message);
+
+ ipc::IPCResult GetFrontBufferSnapshot(
+ IProtocol* aProtocol, const layers::RemoteTextureOwnerId& aOwnerId,
+ Maybe<Shmem>& aShmem, gfx::IntSize& aSize);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ struct BufferMapData {
+ ipc::WritableSharedMemoryMapping mShmem;
+ // True if buffer's usage has MAP_READ or MAP_WRITE set.
+ bool mHasMapFlags;
+ uint64_t mMappedOffset;
+ uint64_t mMappedSize;
+ };
+
+ BufferMapData* GetBufferMapData(RawId aBufferId);
+
+ private:
+ void DeallocBufferShmem(RawId aBufferId);
+
+ virtual ~WebGPUParent();
+ void MaintainDevices();
+ bool ForwardError(RawId aDeviceId, ErrorBuffer& aError);
+ void ReportError(RawId aDeviceId, const nsCString& message);
+
+ UniquePtr<ffi::WGPUGlobal> mContext;
+ base::RepeatingTimer<WebGPUParent> mTimer;
+
+ /// A map from wgpu buffer ids to data about their shared memory segments.
+ /// Includes entries about mappedAtCreation, MAP_READ and MAP_WRITE buffers,
+ /// regardless of their state.
+ std::unordered_map<uint64_t, BufferMapData> mSharedMemoryMap;
+ /// Associated presentation data for each swapchain.
+ std::unordered_map<layers::RemoteTextureOwnerId, RefPtr<PresentationData>,
+ layers::RemoteTextureOwnerId::HashFn>
+ mCanvasMap;
+
+ RefPtr<layers::RemoteTextureOwnerClient> mRemoteTextureOwner;
+
+ /// Associated stack of error scopes for each device.
+ std::unordered_map<uint64_t, ErrorScopeStack> mErrorScopeMap;
+};
+
+} // namespace webgpu
+} // namespace mozilla
+
+#endif // WEBGPU_PARENT_H_
diff --git a/dom/webgpu/ipc/WebGPUSerialize.h b/dom/webgpu/ipc/WebGPUSerialize.h
new file mode 100644
index 0000000000..b130fc992e
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUSerialize.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef WEBGPU_SERIALIZE_H_
+#define WEBGPU_SERIALIZE_H_
+
+#include "WebGPUTypes.h"
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/webgpu/ffi/wgpu.h"
+
+namespace IPC {
+
+#define DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, guard) \
+ template <> \
+ struct ParamTraits<something> \
+ : public ContiguousEnumSerializer<something, something(0), guard> {}
+
+#define DEFINE_IPC_SERIALIZER_DOM_ENUM(something) \
+ DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, something::EndGuard_)
+#define DEFINE_IPC_SERIALIZER_FFI_ENUM(something) \
+ DEFINE_IPC_SERIALIZER_ENUM_GUARD(something, something##_Sentinel)
+
+DEFINE_IPC_SERIALIZER_DOM_ENUM(mozilla::dom::GPUPowerPreference);
+
+DEFINE_IPC_SERIALIZER_FFI_ENUM(mozilla::webgpu::ffi::WGPUHostMap);
+
+DEFINE_IPC_SERIALIZER_WITHOUT_FIELDS(mozilla::dom::GPUCommandBufferDescriptor);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::GPURequestAdapterOptions,
+ mPowerPreference, mForceFallbackAdapter);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::GPUBufferDescriptor, mSize,
+ mUsage, mMappedAtCreation);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::ScopedError, operationError,
+ validationMessage);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::webgpu::WebGPUCompilationMessage,
+ message, lineNum, linePos);
+
+#undef DEFINE_IPC_SERIALIZER_FFI_ENUM
+#undef DEFINE_IPC_SERIALIZER_DOM_ENUM
+#undef DEFINE_IPC_SERIALIZER_ENUM_GUARD
+
+} // namespace IPC
+#endif // WEBGPU_SERIALIZE_H_
diff --git a/dom/webgpu/ipc/WebGPUTypes.h b/dom/webgpu/ipc/WebGPUTypes.h
new file mode 100644
index 0000000000..e607e03b99
--- /dev/null
+++ b/dom/webgpu/ipc/WebGPUTypes.h
@@ -0,0 +1,69 @@
+/* -*- 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/. */
+
+#ifndef WEBGPU_TYPES_H_
+#define WEBGPU_TYPES_H_
+
+#include <cstdint>
+#include "mozilla/Maybe.h"
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+namespace mozilla::webgpu {
+
+using RawId = uint64_t;
+using BufferAddress = uint64_t;
+
+struct ScopedError {
+ // Did an error occur as a result the attempt to retrieve an error
+ // (e.g. from a dead device, from an empty scope stack)?
+ bool operationError = false;
+
+ // If non-empty, the first error generated when this scope was on
+ // the top of the stack. This is interpreted as UTF-8.
+ nsCString validationMessage;
+};
+using MaybeScopedError = Maybe<ScopedError>;
+
+enum class WebGPUCompilationMessageType { Error, Warning, Info };
+
+// TODO: Better name? CompilationMessage alread taken by the dom object.
+/// The serializable counterpart of the dom object CompilationMessage.
+struct WebGPUCompilationMessage {
+ nsString message;
+ uint64_t lineNum = 0;
+ uint64_t linePos = 0;
+ // In utf16 code units.
+ uint64_t offset = 0;
+ // In utf16 code units.
+ uint64_t length = 0;
+ WebGPUCompilationMessageType messageType =
+ WebGPUCompilationMessageType::Error;
+};
+
+/// A helper to reduce the boiler plate of turning the many Optional<nsAString>
+/// we get from the dom to the nullable nsACString* we pass to the wgpu ffi.
+class StringHelper {
+ public:
+ explicit StringHelper(const dom::Optional<nsString>& aWide) {
+ if (aWide.WasPassed()) {
+ mNarrow = Some(NS_ConvertUTF16toUTF8(aWide.Value()));
+ }
+ }
+
+ const nsACString* Get() const {
+ if (mNarrow.isSome()) {
+ return mNarrow.ptr();
+ }
+ return nullptr;
+ }
+
+ private:
+ Maybe<NS_ConvertUTF16toUTF8> mNarrow;
+};
+
+} // namespace mozilla::webgpu
+
+#endif // WEBGPU_TYPES_H_
diff --git a/dom/webgpu/mochitest/mochitest-no-pref.ini b/dom/webgpu/mochitest/mochitest-no-pref.ini
new file mode 100644
index 0000000000..c1e2681367
--- /dev/null
+++ b/dom/webgpu/mochitest/mochitest-no-pref.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = webgpu
+
+[test_disabled.html]
diff --git a/dom/webgpu/mochitest/mochitest.ini b/dom/webgpu/mochitest/mochitest.ini
new file mode 100644
index 0000000000..3bd5124a21
--- /dev/null
+++ b/dom/webgpu/mochitest/mochitest.ini
@@ -0,0 +1,32 @@
+[DEFAULT]
+subsuite = webgpu
+run-if = !release_or_beta
+prefs =
+ dom.webgpu.enabled=true
+ gfx.offscreencanvas.enabled=true
+support-files =
+ worker_wrapper.js
+ test_basic_canvas.worker.js
+ test_submit_render_empty.worker.js
+
+[test_enabled.html]
+[test_basic_canvas.worker.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_device_creation.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_buffer_mapping.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_command_buffer_creation.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_submit_compute_empty.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_submit_render_empty.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_submit_render_empty.worker.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_queue_copyExternalImageToTexture.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_queue_write.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
+[test_error_scope.html]
+fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
diff --git a/dom/webgpu/mochitest/test_basic_canvas.worker.html b/dom/webgpu/mochitest/test_basic_canvas.worker.html
new file mode 100644
index 0000000000..f34ef8af24
--- /dev/null
+++ b/dom/webgpu/mochitest/test_basic_canvas.worker.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="worker_wrapper.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<script>
+
+const canvas = document.getElementById("canvas");
+const offscreen = canvas.transferControlToOffscreen();
+
+runWorkerTest('test_basic_canvas.worker.js', {offscreen}, [offscreen]);
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_basic_canvas.worker.js b/dom/webgpu/mochitest/test_basic_canvas.worker.js
new file mode 100644
index 0000000000..ddbc15c512
--- /dev/null
+++ b/dom/webgpu/mochitest/test_basic_canvas.worker.js
@@ -0,0 +1,32 @@
+self.addEventListener("message", async function(event) {
+ try {
+ const offscreen = event.data.offscreen;
+ const context = offscreen.getContext("webgpu");
+
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ const swapChainFormat = context.getPreferredFormat(adapter);
+
+ context.configure({
+ device,
+ format: swapChainFormat,
+ size: { width: 100, height: 100, depth: 1 },
+ });
+
+ const texture = context.getCurrentTexture();
+
+ self.postMessage([
+ {
+ value: texture !== undefined,
+ message: "texture !== undefined",
+ },
+ ]);
+ } catch (e) {
+ self.postMessage([
+ {
+ value: false,
+ message: "Unhandled exception " + e,
+ },
+ ]);
+ }
+});
diff --git a/dom/webgpu/mochitest/test_buffer_mapping.html b/dom/webgpu/mochitest/test_buffer_mapping.html
new file mode 100644
index 0000000000..a3f59d0caf
--- /dev/null
+++ b/dom/webgpu/mochitest/test_buffer_mapping.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+async function testBody() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+
+ const bufferRead = device.createBuffer({ size:4, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
+ const bufferWrite = device.createBuffer({ size:4, usage: GPUBufferUsage.COPY_SRC, mappedAtCreation: true });
+ (new Float32Array(bufferWrite.getMappedRange())).set([1.0]);
+ bufferWrite.unmap();
+
+ const encoder = device.createCommandEncoder();
+ encoder.copyBufferToBuffer(bufferWrite, 0, bufferRead, 0, 4);
+ device.queue.submit([encoder.finish()]);
+
+ await bufferRead.mapAsync(GPUMapMode.READ);
+
+ try {
+ bufferRead.getMappedRange(0, 5);
+ ok(false, 'mapped with size outside buffer should throw');
+ } catch(e) {
+ ok(true, 'mapped with size outside buffer should throw OperationError');
+ }
+
+ try {
+ bufferRead.getMappedRange(4, 1);
+ ok(false, 'mapped with offset outside buffer should throw');
+ } catch(e) {
+ ok(true, 'mapped with offset outside buffer should throw OperationError');
+ }
+
+ const data = bufferRead.getMappedRange();
+ is(data.byteLength, 4, 'array should be 4 bytes long');
+
+ const value = (new Float32Array(data))[0];
+ ok(value == 1.0, 'value == 1.0');
+
+ bufferRead.unmap();
+ is(data.byteLength, 0, 'array should be detached after explicit unmap');
+};
+
+SimpleTest.waitForExplicitFinish();
+testBody()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_command_buffer_creation.html b/dom/webgpu/mochitest/test_command_buffer_creation.html
new file mode 100644
index 0000000000..e4f7356e0a
--- /dev/null
+++ b/dom/webgpu/mochitest/test_command_buffer_creation.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ const encoder = device.createCommandEncoder();
+ const command_buffer = encoder.finish();
+ ok(command_buffer !== undefined, 'command_buffer !== undefined');
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_device_creation.html b/dom/webgpu/mochitest/test_device_creation.html
new file mode 100644
index 0000000000..8e14ac2760
--- /dev/null
+++ b/dom/webgpu/mochitest/test_device_creation.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const limits = adapter.limits;
+ const features = adapter.features;
+ const device = await adapter.requestDevice();
+ ok(device !== undefined, 'device !== undefined');
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_disabled.html b/dom/webgpu/mochitest/test_disabled.html
new file mode 100644
index 0000000000..e96b8d7ecf
--- /dev/null
+++ b/dom/webgpu/mochitest/test_disabled.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(!SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be disabled.');
+ok(navigator.gpu === undefined, 'navigator.gpu === undefined');
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_enabled.html b/dom/webgpu/mochitest/test_enabled.html
new file mode 100644
index 0000000000..3f4af2177b
--- /dev/null
+++ b/dom/webgpu/mochitest/test_enabled.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+ok(navigator.gpu !== undefined, 'navigator.gpu !== undefined');
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_error_scope.html b/dom/webgpu/mochitest/test_error_scope.html
new file mode 100644
index 0000000000..c59996d39e
--- /dev/null
+++ b/dom/webgpu/mochitest/test_error_scope.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+
+ device.pushErrorScope("validation");
+ const buffer = device.createBuffer({ size:0, usage: 0 });
+ const error = await device.popErrorScope();
+
+ isnot(error, null)
+
+ try {
+ await device.popErrorScope();
+ ok(false, "Should have thrown")
+ } catch (ex) {
+ ok(ex.name == 'OperationError', "Should throw an OperationError")
+ }
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_queue_copyExternalImageToTexture.html b/dom/webgpu/mochitest/test_queue_copyExternalImageToTexture.html
new file mode 100644
index 0000000000..0224620e85
--- /dev/null
+++ b/dom/webgpu/mochitest/test_queue_copyExternalImageToTexture.html
@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset='utf-8'>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <script type='text/javascript'>
+'use strict';
+
+ok(
+ SpecialPowers.getBoolPref("dom.webgpu.enabled"),
+ "WebGPU pref should be enabled."
+);
+ok(
+ SpecialPowers.getBoolPref("gfx.offscreencanvas.enabled"),
+ "OffscreenCanvas pref should be enabled."
+);
+
+SimpleTest.waitForExplicitFinish();
+
+function requestAnimationFramePromise() {
+ return new Promise(requestAnimationFrame);
+}
+
+function createSourceCanvasWebgl() {
+ const offscreenCanvas = new OffscreenCanvas(200, 200);
+ const gl = offscreenCanvas.getContext("webgl");
+
+ const COLOR_VALUE = 127.0 / 255.0;
+ const ALPHA_VALUE = 127.0 / 255.0;
+
+ gl.enable(gl.SCISSOR_TEST);
+
+ gl.scissor(0, 0, 100, 100);
+ gl.clearColor(COLOR_VALUE, 0.0, 0.0, ALPHA_VALUE);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(100, 0, 100, 100);
+ gl.clearColor(0.0, COLOR_VALUE, 0.0, ALPHA_VALUE);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(0, 100, 100, 100);
+ gl.clearColor(0.0, 0.0, COLOR_VALUE, ALPHA_VALUE);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(100, 100, 100, 100);
+ gl.clearColor(0.0, 0.0, 0.0, ALPHA_VALUE);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ return {
+ source: offscreenCanvas,
+ origin: { x: 0, y: 0 },
+ flipY: true,
+ };
+}
+
+function createSourceCanvas2d() {
+ const offscreenCanvas = new OffscreenCanvas(200, 200);
+ const context = offscreenCanvas.getContext("2d");
+
+ context.fillStyle = "rgba(255,0,0,0.498)";
+ context.fillRect(0, 0, 100, 100);
+
+ context.fillStyle = "rgba(0,255,0,0.498)";
+ context.fillRect(100, 0, 100, 100);
+
+ context.fillStyle = "rgba(0,0,255,0.498)";
+ context.fillRect(0, 100, 100, 100);
+
+ context.fillStyle = "rgba(0,0,0,0.498)";
+ context.fillRect(100, 100, 100, 100);
+
+ return {
+ source: offscreenCanvas,
+ origin: { x: 0, y: 0 },
+ flipY: false,
+ };
+}
+
+function createSourceImageBitmap() {
+ const sourceCanvas = createSourceCanvas2d();
+ return {
+ source: sourceCanvas.source.transferToImageBitmap(),
+ origin: { x: 0, y: 0 },
+ flipY: false,
+ };
+}
+
+async function mapDestTexture(device, source, destFormat, premultiply, copySize) {
+ const bytesPerRow = 256 * 4; // 256 aligned for 200 pixels
+ const texture = device.createTexture({
+ format: destFormat,
+ size: copySize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ device.queue.copyExternalImageToTexture(
+ source,
+ { texture, premultipliedAlpha: premultiply },
+ copySize,
+ );
+
+ const buffer = device.createBuffer({
+ size: 1024 * 200,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ });
+
+ const encoder = device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ { texture },
+ { buffer, bytesPerRow },
+ copySize,
+ );
+ device.queue.submit([encoder.finish()]);
+
+ await buffer.mapAsync(GPUMapMode.READ);
+ return buffer;
+}
+
+async function verifyBuffer(test, device, source, format, premultiply, copyDim, topLeftPixelData) {
+ try {
+ const buffer = await mapDestTexture(device, source, format, premultiply, copyDim);
+ const arrayBuffer = buffer.getMappedRange();
+ const view = new Uint8Array(arrayBuffer);
+ for (let i = 0; i < topLeftPixelData.length; ++i) {
+ is(view[i], topLeftPixelData[i], test + " " + format + " (" + source.origin.x + "," + source.origin.y + ") channel " + i);
+ }
+ } catch(e) {
+ ok(false, "WebGPU exception: " + e);
+ }
+}
+
+async function verifySourceCanvas(test, device, source) {
+ await verifyBuffer(test, device, source, "rgba8unorm", /* premultiply */ true, { width: 200, height: 200 }, [127, 0, 0, 127]);
+ await verifyBuffer(test, device, source, "bgra8unorm", /* premultiply */ true, { width: 200, height: 200 }, [0, 0, 127, 127]);
+ await verifyBuffer(test, device, source, "rgba8unorm", /* premultiply */ false, { width: 200, height: 200 }, [255, 0, 0, 127]);
+ await verifyBuffer(test, device, source, "bgra8unorm", /* premultiply */ false, { width: 200, height: 200 }, [0, 0, 255, 127]);
+
+ // The copy is flipped but the origin is relative to the original source data,
+ // so we need to invert for WebGL.
+ const topRightPixelData = test === "webgl" ? [0, 0, 0, 127] : [0, 127, 0, 127];
+ const topRightOrigin = { origin: { x: 100, y: 0 } };
+ await verifyBuffer(test, device, { ...source, ...topRightOrigin }, "bgra8unorm", /* premultiply */ true, { width: 100, height: 100 }, topRightPixelData);
+
+ const bottomLeftPixelData = test === "webgl" ? [0, 0, 127, 127] : [127, 0, 0, 127];
+ const bottomLeftOrigin = { origin: { x: 0, y: 100 } };
+ await verifyBuffer(test, device, { ...source, ...bottomLeftOrigin }, "bgra8unorm", /* premultiply */ true, { width: 100, height: 100 }, bottomLeftPixelData);
+}
+
+async function writeDestCanvas(source2d, sourceWebgl, sourceImageBitmap) {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ await verifySourceCanvas("2d", device, source2d);
+ await verifySourceCanvas("imageBitmap", device, sourceImageBitmap);
+ await verifySourceCanvas("webgl", device, sourceWebgl);
+}
+
+async function runTest() {
+ try {
+ const source2d = createSourceCanvas2d();
+ const sourceWebgl = createSourceCanvasWebgl();
+ const sourceImageBitmap = createSourceImageBitmap();
+ await requestAnimationFramePromise();
+ await requestAnimationFramePromise();
+ await writeDestCanvas(source2d, sourceWebgl, sourceImageBitmap);
+ } catch(e) {
+ ok(false, "Uncaught exception: " + e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+runTest();
+ </script>
+</body>
+
+</html>
diff --git a/dom/webgpu/mochitest/test_queue_write.html b/dom/webgpu/mochitest/test_queue_write.html
new file mode 100644
index 0000000000..bcc8a104df
--- /dev/null
+++ b/dom/webgpu/mochitest/test_queue_write.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ const buffer = device.createBuffer({size:16, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX});
+ const arrayBuf = new ArrayBuffer(16);
+ (new Int32Array(arrayBuf)).fill(5)
+ device.queue.writeBuffer(buffer, 0, arrayBuf, 0);
+ const texture = device.createTexture({size: [2,2,1], dimension: "2d", format: "rgba8unorm", usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC });
+ device.queue.writeTexture({ texture }, arrayBuf, { bytesPerRow:8 }, [2,2,1]);
+ // this isn't a process check, we need to read back the contents and verify the writes happened
+ ok(device !== undefined, '');
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_submit_compute_empty.html b/dom/webgpu/mochitest/test_submit_compute_empty.html
new file mode 100644
index 0000000000..e5a9eabfc1
--- /dev/null
+++ b/dom/webgpu/mochitest/test_submit_compute_empty.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.endPass();
+ const command_buffer = encoder.finish();
+ device.queue.submit([command_buffer]);
+ ok(command_buffer !== undefined, 'command_buffer !== undefined');
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_submit_render_empty.html b/dom/webgpu/mochitest/test_submit_render_empty.html
new file mode 100644
index 0000000000..aec2a606ef
--- /dev/null
+++ b/dom/webgpu/mochitest/test_submit_render_empty.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+ok(SpecialPowers.getBoolPref('dom.webgpu.enabled'), 'Pref should be enabled.');
+
+const func = async function() {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+
+ const swapChainFormat = "rgba8unorm";
+ const bundleEncoder = device.createRenderBundleEncoder({
+ colorFormats: [swapChainFormat],
+ });
+ const bundle = bundleEncoder.finish({});
+
+ const texture = device.createTexture({
+ size: { width: 100, height: 100, depth: 1 },
+ format: swapChainFormat,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ const view = texture.createView();
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [{
+ view,
+ loadValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: "store",
+ }],
+ });
+ pass.executeBundles([bundle]);
+ pass.endPass();
+ const command_buffer = encoder.finish();
+
+ device.queue.submit([command_buffer]);
+ ok(command_buffer !== undefined, 'command_buffer !== undefined');
+};
+
+SimpleTest.waitForExplicitFinish();
+func()
+ .catch((e) => ok(false, "Unhandled exception " + e))
+ .finally(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_submit_render_empty.worker.html b/dom/webgpu/mochitest/test_submit_render_empty.worker.html
new file mode 100644
index 0000000000..100f866ecc
--- /dev/null
+++ b/dom/webgpu/mochitest/test_submit_render_empty.worker.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="worker_wrapper.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+runWorkerTest('test_submit_render_empty.worker.js', {}, []);
+
+</script>
+</body>
+</html>
diff --git a/dom/webgpu/mochitest/test_submit_render_empty.worker.js b/dom/webgpu/mochitest/test_submit_render_empty.worker.js
new file mode 100644
index 0000000000..1d922d38d5
--- /dev/null
+++ b/dom/webgpu/mochitest/test_submit_render_empty.worker.js
@@ -0,0 +1,48 @@
+self.addEventListener("message", async function(event) {
+ try {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+
+ const swapChainFormat = "rgba8unorm";
+ const bundleEncoder = device.createRenderBundleEncoder({
+ colorFormats: [swapChainFormat],
+ });
+ const bundle = bundleEncoder.finish({});
+
+ const texture = device.createTexture({
+ size: { width: 100, height: 100, depth: 1 },
+ format: swapChainFormat,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ const view = texture.createView();
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view,
+ loadValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: "store",
+ },
+ ],
+ });
+ pass.executeBundles([bundle]);
+ pass.endPass();
+ const command_buffer = encoder.finish();
+
+ device.queue.submit([command_buffer]);
+ self.postMessage([
+ {
+ value: command_buffer !== undefined,
+ message: "command_buffer !== undefined",
+ },
+ ]);
+ } catch (e) {
+ self.postMessage([
+ {
+ value: false,
+ message: "Unhandled exception " + e,
+ },
+ ]);
+ }
+});
diff --git a/dom/webgpu/mochitest/worker_wrapper.js b/dom/webgpu/mochitest/worker_wrapper.js
new file mode 100644
index 0000000000..19ce6dd21e
--- /dev/null
+++ b/dom/webgpu/mochitest/worker_wrapper.js
@@ -0,0 +1,33 @@
+ok(
+ SpecialPowers.getBoolPref("dom.webgpu.enabled"),
+ "WebGPU pref should be enabled."
+);
+ok(
+ SpecialPowers.getBoolPref("gfx.offscreencanvas.enabled"),
+ "OffscreenCanvas pref should be enabled."
+);
+SimpleTest.waitForExplicitFinish();
+
+const workerWrapperFunc = async function(worker_path, data, transfer) {
+ const worker = new Worker(worker_path);
+
+ const results = new Promise((resolve, reject) => {
+ worker.addEventListener("message", event => {
+ resolve(event.data);
+ });
+ });
+
+ worker.postMessage(data, transfer);
+ for (const result of await results) {
+ ok(result.value, result.message);
+ }
+};
+
+async function runWorkerTest(worker_path, data, transfer) {
+ try {
+ await workerWrapperFunc(worker_path, data, transfer);
+ } catch (e) {
+ ok(false, "Unhandled exception " + e);
+ }
+ SimpleTest.finish();
+}
diff --git a/dom/webgpu/moz.build b/dom/webgpu/moz.build
new file mode 100644
index 0000000000..9ff27f6dd4
--- /dev/null
+++ b/dom/webgpu/moz.build
@@ -0,0 +1,74 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Graphics: WebGPU")
+
+MOCHITEST_MANIFESTS += [
+ "mochitest/mochitest-no-pref.ini",
+ "mochitest/mochitest.ini",
+]
+
+DIRS += []
+
+h_and_cpp = [
+ "Adapter",
+ "BindGroup",
+ "BindGroupLayout",
+ "Buffer",
+ "CanvasContext",
+ "CommandBuffer",
+ "CommandEncoder",
+ "CompilationInfo",
+ "CompilationMessage",
+ "ComputePassEncoder",
+ "ComputePipeline",
+ "Device",
+ "DeviceLostInfo",
+ "Instance",
+ "ObjectModel",
+ "OutOfMemoryError",
+ "PipelineLayout",
+ "QuerySet",
+ "Queue",
+ "RenderBundle",
+ "RenderBundleEncoder",
+ "RenderPassEncoder",
+ "RenderPipeline",
+ "Sampler",
+ "ShaderModule",
+ "SupportedFeatures",
+ "SupportedLimits",
+ "Texture",
+ "TextureView",
+ "ValidationError",
+]
+EXPORTS.mozilla.webgpu += [x + ".h" for x in h_and_cpp]
+UNIFIED_SOURCES += [x + ".cpp" for x in h_and_cpp]
+
+IPDL_SOURCES += [
+ "ipc/PWebGPU.ipdl",
+ "ipc/PWebGPUTypes.ipdlh",
+]
+
+EXPORTS.mozilla.webgpu += [
+ "ipc/WebGPUChild.h",
+ "ipc/WebGPUParent.h",
+ "ipc/WebGPUSerialize.h",
+ "ipc/WebGPUTypes.h",
+]
+
+UNIFIED_SOURCES += [
+ "ipc/WebGPUChild.cpp",
+ "ipc/WebGPUParent.cpp",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "clang-cl"):
+ CXXFLAGS += ["-Werror=implicit-int-conversion"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"