diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/webgpu | |
parent | Initial commit. (diff) | |
download | firefox-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 'dom/webgpu')
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, ©View); + 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, ©View); + 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" |