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
->SendQueueSubmit(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 dom::ProcessTypedArraysFixed(aData
, [&](const Span
<const uint8_t>& aData
) {
73 uint64_t length
= aData
.Length();
74 const auto checkedSize
= aSize
.WasPassed()
75 ? CheckedInt
<size_t>(aSize
.Value())
76 : CheckedInt
<size_t>(length
) - aDataOffset
;
77 if (!checkedSize
.isValid()) {
78 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
82 const auto& size
= checkedSize
.value();
83 if (aDataOffset
+ size
> length
) {
84 aRv
.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR
, size
));
89 aRv
.ThrowAbortError("Byte size must be a multiple of 4");
93 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size
);
94 if (alloc
.isNothing()) {
95 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
99 auto handle
= std::move(alloc
.ref().first
);
100 auto mapping
= std::move(alloc
.ref().second
);
102 memcpy(mapping
.Bytes().data(), aData
.Elements() + aDataOffset
, size
);
104 ffi::wgpu_queue_write_buffer(aBuffer
.mId
, aBufferOffset
, ToFFI(&bb
));
105 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
106 std::move(handle
))) {
107 MOZ_CRASH("IPC failure");
112 void Queue::WriteTexture(const dom::GPUImageCopyTexture
& aDestination
,
113 const dom::ArrayBufferViewOrArrayBuffer
& aData
,
114 const dom::GPUImageDataLayout
& aDataLayout
,
115 const dom::GPUExtent3D
& aSize
, ErrorResult
& aRv
) {
116 ffi::WGPUImageCopyTexture copyView
= {};
117 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
118 ffi::WGPUImageDataLayout dataLayout
= {};
119 CommandEncoder::ConvertTextureDataLayoutToFFI(aDataLayout
, &dataLayout
);
120 dataLayout
.offset
= 0; // our Shmem has the contents starting from 0.
121 ffi::WGPUExtent3d extent
= {};
122 ConvertExtent3DToFFI(aSize
, &extent
);
124 dom::ProcessTypedArraysFixed(aData
, [&](const Span
<const uint8_t>& aData
) {
125 if (aData
.IsEmpty()) {
126 aRv
.ThrowAbortError("Input size cannot be zero.");
130 const auto checkedSize
=
131 CheckedInt
<size_t>(aData
.Length()) - aDataLayout
.mOffset
;
132 if (!checkedSize
.isValid()) {
133 aRv
.ThrowAbortError("Offset is higher than the size");
136 const auto size
= checkedSize
.value();
138 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size
);
139 if (alloc
.isNothing()) {
140 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
144 auto handle
= std::move(alloc
.ref().first
);
145 auto mapping
= std::move(alloc
.ref().second
);
147 memcpy(mapping
.Bytes().data(), aData
.Elements() + aDataLayout
.mOffset
,
151 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
152 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
153 std::move(handle
))) {
154 MOZ_CRASH("IPC failure");
159 static WebGLTexelFormat
ToWebGLTexelFormat(gfx::SurfaceFormat aFormat
) {
161 case gfx::SurfaceFormat::B8G8R8A8
:
162 case gfx::SurfaceFormat::B8G8R8X8
:
163 return WebGLTexelFormat::BGRA8
;
164 case gfx::SurfaceFormat::R8G8B8A8
:
165 case gfx::SurfaceFormat::R8G8B8X8
:
166 return WebGLTexelFormat::RGBA8
;
168 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
172 static WebGLTexelFormat
ToWebGLTexelFormat(dom::GPUTextureFormat aFormat
) {
173 // TODO: We need support for Rbg10a2unorm as well.
175 case dom::GPUTextureFormat::R8unorm
:
176 return WebGLTexelFormat::R8
;
177 case dom::GPUTextureFormat::R16float
:
178 return WebGLTexelFormat::R16F
;
179 case dom::GPUTextureFormat::R32float
:
180 return WebGLTexelFormat::R32F
;
181 case dom::GPUTextureFormat::Rg8unorm
:
182 return WebGLTexelFormat::RG8
;
183 case dom::GPUTextureFormat::Rg16float
:
184 return WebGLTexelFormat::RG16F
;
185 case dom::GPUTextureFormat::Rg32float
:
186 return WebGLTexelFormat::RG32F
;
187 case dom::GPUTextureFormat::Rgba8unorm
:
188 case dom::GPUTextureFormat::Rgba8unorm_srgb
:
189 return WebGLTexelFormat::RGBA8
;
190 case dom::GPUTextureFormat::Bgra8unorm
:
191 case dom::GPUTextureFormat::Bgra8unorm_srgb
:
192 return WebGLTexelFormat::BGRA8
;
193 case dom::GPUTextureFormat::Rgba16float
:
194 return WebGLTexelFormat::RGBA16F
;
195 case dom::GPUTextureFormat::Rgba32float
:
196 return WebGLTexelFormat::RGBA32F
;
198 return WebGLTexelFormat::FormatNotSupportingAnyConversion
;
202 void Queue::CopyExternalImageToTexture(
203 const dom::GPUImageCopyExternalImage
& aSource
,
204 const dom::GPUImageCopyTextureTagged
& aDestination
,
205 const dom::GPUExtent3D
& aCopySize
, ErrorResult
& aRv
) {
206 const auto dstFormat
= ToWebGLTexelFormat(aDestination
.mTexture
->Format());
207 if (dstFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
208 aRv
.ThrowInvalidStateError("Unsupported destination format");
212 const uint32_t surfaceFlags
= nsLayoutUtils::SFE_ALLOW_NON_PREMULT
;
213 SurfaceFromElementResult sfeResult
;
214 switch (aSource
.mSource
.GetType()) {
215 case decltype(aSource
.mSource
)::Type::eImageBitmap
: {
216 const auto& bitmap
= aSource
.mSource
.GetAsImageBitmap();
217 if (bitmap
->IsClosed()) {
218 aRv
.ThrowInvalidStateError("Detached ImageBitmap");
222 sfeResult
= nsLayoutUtils::SurfaceFromImageBitmap(bitmap
, surfaceFlags
);
225 case decltype(aSource
.mSource
)::Type::eHTMLCanvasElement
: {
226 MOZ_ASSERT(NS_IsMainThread());
228 const auto& canvas
= aSource
.mSource
.GetAsHTMLCanvasElement();
229 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
230 aRv
.ThrowInvalidStateError("Zero-sized HTMLCanvasElement");
234 sfeResult
= nsLayoutUtils::SurfaceFromElement(canvas
, surfaceFlags
);
237 case decltype(aSource
.mSource
)::Type::eOffscreenCanvas
: {
238 const auto& canvas
= aSource
.mSource
.GetAsOffscreenCanvas();
239 if (canvas
->Width() == 0 || canvas
->Height() == 0) {
240 aRv
.ThrowInvalidStateError("Zero-sized OffscreenCanvas");
245 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas
, surfaceFlags
);
250 if (!sfeResult
.mCORSUsed
) {
251 nsIGlobalObject
* global
= mParent
->GetOwnerGlobal();
252 nsIPrincipal
* dstPrincipal
= global
? global
->PrincipalOrNull() : nullptr;
253 if (!sfeResult
.mPrincipal
|| !dstPrincipal
||
254 !dstPrincipal
->Subsumes(sfeResult
.mPrincipal
)) {
255 aRv
.ThrowSecurityError("Cross-origin elements require CORS!");
260 if (sfeResult
.mIsWriteOnly
) {
261 aRv
.ThrowSecurityError("Write only source data not supported!");
265 RefPtr
<gfx::SourceSurface
> surface
= sfeResult
.GetSourceSurface();
267 aRv
.ThrowInvalidStateError("No surface available from source");
271 RefPtr
<gfx::DataSourceSurface
> dataSurface
= surface
->GetDataSurface();
273 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
277 bool srcPremultiplied
;
278 switch (sfeResult
.mAlphaType
) {
279 case gfxAlphaType::Premult
:
280 srcPremultiplied
= true;
282 case gfxAlphaType::NonPremult
:
283 srcPremultiplied
= false;
285 case gfxAlphaType::Opaque
:
286 // No (un)premultiplication necessary so match the output.
287 srcPremultiplied
= aDestination
.mPremultipliedAlpha
;
291 const auto surfaceFormat
= dataSurface
->GetFormat();
292 const auto srcFormat
= ToWebGLTexelFormat(surfaceFormat
);
293 if (srcFormat
== WebGLTexelFormat::FormatNotSupportingAnyConversion
) {
294 gfxCriticalError() << "Unsupported surface format from source "
299 gfx::DataSourceSurface::ScopedMap
map(dataSurface
,
300 gfx::DataSourceSurface::READ
);
301 if (!map
.IsMapped()) {
302 aRv
.ThrowInvalidStateError("Cannot map surface from source");
306 if (!aSource
.mOrigin
.IsGPUOrigin2DDict()) {
307 aRv
.ThrowInvalidStateError("Cannot get origin from source");
311 ffi::WGPUExtent3d extent
= {};
312 ConvertExtent3DToFFI(aCopySize
, &extent
);
313 if (extent
.depth_or_array_layers
> 1) {
314 aRv
.ThrowOperationError("Depth is greater than 1");
320 if (aSource
.mOrigin
.IsRangeEnforcedUnsignedLongSequence()) {
321 const auto& seq
= aSource
.mOrigin
.GetAsRangeEnforcedUnsignedLongSequence();
322 srcOriginX
= seq
.Length() > 0 ? seq
[0] : 0;
323 srcOriginY
= seq
.Length() > 1 ? seq
[1] : 0;
324 } else if (aSource
.mOrigin
.IsGPUOrigin2DDict()) {
325 const auto& dict
= aSource
.mOrigin
.GetAsGPUOrigin2DDict();
326 srcOriginX
= dict
.mX
;
327 srcOriginY
= dict
.mY
;
329 MOZ_CRASH("Unexpected origin type!");
332 const auto checkedMaxWidth
= CheckedInt
<uint32_t>(srcOriginX
) + extent
.width
;
333 const auto checkedMaxHeight
=
334 CheckedInt
<uint32_t>(srcOriginY
) + extent
.height
;
335 if (!checkedMaxWidth
.isValid() || !checkedMaxHeight
.isValid()) {
336 aRv
.ThrowOperationError("Offset and copy size exceed integer bounds");
340 const gfx::IntSize surfaceSize
= dataSurface
->GetSize();
341 const auto surfaceWidth
= AssertedCast
<uint32_t>(surfaceSize
.width
);
342 const auto surfaceHeight
= AssertedCast
<uint32_t>(surfaceSize
.height
);
343 if (surfaceWidth
< checkedMaxWidth
.value() ||
344 surfaceHeight
< checkedMaxHeight
.value()) {
345 aRv
.ThrowOperationError("Offset and copy size exceed surface bounds");
349 const auto dstWidth
= extent
.width
;
350 const auto dstHeight
= extent
.height
;
351 if (dstWidth
== 0 || dstHeight
== 0) {
352 aRv
.ThrowOperationError("Destination size is empty");
356 if (!aDestination
.mTexture
->mBytesPerBlock
) {
357 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
359 aRv
.ThrowInvalidStateError("Invalid destination format");
363 // Note: This assumes bytes per block == bytes per pixel which is the case
364 // here because the spec only allows non-compressed texture formats for the
366 const auto dstStride
= CheckedInt
<uint32_t>(extent
.width
) *
367 aDestination
.mTexture
->mBytesPerBlock
.value();
368 const auto dstByteLength
= dstStride
* extent
.height
;
369 if (!dstStride
.isValid() || !dstByteLength
.isValid()) {
370 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
374 auto alloc
= mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
375 dstByteLength
.value());
376 if (alloc
.isNothing()) {
377 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
381 auto handle
= std::move(alloc
.ref().first
);
382 auto mapping
= std::move(alloc
.ref().second
);
384 const int32_t pixelSize
= gfx::BytesPerPixel(surfaceFormat
);
385 auto* dstBegin
= mapping
.Bytes().data();
386 const auto* srcBegin
=
387 map
.GetData() + srcOriginX
* pixelSize
+ srcOriginY
* map
.GetStride();
388 const auto srcOriginPos
= gl::OriginPos::TopLeft
;
389 const auto srcStride
= AssertedCast
<uint32_t>(map
.GetStride());
390 const auto dstOriginPos
=
391 aSource
.mFlipY
? gl::OriginPos::BottomLeft
: gl::OriginPos::TopLeft
;
394 auto dstStrideVal
= dstStride
.value();
396 if (!ConvertImage(dstWidth
, dstHeight
, srcBegin
, srcStride
, srcOriginPos
,
397 srcFormat
, srcPremultiplied
, dstBegin
, dstStrideVal
,
398 dstOriginPos
, dstFormat
, aDestination
.mPremultipliedAlpha
,
400 MOZ_ASSERT_UNREACHABLE("ConvertImage failed!");
401 aRv
.ThrowInvalidStateError(
402 nsPrintfCString("Failed to convert source to destination format "
403 "(%i/%i), please file a bug!",
404 (int)srcFormat
, (int)dstFormat
));
408 ffi::WGPUImageDataLayout dataLayout
= {0, &dstStrideVal
, &dstHeight
};
409 ffi::WGPUImageCopyTexture copyView
= {};
410 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination
, ©View
);
412 ffi::wgpu_queue_write_texture(copyView
, dataLayout
, extent
, ToFFI(&bb
));
413 if (!mBridge
->SendQueueWriteAction(mId
, mParent
->mId
, std::move(bb
),
414 std::move(handle
))) {
415 MOZ_CRASH("IPC failure");
419 } // namespace mozilla::webgpu