Bug 1541396 - Refactor (non-)transform-feedback binding/use checks. r=lsalzman
authorJeff Gilbert <jgilbert@mozilla.com>
Sun, 30 Jun 2019 01:29:42 +0000
changeset 540354 0c28fedb2c6a5face6a488ac940a42af62674e66
parent 540353 edcb607325025e9622d21865f200089eb36dcd8f
child 540355 979f1395a70cce96d272e4684a181dc5eed479d0
push id11522
push userffxbld-merge
push dateMon, 01 Jul 2019 09:00:55 +0000
treeherdermozilla-beta@53ea74d2bd09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1541396
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1541396 - Refactor (non-)transform-feedback binding/use checks. r=lsalzman * Remove WebGLBuffer::SetSlot * Make most bindBuffer calls lazy. * Replace nsTArray with std::vector in WebGLVertexArray. Differential Revision: https://phabricator.services.mozilla.com/D36274
dom/canvas/WebGL2ContextTransformFeedback.cpp
dom/canvas/WebGLBuffer.cpp
dom/canvas/WebGLBuffer.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextBuffers.cpp
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLContextVertexArray.cpp
dom/canvas/WebGLContextVertices.cpp
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.h
dom/canvas/WebGLTransformFeedback.cpp
dom/canvas/WebGLTransformFeedback.h
dom/canvas/WebGLVertexArray.cpp
dom/canvas/WebGLVertexArray.h
dom/canvas/WebGLVertexArrayFake.cpp
dom/canvas/WebGLVertexAttribData.cpp
dom/canvas/WebGLVertexAttribData.h
dom/canvas/test/webgl-conf/generated-mochitest.ini
dom/canvas/test/webgl-conf/mochitest-errata.ini
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -67,26 +67,21 @@ void WebGL2Context::BindTransformFeedbac
     ErrorInvalidOperation(
         "Currently bound transform feedback is active and not"
         " paused.");
     return;
   }
 
   ////
 
-  if (mBoundTransformFeedback) {
-    mBoundTransformFeedback->AddBufferBindCounts(-1);
-  }
-
   mBoundTransformFeedback = (tf ? tf : mDefaultTransformFeedback);
 
   gl->fBindTransformFeedback(target, mBoundTransformFeedback->mGLName);
 
   if (mBoundTransformFeedback) {
-    mBoundTransformFeedback->AddBufferBindCounts(+1);
     mBoundTransformFeedback->mHasBeenBound = true;
   }
 }
 
 void WebGL2Context::BeginTransformFeedback(GLenum primMode) {
   const FuncScope funcScope(*this, "beginTransformFeedback");
   if (IsContextLost()) return;
 
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -7,23 +7,17 @@
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
-    : WebGLRefCountedObject(webgl),
-      mGLName(buf),
-      mContent(Kind::Undefined),
-      mUsage(LOCAL_GL_STATIC_DRAW),
-      mByteLength(0),
-      mTFBindCount(0),
-      mNonTFBindCount(0) {
+    : WebGLRefCountedObject(webgl), mGLName(buf) {
   mContext->mBuffers.insertBack(this);
 }
 
 WebGLBuffer::~WebGLBuffer() { DeleteOnce(); }
 
 void WebGLBuffer::SetContentAfterBind(GLenum target) {
   if (mContent != Kind::Undefined) return;
 
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -50,57 +50,29 @@ class WebGLBuffer final : public nsWrapp
 
   bool ValidateCanBindToTarget(GLenum target);
   void BufferData(GLenum target, uint64_t size, const void* data, GLenum usage);
   void BufferSubData(GLenum target, uint64_t dstByteOffset, uint64_t dataLen,
                      const void* data) const;
 
   ////
 
-  static void AddBindCount(GLenum target, WebGLBuffer* buffer, int8_t addVal) {
-    if (!buffer) return;
-
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
-      MOZ_ASSERT_IF(addVal < 0, buffer->mTFBindCount >= size_t(-addVal));
-      buffer->mTFBindCount += addVal;
-      buffer->mFetchInvalidator.InvalidateCaches();
-    } else {
-      MOZ_ASSERT_IF(addVal < 0, buffer->mNonTFBindCount >= size_t(-addVal));
-      buffer->mNonTFBindCount += addVal;
-    }
-  }
-
-  static void SetSlot(GLenum target, WebGLBuffer* newBuffer,
-                      WebGLRefPtr<WebGLBuffer>* const out_slot) {
-    WebGLBuffer* const oldBuffer = *out_slot;
-    AddBindCount(target, oldBuffer, -1);
-    AddBindCount(target, newBuffer, +1);
-    *out_slot = newBuffer;
-  }
-
-  bool IsBoundForTF() const { return bool(mTFBindCount); }
-  bool IsBoundForNonTF() const { return bool(mNonTFBindCount); }
-
-  ////
-
   const GLenum mGLName;
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLBuffer)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLBuffer)
 
  protected:
   ~WebGLBuffer();
 
   void InvalidateCacheRange(uint64_t byteOffset, uint64_t byteLength) const;
 
