Bug 1310247 - Check if buffers are bound for transform feedback. - r=ethlin draft
authorJeff Gilbert (:jgilbert) <jgilbert@mozilla.com>
Thu, 17 Nov 2016 17:50:38 -0800
changeset 442753 48a9e2368877ab7cefa7cfa3fe9c2497462465fd
parent 442068 0534254e9a40b4bade2577c631fe4cfa0b5db41d
child 442754 7642ac962ce44392cfbb2e500f638d1cd398185d
push id36797
push userbmo:jgilbert@mozilla.com
push dateWed, 23 Nov 2016 04:31:09 +0000
reviewersethlin
bugs1310247
milestone53.0a1
Bug 1310247 - Check if buffers are bound for transform feedback. - r=ethlin MozReview-Commit-ID: 4EsOdz7vXNH
dom/canvas/WebGL2ContextBuffers.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/WebGLContextGL.cpp
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/WebGLTransformFeedback.cpp
dom/canvas/WebGLTransformFeedback.h
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -26,25 +26,16 @@ WebGL2Context::CopyBufferSubData(GLenum 
     const auto& readBuffer = ValidateBufferSelection(funcName, readTarget);
     if (!readBuffer)
         return;
 
     const auto& writeBuffer = ValidateBufferSelection(funcName, writeTarget);
     if (!writeBuffer)
         return;
 
-    if (readBuffer->mNumActiveTFOs ||
-        writeBuffer->mNumActiveTFOs)
-    {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
-        return;
-    }
-
     if (!ValidateNonNegative(funcName, "readOffset", readOffset) ||
         !ValidateNonNegative(funcName, "writeOffset", writeOffset) ||
         !ValidateNonNegative(funcName, "size", size))
     {
         return;
     }
 
     const auto fnValidateOffsetSize = [&](const char* info, GLintptr offset,
@@ -124,31 +115,16 @@ WebGL2Context::GetBufferSubData(GLenum t
     if (!buffer)
         return;
 
     if (!buffer->ValidateRange(funcName, srcByteOffset, byteLen))
         return;
 
     ////
 
-    if (buffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
-        return;
-    }
-
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
-        mBoundTransformFeedback->mIsActive)
-    {
-        ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
-                              funcName);
-        return;
-    }
-
     if (!CheckedInt<GLsizeiptr>(byteLen).isValid()) {
         ErrorOutOfMemory("%s: Size too large.", funcName);
         return;
     }
     const GLsizeiptr glByteLen(byteLen);
 
     ////
 
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -13,25 +13,22 @@
 namespace mozilla {
 
 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
     : WebGLContextBoundObject(webgl)
     , mGLName(buf)
     , mContent(Kind::Undefined)
     , mUsage(LOCAL_GL_STATIC_DRAW)
     , mByteLength(0)
-    , mNumActiveTFOs(0)
-    , mBoundForTF(false)
 {
     mContext->mBuffers.insertBack(this);
 }
 
 WebGLBuffer::~WebGLBuffer()
 {
-    MOZ_ASSERT(!mNumActiveTFOs);
     DeleteOnce();
 }
 
 void
 WebGLBuffer::SetContentAfterBind(GLenum target)
 {
     if (mContent != Kind::Undefined)
         return;
@@ -106,23 +103,16 @@ WebGLBuffer::BufferData(GLenum target, s
     // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr
     // is like intptr_t.
     if (!CheckedInt<GLsizeiptr>(size).isValid())
         return mContext->ErrorOutOfMemory("%s: bad size", funcName);
 
     if (!ValidateBufferUsageEnum(mContext, funcName, usage))
         return;
 
-    if (mNumActiveTFOs) {
-        mContext->ErrorInvalidOperation("%s: Buffer is bound to an active transform"
-                                        " feedback object.",
-                                        funcName);
-        return;
-    }
-
     const auto& gl = mContext->gl;
     gl->MakeCurrent();
     const ScopedLazyBind lazyBind(gl, target, this);
     mContext->InvalidateBufferFetching();
 
 #ifdef XP_MACOSX
     // bug 790879
     if (gl->WorkAroundDriverBugs() &&
@@ -215,25 +205,16 @@ bool
 WebGLBuffer::IsElementArrayUsedWithMultipleTypes() const
 {
     return mCache->BeenUsedWithMultipleTypes();
 }
 
 bool
 WebGLBuffer::ValidateCanBindToTarget(const char* funcName, GLenum target)
 {
-    const bool wouldBeTF = (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
-    if (mWebGLRefCnt && wouldBeTF != mBoundForTF) {
-        mContext->ErrorInvalidOperation("%s: Buffers cannot be simultaneously bound to "
-                                        " transform feedback and bound elsewhere.",
-                                        funcName);
-        return false;
-    }
-    mBoundForTF = wouldBeTF;
-
     /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
      *
      * In the WebGL 2 API, buffers have their WebGL buffer type
      * initially set to undefined. Calling bindBuffer, bindBufferRange
      * or bindBufferBase with the target argument set to any buffer
      * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
      * then set the WebGL buffer type of the buffer being bound
      * according to the table above.
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -74,15 +74,13 @@ public:
 
 protected:
     ~WebGLBuffer();
 
     Kind mContent;
     GLenum mUsage;
     size_t mByteLength;
     UniquePtr<WebGLElementArrayCache> mCache;
-    size_t mNumActiveTFOs;
-    bool mBoundForTF;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -115,16 +115,17 @@ WebGLContextOptions::WebGLContextOptions
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mBufferFetchingIsVerified(false)
     , mBufferFetchingHasPerVertex(false)
     , mMaxFetchedVertices(0)
     , mMaxFetchedInstances(0)
     , mLayerIsMirror(false)
     , mBypassShaderValidation(false)
+    , mBuffersForUB_Dirty(true)
     , mContextLossHandler(this)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoDepth(false)
     , mNeedsFakeNoStencil(false)
     , mNeedsEmulatedLoneDepthStencil(false)
 {
     mGeneration = 0;
     mInvalidated = false;
@@ -257,16 +258,17 @@ WebGLContext::DestroyResourcesAndContext
     mBoundTransformFeedback = nullptr;
     mDefaultTransformFeedback = nullptr;
 
     mQuerySlot_SamplesPassed = nullptr;
     mQuerySlot_TFPrimsWritten = nullptr;
     mQuerySlot_TimeElapsed = nullptr;
 
     mIndexedUniformBufferBindings.clear();
+    OnUBIndexedBindingsChanged();
 
     //////
 
     ClearLinkedList(mBuffers);
     ClearLinkedList(mFramebuffers);
     ClearLinkedList(mPrograms);
     ClearLinkedList(mQueries);
     ClearLinkedList(mRenderbuffers);
@@ -2565,16 +2567,52 @@ WebGLContext::ValidateArrayBufferView(co
         elemCount = elemCountOverride;
     }
 
     *out_bytes = bytes + (elemOffset * elemSize);
     *out_byteLen = elemCount * elemSize;
     return true;
 }
 
+////
+
+const decltype(WebGLContext::mBuffersForUB)&
+WebGLContext::BuffersForUB() const
+{
+    if (mBuffersForUB_Dirty) {
+        mBuffersForUB.clear();
+        for (const auto& cur : mIndexedUniformBufferBindings) {
+            if (cur.mBufferBinding) {
+                mBuffersForUB.insert(cur.mBufferBinding.get());
+            }
+        }
+        mBuffersForUB_Dirty = false;
+    }
+    return mBuffersForUB;
+}
+
+////
+
+bool
+WebGLContext::ValidateForNonTransformFeedback(const char* funcName, WebGLBuffer* buffer)
+{
+    if (!mBoundTransformFeedback)
+        return true;
+
+    const auto& buffersForTF = mBoundTransformFeedback->BuffersForTF();
+    if (buffersForTF.count(buffer)) {
+        ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for transform"
+                              " feedback.",
+                              funcName);
+        return false;
+    }
+
+    return true;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags)
 {
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1580,32 +1580,36 @@ protected:
                               uint32_t* const out_width, uint32_t* const out_height);
 
     bool HasDrawBuffers() const {
         return IsWebGL2() ||
                IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
     }
 
     WebGLRefPtr<WebGLBuffer>* ValidateBufferSlot(const char* funcName, GLenum target);
+public:
     WebGLBuffer* ValidateBufferSelection(const char* funcName, GLenum target);
+protected:
     IndexedBufferBinding* ValidateIndexedBufferSlot(const char* funcName, GLenum target,
                                                     GLuint index);
 
     bool ValidateIndexedBufferBinding(const char* funcName, GLenum target, GLuint index,
                                       WebGLRefPtr<WebGLBuffer>** const out_genericBinding,
                                       IndexedBufferBinding** const out_indexedBinding);
 
     bool ValidateNonNegative(const char* funcName, const char* argName, int64_t val) {
         if (MOZ_UNLIKELY(val < 0)) {
             ErrorInvalidValue("%s: `%s` must be non-negative.", funcName, argName);
             return false;
         }
         return true;
     }
 
+    bool ValidateForNonTransformFeedback(const char* funcName, WebGLBuffer* buffer);
+
 public:
     bool ValidateArrayBufferView(const char* funcName, const dom::ArrayBufferView& view,
                                  GLuint elemOffset, GLuint elemCountOverride,
                                  uint8_t** const out_bytes, size_t* const out_byteLen);
 
 protected:
     ////
 
@@ -1741,16 +1745,27 @@ protected:
     UniquePtr<FakeBlackTexture> mFakeBlack_3D_0001;
     UniquePtr<FakeBlackTexture> mFakeBlack_2D_Array_0000;
     UniquePtr<FakeBlackTexture> mFakeBlack_2D_Array_0001;
 
     bool BindFakeBlack(uint32_t texUnit, TexTarget target, FakeBlackType fakeBlack);
 
     ////////////////////////////////////
 
+private:
+    mutable bool mBuffersForUB_Dirty;
+    mutable std::set<const WebGLBuffer*> mBuffersForUB;
+
+public:
+    void OnUBIndexedBindingsChanged() const { mBuffersForUB_Dirty = true; }
+    const decltype(mBuffersForUB)& BuffersForUB() const;
+
+    ////////////////////////////////////
+
+protected:
     // Generic Vertex Attributes
     UniquePtr<GLenum[]> mVertexAttribType;
     GLfloat mVertexAttrib0Vector[4];
     GLfloat mFakeVertexAttrib0BufferObjectVector[4];
     size_t mFakeVertexAttrib0BufferObjectSize;
     GLuint mFakeVertexAttrib0BufferObject;
     WebGLVertexAttrib0Status mFakeVertexAttrib0BufferStatus;
 
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -70,16 +70,19 @@ WebGLContext::ValidateBufferSelection(co
         return nullptr;
     const auto& buffer = *slot;
 
     if (!buffer) {
         ErrorInvalidOperation("%s: Buffer for `target` is null.", funcName);
         return nullptr;
     }
 
+    if (!ValidateForNonTransformFeedback(funcName, buffer.get()))
+        return nullptr;
+
     return buffer.get();
 }
 
 IndexedBufferBinding*
 WebGLContext::ValidateIndexedBufferSlot(const char* funcName, GLenum target, GLuint index)
 {
     decltype(mIndexedUniformBufferBindings)* bindings;
     const char* maxIndexEnum;
@@ -207,16 +210,25 @@ WebGLContext::BindBufferBase(GLenum targ
     *genericBinding = buffer;
     indexedBinding->mBufferBinding = buffer;
     indexedBinding->mRangeStart = 0;
     indexedBinding->mRangeSize = 0;
 
     if (buffer) {
         buffer->SetContentAfterBind(target);
     }
+
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        mBoundTransformFeedback->OnIndexedBindingsChanged();
+        break;
+    case LOCAL_GL_UNIFORM:
+        OnUBIndexedBindingsChanged();
+        break;
+    }
 }
 
 void
 WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
                               WebGLintptr offset, WebGLsizeiptr size)
 {
     const char funcName[] = "bindBufferRange";
     if (IsContextLost())
@@ -291,16 +303,25 @@ WebGLContext::BindBufferRange(GLenum tar
     *genericBinding = buffer;
     indexedBinding->mBufferBinding = buffer;
     indexedBinding->mRangeStart = offset;
     indexedBinding->mRangeSize = size;
 
     if (buffer) {
         buffer->SetContentAfterBind(target);
     }
+
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        mBoundTransformFeedback->OnIndexedBindingsChanged();
+        break;
+    case LOCAL_GL_UNIFORM:
+        OnUBIndexedBindingsChanged();
+        break;
+    }
 }
 
 ////////////////////////////////////////
 
 void
 WebGLContext::BufferDataImpl(GLenum target, size_t dataLen, const uint8_t* data,
                              GLenum usage)
 {
@@ -388,23 +409,16 @@ WebGLContext::BufferSubDataImpl(GLenum t
 
     if (!ValidateNonNegative(funcName, "byteOffset", dstByteOffset))
         return;
 
     const auto& buffer = ValidateBufferSelection(funcName, target);
     if (!buffer)
         return;
 
-    if (buffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              "bufferSubData");
-        return;
-    }
-
     if (!buffer->ValidateRange(funcName, dstByteOffset, dataLen))
         return;
 
     if (!CheckedInt<GLintptr>(dataLen).isValid()) {
         ErrorOutOfMemory("%s: Size too large.", funcName);
         return;
     }
     const GLintptr glDataLen(dataLen);
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -14,16 +14,18 @@
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
+#include <algorithm>
+
 namespace mozilla {
 
 // For a Tegra workaround.
 static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
 
 ////////////////////////////////////////
 
 class ScopedResolveTexturesForDraw
@@ -295,16 +297,26 @@ WebGLContext::DrawArrays_check(const cha
         return false;
     }
 
     return true;
 }
 
 ////////////////////////////////////////
 
+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());
+}
+
 class ScopedDrawHelper final
 {
     WebGLContext* const mWebGL;
     bool mDidFake;
 
 public:
     ScopedDrawHelper(WebGLContext* webgl, const char* funcName, uint32_t firstVertex,
                      uint32_t vertCount, uint32_t instanceCount, bool* const out_error)
@@ -338,17 +350,17 @@ public:
             *out_error = true;
             return;
         }
         mDidFake = true;
 
         ////
         // Check UBO sizes.
 
-        const auto& linkInfo = webgl->mActiveProgramLinkInfo;
+        const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
         for (const auto& cur : linkInfo->uniformBlocks) {
             const auto& dataSize = cur->mDataSize;
             const auto& binding = cur->mBinding;
             if (!binding) {
                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
                                               funcName);
                 *out_error = true;
                 return;
@@ -361,16 +373,32 @@ public:
                                               funcName);
                 *out_error = true;
                 return;
             }
         }
 
         ////
 
+        const auto& tfo = mWebGL->mBoundTransformFeedback;
+        if (tfo) {
+            const auto& buffersForTF = tfo->BuffersForTF();
+            const auto& buffersForUB = mWebGL->BuffersForUB();
+            if (DoSetsIntersect(buffersForTF, buffersForUB)) {
+                mWebGL->ErrorInvalidOperation("%s: At least one WebGLBuffer is bound for"
+                                              " both transform feedback and as a uniform"
+                                              " buffer.",
+                                              funcName);
+                *out_error = true;
+                return;
+            }
+        }
+
+        ////
+
         mWebGL->RunContextLossTimer();
     }
 
     ~ScopedDrawHelper() {
         if (mDidFake) {
             mWebGL->UndoFakeVertexAttrib0();
         }
     }
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1464,60 +1464,52 @@ WebGLContext::ReadPixels(GLint x, GLint 
 
     ReadPixelsImpl(x, y, width, height, format, type, bytes, byteLen);
 }
 
 void
 WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                          GLenum type, WebGLsizeiptr offset, ErrorResult& out_error)
 {
+    const char funcName[] = "readPixels";
     if (!ReadPixels_SharedPrecheck(&out_error))
         return;
 
-    if (!mBoundPixelPackBuffer) {
-        ErrorInvalidOperation("readPixels: PIXEL_PACK_BUFFER must not be null.");
+    const auto& buffer = ValidateBufferSelection(funcName, LOCAL_GL_PIXEL_PACK_BUFFER);
+    if (!buffer)
         return;
-    }
-
-    if (mBoundPixelPackBuffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              "readPixels");
-        return;
-    }
 
     //////
 
