//
// 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
{
// VertexArrayState implementation.
VertexArrayState::VertexArrayState(VertexArrayID vertexArrayID,
                                   size_t maxAttribs,
                                   size_t maxAttribBindings)
    : mId(vertexArrayID)
{
    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));
        // The default vertex attribute format is R32G32B32A32_FLOAT. Keep mVertexAttributesTypeMask
        // in sync so attributes that keep their default format are treated as float.
        SetComponentTypeMask(ComponentType::Float, i, &mVertexAttributesTypeMask);
    }

    // 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 < mVertexBindings.size());
    return mVertexBindings[bindingIndex].getBoundAttributesMask();
}

// Set an attribute using a new binding.
void VertexArrayState::setAttribBinding(size_t attribIndex, GLuint newBindingIndex)
{
    ASSERT(attribIndex < mVertexAttributes.size() && newBindingIndex < mVertexBindings.size());

    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;

    mEnabledAttributesMask.set(attribIndex, attrib.enabled);
}

bool VertexArrayState::isDefault() const
{
    return mId.value == 0;
}

// VertexArrayPrivate implementation.
VertexArrayPrivate::VertexArrayPrivate(rx::GLImplFactory *factory,
                                       VertexArrayID id,
                                       size_t maxAttribs,
                                       size_t maxAttribBindings)
    : mId(id), mState(mId, maxAttribs, maxAttribBindings), mRobustBufferAccessEnabled(false)
{}

VertexArrayPrivate::~VertexArrayPrivate() {}

void VertexArrayPrivate::setVertexAttribBinding(size_t attribIndex, GLuint newBindingIndex)
{
    ASSERT(attribIndex < getMaxAttribs() && newBindingIndex < getMaxBindings());

    if (mState.mVertexAttributes[attribIndex].bindingIndex == newBindingIndex)
    {
        return;
    }

    mState.setAttribBinding(attribIndex, newBindingIndex);

    if (mRobustBufferAccessEnabled)
    {
        VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
        attrib.updateCachedElementLimit(mState.mVertexBindings[newBindingIndex],
                                        mCachedBufferSize[newBindingIndex]);
    }

    setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_BINDING);

    // Update client attribs mask.
    mState.mClientMemoryAttribsMask.set(attribIndex, !mBufferBindingMask[newBindingIndex]);

    mCachedMappedArrayBuffers.set(attribIndex, mCachedBufferPropertyMapped.test(newBindingIndex));
    mCachedMutableOrImpersistentArrayBuffers.set(
        attribIndex, mCachedBufferPropertyMutableOrImpersistent.test(newBindingIndex));
    mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask &
                                      mCachedMutableOrImpersistentArrayBuffers;
}

const VertexAttribute &VertexArrayPrivate::getVertexAttribute(size_t attribIndex) const
{
    ASSERT(attribIndex < getMaxAttribs());
    return mState.mVertexAttributes[attribIndex];
}

const VertexBinding &VertexArrayPrivate::getVertexBinding(size_t bindingIndex) const
{
    ASSERT(bindingIndex < getMaxBindings());
    return mState.mVertexBindings[bindingIndex];
}

void VertexArrayPrivate::setDirtyAttribBit(size_t attribIndex, DirtyAttribBitType dirtyAttribBit)
{
    mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex);
    mDirtyAttribBits[attribIndex].set(dirtyAttribBit);
}

ANGLE_INLINE void VertexArrayPrivate::clearDirtyAttribBit(size_t attribIndex,
                                                          DirtyAttribBitType dirtyAttribBit)
{
    mDirtyAttribBits[attribIndex].set(dirtyAttribBit, false);
    if (mDirtyAttribBits[attribIndex].any())
    {
        return;
    }
    mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex, false);
}

ANGLE_INLINE void VertexArrayPrivate::setDirtyBindingBit(size_t bindingIndex,
                                                         DirtyBindingBitType dirtyBindingBit)
{
    mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex);
    mDirtyBindingBits[bindingIndex].set(dirtyBindingBit);
}