-  Kind mContent;
-  GLenum mUsage;
-  size_t mByteLength;
-  size_t mTFBindCount;
-  size_t mNonTFBindCount;
+  Kind mContent = Kind::Undefined;
+  GLenum mUsage = LOCAL_GL_STATIC_DRAW;
+  size_t mByteLength = 0;
   mutable uint64_t mLastUpdateFenceId = 0;
 
   struct IndexRange final {
     GLenum type;
     uint64_t byteOffset;
     uint32_t indexCount;
 
     bool operator<(const IndexRange& x) const {
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1993,32 +1993,25 @@ ScopedFBRebinder::~ScopedFBRebinder() {
     MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
     gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
                          fnName(mWebGL->mBoundDrawFramebuffer));
   }
 }
 
 ////////////////////
 
-static GLenum TargetIfLazy(GLenum target) {
-  switch (target) {
-    case LOCAL_GL_PIXEL_PACK_BUFFER:
-    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
-      return target;
-
-    default:
-      return 0;
-  }
+static GLenum IsVirtualBufferTarget(GLenum target) {
+  return target != LOCAL_GL_ELEMENT_ARRAY_BUFFER;
 }
 
 ScopedLazyBind::ScopedLazyBind(gl::GLContext* const gl, const GLenum target,
-                               const WebGLBuffer* buf)
-    : mGL(gl), mTarget(buf ? TargetIfLazy(target) : 0), mBuf(buf) {
+                               const WebGLBuffer* const buf)
+    : mGL(gl), mTarget(IsVirtualBufferTarget(target) ? target : 0) {
   if (mTarget) {
-    mGL->fBindBuffer(mTarget, mBuf->mGLName);
+    mGL->fBindBuffer(mTarget, buf ? buf->mGLName : 0);
   }
 }
 
 ScopedLazyBind::~ScopedLazyBind() {
   if (mTarget) {
     mGL->fBindBuffer(mTarget, 0);
   }
 }
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -249,27 +249,34 @@ struct TexImageSourceAdapter final : pub
     mDomElem = domElem;
     mOut_error = out_error;
   }
 };
 
 // --
 
 namespace webgl {
+
 class AvailabilityRunnable final : public Runnable {
  public:
   const RefPtr<WebGLContext> mWebGL;  // Prevent CC
   std::vector<RefPtr<WebGLQuery>> mQueries;
   std::vector<RefPtr<WebGLSync>> mSyncs;
 
   explicit AvailabilityRunnable(WebGLContext* webgl);
   ~AvailabilityRunnable();
 
   NS_IMETHOD Run() override;
 };
+
+struct BufferAndIndex final {
+  const WebGLBuffer* buffer = nullptr;
+  uint32_t id = -1;
+};
+
 }  // namespace webgl
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class WebGLContext : public nsICanvasRenderingContextInternal,
                      public nsSupportsWeakReference,
                      public nsWrapperCache,
                      public SupportsWeakPtr<WebGLContext> {
@@ -1014,16 +1021,33 @@ class WebGLContext : public nsICanvasRen
   WebGLRefPtr<WebGLBuffer> mBoundUniformBuffer;
 
   std::vector<IndexedBufferBinding> mIndexedUniformBufferBindings;
 
   WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
   WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
                                                          GLuint index);
 
+  // -
+
+  bool ValidateBufferForNonTf(const WebGLBuffer&, GLenum nonTfTarget,
+                              uint32_t nonTfId) const;
+
+  bool ValidateBufferForNonTf(const WebGLBuffer* const nonTfBuffer,
+                              const GLenum nonTfTarget,
+                              const uint32_t nonTfId = -1) const {
+    if (!nonTfBuffer) return true;
+    return ValidateBufferForNonTf(*nonTfBuffer, nonTfTarget, nonTfId);
+  }
+
+  bool ValidateBuffersForTf(const WebGLTransformFeedback&,
+                            const webgl::LinkedProgramInfo&) const;
+  bool ValidateBuffersForTf(
+      const std::vector<webgl::BufferAndIndex>& tfBuffers) const;
+
   // -----------------------------------------------------------------------------
   // Queries (WebGL2ContextQueries.cpp)
  protected:
   WebGLRefPtr<WebGLQuery> mQuerySlot_SamplesPassed;
   WebGLRefPtr<WebGLQuery> mQuerySlot_TFPrimsWritten;
   WebGLRefPtr<WebGLQuery> mQuerySlot_TimeElapsed;
 
   WebGLRefPtr<WebGLQuery>* ValidateQuerySlotByTarget(GLenum target);
@@ -2114,17 +2138,16 @@ class ScopedFBRebinder final {
   explicit ScopedFBRebinder(const WebGLContext* const webgl) : mWebGL(webgl) {}
   ~ScopedFBRebinder();
 };
 
 class ScopedLazyBind final {
  private:
   gl::GLContext* const mGL;
   const GLenum mTarget;
-  const WebGLBuffer* const mBuf;
 
  public:
   ScopedLazyBind(gl::GLContext* gl, GLenum target, const WebGLBuffer* buf);
   ~ScopedLazyBind();
 };
 
 ////
 
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -73,29 +73,24 @@ WebGLBuffer* WebGLContext::ValidateBuffe
 
   if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
     if (mBoundTransformFeedback->IsActiveAndNotPaused()) {
       ErrorInvalidOperation(
           "Cannot select TRANSFORM_FEEDBACK_BUFFER when"
           " transform feedback is active and unpaused.");
       return nullptr;
     }
