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"
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"
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
)
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
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);
48 std::make_shared
<ipc::WritableSharedMemoryMapping
>(std::move(aShmem
));
54 mozilla::DropJSObjects(this);
57 already_AddRefed
<Buffer
> Buffer::Create(Device
* aDevice
, RawId aDeviceId
,
58 const dom::GPUBufferDescriptor
& aDesc
,
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");
79 size_t size
= checked
.value();
81 auto maybeShmem
= ipc::UnsafeSharedMemoryHandle::CreateAndMap(size
);
83 if (maybeShmem
.isNothing()) {
85 nsPrintfCString("Unable to allocate shmem of size %" PRIuPTR
, size
));
89 handle
= std::move(maybeShmem
.ref().first
);
90 mapping
= std::move(maybeShmem
.ref().second
);
92 MOZ_RELEASE_ASSERT(mapping
.Size() >= size
);
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() {
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
);
126 if (mValid
&& !GetDevice().IsLost()) {
127 GetDevice().GetBridge()->SendBufferDrop(mId
);
132 void Buffer::SetMapped(BufferAddress aOffset
, BufferAddress aSize
,
134 MOZ_ASSERT(!mMapped
);
135 MOZ_RELEASE_ASSERT(aOffset
<= mSize
);
136 MOZ_RELEASE_ASSERT(aSize
<= mSize
- aOffset
);
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
,
147 RefPtr
<dom::Promise
> promise
= dom::Promise::Create(GetParentObject(), aRv
);
148 if (NS_WARN_IF(aRv
.Failed())) {
152 if (GetDevice().IsLost()) {
153 promise
->MaybeRejectWithOperationError("Device Lost");
154 return promise
.forget();
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
;
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
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
) {
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(),
196 promise
->MaybeResolve(0);
199 case BufferMapResult::TBufferMapError
: {
200 auto& error
= aResult
.get_BufferMapError();
201 self
->RejectMapRequest(promise
, error
.message());
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
) {
218 auto shm
= static_cast<std::shared_ptr
<ipc::WritableSharedMemoryMapping
>*>(
223 void Buffer::GetMappedRange(JSContext
* aCx
, uint64_t aOffset
,
224 const dom::Optional
<uint64_t>& aSize
,
225 JS::Rooted
<JSObject
*>* aObject
, ErrorResult
& aRv
) {
227 aRv
.ThrowInvalidStateError("Buffer is not mapped");
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");
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
)));
255 aRv
.NoteJSContextException(aCx
);
259 aObject
->set(arrayBuffer
);
260 mMapped
->mArrayBuffers
.AppendElement(*aObject
);
263 void Buffer::UnmapArrayBuffers(JSContext
* aCx
, ErrorResult
& aRv
) {
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();
278 if (NS_WARN_IF(!detachedArrayBuffers
)) {
279 aRv
.NoteJSContextException(aCx
);
284 void Buffer::RejectMapRequest(dom::Promise
* aPromise
, nsACString
& message
) {
285 if (mMapRequest
== aPromise
) {
286 mMapRequest
= nullptr;
289 aPromise
->MaybeRejectWithOperationError(message
);
292 void Buffer::AbortMapRequest() {
294 mMapRequest
->MaybeRejectWithAbortError("Buffer unmapped");
296 mMapRequest
= nullptr;
299 void Buffer::Unmap(JSContext
* aCx
, ErrorResult
& aRv
) {
304 UnmapArrayBuffers(aCx
, aRv
);
306 bool hasMapFlags
= mUsage
& (dom::GPUBufferUsage_Binding::MAP_WRITE
|
307 dom::GPUBufferUsage_Binding::MAP_READ
);
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
,
324 void Buffer::Destroy(JSContext
* aCx
, ErrorResult
& 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
336 dom::GPUBufferMapState
Buffer::MapState() const {
337 // Implementation reference:
338 // <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>.
341 return dom::GPUBufferMapState::Mapped
;
344 return dom::GPUBufferMapState::Pending
;
346 return dom::GPUBufferMapState::Unmapped
;
349 } // namespace mozilla::webgpu