//
// Copyright 2016 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.
//
// VertexArrayVk.cpp:
//    Implements the class methods for VertexArrayVk.
//

#include "libANGLE/renderer/vulkan/VertexArrayVk.h"

#include "common/debug.h"

#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/BufferVk.h"
#include "libANGLE/renderer/vulkan/CommandGraph.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/FramebufferVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/vk_format_utils.h"

namespace rx
{
namespace
{
constexpr size_t kDynamicVertexDataSize = 1024 * 1024;
constexpr size_t kDynamicIndexDataSize  = 1024 * 8;

bool BindingIsAligned(const gl::VertexBinding &binding, unsigned componentSize)
{
    return (binding.getOffset() % componentSize == 0) && (binding.getStride() % componentSize == 0);
}

angle::Result StreamVertexData(ContextVk *contextVk,
                               vk::DynamicBuffer *dynamicBuffer,
                               const uint8_t *sourceData,
                               size_t bytesToAllocate,
                               size_t destOffset,
                               size_t vertexCount,
                               size_t stride,
                               VertexCopyFunction vertexLoadFunction,
                               VkBuffer *bufferHandleOut,
                               VkDeviceSize *bufferOffsetOut)
{
    uint8_t *dst = nullptr;
    ANGLE_TRY(dynamicBuffer->allocate(contextVk, bytesToAllocate, &dst, bufferHandleOut,
                                      bufferOffsetOut, nullptr));
    dst += destOffset;
    vertexLoadFunction(sourceData, stride, vertexCount, dst);

    ANGLE_TRY(dynamicBuffer->flush(contextVk));
    return angle::Result::Continue();
}
}  // anonymous namespace

#define INIT                                        \
    {                                               \
        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1024 * 8 \
    }

VertexArrayVk::VertexArrayVk(const gl::VertexArrayState &state, RendererVk *renderer)
    : VertexArrayImpl(state),
      mCurrentArrayBufferHandles{},
      mCurrentArrayBufferOffsets{},
      mCurrentArrayBufferResources{},
      mCurrentArrayBufferFormats{},
      mCurrentArrayBufferStrides{},
      mCurrentArrayBufferConversion{{
          INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT,
          INIT,
      }},
      mCurrentArrayBufferConversionCanRelease{},
      mCurrentElementArrayBufferHandle(VK_NULL_HANDLE),
      mCurrentElementArrayBufferOffset(0),
      mCurrentElementArrayBufferResource(nullptr),
      mDynamicVertexData(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, kDynamicVertexDataSize),
      mDynamicIndexData(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, kDynamicIndexDataSize),
      mTranslatedByteIndexData(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, kDynamicIndexDataSize),
      mLineLoopHelper(renderer),
      mDirtyLineLoopTranslation(true)
{
    mCurrentArrayBufferHandles.fill(VK_NULL_HANDLE);
    mCurrentArrayBufferOffsets.fill(0);
    mCurrentArrayBufferResources.fill(nullptr);

    mPackedInputBindings.fill({0, 0});
    mPackedInputAttributes.fill({0, 0, 0});

    for (vk::DynamicBuffer &buffer : mCurrentArrayBufferConversion)
    {
        buffer.init(1, renderer);
    }
    mDynamicVertexData.init(1, renderer);
    mDynamicIndexData.init(1, renderer);
    mTranslatedByteIndexData.init(1, renderer);

    // Initially consider all inputs dirty.
    mDirtyPackedInputs.set();
}

VertexArrayVk::~VertexArrayVk()
{
}

void VertexArrayVk::destroy(const gl::Context *context)
{
    VkDevice device = vk::GetImpl(context)->getRenderer()->getDevice();
    for (vk::DynamicBuffer &buffer : mCurrentArrayBufferConversion)
    {
        buffer.destroy(device);
    }
    mDynamicVertexData.destroy(device);
    mDynamicIndexData.destroy(device);
    mTranslatedByteIndexData.destroy(device);
    mLineLoopHelper.destroy(device);
}

angle::Result VertexArrayVk::streamIndexData(ContextVk *contextVk,
                                             GLenum indexType,
                                             size_t indexCount,
                                             const void *sourcePointer,
                                             vk::DynamicBuffer *dynamicBuffer)
{
    ASSERT(!mState.getElementArrayBuffer().get() || indexType == GL_UNSIGNED_BYTE);

    dynamicBuffer->releaseRetainedBuffers(contextVk->getRenderer());

    const size_t amount = sizeof(GLushort) * indexCount;
    GLubyte *dst        = nullptr;

    ANGLE_TRY(dynamicBuffer->allocate(contextVk, amount, &dst, &mCurrentElementArrayBufferHandle,
                                      &mCurrentElementArrayBufferOffset, nullptr));
    if (indexType == GL_UNSIGNED_BYTE)
    {
        // Unsigned bytes don't have direct support in Vulkan so we have to expand the
        // memory to a GLushort.
        const GLubyte *in     = static_cast<const GLubyte *>(sourcePointer);
        GLushort *expandedDst = reinterpret_cast<GLushort *>(dst);
        for (size_t index = 0; index < indexCount; index++)
        {
            expandedDst[index] = static_cast<GLushort>(in[index]);
        }
    }
    else
    {
        memcpy(dst, sourcePointer, amount);
    }
    ANGLE_TRY(dynamicBuffer->flush(contextVk));
    return angle::Result::Continue();
}

// We assume the buffer is completely full of the same kind of data and convert
// and/or align it as we copy it to a DynamicBuffer. The assumption could be wrong
// but the alternative of copying it piecemeal on each draw would have a lot more
// overhead.
angle::Result VertexArrayVk::convertVertexBuffer(ContextVk *contextVk,
                                                 BufferVk *srcBuffer,
                                                 const gl::VertexBinding &binding,
                                                 size_t attribIndex)
{

    // Preparation for mapping source buffer.
    ANGLE_TRY(contextVk->getRenderer()->finish(contextVk));

    unsigned srcFormatSize = mCurrentArrayBufferFormats[attribIndex]->angleFormat().pixelBytes;
    unsigned dstFormatSize = mCurrentArrayBufferStrides[attribIndex];

    mCurrentArrayBufferConversion[attribIndex].releaseRetainedBuffers(contextVk->getRenderer());

    // Bytes usable for vertex data.
    GLint64 bytes = srcBuffer->getSize() - binding.getOffset();
    if (bytes < srcFormatSize)
        return angle::Result::Continue();

    // Count the last vertex.  It may occupy less than a full stride.
    size_t numVertices = 1;
    bytes -= srcFormatSize;

    // Count how many strides fit remaining space.
    if (bytes > 0)
        numVertices += static_cast<size_t>(bytes) / binding.getStride();

    void *src       = nullptr;
    ANGLE_TRY(srcBuffer->mapImpl(contextVk, &src));
    const uint8_t *srcBytes = reinterpret_cast<const uint8_t *>(src);
    srcBytes += binding.getOffset();
    ANGLE_TRY(StreamVertexData(contextVk, &mCurrentArrayBufferConversion[attribIndex], srcBytes,
                               numVertices * dstFormatSize, 0, numVertices, binding.getStride(),
                               mCurrentArrayBufferFormats[attribIndex]->vertexLoadFunction,
                               &mCurrentArrayBufferHandles[attribIndex],
                               &mCurrentArrayBufferOffsets[attribIndex]));
    ANGLE_TRY(srcBuffer->unmapImpl(contextVk));

    mCurrentArrayBufferConversionCanRelease[attribIndex] = true;

    return angle::Result::Continue();
}

void VertexArrayVk::ensureConversionReleased(RendererVk *renderer, size_t attribIndex)
{
    if (mCurrentArrayBufferConversionCanRelease[attribIndex])
    {
        mCurrentArrayBufferConversion[attribIndex].release(renderer);
        mCurrentArrayBufferConversionCanRelease[attribIndex] = false;
    }
}

#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                     \
    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                             \
        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
        invalidatePipeline = true;                                                \
        break;

#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                                    \
    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                            \
        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
        invalidatePipeline = true;                                                \
        break;

#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)                                \
    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX:                        \
        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
        break;

gl::Error VertexArrayVk::syncState(const gl::Context *context,
                                   const gl::VertexArray::DirtyBits &dirtyBits,
                                   const gl::VertexArray::DirtyAttribBitsArray &attribBits,
                                   const gl::VertexArray::DirtyBindingBitsArray &bindingBits)
{
    ASSERT(dirtyBits.any());

    bool invalidatePipeline = false;

    ContextVk *contextVk = vk::GetImpl(context);

    // Rebuild current attribute buffers cache. This will fail horribly if the buffer changes.
    // TODO(jmadill): Handle buffer storage changes.
    const auto &attribs  = mState.getVertexAttributes();
    const auto &bindings = mState.getVertexBindings();

    for (size_t dirtyBit : dirtyBits)
    {
        switch (dirtyBit)
        {
            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
            {
                gl::Buffer *bufferGL = mState.getElementArrayBuffer().get();
                if (bufferGL)
                {
                    BufferVk *bufferVk                 = vk::GetImpl(bufferGL);
                    mCurrentElementArrayBufferResource = bufferVk;
                    mCurrentElementArrayBufferHandle   = bufferVk->getVkBuffer().getHandle();
                }
                else
                {
                    mCurrentElementArrayBufferResource = nullptr;
                    mCurrentElementArrayBufferHandle   = VK_NULL_HANDLE;
                }

                mCurrentElementArrayBufferOffset = 0;
                mLineLoopBufferFirstIndex.reset();
                mLineLoopBufferLastIndex.reset();
                contextVk->setIndexBufferDirty();
                mDirtyLineLoopTranslation = true;
                break;
            }

            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
                mLineLoopBufferFirstIndex.reset();
                mLineLoopBufferLastIndex.reset();
                mDirtyLineLoopTranslation = true;
                break;

                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC);
                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC);
                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC);

