Edit

kc3-lang/angle/src/libANGLE/renderer/gl/VertexArrayGL.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2019-07-04 08:51:34
    Hash : ee21a187
    Message : Store angle::Format in gl::VertexAttribute. Instead of storing type/size/normalized/pureInteger we instead store a pointer to the angle::Format. This makes some code logic simpler and will let us more easily check if a vertex attribute format changes in calls to VertexAttribPointer or VertexAttribFormat. This CL adds extra information to angle::Format to represent the vertex format info needed. It also caches the channel count so that it can be queried faster. Also renames "Int" -> "Sint" in UtilsVk for consistency. Bug: angleproject:3256 Change-Id: I5ef9b983dad8a58c341113c802500b89ce081566 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1684293 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>

  • src/libANGLE/renderer/gl/VertexArrayGL.cpp
  • //
    // Copyright 2015 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.
    //
    
    // VertexArrayGL.cpp: Implements the class methods for VertexArrayGL.
    
    #include "libANGLE/renderer/gl/VertexArrayGL.h"
    
    #include "common/bitset_utils.h"
    #include "common/debug.h"
    #include "common/mathutil.h"
    #include "common/utilities.h"
    #include "libANGLE/Buffer.h"
    #include "libANGLE/Context.h"
    #include "libANGLE/angletypes.h"
    #include "libANGLE/formatutils.h"
    #include "libANGLE/renderer/gl/BufferGL.h"
    #include "libANGLE/renderer/gl/ContextGL.h"
    #include "libANGLE/renderer/gl/FunctionsGL.h"
    #include "libANGLE/renderer/gl/StateManagerGL.h"
    #include "libANGLE/renderer/gl/renderergl_utils.h"
    
    using namespace gl;
    
    namespace rx
    {
    namespace
    {
    bool SameVertexAttribFormat(const VertexAttribute &a, const VertexAttribute &b)
    {
        return a.format == b.format && a.relativeOffset == b.relativeOffset;
    }
    
    bool SameVertexBuffer(const VertexBinding &a, const VertexBinding &b)
    {
        return a.getStride() == b.getStride() && a.getOffset() == b.getOffset() &&
               a.getBuffer().get() == b.getBuffer().get();
    }
    
    bool IsVertexAttribPointerSupported(size_t attribIndex, const VertexAttribute &attrib)
    {
        return (attribIndex == attrib.bindingIndex && attrib.relativeOffset == 0);
    }
    
    GLuint GetAdjustedDivisor(GLuint numViews, GLuint divisor)
    {
        return numViews * divisor;
    }
    
    }  // anonymous namespace
    
    VertexArrayGL::VertexArrayGL(const VertexArrayState &state,
                                 const FunctionsGL *functions,
                                 StateManagerGL *stateManager)
        : VertexArrayImpl(state),
          mFunctions(functions),
          mStateManager(stateManager),
          mVertexArrayID(0),
          mAppliedNumViews(1),
          mAppliedElementArrayBuffer(),
          mAppliedBindings(state.getMaxBindings()),
          mStreamingElementArrayBufferSize(0),
          mStreamingElementArrayBuffer(0),
          mStreamingArrayBufferSize(0),
          mStreamingArrayBuffer(0)
    {
        ASSERT(mFunctions);
        ASSERT(mStateManager);
        mFunctions->genVertexArrays(1, &mVertexArrayID);
    
        // Set the cached vertex attribute array and vertex attribute binding array size
        GLuint maxVertexAttribs = static_cast<GLuint>(state.getMaxAttribs());
        for (GLuint i = 0; i < maxVertexAttribs; i++)
        {
            mAppliedAttributes.emplace_back(i);
        }
    }
    
    VertexArrayGL::~VertexArrayGL() {}
    
    void VertexArrayGL::destroy(const gl::Context *context)
    {
        mStateManager->deleteVertexArray(mVertexArrayID);
        mVertexArrayID   = 0;
        mAppliedNumViews = 1;
    
        mStateManager->deleteBuffer(mStreamingElementArrayBuffer);
        mStreamingElementArrayBufferSize = 0;
        mStreamingElementArrayBuffer     = 0;
    
        mStateManager->deleteBuffer(mStreamingArrayBuffer);
        mStreamingArrayBufferSize = 0;
        mStreamingArrayBuffer     = 0;
    
        mAppliedElementArrayBuffer.set(context, nullptr);
        for (auto &binding : mAppliedBindings)
        {
            binding.setBuffer(context, nullptr);
        }
    }
    
    angle::Result VertexArrayGL::syncClientSideData(const gl::Context *context,
                                                    const gl::AttributesMask &activeAttributesMask,
                                                    GLint first,
                                                    GLsizei count,
                                                    GLsizei instanceCount) const
    {
        return syncDrawState(context, activeAttributesMask, first, count,
                             gl::DrawElementsType::InvalidEnum, nullptr, instanceCount, false, nullptr);
    }
    
    void VertexArrayGL::updateElementArrayBufferBinding(const gl::Context *context) const
    {
        gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer();
        if (elementArrayBuffer != nullptr && elementArrayBuffer != mAppliedElementArrayBuffer.get())
        {
            const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer);
            mStateManager->bindBuffer(gl::BufferBinding::ElementArray, bufferGL->getBufferID());
            mAppliedElementArrayBuffer.set(context, elementArrayBuffer);
        }
    }
    
    angle::Result VertexArrayGL::syncDrawState(const gl::Context *context,
                                               const gl::AttributesMask &activeAttributesMask,
                                               GLint first,
                                               GLsizei count,
                                               gl::DrawElementsType type,
                                               const void *indices,
                                               GLsizei instanceCount,
                                               bool primitiveRestartEnabled,
                                               const void **outIndices) const
    {
        // Check if any attributes need to be streamed, determines if the index range needs to be
        // computed
        const gl::AttributesMask &needsStreamingAttribs =
            context->getStateCache().getActiveClientAttribsMask();
    
        // Determine if an index buffer needs to be streamed and the range of vertices that need to be
        // copied
        IndexRange indexRange;
        if (type != gl::DrawElementsType::InvalidEnum)
        {
            ANGLE_TRY(syncIndexData(context, count, type, indices, primitiveRestartEnabled,
                                    needsStreamingAttribs.any(), &indexRange, outIndices));
        }
        else
        {
            // Not an indexed call, set the range to [first, first + count - 1]
            indexRange.start = first;
            indexRange.end   = first + count - 1;
        }
    
        if (needsStreamingAttribs.any())
        {
            ANGLE_TRY(streamAttributes(context, needsStreamingAttribs, instanceCount, indexRange));
        }
    
        return angle::Result::Continue;
    }
    
    angle::Result VertexArrayGL::syncIndexData(const gl::Context *context,
                                               GLsizei count,
                                               gl::DrawElementsType type,
                                               const void *indices,
                                               bool primitiveRestartEnabled,
                                               bool attributesNeedStreaming,
                                               IndexRange *outIndexRange,
                                               const void **outIndices) const
    {
        ASSERT(outIndices);
    
        gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer();
    
        // Need to check the range of indices if attributes need to be streamed
        if (elementArrayBuffer != nullptr)
        {
            ASSERT(elementArrayBuffer == mAppliedElementArrayBuffer.get());
            // Only compute the index range if the attributes also need to be streamed
            if (attributesNeedStreaming)
            {
                ptrdiff_t elementArrayBufferOffset = reinterpret_cast<ptrdiff_t>(indices);
                ANGLE_TRY(mState.getElementArrayBuffer()->getIndexRange(
                    context, type, elementArrayBufferOffset, count, primitiveRestartEnabled,
                    outIndexRange));
            }
    
            // Indices serves as an offset into the index buffer in this case, use the same value for
            // the draw call
            *outIndices = indices;
        }
        else
        {
            // Need to stream the index buffer
            // TODO: if GLES, nothing needs to be streamed
    
            // Only compute the index range if the attributes also need to be streamed
            if (attributesNeedStreaming)
            {
                *outIndexRange = ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
            }
    
            // Allocate the streaming element array buffer
            if (mStreamingElementArrayBuffer == 0)
            {
                mFunctions->genBuffers(1, &mStreamingElementArrayBuffer);
                mStreamingElementArrayBufferSize = 0;
            }
    
            mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
    
            mStateManager->bindBuffer(gl::BufferBinding::ElementArray, mStreamingElementArrayBuffer);
            mAppliedElementArrayBuffer.set(context, nullptr);
    
            // Make sure the element array buffer is large enough
            const GLuint indexTypeBytes        = gl::GetDrawElementsTypeSize(type);
            size_t requiredStreamingBufferSize = indexTypeBytes * count;
            if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize)
            {
                // Copy the indices in while resizing the buffer
                mFunctions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize, indices,
                                       GL_DYNAMIC_DRAW);
                mStreamingElementArrayBufferSize = requiredStreamingBufferSize;
            }
            else
            {
                // Put the indices at the beginning of the buffer
                mFunctions->bufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredStreamingBufferSize,
                                          indices);
            }
    
            // Set the index offset for the draw call to zero since the supplied index pointer is to
            // client data
            *outIndices = nullptr;
        }
    
        return angle::Result::Continue;
    }
    
    void VertexArrayGL::computeStreamingAttributeSizes(const gl::AttributesMask &attribsToStream,
                                                       GLsizei instanceCount,
                                                       const gl::IndexRange &indexRange,
                                                       size_t *outStreamingDataSize,
                                                       size_t *outMaxAttributeDataSize) const
    {
        *outStreamingDataSize    = 0;
        *outMaxAttributeDataSize = 0;
    
        ASSERT(attribsToStream.any());
    
        const auto &attribs  = mState.getVertexAttributes();
        const auto &bindings = mState.getVertexBindings();
    
        for (auto idx : attribsToStream)
        {
            const auto &attrib  = attribs[idx];
            const auto &binding = bindings[attrib.bindingIndex];
    
            // If streaming is going to be required, compute the size of the required buffer
            // and how much slack space at the beginning of the buffer will be required by determining
            // the attribute with the largest data size.
            size_t typeSize        = ComputeVertexAttributeTypeSize(attrib);
            GLuint adjustedDivisor = GetAdjustedDivisor(mAppliedNumViews, binding.getDivisor());
            *outStreamingDataSize +=
                typeSize * ComputeVertexBindingElementCount(adjustedDivisor, indexRange.vertexCount(),
                                                            instanceCount);
            *outMaxAttributeDataSize = std::max(*outMaxAttributeDataSize, typeSize);
        }
    }
    
    angle::Result VertexArrayGL::streamAttributes(const gl::Context *context,
                                                  const gl::AttributesMask &attribsToStream,
                                                  GLsizei instanceCount,
                                                  const gl::IndexRange &indexRange) const
    {
        // Sync the vertex attribute state and track what data needs to be streamed
        size_t streamingDataSize    = 0;
        size_t maxAttributeDataSize = 0;
    
        computeStreamingAttributeSizes(attribsToStream, instanceCount, indexRange, &streamingDataSize,
                                       &maxAttributeDataSize);
    
        if (streamingDataSize == 0)
        {
            return angle::Result::Continue;
        }
    
        if (mStreamingArrayBuffer == 0)
        {
            mFunctions->genBuffers(1, &mStreamingArrayBuffer);
            mStreamingArrayBufferSize = 0;
        }
    
        // If first is greater than zero, a slack space needs to be left at the beginning of the buffer
        // so that the same 'first' argument can be passed into the draw call.
        const size_t bufferEmptySpace   = maxAttributeDataSize * indexRange.start;
        const size_t requiredBufferSize = streamingDataSize + bufferEmptySpace;
    
        mStateManager->bindBuffer(gl::BufferBinding::Array, mStreamingArrayBuffer);
        if (requiredBufferSize > mStreamingArrayBufferSize)
        {
            mFunctions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW);
            mStreamingArrayBufferSize = requiredBufferSize;
        }
    
        mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
    
        // Unmapping a buffer can return GL_FALSE to indicate that the system has corrupted the data
        // somehow (such as by a screen change), retry writing the data a few times and return
        // OUT_OF_MEMORY if that fails.
        GLboolean unmapResult     = GL_FALSE;
        size_t unmapRetryAttempts = 5;
        while (unmapResult != GL_TRUE && --unmapRetryAttempts > 0)
        {
            uint8_t *bufferPointer = MapBufferRangeWithFallback(mFunctions, GL_ARRAY_BUFFER, 0,
                                                                requiredBufferSize, GL_MAP_WRITE_BIT);
            size_t curBufferOffset = bufferEmptySpace;
    
            const auto &attribs  = mState.getVertexAttributes();
            const auto &bindings = mState.getVertexBindings();
    
            for (auto idx : attribsToStream)
            {
                const auto &attrib = attribs[idx];
                ASSERT(IsVertexAttribPointerSupported(idx, attrib));
    
                const auto &binding = bindings[attrib.bindingIndex];
    
                GLuint adjustedDivisor = GetAdjustedDivisor(mAppliedNumViews, binding.getDivisor());
                const size_t streamedVertexCount = ComputeVertexBindingElementCount(
                    adjustedDivisor, indexRange.vertexCount(), instanceCount);
    
                const size_t sourceStride = ComputeVertexAttributeStride(attrib, binding);
                const size_t destStride   = ComputeVertexAttributeTypeSize(attrib);
    
                // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing
                // a non-instanced draw call
                const size_t firstIndex = adjustedDivisor == 0 ? indexRange.start : 0;
    
                // Attributes using client memory ignore the VERTEX_ATTRIB_BINDING state.
                // https://www.opengl.org/registry/specs/ARB/vertex_attrib_binding.txt
                const uint8_t *inputPointer = static_cast<const uint8_t *>(attrib.pointer);
    
                // Pack the data when copying it, user could have supplied a very large stride that
                // would cause the buffer to be much larger than needed.
                if (destStride == sourceStride)
                {
                    // Can copy in one go, the data is packed
                    memcpy(bufferPointer + curBufferOffset, inputPointer + (sourceStride * firstIndex),
                           destStride * streamedVertexCount);
                }
                else
                {
                    // Copy each vertex individually
                    for (size_t vertexIdx = 0; vertexIdx < streamedVertexCount; vertexIdx++)
                    {
                        uint8_t *out      = bufferPointer + curBufferOffset + (destStride * vertexIdx);
                        const uint8_t *in = inputPointer + sourceStride * (vertexIdx + firstIndex);
                        memcpy(out, in, destStride);
                    }
                }
    
                // Compute where the 0-index vertex would be.
                const size_t vertexStartOffset = curBufferOffset - (firstIndex * destStride);
    
                callVertexAttribPointer(static_cast<GLuint>(idx), attrib,
                                        static_cast<GLsizei>(destStride),
                                        static_cast<GLintptr>(vertexStartOffset));
    
                curBufferOffset += destStride * streamedVertexCount;
            }
    
            unmapResult = mFunctions->unmapBuffer(GL_ARRAY_BUFFER);
        }
    
        ANGLE_CHECK(GetImplAs<ContextGL>(context), unmapResult == GL_TRUE,
                    "Failed to unmap the client data streaming buffer.", GL_OUT_OF_MEMORY);
        return angle::Result::Continue;
    }
    
    GLuint VertexArrayGL::getVertexArrayID() const
    {
        return mVertexArrayID;
    }
    
    GLuint VertexArrayGL::getAppliedElementArrayBufferID() const
    {
        if (mAppliedElementArrayBuffer.get() == nullptr)
        {
            return mStreamingElementArrayBuffer;
        }
    
        return GetImplAs<BufferGL>(mAppliedElementArrayBuffer.get())->getBufferID();
    }
    
    void VertexArrayGL::updateAttribEnabled(size_t attribIndex)
    {
        const bool enabled = mState.getVertexAttribute(attribIndex).enabled &
                             mProgramActiveAttribLocationsMask.test(attribIndex);
        if (mAppliedAttributes[attribIndex].enabled == enabled)
        {
            return;
        }
    
        if (enabled)
        {
            mFunctions->enableVertexAttribArray(static_cast<GLuint>(attribIndex));
        }
        else
        {
            mFunctions->disableVertexAttribArray(static_cast<GLuint>(attribIndex));
        }
    
        mAppliedAttributes[attribIndex].enabled = enabled;
    }
    
    void VertexArrayGL::updateAttribPointer(const gl::Context *context, size_t attribIndex)
    {
        const VertexAttribute &attrib = mState.getVertexAttribute(attribIndex);
    
        // According to SPEC, VertexAttribPointer should update the binding indexed attribIndex instead
        // of the binding indexed attrib.bindingIndex (unless attribIndex == attrib.bindingIndex).
        const VertexBinding &binding = mState.getVertexBinding(attribIndex);
    
        // Early return when the vertex attribute isn't using a buffer object:
        // - If we need to stream, defer the attribPointer to the draw call.
        // - Skip the attribute that is disabled and uses a client memory pointer.
        // - Skip the attribute whose buffer is detached by BindVertexBuffer. Since it cannot have a
        //   client memory pointer either, it must be disabled and shouldn't affect the draw.
        const auto &bindingBuffer = binding.getBuffer();
        const Buffer *arrayBuffer = bindingBuffer.get();
        if (arrayBuffer == nullptr)
        {
            // Mark the applied binding isn't using a buffer by setting its buffer to nullptr so that if
            // it starts to use a buffer later, there is no chance that the caching will skip it.
            mAppliedBindings[attribIndex].setBuffer(context, nullptr);
            return;
        }
    
        // We do not need to compare attrib.pointer because when we use a different client memory
        // pointer, we don't need to update mAttributesNeedStreaming by binding.buffer and we won't
        // update attribPointer in this function.
        if ((SameVertexAttribFormat(mAppliedAttributes[attribIndex], attrib)) &&
            (mAppliedAttributes[attribIndex].bindingIndex == attrib.bindingIndex) &&
            (SameVertexBuffer(mAppliedBindings[attribIndex], binding)))
        {
            return;
        }
    
        // Since ANGLE always uses a non-zero VAO, we cannot use a client memory pointer on it:
        // [OpenGL ES 3.0.2] Section 2.8 page 24:
        // An INVALID_OPERATION error is generated when a non-zero vertex array object is bound,
        // zero is bound to the ARRAY_BUFFER buffer object binding point, and the pointer argument
        // is not NULL.
    
        const BufferGL *arrayBufferGL = GetImplAs<BufferGL>(arrayBuffer);
        mStateManager->bindBuffer(gl::BufferBinding::Array, arrayBufferGL->getBufferID());
        callVertexAttribPointer(static_cast<GLuint>(attribIndex), attrib, binding.getStride(),
                                binding.getOffset());
    
        mAppliedAttributes[attribIndex].format = attrib.format;
    
        // After VertexAttribPointer, attrib.relativeOffset is set to 0 and attrib.bindingIndex is set
        // to attribIndex in driver. If attrib.relativeOffset != 0 or attrib.bindingIndex !=
        // attribIndex, they should be set in updateAttribFormat and updateAttribBinding. The cache
        // should be consistent with driver so that we won't miss anything.
        mAppliedAttributes[attribIndex].relativeOffset = 0;
        mAppliedAttributes[attribIndex].bindingIndex   = static_cast<GLuint>(attribIndex);
    
        mAppliedBindings[attribIndex].setStride(binding.getStride());
        mAppliedBindings[attribIndex].setOffset(binding.getOffset());
        mAppliedBindings[attribIndex].setBuffer(context, binding.getBuffer().get());
    }
    
    void VertexArrayGL::callVertexAttribPointer(GLuint attribIndex,
                                                const VertexAttribute &attrib,
                                                GLsizei stride,
                                                GLintptr offset) const
    {
        const GLvoid *pointer       = reinterpret_cast<const GLvoid *>(offset);
        const angle::Format &format = *attrib.format;
        if (format.isPureInt())
        {
            ASSERT(!format.isNorm());
            mFunctions->vertexAttribIPointer(attribIndex, format.channelCount,
                                             gl::ToGLenum(format.vertexAttribType), stride, pointer);
        }
        else
        {
            mFunctions->vertexAttribPointer(attribIndex, format.channelCount,
                                            gl::ToGLenum(format.vertexAttribType), format.isNorm(),
                                            stride, pointer);
        }
    }
    
    bool VertexArrayGL::supportVertexAttribBinding() const
    {
        ASSERT(mFunctions);
        return (mFunctions->vertexAttribBinding != nullptr);
    }
    
    void VertexArrayGL::updateAttribFormat(size_t attribIndex)
    {
        ASSERT(supportVertexAttribBinding());
    
        const VertexAttribute &attrib = mState.getVertexAttribute(attribIndex);
        if (SameVertexAttribFormat(mAppliedAttributes[attribIndex], attrib))
        {
            return;
        }
    
        const angle::Format &format = *attrib.format;
        if (format.isPureInt())
        {
            ASSERT(!format.isNorm());
            mFunctions->vertexAttribIFormat(static_cast<GLuint>(attribIndex), format.channelCount,
                                            gl::ToGLenum(format.vertexAttribType),
                                            attrib.relativeOffset);
        }
        else
        {
            mFunctions->vertexAttribFormat(static_cast<GLuint>(attribIndex), format.channelCount,
                                           gl::ToGLenum(format.vertexAttribType), format.isNorm(),
                                           attrib.relativeOffset);
        }
    
        mAppliedAttributes[attribIndex].format         = attrib.format;
        mAppliedAttributes[attribIndex].relativeOffset = attrib.relativeOffset;
    }
    
    void VertexArrayGL::updateAttribBinding(size_t attribIndex)
    {
        ASSERT(supportVertexAttribBinding());
    
        GLuint bindingIndex = mState.getVertexAttribute(attribIndex).bindingIndex;
        if (mAppliedAttributes[attribIndex].bindingIndex == bindingIndex)
        {
            return;
        }
    
        mFunctions->vertexAttribBinding(static_cast<GLuint>(attribIndex), bindingIndex);
    
        mAppliedAttributes[attribIndex].bindingIndex = bindingIndex;
    }
    
    void VertexArrayGL::updateBindingBuffer(const gl::Context *context, size_t bindingIndex)
    {
        ASSERT(supportVertexAttribBinding());
    
        const VertexBinding &binding = mState.getVertexBinding(bindingIndex);
        if (SameVertexBuffer(mAppliedBindings[bindingIndex], binding))
        {
            return;
        }
    
        const Buffer *arrayBuffer = binding.getBuffer().get();
        GLuint bufferId           = 0;
        if (arrayBuffer != nullptr)
        {
            bufferId = GetImplAs<BufferGL>(arrayBuffer)->getBufferID();
        }
    
        mFunctions->bindVertexBuffer(static_cast<GLuint>(bindingIndex), bufferId, binding.getOffset(),
                                     binding.getStride());
    
        mAppliedBindings[bindingIndex].setStride(binding.getStride());
        mAppliedBindings[bindingIndex].setOffset(binding.getOffset());
        mAppliedBindings[bindingIndex].setBuffer(context, binding.getBuffer().get());
    }
    
    void VertexArrayGL::updateBindingDivisor(size_t bindingIndex)
    {
        GLuint adjustedDivisor =
            GetAdjustedDivisor(mAppliedNumViews, mState.getVertexBinding(bindingIndex).getDivisor());
        if (mAppliedBindings[bindingIndex].getDivisor() == adjustedDivisor)
        {
            return;
        }
    
        if (supportVertexAttribBinding())
        {
            mFunctions->vertexBindingDivisor(static_cast<GLuint>(bindingIndex), adjustedDivisor);
        }
        else
        {
            // We can only use VertexAttribDivisor on platforms that don't support Vertex Attrib
            // Binding.
            mFunctions->vertexAttribDivisor(static_cast<GLuint>(bindingIndex), adjustedDivisor);
        }
    
        mAppliedBindings[bindingIndex].setDivisor(adjustedDivisor);
    }
    
    void VertexArrayGL::syncDirtyAttrib(const gl::Context *context,
                                        size_t attribIndex,
                                        const gl::VertexArray::DirtyAttribBits &dirtyAttribBits)
    {
        ASSERT(dirtyAttribBits.any());
    
        for (size_t dirtyBit : dirtyAttribBits)
        {
            switch (dirtyBit)
            {
                case VertexArray::DIRTY_ATTRIB_ENABLED:
                    updateAttribEnabled(attribIndex);
                    break;
    
                case VertexArray::DIRTY_ATTRIB_POINTER:
                    updateAttribPointer(context, attribIndex);
                    break;
    
                case VertexArray::DIRTY_ATTRIB_FORMAT:
                    ASSERT(supportVertexAttribBinding());
                    updateAttribFormat(attribIndex);
                    break;
    
                case VertexArray::DIRTY_ATTRIB_BINDING:
                    ASSERT(supportVertexAttribBinding());
                    updateAttribBinding(attribIndex);
                    break;
    
                default:
                    UNREACHABLE();
                    break;
            }
        }
    }
    
    void VertexArrayGL::syncDirtyBinding(const gl::Context *context,
                                         size_t bindingIndex,
                                         const gl::VertexArray::DirtyBindingBits &dirtyBindingBits)
    {
        // Dependent state changes in buffers can trigger updates with no dirty bits set.
    
        for (size_t dirtyBit : dirtyBindingBits)
        {
            switch (dirtyBit)
            {
                case VertexArray::DIRTY_BINDING_BUFFER:
                    ASSERT(supportVertexAttribBinding());
                    updateBindingBuffer(context, bindingIndex);
                    break;
    
                case VertexArray::DIRTY_BINDING_DIVISOR:
                    updateBindingDivisor(bindingIndex);
                    break;
    
                default:
                    UNREACHABLE();
                    break;
            }
        }
    }
    
    #define ANGLE_DIRTY_ATTRIB_FUNC(INDEX)                         \
        case VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:              \
            syncDirtyAttrib(context, INDEX, (*attribBits)[INDEX]); \
            (*attribBits)[INDEX].reset();                          \
            break;
    
    #define ANGLE_DIRTY_BINDING_FUNC(INDEX)                          \
        case VertexArray::DIRTY_BIT_BINDING_0 + INDEX:               \
            syncDirtyBinding(context, INDEX, (*bindingBits)[INDEX]); \
            (*bindingBits)[INDEX].reset();                           \
            break;
    
    #define ANGLE_DIRTY_BUFFER_DATA_FUNC(INDEX)            \
        case VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \
            break;
    
    angle::Result VertexArrayGL::syncState(const gl::Context *context,
                                           const gl::VertexArray::DirtyBits &dirtyBits,
                                           gl::VertexArray::DirtyAttribBitsArray *attribBits,
                                           gl::VertexArray::DirtyBindingBitsArray *bindingBits)
    {
        mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
    
        for (size_t dirtyBit : dirtyBits)
        {
            switch (dirtyBit)
            {
                case VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
                    updateElementArrayBufferBinding(context);
                    break;
    
                case VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
                    break;
    
                    ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_ATTRIB_FUNC)
                    ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_BINDING_FUNC)
                    ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_BUFFER_DATA_FUNC)
    
                default:
                    UNREACHABLE();
                    break;
            }
        }
    
        return angle::Result::Continue;
    }
    
    void VertexArrayGL::applyNumViewsToDivisor(int numViews)
    {
        if (numViews != mAppliedNumViews)
        {
            mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
            mAppliedNumViews = numViews;
            for (size_t index = 0u; index < mAppliedBindings.size(); ++index)
            {
                updateBindingDivisor(index);
            }
        }
    }
    
    void VertexArrayGL::applyActiveAttribLocationsMask(const gl::AttributesMask &activeMask)
    {
        gl::AttributesMask updateMask = mProgramActiveAttribLocationsMask ^ activeMask;
        if (!updateMask.any())
        {
            return;
        }
        ASSERT(mVertexArrayID == mStateManager->getVertexArrayID());
        mProgramActiveAttribLocationsMask = activeMask;
    
        for (size_t attribIndex : updateMask)
        {
            updateAttribEnabled(attribIndex);
        }
    }
    
    }  // namespace rx