-    if (buffer->IsBoundForNonTF()) {
-      ErrorInvalidOperation(
-          "Specified WebGLBuffer is currently bound for"
-          " non-transform-feedback.");
-      return nullptr;
-    }
+    const auto tfBuffers = std::vector<webgl::BufferAndIndex>{{
+        {buffer},
+    }};
+
+    if (!ValidateBuffersForTf(tfBuffers)) return nullptr;
   } else {
-    if (buffer->IsBoundForTF()) {
-      ErrorInvalidOperation(
-          "Specified WebGLBuffer is currently bound for"
-          " transform feedback.");
+    if (mBoundTransformFeedback && !ValidateBufferForNonTf(buffer, target))
       return nullptr;
-    }
   }
 
   return buffer.get();
 }
 
 IndexedBufferBinding* WebGLContext::ValidateIndexedBufferSlot(GLenum target,
                                                               GLuint index) {
   decltype(mIndexedUniformBufferBindings)* bindings;
@@ -132,29 +127,24 @@ void WebGLContext::BindBuffer(GLenum tar
 
   if (buffer && !ValidateObject("buffer", *buffer)) return;
 
   const auto& slot = ValidateBufferSlot(target);
   if (!slot) return;
 
   if (buffer && !buffer->ValidateCanBindToTarget(target)) return;
 
-  gl->fBindBuffer(target, buffer ? buffer->mGLName : 0);
+  if (!IsVirtualBufferTarget(target)) {
+    gl->fBindBuffer(target, buffer ? buffer->mGLName : 0);
+  }
 
-  WebGLBuffer::SetSlot(target, buffer, slot);
+  *slot = buffer;
   if (buffer) {
     buffer->SetContentAfterBind(target);
   }
-
-  switch (target) {
-    case LOCAL_GL_PIXEL_PACK_BUFFER:
-    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
-      gl->fBindBuffer(target, 0);
-      break;
-  }
 }
 
 ////////////////////////////////////////
 
 bool WebGLContext::ValidateIndexedBufferBinding(
     GLenum target, GLuint index,
     WebGLRefPtr<WebGLBuffer>** const out_genericBinding,
     IndexedBufferBinding** const out_indexedBinding) {
@@ -189,21 +179,24 @@ void WebGLContext::BindBufferBase(GLenum
     return;
   }
 
   if (buffer && !buffer->ValidateCanBindToTarget(target)) return;
 
   ////
 
   gl->fBindBufferBase(target, index, buffer ? buffer->mGLName : 0);
+  if (buffer) {
+    gl->fBindBuffer(target, 0);  // Reset generic.
+  }
 
   ////
 
-  WebGLBuffer::SetSlot(target, buffer, genericBinding);
-  WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding);
+  *genericBinding = buffer;
+  indexedBinding->mBufferBinding = buffer;
   indexedBinding->mRangeStart = 0;
   indexedBinding->mRangeSize = 0;
 
   if (buffer) {
     buffer->SetContentAfterBind(target);
   }
 }
 
@@ -265,21 +258,24 @@ void WebGLContext::BindBufferRange(GLenu
     // BindBufferRange will fail if the buffer's contents is undefined.
     // Bind so driver initializes the buffer.
     gl->fBindBuffer(target, buffer->mGLName);
   }
 #endif
 
   gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset,
                        size);
+  if (buffer) {
+    gl->fBindBuffer(target, 0);  // Reset generic.
+  }
 
   ////
 
-  WebGLBuffer::SetSlot(target, buffer, genericBinding);
-  WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding);
+  *genericBinding = buffer;
+  indexedBinding->mBufferBinding = buffer;
   indexedBinding->mRangeStart = offset;
   indexedBinding->mRangeSize = size;
 
   if (buffer) {
     buffer->SetContentAfterBind(target);
   }
 }
 
@@ -403,17 +399,17 @@ void WebGLContext::DeleteBuffer(WebGLBuf
   const FuncScope funcScope(*this, "deleteBuffer");
   if (!ValidateDeleteObject(buffer)) return;
 
   ////
 
   const auto fnClearIfBuffer = [&](GLenum target,
                                    WebGLRefPtr<WebGLBuffer>& bindPoint) {
     if (bindPoint == buffer) {
-      WebGLBuffer::SetSlot(target, nullptr, &bindPoint);
+      bindPoint = nullptr;
     }
   };
 
   fnClearIfBuffer(0, mBoundArrayBuffer);
   fnClearIfBuffer(0, mBoundVertexArray->mElementArrayBuffer);
 
   for (auto& cur : mBoundVertexArray->mAttribs) {
     fnClearIfBuffer(0, cur.mBuf);
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -195,16 +195,133 @@ bool WebGLContext::ValidateStencilParams
         " (before front/back comparison, WRITEMASK and VALUE_MASK"
         " are masked with (2^s)-1, and REF is clamped to"
         " [0, (2^s)-1], where `s` is the number of enabled stencil"
         " bits in the draw framebuffer)");
   }
   return ok;
 }
 
