blob: 4893115e11abcad08565dd388f69fa3727f8107c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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/logging.h"
#include "base/macros.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 {
static GLsizei RoundUpToMultipleOf4(GLsizei 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.
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);
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_;
DISALLOW_COPY_AND_ASSIGN(VertexArrayObject);
};
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_(new VertexArrayObject(max_vertex_attribs)),
bound_vertex_array_object_(default_vertex_array_object_),
support_client_side_arrays_(support_client_side_arrays) {
}
VertexArrayObjectManager::~VertexArrayObjectManager() {
for (VertexArrayObjectMap::iterator it = vertex_array_objects_.begin();
it != vertex_array_objects_.end(); ++it) {
delete it->second;
}
delete default_vertex_array_object_;
}
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], new 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) {
bound_vertex_array_object_ = default_vertex_array_object_;
}
delete it->second;
vertex_array_objects_.erase(it);
}
}
}
}
bool VertexArrayObjectManager::BindVertexArray(GLuint array, bool* changed) {
*changed = false;
VertexArrayObject* vertex_array_object = default_vertex_array_object_;
if (array != 0) {
VertexArrayObjectMap::iterator it = vertex_array_objects_.find(array);
if (it == vertex_array_objects_.end()) {
return false;
}
vertex_array_object = it->second;
}
*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()) {
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_;
}
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;
GLsizei 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()) {
size_t bytes_per_element =
GLES2Util::GetGroupSizeForBufferType(attrib.size(), attrib.type());
GLsizei elements = (primcount && attrib.divisor() > 0) ?
((primcount - 1) / attrib.divisor() + 1) : num_elements;
total_size += RoundUpToMultipleOf4(bytes_per_element * elements);
}
}
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()) {
size_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_);
GLsizei bytes_per_element = GLES2Util::GetGLTypeSizeForBuffers(type);
GLsizei bytes_needed = bytes_per_element * count;
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