//
// Copyright 2017 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.
//
// params:
//   Parameter wrapper structs for OpenGL ES. These helpers cache re-used values
//   in entry point routines.

#include "libANGLE/params.h"

#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/VertexArray.h"

namespace gl
{

// static
constexpr ParamTypeInfo ParamsBase::TypeInfo;
constexpr ParamTypeInfo DrawCallParams::TypeInfo;

// DrawCallParams implementation.
// Called by DrawArrays.
DrawCallParams::DrawCallParams(PrimitiveMode mode,
                               GLint firstVertex,
                               GLsizei vertexCount,
                               GLsizei instances)
    : mMode(mode),
      mFirstVertex(firstVertex),
      mVertexCount(vertexCount),
      mIndexCount(0),
      mBaseVertex(0),
      mType(GL_NONE),
      mIndices(nullptr),
      mInstances(instances),
      mIndirect(nullptr)
{
}

// Called by DrawElements.
DrawCallParams::DrawCallParams(PrimitiveMode mode,
                               GLint indexCount,
                               GLenum type,
                               const void *indices,
                               GLint baseVertex,
                               GLsizei instances)
    : mMode(mode),
      mFirstVertex(0),
      mVertexCount(0),
      mIndexCount(indexCount),
      mBaseVertex(baseVertex),
      mType(type),
      mIndices(indices),
      mInstances(instances),
      mIndirect(nullptr)
{
}

// Called by DrawArraysIndirect.
DrawCallParams::DrawCallParams(PrimitiveMode mode, const void *indirect)
    : mMode(mode),
      mFirstVertex(0),
      mVertexCount(0),
      mIndexCount(0),
      mBaseVertex(0),
      mType(GL_NONE),
      mIndices(nullptr),
      mInstances(0),
      mIndirect(indirect)
{
}

// Called by DrawElementsIndirect.
DrawCallParams::DrawCallParams(PrimitiveMode mode, GLenum type, const void *indirect)
    : mMode(mode),
      mFirstVertex(0),
      mVertexCount(0),
      mIndexCount(0),
      mBaseVertex(0),
      mType(type),
      mIndices(nullptr),
      mInstances(0),
      mIndirect(indirect)
{
}

GLsizei DrawCallParams::indexCount() const
{
    ASSERT(isDrawElements());
    return mIndexCount;
}

GLint DrawCallParams::baseVertex() const
{
    return mBaseVertex;
}

GLenum DrawCallParams::type() const
{
    ASSERT(isDrawElements());
    return mType;
}

const void *DrawCallParams::indices() const
{
    return mIndices;
}

GLsizei DrawCallParams::instances() const
{
    return mInstances;
}

const void *DrawCallParams::indirect() const
{
    return mIndirect;
}

bool DrawCallParams::isDrawIndirect() const
{
    // This is a bit of a hack - it's quite possible for a direct call to have a zero count, but we
    // assume these calls are filtered out before they make it to this code.
    return (mIndexCount == 0 && mVertexCount == 0);
}

Error DrawCallParams::ensureIndexRangeResolved(const Context *context) const
{
    if (mIndexRange.valid() || !isDrawElements())
    {
        return NoError();
    }

    const State &state = context->getGLState();

    const gl::VertexArray *vao     = state.getVertexArray();
    gl::Buffer *elementArrayBuffer = vao->getElementArrayBuffer().get();

    if (elementArrayBuffer)
    {
        uintptr_t offset = reinterpret_cast<uintptr_t>(mIndices);
        IndexRange indexRange;
        ANGLE_TRY(elementArrayBuffer->getIndexRange(context, mType, static_cast<size_t>(offset),
                                                    mIndexCount, state.isPrimitiveRestartEnabled(),
                                                    &indexRange));
        mIndexRange = indexRange;
    }
    else
    {
        mIndexRange =
            ComputeIndexRange(mType, mIndices, mIndexCount, state.isPrimitiveRestartEnabled());
    }

    const IndexRange &indexRange = mIndexRange.value();

    // The entire index range should be within the limits of a 32-bit uint because the largest GL
    // index type is GL_UNSIGNED_INT.
    ASSERT(indexRange.start <= std::numeric_limits<uint32_t>::max() &&
           indexRange.end <= std::numeric_limits<uint32_t>::max());

    // Given the assertion above and the type of mBaseVertex (GLint), adding them both as 64-bit
    // ints is safe.
    int64_t startVertexInt64 =
        static_cast<int64_t>(mBaseVertex) + static_cast<int64_t>(indexRange.start);

    // OpenGL ES 3.2 spec section 10.5: "Behavior of DrawElementsOneInstance is undefined if the
    // vertex ID is negative for any element"
    if (startVertexInt64 < 0)
    {
        return gl::InternalError() << "Negative index value.";
    }

    // OpenGL ES 3.2 spec section 10.5: "If the vertex ID is larger than the maximum value
    // representable by type, it should behave as if the calculation were upconverted to 32-bit
    // unsigned integers(with wrapping on overflow conditions)." ANGLE does not fully handle these
    // rules, an overflow error is returned if the start vertex cannot be stored in a 32-bit signed
    // integer.
    if (startVertexInt64 > std::numeric_limits<GLint>::max())
    {
        return gl::InternalError() << "Negative value overflow.";
    }

    mFirstVertex = static_cast<GLint>(startVertexInt64);
    mVertexCount = indexRange.vertexCount();

    return NoError();
}

const IndexRange &DrawCallParams::getIndexRange() const
{
    ASSERT(isDrawElements() && mIndexRange.valid());
    return mIndexRange.value();
}

}  // namespace gl