+// -
+
+static void GenErrorIllegalUse(const WebGLContext& webgl,
+                               const GLenum useTarget, const uint32_t useId,
+                               const GLenum boundTarget,
+                               const uint32_t boundId) {
+  const auto fnName = [&](const GLenum target, const uint32_t id) {
+    auto name = nsCString(EnumString(target).c_str());
+    if (id != static_cast<uint32_t>(-1)) {
+      name += nsPrintfCString("[%u]", id);
+    }
+    return name;
+  };
+  const auto& useName = fnName(useTarget, useId);
+  const auto& boundName = fnName(boundTarget, boundId);
+  webgl.GenerateError(LOCAL_GL_INVALID_OPERATION,
+                      "Illegal use of buffer at %s"
+                      " while also bound to %s.",
+                      useName.BeginReading(), boundName.BeginReading());
+}
+
+bool WebGLContext::ValidateBufferForNonTf(const WebGLBuffer& nonTfBuffer,
+                                          const GLenum nonTfTarget,
+                                          const uint32_t nonTfId) const {
+  bool dupe = false;
+  const auto& tfAttribs = mBoundTransformFeedback->mIndexedBindings;
+  for (const auto& cur : tfAttribs) {
+    dupe |= (&nonTfBuffer == cur.mBufferBinding.get());
+  }
+  if (MOZ_LIKELY(!dupe)) return true;
+
+  dupe = false;
+  for (const auto tfId : IntegerRange(tfAttribs.size())) {
+    const auto& tfBuffer = tfAttribs[tfId].mBufferBinding;
+    if (&nonTfBuffer == tfBuffer) {
+      dupe = true;
+      GenErrorIllegalUse(*this, nonTfTarget, nonTfId,
+                         LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tfId);
+    }
+  }
+  MOZ_ASSERT(dupe);
+  return false;
+}
+
+bool WebGLContext::ValidateBuffersForTf(
+    const WebGLTransformFeedback& tfo,
+    const webgl::LinkedProgramInfo& linkInfo) const {
+  size_t numUsed;
+  switch (linkInfo.transformFeedbackBufferMode) {
+    case LOCAL_GL_INTERLEAVED_ATTRIBS:
+      numUsed = 1;
+      break;
+
+    case LOCAL_GL_SEPARATE_ATTRIBS:
+      numUsed = linkInfo.transformFeedbackVaryings.size();
+      break;
+
+    default:
+      MOZ_CRASH();
+  }
+
+  std::vector<webgl::BufferAndIndex> tfBuffers;
+  tfBuffers.reserve(numUsed);
+  for (const auto i : IntegerRange(numUsed)) {
+    tfBuffers.push_back({tfo.mIndexedBindings[i].mBufferBinding.get(),
+                         static_cast<uint32_t>(i)});
+  }
+
+  return ValidateBuffersForTf(tfBuffers);
+}
+
+bool WebGLContext::ValidateBuffersForTf(
+    const std::vector<webgl::BufferAndIndex>& tfBuffers) const {
+  bool dupe = false;
+  const auto fnCheck = [&](const WebGLBuffer* const nonTf,
+                           const GLenum nonTfTarget, const uint32_t nonTfId) {
+    for (const auto& tf : tfBuffers) {
+      dupe |= (nonTf && tf.buffer == nonTf);
+    }
+
+    if (MOZ_LIKELY(!dupe)) return false;
+
+    for (const auto& tf : tfBuffers) {
+      if (nonTf && tf.buffer == nonTf) {
+        dupe = true;
+        GenErrorIllegalUse(*this, LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, tf.id,
+                           nonTfTarget, nonTfId);
+      }
+    }
+    return true;
+  };
+
+  fnCheck(mBoundArrayBuffer.get(), LOCAL_GL_ARRAY_BUFFER, -1);
+  fnCheck(mBoundCopyReadBuffer.get(), LOCAL_GL_COPY_READ_BUFFER, -1);
+  fnCheck(mBoundCopyWriteBuffer.get(), LOCAL_GL_COPY_WRITE_BUFFER, -1);
+  fnCheck(mBoundPixelPackBuffer.get(), LOCAL_GL_PIXEL_PACK_BUFFER, -1);
+  fnCheck(mBoundPixelUnpackBuffer.get(), LOCAL_GL_PIXEL_UNPACK_BUFFER, -1);
+  // fnCheck(mBoundTransformFeedbackBuffer.get(),
+  // LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, -1);
+  fnCheck(mBoundUniformBuffer.get(), LOCAL_GL_UNIFORM_BUFFER, -1);
+
+  for (const auto i : IntegerRange(mIndexedUniformBufferBindings.size())) {
+    const auto& cur = mIndexedUniformBufferBindings[i];
+    fnCheck(cur.mBufferBinding.get(), LOCAL_GL_UNIFORM_BUFFER, i);
+  }
+
+  fnCheck(mBoundVertexArray->mElementArrayBuffer.get(),
+          LOCAL_GL_ELEMENT_ARRAY_BUFFER, -1);
+  const auto& vertAttribs = mBoundVertexArray->mAttribs;
+  for (const auto i : IntegerRange(vertAttribs.size())) {
+    const auto& cur = vertAttribs[i];
+    fnCheck(cur.mBuf.get(), LOCAL_GL_ARRAY_BUFFER, i);
+  }
+
+  return !dupe;
+}
+
 ////////////////////////////////////////
 
 template <typename T>
 static bool DoSetsIntersect(const std::set<T>& a, const std::set<T>& b) {
   std::vector<T> intersection;
   std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
                         std::back_inserter(intersection));
   return bool(intersection.size());