-    if (offset < 0) {
-        ErrorInvalidValue("readPixels: offset must not be negative.");
+    if (!ValidateNonNegative(funcName, "offset", offset))
         return;
-    }
 
     {
         const auto bytesPerType = webgl::BytesPerPixel({LOCAL_GL_RED, type});
 
         if (offset % bytesPerType != 0) {
-            ErrorInvalidOperation("readPixels: `offset` must be divisible by the size"
-                                  " a `type` in bytes.");
+            ErrorInvalidOperation("%s: `offset` must be divisible by the size of `type`"
+                                  " in bytes.",
+                                  funcName);
             return;
         }
     }
 
     //////
 
-    const auto bytesAvailable = mBoundPixelPackBuffer->ByteLength();
+    const auto bytesAvailable = buffer->ByteLength();
     const auto checkedBytesAfterOffset = CheckedUint32(bytesAvailable) - offset;
 
     uint32_t bytesAfterOffset = 0;
     if (checkedBytesAfterOffset.isValid()) {
         bytesAfterOffset = checkedBytesAfterOffset.value();
     }
 
     gl->MakeCurrent();
-    const ScopedLazyBind lazyBind(gl, LOCAL_GL_PIXEL_PACK_BUFFER, mBoundPixelPackBuffer);
+    const ScopedLazyBind lazyBind(gl, LOCAL_GL_PIXEL_PACK_BUFFER, buffer);
 
     ReadPixelsImpl(x, y, width, height, format, type, (void*)offset, bytesAfterOffset);
 }
 
 static bool
 ValidateReadPixelsFormatAndType(const webgl::FormatInfo* srcFormat,
                                 const webgl::PackingInfo& pi, gl::GLContext* gl,
                                 WebGLContext* webgl)
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -1439,17 +1439,17 @@ WebGLProgram::TransformFeedbackVaryings(
     case LOCAL_GL_INTERLEAVED_ATTRIBS:
         break;
 
     case LOCAL_GL_SEPARATE_ATTRIBS:
         {
             GLuint maxAttribs = 0;
             gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                              &maxAttribs);
-            if (varyings.Length() >= maxAttribs) {
+            if (varyings.Length() > maxAttribs) {
                 mContext->ErrorInvalidValue("%s: Length of `varyings` exceeds %s.",
                                             funcName,
                                             "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
                 return;
             }
         }
         break;
 
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -180,24 +180,29 @@ FromView(WebGLContext* webgl, const char
         }
     }
     return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth,
                                              isClientData, bytes, availByteCount);
 }
 
 static UniquePtr<webgl::TexUnpackBytes>
 FromPboOffset(WebGLContext* webgl, const char* funcName, TexImageTarget target,
-              uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset,
-              size_t availBufferBytes)
+              uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset)
 {
     if (pboOffset < 0) {
         webgl->ErrorInvalidValue("%s: offset cannot be negative.", funcName);
         return nullptr;
     }
 
+    const auto& buffer = webgl->ValidateBufferSelection(funcName,
+                                                        LOCAL_GL_PIXEL_UNPACK_BUFFER);
+    if (!buffer)
+        return nullptr;
+
+    size_t availBufferBytes = buffer->ByteLength();
     if (size_t(pboOffset) > availBufferBytes) {
         webgl->ErrorInvalidOperation("%s: Offset is passed end of buffer.", funcName);
         return nullptr;
     }
     availBufferBytes -= pboOffset;
 
     const bool isClientData = false;
     const auto ptr = (const uint8_t*)pboOffset;
@@ -361,35 +366,22 @@ WebGLContext::From(const char* funcName,
     uint32_t width, height, depth;
     if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width,
                          &height, &depth))
     {
         return nullptr;
     }
 
     if (src.mPboOffset) {
-        if (!mBoundPixelUnpackBuffer) {
-            ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
-            return nullptr;
-        }
-
-        if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
-            ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                                  " object.",
-                                  funcName);
-            return nullptr;
-        }
-
-        const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
         return FromPboOffset(this, funcName, target, width, height, depth,
-                             *(src.mPboOffset), availBytes);
+                             *(src.mPboOffset));
     }
 
     if (mBoundPixelUnpackBuffer) {
-        ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
+        ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName);
         return nullptr;
     }
 
     if (src.mImageData) {
         return FromImageData(this, funcName, target, width, height, depth,
                              *(src.mImageData), scopedArr);
     }
 
