| // 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 { |
| |
| 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. |
| 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() && 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_; |
| } |
| |
| 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 |
| |
| |