@@ -246,81 +363,52 @@ const webgl::CachedDrawFetchLimits* Vali
     webgl->ErrorInvalidOperation("The current program is not linked.");
     return nullptr;
   }
   const auto& linkInfo = webgl->mActiveProgramLinkInfo;
 
   // -
   // Check UBO sizes.
 
-  for (const auto& cur : linkInfo->uniformBlocks) {
+  for (const auto i : IntegerRange(linkInfo->uniformBlocks.size())) {
+    const auto& cur = linkInfo->uniformBlocks[i];
     const auto& dataSize = cur->mDataSize;
     const auto& binding = cur->mBinding;
     if (!binding) {
       webgl->ErrorInvalidOperation("Buffer for uniform block is null.");
       return nullptr;
     }
 
     const auto availByteCount = binding->ByteCount();
     if (dataSize > availByteCount) {
       webgl->ErrorInvalidOperation(
           "Buffer for uniform block is smaller"
           " than UNIFORM_BLOCK_DATA_SIZE.");
       return nullptr;
     }
 
-    if (binding->mBufferBinding->IsBoundForTF()) {
-      webgl->ErrorInvalidOperation(
-          "Buffer for uniform block is bound or"
-          " in use for transform feedback.");
+    if (!webgl->ValidateBufferForNonTf(binding->mBufferBinding,
+                                       LOCAL_GL_UNIFORM_BUFFER, i))
       return nullptr;
-    }
   }
 
   // -
 
   const auto& tfo = webgl->mBoundTransformFeedback;
   if (tfo && tfo->IsActiveAndNotPaused()) {
     if (fb) {
       const auto& info = *fb->GetCompletenessInfo();
       if (info.isMultiview) {
         webgl->ErrorInvalidOperation(
             "Cannot render to multiview with transform feedback.");
         return nullptr;
       }
     }
 
-    uint32_t numUsed;
-    switch (linkInfo->transformFeedbackBufferMode) {
-      case LOCAL_GL_INTERLEAVED_ATTRIBS:
-        numUsed = 1;
-        break;
-
-      case LOCAL_GL_SEPARATE_ATTRIBS:
-        numUsed = linkInfo->transformFeedbackVaryings.size();
-        break;
-
-      default:
-        MOZ_CRASH();
-    }
-
-    for (uint32_t i = 0; i < numUsed; ++i) {
-      const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
-      if (buffer->IsBoundForNonTF()) {
-        webgl->ErrorInvalidOperation(
-            "Transform feedback varying %u's buffer"
-            " is bound for non-transform-feedback.",
-            i);
-        return nullptr;
-      }
-
-      // Technically we don't know that this will be updated yet, but we can
-      // speculatively mark it.
-      buffer->ResetLastUpdateFenceId();
-    }
+    if (!webgl->ValidateBuffersForTf(*tfo, *linkInfo)) return nullptr;
   }
 
   // -
 
   const auto& fragOutputs = linkInfo->fragOutputs;
   const auto fnValidateFragOutputType =
       [&](const uint8_t loc, const webgl::TextureBaseType dstBaseType) {
         const auto itr = fragOutputs.find(loc);
@@ -382,16 +470,25 @@ const webgl::CachedDrawFetchLimits* Vali
   if (instanceCount > fetchLimits->maxInstances) {
     webgl->ErrorInvalidOperation(
         "Instance fetch requires %u, but attribs only"
         " supply %u.",
         instanceCount, uint32_t(fetchLimits->maxInstances));
     return nullptr;
   }
 
+  if (tfo) {
+    for (const auto& used : fetchLimits->usedBuffers) {
+      MOZ_ASSERT(used.buffer);
+      if (!webgl->ValidateBufferForNonTf(*used.buffer, LOCAL_GL_ARRAY_BUFFER,
+                                         used.id))
+        return nullptr;
+    }
+  }
+
   // -
 
   webgl->RunContextLossTimer();
 
   return fetchLimits;
 }
 
 ////////////////////////////////////////
@@ -484,16 +581,23 @@ class ScopedDrawWithTransformFeedback fi
 
     mUsedVerts = usedVerts.value();
   }
 
   void Advance() const {
     if (!mWithTF) return;
 
     mTFO->mActive_VertPosition += mUsedVerts;
+
+    for (const auto& cur : mTFO->mIndexedBindings) {
+      const auto& buffer = cur.mBufferBinding;
+      if (buffer) {
+        buffer->ResetLastUpdateFenceId();
+      }
+    }
   }
 };
 
 static bool HasInstancedDrawing(const WebGLContext& webgl) {
   return webgl.IsWebGL2() ||
          webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
 }
 
@@ -640,17 +744,16 @@ WebGLBuffer* WebGLContext::DrawElements_
   ////
   // Index fetching
 
   const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
   if (!indexBuffer) {
     ErrorInvalidOperation("Index buffer not bound.");
     return nullptr;
   }