            default:
                UNREACHABLE();
                break;
        }
    }

    if (invalidatePipeline)
    {
        contextVk->invalidateCurrentPipeline();
    }

    return gl::NoError();
}

angle::Result VertexArrayVk::syncDirtyAttrib(ContextVk *contextVk,
                                             const gl::VertexAttribute &attrib,
                                             const gl::VertexBinding &binding,
                                             size_t attribIndex)
{
    // Invalidate the input description for pipelines.
    mDirtyPackedInputs.set(attribIndex);

    RendererVk *renderer   = contextVk->getRenderer();
    bool releaseConversion = true;

    if (attrib.enabled)
    {
        gl::Buffer *bufferGL                    = binding.getBuffer().get();
        mCurrentArrayBufferFormats[attribIndex] = &renderer->getFormat(GetVertexFormatID(attrib));

        if (bufferGL)
        {
            BufferVk *bufferVk = vk::GetImpl(bufferGL);
            unsigned componentSize =
                mCurrentArrayBufferFormats[attribIndex]->angleFormat().pixelBytes / attrib.size;

            if (mCurrentArrayBufferFormats[attribIndex]->vertexLoadRequiresConversion ||
                !BindingIsAligned(binding, componentSize))
            {
                mCurrentArrayBufferStrides[attribIndex] =
                    mCurrentArrayBufferFormats[attribIndex]->bufferFormat().pixelBytes;

                ANGLE_TRY(convertVertexBuffer(contextVk, bufferVk, binding, attribIndex));

                mCurrentArrayBufferResources[attribIndex] = nullptr;
                releaseConversion                         = false;
            }
            else
            {
                mCurrentArrayBufferResources[attribIndex] = bufferVk;
                mCurrentArrayBufferHandles[attribIndex]   = bufferVk->getVkBuffer().getHandle();
                mCurrentArrayBufferOffsets[attribIndex]   = binding.getOffset();
                mCurrentArrayBufferStrides[attribIndex]   = binding.getStride();
            }
        }
        else
        {
            mCurrentArrayBufferResources[attribIndex] = nullptr;
            mCurrentArrayBufferHandles[attribIndex]   = VK_NULL_HANDLE;
            mCurrentArrayBufferOffsets[attribIndex]   = 0;
            mCurrentArrayBufferStrides[attribIndex] =
                mCurrentArrayBufferFormats[attribIndex]->bufferFormat().pixelBytes;
        }
    }
    else
    {
        contextVk->invalidateDefaultAttribute(attribIndex);

        // These will be filled out by the ContextVk.
        mCurrentArrayBufferResources[attribIndex] = nullptr;
        mCurrentArrayBufferHandles[attribIndex]   = VK_NULL_HANDLE;
        mCurrentArrayBufferOffsets[attribIndex]   = 0;
        mCurrentArrayBufferStrides[attribIndex]   = 0;
        mCurrentArrayBufferFormats[attribIndex] =
            &renderer->getFormat(angle::FormatID::R32G32B32A32_FLOAT);
    }

    if (releaseConversion)
        ensureConversionReleased(renderer, attribIndex);

    return angle::Result::Continue();
}

