//
// Copyright (c) 2002-2012 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.
//

// VertexBuffer.cpp: Defines the abstract VertexBuffer class and VertexBufferInterface
// class with derivations, classes that perform graphics API agnostic vertex buffer operations.

#include "libGLESv2/renderer/VertexBuffer.h"
#include "libGLESv2/renderer/Renderer.h"
#include "libGLESv2/Context.h"

namespace rx
{

unsigned int VertexBuffer::mNextSerial = 1;

VertexBuffer::VertexBuffer()
{
    updateSerial();
}

VertexBuffer::~VertexBuffer()
{
}

void VertexBuffer::updateSerial()
{
    mSerial = mNextSerial++;
}

unsigned int VertexBuffer::getSerial() const
{
    return mSerial;
}

VertexBufferInterface::VertexBufferInterface(rx::Renderer *renderer, bool dynamic) : mRenderer(renderer)
{
    mDynamic = dynamic;
    mWritePosition = 0;
    mReservedSpace = 0;

    mVertexBuffer = renderer->createVertexBuffer();
}

VertexBufferInterface::~VertexBufferInterface()
{
    delete mVertexBuffer;
}

unsigned int VertexBufferInterface::getSerial() const
{
    return mVertexBuffer->getSerial();
}

unsigned int VertexBufferInterface::getBufferSize() const
{
    return mVertexBuffer->getBufferSize();
}

bool VertexBufferInterface::setBufferSize(unsigned int size)
{
    if (mVertexBuffer->getBufferSize() == 0)
    {
        return mVertexBuffer->initialize(size, mDynamic);
    }
    else
    {
        return mVertexBuffer->setBufferSize(size);
    }
}

unsigned int VertexBufferInterface::getWritePosition() const
{
    return mWritePosition;
}

void VertexBufferInterface::setWritePosition(unsigned int writePosition)
{
    mWritePosition = writePosition;
}

bool VertexBufferInterface::discard()
{
    return mVertexBuffer->discard();
}

int VertexBufferInterface::storeVertexAttributes(const gl::VertexAttribute &attrib, GLint start, GLsizei count, GLsizei instances)
{
    if (!reserveSpace(mReservedSpace))
    {
        return -1;
    }
    mReservedSpace = 0;

    if (!mVertexBuffer->storeVertexAttributes(attrib, start, count, instances, mWritePosition))
    {
        return -1;
    }

    int oldWritePos = static_cast<int>(mWritePosition);
    mWritePosition += mVertexBuffer->getSpaceRequired(attrib, count, instances);

    return oldWritePos;
}

int VertexBufferInterface::storeRawData(const void* data, unsigned int size)
{
    if (!reserveSpace(mReservedSpace))
    {
        return -1;
    }
    mReservedSpace = 0;

    if (!mVertexBuffer->storeRawData(data, size, mWritePosition))
    {
        return -1;
    }

    int oldWritePos = static_cast<int>(mWritePosition);
    mWritePosition += size;

    return oldWritePos;
}

void VertexBufferInterface::reserveVertexSpace(const gl::VertexAttribute &attribute, GLsizei count, GLsizei instances)
{
    mReservedSpace += mVertexBuffer->getSpaceRequired(attribute, count, instances);
}

void VertexBufferInterface::reserveRawDataSpace(unsigned int size)
{
    mReservedSpace += size;
}

VertexBuffer* VertexBufferInterface::getVertexBuffer() const
{
    return mVertexBuffer;
}


StreamingVertexBufferInterface::StreamingVertexBufferInterface(rx::Renderer *renderer, std::size_t initialSize) : VertexBufferInterface(renderer, true)
{
    setBufferSize(initialSize);
}

StreamingVertexBufferInterface::~StreamingVertexBufferInterface()
{
}

bool StreamingVertexBufferInterface::reserveSpace(unsigned int size)
{
    bool result = true;
    unsigned int curBufferSize = getBufferSize();
    if (size > curBufferSize)
    {
        result = setBufferSize(std::max(size, 3 * curBufferSize / 2));
        setWritePosition(0);
    }
    else if (getWritePosition() + size > curBufferSize)
    {
        if (!discard())
        {
            return false;
        }
        setWritePosition(0);
    }

    return result;
}

StaticVertexBufferInterface::StaticVertexBufferInterface(rx::Renderer *renderer) : VertexBufferInterface(renderer, false)
{
}

StaticVertexBufferInterface::~StaticVertexBufferInterface()
{
}

int StaticVertexBufferInterface::lookupAttribute(const gl::VertexAttribute &attribute)
{
    for (unsigned int element = 0; element < mCache.size(); element++)
    {
        if (mCache[element].type == attribute.mType &&
            mCache[element].size == attribute.mSize &&
            mCache[element].stride == attribute.stride() &&
            mCache[element].normalized == attribute.mNormalized)
        {
            if (mCache[element].attributeOffset == attribute.mOffset % attribute.stride())
            {
                return mCache[element].streamOffset;
            }
        }
    }

    return -1;
}

bool StaticVertexBufferInterface::reserveSpace(unsigned int size)
{
    unsigned int curSize = getBufferSize();
    if (curSize == 0)
    {
        setBufferSize(size);
        return true;
    }
    else if (curSize >= size)
    {
        return true;
    }
    else
    {
        UNREACHABLE();   // Static vertex buffers can't be resized
        return false;
    }
}

int StaticVertexBufferInterface::storeVertexAttributes(const gl::VertexAttribute &attrib, GLint start, GLsizei count, GLsizei instances)
{
    int attributeOffset = attrib.mOffset % attrib.stride();
    VertexElement element = { attrib.mType, attrib.mSize, attrib.stride(), attrib.mNormalized, attributeOffset, getWritePosition() };
    mCache.push_back(element);

    return VertexBufferInterface::storeVertexAttributes(attrib, start, count, instances);
}

}
