tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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, &copyView);
    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, &copyView);
    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