-  MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible.");
 
   const size_t availBytes = indexBuffer->ByteLength();
   const auto availIndices =
       AvailGroups(availBytes, byteOffset, bytesPerIndex, bytesPerIndex);
   if (instanceCount && indexCount > availIndices) {
     ErrorInvalidOperation("Index buffer too small.");
     return nullptr;
   }
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -615,17 +615,17 @@ bool WebGLContext::InitAndValidateGL(Fai
   // regardless of version.
   //
   // GL Spec 4.0.0:
   // (https://www.opengl.org/registry/doc/glspec40.core.20100311.pdf)
   // in Section E.2.2 "Removed Features", pg 397: "[...] The default
   // vertex array object (the name zero) is also deprecated. [...]"
   mDefaultVertexArray = WebGLVertexArray::Create(this);
   mDefaultVertexArray->BindVertexArray();
-  mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
+  mDefaultVertexArray->mAttribs.resize(mGLMaxVertexAttribs);
 
   mPixelStore_FlipY = false;
   mPixelStore_PremultiplyAlpha = false;
   mPixelStore_ColorspaceConversion = BROWSER_DEFAULT_WEBGL;
   mPixelStore_RequireFastPath = false;
 
   // GLES 3.0.4, p259:
   mPixelStore_UnpackImageHeight = 0;
--- a/dom/canvas/WebGLContextVertexArray.cpp
+++ b/dom/canvas/WebGLContextVertexArray.cpp
@@ -13,29 +13,24 @@
 namespace mozilla {
 
 void WebGLContext::BindVertexArray(WebGLVertexArray* array) {
   const FuncScope funcScope(*this, "bindVertexArray");
   if (IsContextLost()) return;
 
   if (array && !ValidateObject("array", *array)) return;
 
-  if (mBoundVertexArray) {
-    mBoundVertexArray->AddBufferBindCounts(-1);
-  }
-
   if (array == nullptr) {
     array = mDefaultVertexArray;
   }
 
   array->BindVertexArray();
 
   MOZ_ASSERT(mBoundVertexArray == array);
   if (mBoundVertexArray) {
-    mBoundVertexArray->AddBufferBindCounts(+1);
     mBoundVertexArray->mHasBeenBound = true;
   }
 }
 
 already_AddRefed<WebGLVertexArray> WebGLContext::CreateVertexArray() {
   const FuncScope funcScope(*this, "createVertexArray");
   if (IsContextLost()) return nullptr;
 
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -388,27 +388,20 @@ void WebGLContext::VertexAttribAnyPointe
   const auto& buffer = mBoundArrayBuffer;
   if (!buffer && byteOffset) {
     ErrorInvalidOperation("If ARRAY_BUFFER is null, byteOffset must be zero.");
     return;
   }
 
   ////
 
-  if (isFuncInt) {
-    gl->fVertexAttribIPointer(index, size, type, stride,
-                              reinterpret_cast<void*>(byteOffset));
-  } else {
-    gl->fVertexAttribPointer(index, size, type, normalized, stride,
-                             reinterpret_cast<void*>(byteOffset));
-  }
-
   WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
   vd.VertexAttribPointer(isFuncInt, buffer, AutoAssertCast(size), type,
                          normalized, stride, byteOffset);
+  vd.DoVertexAttribPointer(gl, index);
   mBoundVertexArray->InvalidateCaches();
 }
 
 ////////////////////////////////////////
 
 void WebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
   const FuncScope funcScope(*this, "vertexAttribDivisor");
   if (IsContextLost()) return;
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -596,35 +596,32 @@ webgl::LinkedProgramInfo::GetDrawFetchLi
       }
       i++;
     }
   }
 
   bool hasActiveAttrib = false;
   bool hasActiveDivisor0 = false;
   webgl::CachedDrawFetchLimits fetchLimits = {UINT64_MAX, UINT64_MAX};
