tor-browser

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

WebGLTextureUpload.cpp (63516B)


      1 /* -*- Mode: C++; tab-width: 20; 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 "WebGLTextureUpload.h"
      7 
      8 #include <algorithm>
      9 
     10 #include "CanvasUtils.h"
     11 #include "ClientWebGLContext.h"
     12 #include "GLBlitHelper.h"
     13 #include "GLContext.h"
     14 #include "ScopedGLHelpers.h"
     15 #include "TexUnpackBlob.h"
     16 #include "WebGLBuffer.h"
     17 #include "WebGLContext.h"
     18 #include "WebGLContextUtils.h"
     19 #include "WebGLFormats.h"
     20 #include "WebGLFramebuffer.h"
     21 #include "WebGLTexelConversions.h"
     22 #include "WebGLTexture.h"
     23 #include "mozilla/MathAlgorithms.h"
     24 #include "mozilla/ScopeExit.h"
     25 #include "mozilla/StaticPrefs_webgl.h"
     26 #include "mozilla/dom/HTMLCanvasElement.h"
     27 #include "mozilla/dom/HTMLVideoElement.h"
     28 #include "mozilla/dom/ImageBitmap.h"
     29 #include "mozilla/dom/ImageData.h"
     30 #include "mozilla/dom/OffscreenCanvas.h"
     31 #include "mozilla/gfx/2D.h"
     32 #include "mozilla/gfx/Logging.h"
     33 #include "mozilla/layers/SharedSurfacesChild.h"
     34 #include "nsLayoutUtils.h"
     35 
     36 namespace mozilla {
     37 namespace webgl {
     38 
     39 // The canvas spec says that drawImage should draw the first frame of
     40 // animated images. The webgl spec doesn't mention the issue, so we do the
     41 // same as drawImage.
     42 static constexpr uint32_t kDefaultSurfaceFromElementFlags =
     43    nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
     44    nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
     45    nsLayoutUtils::SFE_EXACT_SIZE_SURFACE |
     46    nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
     47 
     48 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size,
     49                                         const dom::ImageBitmap& imageBitmap,
     50                                         ErrorResult* const out_rv) {
     51  if (imageBitmap.IsWriteOnly()) {
     52    out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
     53    return {};
     54  }
     55 
     56  const auto cloneData = imageBitmap.ToCloneData();
     57  if (!cloneData) {
     58    return {};
     59  }
     60 
     61  const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
     62  if (NS_WARN_IF(!surf)) {
     63    return {};
     64  }
     65 
     66  const auto imageSize = *uvec2::FromSize(surf->GetSize());
     67  if (!size) {
     68    size.emplace(imageSize.x, imageSize.y, 1);
     69  }
     70 
     71  // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
     72  Maybe<layers::SurfaceDescriptor> sd;
     73  layers::SharedSurfacesChild::Share(surf, sd);
     74 
     75  // WhatWG "HTML Living Standard" (30 October 2015):
     76  // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
     77  // non-premultiplied alpha values."
     78  return Some(TexUnpackBlobDesc{target,
     79                                size.value(),
     80                                cloneData->mAlphaType,
     81                                {},
     82                                {},
     83                                Some(imageSize),
     84                                nullptr,
     85                                sd,
     86                                surf,
     87                                {},
     88                                false});
     89 }
     90 
     91 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
     92  const auto sdType = sd.type();
     93  if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
     94    return sd;
     95  }
     96  const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
     97  const auto& sdvType = sdv.type();
     98  if (sdvType !=
     99      layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
    100    return sd;
    101  }
    102 
    103  const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
    104  const auto& subdesc = sdrd.subdesc();
    105  const auto& subdescType = subdesc.type();
    106  switch (subdescType) {
    107    case layers::RemoteDecoderVideoSubDescriptor::T__None:
    108    case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
    109      return sd;
    110 
    111    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
    112      return subdesc.get_SurfaceDescriptorD3D10();
    113    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
    114      return subdesc.get_SurfaceDescriptorDXGIYCbCr();
    115    case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
    116      return subdesc.get_SurfaceDescriptorDMABuf();
    117    case layers::RemoteDecoderVideoSubDescriptor::
    118        TSurfaceDescriptorMacIOSurface:
    119      return subdesc.get_SurfaceDescriptorMacIOSurface();
    120    case layers::RemoteDecoderVideoSubDescriptor::
    121        TSurfaceDescriptorDcompSurface:
    122      return subdesc.get_SurfaceDescriptorDcompSurface();
    123  }
    124  MOZ_CRASH("unreachable");
    125 }
    126 
    127 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
    128    const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
    129    const dom::OffscreenCanvas& canvas, ErrorResult* const out_error) {
    130  if (canvas.IsWriteOnly()) {
    131    webgl.EnqueueWarning(
    132        "OffscreenCanvas is write-only, thus cannot be uploaded.");
    133    out_error->ThrowSecurityError(
    134        "OffscreenCanvas is write-only, thus cannot be uploaded.");
    135    return {};
    136  }
    137 
    138  auto sfer = nsLayoutUtils::SurfaceFromOffscreenCanvas(
    139      const_cast<dom::OffscreenCanvas*>(&canvas),
    140      kDefaultSurfaceFromElementFlags);
    141  return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
    142 }
    143 
    144 Maybe<webgl::TexUnpackBlobDesc> FromVideoFrame(
    145    const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
    146    const dom::VideoFrame& videoFrame, ErrorResult* const out_error) {
    147  auto sfer = nsLayoutUtils::SurfaceFromVideoFrame(
    148      const_cast<dom::VideoFrame*>(&videoFrame),
    149      kDefaultSurfaceFromElementFlags);
    150  return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
    151 }
    152 
    153 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
    154                                            const GLenum target,
    155                                            Maybe<uvec3> size,
    156                                            const dom::Element& elem,
    157                                            ErrorResult* const out_error) {
    158  if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
    159    const dom::HTMLCanvasElement* srcCanvas =
    160        static_cast<const dom::HTMLCanvasElement*>(&elem);
    161    if (srcCanvas->IsWriteOnly()) {
    162      out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
    163      return {};
    164    }
    165  }
    166 
    167  uint32_t flags = kDefaultSurfaceFromElementFlags;
    168  const auto& unpacking = webgl.State().mPixelUnpackState;
    169  if (unpacking.colorspaceConversion == LOCAL_GL_NONE) {
    170    flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
    171  }
    172 
    173  RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr;  // Don't care for now.
    174  auto sfer = nsLayoutUtils::SurfaceFromElement(
    175      const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
    176  return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
    177 }
    178 
    179 Maybe<webgl::TexUnpackBlobDesc> FromSurfaceFromElementResult(
    180    const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
    181    SurfaceFromElementResult& sfer, ErrorResult* const out_error) {
    182  uvec2 elemSize;
    183 
    184  const auto& layersImage = sfer.mLayersImage;
    185  Maybe<layers::SurfaceDescriptor> sd;
    186  if (layersImage) {
    187    elemSize = *uvec2::FromSize(layersImage->GetSize());
    188 
    189    sd = layersImage->GetDesc();
    190    if (sd) {
    191      sd = Some(Flatten(*sd));
    192    }
    193    if (!sd) {
    194      NS_WARNING("No SurfaceDescriptor for layers::Image!");
    195    }
    196  }
    197 
    198  RefPtr<gfx::SourceSurface> sourceSurf;
    199  if (!sd && sfer.GetSourceSurface()) {
    200    const auto surf = sfer.GetSourceSurface();
    201    elemSize = *uvec2::FromSize(surf->GetSize());
    202    if (surf->GetType() == gfx::SurfaceType::RECORDING) {
    203      // If we find a recording surface, then try to create a descriptor for it
    204      // to avoid transferring data for it. The underlying Canvas2D commands
    205      // have most likely not been processed yet, so trying to access the data
    206      // here would cause cross-process synchronization. The descriptor avoids
    207      // having to synchronize and then read back Canvas2D data across a process
    208      // gap. If CanvasSurface descriptors are unsupported, then the underlying
    209      // source surface will be converted to data for transferring later.
    210      layers::SurfaceDescriptor desc;
    211      if (surf->GetSurfaceDescriptor(desc)) {
    212        sd = Some(desc);
    213        sourceSurf = surf;
    214      }
    215    }
    216    if (!sd) {
    217      // WARNING: OSX can lose our MakeCurrent here.
    218      sourceSurf = surf->GetDataSurface();
    219    }
    220  }
    221 
    222  if (!sd) {
    223    // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
    224    layers::SharedSurfacesChild::Share(sourceSurf, sd);
    225  }
    226 
    227  //////
    228 
    229  if (!size) {
    230    size.emplace(elemSize.x, elemSize.y, 1);
    231  }
    232 
    233  ////
    234 
    235  if (!sd && !sourceSurf) {
    236    webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
    237    if (!size) {
    238      size.emplace(0, 0, 1);
    239    }
    240    return Some(
    241        TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
    242  }
    243 
    244  //////
    245 
    246  // While it's counter-intuitive, the shape of the SFEResult API means that we
    247  // should try to pull out a surface first, and then, if we do pull out a
    248  // surface, check CORS/write-only/etc..
    249 
    250  if (!sfer.mCORSUsed) {
    251    auto& srcPrincipal = sfer.mPrincipal;
    252    nsIPrincipal* dstPrincipal = webgl.PrincipalOrNull();
    253    if (!dstPrincipal || !dstPrincipal->Subsumes(srcPrincipal)) {
    254      webgl.EnqueueWarning("Cross-origin elements require CORS.");
    255      out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
    256      return {};
    257    }
    258  }
    259 
    260  if (sfer.mIsWriteOnly) {
    261    // mIsWriteOnly defaults to true, and so will be true even if SFE merely
    262    // failed. Thus we must test mIsWriteOnly after successfully retrieving an
    263    // Image or SourceSurface.
    264    webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
    265    out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
    266    return {};
    267  }
    268 
    269  //////
    270  // Ok, we're good!
    271 
    272  return Some(TexUnpackBlobDesc{target,
    273                                size.value(),
    274                                sfer.mAlphaType,
    275                                {},
    276                                {},
    277                                Some(elemSize),
    278                                layersImage,
    279                                sd,
    280                                sourceSurf});
    281 }
    282 
    283 }  // namespace webgl
    284 
    285 //////////////////////////////////////////////////////////////////////////////////////////
    286 //////////////////////////////////////////////////////////////////////////////////////////
    287 
    288 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
    289                             TexImageTarget target, uint32_t level,
    290                             webgl::ImageInfo** const out_imageInfo) {
    291  // Check level
    292  if (level >= WebGLTexture::kMaxLevelCount) {
    293    webgl->ErrorInvalidValue("`level` is too large.");
    294    return false;
    295  }
    296 
    297  auto& imageInfo = texture->ImageInfoAt(target, level);
    298  *out_imageInfo = &imageInfo;
    299  return true;
    300 }
    301 
    302 // For *TexImage*
    303 bool WebGLTexture::ValidateTexImageSpecification(
    304    TexImageTarget target, uint32_t level, const uvec3& size,
    305    webgl::ImageInfo** const out_imageInfo) {
    306  if (mImmutable) {
    307    mContext->ErrorInvalidOperation("Specified texture is immutable.");
    308    return false;
    309  }
    310 
    311  // Do this early to validate `level`.
    312  webgl::ImageInfo* imageInfo;
    313  if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
    314    return false;
    315 
    316  if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
    317    mContext->ErrorInvalidValue("Cube map images must be square.");
    318    return false;
    319  }
    320 
    321  /* GLES 3.0.4, p133-134:
    322   * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
    323   * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
    324   * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
    325   * not* result in an INVALID_VALUE, or possibly GL_OOM.
    326   *
    327   * However, we have needed to set our maximums lower in the past to prevent
    328   * resource corruption. Therefore we have limits.maxTex2dSize, which is
    329   * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
    330   *
    331   * Note that limits.maxTex2dSize must be >= than the advertized
    332   * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
    333   * limits.maxTex2dSize.
    334   */
    335 
    336  uint32_t maxWidthHeight = 0;
    337  uint32_t maxDepth = 0;
    338  uint32_t maxLevel = 0;
    339 
    340  const auto& limits = mContext->Limits();
    341  MOZ_ASSERT(level <= 31);
    342  switch (target.get()) {
    343    case LOCAL_GL_TEXTURE_2D:
    344      maxWidthHeight = limits.maxTex2dSize >> level;
    345      maxDepth = 1;
    346      maxLevel = CeilingLog2(limits.maxTex2dSize);
    347      break;
    348 
    349    case LOCAL_GL_TEXTURE_3D:
    350      maxWidthHeight = limits.maxTex3dSize >> level;
    351      maxDepth = maxWidthHeight;
    352      maxLevel = CeilingLog2(limits.maxTex3dSize);
    353      break;
    354 
    355    case LOCAL_GL_TEXTURE_2D_ARRAY:
    356      maxWidthHeight = limits.maxTex2dSize >> level;
    357      // "The maximum number of layers for two-dimensional array textures
    358      // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
    359      maxDepth = limits.maxTexArrayLayers;
    360      maxLevel = CeilingLog2(limits.maxTex2dSize);
    361      break;
    362 
    363    default:  // cube maps
    364      MOZ_ASSERT(IsCubeMap());
    365      maxWidthHeight = limits.maxTexCubeSize >> level;
    366      maxDepth = 1;
    367      maxLevel = CeilingLog2(limits.maxTexCubeSize);
    368      break;
    369  }
    370 
    371  if (level > maxLevel) {
    372    mContext->ErrorInvalidValue("Requested level is not supported for target.");
    373    return false;
    374  }
    375 
    376  if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
    377    mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
    378    return false;
    379  }
    380 
    381  {
    382    /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
    383     *   "If level is greater than zero, and either width or
    384     *   height is not a power-of-two, the error INVALID_VALUE is
    385     *   generated."
    386     *
    387     * This restriction does not apply to GL ES Version 3.0+.
    388     */
    389    bool requirePOT = (!mContext->IsWebGL2() && level != 0);
    390 
    391    if (requirePOT) {
    392      if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
    393        mContext->ErrorInvalidValue(
    394            "For level > 0, width and height must be"
    395            " powers of two.");
    396        return false;
    397      }
    398    }
    399  }
    400 
    401  *out_imageInfo = imageInfo;
    402  return true;
    403 }
    404 
    405 // For *TexSubImage*
    406 bool WebGLTexture::ValidateTexImageSelection(
    407    TexImageTarget target, uint32_t level, const uvec3& offset,
    408    const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
    409  webgl::ImageInfo* imageInfo;
    410  if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
    411    return false;
    412 
    413  if (!imageInfo->IsDefined()) {
    414    mContext->ErrorInvalidOperation(
    415        "The specified TexImage has not yet been"
    416        " specified.");
    417    return false;
    418  }
    419 
    420  const auto totalX = CheckedUint32(offset.x) + size.x;
    421  const auto totalY = CheckedUint32(offset.y) + size.y;
    422  const auto totalZ = CheckedUint32(offset.z) + size.z;
    423 
    424  if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
    425      !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
    426      !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
    427    mContext->ErrorInvalidValue(
    428        "Offset+size must be <= the size of the existing"
    429        " specified image.");
    430    return false;
    431  }
    432 
    433  *out_imageInfo = imageInfo;
    434  return true;
    435 }
    436 
    437 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
    438                                        const webgl::FormatInfo* format,
    439                                        size_t dataSize) {
    440  auto compression = format->compression;
    441 
    442  auto bytesPerBlock = compression->bytesPerBlock;
    443  auto blockWidth = compression->blockWidth;
    444  auto blockHeight = compression->blockHeight;
    445 
    446  auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
    447  auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
    448  if (size.x % blockWidth) widthInBlocks += 1;
    449  if (size.y % blockHeight) heightInBlocks += 1;
    450 
    451  const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
    452  const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
    453  const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
    454 
    455  if (!bytesNeeded.isValid()) {
    456    webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
    457    return false;
    458  }
    459 
    460  if (dataSize != bytesNeeded.value()) {
    461    webgl->ErrorInvalidValue(
    462        "Provided buffer's size must match expected size."
    463        " (needs %u, has %zu)",
    464        bytesNeeded.value(), dataSize);
    465    return false;
    466  }
    467 
    468  return true;
    469 }
    470 
    471 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
    472                                           const webgl::FormatInfo* dstFormat) {
    473  // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
    474  // framebuffer/destination texture base internal format combinations."
    475 
    476  switch (srcFormat->unsizedFormat) {
    477    case webgl::UnsizedFormat::RGBA:
    478      switch (dstFormat->unsizedFormat) {
    479        case webgl::UnsizedFormat::A:
    480        case webgl::UnsizedFormat::L:
    481        case webgl::UnsizedFormat::LA:
    482        case webgl::UnsizedFormat::R:
    483        case webgl::UnsizedFormat::RG:
    484        case webgl::UnsizedFormat::RGB:
    485        case webgl::UnsizedFormat::RGBA:
    486          return true;
    487        default:
    488          return false;
    489      }
    490 
    491    case webgl::UnsizedFormat::RGB:
    492      switch (dstFormat->unsizedFormat) {
    493        case webgl::UnsizedFormat::L:
    494        case webgl::UnsizedFormat::R:
    495        case webgl::UnsizedFormat::RG:
    496        case webgl::UnsizedFormat::RGB:
    497          return true;
    498        default:
    499          return false;
    500      }
    501 
    502    case webgl::UnsizedFormat::RG:
    503      switch (dstFormat->unsizedFormat) {
    504        case webgl::UnsizedFormat::L:
    505        case webgl::UnsizedFormat::R:
    506        case webgl::UnsizedFormat::RG:
    507          return true;
    508        default:
    509          return false;
    510      }
    511 
    512    case webgl::UnsizedFormat::R:
    513      switch (dstFormat->unsizedFormat) {
    514        case webgl::UnsizedFormat::L:
    515        case webgl::UnsizedFormat::R:
    516          return true;
    517        default:
    518          return false;
    519      }
    520 
    521    default:
    522      return false;
    523  }
    524 }
    525 
    526 static bool EnsureImageDataInitializedForUpload(
    527    WebGLTexture* tex, TexImageTarget target, uint32_t level,
    528    const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
    529    bool* const out_expectsInit = nullptr) {
    530  if (out_expectsInit) {
    531    *out_expectsInit = false;
    532  }
    533  if (!imageInfo->mUninitializedSlices) return true;
    534 
    535  if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
    536    bool expectsInit = false;
    537    auto& isSliceUninit = *imageInfo->mUninitializedSlices;
    538    for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
    539      if (!isSliceUninit[z]) continue;
    540      expectsInit = true;
    541      isSliceUninit[z] = false;
    542    }
    543    if (out_expectsInit) {
    544      *out_expectsInit = expectsInit;
    545    }
    546 
    547    if (!expectsInit) return true;
    548 
    549    bool hasUninitialized = false;
    550    for (const auto z : IntegerRange(imageInfo->mDepth)) {
    551      hasUninitialized |= isSliceUninit[z];
    552    }
    553    if (!hasUninitialized) {
    554      imageInfo->mUninitializedSlices.reset();
    555    }
    556    return true;
    557  }
    558 
    559  WebGLContext* webgl = tex->mContext;
    560  webgl->GenerateWarning(
    561      "Texture has not been initialized prior to a"
    562      " partial upload, forcing the browser to clear it."
    563      " This may be slow.");
    564  if (!tex->EnsureImageDataInitialized(target, level)) {
    565    MOZ_ASSERT(false, "Unexpected failure to init image data.");
    566    return false;
    567  }
    568 
    569  return true;
    570 }
    571 
    572 //////////////////////////////////////////////////////////////////////////////////////////
    573 //////////////////////////////////////////////////////////////////////////////////////////
    574 // Actual calls
    575 
    576 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
    577                                  GLsizei levels, GLenum sizedFormat,
    578                                  GLsizei width, GLsizei height,
    579                                  GLsizei depth) {
    580  gl::GLContext::LocalErrorScope errorScope(*gl);
    581 
    582  switch (target.get()) {
    583    case LOCAL_GL_TEXTURE_2D:
    584    case LOCAL_GL_TEXTURE_CUBE_MAP:
    585      MOZ_ASSERT(depth == 1);
    586      gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
    587      break;
    588 
    589    case LOCAL_GL_TEXTURE_3D:
    590    case LOCAL_GL_TEXTURE_2D_ARRAY:
    591      gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
    592                        depth);
    593      break;
    594 
    595    default:
    596      MOZ_CRASH("GFX: bad target");
    597  }
    598 
    599  return errorScope.GetError();
    600 }
    601 
    602 bool IsTarget3D(TexImageTarget target) {
    603  switch (target.get()) {
    604    case LOCAL_GL_TEXTURE_2D:
    605    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    606    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    607    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    608    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    609    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    610    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
    611      return false;
    612 
    613    case LOCAL_GL_TEXTURE_3D:
    614    case LOCAL_GL_TEXTURE_2D_ARRAY:
    615      return true;
    616 
    617    default:
    618      MOZ_CRASH("GFX: bad target");
    619  }
    620 }
    621 
    622 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
    623                  const webgl::DriverUnpackInfo* dui, GLsizei width,
    624                  GLsizei height, GLsizei depth, const void* data) {
    625  const GLint border = 0;
    626 
    627  gl::GLContext::LocalErrorScope errorScope(*gl);
    628 
    629  if (IsTarget3D(target)) {
    630    gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
    631                    depth, border, dui->unpackFormat, dui->unpackType, data);
    632  } else {
    633    MOZ_ASSERT(depth == 1);
    634    gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
    635                    border, dui->unpackFormat, dui->unpackType, data);
    636  }
    637 
    638  return errorScope.GetError();
    639 }
    640 
    641 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
    642                     GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
    643                     GLsizei height, GLsizei depth,
    644                     const webgl::PackingInfo& pi, const void* data) {
    645  gl::GLContext::LocalErrorScope errorScope(*gl);
    646 
    647  if (IsTarget3D(target)) {
    648    gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
    649                       height, depth, pi.format, pi.type, data);
    650  } else {
    651    MOZ_ASSERT(zOffset == 0);
    652    MOZ_ASSERT(depth == 1);
    653    gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
    654                       pi.format, pi.type, data);
    655  }
    656 
    657  return errorScope.GetError();
    658 }
    659 
    660 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
    661                                          TexImageTarget target, GLint level,
    662                                          GLenum internalFormat, GLsizei width,
    663                                          GLsizei height, GLsizei depth,
    664                                          GLsizei dataSize, const void* data) {
    665  const GLint border = 0;
    666 
    667  gl::GLContext::LocalErrorScope errorScope(*gl);
    668 
    669  if (IsTarget3D(target)) {
    670    gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
    671                              height, depth, border, dataSize, data);
    672  } else {
    673    MOZ_ASSERT(depth == 1);
    674    gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
    675                              height, border, dataSize, data);
    676  }
    677 
    678  return errorScope.GetError();
    679 }
    680 
    681 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
    682                               GLint level, GLint xOffset, GLint yOffset,
    683                               GLint zOffset, GLsizei width, GLsizei height,
    684                               GLsizei depth, GLenum sizedUnpackFormat,
    685                               GLsizei dataSize, const void* data) {
    686  gl::GLContext::LocalErrorScope errorScope(*gl);
    687 
    688  if (IsTarget3D(target)) {
    689    gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
    690                                 width, height, depth, sizedUnpackFormat,
    691                                 dataSize, data);
    692  } else {
    693    MOZ_ASSERT(zOffset == 0);
    694    MOZ_ASSERT(depth == 1);
    695    gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
    696                                 height, sizedUnpackFormat, dataSize, data);
    697  }
    698 
    699  return errorScope.GetError();
    700 }
    701 
    702 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
    703                                       GLint level, GLint xOffset,
    704                                       GLint yOffset, GLint zOffset, GLint x,
    705                                       GLint y, GLsizei width, GLsizei height) {
    706  gl::GLContext::LocalErrorScope errorScope(*gl);
    707 
    708  if (IsTarget3D(target)) {
    709    gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
    710                           width, height);
    711  } else {
    712    MOZ_ASSERT(zOffset == 0);
    713    gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
    714                           height);
    715  }
    716 
    717  return errorScope.GetError();
    718 }
    719 
    720 //////////////////////////////////////////////////////////////////////////////////////////
    721 //////////////////////////////////////////////////////////////////////////////////////////
    722 // Actual (mostly generic) function implementations
    723 
    724 static bool ValidateCompressedTexImageRestrictions(
    725    const WebGLContext* webgl, TexImageTarget target, uint32_t level,
    726    const webgl::FormatInfo* format, const uvec3& size) {
    727  const auto fnIsDimValid_S3TC = [&](const char* const name, uint32_t levelSize,
    728                                     uint32_t blockSize) {
    729    const auto impliedBaseSize = levelSize << level;
    730    if (impliedBaseSize % blockSize == 0) return true;
    731    webgl->ErrorInvalidOperation(
    732        "%u is never a valid %s for level %u, because it implies a base mip %s "
    733        "of %u."
    734        " %s requires that base mip levels have a %s multiple of %u.",
    735        levelSize, name, level, name, impliedBaseSize, format->name, name,
    736        blockSize);
    737    return false;
    738  };
    739 
    740  switch (format->compression->family) {
    741    case webgl::CompressionFamily::ASTC:
    742      if (target == LOCAL_GL_TEXTURE_3D &&
    743          !webgl->gl->IsExtensionSupported(
    744              gl::GLContext::KHR_texture_compression_astc_hdr)) {
    745        webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
    746        return false;
    747      }
    748      break;
    749 
    750    case webgl::CompressionFamily::PVRTC:
    751      if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
    752        webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
    753                                 format->name);
    754        return false;
    755      }
    756      break;
    757 
    758    case webgl::CompressionFamily::BPTC:
    759    case webgl::CompressionFamily::RGTC:
    760    case webgl::CompressionFamily::S3TC:
    761      if (!fnIsDimValid_S3TC("width", size.x,
    762                             format->compression->blockWidth) ||
    763          !fnIsDimValid_S3TC("height", size.y,
    764                             format->compression->blockHeight)) {
    765        return false;
    766      }
    767      break;
    768 
    769    // Default: There are no restrictions on CompressedTexImage.
    770    case webgl::CompressionFamily::ES3:
    771    case webgl::CompressionFamily::ETC1:
    772      break;
    773  }
    774 
    775  return true;
    776 }
    777 
    778 static bool ValidateFormatAndSize(const WebGLContext* webgl,
    779                                  TexImageTarget target,
    780                                  const webgl::FormatInfo* format,
    781                                  const uvec3& size) {
    782  // Check if texture size will likely be rejected by the driver and give a more
    783  // meaningful error message.
    784  auto baseImageSize = CheckedInt<uint64_t>(format->estimatedBytesPerPixel) *
    785                       (uint32_t)size.x * (uint32_t)size.y * (uint32_t)size.z;
    786  if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
    787    baseImageSize *= 6;
    788  }
    789  if (!baseImageSize.isValid() ||
    790      baseImageSize.value() >
    791          (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
    792              (1024 * 1024)) {
    793    webgl->ErrorOutOfMemory(
    794        "Texture size too large; base image mebibytes > "
    795        "webgl.max-size-per-texture-mib");
    796    return false;
    797  }
    798 
    799  // GLES 3.0.4 p127:
    800  // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
    801  // are supported by texture image specification commands only if `target` is
    802  // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
    803  // conjunction with any other `target` will result in an INVALID_OPERATION
    804  // error."
    805  const bool ok = [&]() {
    806    if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
    807 
    808    if (format->compression) {
    809      switch (format->compression->family) {
    810        case webgl::CompressionFamily::ES3:
    811        case webgl::CompressionFamily::S3TC:
    812          if (target == LOCAL_GL_TEXTURE_3D) return false;
    813          break;
    814 
    815        case webgl::CompressionFamily::ETC1:
    816        case webgl::CompressionFamily::PVRTC:
    817        case webgl::CompressionFamily::RGTC:
    818          if (target == LOCAL_GL_TEXTURE_3D ||
    819              target == LOCAL_GL_TEXTURE_2D_ARRAY) {
    820            return false;
    821          }
    822          break;
    823        default:
    824          break;
    825      }
    826    }
    827    return true;
    828  }();
    829  if (!ok) {
    830    webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
    831                                 format->name, GetEnumName(target.get()));
    832    return false;
    833  }
    834 
    835  return true;
    836 }
    837 
    838 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
    839                              GLenum sizedFormat, const uvec3& size) {
    840  // Check levels
    841  if (levels < 1) {
    842    mContext->ErrorInvalidValue("`levels` must be >= 1.");
    843    return;
    844  }
    845 
    846  if (!size.x || !size.y || !size.z) {
    847    mContext->ErrorInvalidValue("Dimensions must be non-zero.");
    848    return;
    849  }
    850 
    851  const TexImageTarget testTarget =
    852      IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
    853  webgl::ImageInfo* baseImageInfo;
    854  if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
    855    return;
    856  }
    857  MOZ_ALWAYS_TRUE(baseImageInfo);
    858 
    859  auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
    860  if (!dstUsage) {
    861    mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
    862    return;
    863  }
    864  auto dstFormat = dstUsage->format;
    865 
    866  if (!ValidateFormatAndSize(mContext, testTarget, dstFormat, size)) return;
    867 
    868  if (dstFormat->compression) {
    869    if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
    870                                                dstFormat, size)) {
    871      return;
    872    }
    873  }
    874 
    875  ////////////////////////////////////
    876 
    877  const bool levelsOk = [&]() {
    878    // Right-shift is only defined for bits-1, which is too large anyways.
    879    const auto lastLevel = uint32_t(levels - 1);
    880    if (lastLevel > 31) return false;
    881 
    882    const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
    883    const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
    884 
    885    // If these are all zero, then some earlier level was the final 1x1(x1)
    886    // level.
    887    bool ok = lastLevelWidth || lastLevelHeight;
    888    if (target == LOCAL_GL_TEXTURE_3D) {
    889      const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
    890      ok |= bool(lastLevelDepth);
    891    }
    892    return ok;
    893  }();
    894  if (!levelsOk) {
    895    mContext->ErrorInvalidOperation(
    896        "Too many levels requested for the given"
    897        " dimensions. (levels: %u, width: %u, height: %u,"
    898        " depth: %u)",
    899        levels, size.x, size.y, size.z);
    900    return;
    901  }
    902 
    903  ////////////////////////////////////
    904  // Do the thing!
    905 
    906  GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
    907                              size.x, size.y, size.z);
    908 
    909  mContext->OnDataAllocCall();
    910 
    911  if (error == LOCAL_GL_OUT_OF_MEMORY) {
    912    mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
    913    Truncate();
    914    return;
    915  }
    916  if (error) {
    917    mContext->GenerateError(error, "Unexpected error from driver.");
    918    const nsPrintfCString call(
    919        "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
    920        levels, sizedFormat, size.x, size.y, size.z, error);
    921    gfxCriticalError() << "Unexpected error from driver: "
    922                       << call.BeginReading();
    923    return;
    924  }
    925 
    926  ////////////////////////////////////
    927  // Update our specification data.
    928 
    929  std::vector<bool> uninitializedSlices(size.z, true);
    930  const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
    931                                 std::move(uninitializedSlices)};
    932 
    933  {
    934    const auto base_level = mBaseMipmapLevel;
    935    mBaseMipmapLevel = 0;
    936 
    937    ImageInfoAtFace(0, 0) = newInfo;
    938    PopulateMipChain(levels - 1);
    939 
    940    mBaseMipmapLevel = base_level;
    941  }
    942 
    943  mImmutable = true;
    944  mImmutableLevelCount = AutoAssertCast(levels);
    945  ClampLevelBaseAndMax();
    946 }
    947 
    948 ////////////////////////////////////////
    949 // Tex(Sub)Image
    950 
    951 // TexSubImage iff `!respectFormat`
    952 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
    953                            const uvec3& offset, const webgl::PackingInfo& pi,
    954                            const webgl::TexUnpackBlobDesc& src) {
    955  const auto blob = webgl::TexUnpackBlob::Create(src);
    956  if (!blob) {
    957    MOZ_ASSERT(false);
    958    return;
    959  }
    960 
    961  const auto imageTarget = blob->mDesc.imageTarget;
    962  auto size = blob->mDesc.size;
    963 
    964  if (!IsTarget3D(imageTarget)) {
    965    size.z = 1;
    966  }
    967 
    968  ////////////////////////////////////
    969  // Get dest info
    970 
    971  const auto& fua = mContext->mFormatUsage;
    972  const auto fnValidateUnpackEnums = [&]() {
    973    if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
    974      mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
    975                                 EnumString(pi.format).c_str(),
    976                                 EnumString(pi.type).c_str());
    977      return false;
    978    }
    979    return true;
    980  };
    981 
    982  webgl::ImageInfo* imageInfo;
    983  const webgl::FormatUsageInfo* dstUsage;
    984  if (respecFormat) {
    985    if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
    986      return;
    987    MOZ_ASSERT(imageInfo);
    988 
    989    if (!fua->IsInternalFormatEnumValid(respecFormat)) {
    990      mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
    991                                  respecFormat);
    992      return;
    993    }
    994 
    995    dstUsage = fua->GetSizedTexUsage(respecFormat);
    996    if (!dstUsage) {
    997      if (respecFormat != pi.format) {
    998        /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
    999         *   "Specifying a combination of values for format, type, and
   1000         *   internalformat that is not listed as a valid combination
   1001         *   in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
   1002         */
   1003        if (!fnValidateUnpackEnums()) return;
   1004        mContext->ErrorInvalidOperation(
   1005            "Unsized internalFormat must match"
   1006            " unpack format.");
   1007        return;
   1008      }
   1009 
   1010      dstUsage = fua->GetUnsizedTexUsage(pi);
   1011    }
   1012 
   1013    if (!dstUsage) {
   1014      if (!fnValidateUnpackEnums()) return;
   1015      mContext->ErrorInvalidOperation(
   1016          "Invalid internalformat/format/type:"
   1017          " 0x%04x/0x%04x/0x%04x",
   1018          respecFormat, pi.format, pi.type);
   1019      return;
   1020    }
   1021 
   1022    const auto& dstFormat = dstUsage->format;
   1023    if (!ValidateFormatAndSize(mContext, imageTarget, dstFormat, size)) return;
   1024 
   1025    if (!mContext->IsWebGL2() && dstFormat->d) {
   1026      if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
   1027        mContext->ErrorInvalidOperation(
   1028            "With format %s, this function may only"
   1029            " be called with target=TEXTURE_2D,"
   1030            " data=null, and level=0.",
   1031            dstFormat->name);
   1032        return;
   1033      }
   1034    }
   1035  } else {
   1036    if (!ValidateTexImageSelection(imageTarget, level, offset, size,
   1037                                   &imageInfo)) {
   1038      return;
   1039    }
   1040    MOZ_ASSERT(imageInfo);
   1041    dstUsage = imageInfo->mFormat;
   1042 
   1043    const auto& dstFormat = dstUsage->format;
   1044    if (!mContext->IsWebGL2() && dstFormat->d) {
   1045      mContext->ErrorInvalidOperation(
   1046          "Function may not be called on a texture of"
   1047          " format %s.",
   1048          dstFormat->name);
   1049      return;
   1050    }
   1051  }
   1052 
   1053  ////////////////////////////////////
   1054  // Get source info
   1055 
   1056  const webgl::DriverUnpackInfo* driverUnpackInfo;
   1057  if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
   1058    if (!fnValidateUnpackEnums()) return;
   1059    mContext->ErrorInvalidOperation(
   1060        "Mismatched internalFormat and format/type:"
   1061        " 0x%04x and 0x%04x/0x%04x",
   1062        respecFormat, pi.format, pi.type);
   1063    return;
   1064  }
   1065 
   1066  if (!blob->Validate(mContext, pi)) return;
   1067 
   1068  ////////////////////////////////////
   1069  // Do the thing!
   1070 
   1071  Maybe<webgl::ImageInfo> newImageInfo;
   1072  bool isRespec = false;
   1073  if (respecFormat) {
   1074    // It's tempting to do allocation first, and TexSubImage second, but this is
   1075    // generally slower.
   1076    newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
   1077    if (!blob->HasData()) {
   1078      newImageInfo->mUninitializedSlices.emplace(size.z, true);
   1079    }
   1080 
   1081    isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
   1082                imageInfo->mHeight != newImageInfo->mHeight ||
   1083                imageInfo->mDepth != newImageInfo->mDepth ||
   1084                imageInfo->mFormat != newImageInfo->mFormat);
   1085  } else {
   1086    if (!blob->HasData()) {
   1087      mContext->ErrorInvalidValue("`source` cannot be null.");
   1088      return;
   1089    }
   1090    if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
   1091                                             size, imageInfo)) {
   1092      return;
   1093    }
   1094  }
   1095 
   1096  webgl::PixelPackingState{}.AssertCurrentUnpack(*mContext->gl,
   1097                                                 mContext->IsWebGL2());
   1098 
   1099  blob->mDesc.unpacking.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(), size);
   1100  const auto revertUnpacking = MakeScopeExit([&]() {
   1101    webgl::PixelPackingState{}.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(),
   1102                                           size);
   1103  });
   1104 
   1105  const bool isSubImage = !respecFormat;
   1106  GLenum glError = 0;
   1107  if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
   1108                           offset.x, offset.y, offset.z, pi, &glError)) {
   1109    return;
   1110  }
   1111 
   1112  if (glError == LOCAL_GL_OUT_OF_MEMORY) {
   1113    mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
   1114    Truncate();
   1115    return;
   1116  }
   1117 
   1118  if (glError) {
   1119    const auto enumStr = EnumString(glError);
   1120    const nsPrintfCString dui(
   1121        "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
   1122        driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
   1123        driverUnpackInfo->unpackType);
   1124    mContext->ErrorInvalidOperation("%s", dui.BeginReading());
   1125    gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
   1126    return;
   1127  }
   1128 
   1129  ////////////////////////////////////
   1130  // Update our specification data?
   1131 
   1132  if (respecFormat) {
   1133    mContext->OnDataAllocCall();
   1134    *imageInfo = *newImageInfo;
   1135    InvalidateCaches();
   1136  }
   1137 }
   1138 
   1139 ////////////////////////////////////////
   1140 // CompressedTex(Sub)Image
   1141 
   1142 static inline bool IsSubImageBlockAligned(
   1143    const webgl::CompressedFormatInfo* compression,
   1144    const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
   1145    uint32_t width, uint32_t height) {
   1146  if (xOffset % compression->blockWidth != 0 ||
   1147      yOffset % compression->blockHeight != 0) {
   1148    return false;
   1149  }
   1150 
   1151  if (width % compression->blockWidth != 0 &&
   1152      xOffset + width != imageInfo->mWidth)
   1153    return false;
   1154 
   1155  if (height % compression->blockHeight != 0 &&
   1156      yOffset + height != imageInfo->mHeight)
   1157    return false;
   1158 
   1159  return true;
   1160 }
   1161 
   1162 // CompressedTexSubImage iff `sub`
   1163 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
   1164                                      uint32_t level, GLenum formatEnum,
   1165                                      const uvec3& offset, const uvec3& size,
   1166                                      const Range<const uint8_t>& src,
   1167                                      const uint32_t pboImageSize,
   1168                                      const Maybe<uint64_t>& pboOffset) {
   1169  auto imageSize = pboImageSize;
   1170  if (pboOffset) {
   1171    const auto& buffer =
   1172        mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
   1173    if (!buffer) return;
   1174    auto availBytes = buffer->ByteLength();
   1175    if (*pboOffset > availBytes) {
   1176      mContext->GenerateError(
   1177          LOCAL_GL_INVALID_OPERATION,
   1178          "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
   1179          *pboOffset, availBytes);
   1180      return;
   1181    }
   1182    availBytes -= *pboOffset;
   1183    if (availBytes < pboImageSize) {
   1184      mContext->GenerateError(
   1185          LOCAL_GL_INVALID_OPERATION,
   1186          "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
   1187          " `pboImageSize` (%u).",
   1188          availBytes, pboImageSize);
   1189      return;
   1190    }
   1191  } else {
   1192    if (mContext->mBoundPixelUnpackBuffer) {
   1193      mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
   1194                              "PIXEL_UNPACK_BUFFER is non-null.");
   1195      return;
   1196    }
   1197    imageSize = src.length();
   1198  }
   1199 
   1200  // -
   1201 
   1202  const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
   1203  if (!usage || !usage->format->compression) {
   1204    mContext->ErrorInvalidEnumArg("format", formatEnum);
   1205    return;
   1206  }
   1207 
   1208  webgl::ImageInfo* imageInfo;
   1209  if (!sub) {
   1210    if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
   1211      return;
   1212    }
   1213    MOZ_ASSERT(imageInfo);
   1214 
   1215    if (!ValidateFormatAndSize(mContext, imageTarget, usage->format, size))
   1216      return;
   1217    if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
   1218                                                usage->format, size)) {
   1219      return;
   1220    }
   1221  } else {
   1222    if (!ValidateTexImageSelection(imageTarget, level, offset, size,
   1223                                   &imageInfo))
   1224      return;
   1225    MOZ_ASSERT(imageInfo);
   1226 
   1227    const auto dstUsage = imageInfo->mFormat;
   1228    if (usage != dstUsage) {
   1229      mContext->ErrorInvalidOperation(
   1230          "`format` must match the format of the"
   1231          " existing texture image.");
   1232      return;
   1233    }
   1234 
   1235    const auto& format = usage->format;
   1236    switch (format->compression->family) {
   1237      // Forbidden:
   1238      case webgl::CompressionFamily::ETC1:
   1239        mContext->ErrorInvalidOperation(
   1240            "Format does not allow sub-image"
   1241            " updates.");
   1242        return;
   1243 
   1244      // Block-aligned:
   1245      case webgl::CompressionFamily::ES3:   // Yes, the ES3 formats don't match
   1246                                            // the ES3
   1247      case webgl::CompressionFamily::S3TC:  // default behavior.
   1248      case webgl::CompressionFamily::BPTC:
   1249      case webgl::CompressionFamily::RGTC:
   1250        if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
   1251                                    offset.y, size.x, size.y)) {
   1252          mContext->ErrorInvalidOperation(
   1253              "Format requires block-aligned sub-image"
   1254              " updates.");
   1255          return;
   1256        }
   1257        break;
   1258 
   1259      // Full-only: (The ES3 default)
   1260      case webgl::CompressionFamily::ASTC:
   1261      case webgl::CompressionFamily::PVRTC:
   1262        if (offset.x || offset.y || size.x != imageInfo->mWidth ||
   1263            size.y != imageInfo->mHeight) {
   1264          mContext->ErrorInvalidOperation(
   1265              "Format does not allow partial sub-image"
   1266              " updates.");
   1267          return;
   1268        }
   1269        break;
   1270    }
   1271  }
   1272 
   1273  switch (usage->format->compression->family) {
   1274    case webgl::CompressionFamily::BPTC:
   1275    case webgl::CompressionFamily::RGTC:
   1276      if (level == 0) {
   1277        if (size.x % 4 != 0 || size.y % 4 != 0) {
   1278          mContext->ErrorInvalidOperation(
   1279              "For level == 0, width and height must be multiples of 4.");
   1280          return;
   1281        }
   1282      }
   1283      break;
   1284 
   1285    default:
   1286      break;
   1287  }
   1288 
   1289  if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
   1290    return;
   1291 
   1292  ////////////////////////////////////
   1293  // Do the thing!
   1294 
   1295  if (sub) {
   1296    if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
   1297                                             size, imageInfo)) {
   1298      return;
   1299    }
   1300  }
   1301 
   1302  const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
   1303                               mContext->mBoundPixelUnpackBuffer);
   1304  GLenum error;
   1305  const void* ptr;
   1306  if (pboOffset) {
   1307    ptr = reinterpret_cast<const void*>(*pboOffset);
   1308  } else {
   1309    ptr = reinterpret_cast<const void*>(src.begin().get());
   1310  }
   1311 
   1312  if (!sub) {
   1313    error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
   1314                                 size.x, size.y, size.z, imageSize, ptr);
   1315  } else {
   1316    error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
   1317                                    offset.y, offset.z, size.x, size.y, size.z,
   1318                                    formatEnum, imageSize, ptr);
   1319  }
   1320  if (error == LOCAL_GL_OUT_OF_MEMORY) {
   1321    mContext->ErrorOutOfMemory("Ran out of memory during upload.");
   1322    Truncate();
   1323    return;
   1324  }
   1325  if (error) {
   1326    mContext->GenerateError(error, "Unexpected error from driver.");
   1327    nsCString call;
   1328    if (!sub) {
   1329      call = nsPrintfCString(
   1330          "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
   1331          imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
   1332          ptr);
   1333    } else {
   1334      call = nsPrintfCString(
   1335          "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
   1336          "%p)",
   1337          imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
   1338          size.z, formatEnum, imageSize, ptr);
   1339    }
   1340    gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
   1341                       << " from driver: " << call.BeginReading();
   1342    return;
   1343  }
   1344 
   1345  ////////////////////////////////////
   1346  // Update our specification data?
   1347 
   1348  if (!sub) {
   1349    constexpr auto uninitializedSlices = std::nullopt;
   1350    const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
   1351                                        uninitializedSlices};
   1352    *imageInfo = newImageInfo;
   1353    InvalidateCaches();
   1354  }
   1355 }
   1356 
   1357 ////////////////////////////////////////
   1358 // CopyTex(Sub)Image
   1359 
   1360 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
   1361                                        const webgl::FormatInfo* srcFormat,
   1362                                        const webgl::FormatInfo* dstFormat) {
   1363  MOZ_ASSERT(!srcFormat->compression);
   1364  if (dstFormat->compression) {
   1365    webgl->ErrorInvalidEnum(
   1366        "Specified destination must not have a compressed"
   1367        " format.");
   1368    return false;
   1369  }
   1370 
   1371  if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
   1372    webgl->ErrorInvalidOperation(
   1373        "RGB9_E5 is an invalid destination for"
   1374        " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
   1375    return false;
   1376  }
   1377 
   1378  if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
   1379    webgl->ErrorInvalidOperation(
   1380        "Destination channels must be compatible with"
   1381        " source channels. (GLES 3.0.4 p140 Table 3.16)");
   1382    return false;
   1383  }
   1384 
   1385  return true;
   1386 }
   1387 
   1388 ////////////////////////////////////////////////////////////////////////////////
   1389 
   1390 class ScopedCopyTexImageSource {
   1391  WebGLContext* const mWebGL;
   1392  GLuint mRB;
   1393  GLuint mFB;
   1394 
   1395 public:
   1396  ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
   1397                           uint32_t srcHeight,
   1398                           const webgl::FormatInfo* srcFormat,
   1399                           const webgl::FormatUsageInfo* dstUsage);
   1400  ~ScopedCopyTexImageSource();
   1401 };
   1402 
   1403 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
   1404    WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
   1405    const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
   1406    : mWebGL(webgl), mRB(0), mFB(0) {
   1407  switch (dstUsage->format->unsizedFormat) {
   1408    case webgl::UnsizedFormat::L:
   1409    case webgl::UnsizedFormat::A:
   1410    case webgl::UnsizedFormat::LA:
   1411      webgl->GenerateWarning(
   1412          "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
   1413          " is deprecated, and has severely reduced performance"
   1414          " on some platforms.");
   1415      break;
   1416 
   1417    default:
   1418      MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
   1419      return;
   1420  }
   1421 
   1422  if (!dstUsage->textureSwizzleRGBA) return;
   1423 
   1424  gl::GLContext* gl = webgl->gl;
   1425 
   1426  GLenum sizedFormat;
   1427 
   1428  switch (srcFormat->componentType) {
   1429    case webgl::ComponentType::NormUInt:
   1430      sizedFormat = LOCAL_GL_RGBA8;
   1431      break;
   1432 
   1433    case webgl::ComponentType::Float:
   1434      if (webgl->IsExtensionEnabled(
   1435              WebGLExtensionID::WEBGL_color_buffer_float)) {
   1436        sizedFormat = LOCAL_GL_RGBA32F;
   1437        webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
   1438        break;
   1439      }
   1440 
   1441      if (webgl->IsExtensionEnabled(
   1442              WebGLExtensionID::EXT_color_buffer_half_float)) {
   1443        sizedFormat = LOCAL_GL_RGBA16F;
   1444        webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
   1445        break;
   1446      }
   1447      MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
   1448 
   1449    default:
   1450      MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
   1451  }
   1452 
   1453  gl::ScopedTexture scopedTex(gl);
   1454  gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
   1455                                      LOCAL_GL_TEXTURE_2D);
   1456 
   1457  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
   1458                     LOCAL_GL_NEAREST);
   1459  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
   1460                     LOCAL_GL_NEAREST);
   1461 
   1462  GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
   1463  switch (dstUsage->format->unsizedFormat) {
   1464    case webgl::UnsizedFormat::L:
   1465      blitSwizzle[0] = LOCAL_GL_RED;
   1466      break;
   1467 
   1468    case webgl::UnsizedFormat::A:
   1469      blitSwizzle[0] = LOCAL_GL_ALPHA;
   1470      break;
   1471 
   1472    case webgl::UnsizedFormat::LA:
   1473      blitSwizzle[0] = LOCAL_GL_RED;
   1474      blitSwizzle[1] = LOCAL_GL_ALPHA;
   1475      break;
   1476 
   1477    default:
   1478      MOZ_CRASH("GFX: Unhandled unsizedFormat.");
   1479  }
   1480 
   1481  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
   1482                     blitSwizzle[0]);
   1483  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
   1484                     blitSwizzle[1]);
   1485  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
   1486                     blitSwizzle[2]);
   1487  gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
   1488                     blitSwizzle[3]);
   1489 
   1490  gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
   1491                      srcHeight, 0);
   1492 
   1493  // Now create the swizzled FB we'll be exposing.
   1494 
   1495  GLuint rgbaRB = 0;
   1496  GLuint rgbaFB = 0;
   1497  {
   1498    gl->fGenRenderbuffers(1, &rgbaRB);
   1499    gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
   1500    gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
   1501                             srcHeight);
   1502 
   1503    gl->fGenFramebuffers(1, &rgbaFB);
   1504    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
   1505    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
   1506                                 LOCAL_GL_COLOR_ATTACHMENT0,
   1507                                 LOCAL_GL_RENDERBUFFER, rgbaRB);
   1508 
   1509    const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
   1510    if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
   1511      MOZ_CRASH("GFX: Temp framebuffer is not complete.");
   1512    }
   1513  }
   1514 
   1515  // Draw-blit rgbaTex into rgbaFB.
   1516  const gfx::IntSize srcSize(srcWidth, srcHeight);
   1517  {
   1518    const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
   1519    gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
   1520                                                   srcSize);
   1521  }
   1522 
   1523  // Leave RB and FB alive, and FB bound.
   1524  mRB = rgbaRB;
   1525  mFB = rgbaFB;
   1526 }
   1527 
   1528 template <typename T>
   1529 static inline GLenum ToGLHandle(const T& obj) {
   1530  return (obj ? obj->mGLName : 0);
   1531 }
   1532 
   1533 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
   1534  if (!mFB) {
   1535    MOZ_ASSERT(!mRB);
   1536    return;
   1537  }
   1538  MOZ_ASSERT(mRB);
   1539 
   1540  gl::GLContext* gl = mWebGL->gl;
   1541 
   1542  // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
   1543  // has split framebuffer support.
   1544  gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
   1545                       ToGLHandle(mWebGL->mBoundDrawFramebuffer));
   1546  gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
   1547                       ToGLHandle(mWebGL->mBoundReadFramebuffer));
   1548 
   1549  gl->fDeleteFramebuffers(1, &mFB);
   1550  gl->fDeleteRenderbuffers(1, &mRB);
   1551 }
   1552 
   1553 ////////////////////////////////////////////////////////////////////////////////
   1554 
   1555 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
   1556                                    webgl::UnsizedFormat* const out) {
   1557  switch (internalFormat) {
   1558    case LOCAL_GL_RED:
   1559      *out = webgl::UnsizedFormat::R;
   1560      break;
   1561    case LOCAL_GL_RG:
   1562      *out = webgl::UnsizedFormat::RG;
   1563      break;
   1564    case LOCAL_GL_RGB:
   1565      *out = webgl::UnsizedFormat::RGB;
   1566      break;
   1567    case LOCAL_GL_RGBA:
   1568      *out = webgl::UnsizedFormat::RGBA;
   1569      break;
   1570    case LOCAL_GL_LUMINANCE:
   1571      *out = webgl::UnsizedFormat::L;
   1572      break;
   1573    case LOCAL_GL_ALPHA:
   1574      *out = webgl::UnsizedFormat::A;
   1575      break;
   1576    case LOCAL_GL_LUMINANCE_ALPHA:
   1577      *out = webgl::UnsizedFormat::LA;
   1578      break;
   1579 
   1580    default:
   1581      return false;
   1582  }
   1583 
   1584  return true;
   1585 }
   1586 
   1587 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
   1588    WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
   1589    GLenum internalFormat) {
   1590  const auto& fua = webgl->mFormatUsage;
   1591 
   1592  switch (internalFormat) {
   1593    case LOCAL_GL_R8_SNORM:
   1594    case LOCAL_GL_RG8_SNORM:
   1595    case LOCAL_GL_RGB8_SNORM:
   1596    case LOCAL_GL_RGBA8_SNORM:
   1597      webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
   1598      return nullptr;
   1599  }
   1600 
   1601  auto dstUsage = fua->GetSizedTexUsage(internalFormat);
   1602  if (!dstUsage) {
   1603    // Ok, maybe it's unsized.
   1604    webgl::UnsizedFormat unsizedFormat;
   1605    if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
   1606      webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
   1607      return nullptr;
   1608    }
   1609 
   1610    const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
   1611    if (dstFormat) {
   1612      dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
   1613    }
   1614    if (!dstUsage) {
   1615      webgl->ErrorInvalidOperation(
   1616          "0x%04x is not a valid unsized format for"
   1617          " source format %s.",
   1618          internalFormat, srcFormat->name);
   1619      return nullptr;
   1620    }
   1621 
   1622    return dstUsage;
   1623  }
   1624  // Alright, it's sized.
   1625 
   1626  const auto dstFormat = dstUsage->format;
   1627 
   1628  if (dstFormat->componentType != srcFormat->componentType) {
   1629    webgl->ErrorInvalidOperation(
   1630        "For sized internalFormats, source and dest"
   1631        " component types must match. (source: %s, dest:"
   1632        " %s)",
   1633        srcFormat->name, dstFormat->name);
   1634    return nullptr;
   1635  }
   1636 
   1637  bool componentSizesMatch = true;
   1638  if (dstFormat->r) {
   1639    componentSizesMatch &= (dstFormat->r == srcFormat->r);
   1640  }
   1641  if (dstFormat->g) {
   1642    componentSizesMatch &= (dstFormat->g == srcFormat->g);
   1643  }
   1644  if (dstFormat->b) {
   1645    componentSizesMatch &= (dstFormat->b == srcFormat->b);
   1646  }
   1647  if (dstFormat->a) {
   1648    componentSizesMatch &= (dstFormat->a == srcFormat->a);
   1649  }
   1650 
   1651  if (!componentSizesMatch) {
   1652    webgl->ErrorInvalidOperation(
   1653        "For sized internalFormats, source and dest"
   1654        " component sizes must match exactly. (source: %s,"
   1655        " dest: %s)",
   1656        srcFormat->name, dstFormat->name);
   1657    return nullptr;
   1658  }
   1659 
   1660  return dstUsage;
   1661 }
   1662 
   1663 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
   1664                                            const WebGLTexture& tex,
   1665                                            const uint32_t mipLevel,
   1666                                            const uint32_t zLayer) {
   1667  const auto& fb = webgl.BoundReadFb();
   1668  if (fb) {
   1669    MOZ_ASSERT(fb->ColorReadBuffer());
   1670    const auto& attach = *fb->ColorReadBuffer();
   1671    MOZ_ASSERT(attach.ZLayerCount() ==
   1672               1);  // Multiview invalid for copyTexImage.
   1673 
   1674    if (attach.Texture() == &tex && attach.Layer() == zLayer &&
   1675        attach.MipLevel() == mipLevel) {
   1676      // Note that the TexImageTargets *don't* have to match for this to be
   1677      // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
   1678      webgl.ErrorInvalidOperation(
   1679          "Feedback loop detected, as this texture"
   1680          " is already attached to READ_FRAMEBUFFER's"
   1681          " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
   1682          attach.mAttachmentPoint);
   1683      return false;
   1684    }
   1685  }
   1686  return true;
   1687 }
   1688 
   1689 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
   1690                                bool needsInit, WebGLTexture* const tex,
   1691                                const TexImageTarget target, GLint level,
   1692                                GLint xWithinSrc, GLint yWithinSrc,
   1693                                uint32_t srcTotalWidth, uint32_t srcTotalHeight,
   1694                                const webgl::FormatUsageInfo* srcUsage,
   1695                                GLint xOffset, GLint yOffset, GLint zOffset,
   1696                                uint32_t dstWidth, uint32_t dstHeight,
   1697                                const webgl::FormatUsageInfo* dstUsage) {
   1698  const auto& gl = webgl->gl;
   1699 
   1700  ////
   1701 
   1702  int32_t readX, readY;
   1703  int32_t writeX, writeY;
   1704  int32_t rwWidth, rwHeight;
   1705  if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
   1706                 &rwWidth) ||
   1707      !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
   1708                 &rwHeight)) {
   1709    webgl->ErrorOutOfMemory("Bad subrect selection.");
   1710    return false;
   1711  }
   1712 
   1713  writeX += xOffset;
   1714  writeY += yOffset;
   1715 
   1716  ////
   1717 
   1718  GLenum error = 0;
   1719  nsCString errorText;
   1720  do {
   1721    const auto& idealUnpack = dstUsage->idealUnpack;
   1722    const auto& pi = idealUnpack->ToPacking();
   1723 
   1724    UniqueBuffer zeros;
   1725    const bool fullOverwrite =
   1726        (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
   1727    if (needsInit && !fullOverwrite) {
   1728      CheckedInt<size_t> byteCount = BytesPerPixel(pi);
   1729      byteCount *= dstWidth;
   1730      byteCount *= dstHeight;
   1731 
   1732      if (byteCount.isValid()) {
   1733        zeros = UniqueBuffer::Take(calloc(1u, byteCount.value()));
   1734      }
   1735 
   1736      if (!zeros.get()) {
   1737        webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
   1738        return false;
   1739      }
   1740    }
   1741 
   1742    if (!isSubImage || zeros) {
   1743      webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
   1744 
   1745      gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
   1746      const auto revert = MakeScopeExit(
   1747          [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
   1748      if (!isSubImage) {
   1749        error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
   1750                           1, nullptr);
   1751        if (error) {
   1752          errorText = nsPrintfCString(
   1753              "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
   1754              "0x%04x",
   1755              target.get(), level, idealUnpack->internalFormat,
   1756              idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
   1757              dstHeight, error);
   1758          break;
   1759        }
   1760      }
   1761      if (zeros) {
   1762        error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
   1763                              dstWidth, dstHeight, 1, pi, zeros.get());
   1764        if (error) {
   1765          errorText = nsPrintfCString(
   1766              "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
   1767              "-> "
   1768              "0x%04x",
   1769              target.get(), level, xOffset, yOffset, zOffset, dstWidth,
   1770              dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
   1771              error);
   1772          break;
   1773        }
   1774      }
   1775    }
   1776 
   1777    if (!rwWidth || !rwHeight) {
   1778      // There aren't any pixels to copy, so we're 'done'.
   1779      return true;
   1780    }
   1781 
   1782    const auto& srcFormat = srcUsage->format;
   1783    ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
   1784                                          srcFormat, dstUsage);
   1785 
   1786    error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
   1787                              readY, rwWidth, rwHeight);
   1788    if (error) {
   1789      errorText = nsPrintfCString(
   1790          "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
   1791          target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
   1792          rwHeight, error);
   1793      break;
   1794    }
   1795 
   1796    return true;
   1797  } while (false);
   1798 
   1799  if (error == LOCAL_GL_OUT_OF_MEMORY) {
   1800    webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
   1801    tex->Truncate();
   1802    return false;
   1803  }
   1804 
   1805  if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
   1806    webgl->ErrorImplementationBug(
   1807        "ANGLE is particular about CopyTexSubImage"
   1808        " formats matching exactly.");
   1809    return false;
   1810  }
   1811 
   1812  webgl->GenerateError(error, "Unexpected error from driver.");
   1813  gfxCriticalError() << "Unexpected error from driver: "
   1814                     << errorText.BeginReading();
   1815  return false;
   1816 }
   1817 
   1818 // CopyTexSubImage if `!respecFormat`
   1819 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
   1820                                GLenum respecFormat, const uvec3& dstOffset,
   1821                                const ivec2& srcOffset, const uvec2& size2) {
   1822  ////////////////////////////////////
   1823  // Get source info
   1824 
   1825  const webgl::FormatUsageInfo* srcUsage;
   1826  uint32_t srcTotalWidth;
   1827  uint32_t srcTotalHeight;
   1828  if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
   1829                                       &srcTotalHeight)) {
   1830    return;
   1831  }
   1832  const auto& srcFormat = srcUsage->format;
   1833 
   1834  if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
   1835    return;
   1836 
   1837  const auto size = uvec3{size2.x, size2.y, 1};
   1838 
   1839  ////////////////////////////////////
   1840  // Get dest info
   1841 
   1842  webgl::ImageInfo* imageInfo;
   1843  const webgl::FormatUsageInfo* dstUsage;
   1844  if (respecFormat) {
   1845    if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
   1846      return;
   1847    MOZ_ASSERT(imageInfo);
   1848 
   1849    dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
   1850    if (!dstUsage) return;
   1851 
   1852    if (!ValidateFormatAndSize(mContext, imageTarget, dstUsage->format, size))
   1853      return;
   1854  } else {
   1855    if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
   1856                                   &imageInfo)) {
   1857      return;
   1858    }
   1859    MOZ_ASSERT(imageInfo);
   1860 
   1861    dstUsage = imageInfo->mFormat;
   1862    MOZ_ASSERT(dstUsage);
   1863  }
   1864 
   1865  const auto& dstFormat = dstUsage->format;
   1866  if (!mContext->IsWebGL2() && dstFormat->d) {
   1867    mContext->ErrorInvalidOperation(
   1868        "Function may not be called with format %s.", dstFormat->name);
   1869    return;
   1870  }
   1871 
   1872  ////////////////////////////////////
   1873  // Check that source and dest info are compatible
   1874 
   1875  if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
   1876 
   1877  ////////////////////////////////////
   1878  // Do the thing!
   1879 
   1880  const bool isSubImage = !respecFormat;
   1881  bool expectsInit = true;
   1882  if (isSubImage) {
   1883    if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
   1884                                             dstOffset, size, imageInfo,
   1885                                             &expectsInit)) {
   1886      return;
   1887    }
   1888  }
   1889 
   1890  if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
   1891                           level, srcOffset.x, srcOffset.y, srcTotalWidth,
   1892                           srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
   1893                           dstOffset.z, size.x, size.y, dstUsage)) {
   1894    Truncate();
   1895    return;
   1896  }
   1897 
   1898  mContext->OnDataAllocCall();
   1899 
   1900  ////////////////////////////////////
   1901  // Update our specification data?
   1902 
   1903  if (respecFormat) {
   1904    constexpr auto uninitializedSlices = std::nullopt;
   1905    const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
   1906                                        uninitializedSlices};
   1907    *imageInfo = newImageInfo;
   1908    InvalidateCaches();
   1909  }
   1910 }
   1911 
   1912 }  // namespace mozilla