Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / webgpu / Queue.cpp
blobca25b2f29017c406a4f1647da003697edd385b0c
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->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())) {
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 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);
87 return;
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);
96 return;
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));
103 return;
106 if (size % 4 != 0) {
107 aRv.ThrowAbortError("Byte size must be a multiple of 4");
108 return;
111 auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
112 if (alloc.isNothing()) {
113 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
114 return;
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);
121 ipc::ByteBuf bb;
122 ffi::wgpu_queue_write_buffer(aBuffer.mId, aBufferOffset, ToFFI(&bb));
123 mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
124 std::move(handle));
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, &copyView);
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.");
143 return;
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");
150 return;
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);
157 return;
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,
164 size);
166 ipc::ByteBuf bb;
167 ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
168 mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
169 std::move(handle));
173 static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) {
174 switch (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;
181 default:
182 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
186 static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) {
187 // TODO: We need support for Rbg10a2unorm as well.
188 switch (aFormat) {
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;
211 default:
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");
223 return;
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");
233 return;
236 sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags);
237 break;
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");
245 return;
248 sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags);
249 break;
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");
255 return;
258 sfeResult =
259 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags);
260 break;
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!");
270 return;
274 if (sfeResult.mIsWriteOnly) {
275 aRv.ThrowSecurityError("Write only source data not supported!");
276 return;
279 RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface();
280 if (!surface) {
281 aRv.ThrowInvalidStateError("No surface available from source");
282 return;
285 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
286 if (!dataSurface) {
287 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
288 return;
291 bool srcPremultiplied;
292 switch (sfeResult.mAlphaType) {
293 case gfxAlphaType::Premult:
294 srcPremultiplied = true;
295 break;
296 case gfxAlphaType::NonPremult:
297 srcPremultiplied = false;
298 break;
299 case gfxAlphaType::Opaque:
300 // No (un)premultiplication necessary so match the output.
301 srcPremultiplied = aDestination.mPremultipliedAlpha;
302 break;
305 const auto surfaceFormat = dataSurface->GetFormat();
306 const auto srcFormat = ToWebGLTexelFormat(surfaceFormat);
307 if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
308 gfxCriticalError() << "Unsupported surface format from source "
309 << surfaceFormat;
310 MOZ_CRASH();
313 gfx::DataSourceSurface::ScopedMap map(dataSurface,
314 gfx::DataSourceSurface::READ);
315 if (!map.IsMapped()) {
316 aRv.ThrowInvalidStateError("Cannot map surface from source");
317 return;
320 if (!aSource.mOrigin.IsGPUOrigin2DDict()) {
321 aRv.ThrowInvalidStateError("Cannot get origin from source");
322 return;
325 ffi::WGPUExtent3d extent = {};
326 ConvertExtent3DToFFI(aCopySize, &extent);
327 if (extent.depth_or_array_layers > 1) {
328 aRv.ThrowOperationError("Depth is greater than 1");
329 return;
332 uint32_t srcOriginX;
333 uint32_t srcOriginY;
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;
342 } else {
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");
351 return;
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");
360 return;
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");
367 return;
370 if (!aDestination.mTexture->mBytesPerBlock) {
371 // TODO(bug 1781071) This should emmit a GPUValidationError on the device
372 // timeline.
373 aRv.ThrowInvalidStateError("Invalid destination format");
374 return;
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
379 // destination.
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);
385 return;
388 auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(
389 dstByteLength.value());
390 if (alloc.isNothing()) {
391 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
392 return;
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;
406 bool wasTrivial;
408 auto dstStrideVal = dstStride.value();
410 if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos,
411 srcFormat, srcPremultiplied, dstBegin, dstStrideVal,
412 dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha,
413 &wasTrivial)) {
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));
419 return;
422 ffi::WGPUImageDataLayout dataLayout = {0, &dstStrideVal, &dstHeight};
423 ffi::WGPUImageCopyTexture copyView = {};
424 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, &copyView);
425 ipc::ByteBuf bb;
426 ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
427 mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
428 std::move(handle));
431 } // namespace mozilla::webgpu