+  fetchLimits.usedBuffers.reserve(this->attribs.size());
 
   for (const auto& progAttrib : this->attribs) {
     const auto& loc = progAttrib.mLoc;
     if (loc == -1) continue;
     hasActiveAttrib |= true;
 
     const auto& attribData = vao->mAttribs[loc];
     hasActiveDivisor0 |= (attribData.mDivisor == 0);
 
     webgl::AttribBaseType attribDataBaseType;
     if (attribData.mEnabled) {
       MOZ_ASSERT(attribData.mBuf);
-      if (attribData.mBuf->IsBoundForTF()) {
-        webgl->ErrorInvalidOperation(
-            "Vertex attrib %u's buffer is bound for"
-            " transform feedback.",
-            loc);
-        return nullptr;
-      }
+      fetchLimits.usedBuffers.push_back(
+          {attribData.mBuf.get(), static_cast<uint32_t>(loc)});
+
       cacheDeps.push_back(&attribData.mBuf->mFetchInvalidator);
 
       attribDataBaseType = attribData.BaseType();
 
       const size_t availBytes = attribData.mBuf->ByteLength();
       const auto availElems =
           AvailGroups(availBytes, attribData.ByteOffset(),
                       attribData.BytesPerVertex(), attribData.ExplicitStride());
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -80,18 +80,19 @@ struct UniformBlockInfo final {
 struct FragOutputInfo final {
   const uint8_t loc;
   const nsCString userName;
   const nsCString mappedName;
   const TextureBaseType baseType;
 };
 
 struct CachedDrawFetchLimits final {
-  uint64_t maxVerts;
-  uint64_t maxInstances;
+  uint64_t maxVerts = 0;
+  uint64_t maxInstances = 0;
+  std::vector<BufferAndIndex> usedBuffers;
 };
 
 struct LinkedProgramInfo final : public RefCounted<LinkedProgramInfo>,
                                  public SupportsWeakPtr<LinkedProgramInfo>,
                                  public CacheInvalidator {
   friend class mozilla::WebGLProgram;
 
   MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -159,25 +159,16 @@ void WebGLTransformFeedback::ResumeTrans
   ////
 
   MOZ_ASSERT(mIsActive);
   mIsPaused = false;
 }
 
 ////////////////////////////////////////
 
-void WebGLTransformFeedback::AddBufferBindCounts(int8_t addVal) const {
-  const GLenum target = LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER;
-  for (const auto& binding : mIndexedBindings) {
-    WebGLBuffer::AddBindCount(target, binding.mBufferBinding.get(), addVal);
-  }
-}
-
-////////////////////////////////////////
-
 JSObject* WebGLTransformFeedback::WrapObject(JSContext* cx,
                                              JS::Handle<JSObject*> givenProto) {
   return dom::WebGLTransformFeedback_Binding::Wrap(cx, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTransformFeedback, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTransformFeedback, Release)
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedback, mIndexedBindings,
--- a/dom/canvas/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -55,18 +55,16 @@ class WebGLTransformFeedback final
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTransformFeedback)
 
   void Delete();
   WebGLContext* GetParentObject() const { return mContext; }
   virtual JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
 
   bool IsActiveAndNotPaused() const { return mIsActive && !mIsPaused; }
 
-  void AddBufferBindCounts(int8_t addVal) const;
-
   // GL Funcs
   void BeginTransformFeedback(GLenum primMode);
   void EndTransformFeedback();
   void PauseTransformFeedback();
   void ResumeTransformFeedback();
 };
 
 }  // namespace mozilla
--- a/dom/canvas/WebGLVertexArray.cpp
+++ b/dom/canvas/WebGLVertexArray.cpp
@@ -15,47 +15,58 @@
 namespace mozilla {
 
 JSObject* WebGLVertexArray::WrapObject(JSContext* cx,
                                        JS::Handle<JSObject*> givenProto) {
   return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
 }
 
 WebGLVertexArray::WebGLVertexArray(WebGLContext* const webgl, const GLuint name)
-    : WebGLRefCountedObject(webgl), mGLName(name) {
-  mAttribs.SetLength(mContext->mGLMaxVertexAttribs);
+    : WebGLRefCountedObject(webgl),
+      mGLName(name),
+      mAttribs(mContext->mGLMaxVertexAttribs) {
   mContext->mVertexArrays.insertBack(this);
 }
 
 WebGLVertexArray::~WebGLVertexArray() { MOZ_ASSERT(IsDeleted()); }
 
-void WebGLVertexArray::AddBufferBindCounts(int8_t addVal) const {
-  const GLenum target = 0;  // Anything non-TF is fine.
-  WebGLBuffer::AddBindCount(target, mElementArrayBuffer.get(), addVal);
-  for (const auto& attrib : mAttribs) {
-    WebGLBuffer::AddBindCount(target, attrib.mBuf.get(), addVal);
-  }
-}
-
 WebGLVertexArray* WebGLVertexArray::Create(WebGLContext* webgl) {
   WebGLVertexArray* array;
   if (webgl->gl->IsSupported(gl::GLFeature::vertex_array_object)) {
     array = new WebGLVertexArrayGL(webgl);
   } else {
     array = new WebGLVertexArrayFake(webgl);
   }
   return array;
 }
 
 void WebGLVertexArray::Delete() {
   DeleteImpl();
 
   LinkedListElement<WebGLVertexArray>::removeFrom(mContext->mVertexArrays);
   mElementArrayBuffer = nullptr;
-  mAttribs.Clear();
+  mAttribs.clear();
+}
+
+// -
+
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& callback,
+    const std::vector<WebGLVertexAttribData>& field, const char* name,
+    uint32_t flags = 0) {
+  for (auto& cur : field) {
+    ImplCycleCollectionTraverse(callback, cur.mBuf, name, flags);
+  }
+}
+
+inline void ImplCycleCollectionUnlink(
+    std::vector<WebGLVertexAttribData>& field) {
+  for (auto& cur : field) {
+    cur.mBuf = nullptr;
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArray, mAttribs,
                                       mElementArrayBuffer)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLVertexArray, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLVertexArray, Release)
 
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -1,17 +1,18 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_VERTEX_ARRAY_H_
 #define WEBGL_VERTEX_ARRAY_H_
 
-#include "nsTArray.h"
+#include <vector>
+
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 
 #include "CacheInvalidator.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLVertexAttribData.h"
 
@@ -34,31 +35,29 @@ class WebGLVertexArray : public nsWrappe
   WebGLContext* GetParentObject() const { return mContext; }
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> givenProto) override;
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLVertexArray)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLVertexArray)
 
