VertexStorageType ClassifyAttributeStorage(const gl::VertexAttribute &attrib)
{
    // If attribute is disabled, we use the current value.
    if (!attrib.enabled)
    {
        return VertexStorageType::CURRENT_VALUE;
    }

    // If specified with immediate data, we must use dynamic storage.
    auto *buffer = attrib.buffer.get();
    if (!buffer)
    {
        return VertexStorageType::DYNAMIC;
    }

    // Check if the buffer supports direct storage.
    if (DirectStoragePossible(attrib))
    {
        return VertexStorageType::DIRECT;
    }

    // Otherwise the storage is static or dynamic.
    BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);
    ASSERT(bufferD3D);
    switch (bufferD3D->getUsage())
    {
        case D3DBufferUsage::DYNAMIC:
            return VertexStorageType::DYNAMIC;
        case D3DBufferUsage::STATIC:
            return VertexStorageType::STATIC;
        default:
            UNREACHABLE();
            return VertexStorageType::UNKNOWN;
    }
}
angle::Result VertexDataManager::reserveSpaceForAttrib(const gl::Context *context,
                                                       const TranslatedAttribute &translatedAttrib,
                                                       GLint start,
                                                       size_t count,
                                                       GLsizei instances)
{
    ASSERT(translatedAttrib.attribute && translatedAttrib.binding);
    const auto &attrib  = *translatedAttrib.attribute;
    const auto &binding = *translatedAttrib.binding;

    ASSERT(!DirectStoragePossible(context, attrib, binding));

    gl::Buffer *buffer   = binding.getBuffer().get();
    BufferD3D *bufferD3D = buffer ? GetImplAs<BufferD3D>(buffer) : nullptr;
    ASSERT(!bufferD3D || bufferD3D->getStaticVertexBuffer(attrib, binding) == nullptr);

    size_t totalCount = gl::ComputeVertexBindingElementCount(binding.getDivisor(), count,
                                                             static_cast<size_t>(instances));
    // TODO([email protected]): force the index buffer to clamp any out of range indices instead
    // of invalid operation here.
    if (bufferD3D)
    {
        // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing
        // a non-instanced draw call
        GLint firstVertexIndex = binding.getDivisor() > 0 ? 0 : start;
        int64_t maxVertexCount =
            static_cast<int64_t>(firstVertexIndex) + static_cast<int64_t>(totalCount);
        int elementsInBuffer =
            ElementsInBuffer(attrib, binding, static_cast<unsigned int>(bufferD3D->getSize()));

        ANGLE_CHECK_HR(GetImplAs<ContextD3D>(context), maxVertexCount <= elementsInBuffer,
                       "Vertex buffer is not big enough for the draw call.", E_FAIL);
    }
    return mStreamingBuffer.reserveVertexSpace(context, attrib, binding, totalCount, instances);
}
// static
angle::Result VertexDataManager::StoreStaticAttrib(const gl::Context *context,
                                                   TranslatedAttribute *translated)
{
    ASSERT(translated->attribute && translated->binding);
    const auto &attrib  = *translated->attribute;
    const auto &binding = *translated->binding;

    gl::Buffer *buffer = binding.getBuffer().get();
    ASSERT(buffer && attrib.enabled && !DirectStoragePossible(context, attrib, binding));
    BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);

    // Compute source data pointer
    const uint8_t *sourceData = nullptr;
    const int offset          = static_cast<int>(ComputeVertexAttributeOffset(attrib, binding));

    ANGLE_TRY(bufferD3D->getData(context, &sourceData));
    sourceData += offset;

    unsigned int streamOffset = 0;

    translated->storage = nullptr;
    ANGLE_TRY(bufferD3D->getFactory()->getVertexSpaceRequired(context, attrib, binding, 1, 0,
                                                              &translated->stride));

    auto *staticBuffer = bufferD3D->getStaticVertexBuffer(attrib, binding);
    ASSERT(staticBuffer);

    if (staticBuffer->empty())
    {
        // Convert the entire buffer
        int totalCount =
            ElementsInBuffer(attrib, binding, static_cast<unsigned int>(bufferD3D->getSize()));
        int startIndex = offset / static_cast<int>(ComputeVertexAttributeStride(attrib, binding));

        ANGLE_TRY(staticBuffer->storeStaticAttribute(context, attrib, binding, -startIndex,
                                                     totalCount, 0, sourceData));
    }

    unsigned int firstElementOffset =
        (static_cast<unsigned int>(offset) /
         static_cast<unsigned int>(ComputeVertexAttributeStride(attrib, binding))) *
        translated->stride;

    VertexBuffer *vertexBuffer = staticBuffer->getVertexBuffer();

    CheckedNumeric<unsigned int> checkedOffset(streamOffset);
    checkedOffset += firstElementOffset;

    ANGLE_CHECK_HR_MATH(GetImplAs<ContextD3D>(context), checkedOffset.IsValid());

    translated->vertexBuffer.set(vertexBuffer);
    translated->serial = vertexBuffer->getSerial();
    translated->baseOffset = streamOffset + firstElementOffset;

    // Instanced vertices do not apply the 'start' offset
    translated->usesFirstVertexOffset = (binding.getDivisor() == 0);

    return angle::Result::Continue();
}
// static
void VertexDataManager::StoreDirectAttrib(TranslatedAttribute *directAttrib, GLint start)
{
    const auto &attrib   = *directAttrib->attribute;
    gl::Buffer *buffer   = attrib.buffer.get();
    BufferD3D *bufferD3D = buffer ? GetImplAs<BufferD3D>(buffer) : nullptr;

    // Instanced vertices do not apply the 'start' offset
    GLint firstVertexIndex = (attrib.divisor > 0 ? 0 : start);

    ASSERT(DirectStoragePossible(attrib));
    directAttrib->vertexBuffer.set(nullptr);
    directAttrib->storage = bufferD3D;
    directAttrib->serial  = bufferD3D->getSerial();
    directAttrib->stride = static_cast<unsigned int>(ComputeVertexAttributeStride(attrib));
    directAttrib->offset =
        static_cast<unsigned int>(attrib.offset + directAttrib->stride * firstVertexIndex);
}
gl::Error VertexDataManager::reserveSpaceForAttrib(const TranslatedAttribute &translatedAttrib,
                                                   GLsizei count,
                                                   GLsizei instances) const
{
    const gl::VertexAttribute &attrib = *translatedAttrib.attribute;
    ASSERT(!DirectStoragePossible(attrib));

    gl::Buffer *buffer   = attrib.buffer.get();
    BufferD3D *bufferD3D = buffer ? GetImplAs<BufferD3D>(buffer) : nullptr;
    ASSERT(!bufferD3D || bufferD3D->getStaticVertexBuffer(attrib) == nullptr);
    UNUSED_ASSERTION_VARIABLE(bufferD3D);

    size_t totalCount = ComputeVertexAttributeElementCount(attrib, count, instances);
    ASSERT(!bufferD3D ||
           ElementsInBuffer(attrib, static_cast<unsigned int>(bufferD3D->getSize())) >=
               static_cast<int>(totalCount));

    return mStreamingBuffer->reserveVertexSpace(attrib, static_cast<GLsizei>(totalCount),
                                                instances);
}
// static
void VertexDataManager::StoreDirectAttrib(const gl::Context *context,
                                          TranslatedAttribute *directAttrib)
{
    ASSERT(directAttrib->attribute && directAttrib->binding);
    const auto &attrib  = *directAttrib->attribute;
    const auto &binding = *directAttrib->binding;

    gl::Buffer *buffer   = binding.getBuffer().get();
    ASSERT(buffer);
    BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);

    ASSERT(DirectStoragePossible(context, attrib, binding));
    directAttrib->vertexBuffer.set(nullptr);
    directAttrib->storage = bufferD3D;
    directAttrib->serial  = bufferD3D->getSerial();
    directAttrib->stride = static_cast<unsigned int>(ComputeVertexAttributeStride(attrib, binding));
    directAttrib->baseOffset =
        static_cast<unsigned int>(ComputeVertexAttributeOffset(attrib, binding));

    // Instanced vertices do not apply the 'start' offset
    directAttrib->usesFirstVertexOffset = (binding.getDivisor() == 0);
}
// static
gl::Error VertexDataManager::StoreStaticAttrib(TranslatedAttribute *translated,
                                               GLint start,
                                               GLsizei count,
                                               GLsizei instances)
{
    const gl::VertexAttribute &attrib = *translated->attribute;

    gl::Buffer *buffer = attrib.buffer.get();
    ASSERT(buffer && attrib.enabled && !DirectStoragePossible(attrib));
    BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);

    // Instanced vertices do not apply the 'start' offset
    GLint firstVertexIndex = (attrib.divisor > 0 ? 0 : start);

    // Compute source data pointer
    const uint8_t *sourceData = nullptr;

    gl::Error error = bufferD3D->getData(&sourceData);
    if (error.isError())
    {
        return error;
    }
    sourceData += static_cast<int>(attrib.offset);

    unsigned int streamOffset = 0;

    auto errorOrOutputElementSize = bufferD3D->getFactory()->getVertexSpaceRequired(attrib, 1, 0);
    if (errorOrOutputElementSize.isError())
    {
        return errorOrOutputElementSize.getError();
    }

    translated->storage = nullptr;
    translated->stride  = errorOrOutputElementSize.getResult();

    auto *staticBuffer = bufferD3D->getStaticVertexBuffer(attrib);
    ASSERT(staticBuffer);

    if (staticBuffer->empty())
    {
        // Convert the entire buffer
        int totalCount = ElementsInBuffer(attrib, static_cast<unsigned int>(bufferD3D->getSize()));
        int startIndex = static_cast<int>(attrib.offset) /
                         static_cast<int>(ComputeVertexAttributeStride(attrib));

        error = staticBuffer->storeStaticAttribute(attrib, -startIndex, totalCount, 0, sourceData);
        if (error.isError())
        {
            return error;
        }
    }

    unsigned int firstElementOffset =
        (static_cast<unsigned int>(attrib.offset) /
         static_cast<unsigned int>(ComputeVertexAttributeStride(attrib))) *
        translated->stride;
    ASSERT(attrib.divisor == 0 || firstVertexIndex == 0);
    unsigned int startOffset = firstVertexIndex * translated->stride;
    if (streamOffset + firstElementOffset + startOffset < streamOffset)
    {
        return gl::Error(GL_OUT_OF_MEMORY);
    }

    VertexBuffer *vertexBuffer = staticBuffer->getVertexBuffer();

    translated->vertexBuffer.set(vertexBuffer);
    translated->serial = vertexBuffer->getSerial();
    translated->offset = streamOffset + firstElementOffset + startOffset;

    return gl::Error(GL_NO_ERROR);
}