// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/client/vertex_array_object_manager.h"

#include <stddef.h>
#include <stdint.h>

#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"

namespace gpu {
namespace gles2 {

template <typename T>
static T RoundUpToMultipleOf4(T size) {
  return (size + 3) & ~3;
}

// This class tracks VertexAttribPointers and helps emulate client side buffers.
//
// The way client side buffers work is we shadow all the Vertex Attribs so we
// know which ones are pointing to client side buffers.
//
// At Draw time, for any attribs pointing to client side buffers we copy them
// to a special VBO and reset the actual vertex attrib pointers to point to this
// VBO.
//
// This also means we have to catch calls to query those values so that when
// an attrib is a client side buffer we pass the info back the user expects.

class GLES2_IMPL_EXPORT VertexArrayObject {
 public:
  // Info about Vertex Attributes. This is used to track what the user currently
  // has bound on each Vertex Attribute so we can simulate client side buffers
  // at glDrawXXX time.
  class VertexAttrib {
   public:
    VertexAttrib()
        : enabled_(false),
          buffer_id_(0),
          size_(4),
          type_(GL_FLOAT),
          normalized_(GL_FALSE),
          pointer_(nullptr),
          gl_stride_(0),
          divisor_(0),
          integer_(GL_FALSE) {}

    bool enabled() const {
      return enabled_;
    }

    void set_enabled(bool enabled) {
      enabled_ = enabled;
    }

    GLuint buffer_id() const {
      return buffer_id_;
    }

    void set_buffer_id(GLuint id) {
      buffer_id_ = id;
    }

    GLenum type() const {
      return type_;
    }

    GLint size() const {
      return size_;
    }

    GLsizei stride() const {
      return gl_stride_;
    }

    GLboolean normalized() const {
      return normalized_;
    }

    const GLvoid* pointer() const {
      return pointer_;
    }

    bool IsClientSide() const {
      return buffer_id_ == 0;
    }

    GLuint divisor() const {
      return divisor_;
    }

    GLboolean integer() const {
      return integer_;
    }

    void SetInfo(
        GLuint buffer_id,
        GLint size,
        GLenum type,
        GLboolean normalized,
        GLsizei gl_stride,
        const GLvoid* pointer,
        GLboolean integer) {
      buffer_id_ = buffer_id;
      size_ = size;
      type_ = type;
      normalized_ = normalized;
      gl_stride_ = gl_stride;
      pointer_ = pointer;
      integer_ = integer;
    }

    void SetDivisor(GLuint divisor) {
      divisor_ = divisor;
    }

   private:
    // Whether or not this attribute is enabled.
    bool enabled_;

    // The id of the buffer. 0 = client side buffer.
    GLuint buffer_id_;

    // Number of components (1, 2, 3, 4).
    GLint size_;

    // GL_BYTE, GL_FLOAT, etc. See glVertexAttribPointer.
    GLenum type_;

    // GL_TRUE or GL_FALSE
    GLboolean normalized_;

    // The pointer/offset into the buffer.
    raw_ptr<const GLvoid> pointer_;

    // The stride that will be used to access the buffer. This is the bogus GL
    // stride where 0 = compute the stride based on size and type.
    GLsizei gl_stride_;

    // Divisor, for geometry instancing.
    GLuint divisor_;

    GLboolean integer_;
  };

  typedef std::vector<VertexAttrib> VertexAttribs;

  explicit VertexArrayObject(GLuint max_vertex_attribs);

  VertexArrayObject(const VertexArrayObject&) = delete;
  VertexArrayObject& operator=(const VertexArrayObject&) = delete;

  void UnbindBuffer(GLuint id);

  bool BindElementArray(GLuint id);

  bool HaveEnabledClientSideBuffers() const;

  void SetAttribEnable(GLuint index, bool enabled);

  void SetAttribPointer(
    GLuint buffer_id,
    GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
    const void* ptr, GLboolean integer);

  bool GetVertexAttrib(GLuint index, GLenum pname, uint32_t* param) const;

  void SetAttribDivisor(GLuint index, GLuint divisor);

  bool GetAttribPointer(GLuint index, GLenum pname, void** ptr) const;

  const VertexAttribs& vertex_attribs() const {
    return vertex_attribs_;
  }

  GLuint bound_element_array_buffer() const {
    return bound_element_array_buffer_id_;
  }

