| // 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 | |