ANGLE_INLINE void VertexArrayPrivate::updateCachedElementLimit(const VertexBinding &binding,
                                                               GLint64 bufferSize)
{
    ASSERT(mRobustBufferAccessEnabled);
    for (size_t boundAttribute : binding.getBoundAttributesMask())
    {
        mState.mVertexAttributes[boundAttribute].updateCachedElementLimit(binding, bufferSize);
    }
}

ANGLE_INLINE void VertexArrayPrivate::updateCachedArrayBuffersMasks(
    bool isMapped,
    bool isImmutable,
    bool isPersistent,
    const AttributesMask &boundAttributesMask)
{
    if (isMapped)
    {
        mCachedMappedArrayBuffers |= boundAttributesMask;
    }
    else
    {
        mCachedMappedArrayBuffers &= ~boundAttributesMask;
    }

    if (!isImmutable || !isPersistent)
    {
        mCachedMutableOrImpersistentArrayBuffers |= boundAttributesMask;
    }
    else
    {
        mCachedMutableOrImpersistentArrayBuffers &= ~boundAttributesMask;
    }

    mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask &
                                      mCachedMutableOrImpersistentArrayBuffers;
}

void VertexArrayPrivate::setVertexBindingDivisor(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);
}

bool VertexArrayPrivate::setVertexAttribFormatImpl(VertexAttribute *attrib,
                                                   GLint size,
                                                   VertexAttribType type,
                                                   bool normalized,
                                                   bool pureInteger,
                                                   GLuint relativeOffset)
{
    angle::FormatID formatID = 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 VertexArrayPrivate::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);
    }

    if (mRobustBufferAccessEnabled)
    {
        attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex],
                                        mCachedBufferSize[attrib.bindingIndex]);
    }
}

void VertexArrayPrivate::setVertexAttribDivisor(size_t attribIndex, GLuint divisor)
{
    ASSERT(attribIndex < getMaxAttribs());

    setVertexAttribBinding(attribIndex, static_cast<GLuint>(attribIndex));
    setVertexBindingDivisor(attribIndex, divisor);
}

void VertexArrayPrivate::enableAttribute(size_t attribIndex, bool enabledState)
{
    ASSERT(attribIndex < getMaxAttribs());

    VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];

    if (mState.mEnabledAttributesMask.test(attribIndex) == enabledState)
    {
        return;
    }

    attrib.enabled = enabledState;

    // Update state cache
    mState.mEnabledAttributesMask.set(attribIndex, enabledState);
    bool enableChanged = (mState.mEnabledAttributesMask.test(attribIndex) !=
                          mState.mLastSyncedEnabledAttributesMask.test(attribIndex));

    if (enableChanged)
    {
        setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_ENABLED);
    }
    else
    {
        clearDirtyAttribBit(attribIndex, DIRTY_ATTRIB_ENABLED);
    }

    mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask &
                                      mCachedMutableOrImpersistentArrayBuffers;
}

bool VertexArrayPrivate::hasTransformFeedbackBindingConflict(const Context *context) const
{
    // Fast check first.
    if (!mCachedBufferPropertyTransformFeedbackConflict.any())
    {
        return false;
    }

    const AttributesMask &activeAttribues = context->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 (mCachedBufferPropertyTransformFeedbackConflict[attrib.bindingIndex])
        {
            return true;
        }
    }

    return false;
}

// VertexArray implementation.
VertexArray::VertexArray(rx::GLImplFactory *factory,
                         VertexArrayID id,
                         size_t maxAttribs,
                         size_t maxAttribBindings)
    : VertexArrayPrivate(factory, id, maxAttribs, maxAttribBindings),
      mVertexArray(factory->createVertexArray(mState, mVertexArrayBuffers))
{}

void VertexArray::onDestroy(const Context *context)
{
    bool isBound = context->isCurrentVertexArray(this);

    for (size_t bindingIndex : mBufferBindingMask)
    {
        Buffer *buffer = mVertexArrayBuffers[bindingIndex].get();
        ASSERT(buffer != nullptr);
        if (isBound)
        {
            buffer->onNonTFBindingChanged(-1);
            buffer->removeVertexArrayBinding(context, bindingIndex);
        }
        mVertexArrayBuffers[bindingIndex].set(context, nullptr);
    }

    mBufferBindingMask.reset();
    mVertexArray->destroy(context);
    SafeDelete(mVertexArray);
    delete this;
}

