Bug 1326159 - Update TF buffer restrictions. - r=daoshengmu draft
authorJeff Gilbert <jgilbert@mozilla.com>
Thu, 29 Dec 2016 00:08:39 -0800
changeset 454405 2024209c413de9dab83006d5344ea9249d039200
parent 454404 65b6a8be5d5702432e653b7614ccf841c11ab8e4
child 540698 9de29ee37b7e1083d2569c6384d6caea97767898
push id39917
push userbmo:jgilbert@mozilla.com
push dateThu, 29 Dec 2016 08:09:15 +0000
reviewersdaoshengmu
bugs1326159
milestone53.0a1
Bug 1326159 - Update TF buffer restrictions. - r=daoshengmu MozReview-Commit-ID: CgfP70Y0wul
dom/canvas/WebGL2Context.cpp
dom/canvas/WebGL2ContextBuffers.cpp
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/WebGLContextGL.cpp
dom/canvas/WebGLContextVertexArray.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/WebGLVertexAttribData.cpp
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -158,16 +158,18 @@ WebGLContext::InitWebGL2(FailureReason* 
     gl->GetUIntegerv(LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS,
                      &mGLMaxUniformBufferBindings);
 
     mIndexedUniformBufferBindings.resize(mGLMaxUniformBufferBindings);
 
     mDefaultTransformFeedback = new WebGLTransformFeedback(this, 0);
     mBoundTransformFeedback = mDefaultTransformFeedback;
 
+    gl->fGenTransformFeedbacks(1, &mEmptyTFO);
+
     ////
 
     if (!gl->IsGLES()) {
         // Desktop OpenGL requires the following to be enabled in order to
         // support sRGB operations on framebuffers.
         gl->fEnable(LOCAL_GL_FRAMEBUFFER_SRGB_EXT);
     }
 
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -127,17 +127,32 @@ WebGL2Context::GetBufferSubData(GLenum t
     const GLsizeiptr glByteLen(byteLen);
 
     ////
 
     gl->MakeCurrent();
     const ScopedLazyBind readBind(gl, target, buffer);
 
     if (byteLen) {
-        const auto mappedBytes = gl->fMapBufferRange(target, srcByteOffset, glByteLen,
+        const bool isTF = (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
+        GLenum mapTarget = target;
+        if (isTF) {
+            gl->fBindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, mEmptyTFO);
+            gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, buffer->mGLName);
+            mapTarget = LOCAL_GL_ARRAY_BUFFER;
+        }
+
+        const auto mappedBytes = gl->fMapBufferRange(mapTarget, srcByteOffset, glByteLen,
                                                      LOCAL_GL_MAP_READ_BIT);
-        // Warning: Possibly shared memory.  See bug 1225033.
         memcpy(bytes, mappedBytes, byteLen);
-        gl->fUnmapBuffer(target);
+        gl->fUnmapBuffer(mapTarget);
+
+        if (isTF) {
+            const GLuint vbo = (mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
+            gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, vbo);
+            const GLuint tfo = (mBoundTransformFeedback ? mBoundTransformFeedback->mGLName
+                                                        : 0);
+            gl->fBindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, tfo);
+        }
     }
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -76,20 +76,28 @@ WebGL2Context::BindTransformFeedback(GLe
         ErrorInvalidOperation("%s: Currently bound transform feedback is active and not"
                               " paused.",
                               funcName);
         return;
     }
 
     ////
 
+    if (mBoundTransformFeedback) {
+        mBoundTransformFeedback->AddBufferBindCounts(-1);
+    }
+
     mBoundTransformFeedback = (tf ? tf : mDefaultTransformFeedback);
 
     MakeContextCurrent();
     gl->fBindTransformFeedback(target, mBoundTransformFeedback->mGLName);
+
+    if (mBoundTransformFeedback) {
+        mBoundTransformFeedback->AddBufferBindCounts(+1);
+    }
 }
 
 void
 WebGL2Context::BeginTransformFeedback(GLenum primMode)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -13,16 +13,18 @@
 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)
 {
     mContext->mBuffers.insertBack(this);
 }
 
 WebGLBuffer::~WebGLBuffer()
 {
     DeleteOnce();
 }
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -21,17 +21,16 @@ class WebGLElementArrayCache;
 class WebGLBuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLBuffer>
     , public LinkedListElement<WebGLBuffer>
 {
     friend class WebGLContext;
     friend class WebGL2Context;
     friend class WebGLTexture;
-    friend class WebGLTransformFeedback;
 
 public:
     enum class Kind {
         Undefined,
         ElementArray,
         OtherData
     };
 
@@ -61,25 +60,56 @@ public:
         return mContext;
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
     bool ValidateCanBindToTarget(const char* funcName, GLenum target);
     void BufferData(GLenum target, size_t size, const void* data, GLenum usage);
 
+    ////
+
+    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;
+        } 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();
 
     Kind mContent;
     GLenum mUsage;
     size_t mByteLength;
     UniquePtr<WebGLElementArrayCache> mCache;