void VertexArrayVk::getPackedInputDescriptions(vk::PipelineDesc *pipelineDesc)
{
    updatePackedInputDescriptions();
    pipelineDesc->updateVertexInputInfo(mPackedInputBindings, mPackedInputAttributes);
}

void VertexArrayVk::updatePackedInputDescriptions()
{
    if (!mDirtyPackedInputs.any())
    {
        return;
    }

    const auto &attribs  = mState.getVertexAttributes();
    const auto &bindings = mState.getVertexBindings();

    for (auto attribIndex : mDirtyPackedInputs)
    {
        const auto &attrib  = attribs[attribIndex];
        const auto &binding = bindings[attrib.bindingIndex];
        if (attrib.enabled)
        {
            updatePackedInputInfo(static_cast<uint32_t>(attribIndex), binding, attrib);
        }
        else
        {
            vk::PackedVertexInputBindingDesc &bindingDesc = mPackedInputBindings[attribIndex];
            bindingDesc.stride                            = 0;
            bindingDesc.inputRate                         = VK_VERTEX_INPUT_RATE_VERTEX;

            vk::PackedVertexInputAttributeDesc &attribDesc = mPackedInputAttributes[attribIndex];
            attribDesc.format   = static_cast<uint16_t>(VK_FORMAT_R32G32B32A32_SFLOAT);
            attribDesc.location = static_cast<uint16_t>(attribIndex);
            attribDesc.offset   = 0;
        }
    }

    mDirtyPackedInputs.reset();
}

