Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / webgpu / Queue.cpp
blob9fe8f8009338eb548716aebea0c5231186f476cc
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"
8 #include "Queue.h"
10 #include <algorithm>
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"
24 #include "Utility.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(); }
36 void Queue::Submit(
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();
41 if (idMaybe) {
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())) {
52 return nullptr;
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`
75 // as:
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,
84 ErrorResult& aRv) {
85 uint64_t dataSize = 0;
86 uint8_t* dataContents = nullptr;
87 if (aBufferSource.IsArrayBufferView()) {
88 const auto& view = aBufferSource.GetAsArrayBufferView();
89 view.ComputeState();
90 dataSize = view.Length();
91 dataContents = view.Data();
93 if (aBufferSource.IsArrayBuffer()) {
94 const auto& ab = aBufferSource.GetAsArrayBuffer();
95 ab.ComputeState();
96 dataSize = ab.Length();
97 dataContents = ab.Data();
100 if (aDataOffset > dataSize) {
101 aRv.ThrowOperationError(
102 nsPrintfCString("%s is greater than data length", aOffsetName));
103 return;
106 uint64_t contentsSize = 0;
107 if (aSizeOrRemainder.WasPassed()) {
108 contentsSize = aSizeOrRemainder.Value();
109 } else {
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));
120 return;
123 if (!dataContents) {
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,
145 ErrorResult& aRv) {
146 uint8_t* dataContents = nullptr;
147 uint64_t contentsSize = 0;
148 GetBufferSourceDataAndSize(aData, aDataOffset, aSize, dataContents,
149 contentsSize, "dataOffset", aRv);
150 if (aRv.Failed()) {
151 return;
154 if (contentsSize % 4 != 0) {
155 aRv.ThrowAbortError("Byte size must be a multiple of 4");
156 return;
159 auto alloc =
160 mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize);
161 if (alloc.isNothing()) {
162 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
163 return;
166 auto handle = std::move(alloc.ref().first);
167 auto mapping = std::move(alloc.ref().second);
169 memcpy(mapping.Bytes().data(), dataContents + aDataOffset, contentsSize);
170 ipc::ByteBuf bb;
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, &copyView);
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);
195 if (aRv.Failed()) {
196 return;
199 if (!contentsSize) {
200 aRv.ThrowAbortError("Input size cannot be zero.");
201 return;
203 MOZ_ASSERT(dataContents != nullptr);
205 auto alloc =
206 mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize);
207 if (alloc.isNothing()) {
208 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
209 return;
212 auto handle = std::move(alloc.ref().first);
213 auto mapping = std::move(alloc.ref().second);
215 memcpy(mapping.Bytes().data(), dataContents + aDataLayout.mOffset,
216 contentsSize);
218 ipc::ByteBuf bb;
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) {
227 switch (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;
234 default:
235 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
239 static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) {
240 // TODO: We need support for Rbg10a2unorm as well.
241 switch (aFormat) {
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;
264 default:
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");
276 return;
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");
286 return;
289 sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags);
290 break;
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");
298 return;
301 sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags);
302 break;
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");
308 return;
311 sfeResult =
312 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags);
313 break;
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!");
323 return;
327 if (sfeResult.mIsWriteOnly) {
328 aRv.ThrowSecurityError("Write only source data not supported!");
329 return;
332 RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface();
333 if (!surface) {
334 aRv.ThrowInvalidStateError("No surface available from source");
335 return;
338 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
339 if (!dataSurface) {
340 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
341 return;
344 bool srcPremultiplied;
345 switch (sfeResult.mAlphaType) {
346 case gfxAlphaType::Premult:
347 srcPremultiplied = true;
348 break;
349 case gfxAlphaType::NonPremult:
350 srcPremultiplied = false;
351 break;
352 case gfxAlphaType::Opaque:
353 // No (un)premultiplication necessary so match the output.
354 srcPremultiplied = aDestination.mPremultipliedAlpha;
355 break;
358 const auto surfaceFormat = dataSurface->GetFormat();
359 const auto srcFormat = ToWebGLTexelFormat(surfaceFormat);
360 if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
361 gfxCriticalError() << "Unsupported surface format from source "
362 << surfaceFormat;
363 MOZ_CRASH();
366 gfx::DataSourceSurface::ScopedMap map(dataSurface,
367 gfx::DataSourceSurface::READ);
368 if (!map.IsMapped()) {
369 aRv.ThrowInvalidStateError("Cannot map surface from source");
370 return;
373 if (!aSource.mOrigin.IsGPUOrigin2DDict()) {
374 aRv.ThrowInvalidStateError("Cannot get origin from source");
375 return;
378 ffi::WGPUExtent3d extent = {};
379 ConvertExtent3DToFFI(aCopySize, &extent);
380 if (extent.depth_or_array_layers > 1) {
381 aRv.ThrowOperationError("Depth is greater than 1");
382 return;
385 uint32_t srcOriginX;
386 uint32_t srcOriginY;
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;
395 } else {
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");
404 return;
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");
413 return;
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");
420 return;
423 if (!aDestination.mTexture->mBytesPerBlock) {
424 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
425 // timeline.
426 aRv.ThrowInvalidStateError("Invalid destination format");
427 return;
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
432 // destination.
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);
438 return;
441 auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
442 dstByteLength.value());
443 if (alloc.isNothing()) {
444 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
445 return;
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;
459 bool wasTrivial;
461 auto dstStrideVal = dstStride.value();
463 if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos,
464 srcFormat, srcPremultiplied, dstBegin, dstStrideVal,
465 dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha,
466 &wasTrivial)) {
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));
472 return;
475 ffi::WGPUImageDataLayout dataLayout = {0, &dstStrideVal, &dstHeight};
476 ffi::WGPUImageCopyTexture copyView = {};
477 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, &copyView);
478 ipc::ByteBuf bb;
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