tor-browser

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

WebGLTexture.cpp (36175B)


      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 "WebGLTexture.h"
      7 
      8 #include <algorithm>
      9 
     10 #include "GLContext.h"
     11 #include "ScopedGLHelpers.h"
     12 #include "WebGLContext.h"
     13 #include "WebGLContextUtils.h"
     14 #include "WebGLFormats.h"
     15 #include "WebGLFramebuffer.h"
     16 #include "WebGLSampler.h"
     17 #include "WebGLTexelConversions.h"
     18 #include "mozilla/IntegerRange.h"
     19 #include "mozilla/MathAlgorithms.h"
     20 #include "mozilla/ScopeExit.h"
     21 #include "mozilla/dom/WebGLRenderingContextBinding.h"
     22 #include "mozilla/gfx/Logging.h"
     23 
     24 namespace mozilla {
     25 namespace webgl {
     26 
     27 constinit /*static*/ const ImageInfo ImageInfo::kUndefined;
     28 
     29 size_t ImageInfo::MemoryUsage() const {
     30  if (!IsDefined()) return 0;
     31 
     32  size_t samples = mSamples;
     33  if (!samples) {
     34    samples = 1;
     35  }
     36 
     37  const size_t bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
     38  return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * samples *
     39         bytesPerTexel;
     40 }
     41 
     42 Maybe<ImageInfo> ImageInfo::NextMip(const GLenum target) const {
     43  MOZ_ASSERT(IsDefined());
     44 
     45  auto next = *this;
     46 
     47  if (target == LOCAL_GL_TEXTURE_3D) {
     48    if (mWidth <= 1 && mHeight <= 1 && mDepth <= 1) {
     49      return {};
     50    }
     51 
     52    next.mDepth = std::max(uint32_t(1), next.mDepth / 2);
     53  } else {
     54    // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
     55    if (mWidth <= 1 && mHeight <= 1) {
     56      return {};
     57    }
     58  }
     59  if (next.mUninitializedSlices) {
     60    next.mUninitializedSlices.emplace(next.mDepth, true);
     61  }
     62 
     63  next.mWidth = std::max(uint32_t(1), next.mWidth / 2);
     64  next.mHeight = std::max(uint32_t(1), next.mHeight / 2);
     65  return Some(next);
     66 }
     67 
     68 }  // namespace webgl
     69 
     70 ////////////////////////////////////////
     71 
     72 WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex)
     73    : WebGLContextBoundObject(webgl),
     74      mGLName(tex),
     75      mTarget(LOCAL_GL_NONE),
     76      mFaceCount(0),
     77      mImmutable(false),
     78      mImmutableLevelCount(0),
     79      mBaseMipmapLevel(0),
     80      mMaxMipmapLevel(1000) {}
     81 
     82 WebGLTexture::~WebGLTexture() {
     83  for (auto& cur : mImageInfoArr) {
     84    cur = webgl::ImageInfo();
     85  }
     86  InvalidateCaches();
     87 
     88  if (!mContext) return;
     89  mContext->gl->fDeleteTextures(1, &mGLName);
     90 }
     91 
     92 size_t WebGLTexture::MemoryUsage() const {
     93  size_t accum = 0;
     94  for (const auto& cur : mImageInfoArr) {
     95    accum += cur.MemoryUsage();
     96  }
     97  return accum;
     98 }
     99 
    100 // ---------------------------
    101 
    102 void WebGLTexture::PopulateMipChain(const uint32_t maxLevel) {
    103  // Used by GenerateMipmap and TexStorage.
    104  // Populates based on mBaseMipmapLevel.
    105 
    106  auto ref = BaseImageInfo();
    107  MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
    108 
    109  for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
    110    // GLES 3.0.4, p161
    111    // "A cube map texture is mipmap complete if each of the six texture images,
    112    //  considered individually, is mipmap complete."
    113 
    114    for (uint8_t face = 0; face < mFaceCount; face++) {
    115      auto& cur = ImageInfoAtFace(face, level);
    116      cur = ref;
    117    }
    118 
    119    const auto next = ref.NextMip(mTarget.get());
    120    if (!next) break;
    121    ref = next.ref();
    122  }
    123  InvalidateCaches();
    124 }
    125 
    126 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
    127                            TexImageTarget target, uint32_t level,
    128                            const webgl::ImageInfo& info);
    129 
    130 bool WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel,
    131                                        const bool ensureInit,
    132                                        bool* const out_initFailed) const {
    133  *out_initFailed = false;
    134 
    135  // Reference dimensions based on baseLevel.
    136  auto ref = BaseImageInfo();
    137  MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
    138 
    139  for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
    140    // GLES 3.0.4, p161
    141    // "A cube map texture is mipmap complete if each of the six texture images,
    142    // considered individually, is mipmap complete."
    143 
    144    for (uint8_t face = 0; face < mFaceCount; face++) {
    145      auto& cur = ImageInfoAtFace(face, level);
    146 
    147      // "* The set of mipmap arrays `level_base` through `q` (where `q`
    148      //    is defined the "Mipmapping" discussion of section 3.8.10) were
    149      //    each specified with the same effective internal format."
    150 
    151      // "* The dimensions of the arrays follow the sequence described in
    152      //    the "Mipmapping" discussion of section 3.8.10."
    153 
    154      if (cur.mWidth != ref.mWidth || cur.mHeight != ref.mHeight ||
    155          cur.mDepth != ref.mDepth || cur.mFormat != ref.mFormat) {
    156        return false;
    157      }
    158 
    159      if (MOZ_UNLIKELY(ensureInit && cur.mUninitializedSlices)) {
    160        auto imageTarget = mTarget.get();
    161        if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) {
    162          imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
    163        }
    164        if (!ZeroTextureData(mContext, mGLName, imageTarget, level, cur)) {
    165          mContext->ErrorOutOfMemory("Failed to zero tex image data.");
    166          *out_initFailed = true;
    167          return false;
    168        }
    169        cur.mUninitializedSlices.reset();
    170      }
    171    }
    172 
    173    const auto next = ref.NextMip(mTarget.get());
    174    if (!next) break;
    175    ref = next.ref();
    176  }
    177 
    178  return true;
    179 }
    180 
    181 Maybe<const WebGLTexture::CompletenessInfo> WebGLTexture::CalcCompletenessInfo(
    182    const bool ensureInit, const bool skipMips) const {
    183  Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
    184 
    185  // -
    186 
    187  const auto level_base = Es3_level_base();
    188  if (level_base > kMaxLevelCount - 1) {
    189    ret->incompleteReason = "`level_base` too high.";
    190    return ret;
    191  }
    192 
    193  // Texture completeness is established at GLES 3.0.4, p160-161.
    194  // "[A] texture is complete unless any of the following conditions hold true:"
    195 
    196  // "* Any dimension of the `level_base` array is not positive."
    197  const auto& baseImageInfo = ImageInfoAtFace(0, level_base);
    198  if (!baseImageInfo.IsDefined()) {
    199    // In case of undefined texture image, we don't print any message because
    200    // this is a very common and often legitimate case (asynchronous texture
    201    // loading).
    202    ret->incompleteReason = nullptr;
    203    return ret;
    204  }
    205 
    206  if (!baseImageInfo.mWidth || !baseImageInfo.mHeight ||
    207      !baseImageInfo.mDepth) {
    208    ret->incompleteReason =
    209        "The dimensions of `level_base` are not all positive.";
    210    return ret;
    211  }
    212 
    213  // "* The texture is a cube map texture, and is not cube complete."
    214  bool initFailed = false;
    215  if (!IsMipAndCubeComplete(level_base, ensureInit, &initFailed)) {
    216    if (initFailed) return {};
    217 
    218    // Can only fail if not cube-complete.
    219    ret->incompleteReason = "Cubemaps must be \"cube complete\".";
    220    return ret;
    221  }
    222  ret->levels = 1;
    223  ret->usage = baseImageInfo.mFormat;
    224  RefreshSwizzle();
    225 
    226  ret->powerOfTwo = mozilla::IsPowerOfTwo(baseImageInfo.mWidth) &&
    227                    mozilla::IsPowerOfTwo(baseImageInfo.mHeight);
    228  if (mTarget == LOCAL_GL_TEXTURE_3D) {
    229    ret->powerOfTwo &= mozilla::IsPowerOfTwo(baseImageInfo.mDepth);
    230  }
    231 
    232  // -
    233 
    234  if (!mContext->IsWebGL2() && !ret->powerOfTwo) {
    235    // WebGL 1 mipmaps require POT.
    236    ret->incompleteReason = "Mipmapping requires power-of-two sizes.";
    237    return ret;
    238  }
    239 
    240  // "* `level_base <= level_max`"
    241 
    242  const auto level_max = Es3_level_max();
    243  const auto maxLevel_aka_q = Es3_q();
    244  if (level_base > level_max) {  // `level_max` not `q`!
    245    ret->incompleteReason = "`level_base > level_max`.";
    246    return ret;
    247  }
    248 
    249  if (skipMips) return ret;
    250 
    251  if (!IsMipAndCubeComplete(maxLevel_aka_q, ensureInit, &initFailed)) {
    252    if (initFailed) return {};
    253 
    254    ret->incompleteReason = "Bad mipmap dimension or format.";
    255    return ret;
    256  }
    257  ret->levels = AutoAssertCast(maxLevel_aka_q - level_base + 1);
    258  ret->mipmapComplete = true;
    259 
    260  // -
    261 
    262  return ret;
    263 }
    264 
    265 Maybe<const webgl::SampleableInfo> WebGLTexture::CalcSampleableInfo(
    266    const WebGLSampler* const sampler) const {
    267  Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
    268 
    269  const bool ensureInit = true;
    270  const auto completeness = CalcCompletenessInfo(ensureInit);
    271  if (!completeness) return {};
    272 
    273  ret->incompleteReason = completeness->incompleteReason;
    274 
    275  if (!completeness->levels) return ret;
    276 
    277  const auto* sampling = &mSamplingState;
    278  if (sampler) {
    279    sampling = &sampler->State();
    280  }
    281  const auto isDepthTex = bool(completeness->usage->format->d);
    282  ret->isDepthTexCompare = isDepthTex & bool(sampling->compareMode.get());
    283  // Because if it's not a depth texture, we always ignore compareMode.
    284 
    285  const auto& minFilter = sampling->minFilter;
    286  const auto& magFilter = sampling->magFilter;
    287 
    288  // -
    289 
    290  const bool needsMips = (minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST ||
    291                          minFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR ||
    292                          minFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
    293                          minFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR);
    294  if (needsMips & !completeness->mipmapComplete) return ret;
    295 
    296  const bool isMinFilteringNearest =
    297      (minFilter == LOCAL_GL_NEAREST ||
    298       minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST);
    299  const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST);
    300  const bool isFilteringNearestOnly =
    301      (isMinFilteringNearest && isMagFilteringNearest);
    302  if (!isFilteringNearestOnly) {
    303    bool isFilterable = completeness->usage->isFilterable;
    304 
    305    // "* The effective internal format specified for the texture arrays is a
    306    //    sized internal depth or depth and stencil format, the value of
    307    //    TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter
    308    //    is not NEAREST, or the minification filter is neither NEAREST nor
    309    //    NEAREST_MIPMAP_NEAREST."
    310    // [1]: This sounds suspect, but is explicitly noted in the change log for
    311    //      GLES 3.0.1:
    312    //      "* Clarify that a texture is incomplete if it has a depth component,
    313    //         no shadow comparison, and linear filtering (also Bug 9481)."
    314    // In short, depth formats are not filterable, but shadow-samplers are.
    315    if (ret->isDepthTexCompare) {
    316      isFilterable = true;
    317 
    318      if (mContext->mWarnOnce_DepthTexCompareFilterable) {
    319        mContext->mWarnOnce_DepthTexCompareFilterable = false;
    320        mContext->GenerateWarning(
    321            "Depth texture comparison requests (e.g. `LINEAR`) Filtering, but"
    322            " behavior is implementation-defined, and so on some systems will"
    323            " sometimes behave as `NEAREST`. (warns once)");
    324      }
    325    }
    326 
    327    // "* The effective internal format specified for the texture arrays is a
    328    //    sized internal color format that is not texture-filterable, and either
    329    //    the magnification filter is not NEAREST or the minification filter is
    330    //    neither NEAREST nor NEAREST_MIPMAP_NEAREST."
    331    // Since all (GLES3) unsized color formats are filterable just like their
    332    // sized equivalents, we don't have to care whether its sized or not.
    333    if (!isFilterable) {
    334      ret->incompleteReason =
    335          "Minification or magnification filtering is not"
    336          " NEAREST or NEAREST_MIPMAP_NEAREST, and the"
    337          " texture's format is not \"texture-filterable\".";
    338      return ret;
    339    }
    340  }
    341 
    342  // Texture completeness is effectively (though not explicitly) amended for
    343  // GLES2 by the "Texture Access" section under $3.8 "Fragment Shaders". This
    344  // also applies to vertex shaders, as noted on GLES 2.0.25, p41.
    345  if (!mContext->IsWebGL2() && !completeness->powerOfTwo) {
    346    // GLES 2.0.25, p87-88:
    347    // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1)
    348    // if
    349    //  any of the following conditions are true:"
    350 
    351    // "* A two-dimensional sampler is called, the minification filter is one
    352    //    that requires a mipmap[...], and the sampler's associated texture
    353    //    object is not complete[.]"
    354    // (already covered)
    355 
    356    // "* A two-dimensional sampler is called, the minification filter is
    357    //    not one that requires a mipmap (either NEAREST nor[sic] LINEAR), and
    358    //    either dimension of the level zero array of the associated texture
    359    //    object is not positive."
    360    // (already covered)
    361 
    362    // "* A two-dimensional sampler is called, the corresponding texture
    363    //    image is a non-power-of-two image[...], and either the texture wrap
    364    //    mode is not CLAMP_TO_EDGE, or the minification filter is neither
    365    //    NEAREST nor LINEAR."
    366 
    367    // "* A cube map sampler is called, any of the corresponding texture
    368    //    images are non-power-of-two images, and either the texture wrap mode
    369    //    is not CLAMP_TO_EDGE, or the minification filter is neither NEAREST
    370    //    nor LINEAR."
    371    // "either the texture wrap mode is not CLAMP_TO_EDGE"
    372    if (sampling->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
    373        sampling->wrapT != LOCAL_GL_CLAMP_TO_EDGE) {
    374      ret->incompleteReason =
    375          "Non-power-of-two textures must have a wrap mode of"
    376          " CLAMP_TO_EDGE.";
    377      return ret;
    378    }
    379 
    380    // "* A cube map sampler is called, and either the corresponding cube
    381    //    map texture image is not cube complete, or TEXTURE_MIN_FILTER is one
    382    //    that requires a mipmap and the texture is not mipmap cube complete."
    383    // (already covered)
    384  }
    385 
    386  // Mark complete.
    387  ret->incompleteReason =
    388      nullptr;  // NB: incompleteReason is also null for undefined
    389  ret->levels = completeness->levels;  //   textures.
    390  if (!needsMips && ret->levels) {
    391    ret->levels = 1;
    392  }
    393  ret->usage = completeness->usage;
    394  return ret;
    395 }
    396 
    397 const webgl::SampleableInfo* WebGLTexture::GetSampleableInfo(
    398    const WebGLSampler* const sampler) const {
    399  auto itr = mSamplingCache.Find(sampler);
    400  if (!itr) {
    401    const auto info = CalcSampleableInfo(sampler);
    402    if (!info) return nullptr;
    403 
    404    auto entry = mSamplingCache.MakeEntry(sampler, info.value());
    405    entry->AddInvalidator(*this);
    406    if (sampler) {
    407      entry->AddInvalidator(*sampler);
    408    }
    409    itr = mSamplingCache.Insert(std::move(entry));
    410  }
    411  return itr;
    412 }
    413 
    414 // ---------------------------
    415 
    416 uint32_t WebGLTexture::Es3_q() const {
    417  const auto& imageInfo = BaseImageInfo();
    418  if (!imageInfo.IsDefined()) return mBaseMipmapLevel;
    419 
    420  uint32_t largestDim = std::max(imageInfo.mWidth, imageInfo.mHeight);
    421  if (mTarget == LOCAL_GL_TEXTURE_3D) {
    422    largestDim = std::max(largestDim, imageInfo.mDepth);
    423  }
    424  if (!largestDim) return mBaseMipmapLevel;
    425 
    426  // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
    427  const auto numLevels = FloorLog2Size(largestDim) + 1;
    428 
    429  const auto maxLevelBySize = mBaseMipmapLevel + numLevels - 1;
    430  return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
    431 }
    432 
    433 // -
    434 
    435 static void SetSwizzle(gl::GLContext* gl, TexTarget target,
    436                       const GLint* swizzle) {
    437  static const GLint kNoSwizzle[4] = {LOCAL_GL_RED, LOCAL_GL_GREEN,
    438                                      LOCAL_GL_BLUE, LOCAL_GL_ALPHA};
    439  if (!swizzle) {
    440    swizzle = kNoSwizzle;
    441  } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
    442    MOZ_CRASH("GFX: Needs swizzle feature to swizzle!");
    443  }
    444 
    445  gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
    446  gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
    447  gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
    448  gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
    449 }
    450 
    451 void WebGLTexture::RefreshSwizzle() const {
    452  const auto& imageInfo = BaseImageInfo();
    453  const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA;
    454 
    455  if (swizzle != mCurSwizzle) {
    456    const gl::ScopedBindTexture scopeBindTexture(mContext->gl, mGLName,
    457                                                 mTarget.get());
    458    SetSwizzle(mContext->gl, mTarget, swizzle);
    459    mCurSwizzle = swizzle;
    460  }
    461 }
    462 
    463 bool WebGLTexture::EnsureImageDataInitialized(const TexImageTarget target,
    464                                              const uint32_t level) {
    465  auto& imageInfo = ImageInfoAt(target, level);
    466  if (!imageInfo.IsDefined()) return true;
    467 
    468  if (!imageInfo.mUninitializedSlices) return true;
    469 
    470  if (!ZeroTextureData(mContext, mGLName, target, level, imageInfo)) {
    471    return false;
    472  }
    473  imageInfo.mUninitializedSlices.reset();
    474  return true;
    475 }
    476 
    477 static bool ClearDepthTexture(const WebGLContext& webgl, const GLuint tex,
    478                              const TexImageTarget imageTarget,
    479                              const uint32_t level,
    480                              const webgl::ImageInfo& info) {
    481  const auto& gl = webgl.gl;
    482  const auto& usage = info.mFormat;
    483  const auto& format = usage->format;
    484 
    485  // Depth resources actually clear to 1.0f, not 0.0f!
    486  // They are also always renderable.
    487  MOZ_ASSERT(usage->IsRenderable());
    488  MOZ_ASSERT(info.mUninitializedSlices);
    489 
    490  GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
    491  GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT;
    492 
    493  if (format->s) {
    494    attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT;
    495    clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
    496  }
    497 
    498  // -
    499 
    500  gl::ScopedFramebuffer scopedFB(gl);
    501  const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
    502  const webgl::ScopedPrepForResourceClear scopedPrep(webgl);
    503 
    504  const auto fnAttach = [&](const uint32_t z) {
    505    switch (imageTarget.get()) {
    506      case LOCAL_GL_TEXTURE_3D:
    507      case LOCAL_GL_TEXTURE_2D_ARRAY:
    508        gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint, tex,
    509                                     level, z);
    510        break;
    511      default:
    512        if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
    513          gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
    514                                    LOCAL_GL_DEPTH_ATTACHMENT,
    515                                    imageTarget.get(), tex, level);
    516          gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
    517                                    LOCAL_GL_STENCIL_ATTACHMENT,
    518                                    imageTarget.get(), tex, level);
    519        } else {
    520          gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint,
    521                                    imageTarget.get(), tex, level);
    522        }
    523        break;
    524    }
    525  };
    526 
    527  for (const auto z : IntegerRange(info.mDepth)) {
    528    if ((*info.mUninitializedSlices)[z]) {
    529      fnAttach(z);
    530      gl->fClear(clearBits);
    531    }
    532  }
    533  const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
    534  const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
    535  MOZ_ASSERT(isComplete);
    536  return isComplete;
    537 }
    538 
    539 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
    540                            TexImageTarget target, uint32_t level,
    541                            const webgl::ImageInfo& info) {
    542  // This has one usecase:
    543  // Lazy zeroing of uninitialized textures:
    544  //    a. Before draw.
    545  //    b. Before partial upload. (TexStorage + TexSubImage)
    546 
    547  // We have no sympathy for this case.
    548 
    549  // "Doctor, it hurts when I do this!" "Well don't do that!"
    550  MOZ_ASSERT(info.mUninitializedSlices);
    551 
    552  const auto targetStr = EnumString(target.get());
    553  webgl->GenerateWarning(
    554      "Tex image %s level %u is incurring lazy initialization.",
    555      targetStr.c_str(), level);
    556 
    557  gl::GLContext* gl = webgl->GL();
    558  const auto& width = info.mWidth;
    559  const auto& height = info.mHeight;
    560  const auto& depth = info.mDepth;
    561  const auto& usage = info.mFormat;
    562 
    563  GLenum scopeBindTarget;
    564  switch (target.get()) {
    565    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    566    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    567    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    568    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    569    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    570    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
    571      scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
    572      break;
    573    default:
    574      scopeBindTarget = target.get();
    575      break;
    576  }
    577  const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
    578  const auto& compression = usage->format->compression;
    579  if (compression) {
    580    auto sizedFormat = usage->format->sizedFormat;
    581    MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
    582 
    583    const auto fnSizeInBlocks = [](CheckedUint32 pixels,
    584                                   uint8_t pixelsPerBlock) {
    585      return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
    586    };
    587 
    588    const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
    589    const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
    590 
    591    CheckedUint32 checkedByteCount = compression->bytesPerBlock;
    592    checkedByteCount *= widthBlocks;
    593    checkedByteCount *= heightBlocks;
    594 
    595    if (!checkedByteCount.isValid()) return false;
    596 
    597    const size_t sliceByteCount = checkedByteCount.value();
    598 
    599    const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
    600    if (!zeros) return false;
    601 
    602    // Don't bother with striding it well.
    603    // TODO: We shouldn't need to do this for CompressedTexSubImage.
    604    webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
    605    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
    606    const auto revert = MakeScopeExit(
    607        [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
    608 
    609    GLenum error = 0;
    610    for (const auto z : IntegerRange(depth)) {
    611      if ((*info.mUninitializedSlices)[z]) {
    612        error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, z, width,
    613                                        height, 1, sizedFormat, sliceByteCount,
    614                                        zeros.get());
    615        if (error) break;
    616      }
    617    }
    618    return !error;
    619  }
    620 
    621  const auto driverUnpackInfo = usage->idealUnpack;
    622  MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
    623 
    624  if (usage->format->d) {
    625    // ANGLE_depth_texture does not allow uploads, so we have to clear.
    626    // (Restriction because of D3D9)
    627    // Also, depth resources are cleared to 1.0f and are always renderable, so
    628    // just use FB clears.
    629    return ClearDepthTexture(*webgl, tex, target, level, info);
    630  }
    631 
    632  const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
    633 
    634  const auto bytesPerPixel = webgl::BytesPerPixel(packing);
    635 
    636  CheckedUint32 checkedByteCount = bytesPerPixel;
    637  checkedByteCount *= width;
    638  checkedByteCount *= height;
    639 
    640  if (!checkedByteCount.isValid()) return false;
    641 
    642  const size_t sliceByteCount = checkedByteCount.value();
    643 
    644  const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
    645  if (!zeros) return false;
    646 
    647  // Don't bother with striding it well.
    648  webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
    649  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
    650  const auto revert =
    651      MakeScopeExit([&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
    652 
    653  GLenum error = 0;
    654  for (const auto z : IntegerRange(depth)) {
    655    if ((*info.mUninitializedSlices)[z]) {
    656      error = DoTexSubImage(gl, target, level, 0, 0, z, width, height, 1,
    657                            packing, zeros.get());
    658      if (error) break;
    659    }
    660  }
    661  return !error;
    662 }
    663 
    664 void WebGLTexture::ClampLevelBaseAndMax() {
    665  if (!mImmutable) return;
    666 
    667  // GLES 3.0.4, p158:
    668  // "For immutable-format textures, `level_base` is clamped to the range
    669  //  `[0, levels-1]`, `level_max` is then clamped to the range `
    670  //  `[level_base, levels-1]`, where `levels` is the parameter passed to
    671  //   TexStorage* for the texture object."
    672  MOZ_ASSERT(mImmutableLevelCount > 0);
    673  const auto oldBase = mBaseMipmapLevel;
    674  const auto oldMax = mMaxMipmapLevel;
    675  mBaseMipmapLevel =
    676      std::clamp(mBaseMipmapLevel, 0u, mImmutableLevelCount - 1u);
    677  mMaxMipmapLevel =
    678      std::clamp(mMaxMipmapLevel, mBaseMipmapLevel, mImmutableLevelCount - 1u);
    679  if (oldBase != mBaseMipmapLevel &&
    680      mBaseMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
    681    mBaseMipmapLevelState = MIPMAP_LEVEL_DIRTY;
    682  }
    683  if (oldMax != mMaxMipmapLevel &&
    684      mMaxMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
    685    mMaxMipmapLevelState = MIPMAP_LEVEL_DIRTY;
    686  }
    687 
    688  // Note: This means that immutable textures are *always* texture-complete!
    689 }
    690 
    691 //////////////////////////////////////////////////////////////////////////////////////////
    692 // GL calls
    693 
    694 bool WebGLTexture::BindTexture(TexTarget texTarget) {
    695  const bool isFirstBinding = !mTarget;
    696  if (!isFirstBinding && mTarget != texTarget) {
    697    mContext->ErrorInvalidOperation(
    698        "bindTexture: This texture has already been bound"
    699        " to a different target.");
    700    return false;
    701  }
    702 
    703  mTarget = texTarget;
    704 
    705  mContext->gl->fBindTexture(mTarget.get(), mGLName);
    706 
    707  if (isFirstBinding) {
    708    mFaceCount = IsCubeMap() ? 6 : 1;
    709 
    710    gl::GLContext* gl = mContext->gl;
    711 
    712    // Thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R
    713    // is not present in GLES 2, but is present in GL and it seems as if for
    714    // cube maps we need to set it to GL_CLAMP_TO_EDGE to get the expected
    715    // GLES behavior.
    716    // If we are WebGL 2 though, we'll want to leave it as REPEAT.
    717    const bool hasWrapR = gl->IsSupported(gl::GLFeature::texture_3D);
    718    if (IsCubeMap() && hasWrapR && !mContext->IsWebGL2()) {
    719      gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R,
    720                         LOCAL_GL_CLAMP_TO_EDGE);
    721    }
    722  }
    723 
    724  return true;
    725 }
    726 
    727 static constexpr GLint ClampMipmapLevelForDriver(uint32_t level) {
    728  return static_cast<GLint>(
    729      std::clamp(level, 0u, (uint32_t)WebGLTexture::kMaxLevelCount));
    730 }
    731 
    732 void WebGLTexture::GenerateMipmap() {
    733  // GLES 3.0.4 p160:
    734  // "Mipmap generation replaces texel array levels level base + 1 through q
    735  //  with arrrays derived from the level base array, regardless of their
    736  //  previous contents. All other mipmap arrays, including the level base
    737  //  array, are left unchanged by this computation."
    738  // But only check and init the base level.
    739  const bool ensureInit = true;
    740  const bool skipMips = true;
    741  const auto completeness = CalcCompletenessInfo(ensureInit, skipMips);
    742  if (!completeness || !completeness->levels) {
    743    mContext->ErrorInvalidOperation(
    744        "The texture's base level must be complete.");
    745    return;
    746  }
    747  const auto& usage = completeness->usage;
    748  const auto& format = usage->format;
    749  if (!mContext->IsWebGL2()) {
    750    if (!completeness->powerOfTwo) {
    751      mContext->ErrorInvalidOperation(
    752          "The base level of the texture does not"
    753          " have power-of-two dimensions.");
    754      return;
    755    }
    756    if (format->isSRGB) {
    757      mContext->ErrorInvalidOperation(
    758          "EXT_sRGB forbids GenerateMipmap with"
    759          " sRGB.");
    760      return;
    761    }
    762  }
    763 
    764  if (format->compression) {
    765    mContext->ErrorInvalidOperation(
    766        "Texture data at base level is compressed.");
    767    return;
    768  }
    769 
    770  if (format->d) {
    771    mContext->ErrorInvalidOperation("Depth textures are not supported.");
    772    return;
    773  }
    774 
    775  // OpenGL ES 3.0.4 p160:
    776  // If the level base array was not specified with an unsized internal format
    777  // from table 3.3 or a sized internal format that is both color-renderable and
    778  // texture-filterable according to table 3.13, an INVALID_OPERATION error
    779  // is generated.
    780  bool canGenerateMipmap = (usage->IsRenderable() && usage->isFilterable);
    781  switch (usage->format->effectiveFormat) {
    782    case webgl::EffectiveFormat::Luminance8:
    783    case webgl::EffectiveFormat::Alpha8:
    784    case webgl::EffectiveFormat::Luminance8Alpha8:
    785      // Non-color-renderable formats from Table 3.3.
    786      canGenerateMipmap = true;
    787      break;
    788    default:
    789      break;
    790  }
    791 
    792  if (!canGenerateMipmap) {
    793    mContext->ErrorInvalidOperation(
    794        "Texture at base level is not unsized"
    795        " internal format or is not"
    796        " color-renderable or texture-filterable.");
    797    return;
    798  }
    799 
    800  if (usage->IsRenderable() && !usage->IsExplicitlyRenderable()) {
    801    mContext->WarnIfImplicit(usage->GetExtensionID());
    802  }
    803 
    804  // Done with validation. Do the operation.
    805 
    806  gl::GLContext* gl = mContext->gl;
    807 
    808  if (gl->WorkAroundDriverBugs()) {
    809    // If we first set GL_TEXTURE_BASE_LEVEL to a number such as 20, then set
    810    // MGL_TEXTURE_MAX_LEVEL to a smaller number like 8, our copy of the
    811    // base level will be lowered, but we havn't yet updated the driver, we
    812    // should do so now, before calling glGenerateMipmap().
    813    if (mBaseMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
    814      gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_BASE_LEVEL,
    815                         ClampMipmapLevelForDriver(mBaseMipmapLevel));
    816      mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
    817    }
    818    if (mMaxMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
    819      gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MAX_LEVEL,
    820                         ClampMipmapLevelForDriver(mMaxMipmapLevel));
    821      mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
    822    }
    823 
    824    // bug 696495 - to work around failures in the texture-mips.html test on
    825    // various drivers, we set the minification filter before calling
    826    // glGenerateMipmap. This should not carry a significant performance
    827    // overhead so we do it unconditionally.
    828    //
    829    // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See
    830    // Chromium bug 101105.
    831    gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
    832                       LOCAL_GL_NEAREST_MIPMAP_NEAREST);
    833    gl->fGenerateMipmap(mTarget.get());
    834    gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
    835                       mSamplingState.minFilter.get());
    836  } else {
    837    gl->fGenerateMipmap(mTarget.get());
    838  }
    839 
    840  // Record the results.
    841 
    842  const auto maxLevel = Es3_q();
    843  PopulateMipChain(maxLevel);
    844 }
    845 
    846 Maybe<double> WebGLTexture::GetTexParameter(GLenum pname) const {
    847  GLint i = 0;
    848  GLfloat f = 0.0f;
    849 
    850  switch (pname) {
    851    case LOCAL_GL_TEXTURE_BASE_LEVEL:
    852      return Some(mBaseMipmapLevel);
    853 
    854    case LOCAL_GL_TEXTURE_MAX_LEVEL:
    855      return Some(mMaxMipmapLevel);
    856 
    857    case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
    858      return Some(mImmutable);
    859 
    860    case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
    861      return Some(uint32_t(mImmutableLevelCount));
    862 
    863    case LOCAL_GL_TEXTURE_MIN_FILTER:
    864    case LOCAL_GL_TEXTURE_MAG_FILTER:
    865    case LOCAL_GL_TEXTURE_WRAP_S:
    866    case LOCAL_GL_TEXTURE_WRAP_T:
    867    case LOCAL_GL_TEXTURE_WRAP_R:
    868    case LOCAL_GL_TEXTURE_COMPARE_MODE:
    869    case LOCAL_GL_TEXTURE_COMPARE_FUNC: {
    870      MOZ_ASSERT(mTarget);
    871      const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
    872      mContext->gl->fGetTexParameteriv(mTarget.get(), pname, &i);
    873      return Some(i);
    874    }
    875 
    876    case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
    877    case LOCAL_GL_TEXTURE_MAX_LOD:
    878    case LOCAL_GL_TEXTURE_MIN_LOD: {
    879      MOZ_ASSERT(mTarget);
    880      const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
    881      mContext->gl->fGetTexParameterfv(mTarget.get(), pname, &f);
    882      return Some(f);
    883    }
    884 
    885    default:
    886      MOZ_CRASH("GFX: Unhandled pname.");
    887  }
    888 }
    889 
    890 // Here we have to support all pnames with both int and float params.
    891 // See this discussion:
    892 //   https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
    893 void WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname,
    894                                const FloatOrInt& param) {
    895  bool isPNameValid = false;
    896  switch (pname) {
    897    // GLES 2.0.25 p76:
    898    case LOCAL_GL_TEXTURE_WRAP_S:
    899    case LOCAL_GL_TEXTURE_WRAP_T:
    900    case LOCAL_GL_TEXTURE_MIN_FILTER:
    901    case LOCAL_GL_TEXTURE_MAG_FILTER:
    902      isPNameValid = true;
    903      break;
    904 
    905    // GLES 3.0.4 p149-150:
    906    case LOCAL_GL_TEXTURE_BASE_LEVEL:
    907    case LOCAL_GL_TEXTURE_COMPARE_MODE:
    908    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
    909    case LOCAL_GL_TEXTURE_MAX_LEVEL:
    910    case LOCAL_GL_TEXTURE_MAX_LOD:
    911    case LOCAL_GL_TEXTURE_MIN_LOD:
    912    case LOCAL_GL_TEXTURE_WRAP_R:
    913      if (mContext->IsWebGL2()) isPNameValid = true;
    914      break;
    915 
    916    case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
    917      if (mContext->IsExtensionEnabled(
    918              WebGLExtensionID::EXT_texture_filter_anisotropic))
    919        isPNameValid = true;
    920      break;
    921  }
    922 
    923  if (!isPNameValid) {
    924    mContext->ErrorInvalidEnumInfo("texParameter: pname", pname);
    925    return;
    926  }
    927 
    928  ////////////////
    929  // Validate params and invalidate if needed.
    930 
    931  bool paramBadEnum = false;
    932  bool paramBadValue = false;
    933 
    934  switch (pname) {
    935    case LOCAL_GL_TEXTURE_BASE_LEVEL:
    936    case LOCAL_GL_TEXTURE_MAX_LEVEL:
    937      paramBadValue = (param.i < 0);
    938      break;
    939 
    940    case LOCAL_GL_TEXTURE_COMPARE_MODE:
    941      paramBadValue = (param.i != LOCAL_GL_NONE &&
    942                       param.i != LOCAL_GL_COMPARE_REF_TO_TEXTURE);
    943      break;
    944 
    945    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
    946      switch (param.i) {
    947        case LOCAL_GL_LEQUAL:
    948        case LOCAL_GL_GEQUAL:
    949        case LOCAL_GL_LESS:
    950        case LOCAL_GL_GREATER:
    951        case LOCAL_GL_EQUAL:
    952        case LOCAL_GL_NOTEQUAL:
    953        case LOCAL_GL_ALWAYS:
    954        case LOCAL_GL_NEVER:
    955          break;
    956 
    957        default:
    958          paramBadValue = true;
    959          break;
    960      }
    961      break;
    962 
    963    case LOCAL_GL_TEXTURE_MIN_FILTER:
    964      switch (param.i) {
    965        case LOCAL_GL_NEAREST:
    966        case LOCAL_GL_LINEAR:
    967        case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
    968        case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
    969        case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
    970        case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
    971          break;
    972 
    973        default:
    974          paramBadEnum = true;
    975          break;
    976      }
    977      break;
    978 
    979    case LOCAL_GL_TEXTURE_MAG_FILTER:
    980      switch (param.i) {
    981        case LOCAL_GL_NEAREST:
    982        case LOCAL_GL_LINEAR:
    983          break;
    984 
    985        default:
    986          paramBadEnum = true;
    987          break;
    988      }
    989      break;
    990 
    991    case LOCAL_GL_TEXTURE_WRAP_S:
    992    case LOCAL_GL_TEXTURE_WRAP_T:
    993    case LOCAL_GL_TEXTURE_WRAP_R:
    994      switch (param.i) {
    995        case LOCAL_GL_CLAMP_TO_EDGE:
    996        case LOCAL_GL_MIRRORED_REPEAT:
    997        case LOCAL_GL_REPEAT:
    998          break;
    999 
   1000        default:
   1001          paramBadEnum = true;
   1002          break;
   1003      }
   1004      break;
   1005 
   1006    case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
   1007      if (param.f < 1.0f) paramBadValue = true;
   1008 
   1009      break;
   1010  }
   1011 
   1012  if (paramBadEnum) {
   1013    if (!param.isFloat) {
   1014      mContext->ErrorInvalidEnum(
   1015          "pname 0x%04x: Invalid param"
   1016          " 0x%04x.",
   1017          pname, param.i);
   1018    } else {
   1019      mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", pname,
   1020                                 param.f);
   1021    }
   1022    return;
   1023  }
   1024 
   1025  if (paramBadValue) {
   1026    if (!param.isFloat) {
   1027      mContext->ErrorInvalidValue(
   1028          "pname 0x%04x: Invalid param %i"
   1029          " (0x%x).",
   1030          pname, param.i, param.i);
   1031    } else {
   1032      mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", pname,
   1033                                  param.f);
   1034    }
   1035    return;
   1036  }
   1037 
   1038  ////////////////
   1039  // Store any needed values
   1040 
   1041  FloatOrInt clamped = param;
   1042  bool invalidate = true;
   1043  switch (pname) {
   1044    case LOCAL_GL_TEXTURE_BASE_LEVEL: {
   1045      mBaseMipmapLevel = clamped.i;
   1046      mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
   1047      ClampLevelBaseAndMax();
   1048      clamped = FloatOrInt(ClampMipmapLevelForDriver(mBaseMipmapLevel));
   1049      break;
   1050    }
   1051 
   1052    case LOCAL_GL_TEXTURE_MAX_LEVEL: {
   1053      mMaxMipmapLevel = clamped.i;
   1054      mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
   1055      ClampLevelBaseAndMax();
   1056      clamped = FloatOrInt(ClampMipmapLevelForDriver(mMaxMipmapLevel));
   1057      break;
   1058    }
   1059 
   1060    case LOCAL_GL_TEXTURE_MIN_FILTER:
   1061      mSamplingState.minFilter = clamped.i;
   1062      break;
   1063 
   1064    case LOCAL_GL_TEXTURE_MAG_FILTER:
   1065      mSamplingState.magFilter = clamped.i;
   1066      break;
   1067 
   1068    case LOCAL_GL_TEXTURE_WRAP_S:
   1069      mSamplingState.wrapS = clamped.i;
   1070      break;
   1071 
   1072    case LOCAL_GL_TEXTURE_WRAP_T:
   1073      mSamplingState.wrapT = clamped.i;
   1074      break;
   1075 
   1076    case LOCAL_GL_TEXTURE_COMPARE_MODE:
   1077      mSamplingState.compareMode = clamped.i;
   1078      break;
   1079 
   1080    default:
   1081      invalidate = false;  // Texture completeness will not change.
   1082      break;
   1083  }
   1084 
   1085  if (invalidate) {
   1086    InvalidateCaches();
   1087  }
   1088 
   1089  ////////////////
   1090 
   1091  if (!clamped.isFloat) {
   1092    mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i);
   1093  } else {
   1094    mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f);
   1095  }
   1096 }
   1097 
   1098 void WebGLTexture::Truncate() {
   1099  for (auto& cur : mImageInfoArr) {
   1100    cur = {};
   1101  }
   1102  InvalidateCaches();
   1103 }
   1104 
   1105 }  // namespace mozilla