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
) {
33 MOZ_RELEASE_ASSERT(aId
);
36 Queue::~Queue() { Cleanup(); }
39 const dom::Sequence
<OwningNonNull
<CommandBuffer
>>& aCommandBuffers
) {
40 nsTArray
<RawId
> list(aCommandBuffers
.Length());
41 for (uint32_t i
= 0; i
< aCommandBuffers
.Length(); ++i
) {
42 auto idMaybe
= aCommandBuffers
[i
]->Commit();
44 list
.AppendElement(*idMaybe
);
48 mBridge
->QueueSubmit(mId
, mParent
->mId
, list
);
51 already_AddRefed
<dom::Promise
> Queue::OnSubmittedWorkDone(ErrorResult
& aRv
) {
52 RefPtr
<dom::Promise
> promise
= dom::Promise::Create(GetParentObject(), aRv
);
53 if (NS_WARN_IF(aRv
.Failed())) {
56 mBridge
->QueueOnSubmittedWorkDone(mId
, promise
);
58 return promise
.forget();
61 void Queue::WriteBuffer(const Buffer
& aBuffer
, uint64_t aBufferOffset
,
62 const dom::ArrayBufferViewOrArrayBuffer
& aData
,
64 const dom::Optional
<uint64_t>& aSize
,
67 // Invalid buffers are unknown to the parent -- don't try to write
72 size_t elementByteSize
= 1;
73 if (aData
.IsArrayBufferView()) {
74 auto type
= aData
.GetAsArrayBufferView().Type();
75 if (type
!= JS::Scalar::MaxTypedArrayViewType
) {
76 elementByteSize
= byteSize(type
);
79 dom::ProcessTypedArraysFixed(aData
, [&, elementByteSize
](
80 const Span
<const uint8_t>& aData
) {
81 uint64_t byteLength
= aData
.Length();
83 auto checkedByteOffset
=
84 CheckedInt
<uint64_t>(aDataOffset
) * elementByteSize
;
85 if (!checkedByteOffset
.isValid()) {
86 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
89 auto offset
= checkedByteOffset
.value();
91 const auto checkedByteSize
=
92 aSize
.WasPassed() ? CheckedInt
<size_t>(aSize
.Value()) * elementByteSize
93 : CheckedInt
<size_t>(byteLength
) - offset
;
94 if (!checkedByteSize
.isValid()) {
95 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
98 auto size
= checkedByteSize
.value();
100 auto checkedByteEnd
= CheckedInt
<uint64_t>(offset
) + size
;
101 if (!checkedByteEnd
.isValid() || checkedByteEnd
.value() > byteLength
) {
102 aRv
.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR
, size
));
107 aRv
.ThrowAbortError("Byte size must be a multiple of 4");
111 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size
);
112 if (alloc
.isNothing()) {
113 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
117 auto handle
= std::move(alloc
.ref().first
);
118 auto mapping
= std::move(alloc
.ref().second
);
120 memcpy(mapping
.Bytes().data(), aData
.Elements() + aDataOffset
, size
);
122 ffi::wgpu_queue_write_buffer(aBuffer
.mId
, aBufferOffset
, ToFFI(&bb
));
123 mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
128 void Queue::WriteTexture(const dom::GPUImageCopyTexture
& aDestination
,
129 const dom::ArrayBufferViewOrArrayBuffer
& aData
,
130 const dom::GPUImageDataLayout
& aDataLayout
,
131 const dom::GPUExtent3D
& aSize
, ErrorResult
& aRv
) {
132 ffi::WGPUImageCopyTexture copyView
= {};
133 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
134 ffi::WGPUImageDataLayout dataLayout
= {};
135 CommandEncoder::ConvertTextureDataLayoutToFFI(aDataLayout
, &dataLayout
);
136 dataLayout
.offset
= 0; // our Shmem has the contents starting from 0.
137 ffi::WGPUExtent3d extent
= {};
138 ConvertExtent3DToFFI(aSize
, &extent
);
140 dom::ProcessTypedArraysFixed(aData
, [&](const Span
<const uint8_t>& aData
) {
141 if (aData
.IsEmpty()) {
142 aRv
.ThrowAbortError("Input size cannot be zero.");
146 const auto checkedSize
=
147 CheckedInt
<size_t>(aData
.Length()) - aDataLayout
.mOffset
;
148 if (!checkedSize
.isValid()) {
149 aRv
.ThrowAbortError("Offset is higher than the size");
152 const auto size
= checkedSize
.value();
154 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size
);
155 if (alloc
.isNothing()) {
156 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
160 auto handle
= std::move(alloc
.ref().first
);
161 auto mapping
= std::move(alloc
.ref().second
);
163 memcpy(mapping
.Bytes().data(), aData
.Elements() + aDataLayout
.mOffset
,
167 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
168 mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
173 static WebGLTexelFormat
ToWebGLTexelFormat(gfx::SurfaceFormat aFormat
) {
175 case gfx::SurfaceFormat::B8G8R8A8
:
176 case gfx::SurfaceFormat::B8G8R8X8
:
177 return WebGLTexelFormat::BGRA8
;
178 case gfx::SurfaceFormat::R8G8B8A8
:
179 case gfx::SurfaceFormat::R8G8B8X8
:
180 return WebGLTexelFormat::RGBA8
;
182 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
186 static WebGLTexelFormat
ToWebGLTexelFormat(dom::GPUTextureFormat aFormat
) {
187 // TODO: We need support for Rbg10a2unorm as well.
189 case dom::GPUTextureFormat::R8unorm
:
190 return WebGLTexelFormat::R8
;
191 case dom::GPUTextureFormat::R16float
:
192 return WebGLTexelFormat::R16F
;
193 case dom::GPUTextureFormat::R32float
:
194 return WebGLTexelFormat::R32F
;
195 case dom::GPUTextureFormat::Rg8unorm
:
196 return WebGLTexelFormat::RG8
;
197 case dom::GPUTextureFormat::Rg16float
:
198 return WebGLTexelFormat::RG16F
;
199 case dom::GPUTextureFormat::Rg32float
:
200 return WebGLTexelFormat::RG32F
;
201 case dom::GPUTextureFormat::Rgba8unorm
:
202 case dom::GPUTextureFormat::Rgba8unorm_srgb
:
203 return WebGLTexelFormat::RGBA8
;
204 case dom::GPUTextureFormat::Bgra8unorm
:
205 case dom::GPUTextureFormat::Bgra8unorm_srgb
:
206 return WebGLTexelFormat::BGRA8
;
207 case dom::GPUTextureFormat::Rgba16float
:
208 return WebGLTexelFormat::RGBA16F
;
209 case dom::GPUTextureFormat::Rgba32float
:
210 return WebGLTexelFormat::RGBA32F
;
212 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
216 void Queue::CopyExternalImageToTexture(
217 const dom::GPUImageCopyExternalImage
& aSource
,
218 const dom::GPUImageCopyTextureTagged
& aDestination
,
219 const dom::GPUExtent3D
& aCopySize
, ErrorResult
& aRv
) {
220 const auto dstFormat
= ToWebGLTexelFormat(aDestination
.mTexture
->Format());
221 if (dstFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
222 aRv
.ThrowInvalidStateError("Unsupported destination format");
226 const uint32_t surfaceFlags
= nsLayoutUtils::SFE_ALLOW_NON_PREMULT
;
227 SurfaceFromElementResult sfeResult
;
228 switch (aSource
.mSource
.GetType()) {
229 case decltype(aSource
.mSource
)::Type::eImageBitmap
: {
230 const auto& bitmap
= aSource
.mSource
.GetAsImageBitmap();
231 if (bitmap
->IsClosed()) {
232 aRv
.ThrowInvalidStateError("Detached ImageBitmap");
236 sfeResult
= nsLayoutUtils::SurfaceFromImageBitmap(bitmap
, surfaceFlags
);
239 case decltype(aSource
.mSource
)::Type::eHTMLCanvasElement
: {
240 MOZ_ASSERT(NS_IsMainThread());
242 const auto& canvas
= aSource
.mSource
.GetAsHTMLCanvasElement();
243 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
244 aRv
.ThrowInvalidStateError("Zero-sized HTMLCanvasElement");
248 sfeResult
= nsLayoutUtils::SurfaceFromElement(canvas
, surfaceFlags
);
251 case decltype(aSource
.mSource
)::Type::eOffscreenCanvas
: {
252 const auto& canvas
= aSource
.mSource
.GetAsOffscreenCanvas();
253 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
254 aRv
.ThrowInvalidStateError("Zero-sized OffscreenCanvas");
259 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas
, surfaceFlags
);
264 if (!sfeResult
.mCORSUsed
) {
265 nsIGlobalObject
* global
= mParent
->GetOwnerGlobal();
266 nsIPrincipal
* dstPrincipal
= global
? global
->PrincipalOrNull() : nullptr;
267 if (!sfeResult
.mPrincipal
|| !dstPrincipal
||
268 !dstPrincipal
->Subsumes(sfeResult
.mPrincipal
)) {
269 aRv
.ThrowSecurityError("Cross-origin elements require CORS!");
274 if (sfeResult
.mIsWriteOnly
) {
275 aRv
.ThrowSecurityError("Write only source data not supported!");
279 RefPtr
<gfx::SourceSurface
> surface
= sfeResult
.GetSourceSurface();
281 aRv
.ThrowInvalidStateError("No surface available from source");
285 RefPtr
<gfx::DataSourceSurface
> dataSurface
= surface
->GetDataSurface();
287 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
291 bool srcPremultiplied
;
292 switch (sfeResult
.mAlphaType
) {
293 case gfxAlphaType::Premult
:
294 srcPremultiplied
= true;
296 case gfxAlphaType::NonPremult
:
297 srcPremultiplied
= false;
299 case gfxAlphaType::Opaque
:
300 // No (un)premultiplication necessary so match the output.
301 srcPremultiplied
= aDestination
.mPremultipliedAlpha
;
305 const auto surfaceFormat
= dataSurface
->GetFormat();
306 const auto srcFormat
= ToWebGLTexelFormat(surfaceFormat
);
307 if (srcFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
308 gfxCriticalError() << "Unsupported surface format from source "
313 gfx::DataSourceSurface::ScopedMap
map(dataSurface
,
314 gfx::DataSourceSurface::READ
);
315 if (!map
.IsMapped()) {
316 aRv
.ThrowInvalidStateError("Cannot map surface from source");
320 if (!aSource
.mOrigin
.IsGPUOrigin2DDict()) {
321 aRv
.ThrowInvalidStateError("Cannot get origin from source");
325 ffi::WGPUExtent3d extent
= {};
326 ConvertExtent3DToFFI(aCopySize
, &extent
);
327 if (extent
.depth_or_array_layers
> 1) {
328 aRv
.ThrowOperationError("Depth is greater than 1");
334 if (aSource
.mOrigin
.IsRangeEnforcedUnsignedLongSequence()) {
335 const auto& seq
= aSource
.mOrigin
.GetAsRangeEnforcedUnsignedLongSequence();
336 srcOriginX
= seq
.Length() > 0 ? seq
[0] : 0;
337 srcOriginY
= seq
.Length() > 1 ? seq
[1] : 0;
338 } else if (aSource
.mOrigin
.IsGPUOrigin2DDict()) {
339 const auto& dict
= aSource
.mOrigin
.GetAsGPUOrigin2DDict();
340 srcOriginX
= dict
.mX
;
341 srcOriginY
= dict
.mY
;
343 MOZ_CRASH("Unexpected origin type!");
346 const auto checkedMaxWidth
= CheckedInt
<uint32_t>(srcOriginX
) + extent
.width
;
347 const auto checkedMaxHeight
=
348 CheckedInt
<uint32_t>(srcOriginY
) + extent
.height
;
349 if (!checkedMaxWidth
.isValid() || !checkedMaxHeight
.isValid()) {
350 aRv
.ThrowOperationError("Offset and copy size exceed integer bounds");
354 const gfx::IntSize surfaceSize
= dataSurface
->GetSize();
355 const auto surfaceWidth
= AssertedCast
<uint32_t>(surfaceSize
.width
);
356 const auto surfaceHeight
= AssertedCast
<uint32_t>(surfaceSize
.height
);
357 if (surfaceWidth
< checkedMaxWidth
.value() ||
358 surfaceHeight
< checkedMaxHeight
.value()) {
359 aRv
.ThrowOperationError("Offset and copy size exceed surface bounds");
363 const auto dstWidth
= extent
.width
;
364 const auto dstHeight
= extent
.height
;
365 if (dstWidth
== 0 || dstHeight
== 0) {
366 aRv
.ThrowOperationError("Destination size is empty");
370 if (!aDestination
.mTexture
->mBytesPerBlock
) {
371 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
373 aRv
.ThrowInvalidStateError("Invalid destination format");
377 // Note: This assumes bytes per block == bytes per pixel which is the case
378 // here because the spec only allows non-compressed texture formats for the
380 const auto dstStride
= CheckedInt
<uint32_t>(extent
.width
) *
381 aDestination
.mTexture
->mBytesPerBlock
.value();
382 const auto dstByteLength
= dstStride
* extent
.height
;
383 if (!dstStride
.isValid() || !dstByteLength
.isValid()) {
384 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
388 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
389 dstByteLength
.value());
390 if (alloc
.isNothing()) {
391 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
395 auto handle
= std::move(alloc
.ref().first
);
396 auto mapping
= std::move(alloc
.ref().second
);
398 const int32_t pixelSize
= gfx::BytesPerPixel(surfaceFormat
);
399 auto* dstBegin
= mapping
.Bytes().data();
400 const auto* srcBegin
=
401 map
.GetData() + srcOriginX
* pixelSize
+ srcOriginY
* map
.GetStride();
402 const auto srcOriginPos
= gl::OriginPos::TopLeft
;
403 const auto srcStride
= AssertedCast
<uint32_t>(map
.GetStride());
404 const auto dstOriginPos
=
405 aSource
.mFlipY
? gl::OriginPos::BottomLeft
: gl::OriginPos::TopLeft
;
408 auto dstStrideVal
= dstStride
.value();
410 if (!ConvertImage(dstWidth
, dstHeight
, srcBegin
, srcStride
, srcOriginPos
,
411 srcFormat
, srcPremultiplied
, dstBegin
, dstStrideVal
,
412 dstOriginPos
, dstFormat
, aDestination
.mPremultipliedAlpha
,
414 MOZ_ASSERT_UNREACHABLE("ConvertImage failed!");
415 aRv
.ThrowInvalidStateError(
416 nsPrintfCString("Failed to convert source to destination format "
417 "(%i/%i), please file a bug!",
418 (int)srcFormat
, (int)dstFormat
));
422 ffi::WGPUImageDataLayout dataLayout
= {0, &dstStrideVal
, &dstHeight
};
423 ffi::WGPUImageCopyTexture copyView
= {};
424 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
426 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
427 mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
431 } // namespace mozilla::webgpu