-  void AddBufferBindCounts(int8_t addVal) const;
-
  protected:
   WebGLVertexArray(WebGLContext* webgl, GLuint name);
   virtual ~WebGLVertexArray();
 
   virtual void BindVertexArray() = 0;
   virtual void DeleteImpl() = 0;
 
  public:
   const GLuint mGLName;
   bool mHasBeenBound = false;
 
  protected:
-  nsTArray<WebGLVertexAttribData> mAttribs;
+  std::vector<WebGLVertexAttribData> mAttribs;
   WebGLRefPtr<WebGLBuffer> mElementArrayBuffer;
 
   friend class ScopedDrawHelper;
   friend class WebGLContext;
   friend class WebGLVertexArrayFake;
   friend class WebGL2Context;
   friend struct webgl::LinkedProgramInfo;
 };
--- a/dom/canvas/WebGLVertexArrayFake.cpp
+++ b/dom/canvas/WebGLVertexArrayFake.cpp
@@ -33,17 +33,17 @@ void WebGLVertexArrayFake::BindVertexArr
     if (vd.mEnabled) {
       gl->fEnableVertexAttribArray(i);
     } else {
       gl->fDisableVertexAttribArray(i);
     }
     ++i;
   }
 
-  size_t len = prevVertexArray->mAttribs.Length();
+  size_t len = prevVertexArray->mAttribs.size();
   for (; i < len; ++i) {
     const auto& vd = prevVertexArray->mAttribs[i];
 
     if (vd.mEnabled) {
       gl->fDisableVertexAttribArray(i);
     }
   }
 
--- a/dom/canvas/WebGLVertexAttribData.cpp
+++ b/dom/canvas/WebGLVertexAttribData.cpp
@@ -64,29 +64,30 @@ static webgl::AttribBaseType AttribPoint
 }
 
 void WebGLVertexAttribData::VertexAttribPointer(bool integerFunc,
                                                 WebGLBuffer* buf, uint8_t size,
                                                 GLenum type, bool normalized,
                                                 uint32_t stride,
                                                 uint64_t byteOffset) {
   mIntegerFunc = integerFunc;
-  WebGLBuffer::SetSlot(0, buf, &mBuf);
+  mBuf = buf;
   mType = type;
   mBaseType = AttribPointerBaseType(integerFunc, type);
   mSize = size;
   mBytesPerVertex = CalcBytesPerVertex(mType, mSize);
   mNormalized = normalized;
   mStride = stride;
   mExplicitStride = (mStride ? mStride : mBytesPerVertex);
   mByteOffset = byteOffset;
 }
 
 void WebGLVertexAttribData::DoVertexAttribPointer(gl::GLContext* gl,
                                                   GLuint index) const {
+  const ScopedLazyBind lazyBind(gl, LOCAL_GL_ARRAY_BUFFER, mBuf);
   if (mIntegerFunc) {
     gl->fVertexAttribIPointer(index, mSize, mType, mStride,
                               (const void*)mByteOffset);
   } else {
     gl->fVertexAttribPointer(index, mSize, mType, mNormalized, mStride,
                              (const void*)mByteOffset);
   }
 }
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -70,16 +70,9 @@ class WebGLVertexAttribData final {
                            GLenum type, bool normalized, uint32_t stride,
                            uint64_t byteOffset);
 
   void DoVertexAttribPointer(gl::GLContext* gl, GLuint index) const;
 };
 
 }  // namespace mozilla
 
-inline void ImplCycleCollectionTraverse(
-    nsCycleCollectionTraversalCallback& callback,
-    mozilla::WebGLVertexAttribData& field, const char* name,
-    uint32_t flags = 0) {
-  CycleCollectionNoteChild(callback, field.mBuf.get(), name, flags);
-}
-
 #endif  // WEBGL_VERTEX_ATTRIB_DATA_H_
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -7380,17 +7380,16 @@ subsuite = webgl2-core
 [generated/test_2_conformance2__transform_feedback__non-existent-varying.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__transform_feedback__same-buffer-two-binding-points.html]
 subsuite = webgl2-core
 fail-if = 1
 skip-if = (os == 'win')
 [generated/test_2_conformance2__transform_feedback__simultaneous_binding.html]
 subsuite = webgl2-core
-fail-if = 1
 [generated/test_2_conformance2__transform_feedback__switching-objects.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__transform_feedback__too-small-buffers.html]
 subsuite = webgl2-core
 skip-if = 1
 [generated/test_2_conformance2__transform_feedback__transform_feedback.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__transform_feedback__two-unreferenced-varyings.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -175,18 +175,16 @@ fail-if = 1
 fail-if = 1
 
 [generated/test_2_conformance2__textures__misc__tex-unpack-params-with-flip-y-and-premultiply-alpha.html]
 fail-if = 1
 [generated/test_2_conformance2__transform_feedback__same-buffer-two-binding-points.html]
 fail-if = 1
 # ABORT_ON_ERROR from ANGLE
 skip-if = (os == 'win')
-[generated/test_2_conformance2__transform_feedback__simultaneous_binding.html]
-fail-if = 1
 
 ########################################################################
 # Complicated
 
 [generated/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html]
 # Asserts on linux debug. Crashes on Android.
 skip-if = (os == 'linux') || (os == 'android')