Backed out 4 changesets (bug 1861985, bug 1860958, bug 1865364) for causing bustage...
[gecko.git] / dom / webgpu / Buffer.cpp
blobd5cdadd239c7a8b9910e0a87ee83665cbdf58cc6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/WebGPUBinding.h"
7 #include "Buffer.h"
9 #include "mozilla/dom/Promise.h"
10 #include "mozilla/dom/ScriptSettings.h"
11 #include "mozilla/HoldDropJSObjects.h"
12 #include "mozilla/ipc/Shmem.h"
13 #include "ipc/WebGPUChild.h"
14 #include "js/ArrayBuffer.h"
15 #include "js/RootingAPI.h"
16 #include "nsContentUtils.h"
17 #include "nsWrapperCache.h"
18 #include "Device.h"
19 #include "mozilla/webgpu/ffi/wgpu.h"
21 namespace mozilla::webgpu {
23 GPU_IMPL_JS_WRAP(Buffer)
25 NS_IMPL_CYCLE_COLLECTION_CLASS(Buffer)
26 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Buffer)
27 tmp->Drop();
28 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
30 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Buffer)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
34 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Buffer)
35 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
36 if (tmp->mMapped) {
37 for (uint32_t i = 0; i < tmp->mMapped->mArrayBuffers.Length(); ++i) {
38 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(
39 mMapped->mArrayBuffers[i])
42 NS_IMPL_CYCLE_COLLECTION_TRACE_END
44 Buffer::Buffer(Device* const aParent, RawId aId, BufferAddress aSize,
45 uint32_t aUsage, ipc::WritableSharedMemoryMapping&& aShmem)
46 : ChildOf(aParent), mId(aId), mSize(aSize), mUsage(aUsage) {
47 mozilla::HoldJSObjects(this);
48 mShmem =
49 std::make_shared<ipc::WritableSharedMemoryMapping>(std::move(aShmem));
50 MOZ_ASSERT(mParent);
53 Buffer::~Buffer() {
54 Drop();
55 mozilla::DropJSObjects(this);
58 already_AddRefed<Buffer> Buffer::Create(Device* aDevice, RawId aDeviceId,
59 const dom::GPUBufferDescriptor& aDesc,
60 ErrorResult& aRv) {
61 if (aDevice->IsLost()) {
62 // Create and return an invalid Buffer. This Buffer will have id 0 and
63 // won't be sent in any messages to the parent.
64 RefPtr<Buffer> buffer = new Buffer(aDevice, 0, aDesc.mSize, 0,
65 ipc::WritableSharedMemoryMapping());
67 // Track the invalid Buffer to ensure that ::Drop can untrack it later.
68 aDevice->TrackBuffer(buffer.get());
69 return buffer.forget();
72 RefPtr<WebGPUChild> actor = aDevice->GetBridge();
74 auto handle = ipc::UnsafeSharedMemoryHandle();
75 auto mapping = ipc::WritableSharedMemoryMapping();
77 bool hasMapFlags = aDesc.mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
78 dom::GPUBufferUsage_Binding::MAP_READ);
80 bool allocSucceeded = false;
81 if (hasMapFlags || aDesc.mMappedAtCreation) {
82 // If shmem allocation fails, we continue and provide the parent side with
83 // an empty shmem which it will interpret as an OOM situtation.
84 const auto checked = CheckedInt<size_t>(aDesc.mSize);
85 const size_t maxSize = WGPUMAX_BUFFER_SIZE;
86 if (checked.isValid()) {
87 size_t size = checked.value();
89 if (size > 0 && size < maxSize) {
90 auto maybeShmem = ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
92 if (maybeShmem.isSome()) {
93 allocSucceeded = true;
94 handle = std::move(maybeShmem.ref().first);
95 mapping = std::move(maybeShmem.ref().second);
97 MOZ_RELEASE_ASSERT(mapping.Size() >= size);
99 // zero out memory
100 memset(mapping.Bytes().data(), 0, size);
104 if (size == 0) {
105 // Zero-sized buffers is a special case. We don't create a shmem since
106 // allocating the memory would not make sense, however mappable null
107 // buffers are allowed by the spec so we just pass the null handle which
108 // in practice deserializes into a null handle on the parent side and
109 // behaves like a zero-sized allocation.
110 allocSucceeded = true;
115 // If mapped at creation and the shmem allocation failed, immediately throw
116 // a range error and don't attempt to create the buffer.
117 if (aDesc.mMappedAtCreation && !allocSucceeded) {
118 aRv.ThrowRangeError("Allocation failed");
119 return nullptr;
122 RawId id = actor->DeviceCreateBuffer(aDeviceId, aDesc, std::move(handle));
124 RefPtr<Buffer> buffer =
125 new Buffer(aDevice, id, aDesc.mSize, aDesc.mUsage, std::move(mapping));
126 buffer->SetLabel(aDesc.mLabel);
128 if (aDesc.mMappedAtCreation) {
129 // Mapped at creation's raison d'ĂȘtre is write access, since the buffer is
130 // being created and there isn't anything interesting to read in it yet.
131 bool writable = true;
132 buffer->SetMapped(0, aDesc.mSize, writable);
135 aDevice->TrackBuffer(buffer.get());
137 return buffer.forget();
140 void Buffer::Drop() {
141 if (!mValid) {
142 return;
145 mValid = false;
147 AbortMapRequest();
149 if (mMapped && !mMapped->mArrayBuffers.IsEmpty()) {
150 // The array buffers could live longer than us and our shmem, so make sure
151 // we clear the external buffer bindings.
152 dom::AutoJSAPI jsapi;
153 if (jsapi.Init(GetDevice().GetOwnerGlobal())) {
154 IgnoredErrorResult rv;
155 UnmapArrayBuffers(jsapi.cx(), rv);
158 mMapped.reset();
160 GetDevice().UntrackBuffer(this);
162 if (GetDevice().IsBridgeAlive() && mId) {
163 GetDevice().GetBridge()->SendBufferDrop(mId);
167 void Buffer::SetMapped(BufferAddress aOffset, BufferAddress aSize,
168 bool aWritable) {
169 MOZ_ASSERT(!mMapped);
170 MOZ_RELEASE_ASSERT(aOffset <= mSize);
171 MOZ_RELEASE_ASSERT(aSize <= mSize - aOffset);
173 mMapped.emplace();
174 mMapped->mWritable = aWritable;
175 mMapped->mOffset = aOffset;
176 mMapped->mSize = aSize;
179 already_AddRefed<dom::Promise> Buffer::MapAsync(
180 uint32_t aMode, uint64_t aOffset, const dom::Optional<uint64_t>& aSize,
181 ErrorResult& aRv) {
182 RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
183 if (NS_WARN_IF(aRv.Failed())) {
184 return nullptr;
187 if (GetDevice().IsLost()) {
188 promise->MaybeRejectWithOperationError("Device Lost");
189 return promise.forget();
192 if (mMapRequest) {
193 promise->MaybeRejectWithOperationError("Buffer mapping is already pending");
194 return promise.forget();
197 BufferAddress size = 0;
198 if (aSize.WasPassed()) {
199 size = aSize.Value();
200 } else if (aOffset <= mSize) {
201 // Default to passing the reminder of the buffer after the provided offset.
202 size = mSize - aOffset;
203 } else {
204 // The provided offset is larger than the buffer size.
205 // The parent side will handle the error, we can let the requested size be
206 // zero.
209 RefPtr<Buffer> self(this);
211 auto mappingPromise =
212 GetDevice().GetBridge()->SendBufferMap(mId, aMode, aOffset, size);
213 MOZ_ASSERT(mappingPromise);
215 mMapRequest = promise;
217 mappingPromise->Then(
218 GetCurrentSerialEventTarget(), __func__,
219 [promise, self](BufferMapResult&& aResult) {
220 // Unmap might have been called while the result was on the way back.
221 if (promise->State() != dom::Promise::PromiseState::Pending) {
222 return;
225 // mValid should be true or we should have called unmap while marking
226 // the buffer invalid, causing the promise to be rejected and the branch
227 // above to have early-returned.
228 MOZ_RELEASE_ASSERT(self->mValid);
230 switch (aResult.type()) {
231 case BufferMapResult::TBufferMapSuccess: {
232 auto& success = aResult.get_BufferMapSuccess();
233 self->mMapRequest = nullptr;
234 self->SetMapped(success.offset(), success.size(),
235 success.writable());
236 promise->MaybeResolve(0);
237 break;
239 case BufferMapResult::TBufferMapError: {
240 auto& error = aResult.get_BufferMapError();
241 self->RejectMapRequest(promise, error.message());
242 break;
244 default: {
245 MOZ_CRASH("unreachable");
249 [promise](const ipc::ResponseRejectReason&) {
250 promise->MaybeRejectWithAbortError("Internal communication error!");
253 return promise.forget();
256 static void ExternalBufferFreeCallback(void* aContents, void* aUserData) {
257 Unused << aContents;
258 auto shm = static_cast<std::shared_ptr<ipc::WritableSharedMemoryMapping>*>(
259 aUserData);
260 delete shm;
263 void Buffer::GetMappedRange(JSContext* aCx, uint64_t aOffset,
264 const dom::Optional<uint64_t>& aSize,
265 JS::Rooted<JSObject*>* aObject, ErrorResult& aRv) {
266 if (!mMapped) {
267 aRv.ThrowInvalidStateError("Buffer is not mapped");
268 return;
271 const auto checkedOffset = CheckedInt<size_t>(aOffset);
272 const auto checkedSize = aSize.WasPassed()
273 ? CheckedInt<size_t>(aSize.Value())
274 : CheckedInt<size_t>(mSize) - aOffset;
275 const auto checkedMinBufferSize = checkedOffset + checkedSize;
277 if (!checkedOffset.isValid() || !checkedSize.isValid() ||
278 !checkedMinBufferSize.isValid() || aOffset < mMapped->mOffset ||
279 checkedMinBufferSize.value() > mMapped->mOffset + mMapped->mSize) {
280 aRv.ThrowRangeError("Invalid range");
281 return;
284 auto offset = checkedOffset.value();
285 auto size = checkedSize.value();
286 auto span = mShmem->Bytes().Subspan(offset, size);
288 std::shared_ptr<ipc::WritableSharedMemoryMapping>* userData =
289 new std::shared_ptr<ipc::WritableSharedMemoryMapping>(mShmem);
290 UniquePtr<void, JS::BufferContentsDeleter> dataPtr{
291 span.data(), {&ExternalBufferFreeCallback, userData}};
292 JS::Rooted<JSObject*> arrayBuffer(
293 aCx, JS::NewExternalArrayBuffer(aCx, size, std::move(dataPtr)));
294 if (!arrayBuffer) {
295 aRv.NoteJSContextException(aCx);
296 return;
299 aObject->set(arrayBuffer);
300 mMapped->mArrayBuffers.AppendElement(*aObject);
303 void Buffer::UnmapArrayBuffers(JSContext* aCx, ErrorResult& aRv) {
304 MOZ_ASSERT(mMapped);
306 bool detachedArrayBuffers = true;
307 for (const auto& arrayBuffer : mMapped->mArrayBuffers) {
308 JS::Rooted<JSObject*> rooted(aCx, arrayBuffer);
309 if (!JS::DetachArrayBuffer(aCx, rooted)) {
310 detachedArrayBuffers = false;
314 mMapped->mArrayBuffers.Clear();
316 AbortMapRequest();
318 if (NS_WARN_IF(!detachedArrayBuffers)) {
319 aRv.NoteJSContextException(aCx);
320 return;
324 void Buffer::RejectMapRequest(dom::Promise* aPromise, nsACString& message) {
325 if (mMapRequest == aPromise) {
326 mMapRequest = nullptr;
329 aPromise->MaybeRejectWithOperationError(message);
332 void Buffer::AbortMapRequest() {
333 if (mMapRequest) {
334 mMapRequest->MaybeRejectWithAbortError("Buffer unmapped");
336 mMapRequest = nullptr;
339 void Buffer::Unmap(JSContext* aCx, ErrorResult& aRv) {
340 if (!mMapped) {
341 return;
344 UnmapArrayBuffers(aCx, aRv);
346 bool hasMapFlags = mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
347 dom::GPUBufferUsage_Binding::MAP_READ);
349 if (!hasMapFlags) {
350 // We get here if the buffer was mapped at creation without map flags.
351 // It won't be possible to map the buffer again so we can get rid of
352 // our shmem on this side.
353 mShmem = std::make_shared<ipc::WritableSharedMemoryMapping>();
356 if (!GetDevice().IsLost()) {
357 GetDevice().GetBridge()->SendBufferUnmap(GetDevice().mId, mId,
358 mMapped->mWritable);
361 mMapped.reset();
364 void Buffer::Destroy(JSContext* aCx, ErrorResult& aRv) {
365 if (mMapped) {
366 Unmap(aCx, aRv);
369 if (!GetDevice().IsLost()) {
370 GetDevice().GetBridge()->SendBufferDestroy(mId);
372 // TODO: we don't have to implement it right now, but it's used by the
373 // examples
376 dom::GPUBufferMapState Buffer::MapState() const {
377 // Implementation reference:
378 // <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>.
380 if (mMapped) {
381 return dom::GPUBufferMapState::Mapped;
383 if (mMapRequest) {
384 return dom::GPUBufferMapState::Pending;
386 return dom::GPUBufferMapState::Unmapped;
389 } // namespace mozilla::webgpu