+    size_t mTFBindCount;
+    size_t mNonTFBindCount;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -115,17 +115,17 @@ WebGLContextOptions::WebGLContextOptions
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mBufferFetchingIsVerified(false)
     , mBufferFetchingHasPerVertex(false)
     , mMaxFetchedVertices(0)
     , mMaxFetchedInstances(0)
     , mLayerIsMirror(false)
     , mBypassShaderValidation(false)
-    , mBuffersForUB_Dirty(true)
+    , mEmptyTFO(0)
     , mContextLossHandler(this)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoDepth(false)
     , mNeedsFakeNoStencil(false)
     , mNeedsEmulatedLoneDepthStencil(false)
     , mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation())
 {
     mGeneration = 0;
@@ -245,17 +245,16 @@ 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);
@@ -263,16 +262,23 @@ WebGLContext::DestroyResourcesAndContext
     ClearLinkedList(mShaders);
     ClearLinkedList(mSyncs);
     ClearLinkedList(mTextures);
     ClearLinkedList(mTransformFeedbacks);
     ClearLinkedList(mVertexArrays);
 
     //////
 
+    if (mEmptyTFO) {
+        gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
+        mEmptyTFO = 0;
+    }
+
+    //////
+
     mFakeBlack_2D_0000       = nullptr;
     mFakeBlack_2D_0001       = nullptr;
     mFakeBlack_CubeMap_0000  = nullptr;
     mFakeBlack_CubeMap_0001  = nullptr;
     mFakeBlack_3D_0000       = nullptr;
     mFakeBlack_3D_0001       = nullptr;
     mFakeBlack_2D_Array_0000 = nullptr;
     mFakeBlack_2D_Array_0001 = nullptr;
