Edit

kc3-lang/angle/src/libANGLE/VertexArray.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2021-09-27 14:27:31
    Hash : 3d429f59
    Message : VertexArray: Don't syncState on Buffer map/unmap. Map/unmap calls are now treated like contents change events. Similar to BufferSubData calls. Bug: angleproject:6371 Change-Id: Ie0a4c521ffdedfa723d47eddd21c6f68dde044ac Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3187811 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Tim Van Patten <timvp@google.com>

  • src/libANGLE/VertexArray.cpp
  • //
    // Copyright 2013 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    // Implementation of the state class for mananging GLES 3 Vertex Array Objects.
    //
    
    #include "libANGLE/VertexArray.h"
    
    #include "common/utilities.h"
    #include "libANGLE/Buffer.h"
    #include "libANGLE/Context.h"
    #include "libANGLE/renderer/BufferImpl.h"
    #include "libANGLE/renderer/GLImplFactory.h"
    #include "libANGLE/renderer/VertexArrayImpl.h"
    
    namespace gl
    {
    namespace
    {
    bool IsElementArrayBufferSubjectIndex(angle::SubjectIndex subjectIndex)
    {
        return (subjectIndex == kElementArrayBufferIndex);
    }
    }  // namespace
    
    // VertexArrayState implementation.
    VertexArrayState::VertexArrayState(VertexArray *vertexArray,
                                       size_t maxAttribs,
                                       size_t maxAttribBindings)
        : mElementArrayBuffer(vertexArray, kElementArrayBufferIndex)
    {
        ASSERT(maxAttribs <= maxAttribBindings);
    
        for (size_t i = 0; i < maxAttribs; i++)
        {
            mVertexAttributes.emplace_back(static_cast<GLuint>(i));
            mVertexBindings.emplace_back(static_cast<GLuint>(i));
        }
    
        // Initially all attributes start as "client" with no buffer bound.
        mClientMemoryAttribsMask.set();
    }
    
    VertexArrayState::~VertexArrayState() {}
    
    bool VertexArrayState::hasEnabledNullPointerClientArray() const
    {
        return (mNullPointerClientMemoryAttribsMask & mEnabledAttributesMask).any();
    }
    
    AttributesMask VertexArrayState::getBindingToAttributesMask(GLuint bindingIndex) const
    {
        ASSERT(bindingIndex < MAX_VERTEX_ATTRIB_BINDINGS);
        return mVertexBindings[bindingIndex].getBoundAttributesMask();
    }
    
    // Set an attribute using a new binding.
    void VertexArrayState::setAttribBinding(const Context *context,
                                            size_t attribIndex,
                                            GLuint newBindingIndex)
    {
        ASSERT(attribIndex < MAX_VERTEX_ATTRIBS && newBindingIndex < MAX_VERTEX_ATTRIB_BINDINGS);
    
        VertexAttribute &attrib = mVertexAttributes[attribIndex];
    
        // Update the binding-attribute map.
        const GLuint oldBindingIndex = attrib.bindingIndex;
        ASSERT(oldBindingIndex != newBindingIndex);
    
        VertexBinding &oldBinding = mVertexBindings[oldBindingIndex];
        VertexBinding &newBinding = mVertexBindings[newBindingIndex];
    
        ASSERT(oldBinding.getBoundAttributesMask().test(attribIndex) &&
               !newBinding.getBoundAttributesMask().test(attribIndex));
    
        oldBinding.resetBoundAttribute(attribIndex);
        newBinding.setBoundAttribute(attribIndex);
    
        // Set the attribute using the new binding.
        attrib.bindingIndex = newBindingIndex;
    
        if (context->isBufferAccessValidationEnabled())
        {
            attrib.updateCachedElementLimit(newBinding);
        }
    
        bool isMapped = newBinding.getBuffer().get() && newBinding.getBuffer()->isMapped();
        mCachedMappedArrayBuffers.set(attribIndex, isMapped);
        mEnabledAttributesMask.set(attribIndex, attrib.enabled);
        updateCachedMutableOrNonPersistentArrayBuffers(attribIndex);
        mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mEnabledAttributesMask &
                                          mCachedMutableOrImpersistentArrayBuffers;
    }
    
    void VertexArrayState::updateCachedMutableOrNonPersistentArrayBuffers(size_t index)
    {
        const VertexBinding &vertexBinding   = mVertexBindings[index];
        const BindingPointer<Buffer> &buffer = vertexBinding.getBuffer();
        bool isMutableOrImpersistentArrayBuffer =
            buffer.get() &&
            (!buffer->isImmutable() || (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) == 0);
        mCachedMutableOrImpersistentArrayBuffers.set(index, isMutableOrImpersistentArrayBuffer);
    }
    
    // VertexArray implementation.
    VertexArray::VertexArray(rx::GLImplFactory *factory,
                             VertexArrayID id,
                             size_t maxAttribs,
                             size_t maxAttribBindings)
        : mId(id),
          mState(this, maxAttribs, maxAttribBindings),
          mVertexArray(factory->createVertexArray(mState)),
          mBufferAccessValidationEnabled(false),
          mContentsObservers(this)
    {
        for (size_t attribIndex = 0; attribIndex < maxAttribBindings; ++attribIndex)
        {
            mArrayBufferObserverBindings.emplace_back(this, attribIndex);
        }
    
        mVertexArray->setContentsObservers(&mContentsObservers);
    }
    
    void VertexArray::onDestroy(const Context *context)
    {
        bool isBound = context->isCurrentVertexArray(this);
        for (uint32_t bindingIndex = 0; bindingIndex < gl::MAX_VERTEX_ATTRIB_BINDINGS; ++bindingIndex)
        {
            VertexBinding &binding = mState.mVertexBindings[bindingIndex];
            Buffer *buffer         = binding.getBuffer().get();
            if (isBound)
            {
                if (buffer)
                {
                    buffer->onNonTFBindingChanged(-1);
                }
            }
            if (buffer)
            {
                // Note: the non-contents observer is unbound in the ObserverBinding destructor.
                buffer->removeContentsObserver(this, bindingIndex);
            }
            binding.setBuffer(context, nullptr);
        }
        if (isBound && mState.mElementArrayBuffer.get())
            mState.mElementArrayBuffer->onNonTFBindingChanged(-1);
        mState.mElementArrayBuffer.bind(context, nullptr);
        mVertexArray->destroy(context);
        SafeDelete(mVertexArray);
        delete this;
    }
    
    VertexArray::~VertexArray()
    {
        ASSERT(!mVertexArray);
    }
    
    void VertexArray::setLabel(const Context *context, const std::string &label)
    {
        mState.mLabel = label;
    }
    
    const std::string &VertexArray::getLabel() const
    {
        return mState.mLabel;
    }
    
    bool VertexArray::detachBuffer(const Context *context, BufferID bufferID)
    {
        bool isBound           = context->isCurrentVertexArray(this);
        bool anyBufferDetached = false;
        for (uint32_t bindingIndex = 0; bindingIndex < gl::MAX_VERTEX_ATTRIB_BINDINGS; ++bindingIndex)
        {
            VertexBinding &binding                      = mState.mVertexBindings[bindingIndex];
            const BindingPointer<Buffer> &bufferBinding = binding.getBuffer();
            if (bufferBinding.id() == bufferID)
            {
                if (isBound)
                {
                    if (bufferBinding.get())
                        bufferBinding->onNonTFBindingChanged(-1);
                }
                bufferBinding->removeContentsObserver(this, bindingIndex);
                binding.setBuffer(context, nullptr);
                mArrayBufferObserverBindings[bindingIndex].reset();
    
                if (context->getClientVersion() >= ES_3_1)
                {
                    setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER);
                }
                else
                {
                    static_assert(gl::MAX_VERTEX_ATTRIB_BINDINGS < 8 * sizeof(uint32_t),
                                  "Not enough bits in bindingIndex");
                    // The redundant uint32_t cast here is required to avoid a warning on MSVC.
                    ASSERT(binding.getBoundAttributesMask() ==
                           AttributesMask(static_cast<uint32_t>(1 << bindingIndex)));
                    setDirtyAttribBit(bindingIndex, DIRTY_ATTRIB_POINTER);
                }
    
                anyBufferDetached = true;
                mState.mClientMemoryAttribsMask |= binding.getBoundAttributesMask();
            }
        }
    
        if (mState.mElementArrayBuffer.get() && mState.mElementArrayBuffer->id() == bufferID)
        {
            if (isBound && mState.mElementArrayBuffer.get())
                mState.mElementArrayBuffer->onNonTFBindingChanged(-1);
            mState.mElementArrayBuffer.bind(context, nullptr);
            mDirtyBits.set(DIRTY_BIT_ELEMENT_ARRAY_BUFFER);
            anyBufferDetached = true;
        }
    
        return anyBufferDetached;
    }
    
    const VertexAttribute &VertexArray::getVertexAttribute(size_t attribIndex) const
    {
        ASSERT(attribIndex < getMaxAttribs());
        return mState.mVertexAttributes[attribIndex];
    }
    
    const VertexBinding &VertexArray::getVertexBinding(size_t bindingIndex) const
    {
        ASSERT(bindingIndex < getMaxBindings());
        return mState.mVertexBindings[bindingIndex];
    }
    
    size_t VertexArray::GetVertexIndexFromDirtyBit(size_t dirtyBit)
    {
        static_assert(gl::MAX_VERTEX_ATTRIBS == gl::MAX_VERTEX_ATTRIB_BINDINGS,
                      "The stride of vertex attributes should equal to that of vertex bindings.");
        ASSERT(dirtyBit > DIRTY_BIT_ELEMENT_ARRAY_BUFFER);
        return (dirtyBit - DIRTY_BIT_ATTRIB_0) % gl::MAX_VERTEX_ATTRIBS;
    }
    
    ANGLE_INLINE void VertexArray::setDirtyAttribBit(size_t attribIndex,
                                                     DirtyAttribBitType dirtyAttribBit)
    {
        mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex);
        mDirtyAttribBits[attribIndex].set(dirtyAttribBit);
    }
    
    ANGLE_INLINE void VertexArray::setDirtyBindingBit(size_t bindingIndex,
                                                      DirtyBindingBitType dirtyBindingBit)
    {
        mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex);
        mDirtyBindingBits[bindingIndex].set(dirtyBindingBit);
    }
    
    ANGLE_INLINE void VertexArray::updateCachedBufferBindingSize(VertexBinding *binding)
    {
        if (!mBufferAccessValidationEnabled)
            return;
    
        for (size_t boundAttribute : binding->getBoundAttributesMask())
        {
            mState.mVertexAttributes[boundAttribute].updateCachedElementLimit(*binding);
        }
    }
    
    ANGLE_INLINE void VertexArray::updateCachedArrayBuffersMasks(
        bool isMapped,
        bool isImmutable,
        bool isPersistent,
        const AttributesMask &boundAttributesMask)
    {
        if (isMapped)
        {
            mState.mCachedMappedArrayBuffers |= boundAttributesMask;
        }
        else
        {
            mState.mCachedMappedArrayBuffers &= ~boundAttributesMask;
        }
    
        if (!isImmutable || !isPersistent)
        {
            mState.mCachedMutableOrImpersistentArrayBuffers |= boundAttributesMask;
        }
        else
        {
            mState.mCachedMutableOrImpersistentArrayBuffers &= ~boundAttributesMask;
        }
    
        mState.mCachedInvalidMappedArrayBuffer = mState.mCachedMappedArrayBuffers &
                                                 mState.mEnabledAttributesMask &
                                                 mState.mCachedMutableOrImpersistentArrayBuffers;
    }
    
    ANGLE_INLINE void VertexArray::updateCachedMappedArrayBuffersBinding(const VertexBinding &binding)
    {
        const Buffer *buffer = binding.getBuffer().get();
        bool isMapped        = buffer && buffer->isMapped();
        bool isImmutable     = buffer && buffer->isImmutable();
        bool isPersistent    = buffer && (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0;
        return updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent,
                                             binding.getBoundAttributesMask());
    }
    
    ANGLE_INLINE void VertexArray::updateCachedTransformFeedbackBindingValidation(size_t bindingIndex,
                                                                                  const Buffer *buffer)
    {
        const bool hasConflict = buffer && buffer->hasWebGLXFBBindingConflict(true);
        mCachedTransformFeedbackConflictedBindingsMask.set(bindingIndex, hasConflict);
    }
    
    bool VertexArray::bindVertexBufferImpl(const Context *context,
                                           size_t bindingIndex,
                                           Buffer *boundBuffer,
                                           GLintptr offset,
                                           GLsizei stride)
    {
        ASSERT(bindingIndex < getMaxBindings());
        ASSERT(context->isCurrentVertexArray(this));
    
        VertexBinding *binding = &mState.mVertexBindings[bindingIndex];
    
        Buffer *oldBuffer = binding->getBuffer().get();
    
        const bool sameBuffer = oldBuffer == boundBuffer;
        const bool sameStride = static_cast<GLuint>(stride) == binding->getStride();
        const bool sameOffset = offset == binding->getOffset();
    
        if (sameBuffer && sameStride && sameOffset)
        {
            return false;
        }
    
        angle::ObserverBinding *observer = &mArrayBufferObserverBindings[bindingIndex];
        observer->assignSubject(boundBuffer);
    
        // Several nullptr checks are combined here for optimization purposes.
        if (oldBuffer)
        {
            oldBuffer->onNonTFBindingChanged(-1);
            oldBuffer->removeObserver(observer);
            oldBuffer->removeContentsObserver(this, static_cast<uint32_t>(bindingIndex));
            oldBuffer->release(context);
        }
    
        binding->assignBuffer(boundBuffer);
        binding->setOffset(offset);
        binding->setStride(stride);
        updateCachedBufferBindingSize(binding);
    
        // Update client memory attribute pointers. Affects all bound attributes.
        if (boundBuffer)
        {
            boundBuffer->addRef();
            boundBuffer->onNonTFBindingChanged(1);
            boundBuffer->addObserver(observer);
            if (context->isWebGL())
            {
                mCachedTransformFeedbackConflictedBindingsMask.set(
                    bindingIndex, boundBuffer->hasWebGLXFBBindingConflict(true));
            }
            mState.mClientMemoryAttribsMask &= ~binding->getBoundAttributesMask();
    
            bool isMapped     = boundBuffer->isMapped() == GL_TRUE;
            bool isImmutable  = boundBuffer->isImmutable() == GL_TRUE;
            bool isPersistent = (boundBuffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0;
            updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent,
                                          binding->getBoundAttributesMask());
        }
        else
        {
            if (context->isWebGL())
            {
                mCachedTransformFeedbackConflictedBindingsMask.set(bindingIndex, false);
            }
            mState.mClientMemoryAttribsMask |= binding->getBoundAttributesMask();
            updateCachedArrayBuffersMasks(false, false, false, binding->getBoundAttributesMask());
        }
    
        return true;
    }
    
    void VertexArray::bindVertexBuffer(const Context *context,
                                       size_t bindingIndex,
                                       Buffer *boundBuffer,
                                       GLintptr offset,
                                       GLsizei stride)
    {
        if (bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride))
        {
            setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER);
        }
    }
    
    void VertexArray::setVertexAttribBinding(const Context *context,
                                             size_t attribIndex,
                                             GLuint bindingIndex)
    {
        ASSERT(attribIndex < getMaxAttribs() && bindingIndex < getMaxBindings());
    
        if (mState.mVertexAttributes[attribIndex].bindingIndex == bindingIndex)
        {
            return;
        }
    
        // In ES 3.0 contexts, the binding cannot change, hence the code below is unreachable.
        ASSERT(context->getClientVersion() >= ES_3_1);
    
        mState.setAttribBinding(context, attribIndex, bindingIndex);
    
        setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_BINDING);
    
        // Update client attribs mask.
        bool hasBuffer = mState.mVertexBindings[bindingIndex].getBuffer().get() != nullptr;
        mState.mClientMemoryAttribsMask.set(attribIndex, !hasBuffer);
    }
    
    void VertexArray::setVertexBindingDivisor(const Context *context,
                                              size_t bindingIndex,
                                              GLuint divisor)
    {
        ASSERT(bindingIndex < getMaxBindings());
    
        VertexBinding &binding = mState.mVertexBindings[bindingIndex];
    
        if (binding.getDivisor() == divisor)
        {
            return;
        }
    
        binding.setDivisor(divisor);
        setDirtyBindingBit(bindingIndex, DIRTY_BINDING_DIVISOR);
    
        // Trigger updates in all bound attributes.
        if (context->isBufferAccessValidationEnabled())
        {
            for (size_t attribIndex : binding.getBoundAttributesMask())
            {
                mState.mVertexAttributes[attribIndex].updateCachedElementLimit(binding);
            }
        }
    }
    
    ANGLE_INLINE bool VertexArray::setVertexAttribFormatImpl(VertexAttribute *attrib,
                                                             GLint size,
                                                             VertexAttribType type,
                                                             bool normalized,
                                                             bool pureInteger,
                                                             GLuint relativeOffset)
    {
        angle::FormatID formatID = gl::GetVertexFormatID(type, normalized, size, pureInteger);
    
        if (formatID != attrib->format->id || attrib->relativeOffset != relativeOffset)
        {
            attrib->relativeOffset = relativeOffset;
            attrib->format         = &angle::Format::Get(formatID);
            return true;
        }
    
        return false;
    }
    
    void VertexArray::setVertexAttribFormat(size_t attribIndex,
                                            GLint size,
                                            VertexAttribType type,
                                            bool normalized,
                                            bool pureInteger,
                                            GLuint relativeOffset)
    {
        VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
    
        ComponentType componentType = GetVertexAttributeComponentType(pureInteger, type);
        SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask);
    
        if (setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, relativeOffset))
        {
            setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_FORMAT);
        }
    
        attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex]);
    }
    
    void VertexArray::setVertexAttribDivisor(const Context *context, size_t attribIndex, GLuint divisor)
    {
        ASSERT(attribIndex < getMaxAttribs());
    
        setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex));
        setVertexBindingDivisor(context, attribIndex, divisor);
    }
    
    void VertexArray::enableAttribute(size_t attribIndex, bool enabledState)
    {
        ASSERT(attribIndex < getMaxAttribs());
    
        VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
    
        if (mState.mEnabledAttributesMask.test(attribIndex) == enabledState)
        {
            return;
        }
    
        attrib.enabled = enabledState;
    
        setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_ENABLED);
    
        // Update state cache
        mState.mEnabledAttributesMask.set(attribIndex, enabledState);
        mState.updateCachedMutableOrNonPersistentArrayBuffers(attribIndex);
        mState.mCachedInvalidMappedArrayBuffer = mState.mCachedMappedArrayBuffers &
                                                 mState.mEnabledAttributesMask &
                                                 mState.mCachedMutableOrImpersistentArrayBuffers;
    }
    
    ANGLE_INLINE void VertexArray::setVertexAttribPointerImpl(const Context *context,
                                                              ComponentType componentType,
                                                              bool pureInteger,
                                                              size_t attribIndex,
                                                              Buffer *boundBuffer,
                                                              GLint size,
                                                              VertexAttribType type,
                                                              bool normalized,
                                                              GLsizei stride,
                                                              const void *pointer)
    {
        ASSERT(attribIndex < getMaxAttribs());
    
        VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
    
        SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask);
    
        bool attribDirty = setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, 0);
    
        if (attrib.bindingIndex != attribIndex)
        {
            setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex));
        }
    
        GLsizei effectiveStride =
            stride == 0 ? static_cast<GLsizei>(ComputeVertexAttributeTypeSize(attrib)) : stride;
    
        if (attrib.vertexAttribArrayStride != static_cast<GLuint>(stride))
        {
            attribDirty = true;
        }
        attrib.vertexAttribArrayStride = stride;
    
        // If we switch from an array buffer to a client pointer(or vice-versa), we set the whole
        // attribute dirty. This notifies the Vulkan back-end to update all its caches.
        const VertexBinding &binding = mState.mVertexBindings[attribIndex];
        if ((boundBuffer == nullptr) != (binding.getBuffer().get() == nullptr))
        {
            attribDirty = true;
        }
    
        // Change of attrib.pointer is not part of attribDirty. Pointer is actually the buffer offset
        // which is handled within bindVertexBufferImpl and reflected in bufferDirty.
        attrib.pointer  = pointer;
        GLintptr offset = boundBuffer ? reinterpret_cast<GLintptr>(pointer) : 0;
        const bool bufferDirty =
            bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride);
    
        if (attribDirty)
        {
            setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER);
        }
        else if (bufferDirty)
        {
            setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER_BUFFER);
        }
    
        mState.mNullPointerClientMemoryAttribsMask.set(attribIndex,
                                                       boundBuffer == nullptr && pointer == nullptr);
    }
    
    void VertexArray::setVertexAttribPointer(const Context *context,
                                             size_t attribIndex,
                                             gl::Buffer *boundBuffer,
                                             GLint size,
                                             VertexAttribType type,
                                             bool normalized,
                                             GLsizei stride,
                                             const void *pointer)
    {
        setVertexAttribPointerImpl(context, ComponentType::Float, false, attribIndex, boundBuffer, size,
                                   type, normalized, stride, pointer);
    }
    
    void VertexArray::setVertexAttribIPointer(const Context *context,
                                              size_t attribIndex,
                                              gl::Buffer *boundBuffer,
                                              GLint size,
                                              VertexAttribType type,
                                              GLsizei stride,
                                              const void *pointer)
    {
        ComponentType componentType = GetVertexAttributeComponentType(true, type);
        setVertexAttribPointerImpl(context, componentType, true, attribIndex, boundBuffer, size, type,
                                   false, stride, pointer);
    }
    
    angle::Result VertexArray::syncState(const Context *context)
    {
        if (mDirtyBits.any())
        {
            mDirtyBitsGuard = mDirtyBits;
            ANGLE_TRY(
                mVertexArray->syncState(context, mDirtyBits, &mDirtyAttribBits, &mDirtyBindingBits));
            mDirtyBits.reset();
            mDirtyBitsGuard.reset();
    
            // The dirty bits should be reset in the back-end. To simplify ASSERTs only check attrib 0.
            ASSERT(mDirtyAttribBits[0].none());
            ASSERT(mDirtyBindingBits[0].none());
        }
        return angle::Result::Continue;
    }
    
    void VertexArray::onBindingChanged(const Context *context, int incr)
    {
        if (mState.mElementArrayBuffer.get())
            mState.mElementArrayBuffer->onNonTFBindingChanged(incr);
        for (auto &binding : mState.mVertexBindings)
        {
            binding.onContainerBindingChanged(context, incr);
        }
    }
    
    VertexArray::DirtyBitType VertexArray::getDirtyBitFromIndex(bool contentsChanged,
                                                                angle::SubjectIndex index) const
    {
        if (IsElementArrayBufferSubjectIndex(index))
        {
            mIndexRangeCache.invalidate();
            return contentsChanged ? DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA
                                   : DIRTY_BIT_ELEMENT_ARRAY_BUFFER;
        }
        else
        {
            // Note: this currently just gets the top-level dirty bit.
            ASSERT(index < mArrayBufferObserverBindings.size());
            return static_cast<DirtyBitType>(
                (contentsChanged ? DIRTY_BIT_BUFFER_DATA_0 : DIRTY_BIT_BINDING_0) + index);
        }
    }
    
    void VertexArray::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
    {
        switch (message)
        {
            case angle::SubjectMessage::ContentsChanged:
                ASSERT(IsElementArrayBufferSubjectIndex(index));
                setDependentDirtyBit(true, index);
                break;
    
            case angle::SubjectMessage::SubjectChanged:
                if (!IsElementArrayBufferSubjectIndex(index))
                {
                    updateCachedBufferBindingSize(&mState.mVertexBindings[index]);
                }
                setDependentDirtyBit(false, index);
                break;
    
            case angle::SubjectMessage::BindingChanged:
                if (!IsElementArrayBufferSubjectIndex(index))
                {
                    const Buffer *buffer = mState.mVertexBindings[index].getBuffer().get();
                    updateCachedTransformFeedbackBindingValidation(index, buffer);
                }
                break;
    
            case angle::SubjectMessage::SubjectMapped:
                if (!IsElementArrayBufferSubjectIndex(index))
                {
                    updateCachedMappedArrayBuffersBinding(mState.mVertexBindings[index]);
                }
                onStateChange(angle::SubjectMessage::SubjectMapped);
                break;
    
            case angle::SubjectMessage::SubjectUnmapped:
                if (!IsElementArrayBufferSubjectIndex(index))
                {
                    updateCachedMappedArrayBuffersBinding(mState.mVertexBindings[index]);
                }
                onStateChange(angle::SubjectMessage::SubjectUnmapped);
                break;
    
            case angle::SubjectMessage::InternalMemoryAllocationChanged:
                setDependentDirtyBit(false, index);
                break;
    
            default:
                UNREACHABLE();
                break;
        }
    }
    
    void VertexArray::setDependentDirtyBit(bool contentsChanged, angle::SubjectIndex index)
    {
        DirtyBitType dirtyBit = getDirtyBitFromIndex(contentsChanged, index);
        ASSERT(!mDirtyBitsGuard.valid() || mDirtyBitsGuard.value().test(dirtyBit));
        mDirtyBits.set(dirtyBit);
        onStateChange(angle::SubjectMessage::ContentsChanged);
    }
    
    bool VertexArray::hasTransformFeedbackBindingConflict(const gl::Context *context) const
    {
        // Fast check first.
        if (!mCachedTransformFeedbackConflictedBindingsMask.any())
        {
            return false;
        }
    
        const AttributesMask &activeAttribues = context->getStateCache().getActiveBufferedAttribsMask();
    
        // Slow check. We must ensure that the conflicting attributes are enabled/active.
        for (size_t attribIndex : activeAttribues)
        {
            const VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
            if (mCachedTransformFeedbackConflictedBindingsMask[attrib.bindingIndex])
            {
                return true;
            }
        }
    
        return false;
    }
    
    angle::Result VertexArray::getIndexRangeImpl(const Context *context,
                                                 DrawElementsType type,
                                                 GLsizei indexCount,
                                                 const void *indices,
                                                 IndexRange *indexRangeOut) const
    {
        Buffer *elementArrayBuffer = mState.mElementArrayBuffer.get();
        if (!elementArrayBuffer)
        {
            *indexRangeOut = ComputeIndexRange(type, indices, indexCount,
                                               context->getState().isPrimitiveRestartEnabled());
            return angle::Result::Continue;
        }
    
        size_t offset = reinterpret_cast<uintptr_t>(indices);
        ANGLE_TRY(elementArrayBuffer->getIndexRange(context, type, offset, indexCount,
                                                    context->getState().isPrimitiveRestartEnabled(),
                                                    indexRangeOut));
    
        mIndexRangeCache.put(type, indexCount, offset, *indexRangeOut);
        return angle::Result::Continue;
    }
    
    VertexArray::IndexRangeCache::IndexRangeCache() = default;
    
    void VertexArray::IndexRangeCache::put(DrawElementsType type,
                                           GLsizei indexCount,
                                           size_t offset,
                                           const IndexRange &indexRange)
    {
        ASSERT(type != DrawElementsType::InvalidEnum);
    
        mTypeKey       = type;
        mIndexCountKey = indexCount;
        mOffsetKey     = offset;
        mPayload       = indexRange;
    }
    
    void VertexArray::onBufferContentsChange(uint32_t bufferIndex)
    {
        setDependentDirtyBit(true, bufferIndex);
    }
    
    VertexArrayBufferContentsObservers::VertexArrayBufferContentsObservers(VertexArray *vertexArray)
        : mVertexArray(vertexArray)
    {}
    
    void VertexArrayBufferContentsObservers::enableForBuffer(Buffer *buffer, uint32_t bufferIndex)
    {
        buffer->addContentsObserver(mVertexArray, bufferIndex);
    }
    
    void VertexArrayBufferContentsObservers::disableForBuffer(Buffer *buffer, uint32_t bufferIndex)
    {
        buffer->removeContentsObserver(mVertexArray, bufferIndex);
    }
    }  // namespace gl