VertexArray::~VertexArray()
{
    ASSERT(!mVertexArray);
}

angle::Result VertexArray::setLabel(const Context *context, const std::string &label)
{
    mState.mLabel = label;

    if (mVertexArray)
    {
        return mVertexArray->onLabelUpdate(context);
    }
    return angle::Result::Continue;
}

const std::string &VertexArray::getLabel() const
{
    return mState.getLabel();
}

bool VertexArray::detachBuffer(const Context *context, BufferID bufferID)
{
    bool isBound           = context->isCurrentVertexArray(this);
    bool anyBufferDetached = false;

    for (size_t bindingIndex : mBufferBindingMask)
    {
        Buffer *buffer = mVertexArrayBuffers[bindingIndex].get();
        ASSERT(buffer != nullptr);
        if (buffer->id() == bufferID)
        {
            if (isBound)
            {
                buffer->onNonTFBindingChanged(-1);
            }

            buffer->removeVertexArrayBinding(context, bindingIndex);
            mVertexArrayBuffers[bindingIndex].set(context, nullptr);
            mBufferBindingMask.reset(bindingIndex);

            if (bindingIndex == kElementArrayBufferIndex)
            {
                mDirtyBits.set(DIRTY_BIT_ELEMENT_ARRAY_BUFFER);
            }
            else
            {
                VertexBinding &binding = mState.mVertexBindings[bindingIndex];
                if (context->getClientVersion() >= ES_3_1 && !mState.isDefault())
                {
                    setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER);
                }
                else
                {
                    static_assert(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);
                }

                for (size_t attribIndex : binding.getBoundAttributesMask())
                {
                    VertexAttribute &attrib = mState.mVertexAttributes[attribIndex];
                    attrib.pointer          = nullptr;
                }

                mState.mClientMemoryAttribsMask |= binding.getBoundAttributesMask();
            }

            anyBufferDetached = true;
        }
    }

    return anyBufferDetached;
}

ANGLE_INLINE void VertexArray::updateCachedMappedArrayBuffersBinding(size_t bindingIndex)
{
    const VertexBinding &binding = mState.mVertexBindings[bindingIndex];
    const Buffer *buffer         = mVertexArrayBuffers[bindingIndex].get();
    ASSERT(mBufferBindingMask.test(bindingIndex));
    ASSERT(buffer != nullptr);

    bool isMapped     = buffer->isMapped() == GL_TRUE;
    bool isImmutable  = buffer->isImmutable() == GL_TRUE;
    bool isPersistent = (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0;

    mCachedBufferPropertyMapped.set(bindingIndex, isMapped);
    mCachedBufferPropertyMutableOrImpersistent.set(bindingIndex, !isImmutable || !isPersistent);

    return updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent,
                                         binding.getBoundAttributesMask());
}

void VertexArray::bindElementBuffer(const Context *context, Buffer *boundBuffer)
{
    Buffer *oldBuffer = getElementArrayBuffer();

    if (oldBuffer)
    {
        oldBuffer->removeVertexArrayBinding(context, kElementArrayBufferIndex);
        if (context->isWebGL())
        {
            oldBuffer->onNonTFBindingChanged(-1);
        }
        oldBuffer->release(context);
        mBufferBindingMask.reset(kElementArrayBufferIndex);
    }

    mVertexArrayBuffers[kElementArrayBufferIndex].assign(boundBuffer);

    if (boundBuffer)
    {
        boundBuffer->addVertexArrayBinding(context, kElementArrayBufferIndex);
        if (context->isWebGL())
        {
            boundBuffer->onNonTFBindingChanged(1);
        }
        boundBuffer->addRef();
        mBufferBindingMask.set(kElementArrayBufferIndex);
    }

    mDirtyBits.set(VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER);
    mIndexRangeInlineCache = {};
}