@@ -2380,52 +2386,16 @@ 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
@@ -1649,18 +1649,16 @@ protected:
     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:
     template<typename T>
     bool ValidateNonNull(const char* funcName, const dom::Nullable<T>& maybe) {
         if (maybe.IsNull()) {
             ErrorInvalidValue("%s: `null` is invalid.", funcName);
             return false;
         }
         return true;
@@ -1877,27 +1875,19 @@ 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;
+protected:
+    GLuint mEmptyTFO;
 
-public:
-    void OnUBIndexedBindingsChanged() const { mBuffersForUB_Dirty = true; }
-    const decltype(mBuffersForUB)& BuffersForUB() const;
-
-    ////////////////////////////////////
-
-protected:
     // Generic Vertex Attributes
     // Though CURRENT_VERTEX_ATTRIB is listed under "Vertex Shader State" in the spec
     // state tables, this isn't vertex shader /object/ state. This array is merely state
     // useful to vertex shaders, but is global state.
     UniquePtr<GLenum[]> mGenericVertexAttribTypes;
     uint8_t mGenericVertexAttrib0Data[sizeof(float) * 4];
 
     GLuint mFakeVertexAttrib0BufferObject;
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -70,18 +70,37 @@ 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;
+    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
+        if (mBoundTransformFeedback->IsActiveAndNotPaused()) {
+            ErrorInvalidOperation("%s: Cannot select TRANSFORM_FEEDBACK_BUFFER when"
+                                  " transform feedback is active and unpaused.",
+                                  funcName);
+            return nullptr;
+        }
+        if (buffer->IsBoundForNonTF()) {
+            ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for"
+                                  " non-transform-feedback.",
+                                  funcName);
+            return nullptr;
+        }
+    } else {
+        if (buffer->IsBoundForTF()) {
+            ErrorInvalidOperation("%s: Specified WebGLBuffer is currently bound for"
+                                  " transform feedback.",
+                                  funcName);
+            return nullptr;
+        }
+    }
 
     return buffer.get();
 }
 
 IndexedBufferBinding*
 WebGLContext::ValidateIndexedBufferSlot(const char* funcName, GLenum target, GLuint index)
 {
     decltype(mIndexedUniformBufferBindings)* bindings;
@@ -127,17 +146,17 @@ WebGLContext::BindBuffer(GLenum target, 
         return;
 
     if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
         return;
 
     gl->MakeCurrent();
     gl->fBindBuffer(target, buffer ? buffer->mGLName : 0);
 
-    *slot = buffer;
+    WebGLBuffer::SetSlot(target, buffer, slot);
     if (buffer) {
         buffer->SetContentAfterBind(target);
     }
 
     switch (target) {
     case LOCAL_GL_PIXEL_PACK_BUFFER:
     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
         gl->fBindBuffer(target, 0);
@@ -196,33 +215,24 @@ WebGLContext::BindBufferBase(GLenum targ
 
     ////
 
     gl->MakeCurrent();
     gl->fBindBufferBase(target, index, buffer ? buffer->mGLName : 0);
 
     ////
 
-    *genericBinding = buffer;
-    indexedBinding->mBufferBinding = buffer;
+    WebGLBuffer::SetSlot(target, buffer, genericBinding);
+    WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding);
     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,33 +301,24 @@ WebGLContext::BindBufferRange(GLenum tar
         gl->fBindBuffer(target, buffer->mGLName);
     }
 #endif
 
     gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset, size);
 
     ////
 
-    *genericBinding = buffer;
-    indexedBinding->mBufferBinding = buffer;
+    WebGLBuffer::SetSlot(target, buffer, genericBinding);
+    WebGLBuffer::SetSlot(target, buffer, &indexedBinding->mBufferBinding);
     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)
 {
@@ -472,49 +473,51 @@ WebGLContext::CreateBuffer()
 void
 WebGLContext::DeleteBuffer(WebGLBuffer* buffer)
 {
     if (!ValidateDeleteObject("deleteBuffer", buffer))
         return;
 
     ////
 
-    const auto fnClearIfBuffer = [&](WebGLRefPtr<WebGLBuffer>& bindPoint) {
+    const auto fnClearIfBuffer = [&](GLenum target, WebGLRefPtr<WebGLBuffer>& bindPoint) {
         if (bindPoint == buffer) {
-            bindPoint = nullptr;
+            WebGLBuffer::SetSlot(target, nullptr, &bindPoint);
         }
     };
 
-    fnClearIfBuffer(mBoundArrayBuffer);
-    fnClearIfBuffer(mBoundVertexArray->mElementArrayBuffer);
+    fnClearIfBuffer(0, mBoundArrayBuffer);
+    fnClearIfBuffer(0, mBoundVertexArray->mElementArrayBuffer);
+
+    for (auto& cur : mBoundVertexArray->mAttribs) {
+        fnClearIfBuffer(0, cur.mBuf);
+    }
 
     // WebGL binding points
     if (IsWebGL2()) {
-        fnClearIfBuffer(mBoundCopyReadBuffer);
-        fnClearIfBuffer(mBoundCopyWriteBuffer);
-        fnClearIfBuffer(mBoundPixelPackBuffer);
-        fnClearIfBuffer(mBoundPixelUnpackBuffer);
-        fnClearIfBuffer(mBoundUniformBuffer);
-        fnClearIfBuffer(mBoundTransformFeedback->mGenericBufferBinding);
+        fnClearIfBuffer(0, mBoundCopyReadBuffer);
+        fnClearIfBuffer(0, mBoundCopyWriteBuffer);
+        fnClearIfBuffer(0, mBoundPixelPackBuffer);
+        fnClearIfBuffer(0, mBoundPixelUnpackBuffer);
+        fnClearIfBuffer(0, mBoundUniformBuffer);
+        fnClearIfBuffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER,
+                        mBoundTransformFeedback->mGenericBufferBinding);
 
         if (!mBoundTransformFeedback->mIsActive) {
             for (auto& binding : mBoundTransformFeedback->mIndexedBindings) {
-                fnClearIfBuffer(binding.mBufferBinding);
+                fnClearIfBuffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER,
+                                binding.mBufferBinding);
             }
         }
 
         for (auto& binding : mIndexedUniformBufferBindings) {
-            fnClearIfBuffer(binding.mBufferBinding);
+            fnClearIfBuffer(0, binding.mBufferBinding);
         }
     }
 
-    for (auto& cur : mBoundVertexArray->mAttribs) {
-        fnClearIfBuffer(cur.mBuf);
-    }
-
     ////
 
     buffer->RequestDelete();
 }
 
 bool
 WebGLContext::IsBuffer(WebGLBuffer* buffer)
 {
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -351,16 +351,17 @@ public:
             return;
         }
         mDidFake = true;
 
         ////
         // Check UBO sizes.
 
         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;
@@ -369,46 +370,77 @@ public:
             const auto availByteCount = binding->ByteCount();
             if (dataSize > availByteCount) {
                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
                                               " than UNIFORM_BLOCK_DATA_SIZE.",
                                               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.",
+            if (binding->mBufferBinding->IsBoundForTF()) {
+                mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is bound or"
+                                              " in use for transform feedback.",
                                               funcName);
                 *out_error = true;
                 return;
             }
         }
 
         ////
 
