Queue.cpp (22962B)
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/. */ 5 6 #include "Queue.h" 7 8 #include <algorithm> 9 10 #include "CommandBuffer.h" 11 #include "CommandEncoder.h" 12 #include "ExternalTexture.h" 13 #include "Utility.h" 14 #include "ipc/WebGPUChild.h" 15 #include "mozilla/Casting.h" 16 #include "mozilla/ErrorResult.h" 17 #include "mozilla/dom/BufferSourceBinding.h" 18 #include "mozilla/dom/HTMLCanvasElement.h" 19 #include "mozilla/dom/HTMLImageElement.h" 20 #include "mozilla/dom/ImageBitmap.h" 21 #include "mozilla/dom/OffscreenCanvas.h" 22 #include "mozilla/dom/Promise.h" 23 #include "mozilla/dom/PromiseNativeHandler.h" 24 #include "mozilla/dom/UnionTypes.h" 25 #include "mozilla/dom/WebGLTexelConversions.h" 26 #include "mozilla/dom/WebGLTypes.h" 27 #include "mozilla/dom/WebGPUBinding.h" 28 #include "mozilla/ipc/SharedMemoryHandle.h" 29 #include "mozilla/ipc/SharedMemoryMapping.h" 30 #include "nsLayoutUtils.h" 31 32 namespace mozilla::webgpu { 33 34 GPU_IMPL_CYCLE_COLLECTION(Queue, mParent) 35 GPU_IMPL_JS_WRAP(Queue) 36 37 Queue::Queue(Device* const aParent, RawId aId) 38 : ObjectBase(aParent->GetChild(), aId, ffi::wgpu_client_drop_queue), 39 ChildOf(aParent) {} 40 41 Queue::~Queue() = default; 42 43 struct ExternalTextureWorkDoneHandler : dom::PromiseNativeHandler { 44 NS_DECL_ISUPPORTS 45 46 explicit ExternalTextureWorkDoneHandler( 47 nsTArray<RefPtr<ExternalTexture>>&& aExternalTextures, 48 uint64_t aSubmissionId) 49 : mExternalTextures(std::move(aExternalTextures)), 50 mSubmissionId(aSubmissionId) {} 51 52 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 53 ErrorResult& aRv) override { 54 for (const auto& externalTexture : mExternalTextures) { 55 externalTexture->OnSubmittedWorkDone(mSubmissionId); 56 } 57 } 58 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 59 ErrorResult& aRv) override { 60 MOZ_ASSERT_UNREACHABLE("Work done promise should not be rejected"); 61 } 62 63 private: 64 ~ExternalTextureWorkDoneHandler() = default; 65 // We must hold a strong reference to the external textures to ensure that 66 // they are not released before all work involving them is done. 67 const nsTArray<RefPtr<ExternalTexture>> mExternalTextures; 68 const uint64_t mSubmissionId; 69 }; 70 71 NS_IMPL_ISUPPORTS0(ExternalTextureWorkDoneHandler) 72 73 void Queue::Submit( 74 const dom::Sequence<OwningNonNull<CommandBuffer>>& aCommandBuffers) { 75 nsTArray<RawId> list(aCommandBuffers.Length()); 76 nsTArray<RefPtr<ExternalTexture>> externalTextures; 77 for (uint32_t i = 0; i < aCommandBuffers.Length(); ++i) { 78 auto idMaybe = aCommandBuffers[i]->Commit(); 79 80 // Generate a validation error if any external texture used by any command 81 // buffer is expired. Technically this is a Device timeline step, but since 82 // the external texture's expired state is only set on the content timeline 83 // it is functionally equivalent to check here and raise any error on the 84 // device timeline. A compromised content process could skip this step, but 85 // equally it could skip setting the external texture's expired state even 86 // if this check were performed on the server side. 87 // https://www.w3.org/TR/webgpu/#dom-gpuqueue-submit 88 for (const auto& externalTexture : 89 aCommandBuffers[i]->GetExternalTextures()) { 90 if (externalTexture->IsExpired()) { 91 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), 92 "External texture is expired"); 93 return; 94 } 95 } 96 externalTextures.AppendElements(aCommandBuffers[i]->GetExternalTextures()); 97 98 if (idMaybe) { 99 list.AppendElement(idMaybe); 100 } 101 } 102 103 nsTArray<RawId> externalTextureSourceIds; 104 for (auto& externalTexture : externalTextures) { 105 externalTextureSourceIds.AppendElement(externalTexture->Source()->GetId()); 106 } 107 108 GetChild()->QueueSubmit(GetId(), mParent->GetId(), list, 109 externalTextureSourceIds); 110 111 if (!externalTextures.IsEmpty()) { 112 for (const auto& externalTexture : externalTextures) { 113 externalTexture->OnSubmit(mNextExternalTextureSubmissionIndex); 114 } 115 ErrorResult rv; 116 RefPtr<dom::Promise> promise = OnSubmittedWorkDone(rv); 117 // Without this promise we have no way of knowing when work involving the 118 // external textures is done. This would lead to us holding on to the 119 // external texture's resources indefinitely, which we don't want. 120 // The alternative of releasing the resources immediately is unacceptable 121 // while there is still pending work, so just crash. 122 MOZ_RELEASE_ASSERT(promise); 123 RefPtr<ExternalTextureWorkDoneHandler> handler = 124 new ExternalTextureWorkDoneHandler(std::move(externalTextures), 125 mNextExternalTextureSubmissionIndex); 126 promise->AppendNativeHandler(handler); 127 128 mNextExternalTextureSubmissionIndex++; 129 } 130 } 131 132 already_AddRefed<dom::Promise> Queue::OnSubmittedWorkDone(ErrorResult& aRv) { 133 RefPtr<dom::Promise> promise = dom::Promise::Create(GetParentObject(), aRv); 134 if (NS_WARN_IF(aRv.Failed())) { 135 return nullptr; 136 } 137 138 ffi::wgpu_client_on_submitted_work_done(GetClient(), GetId()); 139 140 GetChild()->mPendingOnSubmittedWorkDonePromises[GetId()].push_back(promise); 141 142 return promise.forget(); 143 } 144 145 void Queue::WriteBuffer( 146 const Buffer& aBuffer, uint64_t aBufferOffset, 147 const dom::MaybeSharedArrayBufferOrMaybeSharedArrayBufferView& aData, 148 uint64_t aDataOffset, const dom::Optional<uint64_t>& aSize, 149 ErrorResult& aRv) { 150 if (!aBuffer.GetId()) { 151 // Invalid buffers are unknown to the parent -- don't try to write 152 // to them. 153 return; 154 } 155 156 size_t elementByteSize = 1; 157 if (aData.IsArrayBufferView()) { 158 auto type = aData.GetAsArrayBufferView().Type(); 159 if (type != JS::Scalar::MaxTypedArrayViewType) { 160 elementByteSize = byteSize(type); 161 } 162 } 163 dom::ProcessTypedArraysFixed(aData, [&, elementByteSize]( 164 const Span<const uint8_t>& aData) { 165 uint64_t byteLength = aData.Length(); 166 167 auto checkedByteOffset = 168 CheckedInt<uint64_t>(aDataOffset) * elementByteSize; 169 if (!checkedByteOffset.isValid()) { 170 aRv.ThrowOperationError("offset x element size overflows"); 171 return; 172 } 173 auto offset = checkedByteOffset.value(); 174 175 size_t size; 176 if (aSize.WasPassed()) { 177 const auto checkedByteSize = 178 CheckedInt<size_t>(aSize.Value()) * elementByteSize; 179 if (!checkedByteSize.isValid()) { 180 aRv.ThrowOperationError("write size x element size overflows"); 181 return; 182 } 183 size = checkedByteSize.value(); 184 } else { 185 const auto checkedByteSize = CheckedInt<size_t>(byteLength) - offset; 186 if (!checkedByteSize.isValid()) { 187 aRv.ThrowOperationError("data byte length - offset underflows"); 188 return; 189 } 190 size = checkedByteSize.value(); 191 } 192 193 auto checkedByteEnd = CheckedInt<uint64_t>(offset) + size; 194 if (!checkedByteEnd.isValid() || checkedByteEnd.value() > byteLength) { 195 aRv.ThrowOperationError( 196 nsPrintfCString("Wrong data size %" PRIuPTR, size)); 197 return; 198 } 199 200 if (size % 4 != 0) { 201 aRv.ThrowOperationError("Byte size must be a multiple of 4"); 202 return; 203 } 204 205 if (size < 1024) { 206 ipc::ByteBuf bb{}; 207 bb.Allocate(size); 208 memcpy(bb.mData, aData.Elements() + offset, size); 209 auto data_buffer_index = GetChild()->QueueDataBuffer(std::move(bb)); 210 ffi::wgpu_queue_write_buffer_inline(GetClient(), mParent->GetId(), 211 GetId(), aBuffer.GetId(), 212 aBufferOffset, data_buffer_index); 213 return; 214 } 215 216 mozilla::ipc::MutableSharedMemoryHandle handle; 217 if (size != 0) { 218 handle = mozilla::ipc::shared_memory::Create(size); 219 auto mapping = handle.Map(); 220 if (!handle || !mapping) { 221 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 222 return; 223 } 224 225 memcpy(mapping.DataAs<uint8_t>(), aData.Elements() + offset, size); 226 } 227 auto shmem_handle_index = GetChild()->QueueShmemHandle(std::move(handle)); 228 ffi::wgpu_queue_write_buffer_via_shmem(GetClient(), mParent->GetId(), 229 GetId(), aBuffer.GetId(), 230 aBufferOffset, shmem_handle_index); 231 }); 232 } 233 234 static CheckedInt<size_t> ComputeApproxSize( 235 const dom::GPUTexelCopyTextureInfo& aDestination, 236 const dom::GPUTexelCopyBufferLayout& aDataLayout, 237 const ffi::WGPUExtent3d& extent, 238 const ffi::WGPUTextureFormatBlockInfo& info) { 239 // The spec's algorithm for [validating linear texture data][vltd] computes 240 // an exact size for the transfer. wgpu implements the algorithm and will 241 // fully validate the operation as described in the spec. 242 // 243 // Here, we just want to avoid copying excessive amounts of data in the case 244 // where the transfer will use only a small portion of the buffer. So we 245 // compute an approximation that will be at least the actual transfer size 246 // for any valid request. Then we copy the smaller of the approximated size 247 // or the remainder of the buffer. 248 // 249 // [vltd]: 250 // https://www.w3.org/TR/webgpu/#abstract-opdef-validating-linear-texture-data 251 252 // VLTD requires that width/height are multiples of the block size. 253 auto widthInBlocks = extent.width / info.width; 254 auto heightInBlocks = extent.height / info.height; 255 auto bytesInLastRow = CheckedInt<size_t>(widthInBlocks) * info.copy_size; 256 257 // VLTD requires bytesPerRow present if heightInBlocks > 1. 258 auto bytesPerRow = CheckedInt<size_t>(aDataLayout.mBytesPerRow.WasPassed() 259 ? aDataLayout.mBytesPerRow.Value() 260 : bytesInLastRow); 261 262 if (extent.depth_or_array_layers > 1) { 263 // VLTD requires rowsPerImage present if layers > 1 264 auto rowsPerImage = aDataLayout.mRowsPerImage.WasPassed() 265 ? aDataLayout.mRowsPerImage.Value() 266 : heightInBlocks; 267 return bytesPerRow * rowsPerImage * extent.depth_or_array_layers; 268 } else { 269 return bytesPerRow * heightInBlocks; 270 } 271 } 272 273 void Queue::WriteTexture( 274 const dom::GPUTexelCopyTextureInfo& aDestination, 275 const dom::MaybeSharedArrayBufferOrMaybeSharedArrayBufferView& aData, 276 const dom::GPUTexelCopyBufferLayout& aDataLayout, 277 const dom::GPUExtent3D& aSize, ErrorResult& aRv) { 278 ffi::WGPUTexelCopyTextureInfo copyView = {}; 279 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, ©View); 280 ffi::WGPUTexelCopyBufferLayout dataLayout = {}; 281 CommandEncoder::ConvertTextureDataLayoutToFFI(aDataLayout, &dataLayout); 282 dataLayout.offset = 0; // our Shmem has the contents starting from 0. 283 ffi::WGPUExtent3d extent = {}; 284 ConvertExtent3DToFFI(aSize, &extent); 285 286 auto format = ConvertTextureFormat(aDestination.mTexture->Format()); 287 auto aspect = ConvertTextureAspect(aDestination.mAspect); 288 ffi::WGPUTextureFormatBlockInfo info = {}; 289 bool valid = ffi::wgpu_texture_format_get_block_info(format, aspect, &info); 290 CheckedInt<size_t> approxSize; 291 if (valid) { 292 approxSize = ComputeApproxSize(aDestination, aDataLayout, extent, info); 293 } else { 294 // This happens when the caller does not indicate a single aspect to 295 // target in a multi-aspect texture. It needs to be validated on the 296 // device timeline, so proceed without an estimated size for now 297 approxSize = CheckedInt<size_t>(SIZE_MAX) + 1; 298 } 299 300 dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) { 301 const auto checkedSize = 302 CheckedInt<size_t>(aData.Length()) - aDataLayout.mOffset; 303 size_t size; 304 if (checkedSize.isValid() && approxSize.isValid()) { 305 size = std::min(checkedSize.value(), approxSize.value()); 306 } else if (checkedSize.isValid()) { 307 size = checkedSize.value(); 308 } else { 309 // CheckedSize is invalid when the caller-provided offset was past the 310 // end of their buffer. Maintain that condition, and fail the operation 311 // on the device timeline. 312 dataLayout.offset = 1; 313 size = 0; 314 } 315 316 mozilla::ipc::MutableSharedMemoryHandle handle; 317 if (size != 0) { 318 handle = mozilla::ipc::shared_memory::Create(size); 319 auto mapping = handle.Map(); 320 if (!handle || !mapping) { 321 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 322 return; 323 } 324 325 memcpy(mapping.DataAs<uint8_t>(), aData.Elements() + aDataLayout.mOffset, 326 size); 327 } else { 328 handle = mozilla::ipc::MutableSharedMemoryHandle(); 329 } 330 331 auto shmem_handle_index = GetChild()->QueueShmemHandle(std::move(handle)); 332 ffi::wgpu_queue_write_texture_via_shmem(GetClient(), mParent->GetId(), 333 GetId(), copyView, dataLayout, 334 extent, shmem_handle_index); 335 }); 336 } 337 338 static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) { 339 switch (aFormat) { 340 case gfx::SurfaceFormat::B8G8R8A8: 341 case gfx::SurfaceFormat::B8G8R8X8: 342 return WebGLTexelFormat::BGRA8; 343 case gfx::SurfaceFormat::R8G8B8A8: 344 case gfx::SurfaceFormat::R8G8B8X8: 345 return WebGLTexelFormat::RGBA8; 346 default: 347 return WebGLTexelFormat::FormatNotSupportingAnyConversion; 348 } 349 } 350 351 static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) { 352 // TODO: We need support for Rbg10a2unorm as well. 353 switch (aFormat) { 354 case dom::GPUTextureFormat::R8unorm: 355 return WebGLTexelFormat::R8; 356 case dom::GPUTextureFormat::R16float: 357 return WebGLTexelFormat::R16F; 358 case dom::GPUTextureFormat::R32float: 359 return WebGLTexelFormat::R32F; 360 case dom::GPUTextureFormat::Rg8unorm: 361 return WebGLTexelFormat::RG8; 362 case dom::GPUTextureFormat::Rg16float: 363 return WebGLTexelFormat::RG16F; 364 case dom::GPUTextureFormat::Rg32float: 365 return WebGLTexelFormat::RG32F; 366 case dom::GPUTextureFormat::Rgba8unorm: 367 case dom::GPUTextureFormat::Rgba8unorm_srgb: 368 return WebGLTexelFormat::RGBA8; 369 case dom::GPUTextureFormat::Bgra8unorm: 370 case dom::GPUTextureFormat::Bgra8unorm_srgb: 371 return WebGLTexelFormat::BGRA8; 372 case dom::GPUTextureFormat::Rgba16float: 373 return WebGLTexelFormat::RGBA16F; 374 case dom::GPUTextureFormat::Rgba32float: 375 return WebGLTexelFormat::RGBA32F; 376 default: 377 return WebGLTexelFormat::FormatNotSupportingAnyConversion; 378 } 379 } 380 381 void Queue::CopyExternalImageToTexture( 382 const dom::GPUCopyExternalImageSourceInfo& aSource, 383 const dom::GPUCopyExternalImageDestInfo& aDestination, 384 const dom::GPUExtent3D& aCopySize, ErrorResult& aRv) { 385 if (aSource.mOrigin.IsRangeEnforcedUnsignedLongSequence()) { 386 auto seq = aSource.mOrigin.GetAsRangeEnforcedUnsignedLongSequence(); 387 if (seq.Length() > 2) { 388 aRv.ThrowTypeError("`origin` must have a sequence size of 2 or less"); 389 return; 390 } 391 } 392 393 const auto dstFormat = ToWebGLTexelFormat(aDestination.mTexture->Format()); 394 if (dstFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) { 395 aRv.ThrowInvalidStateError("Unsupported destination format"); 396 return; 397 } 398 399 const uint32_t surfaceFlags = nsLayoutUtils::SFE_ALLOW_NON_PREMULT; 400 SurfaceFromElementResult sfeResult; 401 switch (aSource.mSource.GetType()) { 402 case decltype(aSource.mSource)::Type::eImageBitmap: { 403 const auto& bitmap = aSource.mSource.GetAsImageBitmap(); 404 if (bitmap->IsClosed()) { 405 aRv.ThrowInvalidStateError("Detached ImageBitmap"); 406 return; 407 } 408 409 sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags); 410 break; 411 } 412 case decltype(aSource.mSource)::Type::eHTMLImageElement: { 413 const auto& image = aSource.mSource.GetAsHTMLImageElement(); 414 if (image->NaturalWidth() == 0 || image->NaturalHeight() == 0) { 415 aRv.ThrowInvalidStateError("Zero-sized HTMLImageElement"); 416 return; 417 } 418 419 sfeResult = nsLayoutUtils::SurfaceFromElement(image, surfaceFlags); 420 break; 421 } 422 case decltype(aSource.mSource)::Type::eHTMLCanvasElement: { 423 MOZ_ASSERT(NS_IsMainThread()); 424 425 const auto& canvas = aSource.mSource.GetAsHTMLCanvasElement(); 426 if (canvas->Width() == 0 || canvas->Height() == 0) { 427 aRv.ThrowInvalidStateError("Zero-sized HTMLCanvasElement"); 428 return; 429 } 430 431 sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags); 432 break; 433 } 434 case decltype(aSource.mSource)::Type::eOffscreenCanvas: { 435 const auto& canvas = aSource.mSource.GetAsOffscreenCanvas(); 436 if (canvas->Width() == 0 || canvas->Height() == 0) { 437 aRv.ThrowInvalidStateError("Zero-sized OffscreenCanvas"); 438 return; 439 } 440 441 sfeResult = 442 nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags); 443 break; 444 } 445 } 446 447 if (!sfeResult.mCORSUsed) { 448 nsIGlobalObject* global = mParent->GetOwnerGlobal(); 449 nsIPrincipal* dstPrincipal = global ? global->PrincipalOrNull() : nullptr; 450 if (!sfeResult.mPrincipal || !dstPrincipal || 451 !dstPrincipal->Subsumes(sfeResult.mPrincipal)) { 452 aRv.ThrowSecurityError("Cross-origin elements require CORS!"); 453 return; 454 } 455 } 456 457 if (sfeResult.mIsWriteOnly) { 458 aRv.ThrowSecurityError("Write only source data not supported!"); 459 return; 460 } 461 462 RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface(); 463 if (!surface) { 464 aRv.ThrowInvalidStateError("No surface available from source"); 465 return; 466 } 467 468 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface(); 469 if (!dataSurface) { 470 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 471 return; 472 } 473 474 bool srcPremultiplied; 475 switch (sfeResult.mAlphaType) { 476 case gfxAlphaType::Premult: 477 srcPremultiplied = true; 478 break; 479 case gfxAlphaType::NonPremult: 480 srcPremultiplied = false; 481 break; 482 case gfxAlphaType::Opaque: 483 // No (un)premultiplication necessary so match the output. 484 srcPremultiplied = aDestination.mPremultipliedAlpha; 485 break; 486 } 487 488 const auto surfaceFormat = dataSurface->GetFormat(); 489 const auto srcFormat = ToWebGLTexelFormat(surfaceFormat); 490 if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) { 491 gfxCriticalError() << "Unsupported surface format from source " 492 << surfaceFormat; 493 MOZ_CRASH(); 494 } 495 496 gfx::DataSourceSurface::ScopedMap map(dataSurface, 497 gfx::DataSourceSurface::READ); 498 if (!map.IsMapped()) { 499 aRv.ThrowInvalidStateError("Cannot map surface from source"); 500 return; 501 } 502 503 ffi::WGPUExtent3d extent = {}; 504 ConvertExtent3DToFFI(aCopySize, &extent); 505 if (extent.depth_or_array_layers > 1) { 506 aRv.ThrowOperationError("Depth is greater than 1"); 507 return; 508 } 509 510 uint32_t srcOriginX; 511 uint32_t srcOriginY; 512 if (aSource.mOrigin.IsRangeEnforcedUnsignedLongSequence()) { 513 const auto& seq = aSource.mOrigin.GetAsRangeEnforcedUnsignedLongSequence(); 514 srcOriginX = seq.Length() > 0 ? seq[0] : 0; 515 srcOriginY = seq.Length() > 1 ? seq[1] : 0; 516 } else if (aSource.mOrigin.IsGPUOrigin2DDict()) { 517 const auto& dict = aSource.mOrigin.GetAsGPUOrigin2DDict(); 518 srcOriginX = dict.mX; 519 srcOriginY = dict.mY; 520 } else { 521 MOZ_CRASH("Unexpected origin type!"); 522 } 523 524 const auto checkedMaxWidth = CheckedInt<uint32_t>(srcOriginX) + extent.width; 525 const auto checkedMaxHeight = 526 CheckedInt<uint32_t>(srcOriginY) + extent.height; 527 if (!checkedMaxWidth.isValid() || !checkedMaxHeight.isValid()) { 528 aRv.ThrowOperationError("Offset and copy size exceed integer bounds"); 529 return; 530 } 531 532 const gfx::IntSize surfaceSize = dataSurface->GetSize(); 533 const auto surfaceWidth = AssertedCast<uint32_t>(surfaceSize.width); 534 const auto surfaceHeight = AssertedCast<uint32_t>(surfaceSize.height); 535 if (surfaceWidth < checkedMaxWidth.value() || 536 surfaceHeight < checkedMaxHeight.value()) { 537 aRv.ThrowOperationError("Offset and copy size exceed surface bounds"); 538 return; 539 } 540 541 const auto dstWidth = extent.width; 542 const auto dstHeight = extent.height; 543 if (dstWidth == 0 || dstHeight == 0) { 544 aRv.ThrowOperationError("Destination size is empty"); 545 return; 546 } 547 548 if (!aDestination.mTexture->mBytesPerBlock) { 549 // TODO(bug 1781071) This should emmit a GPUValidationError on the device 550 // timeline. 551 aRv.ThrowInvalidStateError("Invalid destination format"); 552 return; 553 } 554 555 // Note: This assumes bytes per block == bytes per pixel which is the case 556 // here because the spec only allows non-compressed texture formats for the 557 // destination. 558 const auto dstStride = CheckedInt<uint32_t>(extent.width) * 559 aDestination.mTexture->mBytesPerBlock.value(); 560 const auto dstByteLength = dstStride * extent.height; 561 if (!dstStride.isValid() || !dstByteLength.isValid()) { 562 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 563 return; 564 } 565 566 auto handle = mozilla::ipc::shared_memory::Create(dstByteLength.value()); 567 auto mapping = handle.Map(); 568 if (!handle || !mapping) { 569 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 570 return; 571 } 572 573 const int32_t pixelSize = gfx::BytesPerPixel(surfaceFormat); 574 auto* dstBegin = mapping.DataAs<uint8_t>(); 575 const auto* srcBegin = 576 map.GetData() + srcOriginX * pixelSize + srcOriginY * map.GetStride(); 577 const auto srcOriginPos = gl::OriginPos::TopLeft; 578 const auto srcStride = AssertedCast<uint32_t>(map.GetStride()); 579 const auto dstOriginPos = 580 aSource.mFlipY ? gl::OriginPos::BottomLeft : gl::OriginPos::TopLeft; 581 bool wasTrivial; 582 583 auto dstStrideVal = dstStride.value(); 584 585 if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos, 586 srcFormat, srcPremultiplied, dstBegin, dstStrideVal, 587 dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha, 588 dom::PredefinedColorSpace::Srgb, 589 dom::PredefinedColorSpace::Srgb, &wasTrivial)) { 590 MOZ_ASSERT_UNREACHABLE("ConvertImage failed!"); 591 aRv.ThrowInvalidStateError( 592 nsPrintfCString("Failed to convert source to destination format " 593 "(%i/%i), please file a bug!", 594 (int)srcFormat, (int)dstFormat)); 595 return; 596 } 597 598 ffi::WGPUTexelCopyBufferLayout dataLayout = {0, &dstStrideVal, &dstHeight}; 599 ffi::WGPUTexelCopyTextureInfo copyView = {}; 600 CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, ©View); 601 602 auto shmem_handle_index = GetChild()->QueueShmemHandle(std::move(handle)); 603 ffi::wgpu_queue_write_texture_via_shmem(GetClient(), mParent->GetId(), 604 GetId(), copyView, dataLayout, extent, 605 shmem_handle_index); 606 } 607 608 } // namespace mozilla::webgpu