ANGLE_INLINE VertexArray::DirtyBindingBits VertexArray::bindVertexBufferImpl(const Context *context,
                                                                             size_t bindingIndex,
                                                                             Buffer *boundBuffer,
                                                                             uintptr_t offset,
                                                                             GLsizei stride)
{
    ASSERT(bindingIndex < getMaxBindings());
    ASSERT(context->isCurrentVertexArray(this));

    VertexBinding *binding = &mState.mVertexBindings[bindingIndex];

    Buffer *oldBuffer = mVertexArrayBuffers[bindingIndex].get();

    DirtyBindingBits dirtyBindingBits;
    dirtyBindingBits.set(DIRTY_BINDING_BUFFER, oldBuffer != boundBuffer);
    dirtyBindingBits.set(DIRTY_BINDING_STRIDE, static_cast<GLuint>(stride) != binding->getStride());
    dirtyBindingBits.set(DIRTY_BINDING_OFFSET, offset != binding->getOffset());
    if (mRobustBufferAccessEnabled)
    {
        GLint64 bufferSize = boundBuffer ? boundBuffer->getSize() : 0;
        dirtyBindingBits.set(DIRTY_BINDING_SIZE, bufferSize != mCachedBufferSize[bindingIndex]);
        mCachedBufferSize[bindingIndex] = bufferSize;
    }

    if (dirtyBindingBits.none())
    {
        return dirtyBindingBits;
    }

    if (boundBuffer != oldBuffer)
    {
        // Several nullptr checks are combined here for optimization purposes.
        if (oldBuffer)
        {
            oldBuffer->onNonTFBindingChanged(-1);
            oldBuffer->removeVertexArrayBinding(context, bindingIndex);
            oldBuffer->release(context);
            mBufferBindingMask.reset(bindingIndex);
        }

        mVertexArrayBuffers[bindingIndex].assign(boundBuffer);

        // Update client memory attribute pointers. Affects all bound attributes.
        if (boundBuffer)
        {
            boundBuffer->addRef();
            boundBuffer->onNonTFBindingChanged(1);
            boundBuffer->addVertexArrayBinding(context, bindingIndex);
            if (context->isWebGL() || context->isHardenedContext())
            {
                mCachedBufferPropertyTransformFeedbackConflict.set(
                    bindingIndex, boundBuffer->hasTFBBindingConflict());
            }
            mBufferBindingMask.set(bindingIndex);
            mState.mClientMemoryAttribsMask &= ~binding->getBoundAttributesMask();
            updateCachedMappedArrayBuffersBinding(bindingIndex);
        }
        else
        {
            mCachedBufferPropertyTransformFeedbackConflict.set(bindingIndex, false);
            mState.mClientMemoryAttribsMask |= binding->getBoundAttributesMask();
            mCachedBufferPropertyMapped.set(bindingIndex, false);
            mCachedBufferPropertyMutableOrImpersistent.set(bindingIndex, false);
            updateCachedArrayBuffersMasks(false, false, false, binding->getBoundAttributesMask());
        }
    }

    binding->setOffset(offset);
    binding->setStride(stride);

    if (mRobustBufferAccessEnabled)
    {
        updateCachedElementLimit(*binding, mCachedBufferSize[bindingIndex]);
    }

    return dirtyBindingBits;
}

void VertexArray::bindVertexBuffer(const Context *context,
                                   size_t bindingIndex,
                                   Buffer *boundBuffer,
                                   GLintptr offset,
                                   GLsizei stride)
{
    // |offset| must be non-negative per validation rules of glBindVertexBuffer.
    ASSERT(offset >= 0);
    const VertexArray::DirtyBindingBits dirtyBindingBits = bindVertexBufferImpl(
        context, bindingIndex, boundBuffer, static_cast<uintptr_t>(offset), stride);

    if (!dirtyBindingBits.test(DIRTY_BINDING_BUFFER) && context->isSharedContext() &&
        boundBuffer != nullptr)
    {
        ASSERT(boundBuffer == mVertexArrayBuffers[bindingIndex].get());
        VertexArrayBufferBindingMask bindingMask = boundBuffer->getVertexArrayBinding(context);
        ASSERT(!bindingMask.none());
        onSharedBufferBind(context, boundBuffer, bindingMask);
    }

    if (dirtyBindingBits.any())
    {
        mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex);
        mDirtyBindingBits[bindingIndex] |= dirtyBindingBits;
    }
}

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,
                                                          bool *isVertexAttribDirtyOut)
{
    ASSERT(isVertexAttribDirtyOut);
    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(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.
    Buffer *oldBuffer = mVertexArrayBuffers[attrib.bindingIndex].get();
    if ((boundBuffer == nullptr) != (oldBuffer == nullptr))
    {
        attribDirty = true;
    }

    // If using client arrays and the pointer changes, set the attribute as dirty
    if (boundBuffer == nullptr && attrib.pointer != pointer)
    {
        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;
    uintptr_t offset = boundBuffer ? reinterpret_cast<uintptr_t>(pointer) : 0;
    const VertexArray::DirtyBindingBits dirtyBindingBits =
        bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride);

    if (attribDirty)
    {
        setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER);
        *isVertexAttribDirtyOut = true;
    }
    else if (dirtyBindingBits.any())
    {
        setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER_BUFFER);
        *isVertexAttribDirtyOut = true;
    }

    mState.mNullPointerClientMemoryAttribsMask.set(attribIndex,
                                                   boundBuffer == nullptr && pointer == nullptr);
}