-        for (const auto& progAttrib : mWebGL->mActiveProgramLinkInfo->attribs) {
+        const auto& tfo = mWebGL->mBoundTransformFeedback;
+        if (tfo && tfo->IsActiveAndNotPaused()) {
+            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()) {
+                    mWebGL->ErrorInvalidOperation("%s: Transform feedback varying %u's"
+                                                  " buffer is bound for"
+                                                  " non-transform-feedback.",
+                                                  funcName, i);
+                    *out_error = true;
+                    return;
+                }
+            }
+        }
+
+        ////
+
+        for (const auto& progAttrib : linkInfo->attribs) {
             const auto& loc = progAttrib.mLoc;
             if (loc == -1)
                 continue;
 
             const auto& attribData = mWebGL->mBoundVertexArray->mAttribs[loc];
 
             GLenum attribDataBaseType;
             if (attribData.mEnabled) {
                 attribDataBaseType = attribData.BaseType();
+
+                if (attribData.mBuf->IsBoundForTF()) {
+                    mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u's buffer is bound"
+                                                  " or in use for transform feedback.",
+                                                  funcName, loc);
+                    *out_error = true;
+                    return;
+                }
             } else {
                 attribDataBaseType = mWebGL->mGenericVertexAttribTypes[loc];
             }
 
             if (attribDataBaseType != progAttrib.mBaseType) {
                 nsCString progType, dataType;
                 WebGLContext::EnumName(progAttrib.mBaseType, &progType);
                 WebGLContext::EnumName(attribDataBaseType, &dataType);
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -587,22 +587,29 @@ WebGLContext::GetAttribLocation(const We
         return -1;
 
     return prog.GetAttribLocation(name);
 }
 
 JS::Value
 WebGLContext::GetBufferParameter(GLenum target, GLenum pname)
 {
+    const char funcName[] = "getBufferParameter";
     if (IsContextLost())
         return JS::NullValue();
 
-    const auto& buffer = ValidateBufferSelection("getBufferParameter", target);
-    if (!buffer)
+    const auto& slot = ValidateBufferSlot(funcName, target);
+    if (!slot)
         return JS::NullValue();
+    const auto& buffer = *slot;
+
+    if (!buffer) {
+        ErrorInvalidOperation("%s: Buffer for `target` is null.", funcName);
+        return JS::NullValue();
+    }
 
     switch (pname) {
     case LOCAL_GL_BUFFER_SIZE:
         return JS::NumberValue(buffer->ByteLength());
 
     case LOCAL_GL_BUFFER_USAGE:
         return JS::NumberValue(buffer->Usage());
 
--- a/dom/canvas/WebGLContextVertexArray.cpp
+++ b/dom/canvas/WebGLContextVertexArray.cpp
@@ -20,23 +20,30 @@ WebGLContext::BindVertexArray(WebGLVerte
 
     if (array && !ValidateObject("bindVertexArrayObject", *array))
         return;
 
     InvalidateBufferFetching();
 
     MakeContextCurrent();
 
+    if (mBoundVertexArray) {
+        mBoundVertexArray->AddBufferBindCounts(-1);
+    }
+
     if (array == nullptr) {
         array = mDefaultVertexArray;
     }
 
     array->BindVertexArray();
 
     MOZ_ASSERT(mBoundVertexArray == array);
+    if (mBoundVertexArray) {
+        mBoundVertexArray->AddBufferBindCounts(+1);
+    }
 }
 
 already_AddRefed<WebGLVertexArray>
 WebGLContext::CreateVertexArray()
 {
     if (IsContextLost())
         return nullptr;
 
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -430,16 +430,17 @@ QueryProgramInfo(WebGLProgram* prog, gl:
 
     return info.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
     : prog(prog)
+    , transformFeedbackBufferMode(prog->mNextLink_TransformFeedbackBufferMode)
 { }
 
 webgl::LinkedProgramInfo::~LinkedProgramInfo()
 {
     for (auto& cur : uniforms) {
         delete cur;
     }
     for (auto& cur : uniformBlocks) {
@@ -692,44 +693,59 @@ JS::Value
 WebGLProgram::GetProgramParameter(GLenum pname) const
 {
     gl::GLContext* gl = mContext->gl;
     gl->MakeCurrent();
 
     if (mContext->IsWebGL2()) {
         switch (pname) {
         case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
-            return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
+            if (!IsLinked())
+                return JS::NumberValue(0);
+            return JS::NumberValue(LinkInfo()->uniformBlocks.size());
 
         case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
-            return JS::Int32Value(mNextLink_TransformFeedbackVaryings.size());
+            if (!IsLinked())
+                return JS::NumberValue(0);
+            return JS::NumberValue(LinkInfo()->transformFeedbackVaryings.size());
 
         case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
-            return JS::Int32Value(mNextLink_TransformFeedbackBufferMode);
+            if (!IsLinked())
+                return JS::NumberValue(LOCAL_GL_INTERLEAVED_ATTRIBS);
+            return JS::NumberValue(LinkInfo()->transformFeedbackBufferMode);
        }
     }
 
     switch (pname) {
     case LOCAL_GL_ATTACHED_SHADERS:
+        return JS::NumberValue( int(bool(mVertShader.get())) + int(bool(mFragShader)) );
+
     case LOCAL_GL_ACTIVE_UNIFORMS:
+        if (!IsLinked())
+            return JS::NumberValue(0);
+        return JS::NumberValue(LinkInfo()->uniforms.size());
+
     case LOCAL_GL_ACTIVE_ATTRIBUTES:
-        return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
+        if (!IsLinked())
+            return JS::NumberValue(0);
+        return JS::NumberValue(LinkInfo()->attribs.size());
 
     case LOCAL_GL_DELETE_STATUS:
         return JS::BooleanValue(IsDeleteRequested());
 
     case LOCAL_GL_LINK_STATUS:
         return JS::BooleanValue(IsLinked());
 
     case LOCAL_GL_VALIDATE_STATUS:
 #ifdef XP_MACOSX
         // See comment in ValidateProgram.
         if (gl->WorkAroundDriverBugs())
             return JS::BooleanValue(true);
 #endif
+        // Todo: Implement this in our code.
         return JS::BooleanValue(bool(GetProgramiv(gl, mGLName, pname)));
 
     default:
         mContext->ErrorInvalidEnumInfo("getProgramParameter: `pname`",
                                        pname);
         return JS::NullValue();
     }
 }
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -82,16 +82,17 @@ struct LinkedProgramInfo final
     friend class WebGLProgram;
 
     MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(LinkedProgramInfo)
 
     //////
 
     WebGLProgram* const prog;
+    const GLenum transformFeedbackBufferMode;
 
     std::vector<AttribInfo> attribs;
     std::vector<UniformInfo*> uniforms; // Owns its contents.
     std::vector<UniformBlockInfo*> uniformBlocks; // Owns its contents.
     std::vector<RefPtr<WebGLActiveInfo>> transformFeedbackVaryings;
 
     // Needed for draw call validation.
     std::vector<UniformInfo*> uniformSamplers;
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -12,17 +12,16 @@
 namespace mozilla {
 
 WebGLTransformFeedback::WebGLTransformFeedback(WebGLContext* webgl, GLuint tf)
     : WebGLRefCountedObject(webgl)
     , mGLName(tf)
     , mIndexedBindings(webgl->mGLMaxTransformFeedbackSeparateAttribs)
     , mIsPaused(false)
     , mIsActive(false)
-    , mBuffersForTF_Dirty(true)
 {
     mContext->mTransformFeedbacks.insertBack(this);
 }
 
 WebGLTransformFeedback::~WebGLTransformFeedback()
 {
     DeleteOnce();
 }
@@ -32,38 +31,16 @@ 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)
@@ -204,16 +181,28 @@ WebGLTransformFeedback::ResumeTransformF
     ////
 
     MOZ_ASSERT(mIsActive);
     mIsPaused = false;
 }
 
 ////////////////////////////////////////
 
+void
+WebGLTransformFeedback::AddBufferBindCounts(int8_t addVal) const
+{
+    const GLenum target = LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER;
+    WebGLBuffer::AddBindCount(target, mGenericBufferBinding.get(), addVal);
+    for (const auto& binding : mIndexedBindings) {
+        WebGLBuffer::AddBindCount(target, binding.mBufferBinding.get(), addVal);
+    }
+}
+
+////////////////////////////////////////
+
 JSObject*
 WebGLTransformFeedback::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLTransformFeedbackBinding::Wrap(cx, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTransformFeedback, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTransformFeedback, Release)
--- a/dom/canvas/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -12,16 +12,17 @@
 
 namespace mozilla {
 
 class WebGLTransformFeedback final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLTransformFeedback>
     , public LinkedListElement<WebGLTransformFeedback>
 {
+    friend class ScopedDrawHelper;
     friend class ScopedDrawWithTransformFeedback;
     friend class WebGLContext;
     friend class WebGL2Context;
     friend class WebGLProgram;
 
 public:
     const GLuint mGLName;
 private:
@@ -31,36 +32,32 @@ 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;
 
-    ////
+    bool IsActiveAndNotPaused() const { return mIsActive && !mIsPaused; }
 
-    void OnIndexedBindingsChanged() const { mBuffersForTF_Dirty = true; }
-    const decltype(mBuffersForTF)& BuffersForTF() const;
+    void AddBufferBindCounts(int8_t addVal) const;
 
     // GL Funcs
     void BeginTransformFeedback(GLenum primMode);
     void EndTransformFeedback();
     void PauseTransformFeedback();
     void ResumeTransformFeedback();
 };
 
--- a/dom/canvas/WebGLVertexArray.cpp
+++ b/dom/canvas/WebGLVertexArray.cpp
@@ -23,16 +23,31 @@ WebGLVertexArray::WrapObject(JSContext* 
 WebGLVertexArray::WebGLVertexArray(WebGLContext* webgl)
     : WebGLRefCountedObject(webgl)
     , mGLName(0)
 {
     mAttribs.SetLength(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);
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -5,17 +5,16 @@
 
 #ifndef WEBGL_VERTEX_ARRAY_H_
 #define WEBGL_VERTEX_ARRAY_H_
 
 #include "nsTArray.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 
-#include "WebGLBuffer.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLVertexAttribData.h"
 
 namespace mozilla {
 
 class WebGLVertexArrayFake;
 
@@ -43,22 +42,21 @@ public:
 
     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)
 
     GLuint GLName() const { return mGLName; }
 
+    void AddBufferBindCounts(int8_t addVal) const;
+
 protected:
     explicit WebGLVertexArray(WebGLContext* webgl);
-
-    virtual ~WebGLVertexArray() {
-        MOZ_ASSERT(IsDeleted());
-    }
+    virtual ~WebGLVertexArray();
 
     virtual void GenVertexArray() = 0;
     virtual void BindVertexArrayImpl() = 0;
     virtual void DeleteImpl() = 0;
     virtual bool IsVertexArrayImpl() const = 0;
 
     GLuint mGLName;
     nsTArray<WebGLVertexAttribData> mAttribs;
--- a/dom/canvas/WebGLVertexAttribData.cpp
+++ b/dom/canvas/WebGLVertexAttribData.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "WebGLVertexAttribData.h"
 
 #include "GLContext.h"
+#include "WebGLBuffer.h"
 
 namespace mozilla {
 
 static uint8_t
 CalcBytesPerVertex(GLenum type, uint8_t size)
 {
     uint8_t bytesPerType;
     switch (type) {
@@ -66,17 +67,17 @@ AttribPointerBaseType(bool integerFunc, 
 }
 
 void
 WebGLVertexAttribData::VertexAttribPointer(bool integerFunc, WebGLBuffer* buf,
                                            uint8_t size, GLenum type, bool normalized,
                                            uint32_t stride, uint64_t byteOffset)
 {
     mIntegerFunc = integerFunc;
-    mBuf = buf;
+    WebGLBuffer::SetSlot(0, buf, &mBuf);
     mType = type;
     mBaseType = AttribPointerBaseType(integerFunc, type);
     mSize = size;
     mBytesPerVertex = CalcBytesPerVertex(mType, mSize);
     mNormalized = normalized;
     mStride = stride;
     mExplicitStride = (mStride ? mStride : mBytesPerVertex);
     mByteOffset = byteOffset;