no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / webgpu / Queue.cpp
blobe7c5c0bc0151d840abc7dd7117167a047a127588
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) {
33 MOZ_RELEASE_ASSERT(aId);
36 Queue::~Queue() { Cleanup(); }
38 void Queue::Submit(
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();
43 if (idMaybe) {
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())) {
54 return nullptr;
56 mBridge->QueueOnSubmittedWorkDone(mId, promise);
58 return promise.forget();
61 void Queue::WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset,
62 const dom::ArrayBufferViewOrArrayBuffer& aData,
63 uint64_t aDataOffset,
64 const dom::Optional<uint64_t>& aSize,
65 ErrorResult& aRv) {
66 if (!aBuffer.mId) {
67 // Invalid buffers are unknown to the parent -- don't try to write
68 // to them.
69 return;
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);
79 return;
82 const auto& size = checkedSize.value();
83 if (aDataOffset + size > length) {
84 aRv.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR, size));
85 return;
88 if (size % 4 != 0) {
89 aRv.ThrowAbortError("Byte size must be a multiple of 4");
90 return;
93 auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
94 if (alloc.isNothing()) {
95 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
96 return;
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);
103 ipc::ByteBuf bb;
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, &copyView);
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.");
127 return;
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");
134 return;
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);
141 return;
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,
148 size);
150 ipc::ByteBuf bb;
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) {
160 switch (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;
167 default:
168 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
172 static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) {
173 // TODO: We need support for Rbg10a2unorm as well.
174 switch (aFormat) {
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;
197 default:
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");
209 return;
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");
219 return;
222 sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags);
223 break;
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");
231 return;
234 sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags);
235 break;
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");
241 return;
244 sfeResult =
245 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags);
246 break;
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!");
256 return;
260 if (sfeResult.mIsWriteOnly) {
261 aRv.ThrowSecurityError("Write only source data not supported!");
262 return;
265 RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface();
266 if (!surface) {
267 aRv.ThrowInvalidStateError("No surface available from source");
268 return;
271 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
272 if (!dataSurface) {
273 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
274 return;
277 bool srcPremultiplied;
278 switch (sfeResult.mAlphaType) {
279 case gfxAlphaType::Premult:
280 srcPremultiplied = true;
281 break;
282 case gfxAlphaType::NonPremult:
283 srcPremultiplied = false;
284 break;
285 case gfxAlphaType::Opaque:
286 // No (un)premultiplication necessary so match the output.
287 srcPremultiplied = aDestination.mPremultipliedAlpha;
288 break;
291 const auto surfaceFormat = dataSurface->GetFormat();
292 const auto srcFormat = ToWebGLTexelFormat(surfaceFormat);
293 if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
294 gfxCriticalError() << "Unsupported surface format from source "
295 << surfaceFormat;
296 MOZ_CRASH();
299 gfx::DataSourceSurface::ScopedMap map(dataSurface,
300 gfx::DataSourceSurface::READ);
301 if (!map.IsMapped()) {
302 aRv.ThrowInvalidStateError("Cannot map surface from source");
303 return;
306 if (!aSource.mOrigin.IsGPUOrigin2DDict()) {
307 aRv.ThrowInvalidStateError("Cannot get origin from source");
308 return;
311 ffi::WGPUExtent3d extent = {};
312 ConvertExtent3DToFFI(aCopySize, &extent);
313 if (extent.depth_or_array_layers > 1) {
314 aRv.ThrowOperationError("Depth is greater than 1");
315 return;
318 uint32_t srcOriginX;
319 uint32_t srcOriginY;
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;
328 } else {
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");
337 return;
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");
346 return;
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");
353 return;
356 if (!aDestination.mTexture->mBytesPerBlock) {
357 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
358 // timeline.
359 aRv.ThrowInvalidStateError("Invalid destination format");
360 return;
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
365 // destination.
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);
371 return;
374 auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
375 dstByteLength.value());
376 if (alloc.isNothing()) {
377 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
378 return;
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;
392 bool wasTrivial;
394 auto dstStrideVal = dstStride.value();
396 if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos,
397 srcFormat, srcPremultiplied, dstBegin, dstStrideVal,
398 dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha,
399 &wasTrivial)) {
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));
405 return;
408 ffi::WGPUImageDataLayout dataLayout = {0, &dstStrideVal, &dstHeight};
409 ffi::WGPUImageCopyTexture copyView = {};
410 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, &copyView);
411 ipc::ByteBuf bb;
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