tor-browser

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

WebGLContextDraw.cpp (43334B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include <algorithm>
      7 
      8 #include "GLContext.h"
      9 #include "MozFramebuffer.h"
     10 #include "WebGLBuffer.h"
     11 #include "WebGLContext.h"
     12 #include "WebGLContextUtils.h"
     13 #include "WebGLFormats.h"
     14 #include "WebGLFramebuffer.h"
     15 #include "WebGLProgram.h"
     16 #include "WebGLRenderbuffer.h"
     17 #include "WebGLShader.h"
     18 #include "WebGLTexture.h"
     19 #include "WebGLTransformFeedback.h"
     20 #include "WebGLVertexArray.h"
     21 #include "mozilla/CheckedInt.h"
     22 #include "mozilla/ProfilerLabels.h"
     23 #include "mozilla/ScopeExit.h"
     24 #include "mozilla/StaticPrefs_webgl.h"
     25 #include "nsPrintfCString.h"
     26 
     27 namespace mozilla {
     28 
     29 // For a Tegra workaround.
     30 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
     31 
     32 ////////////////////////////////////////
     33 
     34 class ScopedResolveTexturesForDraw {
     35  struct TexRebindRequest {
     36    uint32_t texUnit;
     37    WebGLTexture* tex;
     38  };
     39 
     40  WebGLContext* const mWebGL;
     41  std::vector<TexRebindRequest> mRebindRequests;
     42 
     43 public:
     44  ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
     45  ~ScopedResolveTexturesForDraw();
     46 };
     47 
     48 static bool ValidateNoSamplingFeedback(const WebGLTexture& tex,
     49                                       const uint32_t sampledLevels,
     50                                       const WebGLFramebuffer* const fb,
     51                                       const uint32_t texUnit) {
     52  if (!fb) return true;
     53 
     54  const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
     55  for (const auto& attach : texAttachments) {
     56    if (attach->Texture() != &tex) continue;
     57 
     58    const auto& srcBase = tex.Es3_level_base();
     59    const auto srcLast = srcBase + sampledLevels - 1;
     60    const auto& dstLevel = attach->MipLevel();
     61    if (MOZ_UNLIKELY(srcBase <= dstLevel && dstLevel <= srcLast)) {
     62      const auto& webgl = tex.mContext;
     63      const auto& texTargetStr = EnumString(tex.Target().get());
     64      const auto& attachStr = EnumString(attach->mAttachmentPoint);
     65      webgl->ErrorInvalidOperation(
     66          "Texture level %u would be read by %s unit %u,"
     67          " but written by framebuffer attachment %s,"
     68          " which would be illegal feedback.",
     69          dstLevel, texTargetStr.c_str(), texUnit, attachStr.c_str());
     70      return false;
     71    }
     72  }
     73  return true;
     74 }
     75 
     76 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(
     77    WebGLContext* webgl, bool* const out_error)
     78    : mWebGL(webgl) {
     79  const auto& fb = mWebGL->mBoundDrawFramebuffer;
     80 
     81  struct SamplerByTexUnit {
     82    uint8_t texUnit;
     83    const webgl::SamplerUniformInfo* sampler;
     84  };
     85  Vector<SamplerByTexUnit, 8> samplerByTexUnit;
     86 
     87  MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
     88  const auto& samplerUniforms = mWebGL->mActiveProgramLinkInfo->samplerUniforms;
     89  for (const auto& pUniform : samplerUniforms) {
     90    const auto& uniform = *pUniform;
     91    const auto& texList = uniform.texListForType;
     92 
     93    const auto& uniformBaseType = uniform.texBaseType;
     94    for (const auto& texUnit : uniform.texUnits) {
     95      MOZ_ASSERT(texUnit < texList.Length());
     96 
     97      {
     98        decltype(SamplerByTexUnit::sampler) prevSamplerForTexUnit = nullptr;
     99        for (const auto& cur : samplerByTexUnit) {
    100          if (cur.texUnit == texUnit) {
    101            prevSamplerForTexUnit = cur.sampler;
    102          }
    103        }
    104        if (!prevSamplerForTexUnit) {
    105          prevSamplerForTexUnit = &uniform;
    106          MOZ_RELEASE_ASSERT(samplerByTexUnit.append(
    107              SamplerByTexUnit{texUnit, prevSamplerForTexUnit}));
    108        }
    109 
    110        if (MOZ_UNLIKELY(&uniform.texListForType !=
    111                         &prevSamplerForTexUnit->texListForType)) {
    112          // Pointing to different tex lists means different types!
    113          const auto linkInfo = mWebGL->mActiveProgramLinkInfo;
    114          const auto LocInfoBySampler = [&](const webgl::SamplerUniformInfo* p)
    115              -> const webgl::LocationInfo* {
    116            for (const auto& pair : linkInfo->locationMap) {
    117              const auto& locInfo = pair.second;
    118              if (locInfo.samplerInfo == p) {
    119                return &locInfo;
    120              }
    121            }
    122            MOZ_CRASH("Can't find sampler location.");
    123          };
    124          const auto& cur = *LocInfoBySampler(&uniform);
    125          const auto& prev = *LocInfoBySampler(prevSamplerForTexUnit);
    126          mWebGL->ErrorInvalidOperation(
    127              "Tex unit %u referenced by samplers of different types:"
    128              " %s (via %s) and %s (via %s).",
    129              texUnit, EnumString(cur.info.info.elemType).c_str(),
    130              cur.PrettyName().c_str(),
    131              EnumString(prev.info.info.elemType).c_str(),
    132              prev.PrettyName().c_str());
    133          *out_error = true;
    134          return;
    135        }
    136      }
    137 
    138      const auto& tex = texList[texUnit];
    139      if (!tex) continue;
    140 
    141      const auto& sampler = mWebGL->mBoundSamplers[texUnit];
    142      const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
    143      if (MOZ_UNLIKELY(!samplingInfo)) {  // There was an error.
    144        *out_error = true;
    145        return;
    146      }
    147      if (MOZ_UNLIKELY(!samplingInfo->IsComplete())) {
    148        if (samplingInfo->incompleteReason) {
    149          const auto& targetName = GetEnumName(tex->Target().get());
    150          mWebGL->GenerateWarning("%s at unit %u is incomplete: %s", targetName,
    151                                  texUnit, samplingInfo->incompleteReason);
    152        }
    153        mRebindRequests.push_back({texUnit, tex});
    154        continue;
    155      }
    156 
    157      // We have more validation to do if we're otherwise complete:
    158      const auto& texBaseType = samplingInfo->usage->format->baseType;
    159      if (MOZ_UNLIKELY(texBaseType != uniformBaseType)) {
    160        const auto& targetName = GetEnumName(tex->Target().get());
    161        const auto& srcType = ToString(texBaseType);
    162        const auto& dstType = ToString(uniformBaseType);
    163        mWebGL->ErrorInvalidOperation(
    164            "%s at unit %u is of type %s, but"
    165            " the shader samples as %s.",
    166            targetName, texUnit, srcType, dstType);
    167        *out_error = true;
    168        return;
    169      }
    170 
    171      if (MOZ_UNLIKELY(uniform.isShadowSampler !=
    172                       samplingInfo->isDepthTexCompare)) {
    173        const auto& targetName = GetEnumName(tex->Target().get());
    174        mWebGL->ErrorInvalidOperation(
    175            "%s at unit %u is%s a depth texture"
    176            " with TEXTURE_COMPARE_MODE, but"
    177            " the shader sampler is%s a shadow"
    178            " sampler.",
    179            targetName, texUnit, samplingInfo->isDepthTexCompare ? "" : " not",
    180            uniform.isShadowSampler ? "" : " not");
    181        *out_error = true;
    182        return;
    183      }
    184 
    185      if (MOZ_UNLIKELY(!ValidateNoSamplingFeedback(*tex, samplingInfo->levels,
    186                                                   fb.get(), texUnit))) {
    187        *out_error = true;
    188        return;
    189      }
    190    }
    191  }
    192 
    193  const auto& gl = mWebGL->gl;
    194  for (const auto& itr : mRebindRequests) {
    195    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
    196    GLuint incompleteTex = 0;  // Tex 0 is always incomplete.
    197    const auto& overrideTex = webgl->mIncompleteTexOverride;
    198    if (overrideTex) {
    199      // In all but the simplest cases, this will be incomplete anyway, since
    200      // e.g. int-samplers need int-textures. This is useful for e.g.
    201      // dom-to-texture failures, though.
    202      incompleteTex = overrideTex->name;
    203    }
    204    gl->fBindTexture(itr.tex->Target().get(), incompleteTex);
    205  }
    206 }
    207 
    208 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw() {
    209  if (mRebindRequests.empty()) return;
    210 
    211  gl::GLContext* gl = mWebGL->gl;
    212 
    213  for (const auto& itr : mRebindRequests) {
    214    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
    215    gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
    216  }
    217 
    218  gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
    219 }
    220 
    221 ////////////////////////////////////////
    222 
    223 bool WebGLContext::ValidateStencilParamsForDrawCall() const {
    224  const auto stencilBits = [&]() -> uint8_t {
    225    if (!mStencilTestEnabled) return 0;
    226 
    227    if (!mBoundDrawFramebuffer) return mOptions.stencil ? 8 : 0;
    228 
    229    if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment()) return 8;
    230 
    231    if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
    232      return 8;
    233 
    234    return 0;
    235  }();
    236  const uint32_t stencilMax = (1 << stencilBits) - 1;
    237 
    238  const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
    239  const auto fnClamp = [&](const int32_t x) {
    240    return std::clamp(x, 0, (int32_t)stencilMax);
    241  };
    242 
    243  bool ok = true;
    244  ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
    245  ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
    246  ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
    247 
    248  if (!ok) {
    249    ErrorInvalidOperation(
    250        "Stencil front/back state must effectively match."
    251        " (before front/back comparison, WRITEMASK and VALUE_MASK"
    252        " are masked with (2^s)-1, and REF is clamped to"
    253        " [0, (2^s)-1], where `s` is the number of enabled stencil"
    254        " bits in the draw framebuffer)");
    255  }
    256  return ok;
    257 }
    258 
    259 // -
    260 
    261 void WebGLContext::GenErrorIllegalUse(const GLenum useTarget,
    262                                      const uint32_t useId,
    263                                      const GLenum boundTarget,
    264                                      const uint32_t boundId) const {
    265  const auto fnName = [&](const GLenum target, const uint32_t id) {
    266    auto name = nsCString(EnumString(target).c_str());
    267    if (id != static_cast<uint32_t>(-1)) {
    268      name += nsPrintfCString("[%u]", id);
    269    }
    270    return name;
    271  };
    272  const auto& useName = fnName(useTarget, useId);
    273  const auto& boundName = fnName(boundTarget, boundId);
    274  GenerateError(LOCAL_GL_INVALID_OPERATION,
    275                "Illegal use of buffer at %s"
    276                " while also bound to %s.",
    277                useName.BeginReading(), boundName.BeginReading());
    278 }
    279 
    280 bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
    281                                          const GLenum nonTfTarget,
    282                                          const uint32_t nonTfId) const {
    283  bool dupe = false;
    284  const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
    285  for (const auto& cur : tfAttribs) {
    286    dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
    287  }
    288  if (MOZ_LIKELY(!dupe)) return true;
    289 
    290  dupe = false;
    291  for (const auto tfId : IntegerRange(tfAttribs.size())) {
    292    const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
    293    if (&nonTfBuffer == tfBuffer) {
    294      dupe = true;
    295      GenErrorIllegalUse(nonTfTarget, nonTfId,
    296                         LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
    297    }
    298  }
    299  MOZ_ASSERT(dupe);
    300  return false;
    301 }
    302 
    303 bool WebGLContext::ValidateBuffersForTf(
    304    const WebGLTransformFeedback& tfo,
    305    const webgl::LinkedProgramInfo& linkInfo) const {
    306  size_t numUsed;
    307  switch (linkInfo.transformFeedbackBufferMode) {
    308    case LOCAL_GL_INTERLEAVED_ATTRIBS:
    309      numUsed = 1;
    310      break;
    311 
    312    case LOCAL_GL_SEPARATE_ATTRIBS:
    313      numUsed = linkInfo.active.activeTfVaryings.size();
    314      break;
    315 
    316    default:
    317      MOZ_CRASH();
    318  }
    319 
    320  std::vector<webgl::BufferAndIndex> tfBuffers;
    321  tfBuffers.reserve(numUsed);
    322  for (const auto i : IntegerRange(numUsed)) {
    323    tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
    324                         static_cast<uint32_t>(i)});
    325  }
    326 
    327  return ValidateBuffersForTf(tfBuffers);
    328 }
    329 
    330 bool WebGLContext::ValidateBuffersForTf(
    331    const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
    332  bool dupe = false;
    333  const auto fnCheck = [&](const WebGLBuffer* const nonTf,
    334                           const GLenum nonTfTarget, const uint32_t nonTfId) {
    335    for (const auto& tf : tfBuffers) {
    336      dupe |= (nonTf && tf.buffer == nonTf);
    337    }
    338 
    339    if (MOZ_LIKELY(!dupe)) return false;
    340 
    341    for (const auto& tf : tfBuffers) {
    342      if (nonTf && tf.buffer == nonTf) {
    343        dupe = true;
    344        GenErrorIllegalUse(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
    345                           nonTfTarget, nonTfId);
    346      }
    347    }
    348    return true;
    349  };
    350 
    351  fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
    352  fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
    353  fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
    354  fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
    355  fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
    356  // fnCheck(mBoundTransformFeedbackBuffer.get(),
    357  // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
    358  fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
    359 
    360  for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
    361    const auto& cur = mIndexedUniformBufferBindings[i];
    362    fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
    363  }
    364 
    365  fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
    366          LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
    367  for (const auto i : IntegerRange(MaxVertexAttribs())) {
    368    const auto& binding = mBoundVertexArray->AttribBinding(i);
    369    fnCheck(binding.buffer.get(), LOCAL_GL_ARRAY_BUFFER, i);
    370  }
    371 
    372  return !dupe;
    373 }
    374 
    375 ////////////////////////////////////////
    376 
    377 template <typename T>
    378 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
    379  std::vector<T> intersection;
    380  std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
    381                        std::back_inserter(intersection));
    382  return !intersection.empty();
    383 }
    384 
    385 template <size_t N>
    386 static size_t FindFirstOne(const std::bitset<N>& bs) {
    387  MOZ_ASSERT(bs.any());
    388  // We don't need this to be fast, so don't bother with CLZ intrinsics.
    389  for (const auto i : IntegerRange(N)) {
    390    if (bs[i]) return i;
    391  }
    392  return -1;
    393 }
    394 
    395 const webgl::CachedDrawFetchLimits* ValidateDraw(WebGLContext* const webgl,
    396                                                 const GLenum mode,
    397                                                 const uint32_t instanceCount) {
    398  if (!webgl->BindCurFBForDraw()) return nullptr;
    399 
    400  const auto& fb = webgl->mBoundDrawFramebuffer;
    401  if (fb) {
    402    const auto& info = *fb->GetCompletenessInfo();
    403    const auto isF32WithBlending = info.isAttachmentF32 & webgl->mBlendEnabled;
    404    if (isF32WithBlending.any()) {
    405      if (!webgl->IsExtensionEnabled(WebGLExtensionID::EXT_float_blend)) {
    406        const auto first = FindFirstOne(isF32WithBlending);
    407        webgl->ErrorInvalidOperation(
    408            "Attachment %u is float32 with blending enabled, which requires "
    409            "EXT_float_blend.",
    410            uint32_t(first));
    411        return nullptr;
    412      }
    413      webgl->WarnIfImplicit(WebGLExtensionID::EXT_float_blend);
    414    }
    415  }
    416 
    417  switch (mode) {
    418    case LOCAL_GL_TRIANGLES:
    419    case LOCAL_GL_TRIANGLE_STRIP:
    420    case LOCAL_GL_TRIANGLE_FAN:
    421    case LOCAL_GL_POINTS:
    422    case LOCAL_GL_LINE_STRIP:
    423    case LOCAL_GL_LINE_LOOP:
    424    case LOCAL_GL_LINES:
    425      break;
    426    default:
    427      webgl->ErrorInvalidEnumInfo("mode", mode);
    428      return nullptr;
    429  }
    430 
    431  if (!webgl->ValidateStencilParamsForDrawCall()) return nullptr;
    432 
    433  if (!webgl->mActiveProgramLinkInfo) {
    434    webgl->ErrorInvalidOperation("The current program is not linked.");
    435    return nullptr;
    436  }
    437  const auto& linkInfo = webgl->mActiveProgramLinkInfo;
    438 
    439  // -
    440  // Check UBO sizes.
    441 
    442  for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
    443    const auto& cur = linkInfo->uniformBlocks[i];
    444    const auto& dataSize = cur.info.dataSize;
    445    const auto& binding = cur.binding;
    446    if (!binding) {
    447      webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
    448      return nullptr;
    449    }
    450 
    451    const auto availByteCount = binding->ByteCount();
    452    if (dataSize > availByteCount) {
    453      webgl->ErrorInvalidOperation(
    454          "Buffer for uniform block is smaller"
    455          " than UNIFORM_BLOCK_DATA_SIZE.");
    456      return nullptr;
    457    }
    458 
    459    if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
    460                                       LOCAL_GL_UNIFORM_BUFFER, i))
    461      return nullptr;
    462  }
    463 
    464  // -
    465 
    466  const auto& tfo = webgl->mBoundTransformFeedback;
    467  if (tfo && tfo->IsActiveAndNotPaused()) {
    468    if (fb) {
    469      const auto& info = *fb->GetCompletenessInfo();
    470      if (info.isMultiview) {
    471        webgl->ErrorInvalidOperation(
    472            "Cannot render to multiview with transform feedback.");
    473        return nullptr;
    474      }
    475    }
    476 
    477    if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
    478  }
    479 
    480  // -
    481 
    482  const auto& fragOutputs = linkInfo->fragOutputs;
    483  const auto fnValidateFragOutputType =
    484      [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
    485        const auto itr = fragOutputs.find(loc);
    486        MOZ_DIAGNOSTIC_ASSERT(itr != fragOutputs.end());
    487 
    488        const auto& info = itr->second;
    489        const auto& srcBaseType = info.baseType;
    490        if (MOZ_UNLIKELY(dstBaseType != srcBaseType)) {
    491          const auto& srcStr = ToString(srcBaseType);
    492          const auto& dstStr = ToString(dstBaseType);
    493          webgl->ErrorInvalidOperation(
    494              "Program frag output at location %u is type %s,"
    495              " but destination draw buffer is type %s.",
    496              uint32_t(loc), srcStr, dstStr);
    497          return false;
    498        }
    499        return true;
    500      };
    501 
    502  if (!webgl->mRasterizerDiscardEnabled) {
    503    uint8_t fbZLayerCount = 1;
    504    auto hasAttachment = std::bitset<webgl::kMaxDrawBuffers>(1);
    505    auto drawBufferEnabled = std::bitset<webgl::kMaxDrawBuffers>();
    506    if (fb) {
    507      drawBufferEnabled = fb->DrawBufferEnabled();
    508      const auto& info = *fb->GetCompletenessInfo();
    509      fbZLayerCount = info.zLayerCount;
    510      hasAttachment = info.hasAttachment;
    511    } else {
    512      drawBufferEnabled[0] = (webgl->mDefaultFB_DrawBuffer0 == LOCAL_GL_BACK);
    513    }
    514 
    515    if (fbZLayerCount != linkInfo->zLayerCount) {
    516      webgl->ErrorInvalidOperation(
    517          "Multiview count mismatch: shader: %u, framebuffer: %u",
    518          uint32_t{linkInfo->zLayerCount}, uint32_t{fbZLayerCount});
    519      return nullptr;
    520    }
    521 
    522    const auto writable =
    523        hasAttachment & drawBufferEnabled & webgl->mColorWriteMaskNonzero;
    524    if (writable.any()) {
    525      // Do we have any undefined outputs with real attachments that
    526      // aren't masked-out by color write mask or drawBuffers?
    527      const auto wouldWriteUndefined = ~linkInfo->hasOutput & writable;
    528      if (wouldWriteUndefined.any()) {
    529        const auto first = FindFirstOne(wouldWriteUndefined);
    530        webgl->ErrorInvalidOperation(
    531            "Program has no frag output at location %u, the"
    532            " destination draw buffer has an attached"
    533            " image, and its color write mask is not all false,"
    534            " and DRAW_BUFFER%u is not NONE.",
    535            uint32_t(first), uint32_t(first));
    536        return nullptr;
    537      }
    538 
    539      const auto outputWrites = linkInfo->hasOutput & writable;
    540 
    541      if (fb) {
    542        for (const auto& attach : fb->ColorDrawBuffers()) {
    543          const auto i =
    544              uint8_t(attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0);
    545          if (!outputWrites[i]) continue;
    546          const auto& imageInfo = attach->GetImageInfo();
    547          if (!imageInfo) continue;
    548          const auto& dstBaseType = imageInfo->mFormat->format->baseType;
    549          if (!fnValidateFragOutputType(i, dstBaseType)) return nullptr;
    550        }
    551      } else {
    552        if (outputWrites[0]) {
    553          if (!fnValidateFragOutputType(0, webgl::TextureBaseType::Float))
    554            return nullptr;
    555        }
    556      }
    557    }
    558  }
    559 
    560  // -
    561 
    562  const auto fetchLimits = linkInfo->GetDrawFetchLimits();
    563  if (!fetchLimits) return nullptr;
    564 
    565  if (instanceCount > fetchLimits->maxInstances) {
    566    webgl->ErrorInvalidOperation(
    567        "Instance fetch requires %u, but attribs only"
    568        " supply %u.",
    569        instanceCount, uint32_t(fetchLimits->maxInstances));
    570    return nullptr;
    571  }
    572 
    573  if (tfo) {
    574    for (const auto& used : fetchLimits->usedBuffers) {
    575      MOZ_ASSERT(used.buffer);
    576      if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
    577                                         used.id))
    578        return nullptr;
    579    }
    580  }
    581 
    582  // -
    583 
    584  webgl->RunContextLossTimer();
    585 
    586  return fetchLimits;
    587 }
    588 
    589 ////////////////////////////////////////
    590 
    591 static uint32_t UsedVertsForTFDraw(GLenum mode, uint32_t vertCount) {
    592  uint8_t vertsPerPrim;
    593 
    594  switch (mode) {
    595    case LOCAL_GL_POINTS:
    596      vertsPerPrim = 1;
    597      break;
    598    case LOCAL_GL_LINES:
    599      vertsPerPrim = 2;
    600      break;
    601    case LOCAL_GL_TRIANGLES:
    602      vertsPerPrim = 3;
    603      break;
    604    default:
    605      MOZ_CRASH("`mode`");
    606  }
    607 
    608  return vertCount / vertsPerPrim * vertsPerPrim;
    609 }
    610 
    611 class ScopedDrawWithTransformFeedback final {
    612  WebGLContext* const mWebGL;
    613  WebGLTransformFeedback* const mTFO;
    614  const bool mWithTF;
    615  uint32_t mUsedVerts;
    616 
    617 public:
    618  ScopedDrawWithTransformFeedback(WebGLContext* webgl, GLenum mode,
    619                                  uint32_t vertCount, uint32_t instanceCount,
    620                                  bool* const out_error)
    621      : mWebGL(webgl),
    622        mTFO(mWebGL->mBoundTransformFeedback),
    623        mWithTF(mTFO && mTFO->mIsActive && !mTFO->mIsPaused),
    624        mUsedVerts(0) {
    625    *out_error = false;
    626    if (!mWithTF) return;
    627 
    628    if (mode != mTFO->mActive_PrimMode) {
    629      mWebGL->ErrorInvalidOperation(
    630          "Drawing with transform feedback requires"
    631          " `mode` to match BeginTransformFeedback's"
    632          " `primitiveMode`.");
    633      *out_error = true;
    634      return;
    635    }
    636 
    637    const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
    638    const auto usedVerts =
    639        CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
    640 
    641    const auto remainingCapacity =
    642        mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
    643    if (!usedVerts.isValid() || usedVerts.value() > remainingCapacity) {
    644      mWebGL->ErrorInvalidOperation(
    645          "Insufficient buffer capacity remaining for"
    646          " transform feedback.");
    647      *out_error = true;
    648      return;
    649    }
    650 
    651    mUsedVerts = usedVerts.value();
    652  }
    653 
    654  void Advance() const {
    655    if (!mWithTF) return;
    656 
    657    mTFO->mActive_VertPosition += mUsedVerts;
    658 
    659    for (const auto& cur : mTFO->mIndexedBindings) {
    660      const auto& buffer = cur.mBufferBinding;
    661      if (buffer) {
    662        buffer->ResetLastUpdateFenceId();
    663      }
    664    }
    665  }
    666 };
    667 
    668 static bool HasInstancedDrawing(const WebGLContext& webgl) {
    669  return webgl.IsWebGL2() ||
    670         webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
    671 }
    672 
    673 ////////////////////////////////////////
    674 
    675 void WebGLContext::DrawArraysInstanced(const GLenum mode, const GLint first,
    676                                       const GLsizei iVertCount,
    677                                       const GLsizei instanceCount) {
    678  const FuncScope funcScope(*this, "drawArraysInstanced");
    679  // AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
    680  if (IsContextLost()) return;
    681  const gl::GLContext::TlsScope inTls(gl);
    682 
    683  // -
    684 
    685  if (!ValidateNonNegative("first", first) ||
    686      !ValidateNonNegative("vertCount", iVertCount) ||
    687      !ValidateNonNegative("instanceCount", instanceCount)) {
    688    return;
    689  }
    690  const auto vertCount = AssertedCast<uint32_t>(iVertCount);
    691 
    692  if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
    693    MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
    694    if (mPrimRestartTypeBytes != 0) {
    695      mPrimRestartTypeBytes = 0;
    696 
    697      // OSX appears to have severe perf issues with leaving this enabled.
    698      gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
    699    }
    700  }
    701 
    702  // -
    703 
    704  const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
    705  if (!fetchLimits) return;
    706 
    707  // -
    708 
    709  const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
    710  if (!totalVertCount_safe.isValid()) {
    711    ErrorOutOfMemory("`first+vertCount` out of range.");
    712    return;
    713  }
    714  auto totalVertCount = totalVertCount_safe.value();
    715 
    716  if (vertCount && instanceCount && totalVertCount > fetchLimits->maxVerts) {
    717    ErrorInvalidOperation(
    718        "Vertex fetch requires %u, but attribs only supply %u.", totalVertCount,
    719        uint32_t(fetchLimits->maxVerts));
    720    return;
    721  }
    722 
    723  if (vertCount > mMaxVertIdsPerDraw) {
    724    ErrorOutOfMemory(
    725        "Context's max vertCount is %u, but %u requested. "
    726        "[webgl.max-vert-ids-per-draw]",
    727        mMaxVertIdsPerDraw, vertCount);
    728    return;
    729  }
    730 
    731  // -
    732 
    733  bool error = false;
    734 
    735  // -
    736 
    737  const ScopedResolveTexturesForDraw scopedResolve(this, &error);
    738  if (error) return;
    739 
    740  const ScopedDrawWithTransformFeedback scopedTF(this, mode, vertCount,
    741                                                 instanceCount, &error);
    742  if (error) return;
    743 
    744  // On MacOS (Intel?), `first` in glDrawArrays also increases where instanced
    745  // attribs are fetched from. There are two ways to fix this:
    746  // 1. DrawElements with a [0,1,2,...] index buffer, converting `first` to
    747  // `byteOffset`
    748  // 2. OR offset all non-instanced vertex attrib pointers back, and call
    749  // DrawArrays with first:0.
    750  //   * But now gl_VertexID will be wrong! So we inject a uniform to offset it
    751  //   back correctly.
    752  // #1 ought to be the lowest overhead for any first>0,
    753  // but DrawElements can't be used with transform-feedback,
    754  // so we need #2 to also work.
    755  // For now, only implement #2.
    756 
    757  const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
    758 
    759  auto driverFirst = first;
    760 
    761  if (first && mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst) {
    762    // This is not particularly optimized, but we can if we need to.
    763    bool hasInstancedUserAttrib = false;
    764    bool hasVertexAttrib = false;
    765    for (const auto& a : activeAttribs) {
    766      if (a.location == -1) {
    767        if (a.name == "gl_VertexID") {
    768          hasVertexAttrib = true;
    769        }
    770        continue;
    771      }
    772      const auto& binding = mBoundVertexArray->AttribBinding(a.location);
    773      if (binding.layout.divisor) {
    774        hasInstancedUserAttrib = true;
    775      } else {
    776        hasVertexAttrib = true;
    777      }
    778    }
    779    if (hasInstancedUserAttrib && hasVertexAttrib) {
    780      driverFirst = 0;
    781    }
    782  }
    783  const bool needsFix_InstancedUserAttribFetch = (driverFirst != first);
    784 
    785  if (needsFix_InstancedUserAttribFetch) {
    786    for (const auto& a : activeAttribs) {
    787      if (a.location == -1) continue;
    788      const auto& binding = mBoundVertexArray->AttribBinding(a.location);
    789      if (binding.layout.divisor) continue;
    790 
    791      mBoundVertexArray->DoVertexAttrib(a.location, first);
    792    }
    793 
    794    gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, first);
    795  }
    796  const auto undoFix_InstancedUserAttribFetch = MakeScopeExit([&]() {
    797    if (needsFix_InstancedUserAttribFetch) {
    798      gl->fUniform1i(mActiveProgramLinkInfo->webgl_gl_VertexID_Offset, 0);
    799 
    800      for (const auto& a : activeAttribs) {
    801        if (a.location == -1) continue;
    802        const auto& binding = mBoundVertexArray->AttribBinding(a.location);
    803        if (binding.layout.divisor) continue;
    804 
    805        mBoundVertexArray->DoVertexAttrib(a.location, 0);
    806      }
    807    }
    808  });
    809 
    810  // -
    811 
    812  {
    813    const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
    814    auto fakeVertCount = uint64_t(driverFirst) + vertCount;
    815    if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default) {
    816      fakeVertCount = 0;
    817    }
    818    if (!(vertCount && instanceCount)) {
    819      fakeVertCount = 0;
    820    }
    821 
    822    const bool needsFix_FakeVertexAttrib0 = bool(fakeVertCount);
    823    const auto undoFix_FakeVertexAttrib0 = MakeScopeExit([&]() {
    824      if (needsFix_FakeVertexAttrib0) {
    825        mBoundVertexArray->DoVertexAttrib(0);
    826      }
    827    });
    828    if (needsFix_FakeVertexAttrib0) {
    829      // fmt::println(FMT_STRING("DoFakeVertexAttrib0(fakeVertCount: {},
    830      // whatDoesAttrib0Need: {})"), fakeVertCount, (int)whatDoesAttrib0Need);
    831      if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) return;
    832    }
    833 
    834    ScopedDrawCallWrapper wrapper(*this);
    835 
    836    if (vertCount && instanceCount) {
    837      if (HasInstancedDrawing(*this)) {
    838        gl->fDrawArraysInstanced(mode, driverFirst, vertCount, instanceCount);
    839      } else {
    840        MOZ_ASSERT(instanceCount == 1);
    841        gl->fDrawArrays(mode, driverFirst, vertCount);
    842      }
    843    }
    844  }
    845 
    846  Draw_cleanup();
    847  scopedTF.Advance();
    848 }
    849 
    850 ////////////////////////////////////////
    851 
    852 WebGLBuffer* WebGLContext::DrawElements_check(const GLsizei rawIndexCount,
    853                                              const GLenum type,
    854                                              const WebGLintptr byteOffset,
    855                                              const GLsizei instanceCount) {
    856  if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive &&
    857      !mBoundTransformFeedback->mIsPaused) {
    858    ErrorInvalidOperation(
    859        "DrawElements* functions are incompatible with"
    860        " transform feedback.");
    861    return nullptr;
    862  }
    863 
    864  if (!ValidateNonNegative("vertCount", rawIndexCount) ||
    865      !ValidateNonNegative("byteOffset", byteOffset) ||
    866      !ValidateNonNegative("instanceCount", instanceCount)) {
    867    return nullptr;
    868  }
    869  const auto indexCount = uint32_t(rawIndexCount);
    870 
    871  uint8_t bytesPerIndex = 0;
    872  switch (type) {
    873    case LOCAL_GL_UNSIGNED_BYTE:
    874      bytesPerIndex = 1;
    875      break;
    876 
    877    case LOCAL_GL_UNSIGNED_SHORT:
    878      bytesPerIndex = 2;
    879      break;
    880 
    881    case LOCAL_GL_UNSIGNED_INT:
    882      if (IsWebGL2() ||
    883          IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
    884        bytesPerIndex = 4;
    885      }
    886      break;
    887  }
    888  if (!bytesPerIndex) {
    889    ErrorInvalidEnumInfo("type", type);
    890    return nullptr;
    891  }
    892  if (byteOffset % bytesPerIndex != 0) {
    893    ErrorInvalidOperation(
    894        "`byteOffset` must be a multiple of the size of `type`");
    895    return nullptr;
    896  }
    897 
    898  ////
    899 
    900  if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
    901    MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
    902    if (mPrimRestartTypeBytes != bytesPerIndex) {
    903      mPrimRestartTypeBytes = bytesPerIndex;
    904 
    905      const uint32_t ones = UINT32_MAX >> (32 - 8 * mPrimRestartTypeBytes);
    906      gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
    907      gl->fPrimitiveRestartIndex(ones);
    908    }
    909  }
    910 
    911  ////
    912  // Index fetching
    913 
    914  const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
    915  if (!indexBuffer) {
    916    ErrorInvalidOperation("Index buffer not bound.");
    917    return nullptr;
    918  }
    919 
    920  const size_t availBytes = indexBuffer->ByteLength();
    921  const auto availIndices =
    922      AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
    923  if (instanceCount && indexCount > availIndices) {
    924    ErrorInvalidOperation("Index buffer too small.");
    925    return nullptr;
    926  }
    927 
    928  return indexBuffer.get();
    929 }
    930 
    931 static void HandleDrawElementsErrors(
    932    WebGLContext* webgl, gl::GLContext::LocalErrorScope& errorScope) {
    933  const auto err = errorScope.GetError();
    934  if (err == LOCAL_GL_INVALID_OPERATION) {
    935    webgl->ErrorInvalidOperation(
    936        "Driver rejected indexed draw call, possibly"
    937        " due to out-of-bounds indices.");
    938    return;
    939  }
    940 
    941  MOZ_ASSERT(!err);
    942  if (err) {
    943    webgl->ErrorImplementationBug(
    944        "Unexpected driver error during indexed draw"
    945        " call. Please file a bug.");
    946    return;
    947  }
    948 }
    949 
    950 void WebGLContext::DrawElementsInstanced(const GLenum mode,
    951                                         const GLsizei iIndexCount,
    952                                         const GLenum type,
    953                                         const WebGLintptr byteOffset,
    954                                         const GLsizei instanceCount) {
    955  const FuncScope funcScope(*this, "drawElementsInstanced");
    956  // AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
    957  if (IsContextLost()) return;
    958 
    959  const gl::GLContext::TlsScope inTls(gl);
    960 
    961  const auto indexBuffer =
    962      DrawElements_check(iIndexCount, type, byteOffset, instanceCount);
    963  if (!indexBuffer) return;
    964  const auto indexCount = AssertedCast<uint32_t>(iIndexCount);
    965 
    966  // -
    967 
    968  const auto fetchLimits = ValidateDraw(this, mode, instanceCount);
    969  if (!fetchLimits) return;
    970 
    971  const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
    972 
    973  uint64_t fakeVertCount = 0;
    974  if (whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default) {
    975    fakeVertCount = fetchLimits->maxVerts;
    976  }
    977  if (!indexCount || !instanceCount) {
    978    fakeVertCount = 0;
    979  }
    980  if (fakeVertCount == UINT64_MAX) {  // Ok well that's too many!
    981    const auto exactMaxVertId =
    982        indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
    983    MOZ_RELEASE_ASSERT(exactMaxVertId);
    984    fakeVertCount = uint32_t{*exactMaxVertId};
    985    fakeVertCount += 1;
    986  }
    987 
    988  // -
    989 
    990  {
    991    uint64_t indexCapacity = indexBuffer->ByteLength();
    992    switch (type) {
    993      case LOCAL_GL_UNSIGNED_BYTE:
    994        break;
    995      case LOCAL_GL_UNSIGNED_SHORT:
    996        indexCapacity /= 2;
    997        break;
    998      case LOCAL_GL_UNSIGNED_INT:
    999        indexCapacity /= 4;
   1000        break;
   1001    }
   1002 
   1003    uint32_t maxVertId = 0;
   1004    const auto isFetchValid = [&]() {
   1005      if (!indexCount || !instanceCount) return true;
   1006 
   1007      const auto globalMaxVertId =
   1008          indexBuffer->GetIndexedFetchMaxVert(type, 0, indexCapacity);
   1009      if (!globalMaxVertId) return true;
   1010      if (globalMaxVertId.value() < fetchLimits->maxVerts) return true;
   1011 
   1012      const auto exactMaxVertId =
   1013          indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
   1014      maxVertId = exactMaxVertId.value();
   1015      return maxVertId < fetchLimits->maxVerts;
   1016    }();
   1017    if (!isFetchValid) {
   1018      ErrorInvalidOperation(
   1019          "Indexed vertex fetch requires %u vertices, but"
   1020          " attribs only supply %u.",
   1021          maxVertId + 1, uint32_t(fetchLimits->maxVerts));
   1022      return;
   1023    }
   1024  }
   1025 
   1026  if (indexCount > mMaxVertIdsPerDraw) {
   1027    ErrorOutOfMemory(
   1028        "Context's max indexCount is %u, but %u requested. "
   1029        "[webgl.max-vert-ids-per-draw]",
   1030        mMaxVertIdsPerDraw, indexCount);
   1031    return;
   1032  }
   1033 
   1034  // -
   1035 
   1036  const bool needsFix_FakeVertexAttrib0 = bool(fakeVertCount);
   1037  const auto undoFix_FakeVertexAttrib0 = MakeScopeExit([&]() {
   1038    if (needsFix_FakeVertexAttrib0) {
   1039      mBoundVertexArray->DoVertexAttrib(0);
   1040    }
   1041  });
   1042  if (needsFix_FakeVertexAttrib0) {
   1043    if (!DoFakeVertexAttrib0(fakeVertCount, whatDoesAttrib0Need)) return;
   1044  }
   1045 
   1046  // -
   1047 
   1048  bool error = false;
   1049  const ScopedResolveTexturesForDraw scopedResolve(this, &error);
   1050  if (error) return;
   1051 
   1052  {
   1053    ScopedDrawCallWrapper wrapper(*this);
   1054    {
   1055      std::unique_ptr<gl::GLContext::LocalErrorScope> errorScope;
   1056      if (MOZ_UNLIKELY(gl->IsANGLE() &&
   1057                       gl->mDebugFlags &
   1058                           gl::GLContext::DebugFlagAbortOnError)) {
   1059        // ANGLE does range validation even when it doesn't need to.
   1060        // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
   1061        errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
   1062      }
   1063 
   1064      if (indexCount && instanceCount) {
   1065        if (HasInstancedDrawing(*this)) {
   1066          gl->fDrawElementsInstanced(mode, indexCount, type,
   1067                                     reinterpret_cast<GLvoid*>(byteOffset),
   1068                                     instanceCount);
   1069        } else {
   1070          MOZ_ASSERT(instanceCount == 1);
   1071          gl->fDrawElements(mode, indexCount, type,
   1072                            reinterpret_cast<GLvoid*>(byteOffset));
   1073        }
   1074      }
   1075 
   1076      if (errorScope) {
   1077        HandleDrawElementsErrors(this, *errorScope);
   1078      }
   1079    }
   1080  }
   1081 
   1082  Draw_cleanup();
   1083 }
   1084 
   1085 ////////////////////////////////////////
   1086 
   1087 void WebGLContext::Draw_cleanup() {
   1088  if (gl->WorkAroundDriverBugs()) {
   1089    if (gl->Renderer() == gl::GLRenderer::Tegra) {
   1090      mDrawCallsSinceLastFlush++;
   1091 
   1092      if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
   1093        gl->fFlush();
   1094        mDrawCallsSinceLastFlush = 0;
   1095      }
   1096    }
   1097  }
   1098 
   1099  // Let's check for a really common error: Viewport is larger than the actual
   1100  // destination framebuffer.
   1101  uint32_t destWidth;
   1102  uint32_t destHeight;
   1103  if (mBoundDrawFramebuffer) {
   1104    const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
   1105    destWidth = info->width;
   1106    destHeight = info->height;
   1107  } else {
   1108    destWidth = mDefaultFB->mSize.width;
   1109    destHeight = mDefaultFB->mSize.height;
   1110  }
   1111 
   1112  if (mViewportWidth > int32_t(destWidth) ||
   1113      mViewportHeight > int32_t(destHeight)) {
   1114    if (!mAlreadyWarnedAboutViewportLargerThanDest) {
   1115      GenerateWarning(
   1116          "Drawing to a destination rect smaller than the viewport"
   1117          " rect. (This warning will only be given once)");
   1118      mAlreadyWarnedAboutViewportLargerThanDest = true;
   1119    }
   1120  }
   1121 }
   1122 
   1123 WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const {
   1124  MOZ_ASSERT(mCurrentProgram);
   1125  MOZ_ASSERT(mActiveProgramLinkInfo);
   1126 
   1127  bool legacyAttrib0 = mNeedsLegacyVertexAttrib0Handling;
   1128  if (gl->WorkAroundDriverBugs() && kIsMacOS) {
   1129    // Also programs with no attribs:
   1130    // conformance/attribs/gl-vertex-attrib-unconsumed-out-of-bounds.html
   1131    const auto& activeAttribs = mActiveProgramLinkInfo->active.activeAttribs;
   1132    bool hasNonInstancedUserAttrib = false;
   1133    for (const auto& a : activeAttribs) {
   1134      if (a.location == -1) continue;
   1135      const auto& layout = mBoundVertexArray->AttribBinding(a.location).layout;
   1136      if (layout.divisor == 0) {
   1137        hasNonInstancedUserAttrib = true;
   1138      }
   1139    }
   1140    legacyAttrib0 |= !hasNonInstancedUserAttrib;
   1141  }
   1142 
   1143  if (!legacyAttrib0) return WebGLVertexAttrib0Status::Default;
   1144  MOZ_RELEASE_ASSERT(mMaybeNeedsLegacyVertexAttrib0Handling,
   1145                     "Invariant need because this turns on index buffer "
   1146                     "validation, needed for fake-attrib0.");
   1147 
   1148  if (!mActiveProgramLinkInfo->attrib0Active) {
   1149    // Attrib0 unused, so just ensure that the legacy code has enough buffer.
   1150    return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
   1151  }
   1152 
   1153  const auto& isAttribArray0Enabled =
   1154      mBoundVertexArray->AttribBinding(0).layout.isArray;
   1155  return isAttribArray0Enabled
   1156             ? WebGLVertexAttrib0Status::Default
   1157             : WebGLVertexAttrib0Status::EmulatedInitializedArray;
   1158 }
   1159 
   1160 bool WebGLContext::DoFakeVertexAttrib0(
   1161    const uint64_t fakeVertexCount,
   1162    const WebGLVertexAttrib0Status whatDoesAttrib0Need) {
   1163  MOZ_ASSERT(fakeVertexCount);
   1164  MOZ_RELEASE_ASSERT(whatDoesAttrib0Need != WebGLVertexAttrib0Status::Default);
   1165 
   1166  if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
   1167    // Padded/strided to vec4, so 4x4bytes.
   1168    const auto effectiveVertAttribBytes =
   1169        CheckedInt<int32_t>(fakeVertexCount) * 4 * 4;
   1170    if (!effectiveVertAttribBytes.isValid()) {
   1171      ErrorOutOfMemory("`offset + count` too large for Mesa.");
   1172      return false;
   1173    }
   1174  }
   1175 
   1176  if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
   1177    GenerateWarning(
   1178        "Drawing without vertex attrib 0 array enabled forces the browser "
   1179        "to do expensive emulation work when running on desktop OpenGL "
   1180        "platforms, for example on Mac. It is preferable to always draw "
   1181        "with vertex attrib 0 array enabled, by using bindAttribLocation "
   1182        "to bind some always-used attribute to location 0.");
   1183    mAlreadyWarnedAboutFakeVertexAttrib0 = true;
   1184  }
   1185 
   1186  if (!mFakeVertexAttrib0BufferObject) {
   1187    gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
   1188    mFakeVertexAttrib0BufferAllocSize = 0;
   1189    mFakeVertexAttrib0BufferInitializedSize = 0;
   1190  }
   1191 
   1192  ////
   1193 
   1194  const auto maxFakeVerts = StaticPrefs::webgl_fake_verts_max();
   1195  if (fakeVertexCount > maxFakeVerts) {
   1196    ErrorOutOfMemory(
   1197        "Draw requires faking a vertex attrib 0 array, but required vert count"
   1198        " (%" PRIu64 ") is more than webgl.fake-verts.max (%u).",
   1199        fakeVertexCount, maxFakeVerts);
   1200    return false;
   1201  }
   1202 
   1203  const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
   1204  const auto checked_dataSize =
   1205      CheckedInt<intptr_t>(fakeVertexCount) * bytesPerVert;
   1206  if (!checked_dataSize.isValid()) {
   1207    ErrorOutOfMemory(
   1208        "Integer overflow trying to construct a fake vertex attrib 0"
   1209        " array for a draw-operation with %" PRIu64
   1210        " vertices. Try"
   1211        " reducing the number of vertices.",
   1212        fakeVertexCount);
   1213    return false;
   1214  }
   1215  const auto dataSize = checked_dataSize.value();
   1216 
   1217  if (mFakeVertexAttrib0BufferAllocSize < dataSize) {
   1218    gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
   1219    gl::GLContext::LocalErrorScope errorScope(*gl);
   1220 
   1221    gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr,
   1222                    LOCAL_GL_STATIC_DRAW);
   1223 
   1224    const auto err = errorScope.GetError();
   1225    if (err) {
   1226      ErrorOutOfMemory(
   1227          "Failed to allocate fake vertex attrib 0 buffer: %zi bytes",
   1228          dataSize);
   1229      return false;
   1230    }
   1231 
   1232    mFakeVertexAttrib0BufferAllocSize = dataSize;
   1233    mFakeVertexAttrib0BufferInitializedSize = 0;
   1234  }
   1235 
   1236  ////
   1237 
   1238  const auto FillWithGenericVertexAttrib0Data = [&]() {
   1239    if (dataSize <= mFakeVertexAttrib0BufferInitializedSize &&
   1240        memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data,
   1241               bytesPerVert) == 0) {
   1242      return true;
   1243    }
   1244    memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
   1245    mFakeVertexAttrib0BufferInitializedSize = 0;
   1246 
   1247    using DataPerVertT = decltype(mFakeVertexAttrib0Data);
   1248    static_assert(sizeof(DataPerVertT) == 4 * sizeof(float));
   1249    MOZ_RELEASE_ASSERT(dataSize % sizeof(DataPerVertT) == 0);
   1250    const size_t vertCount = dataSize / sizeof(DataPerVertT);
   1251    const auto uploadData = std::unique_ptr<DataPerVertT[]>{
   1252        new (std::nothrow) DataPerVertT[vertCount]};
   1253    if (!uploadData) {
   1254      ErrorOutOfMemory("Failed to allocate fake vertex attrib 0 upload data.");
   1255      return false;
   1256    }
   1257    const auto uploadMutSpan = Span{uploadData.get(), vertCount};
   1258    for (auto& vert : uploadMutSpan) {
   1259      memcpy(&vert, mFakeVertexAttrib0Data, bytesPerVert);
   1260    }
   1261 
   1262    {
   1263      gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
   1264      gl::GLContext::LocalErrorScope errorScope(*gl);
   1265 
   1266      const auto uploadBytes = AsBytes(uploadMutSpan);
   1267      MOZ_ASSERT(uploadBytes.size() == (size_t)dataSize);
   1268      gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, uploadBytes.size(),
   1269                         uploadBytes.data());
   1270 
   1271      const auto err = errorScope.GetError();
   1272      if (err) {
   1273        ErrorOutOfMemory("Failed to upload fake vertex attrib 0 data.");
   1274        return false;
   1275      }
   1276    }
   1277 
   1278    mFakeVertexAttrib0BufferInitializedSize = dataSize;
   1279    return true;
   1280  };
   1281 
   1282  if (whatDoesAttrib0Need ==
   1283      WebGLVertexAttrib0Status::EmulatedInitializedArray) {
   1284    if (!FillWithGenericVertexAttrib0Data()) return false;
   1285  }
   1286 
   1287  ////
   1288 
   1289  const auto& attrib0 = mBoundVertexArray->AttribBinding(0);
   1290  if (attrib0.layout.divisor) {
   1291    gl->fVertexAttribDivisor(0, 0);
   1292  }
   1293 
   1294  gl->fEnableVertexAttribArray(0);
   1295 
   1296  gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
   1297  switch (mGenericVertexAttribTypes[0]) {
   1298    case webgl::AttribBaseType::Boolean:
   1299    case webgl::AttribBaseType::Float:
   1300      gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
   1301      break;
   1302 
   1303    case webgl::AttribBaseType::Int:
   1304      gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
   1305      break;
   1306 
   1307    case webgl::AttribBaseType::Uint:
   1308      gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
   1309      break;
   1310  }
   1311 
   1312  ////
   1313 
   1314  return true;
   1315 }
   1316 
   1317 }  // namespace mozilla