void VertexArray::setVertexAttribPointer(const Context *context,
                                         size_t attribIndex,
                                         Buffer *boundBuffer,
                                         GLint size,
                                         VertexAttribType type,
                                         bool normalized,
                                         GLsizei stride,
                                         const void *pointer,
                                         bool *isVertexAttribDirtyOut)
{
    setVertexAttribPointerImpl(context, ComponentType::Float, false, attribIndex, boundBuffer, size,
                               type, normalized, stride, pointer, isVertexAttribDirtyOut);
}

void VertexArray::setVertexAttribIPointer(const Context *context,
                                          size_t attribIndex,
                                          Buffer *boundBuffer,
                                          GLint size,
                                          VertexAttribType type,
                                          GLsizei stride,
                                          const void *pointer,
                                          bool *isVertexAttribDirtyOut)
{
    ComponentType componentType = GetVertexAttributeComponentType(true, type);
    setVertexAttribPointerImpl(context, componentType, true, attribIndex, boundBuffer, size, type,
                               false, stride, pointer, isVertexAttribDirtyOut);
}

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());
        mState.mLastSyncedEnabledAttributesMask = mState.mEnabledAttributesMask;
    }
    return angle::Result::Continue;
}

bool VertexArray::bufferMaskBitsPointToTheSameBuffer(
    VertexArrayBufferBindingMask bufferBindingMask) const
{
    const Buffer *buffer = nullptr;
    for (size_t bindingIndex : bufferBindingMask)
    {
        if (buffer == nullptr)
        {
            buffer = mVertexArrayBuffers[bindingIndex].get();
        }
        else if (buffer != mVertexArrayBuffers[bindingIndex].get())
        {
            return false;
        }
    }
    return true;
}

void VertexArray::updateBindingSizeIfChanged(const size_t bindingIndex, const GLint64 bufferSize)
{
    ASSERT(mRobustBufferAccessEnabled);
    if (mCachedBufferSize[bindingIndex] != bufferSize)
    {
        setDirtyBindingBit(bindingIndex, DIRTY_BINDING_SIZE);
        mCachedBufferSize[bindingIndex] = bufferSize;
    }
}