void VertexArrayVk::updatePackedInputInfo(uint32_t attribIndex,
                                          const gl::VertexBinding &binding,
                                          const gl::VertexAttribute &attrib)
{
    vk::PackedVertexInputBindingDesc &bindingDesc = mPackedInputBindings[attribIndex];

    bindingDesc.stride    = static_cast<uint16_t>(mCurrentArrayBufferStrides[attribIndex]);
    bindingDesc.inputRate = static_cast<uint16_t>(
        binding.getDivisor() > 0 ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX);

    VkFormat vkFormat = mCurrentArrayBufferFormats[attribIndex]->vkBufferFormat;
    ASSERT(vkFormat <= std::numeric_limits<uint16_t>::max());
    if (vkFormat == VK_FORMAT_UNDEFINED)
    {
        // TODO(fjhenigman): Add support for vertex data format.  anglebug.com/2405
        UNIMPLEMENTED();
    }

    vk::PackedVertexInputAttributeDesc &attribDesc = mPackedInputAttributes[attribIndex];
    attribDesc.format                              = static_cast<uint16_t>(vkFormat);
    attribDesc.location                            = static_cast<uint16_t>(attribIndex);
    attribDesc.offset                              = static_cast<uint32_t>(attrib.relativeOffset);
}

angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context,
                                                 const gl::DrawCallParams &drawCallParams)
{
    ContextVk *contextVk = vk::GetImpl(context);
    const gl::AttributesMask &clientAttribs = context->getStateCache().getActiveClientAttribsMask();

    ASSERT(clientAttribs.any());
    ANGLE_TRY_HANDLE(context, drawCallParams.ensureIndexRangeResolved(context));

    mDynamicVertexData.releaseRetainedBuffers(contextVk->getRenderer());

    const auto &attribs  = mState.getVertexAttributes();
    const auto &bindings = mState.getVertexBindings();

    // TODO(fjhenigman): When we have a bunch of interleaved attributes, they end up
    // un-interleaved, wasting space and copying time.  Consider improving on that.
    for (size_t attribIndex : clientAttribs)
    {
        const gl::VertexAttribute &attrib = attribs[attribIndex];
        const gl::VertexBinding &binding  = bindings[attrib.bindingIndex];
        ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);

        const size_t bytesToAllocate =
            (drawCallParams.firstVertex() + drawCallParams.vertexCount()) *
            mCurrentArrayBufferStrides[attribIndex];
        const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer) +
                             drawCallParams.firstVertex() * binding.getStride();

        size_t destOffset = drawCallParams.firstVertex() * mCurrentArrayBufferStrides[attribIndex];

        // Only vertexCount() vertices will be used by the upcoming draw. so that is all we copy.
        // We allocate space for firstVertex() + vertexCount() so indexing will work.  If we
        // don't start at zero all the indices will be off.
        // TODO(fjhenigman): See if we can account for indices being off by adjusting the
        // offset, thus avoiding wasted memory.
        ANGLE_TRY(StreamVertexData(contextVk, &mDynamicVertexData, src, bytesToAllocate, destOffset,
                                   drawCallParams.vertexCount(), binding.getStride(),
                                   mCurrentArrayBufferFormats[attribIndex]->vertexLoadFunction,
                                   &mCurrentArrayBufferHandles[attribIndex],
                                   &mCurrentArrayBufferOffsets[attribIndex]));
    }

    return angle::Result::Continue();
}