@@ -1365,35 +1357,22 @@ WebGLContext::FromCompressed(const char*
     uint32_t width, height, depth;
     if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width,
                          &height, &depth))
     {
         return nullptr;
     }
 
     if (src.mPboOffset) {
-        if (!mBoundPixelUnpackBuffer) {
-            ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
-            return nullptr;
-        }
-
-        if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
-            ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                                  " object.",
-                                  funcName);
-            return nullptr;
-        }
-
-        const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
         return FromPboOffset(this, funcName, target, width, height, depth,
-                             *(src.mPboOffset), availBytes);
+                             *(src.mPboOffset));
     }
 
     if (mBoundPixelUnpackBuffer) {
-        ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
+        ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName);
         return nullptr;
     }
 
     return FromView(this, funcName, target, width, height, depth, src.mView,
                     src.mViewElemOffset, src.mViewElemLengthOverride);
 }
 
 void
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -12,16 +12,17 @@
 namespace mozilla {
 
 WebGLTransformFeedback::WebGLTransformFeedback(WebGLContext* webgl, GLuint tf)
     : WebGLContextBoundObject(webgl)
     , mGLName(tf)
     , mIndexedBindings(webgl->mGLMaxTransformFeedbackSeparateAttribs)
     , mIsPaused(false)
     , mIsActive(false)
+    , mBuffersForTF_Dirty(true)
 {
     mContext->mTransformFeedbacks.insertBack(this);
 }
 
 WebGLTransformFeedback::~WebGLTransformFeedback()
 {
     DeleteOnce();
 }
@@ -31,16 +32,38 @@ WebGLTransformFeedback::Delete()
 {
     if (mGLName) {
         mContext->MakeContextCurrent();
         mContext->gl->fDeleteTransformFeedbacks(1, &mGLName);
     }
     removeFrom(mContext->mTransformFeedbacks);
 }
 
+////
+
+const decltype(WebGLTransformFeedback::mBuffersForTF)&
+WebGLTransformFeedback::BuffersForTF() const
+{
+    // The generic bind point cannot incur undefined read/writes because otherwise it
+    // would be impossible to read back from this. The spec implies that readback from
+    // the TRANSFORM_FEEDBACK target is possible, just not simultaneously with being
+    // "bound or in use for transform feedback".
+    // Therefore, only the indexed bindings of the TFO count.
+    if (mBuffersForTF_Dirty) {
+        mBuffersForTF.clear();
+        for (const auto& cur : mIndexedBindings) {
+            if (cur.mBufferBinding) {
+                mBuffersForTF.insert(cur.mBufferBinding.get());
+            }
+        }
+        mBuffersForTF_Dirty = false;
+    }
+    return mBuffersForTF;
+}
+
 ////////////////////////////////////////
 
 void
 WebGLTransformFeedback::BeginTransformFeedback(GLenum primMode)
 {
     const char funcName[] = "beginTransformFeedback";
 
     if (mIsActive)
@@ -102,23 +125,16 @@ WebGLTransformFeedback::BeginTransformFe
 
     mActive_Program = prog;
     mActive_PrimMode = primMode;
     mActive_VertPosition = 0;
     mActive_VertCapacity = minVertCapacity;
 
     ////
 
-    for (const auto& cur : mIndexedBindings) {
-        const auto& buffer = cur.mBufferBinding;
-        if (buffer) {
-            buffer->mNumActiveTFOs++;
-        }
-    }
-
     mActive_Program->mNumActiveTFOs++;
 }
 
 
 void
 WebGLTransformFeedback::EndTransformFeedback()
 {
     const char funcName[] = "endTransformFeedback";
@@ -134,23 +150,16 @@ WebGLTransformFeedback::EndTransformFeed
 
     ////
 
     mIsActive = false;
     mIsPaused = false;
 
     ////
 
-    for (const auto& cur : mIndexedBindings) {
-        const auto& buffer = cur.mBufferBinding;
-        if (buffer) {
-            buffer->mNumActiveTFOs--;
-        }
-    }
-
     mActive_Program->mNumActiveTFOs--;
 }
 
 void
 WebGLTransformFeedback::PauseTransformFeedback()
 {
     const char funcName[] = "pauseTransformFeedback";
 
--- a/dom/canvas/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -32,29 +32,37 @@ private:
     bool mIsPaused;
     bool mIsActive;
     // Not in state tables:
     WebGLRefPtr<WebGLProgram> mActive_Program;
     MOZ_INIT_OUTSIDE_CTOR GLenum mActive_PrimMode;
     MOZ_INIT_OUTSIDE_CTOR size_t mActive_VertPosition;
     MOZ_INIT_OUTSIDE_CTOR size_t mActive_VertCapacity;
 
+    mutable bool mBuffersForTF_Dirty;
+    mutable std::set<const WebGLBuffer*> mBuffersForTF;
+
 public:
     WebGLTransformFeedback(WebGLContext* webgl, GLuint tf);
 private:
     ~WebGLTransformFeedback();
 
 public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTransformFeedback)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTransformFeedback)
 
     void Delete();
     WebGLContext* GetParentObject() const { return mContext; }
     virtual JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
 
+    ////
+
+    void OnIndexedBindingsChanged() const { mBuffersForTF_Dirty = true; }
+    const decltype(mBuffersForTF)& BuffersForTF() const;
+
     // GL Funcs
     void BeginTransformFeedback(GLenum primMode);
     void EndTransformFeedback();
     void PauseTransformFeedback();
     void ResumeTransformFeedback();
 };
 
 } // namespace mozilla