// This becomes current vertex array on the context
void VertexArray::onBind(const Context *context)
{
    VertexArrayBufferBindingMask bufferBindingMask = mBufferBindingMask;

    if (bufferBindingMask[kElementArrayBufferIndex])
    {
        Buffer *bufferGL = getElementArrayBuffer();
        ASSERT(bufferGL != nullptr);
        bufferGL->addVertexArrayBinding(context, kElementArrayBufferIndex);
        bufferBindingMask.reset(kElementArrayBufferIndex);
    }
    else
    {
        ASSERT(getElementArrayBuffer() == nullptr);
    }

    // This vertex array becoming current. Some of the bindings we may have removed from buffer's
    // observer list. We need to add it back to the buffer's observer list and update dirty bits
    // that we may have missed while we were not observing.
    for (size_t bindingIndex : bufferBindingMask)
    {
        Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get();
        ASSERT(bufferGL != nullptr);
        ASSERT(bindingIndex != kElementArrayBufferIndex);
        bufferGL->addVertexArrayBinding(context, bindingIndex);
        updateCachedMappedArrayBuffersBinding(bindingIndex);
    }

    if (mRobustBufferAccessEnabled)
    {
        for (size_t bindingIndex : bufferBindingMask)
        {
            Buffer *bufferGL                = mVertexArrayBuffers[bindingIndex].get();
            updateBindingSizeIfChanged(bindingIndex, bufferGL->getSize());
            updateCachedElementLimit(mState.mVertexBindings[bindingIndex],
                                     mCachedBufferSize[bindingIndex]);
        }
    }

    if (context->isWebGL() || context->isHardenedContext())
    {
        for (size_t bindingIndex : bufferBindingMask)
        {
            bool hasConflict = mVertexArrayBuffers[bindingIndex]->hasTFBBindingConflict();
            mCachedBufferPropertyTransformFeedbackConflict.set(bindingIndex, hasConflict);
        }
    }

    // Buffers may have changed while vertex array was not current, we need to check buffer's
    // internal storage and set proper dirty bits if buffer has changed since last syncState.
    mDirtyBits |= mVertexArray->checkBufferForDirtyBits(context, mBufferBindingMask);

    // Always reset mIndexRangeInlineCache since we lost buffer observation while unbind
    mIndexRangeInlineCache = {};

    onStateChange(angle::SubjectMessage::ContentsChanged);
}

// This becomes non-current vertex array on the context
void VertexArray::onUnbind(const Context *context)
{
    // This vertex array becoming non-current. For performance reason, we remove it from the
    // buffers' observer list so that the cost of buffer sending signal to observers will not be too
    // expensive.
    for (size_t bindingIndex : mBufferBindingMask)
    {
        Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get();
        ASSERT(bufferGL != nullptr);
        bufferGL->removeVertexArrayBinding(context, bindingIndex);
    }
}

void VertexArray::onBindingChanged(const Context *context, int incr)
{
    // When vertex array gets unbound, we remove it from bound buffers' observer list so that when
    // buffer changes, it wont has to loop over all these non-current vertex arrays and set dirty
    // bit on them. To compensate for that, when we bind a vertex array, we have to check against
    // each bound buffers and see if they have changed and needs to update vertex array's dirty bits
    // accordingly
    ASSERT(incr == 1 || incr == -1);
    if (incr < 0)
    {
        onUnbind(context);
    }
    else
    {
        onBind(context);
    }

    if (context->isWebGL())
    {
        for (size_t bindingIndex : mBufferBindingMask)
        {
            ASSERT(mVertexArrayBuffers[bindingIndex].get());
            mVertexArrayBuffers[bindingIndex]->onNonTFBindingChanged(incr);
        }
    }
}

void VertexArray::setDependentDirtyBits(bool contentsChanged,
                                        VertexArrayBufferBindingMask bufferBindingMask)
{
    DirtyBits dirtyBits(contentsChanged ? (bufferBindingMask.bits() << DIRTY_BIT_BUFFER_DATA_0)
                                        : (bufferBindingMask.bits() << DIRTY_BIT_BINDING_0));
    ASSERT(!mDirtyBitsGuard.valid() || (mDirtyBitsGuard.value() & dirtyBits) == dirtyBits);
    mDirtyBits |= dirtyBits;

    if (bufferBindingMask.test(kElementArrayBufferIndex))
    {
        mIndexRangeInlineCache = {};
    }

    onStateChange(angle::SubjectMessage::ContentsChanged);
}

