Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / webgpu / Buffer.cpp
blob13403412bff08237b076d6a219ce95b772534c33
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"
20 namespace mozilla::webgpu {
22 GPU_IMPL_JS_WRAP(Buffer)
24 NS_IMPL_CYCLE_COLLECTION_CLASS(Buffer)
25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Buffer)
26 tmp->Drop();
27 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
28 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Buffer)
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Buffer)
34 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
35 if (tmp->mMapped) {
36 for (uint32_t i = 0; i < tmp->mMapped->mArrayBuffers.Length(); ++i) {
37 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(
38 mMapped->mArrayBuffers[i])
41 NS_IMPL_CYCLE_COLLECTION_TRACE_END
43 Buffer::Buffer(Device* const aParent, RawId aId, BufferAddress aSize,
44 uint32_t aUsage, ipc::WritableSharedMemoryMapping&& aShmem)
45 : ChildOf(aParent), mId(aId), mSize(aSize), mUsage(aUsage) {
46 mozilla::HoldJSObjects(this);
47 mShmem =
48 std::make_shared<ipc::WritableSharedMemoryMapping>(std::move(aShmem));
49 MOZ_ASSERT(mParent);
52 Buffer::~Buffer() {
53 Drop();
54 mozilla::DropJSObjects(this);
57 already_AddRefed<Buffer> Buffer::Create(Device* aDevice, RawId aDeviceId,
58 const dom::GPUBufferDescriptor& aDesc,
59 ErrorResult& aRv) {
60 if (aDevice->IsLost()) {
61 RefPtr<Buffer> buffer = new Buffer(aDevice, 0, aDesc.mSize, 0,
62 ipc::WritableSharedMemoryMapping());
63 return buffer.forget();
66 RefPtr<WebGPUChild> actor = aDevice->GetBridge();
68 auto handle = ipc::UnsafeSharedMemoryHandle();
69 auto mapping = ipc::WritableSharedMemoryMapping();
71 bool hasMapFlags = aDesc.mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
72 dom::GPUBufferUsage_Binding::MAP_READ);
73 if (hasMapFlags || aDesc.mMappedAtCreation) {
74 const auto checked = CheckedInt<size_t>(aDesc.mSize);
75 if (!checked.isValid()) {
76 aRv.ThrowRangeError("Mappable size is too large");
77 return nullptr;
79 size_t size = checked.value();
81 auto maybeShmem = ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
83 if (maybeShmem.isNothing()) {
84 aRv.ThrowRangeError(
85 nsPrintfCString("Unable to allocate shmem of size %" PRIuPTR, size));
86 return nullptr;
89 handle = std::move(maybeShmem.ref().first);
90 mapping = std::move(maybeShmem.ref().second);
92 MOZ_RELEASE_ASSERT(mapping.Size() >= size);
94 // zero out memory
95 memset(mapping.Bytes().data(), 0, size);
98 RawId id = actor->DeviceCreateBuffer(aDeviceId, aDesc, std::move(handle));
100 RefPtr<Buffer> buffer =
101 new Buffer(aDevice, id, aDesc.mSize, aDesc.mUsage, std::move(mapping));
102 if (aDesc.mMappedAtCreation) {
103 // Mapped at creation's raison d'ĂȘtre is write access, since the buffer is
104 // being created and there isn't anything interesting to read in it yet.
105 bool writable = true;
106 buffer->SetMapped(0, aDesc.mSize, writable);
109 return buffer.forget();
112 void Buffer::Drop() {
113 AbortMapRequest();
115 if (mMapped && !mMapped->mArrayBuffers.IsEmpty()) {
116 // The array buffers could live longer than us and our shmem, so make sure
117 // we clear the external buffer bindings.
118 dom::AutoJSAPI jsapi;
119 if (jsapi.Init(GetDevice().GetOwnerGlobal())) {
120 IgnoredErrorResult rv;
121 UnmapArrayBuffers(jsapi.cx(), rv);
124 mMapped.reset();
126 if (mValid && !GetDevice().IsLost()) {
127 GetDevice().GetBridge()->SendBufferDrop(mId);
129 mValid = false;
132 void Buffer::SetMapped(BufferAddress aOffset, BufferAddress aSize,
133 bool aWritable) {
134 MOZ_ASSERT(!mMapped);
135 MOZ_RELEASE_ASSERT(aOffset <= mSize);
136 MOZ_RELEASE_ASSERT(aSize <= mSize - aOffset);
138 mMapped.emplace();
139 mMapped->mWritable = aWritable;
140 mMapped->mOffset = aOffset;
141 mMapped->mSize = aSize;
144 already_AddRefed<dom::Promise> Buffer::MapAsync(
145 uint32_t aMode, uint64_t aOffset, const dom::Optional<uint64_t>& aSize,
146 ErrorResult& aRv) {
147 RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv);
148 if (NS_WARN_IF(aRv.Failed())) {
149 return nullptr;
152 if (GetDevice().IsLost()) {
153 promise->MaybeRejectWithOperationError("Device Lost");
154 return promise.forget();
157 if (mMapRequest) {
158 promise->MaybeRejectWithOperationError("Buffer mapping is already pending");
159 return promise.forget();
162 BufferAddress size = 0;
163 if (aSize.WasPassed()) {
164 size = aSize.Value();
165 } else if (aOffset <= mSize) {
166 // Default to passing the reminder of the buffer after the provided offset.
167 size = mSize - aOffset;
168 } else {
169 // The provided offset is larger than the buffer size.
170 // The parent side will handle the error, we can let the requested size be
171 // zero.
174 RefPtr<Buffer> self(this);
176 auto mappingPromise =
177 GetDevice().GetBridge()->SendBufferMap(mId, aMode, aOffset, size);
178 MOZ_ASSERT(mappingPromise);
180 mMapRequest = promise;
182 mappingPromise->Then(
183 GetCurrentSerialEventTarget(), __func__,
184 [promise, self](BufferMapResult&& aResult) {
185 // Unmap might have been called while the result was on the way back.
186 if (promise->State() != dom::Promise::PromiseState::Pending) {
187 return;
190 switch (aResult.type()) {
191 case BufferMapResult::TBufferMapSuccess: {
192 auto& success = aResult.get_BufferMapSuccess();
193 self->mMapRequest = nullptr;
194 self->SetMapped(success.offset(), success.size(),
195 success.writable());
196 promise->MaybeResolve(0);
197 break;
199 case BufferMapResult::TBufferMapError: {
200 auto& error = aResult.get_BufferMapError();
201 self->RejectMapRequest(promise, error.message());
202 break;
204 default: {
205 MOZ_CRASH("unreachable");
209 [promise](const ipc::ResponseRejectReason&) {
210 promise->MaybeRejectWithAbortError("Internal communication error!");
213 return promise.forget();
216 static void ExternalBufferFreeCallback(void* aContents, void* aUserData) {
217 Unused << aContents;
218 auto shm = static_cast<std::shared_ptr<ipc::WritableSharedMemoryMapping>*>(
219 aUserData);
220 delete shm;
223 void Buffer::GetMappedRange(JSContext* aCx, uint64_t aOffset,
224 const dom::Optional<uint64_t>& aSize,
225 JS::Rooted<JSObject*>* aObject, ErrorResult& aRv) {
226 if (!mMapped) {
227 aRv.ThrowInvalidStateError("Buffer is not mapped");
228 return;
231 const auto checkedOffset = CheckedInt<size_t>(aOffset);
232 const auto checkedSize = aSize.WasPassed()
233 ? CheckedInt<size_t>(aSize.Value())
234 : CheckedInt<size_t>(mSize) - aOffset;
235 const auto checkedMinBufferSize = checkedOffset + checkedSize;
237 if (!checkedOffset.isValid() || !checkedSize.isValid() ||
238 !checkedMinBufferSize.isValid() || aOffset < mMapped->mOffset ||
239 checkedMinBufferSize.value() > mMapped->mOffset + mMapped->mSize) {
240 aRv.ThrowRangeError("Invalid range");
241 return;
244 auto offset = checkedOffset.value();
245 auto size = checkedSize.value();
246 auto span = mShmem->Bytes().Subspan(offset, size);
248 std::shared_ptr<ipc::WritableSharedMemoryMapping>* userData =
249 new std::shared_ptr<ipc::WritableSharedMemoryMapping>(mShmem);
250 UniquePtr<void, JS::BufferContentsDeleter> dataPtr{
251 span.data(), {&ExternalBufferFreeCallback, userData}};
252 JS::Rooted<JSObject*> arrayBuffer(
253 aCx, JS::NewExternalArrayBuffer(aCx, size, std::move(dataPtr)));
254 if (!arrayBuffer) {
255 aRv.NoteJSContextException(aCx);
256 return;
259 aObject->set(arrayBuffer);
260 mMapped->mArrayBuffers.AppendElement(*aObject);
263 void Buffer::UnmapArrayBuffers(JSContext* aCx, ErrorResult& aRv) {
264 MOZ_ASSERT(mMapped);
266 bool detachedArrayBuffers = true;
267 for (const auto& arrayBuffer : mMapped->mArrayBuffers) {
268 JS::Rooted<JSObject*> rooted(aCx, arrayBuffer);
269 if (!JS::DetachArrayBuffer(aCx, rooted)) {
270 detachedArrayBuffers = false;
274 mMapped->mArrayBuffers.Clear();
276 AbortMapRequest();
278 if (NS_WARN_IF(!detachedArrayBuffers)) {
279 aRv.NoteJSContextException(aCx);
280 return;
284 void Buffer::RejectMapRequest(dom::Promise* aPromise, nsACString& message) {
285 if (mMapRequest == aPromise) {
286 mMapRequest = nullptr;
289 aPromise->MaybeRejectWithOperationError(message);
292 void Buffer::AbortMapRequest() {
293 if (mMapRequest) {
294 mMapRequest->MaybeRejectWithAbortError("Buffer unmapped");
296 mMapRequest = nullptr;
299 void Buffer::Unmap(JSContext* aCx, ErrorResult& aRv) {
300 if (!mMapped) {
301 return;
304 UnmapArrayBuffers(aCx, aRv);
306 bool hasMapFlags = mUsage & (dom::GPUBufferUsage_Binding::MAP_WRITE |
307 dom::GPUBufferUsage_Binding::MAP_READ);
309 if (!hasMapFlags) {
310 // We get here if the buffer was mapped at creation without map flags.
311 // It won't be possible to map the buffer again so we can get rid of
312 // our shmem on this side.
313 mShmem = std::make_shared<ipc::WritableSharedMemoryMapping>();
316 if (!GetDevice().IsLost()) {
317 GetDevice().GetBridge()->SendBufferUnmap(GetDevice().mId, mId,
318 mMapped->mWritable);
321 mMapped.reset();
324 void Buffer::Destroy(JSContext* aCx, ErrorResult& aRv) {
325 if (mMapped) {
326 Unmap(aCx, aRv);
329 if (!GetDevice().IsLost()) {
330 GetDevice().GetBridge()->SendBufferDestroy(mId);
332 // TODO: we don't have to implement it right now, but it's used by the
333 // examples
336 dom::GPUBufferMapState Buffer::MapState() const {
337 // Implementation reference:
338 // <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>.
340 if (mMapped) {
341 return dom::GPUBufferMapState::Mapped;
343 if (mMapRequest) {
344 return dom::GPUBufferMapState::Pending;
346 return dom::GPUBufferMapState::Unmapped;
349 } // namespace mozilla::webgpu