diff options
Diffstat (limited to 'dom/webgpu/Buffer.cpp')
-rw-r--r-- | dom/webgpu/Buffer.cpp | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/dom/webgpu/Buffer.cpp b/dom/webgpu/Buffer.cpp new file mode 100644 index 0000000000..7ccb5e88c2 --- /dev/null +++ b/dom/webgpu/Buffer.cpp @@ -0,0 +1,169 @@ +/* -*- 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/RootingAPI.h" +#include "nsContentUtils.h" +#include "nsWrapperCache.h" +#include "Device.h" + +namespace mozilla { +namespace webgpu { + +GPU_IMPL_JS_WRAP(Buffer) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Buffer, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Buffer, Release) +NS_IMPL_CYCLE_COLLECTION_CLASS(Buffer) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Buffer) + tmp->Cleanup(); + 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) + : ChildOf(aParent), mId(aId), mSize(aSize) { + mozilla::HoldJSObjects(this); +} + +Buffer::~Buffer() { + Cleanup(); + mozilla::DropJSObjects(this); +} + +void Buffer::Cleanup() { + if (mValid && mParent) { + mValid = false; + auto bridge = mParent->GetBridge(); + if (bridge && bridge->IsOpen()) { + bridge->SendBufferDestroy(mId); + } + if (bridge && mMapped) { + bridge->DeallocShmem(mMapped->mShmem); + } + } +} + +void Buffer::SetMapped(ipc::Shmem&& aShmem, bool aWritable) { + MOZ_ASSERT(!mMapped); + mMapped.emplace(); + mMapped->mShmem = std::move(aShmem); + mMapped->mWritable = aWritable; +} + +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 (mMapped) { + aRv.ThrowInvalidStateError("Unable to map a buffer that is already mapped"); + return nullptr; + } + // Initialize with a dummy shmem, it will become real after the promise is + // resolved. + SetMapped(ipc::Shmem(), aMode == dom::GPUMapMode_Binding::WRITE); + + const auto checked = aSize.WasPassed() ? CheckedInt<size_t>(aSize.Value()) + : CheckedInt<size_t>(mSize) - aOffset; + if (!checked.isValid()) { + aRv.ThrowRangeError("Mapped size is too large"); + return nullptr; + } + + const auto& size = checked.value(); + RefPtr<Buffer> self(this); + + auto mappingPromise = mParent->MapBufferAsync(mId, aMode, aOffset, size, aRv); + if (!mappingPromise) { + return nullptr; + } + + mappingPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise, self](ipc::Shmem&& aShmem) { + self->mMapped->mShmem = std::move(aShmem); + promise->MaybeResolve(0); + }, + [promise](const ipc::ResponseRejectReason&) { + promise->MaybeRejectWithAbortError("Internal communication error!"); + }); + + return promise.forget(); +} + +void Buffer::GetMappedRange(JSContext* aCx, uint64_t aOffset, + const dom::Optional<uint64_t>& aSize, + JS::Rooted<JSObject*>* aObject, ErrorResult& aRv) { + const auto checkedOffset = CheckedInt<size_t>(aOffset); + const auto checkedSize = aSize.WasPassed() + ? CheckedInt<size_t>(aSize.Value()) + : CheckedInt<size_t>(mSize) - aOffset; + if (!checkedOffset.isValid() || !checkedSize.isValid()) { + aRv.ThrowRangeError("Invalid mapped range"); + return; + } + if (!mMapped || !mMapped->IsReady()) { + aRv.ThrowInvalidStateError("Buffer is not mapped"); + return; + } + + auto* const arrayBuffer = mParent->CreateExternalArrayBuffer( + aCx, checkedOffset.value(), checkedSize.value(), mMapped->mShmem); + if (!arrayBuffer) { + aRv.NoteJSContextException(aCx); + return; + } + + aObject->set(arrayBuffer); + mMapped->mArrayBuffers.AppendElement(*aObject); +} + +void Buffer::Unmap(JSContext* aCx, ErrorResult& aRv) { + if (!mMapped) { + return; + } + + for (const auto& arrayBuffer : mMapped->mArrayBuffers) { + JS::Rooted<JSObject*> rooted(aCx, arrayBuffer); + bool ok = JS::DetachArrayBuffer(aCx, rooted); + if (!ok) { + aRv.NoteJSContextException(aCx); + return; + } + }; + + mParent->UnmapBuffer(mId, std::move(mMapped->mShmem), mMapped->mWritable); + mMapped.reset(); +} + +void Buffer::Destroy() { + // TODO: we don't have to implement it right now, but it's used by the + // examples +} + +} // namespace webgpu +} // namespace mozilla |