void VertexArray::onSharedBufferBind(const Context *context,
                                     const Buffer *buffer,
                                     VertexArrayBufferBindingMask bufferBindingMask)
{
    bufferBindingMask &= mBufferBindingMask;
    ASSERT(bufferBindingMask.any());

    // vertexBufferBindingMask is bufferBindingMask without elementBuffer.
    VertexArrayBufferBindingMask vertexBufferBindingMask = bufferBindingMask;
    vertexBufferBindingMask.reset(kElementArrayBufferIndex);

    for (size_t bindingIndex : vertexBufferBindingMask)
    {
        updateCachedMappedArrayBuffersBinding(bindingIndex);
    }

    if (mRobustBufferAccessEnabled)
    {
        for (size_t bindingIndex : vertexBufferBindingMask)
        {
            ASSERT(buffer == mVertexArrayBuffers[bindingIndex].get());
            updateBindingSizeIfChanged(bindingIndex, buffer->getSize());
            updateCachedElementLimit(mState.mVertexBindings[bindingIndex],
                                     mCachedBufferSize[bindingIndex]);
        }
    }

    if (context->isWebGL() || context->isHardenedContext())
    {
        if (buffer->hasTFBBindingConflict())
        {
            mCachedBufferPropertyTransformFeedbackConflict |= vertexBufferBindingMask;
        }
        else
        {
            mCachedBufferPropertyTransformFeedbackConflict &= ~vertexBufferBindingMask;
        }
    }

    // Set proper dirty bits on VertexArray
    mDirtyBits |= mVertexArray->checkBufferForDirtyBits(context, bufferBindingMask);

    // mIndexRangeInlineCache is no longer invalid
    mIndexRangeInlineCache = {};
}

void VertexArray::onBufferChanged(const Context *context,
                                  const Buffer *buffer,
                                  angle::SubjectMessage message,
                                  VertexArrayBufferBindingMask vertexArrayBufferBindingMask)
{
    VertexArrayBufferBindingMask bufferBindingMask =
        vertexArrayBufferBindingMask & mBufferBindingMask;
    ASSERT(buffer);
    ASSERT(bufferBindingMask.any());
    ASSERT(bufferMaskBitsPointToTheSameBuffer(bufferBindingMask));

    switch (message)
    {
        case angle::SubjectMessage::SubjectChanged:
            if (mRobustBufferAccessEnabled)
            {
                VertexArrayBufferBindingMask VertexBufferBindingMask = bufferBindingMask;
                VertexBufferBindingMask.reset(kElementArrayBufferIndex);
                for (size_t bindingIndex : VertexBufferBindingMask)
                {
                    updateBindingSizeIfChanged(bindingIndex, buffer->getSize());
                    updateCachedElementLimit(mState.mVertexBindings[bindingIndex],
                                             mCachedBufferSize[bindingIndex]);
                }
            }
            // This has to be called after updateCachedElementLimit due to
            // mCachedElementLimit dependency
            setDependentDirtyBits(false, bufferBindingMask);
            break;

        case angle::SubjectMessage::BindingChanged:
            if (context->isWebGL() || context->isHardenedContext())
            {
                bufferBindingMask.reset(kElementArrayBufferIndex);

                bool hasConflict = buffer->hasTFBBindingConflict();
                if (hasConflict)
                {
                    mCachedBufferPropertyTransformFeedbackConflict |= bufferBindingMask;
                }
                else
                {
                    mCachedBufferPropertyTransformFeedbackConflict &= ~bufferBindingMask;
                }
            }
            break;

        case angle::SubjectMessage::SubjectMapped:
            bufferBindingMask.reset(kElementArrayBufferIndex);
            for (size_t bindingIndex : bufferBindingMask)
            {
                updateCachedMappedArrayBuffersBinding(bindingIndex);
            }
            onStateChange(angle::SubjectMessage::SubjectMapped);
            break;

        case angle::SubjectMessage::SubjectUnmapped:
        {
            VertexArrayBufferBindingMask VertexBufferBindingMask = bufferBindingMask;
            VertexBufferBindingMask.reset(kElementArrayBufferIndex);
            for (size_t bindingIndex : VertexBufferBindingMask)
            {
                updateCachedMappedArrayBuffersBinding(bindingIndex);
            }
            setDependentDirtyBits(true, bufferBindingMask);
            onStateChange(angle::SubjectMessage::SubjectUnmapped);
        }
        break;

        case angle::SubjectMessage::InternalMemoryAllocationChanged:
            setDependentDirtyBits(false, bufferBindingMask);
            break;

        case angle::SubjectMessage::ContentsChanged:
        {
            VertexArrayBufferBindingMask bufferContentObserverBindingMask =
                vertexArrayBufferBindingMask & mVertexArray->getContentObserversBindingMask();
            if (bufferContentObserverBindingMask.any())
            {
                setDependentDirtyBits(true, bufferBindingMask);
            }
        }
        break;

        default:
            UNREACHABLE();
            break;
    }
}
}  // namespace gl
