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 "mozilla/dom/UnionTypes.h"
12 #include "CommandBuffer.h"
13 #include "CommandEncoder.h"
14 #include "ipc/WebGPUChild.h"
15 #include "mozilla/Casting.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/dom/HTMLCanvasElement.h"
18 #include "mozilla/dom/ImageBitmap.h"
19 #include "mozilla/dom/OffscreenCanvas.h"
20 #include "mozilla/dom/Promise.h"
21 #include "mozilla/dom/WebGLTexelConversions.h"
22 #include "mozilla/dom/WebGLTypes.h"
23 #include "nsLayoutUtils.h"
26 namespace mozilla::webgpu
{
28 GPU_IMPL_CYCLE_COLLECTION(Queue
, mParent
, mBridge
)
29 GPU_IMPL_JS_WRAP(Queue
)
31 Queue::Queue(Device
* const aParent
, WebGPUChild
* aBridge
, RawId aId
)
32 : ChildOf(aParent
), mBridge(aBridge
), mId(aId
) {}
34 Queue::~Queue() { Cleanup(); }
37 const dom::Sequence
<OwningNonNull
<CommandBuffer
>>& aCommandBuffers
) {
38 nsTArray
<RawId
> list(aCommandBuffers
.Length());
39 for (uint32_t i
= 0; i
< aCommandBuffers
.Length(); ++i
) {
40 auto idMaybe
= aCommandBuffers
[i
]->Commit();
42 list
.AppendElement(*idMaybe
);
46 mBridge
->SendQueueSubmit(mId
, mParent
->mId
, list
);
49 already_AddRefed
<dom::Promise
> Queue::OnSubmittedWorkDone(ErrorResult
& aRv
) {
50 RefPtr
<dom::Promise
> promise
= dom::Promise::Create(GetParentObject(), aRv
);
51 if (NS_WARN_IF(aRv
.Failed())) {
54 mBridge
->QueueOnSubmittedWorkDone(mId
, promise
);
56 return promise
.forget();
59 // Get the base address and length of part of a `BufferSource`.
61 // Given `aBufferSource` and an offset `aDataOffset` and optional length
62 // `aSizeOrRemainder` describing the range of its contents we want to see, check
63 // all arguments and set `aDataContents` and `aContentsSize` to a pointer to the
64 // bytes and a length. Report errors in `aRv`.
66 // If `ASizeOrRemainder` was not passed, return a view from the starting offset
67 // to the end of `aBufferSource`.
69 // On success, the returned `aDataContents` is never `nullptr`. If the
70 // `ArrayBuffer` is detached, return a pointer to a dummy buffer and set
71 // `aContentsSize` to zero.
73 // The `aBufferSource` argument is a WebIDL `BufferSource`, which WebGPU methods
74 // use anywhere they accept a block of raw bytes. WebIDL defines `BufferSource`
77 // typedef (ArrayBufferView or ArrayBuffer) BufferSource;
79 // This appears in Gecko code as `dom::ArrayBufferViewOrArrayBuffer`.
80 static void GetBufferSourceDataAndSize(
81 const dom::ArrayBufferViewOrArrayBuffer
& aBufferSource
,
82 uint64_t aDataOffset
, const dom::Optional
<uint64_t>& aSizeOrRemainder
,
83 uint8_t*& aDataContents
, uint64_t& aContentsSize
, const char* aOffsetName
,
85 uint64_t dataSize
= 0;
86 uint8_t* dataContents
= nullptr;
87 if (aBufferSource
.IsArrayBufferView()) {
88 const auto& view
= aBufferSource
.GetAsArrayBufferView();
90 dataSize
= view
.Length();
91 dataContents
= view
.Data();
93 if (aBufferSource
.IsArrayBuffer()) {
94 const auto& ab
= aBufferSource
.GetAsArrayBuffer();
96 dataSize
= ab
.Length();
97 dataContents
= ab
.Data();
100 if (aDataOffset
> dataSize
) {
101 aRv
.ThrowOperationError(
102 nsPrintfCString("%s is greater than data length", aOffsetName
));
106 uint64_t contentsSize
= 0;
107 if (aSizeOrRemainder
.WasPassed()) {
108 contentsSize
= aSizeOrRemainder
.Value();
110 // We already know that aDataOffset <= length, so this cannot underflow.
111 contentsSize
= dataSize
- aDataOffset
;
114 // This could be folded into the if above, but it's nice to make it
115 // obvious that the check always occurs.
116 // We already know that aDataOffset <= length, so this cannot underflow.
117 if (contentsSize
> dataSize
- aDataOffset
) {
118 aRv
.ThrowOperationError(
119 nsPrintfCString("%s + size is greater than data length", aOffsetName
));
124 // Passing `nullptr` as either the source or destination to
125 // `memcpy` is undefined behavior, even when the count is zero:
127 // https://en.cppreference.com/w/cpp/string/byte/memcpy
129 // We can either make callers responsible for checking the pointer
130 // before calling `memcpy`, or we can have it point to a
131 // permanently-live `static` dummy byte, so that the copies are
132 // harmless. The latter seems less error-prone.
133 static uint8_t dummy
;
134 dataContents
= &dummy
;
135 MOZ_RELEASE_ASSERT(contentsSize
== 0);
137 aDataContents
= dataContents
;
138 aContentsSize
= contentsSize
;
141 void Queue::WriteBuffer(const Buffer
& aBuffer
, uint64_t aBufferOffset
,
142 const dom::ArrayBufferViewOrArrayBuffer
& aData
,
143 uint64_t aDataOffset
,
144 const dom::Optional
<uint64_t>& aSize
,
146 uint8_t* dataContents
= nullptr;
147 uint64_t contentsSize
= 0;
148 GetBufferSourceDataAndSize(aData
, aDataOffset
, aSize
, dataContents
,
149 contentsSize
, "dataOffset", aRv
);
154 if (contentsSize
% 4 != 0) {
155 aRv
.ThrowAbortError("Byte size must be a multiple of 4");
160 mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize
);
161 if (alloc
.isNothing()) {
162 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
166 auto handle
= std::move(alloc
.ref().first
);
167 auto mapping
= std::move(alloc
.ref().second
);
169 memcpy(mapping
.Bytes().data(), dataContents
+ aDataOffset
, contentsSize
);
171 ffi::wgpu_queue_write_buffer(aBuffer
.mId
, aBufferOffset
, ToFFI(&bb
));
172 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
173 std::move(handle
))) {
174 MOZ_CRASH("IPC failure");
178 void Queue::WriteTexture(const dom::GPUImageCopyTexture
& aDestination
,
179 const dom::ArrayBufferViewOrArrayBuffer
& aData
,
180 const dom::GPUImageDataLayout
& aDataLayout
,
181 const dom::GPUExtent3D
& aSize
, ErrorResult
& aRv
) {
182 ffi::WGPUImageCopyTexture copyView
= {};
183 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
184 ffi::WGPUImageDataLayout dataLayout
= {};
185 CommandEncoder::ConvertTextureDataLayoutToFFI(aDataLayout
, &dataLayout
);
186 dataLayout
.offset
= 0; // our Shmem has the contents starting from 0.
187 ffi::WGPUExtent3d extent
= {};
188 ConvertExtent3DToFFI(aSize
, &extent
);
190 uint8_t* dataContents
= nullptr;
191 uint64_t contentsSize
= 0;
192 GetBufferSourceDataAndSize(aData
, aDataLayout
.mOffset
,
193 dom::Optional
<uint64_t>(), dataContents
,
194 contentsSize
, "dataLayout.offset", aRv
);
200 aRv
.ThrowAbortError("Input size cannot be zero.");
203 MOZ_ASSERT(dataContents
!= nullptr);
206 mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize
);
207 if (alloc
.isNothing()) {
208 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
212 auto handle
= std::move(alloc
.ref().first
);
213 auto mapping
= std::move(alloc
.ref().second
);
215 memcpy(mapping
.Bytes().data(), dataContents
+ aDataLayout
.mOffset
,
219 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
220 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
221 std::move(handle
))) {
222 MOZ_CRASH("IPC failure");
226 static WebGLTexelFormat
ToWebGLTexelFormat(gfx::SurfaceFormat aFormat
) {
228 case gfx::SurfaceFormat::B8G8R8A8
:
229 case gfx::SurfaceFormat::B8G8R8X8
:
230 return WebGLTexelFormat::BGRA8
;
231 case gfx::SurfaceFormat::R8G8B8A8
:
232 case gfx::SurfaceFormat::R8G8B8X8
:
233 return WebGLTexelFormat::RGBA8
;
235 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
239 static WebGLTexelFormat
ToWebGLTexelFormat(dom::GPUTextureFormat aFormat
) {
240 // TODO: We need support for Rbg10a2unorm as well.
242 case dom::GPUTextureFormat::R8unorm
:
243 return WebGLTexelFormat::R8
;
244 case dom::GPUTextureFormat::R16float
:
245 return WebGLTexelFormat::R16F
;
246 case dom::GPUTextureFormat::R32float
:
247 return WebGLTexelFormat::R32F
;
248 case dom::GPUTextureFormat::Rg8unorm
:
249 return WebGLTexelFormat::RG8
;
250 case dom::GPUTextureFormat::Rg16float
:
251 return WebGLTexelFormat::RG16F
;
252 case dom::GPUTextureFormat::Rg32float
:
253 return WebGLTexelFormat::RG32F
;
254 case dom::GPUTextureFormat::Rgba8unorm
:
255 case dom::GPUTextureFormat::Rgba8unorm_srgb
:
256 return WebGLTexelFormat::RGBA8
;
257 case dom::GPUTextureFormat::Bgra8unorm
:
258 case dom::GPUTextureFormat::Bgra8unorm_srgb
:
259 return WebGLTexelFormat::BGRA8
;
260 case dom::GPUTextureFormat::Rgba16float
:
261 return WebGLTexelFormat::RGBA16F
;
262 case dom::GPUTextureFormat::Rgba32float
:
263 return WebGLTexelFormat::RGBA32F
;
265 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
269 void Queue::CopyExternalImageToTexture(
270 const dom::GPUImageCopyExternalImage
& aSource
,
271 const dom::GPUImageCopyTextureTagged
& aDestination
,
272 const dom::GPUExtent3D
& aCopySize
, ErrorResult
& aRv
) {
273 const auto dstFormat
= ToWebGLTexelFormat(aDestination
.mTexture
->Format());
274 if (dstFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
275 aRv
.ThrowInvalidStateError("Unsupported destination format");
279 const uint32_t surfaceFlags
= nsLayoutUtils::SFE_ALLOW_NON_PREMULT
;
280 SurfaceFromElementResult sfeResult
;
281 switch (aSource
.mSource
.GetType()) {
282 case decltype(aSource
.mSource
)::Type::eImageBitmap
: {
283 const auto& bitmap
= aSource
.mSource
.GetAsImageBitmap();
284 if (bitmap
->IsClosed()) {
285 aRv
.ThrowInvalidStateError("Detached ImageBitmap");
289 sfeResult
= nsLayoutUtils::SurfaceFromImageBitmap(bitmap
, surfaceFlags
);
292 case decltype(aSource
.mSource
)::Type::eHTMLCanvasElement
: {
293 MOZ_ASSERT(NS_IsMainThread());
295 const auto& canvas
= aSource
.mSource
.GetAsHTMLCanvasElement();
296 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
297 aRv
.ThrowInvalidStateError("Zero-sized HTMLCanvasElement");
301 sfeResult
= nsLayoutUtils::SurfaceFromElement(canvas
, surfaceFlags
);
304 case decltype(aSource
.mSource
)::Type::eOffscreenCanvas
: {
305 const auto& canvas
= aSource
.mSource
.GetAsOffscreenCanvas();
306 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
307 aRv
.ThrowInvalidStateError("Zero-sized OffscreenCanvas");
312 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas
, surfaceFlags
);
317 if (!sfeResult
.mCORSUsed
) {
318 nsIGlobalObject
* global
= mParent
->GetOwnerGlobal();
319 nsIPrincipal
* dstPrincipal
= global
? global
->PrincipalOrNull() : nullptr;
320 if (!sfeResult
.mPrincipal
|| !dstPrincipal
||
321 !dstPrincipal
->Subsumes(sfeResult
.mPrincipal
)) {
322 aRv
.ThrowSecurityError("Cross-origin elements require CORS!");
327 if (sfeResult
.mIsWriteOnly
) {
328 aRv
.ThrowSecurityError("Write only source data not supported!");
332 RefPtr
<gfx::SourceSurface
> surface
= sfeResult
.GetSourceSurface();
334 aRv
.ThrowInvalidStateError("No surface available from source");
338 RefPtr
<gfx::DataSourceSurface
> dataSurface
= surface
->GetDataSurface();
340 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
344 bool srcPremultiplied
;
345 switch (sfeResult
.mAlphaType
) {
346 case gfxAlphaType::Premult
:
347 srcPremultiplied
= true;
349 case gfxAlphaType::NonPremult
:
350 srcPremultiplied
= false;
352 case gfxAlphaType::Opaque
:
353 // No (un)premultiplication necessary so match the output.
354 srcPremultiplied
= aDestination
.mPremultipliedAlpha
;
358 const auto surfaceFormat
= dataSurface
->GetFormat();
359 const auto srcFormat
= ToWebGLTexelFormat(surfaceFormat
);
360 if (srcFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
361 gfxCriticalError() << "Unsupported surface format from source "
366 gfx::DataSourceSurface::ScopedMap
map(dataSurface
,
367 gfx::DataSourceSurface::READ
);
368 if (!map
.IsMapped()) {
369 aRv
.ThrowInvalidStateError("Cannot map surface from source");
373 if (!aSource
.mOrigin
.IsGPUOrigin2DDict()) {
374 aRv
.ThrowInvalidStateError("Cannot get origin from source");
378 ffi::WGPUExtent3d extent
= {};
379 ConvertExtent3DToFFI(aCopySize
, &extent
);
380 if (extent
.depth_or_array_layers
> 1) {
381 aRv
.ThrowOperationError("Depth is greater than 1");
387 if (aSource
.mOrigin
.IsRangeEnforcedUnsignedLongSequence()) {
388 const auto& seq
= aSource
.mOrigin
.GetAsRangeEnforcedUnsignedLongSequence();
389 srcOriginX
= seq
.Length() > 0 ? seq
[0] : 0;
390 srcOriginY
= seq
.Length() > 1 ? seq
[1] : 0;
391 } else if (aSource
.mOrigin
.IsGPUOrigin2DDict()) {
392 const auto& dict
= aSource
.mOrigin
.GetAsGPUOrigin2DDict();
393 srcOriginX
= dict
.mX
;
394 srcOriginY
= dict
.mY
;
396 MOZ_CRASH("Unexpected origin type!");
399 const auto checkedMaxWidth
= CheckedInt
<uint32_t>(srcOriginX
) + extent
.width
;
400 const auto checkedMaxHeight
=
401 CheckedInt
<uint32_t>(srcOriginY
) + extent
.height
;
402 if (!checkedMaxWidth
.isValid() || !checkedMaxHeight
.isValid()) {
403 aRv
.ThrowOperationError("Offset and copy size exceed integer bounds");
407 const gfx::IntSize surfaceSize
= dataSurface
->GetSize();
408 const auto surfaceWidth
= AssertedCast
<uint32_t>(surfaceSize
.width
);
409 const auto surfaceHeight
= AssertedCast
<uint32_t>(surfaceSize
.height
);
410 if (surfaceWidth
< checkedMaxWidth
.value() ||
411 surfaceHeight
< checkedMaxHeight
.value()) {
412 aRv
.ThrowOperationError("Offset and copy size exceed surface bounds");
416 const auto dstWidth
= extent
.width
;
417 const auto dstHeight
= extent
.height
;
418 if (dstWidth
== 0 || dstHeight
== 0) {
419 aRv
.ThrowOperationError("Destination size is empty");
423 if (!aDestination
.mTexture
->mBytesPerBlock
) {
424 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
426 aRv
.ThrowInvalidStateError("Invalid destination format");
430 // Note: This assumes bytes per block == bytes per pixel which is the case
431 // here because the spec only allows non-compressed texture formats for the
433 const auto dstStride
= CheckedInt
<uint32_t>(extent
.width
) *
434 aDestination
.mTexture
->mBytesPerBlock
.value();
435 const auto dstByteLength
= dstStride
* extent
.height
;
436 if (!dstStride
.isValid() || !dstByteLength
.isValid()) {
437 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
441 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
442 dstByteLength
.value());
443 if (alloc
.isNothing()) {
444 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
448 auto handle
= std::move(alloc
.ref().first
);
449 auto mapping
= std::move(alloc
.ref().second
);
451 const int32_t pixelSize
= gfx::BytesPerPixel(surfaceFormat
);
452 auto* dstBegin
= mapping
.Bytes().data();
453 const auto* srcBegin
=
454 map
.GetData() + srcOriginX
* pixelSize
+ srcOriginY
* map
.GetStride();
455 const auto srcOriginPos
= gl::OriginPos::TopLeft
;
456 const auto srcStride
= AssertedCast
<uint32_t>(map
.GetStride());
457 const auto dstOriginPos
=
458 aSource
.mFlipY
? gl::OriginPos::BottomLeft
: gl::OriginPos::TopLeft
;
461 auto dstStrideVal
= dstStride
.value();
463 if (!ConvertImage(dstWidth
, dstHeight
, srcBegin
, srcStride
, srcOriginPos
,
464 srcFormat
, srcPremultiplied
, dstBegin
, dstStrideVal
,
465 dstOriginPos
, dstFormat
, aDestination
.mPremultipliedAlpha
,
467 MOZ_ASSERT_UNREACHABLE("ConvertImage failed!");
468 aRv
.ThrowInvalidStateError(
469 nsPrintfCString("Failed to convert source to destination format "
470 "(%i/%i), please file a bug!",
471 (int)srcFormat
, (int)dstFormat
));
475 ffi::WGPUImageDataLayout dataLayout
= {0, &dstStrideVal
, &dstHeight
};
476 ffi::WGPUImageCopyTexture copyView
= {};
477 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
479 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
480 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
481 std::move(handle
))) {
482 MOZ_CRASH("IPC failure");
486 } // namespace mozilla::webgpu