 private:
  const VertexAttrib* GetAttrib(GLuint index) const;

  int num_client_side_pointers_enabled_;

  // The currently bound element array buffer.
  GLuint bound_element_array_buffer_id_;

  VertexAttribs vertex_attribs_;
};

VertexArrayObject::VertexArrayObject(GLuint max_vertex_attribs)
    : num_client_side_pointers_enabled_(0),
      bound_element_array_buffer_id_(0) {
  vertex_attribs_.resize(max_vertex_attribs);
}

void VertexArrayObject::UnbindBuffer(GLuint id) {
  if (id == 0) {
    return;
  }
  for (size_t ii = 0; ii < vertex_attribs_.size(); ++ii) {
    VertexAttrib& attrib = vertex_attribs_[ii];
    if (attrib.buffer_id() == id) {
      attrib.set_buffer_id(0);
      if (attrib.enabled()) {
        ++num_client_side_pointers_enabled_;
      }
    }
  }
  if (bound_element_array_buffer_id_ == id) {
    bound_element_array_buffer_id_ = 0;
  }
}

bool VertexArrayObject::BindElementArray(GLuint id) {
  if (id == bound_element_array_buffer_id_) {
    return false;
  }
  bound_element_array_buffer_id_ = id;
  return true;
}
bool VertexArrayObject::HaveEnabledClientSideBuffers() const {
  return num_client_side_pointers_enabled_ > 0;
}

void VertexArrayObject::SetAttribEnable(GLuint index, bool enabled) {
  if (index < vertex_attribs_.size()) {
    VertexAttrib& attrib = vertex_attribs_[index];
    if (attrib.enabled() != enabled) {
      if (attrib.IsClientSide()) {
        num_client_side_pointers_enabled_ += enabled ? 1 : -1;
        DCHECK_GE(num_client_side_pointers_enabled_, 0);
      }
      attrib.set_enabled(enabled);
    }
  }
}

void VertexArrayObject::SetAttribPointer(
    GLuint buffer_id,
    GLuint index,
    GLint size,
    GLenum type,
    GLboolean normalized,
    GLsizei stride,
    const void* ptr,
    GLboolean integer) {
  if (index < vertex_attribs_.size()) {
    VertexAttrib& attrib = vertex_attribs_[index];
    if (attrib.IsClientSide() && attrib.enabled()) {
      --num_client_side_pointers_enabled_;
      DCHECK_GE(num_client_side_pointers_enabled_, 0);
    }

    attrib.SetInfo(buffer_id, size, type, normalized, stride, ptr, integer);

    if (attrib.IsClientSide() && attrib.enabled()) {
      ++num_client_side_pointers_enabled_;
    }
  }
}

bool VertexArrayObject::GetVertexAttrib(GLuint index,
                                        GLenum pname,
                                        uint32_t* param) const {
  const VertexAttrib* attrib = GetAttrib(index);
  if (!attrib) {
    return false;
  }

  switch (pname) {
    case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
      *param = attrib->buffer_id();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
      *param = attrib->enabled();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_SIZE:
      *param = attrib->size();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
      *param = attrib->stride();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_TYPE:
      *param = attrib->type();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
      *param = attrib->normalized();
      break;
    case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
      *param = attrib->integer();
      break;
    default:
      return false;  // pass through to service side.
  }
  return true;
}

void VertexArrayObject::SetAttribDivisor(GLuint index, GLuint divisor) {
  if (index < vertex_attribs_.size()) {
    VertexAttrib& attrib = vertex_attribs_[index];
    attrib.SetDivisor(divisor);
  }
}

// Gets the Attrib pointer for an attrib but only if it's a client side
// pointer. Returns true if it got the pointer.
bool VertexArrayObject::GetAttribPointer(
    GLuint index, GLenum pname, void** ptr) const {
  const VertexAttrib* attrib = GetAttrib(index);
  if (attrib && pname == GL_VERTEX_ATTRIB_ARRAY_POINTER) {
    *ptr = const_cast<void*>(attrib->pointer());
    return true;
  }
  return false;
}

// Gets an attrib if it's in range and it's client side.
const VertexArrayObject::VertexAttrib* VertexArrayObject::GetAttrib(
    GLuint index) const {
  if (index < vertex_attribs_.size()) {
    const VertexAttrib* attrib = &vertex_attribs_[index];
    return attrib;
  }
  return nullptr;
}

VertexArrayObjectManager::VertexArrayObjectManager(
    GLuint max_vertex_attribs,
    GLuint array_buffer_id,
    GLuint element_array_buffer_id,
    bool support_client_side_arrays)
    : max_vertex_attribs_(max_vertex_attribs),
      array_buffer_id_(array_buffer_id),
      array_buffer_size_(0),
      array_buffer_offset_(0),
      element_array_buffer_id_(element_array_buffer_id),
      element_array_buffer_size_(0),
      collection_buffer_size_(0),
      default_vertex_array_object_(
          std::make_unique<VertexArrayObject>(max_vertex_attribs)),
      bound_vertex_array_object_(default_vertex_array_object_.get()),
      support_client_side_arrays_(support_client_side_arrays) {}

VertexArrayObjectManager::~VertexArrayObjectManager() = default;

bool VertexArrayObjectManager::IsReservedId(GLuint id) const {
  return (id != 0 &&
          (id == array_buffer_id_ || id == element_array_buffer_id_));
}

GLuint VertexArrayObjectManager::bound_element_array_buffer() const {
  return bound_vertex_array_object_->bound_element_array_buffer();
}

void VertexArrayObjectManager::UnbindBuffer(GLuint id) {
  bound_vertex_array_object_->UnbindBuffer(id);
}

bool VertexArrayObjectManager::BindElementArray(GLuint id) {
  return  bound_vertex_array_object_->BindElementArray(id);
}

void VertexArrayObjectManager::GenVertexArrays(
    GLsizei n, const GLuint* arrays) {
  DCHECK_GE(n, 0);
  for (GLsizei i = 0; i < n; ++i) {
    std::pair<VertexArrayObjectMap::iterator, bool> result =
        vertex_array_objects_.insert(std::make_pair(
            arrays[i],
            std::make_unique<VertexArrayObject>(max_vertex_attribs_)));
    DCHECK(result.second);
  }
}

void VertexArrayObjectManager::DeleteVertexArrays(
    GLsizei n, const GLuint* arrays) {
  DCHECK_GE(n, 0);
  for (GLsizei i = 0; i < n; ++i) {
    GLuint id = arrays[i];
    if (id) {
      VertexArrayObjectMap::iterator it = vertex_array_objects_.find(id);
      if (it != vertex_array_objects_.end()) {
        if (bound_vertex_array_object_ == it->second.get()) {
          bound_vertex_array_object_ = default_vertex_array_object_.get();
        }
        vertex_array_objects_.erase(it);
      }
    }
  }
}

bool VertexArrayObjectManager::BindVertexArray(GLuint array, bool* changed) {
  *changed = false;
  VertexArrayObject* vertex_array_object = default_vertex_array_object_.get();
  if (array != 0) {
    VertexArrayObjectMap::iterator it = vertex_array_objects_.find(array);
    if (it == vertex_array_objects_.end()) {
      return false;
    }
    vertex_array_object = it->second.get();
  }
  *changed = vertex_array_object != bound_vertex_array_object_;
  bound_vertex_array_object_ = vertex_array_object;
  return true;
}

bool VertexArrayObjectManager::HaveEnabledClientSideBuffers() const {
  return bound_vertex_array_object_->HaveEnabledClientSideBuffers();
}

void VertexArrayObjectManager::SetAttribEnable(GLuint index, bool enabled) {
  bound_vertex_array_object_->SetAttribEnable(index, enabled);
}

bool VertexArrayObjectManager::GetVertexAttrib(GLuint index,
                                               GLenum pname,
                                               uint32_t* param) {
  return bound_vertex_array_object_->GetVertexAttrib(index, pname, param);
}

bool VertexArrayObjectManager::GetAttribPointer(
    GLuint index, GLenum pname, void** ptr) const {
  return bound_vertex_array_object_->GetAttribPointer(index, pname, ptr);
}

bool VertexArrayObjectManager::SetAttribPointer(
    GLuint buffer_id,
    GLuint index,
    GLint size,
    GLenum type,
    GLboolean normalized,
    GLsizei stride,
    const void* ptr,
    GLboolean integer) {
  // Client side arrays are not allowed in vaos.
  if (buffer_id == 0 && !IsDefaultVAOBound() && ptr) {
    return false;
  }
  bound_vertex_array_object_->SetAttribPointer(
      buffer_id, index, size, type, normalized, stride, ptr, integer);
  return true;
}

void VertexArrayObjectManager::SetAttribDivisor(GLuint index, GLuint divisor) {
  bound_vertex_array_object_->SetAttribDivisor(index, divisor);
}

// Collects the data into the collection buffer and returns the number of
// bytes collected.
GLsizei VertexArrayObjectManager::CollectData(
    const void* data,
    GLsizei bytes_per_element,
    GLsizei real_stride,
    GLsizei num_elements) {
  GLsizei bytes_needed = bytes_per_element * num_elements;
  if (collection_buffer_size_ < bytes_needed) {
    collection_buffer_.reset(new int8_t[bytes_needed]);
    collection_buffer_size_ = bytes_needed;
  }
  const int8_t* src = static_cast<const int8_t*>(data);
  int8_t* dst = collection_buffer_.get();
  int8_t* end = dst + bytes_per_element * num_elements;
  for (; dst < end; src += real_stride, dst += bytes_per_element) {
    memcpy(dst, src, bytes_per_element);
  }
  return bytes_needed;
}

bool VertexArrayObjectManager::IsDefaultVAOBound() const {
  return bound_vertex_array_object_ == default_vertex_array_object_.get();
}

bool VertexArrayObjectManager::SupportsClientSideBuffers() {
  return support_client_side_arrays_ &&
         bound_vertex_array_object_->HaveEnabledClientSideBuffers();
}

// Returns true if buffers were setup.
bool VertexArrayObjectManager::SetupSimulatedClientSideBuffers(
    const char* function_name,
    GLES2Implementation* gl,
    GLES2CmdHelper* gl_helper,
    GLsizei num_elements,
    GLsizei primcount,
    bool* simulated) {
  *simulated = false;
  if (!SupportsClientSideBuffers())
    return false;

  if (!IsDefaultVAOBound()) {
    gl->SetGLError(
        GL_INVALID_OPERATION, function_name,
        "client side arrays not allowed with vertex array object");
    return false;
  }
  *simulated = true;
  base::CheckedNumeric<GLsizei> checked_total_size = 0;
  // Compute the size of the buffer we need.
  const VertexArrayObject::VertexAttribs& vertex_attribs =
      bound_vertex_array_object_->vertex_attribs();
  for (GLuint ii = 0; ii < vertex_attribs.size(); ++ii) {
    const VertexArrayObject::VertexAttrib& attrib = vertex_attribs[ii];
    if (attrib.IsClientSide() && attrib.enabled()) {
      uint32_t bytes_per_element =
          GLES2Util::GetGroupSizeForBufferType(attrib.size(), attrib.type());
      GLsizei elements = (primcount && attrib.divisor() > 0) ?
          ((primcount - 1) / attrib.divisor() + 1) : num_elements;
      checked_total_size +=
          RoundUpToMultipleOf4(base::CheckMul(bytes_per_element, elements));
    }
  }
  GLsizei total_size = 0;
  if (!checked_total_size.AssignIfValid(&total_size)) {
    gl->SetGLError(GL_INVALID_OPERATION, function_name,
                   "size overflow for client side arrays");
    return false;
  }
  gl_helper->BindBuffer(GL_ARRAY_BUFFER, array_buffer_id_);
  array_buffer_offset_ = 0;
  if (total_size > array_buffer_size_) {
    gl->BufferDataHelper(GL_ARRAY_BUFFER, total_size, nullptr, GL_DYNAMIC_DRAW);
    array_buffer_size_ = total_size;
  }
  for (GLuint ii = 0; ii < vertex_attribs.size(); ++ii) {
    const VertexArrayObject::VertexAttrib& attrib = vertex_attribs[ii];
    if (attrib.IsClientSide() && attrib.enabled()) {
      uint32_t bytes_per_element =
          GLES2Util::GetGroupSizeForBufferType(attrib.size(), attrib.type());
      GLsizei real_stride = attrib.stride() ?
          attrib.stride() : static_cast<GLsizei>(bytes_per_element);
      GLsizei elements = (primcount && attrib.divisor() > 0) ?
          ((primcount - 1) / attrib.divisor() + 1) : num_elements;
      GLsizei bytes_collected = CollectData(
          attrib.pointer(), bytes_per_element, real_stride, elements);
      gl->BufferSubDataHelper(
          GL_ARRAY_BUFFER, array_buffer_offset_, bytes_collected,
          collection_buffer_.get());
      gl_helper->VertexAttribPointer(
          ii, attrib.size(), attrib.type(), attrib.normalized(), 0,
          array_buffer_offset_);
      array_buffer_offset_ += RoundUpToMultipleOf4(bytes_collected);
      DCHECK_LE(array_buffer_offset_, array_buffer_size_);
    }
  }
  return true;
}

// Copies in indices to the service and returns the highest index accessed + 1
bool VertexArrayObjectManager::SetupSimulatedIndexAndClientSideBuffers(
    const char* function_name,
    GLES2Implementation* gl,
    GLES2CmdHelper* gl_helper,
    GLsizei count,
    GLenum type,
    GLsizei primcount,
    const void* indices,
    GLuint* offset,
    bool* simulated) {
  *simulated = false;
  *offset = ToGLuint(indices);
  if (!support_client_side_arrays_)
    return true;
  GLsizei num_elements = 0;
  if (bound_vertex_array_object_->bound_element_array_buffer() == 0) {
    *simulated = true;
    *offset = 0;
    GLsizei max_index = -1;
    switch (type) {
      case GL_UNSIGNED_BYTE: {
        const uint8_t* src = static_cast<const uint8_t*>(indices);
        for (GLsizei ii = 0; ii < count; ++ii) {
          if (src[ii] > max_index) {
            max_index = src[ii];
          }
        }
        break;
      }
      case GL_UNSIGNED_SHORT: {
        const uint16_t* src = static_cast<const uint16_t*>(indices);
        for (GLsizei ii = 0; ii < count; ++ii) {
          if (src[ii] > max_index) {
            max_index = src[ii];
          }
        }
        break;
      }
      case GL_UNSIGNED_INT: {
        uint32_t max_glsizei =
            static_cast<uint32_t>(std::numeric_limits<GLsizei>::max());
        const uint32_t* src = static_cast<const uint32_t*>(indices);
        for (GLsizei ii = 0; ii < count; ++ii) {
          // Other parts of the API use GLsizei (signed) to store limits.
          // As such, if we encounter a index that cannot be represented with
          // an unsigned int we need to flag it as an error here.
          if(src[ii] > max_glsizei) {
            gl->SetGLError(
                GL_INVALID_OPERATION, function_name, "index too large.");
            return false;
          }
          GLsizei signed_index = static_cast<GLsizei>(src[ii]);
          if (signed_index > max_index) {
            max_index = signed_index;
          }
        }
        break;
      }
      default:
        break;
    }
    gl_helper->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_buffer_id_);
    uint32_t bytes_per_element = GLES2Util::GetGLTypeSizeForBuffers(type);
    GLsizei bytes_needed = 0;
    if (!base::CheckMul(bytes_per_element, count)
             .AssignIfValid(&bytes_needed)) {
      gl->SetGLError(GL_INVALID_OPERATION, function_name,
                     "size overflow for client side index arrays");
      return false;
    }
    if (bytes_needed > element_array_buffer_size_) {
      element_array_buffer_size_ = bytes_needed;
      gl->BufferDataHelper(GL_ELEMENT_ARRAY_BUFFER, bytes_needed, nullptr,
                           GL_DYNAMIC_DRAW);
    }
    gl->BufferSubDataHelper(
        GL_ELEMENT_ARRAY_BUFFER, 0, bytes_needed, indices);

    num_elements = max_index + 1;
  } else if (bound_vertex_array_object_->HaveEnabledClientSideBuffers()) {
    // Index buffer is GL buffer. Ask the service for the highest vertex
    // that will be accessed. Note: It doesn't matter if another context
    // changes the contents of any of the buffers. The service will still
    // validate the indices. We just need to know how much to copy across.
    num_elements = gl->GetMaxValueInBufferCHROMIUMHelper(
        bound_vertex_array_object_->bound_element_array_buffer(),
        count, type, ToGLuint(indices)) + 1;
  }

  bool simulated_client_side_buffers = false;
  SetupSimulatedClientSideBuffers(
      function_name, gl, gl_helper, num_elements, primcount,
      &simulated_client_side_buffers);
  *simulated = *simulated || simulated_client_side_buffers;
  return true;
}

}  // namespace gles2
}  // namespace gpu