angle::Result VertexArrayVk::handleLineLoop(ContextVk *contextVk,
                                            const gl::DrawCallParams &drawCallParams)
{
    if (drawCallParams.isDrawElements())
    {
        // Handle GL_LINE_LOOP drawElements.
        if (mDirtyLineLoopTranslation)
        {
            gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
            VkIndexType indexType          = gl_vk::GetIndexType(drawCallParams.type());

            if (!elementArrayBuffer)
            {
                ANGLE_TRY(mLineLoopHelper.getIndexBufferForClientElementArray(
                    contextVk, drawCallParams, &mCurrentElementArrayBufferHandle,
                    &mCurrentElementArrayBufferOffset));
            }
            else
            {
                // When using an element array buffer, 'indices' is an offset to the first element.
                intptr_t offset = reinterpret_cast<intptr_t>(drawCallParams.indices());
                BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
                ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
                    contextVk, elementArrayBufferVk, indexType, drawCallParams.indexCount(), offset,
                    &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
            }
        }

        // If we've had a drawArrays call with a line loop before, we want to make sure this is
        // invalidated the next time drawArrays is called since we use the same index buffer for
        // both calls.
        mLineLoopBufferFirstIndex.reset();
        mLineLoopBufferLastIndex.reset();
        return angle::Result::Continue();
    }

    // Note: Vertex indexes can be arbitrarily large.
    uint32_t clampedVertexCount = drawCallParams.getClampedVertexCount<uint32_t>();

    // Handle GL_LINE_LOOP drawArrays.
    size_t lastVertex = static_cast<size_t>(drawCallParams.firstVertex() + clampedVertexCount);
    if (!mLineLoopBufferFirstIndex.valid() || !mLineLoopBufferLastIndex.valid() ||
        mLineLoopBufferFirstIndex != drawCallParams.firstVertex() ||
        mLineLoopBufferLastIndex != lastVertex)
    {
        ANGLE_TRY(mLineLoopHelper.getIndexBufferForDrawArrays(contextVk, drawCallParams,
                                                              &mCurrentElementArrayBufferHandle,
                                                              &mCurrentElementArrayBufferOffset));

        mLineLoopBufferFirstIndex = drawCallParams.firstVertex();
        mLineLoopBufferLastIndex  = lastVertex;
    }

    return angle::Result::Continue();
}

angle::Result VertexArrayVk::updateIndexTranslation(ContextVk *contextVk,
                                                    const gl::DrawCallParams &drawCallParams)
{
    ASSERT(drawCallParams.isDrawElements());
    ASSERT(drawCallParams.mode() != gl::PrimitiveMode::LineLoop);

    gl::Buffer *glBuffer = mState.getElementArrayBuffer().get();

    if (!glBuffer)
    {
        ANGLE_TRY(streamIndexData(contextVk, drawCallParams.type(), drawCallParams.indexCount(),
                                  drawCallParams.indices(), &mDynamicIndexData));
    }
    else
    {
        ASSERT(drawCallParams.type() == GL_UNSIGNED_BYTE);
        // Unsigned bytes don't have direct support in Vulkan so we have to expand the
        // memory to a GLushort.
        BufferVk *bufferVk   = vk::GetImpl(glBuffer);
        void *srcDataMapping = nullptr;
        ASSERT(!glBuffer->isMapped());
        ANGLE_TRY(bufferVk->mapImpl(contextVk, &srcDataMapping));
        uint8_t *srcData           = static_cast<uint8_t *>(srcDataMapping);
        intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(drawCallParams.indices());
        srcData += offsetIntoSrcData;

        ANGLE_TRY(streamIndexData(contextVk, drawCallParams.type(),
                                  static_cast<size_t>(bufferVk->getSize()) - offsetIntoSrcData,
                                  srcData, &mTranslatedByteIndexData));

        ANGLE_TRY(bufferVk->unmapImpl(contextVk));
    }

    return angle::Result::Continue();
}

void VertexArrayVk::updateDefaultAttrib(RendererVk *renderer,
                                        size_t attribIndex,
                                        VkBuffer bufferHandle,
                                        uint32_t offset)
{
    if (!mState.getEnabledAttributesMask().test(attribIndex))
    {
        mCurrentArrayBufferHandles[attribIndex]   = bufferHandle;
        mCurrentArrayBufferOffsets[attribIndex]   = offset;
        mCurrentArrayBufferResources[attribIndex] = nullptr;
        mCurrentArrayBufferStrides[attribIndex]   = 0;
        mCurrentArrayBufferFormats[attribIndex] =
            &renderer->getFormat(angle::FormatID::R32G32B32A32_FIXED);
        mDirtyPackedInputs.set(attribIndex);
    }
}
}  // namespace rx
