| // |
| // Copyright 2015 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. |
| // |
| |
| // VertexArrayGL.cpp: Implements the class methods for VertexArrayGL. |
| |
| #include "libANGLE/renderer/gl/VertexArrayGL.h" |
| |
| #include "common/bitset_utils.h" |
| #include "common/debug.h" |
| #include "common/mathutil.h" |
| #include "common/utilities.h" |
| #include "libANGLE/Buffer.h" |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/formatutils.h" |
| #include "libANGLE/renderer/gl/BufferGL.h" |
| #include "libANGLE/renderer/gl/FunctionsGL.h" |
| #include "libANGLE/renderer/gl/StateManagerGL.h" |
| #include "libANGLE/renderer/gl/renderergl_utils.h" |
| |
| using namespace gl; |
| |
| namespace rx |
| { |
| namespace |
| { |
| // Warning: you should ensure binding really matches attrib.bindingIndex before using this function. |
| bool AttributeNeedsStreaming(const VertexAttribute &attrib, const VertexBinding &binding) |
| { |
| return (attrib.enabled && binding.buffer.get() == nullptr); |
| } |
| } // anonymous namespace |
| |
| VertexArrayGL::VertexArrayGL(const VertexArrayState &state, |
| const FunctionsGL *functions, |
| StateManagerGL *stateManager) |
| : VertexArrayImpl(state), |
| mFunctions(functions), |
| mStateManager(stateManager), |
| mVertexArrayID(0), |
| mAppliedElementArrayBuffer(), |
| mAppliedBindings(state.getMaxBindings()), |
| mStreamingElementArrayBufferSize(0), |
| mStreamingElementArrayBuffer(0), |
| mStreamingArrayBufferSize(0), |
| mStreamingArrayBuffer(0) |
| { |
| ASSERT(mFunctions); |
| ASSERT(mStateManager); |
| mFunctions->genVertexArrays(1, &mVertexArrayID); |
| |
| // Set the cached vertex attribute array and vertex attribute binding array size |
| GLuint maxVertexAttribs = static_cast<GLuint>(state.getMaxAttribs()); |
| for (GLuint i = 0; i < maxVertexAttribs; i++) |
| { |
| mAppliedAttributes.emplace_back(i); |
| } |
| } |
| |
| VertexArrayGL::~VertexArrayGL() |
| { |
| mStateManager->deleteVertexArray(mVertexArrayID); |
| mVertexArrayID = 0; |
| |
| mStateManager->deleteBuffer(mStreamingElementArrayBuffer); |
| mStreamingElementArrayBufferSize = 0; |
| mStreamingElementArrayBuffer = 0; |
| |
| mStateManager->deleteBuffer(mStreamingArrayBuffer); |
| mStreamingArrayBufferSize = 0; |
| mStreamingArrayBuffer = 0; |
| |
| mAppliedElementArrayBuffer.set(nullptr); |
| for (auto &binding : mAppliedBindings) |
| { |
| binding.buffer.set(nullptr); |
| } |
| } |
| |
| gl::Error VertexArrayGL::syncDrawArraysState(const gl::AttributesMask &activeAttributesMask, |
| GLint first, |
| GLsizei count, |
| GLsizei instanceCount) const |
| { |
| return syncDrawState(activeAttributesMask, first, count, GL_NONE, nullptr, instanceCount, false, |
| nullptr); |
| } |
| |
| gl::Error VertexArrayGL::syncDrawElementsState(const gl::AttributesMask &activeAttributesMask, |
| GLsizei count, |
| GLenum type, |
| const void *indices, |
| GLsizei instanceCount, |
| bool primitiveRestartEnabled, |
| const void **outIndices) const |
| { |
| return syncDrawState(activeAttributesMask, 0, count, type, indices, instanceCount, |
| primitiveRestartEnabled, outIndices); |
| } |
| |
| gl::Error VertexArrayGL::syncElementArrayState() const |
| { |
| gl::Buffer *elementArrayBuffer = mData.getElementArrayBuffer().get(); |
| ASSERT(elementArrayBuffer); |
| if (elementArrayBuffer != mAppliedElementArrayBuffer.get()) |
| { |
| const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer); |
| mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferGL->getBufferID()); |
| mAppliedElementArrayBuffer.set(elementArrayBuffer); |
| } |
| |
| return gl::NoError(); |
| } |
| |
| gl::Error VertexArrayGL::syncDrawState(const gl::AttributesMask &activeAttributesMask, |
| GLint first, |
| GLsizei count, |
| GLenum type, |
| const void *indices, |
| GLsizei instanceCount, |
| bool primitiveRestartEnabled, |
| const void **outIndices) const |
| { |
| mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID()); |
| |
| // Check if any attributes need to be streamed, determines if the index range needs to be |
| // computed |
| bool attributesNeedStreaming = mAttributesNeedStreaming.any(); |
| |
| // Determine if an index buffer needs to be streamed and the range of vertices that need to be |
| // copied |
| IndexRange indexRange; |
| if (type != GL_NONE) |
| { |
| Error error = syncIndexData(count, type, indices, primitiveRestartEnabled, |
| attributesNeedStreaming, &indexRange, outIndices); |
| if (error.isError()) |
| { |
| return error; |
| } |
| } |
| else |
| { |
| // Not an indexed call, set the range to [first, first + count - 1] |
| indexRange.start = first; |
| indexRange.end = first + count - 1; |
| } |
| |
| if (attributesNeedStreaming) |
| { |
| Error error = streamAttributes(activeAttributesMask, instanceCount, indexRange); |
| if (error.isError()) |
| { |
| return error; |
| } |
| } |
| |
| return NoError(); |
| } |
| |
| gl::Error VertexArrayGL::syncIndexData(GLsizei count, |
| GLenum type, |
| const void *indices, |
| bool primitiveRestartEnabled, |
| bool attributesNeedStreaming, |
| IndexRange *outIndexRange, |
| const void **outIndices) const |
| { |
| ASSERT(outIndices); |
| |
| gl::Buffer *elementArrayBuffer = mData.getElementArrayBuffer().get(); |
| |
| // Need to check the range of indices if attributes need to be streamed |
| if (elementArrayBuffer != nullptr) |
| { |
| if (elementArrayBuffer != mAppliedElementArrayBuffer.get()) |
| { |
| const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer); |
| mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferGL->getBufferID()); |
| mAppliedElementArrayBuffer.set(elementArrayBuffer); |
| } |
| |
| // Only compute the index range if the attributes also need to be streamed |
| if (attributesNeedStreaming) |
| { |
| ptrdiff_t elementArrayBufferOffset = reinterpret_cast<ptrdiff_t>(indices); |
| Error error = mData.getElementArrayBuffer()->getIndexRange( |
| type, elementArrayBufferOffset, count, primitiveRestartEnabled, outIndexRange); |
| if (error.isError()) |
| { |
| return error; |
| } |
| } |
| |
| // Indices serves as an offset into the index buffer in this case, use the same value for |
| // the draw call |
| *outIndices = indices; |
| } |
| else |
| { |
| // Need to stream the index buffer |
| // TODO: if GLES, nothing needs to be streamed |
| |
| // Only compute the index range if the attributes also need to be streamed |
| if (attributesNeedStreaming) |
| { |
| *outIndexRange = ComputeIndexRange(type, indices, count, primitiveRestartEnabled); |
| } |
| |
| // Allocate the streaming element array buffer |
| if (mStreamingElementArrayBuffer == 0) |
| { |
| mFunctions->genBuffers(1, &mStreamingElementArrayBuffer); |
| mStreamingElementArrayBufferSize = 0; |
| } |
| |
| mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, mStreamingElementArrayBuffer); |
| mAppliedElementArrayBuffer.set(nullptr); |
| |
| // Make sure the element array buffer is large enough |
| const Type &indexTypeInfo = GetTypeInfo(type); |
| size_t requiredStreamingBufferSize = indexTypeInfo.bytes * count; |
| if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize) |
| { |
| // Copy the indices in while resizing the buffer |
| mFunctions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize, indices, |
| GL_DYNAMIC_DRAW); |
| mStreamingElementArrayBufferSize = requiredStreamingBufferSize; |
| } |
| else |
| { |
| // Put the indices at the beginning of the buffer |
| mFunctions->bufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredStreamingBufferSize, |
| indices); |
| } |
| |
| // Set the index offset for the draw call to zero since the supplied index pointer is to |
| // client data |
| *outIndices = nullptr; |
| } |
| |
| return NoError(); |
| } |
| |
| void VertexArrayGL::computeStreamingAttributeSizes(const gl::AttributesMask &activeAttributesMask, |
| GLsizei instanceCount, |
| const gl::IndexRange &indexRange, |
| size_t *outStreamingDataSize, |
| size_t *outMaxAttributeDataSize) const |
| { |
| *outStreamingDataSize = 0; |
| *outMaxAttributeDataSize = 0; |
| |
| ASSERT(mAttributesNeedStreaming.any()); |
| |
| const auto &attribs = mData.getVertexAttributes(); |
| const auto &bindings = mData.getVertexBindings(); |
| |
| gl::AttributesMask attribsToStream = (mAttributesNeedStreaming & activeAttributesMask); |
| |
| for (auto idx : attribsToStream) |
| { |
| const auto &attrib = attribs[idx]; |
| const auto &binding = bindings[attrib.bindingIndex]; |
| ASSERT(AttributeNeedsStreaming(attrib, binding)); |
| |
| // If streaming is going to be required, compute the size of the required buffer |
| // and how much slack space at the beginning of the buffer will be required by determining |
| // the attribute with the largest data size. |
| size_t typeSize = ComputeVertexAttributeTypeSize(attrib); |
| *outStreamingDataSize += typeSize * ComputeVertexBindingElementCount( |
| binding, indexRange.vertexCount(), instanceCount); |
| *outMaxAttributeDataSize = std::max(*outMaxAttributeDataSize, typeSize); |
| } |
| } |
| |
| gl::Error VertexArrayGL::streamAttributes(const gl::AttributesMask &activeAttributesMask, |
| GLsizei instanceCount, |
| const gl::IndexRange &indexRange) const |
| { |
| // Sync the vertex attribute state and track what data needs to be streamed |
| size_t streamingDataSize = 0; |
| size_t maxAttributeDataSize = 0; |
| |
| computeStreamingAttributeSizes(activeAttributesMask, instanceCount, indexRange, |
| &streamingDataSize, &maxAttributeDataSize); |
| |
| if (streamingDataSize == 0) |
| { |
| return gl::NoError(); |
| } |
| |
| if (mStreamingArrayBuffer == 0) |
| { |
| mFunctions->genBuffers(1, &mStreamingArrayBuffer); |
| mStreamingArrayBufferSize = 0; |
| } |
| |
| // If first is greater than zero, a slack space needs to be left at the beginning of the buffer |
| // so that the same 'first' argument can be passed into the draw call. |
| const size_t bufferEmptySpace = maxAttributeDataSize * indexRange.start; |
| const size_t requiredBufferSize = streamingDataSize + bufferEmptySpace; |
| |
| mStateManager->bindBuffer(GL_ARRAY_BUFFER, mStreamingArrayBuffer); |
| if (requiredBufferSize > mStreamingArrayBufferSize) |
| { |
| mFunctions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW); |
| mStreamingArrayBufferSize = requiredBufferSize; |
| } |
| |
| // Unmapping a buffer can return GL_FALSE to indicate that the system has corrupted the data |
| // somehow (such as by a screen change), retry writing the data a few times and return |
| // OUT_OF_MEMORY if that fails. |
| GLboolean unmapResult = GL_FALSE; |
| size_t unmapRetryAttempts = 5; |
| while (unmapResult != GL_TRUE && --unmapRetryAttempts > 0) |
| { |
| uint8_t *bufferPointer = MapBufferRangeWithFallback(mFunctions, GL_ARRAY_BUFFER, 0, |
| requiredBufferSize, GL_MAP_WRITE_BIT); |
| size_t curBufferOffset = bufferEmptySpace; |
| |
| const auto &attribs = mData.getVertexAttributes(); |
| const auto &bindings = mData.getVertexBindings(); |
| |
| gl::AttributesMask attribsToStream = (mAttributesNeedStreaming & activeAttributesMask); |
| |
| for (auto idx : attribsToStream) |
| { |
| const auto &attrib = attribs[idx]; |
| const auto &binding = bindings[attrib.bindingIndex]; |
| ASSERT(AttributeNeedsStreaming(attrib, binding)); |
| |
| const size_t streamedVertexCount = |
| ComputeVertexBindingElementCount(binding, indexRange.vertexCount(), instanceCount); |
| |
| const size_t sourceStride = ComputeVertexAttributeStride(attrib, binding); |
| const size_t destStride = ComputeVertexAttributeTypeSize(attrib); |
| |
| // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing |
| // a non-instanced draw call |
| const size_t firstIndex = binding.divisor == 0 ? indexRange.start : 0; |
| |
| // Attributes using client memory ignore the VERTEX_ATTRIB_BINDING state. |
| // https://www.opengl.org/registry/specs/ARB/vertex_attrib_binding.txt |
| const uint8_t *inputPointer = reinterpret_cast<const uint8_t *>(attrib.pointer); |
| |
| // Pack the data when copying it, user could have supplied a very large stride that |
| // would cause the buffer to be much larger than needed. |
| if (destStride == sourceStride) |
| { |
| // Can copy in one go, the data is packed |
| memcpy(bufferPointer + curBufferOffset, inputPointer + (sourceStride * firstIndex), |
| destStride * streamedVertexCount); |
| } |
| else |
| { |
| // Copy each vertex individually |
| for (size_t vertexIdx = 0; vertexIdx < streamedVertexCount; vertexIdx++) |
| { |
| uint8_t *out = bufferPointer + curBufferOffset + (destStride * vertexIdx); |
| const uint8_t *in = inputPointer + sourceStride * (vertexIdx + firstIndex); |
| memcpy(out, in, destStride); |
| } |
| } |
| |
| // Compute where the 0-index vertex would be. |
| const size_t vertexStartOffset = curBufferOffset - (firstIndex * destStride); |
| |
| if (attrib.pureInteger) |
| { |
| ASSERT(!attrib.normalized); |
| mFunctions->vertexAttribIPointer(static_cast<GLuint>(idx), attrib.size, attrib.type, |
| static_cast<GLsizei>(destStride), |
| reinterpret_cast<const void *>(vertexStartOffset)); |
| } |
| else |
| { |
| mFunctions->vertexAttribPointer(static_cast<GLuint>(idx), attrib.size, attrib.type, |
| attrib.normalized, static_cast<GLsizei>(destStride), |
| reinterpret_cast<const void *>(vertexStartOffset)); |
| } |
| |
| curBufferOffset += destStride * streamedVertexCount; |
| |
| // Mark the applied attribute as dirty by setting an invalid size so that if it doesn't |
| // need to be streamed later, there is no chance that the caching will skip it. |
| mAppliedAttributes[idx].size = static_cast<GLuint>(-1); |
| } |
| |
| unmapResult = mFunctions->unmapBuffer(GL_ARRAY_BUFFER); |
| } |
| |
| if (unmapResult != GL_TRUE) |
| { |
| return Error(GL_OUT_OF_MEMORY, "Failed to unmap the client data streaming buffer."); |
| } |
| |
| return NoError(); |
| } |
| |
| GLuint VertexArrayGL::getVertexArrayID() const |
| { |
| return mVertexArrayID; |
| } |
| |
| GLuint VertexArrayGL::getAppliedElementArrayBufferID() const |
| { |
| if (mAppliedElementArrayBuffer.get() == nullptr) |
| { |
| return mStreamingElementArrayBuffer; |
| } |
| |
| return GetImplAs<BufferGL>(mAppliedElementArrayBuffer.get())->getBufferID(); |
| } |
| |
| void VertexArrayGL::updateNeedsStreaming(size_t attribIndex) |
| { |
| const auto &attrib = mData.getVertexAttribute(attribIndex); |
| const auto &binding = mData.getBindingFromAttribIndex(attribIndex); |
| mAttributesNeedStreaming.set(attribIndex, AttributeNeedsStreaming(attrib, binding)); |
| } |
| |
| void VertexArrayGL::updateAttribEnabled(size_t attribIndex) |
| { |
| const VertexAttribute &attrib = mData.getVertexAttribute(attribIndex); |
| if (mAppliedAttributes[attribIndex].enabled == attrib.enabled) |
| { |
| return; |
| } |
| |
| updateNeedsStreaming(attribIndex); |
| |
| mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID()); |
| if (attrib.enabled) |
| { |
| mFunctions->enableVertexAttribArray(static_cast<GLuint>(attribIndex)); |
| } |
| else |
| { |
| mFunctions->disableVertexAttribArray(static_cast<GLuint>(attribIndex)); |
| } |
| mAppliedAttributes[attribIndex].enabled = attrib.enabled; |
| } |
| |
| void VertexArrayGL::updateAttribPointer(size_t attribIndex) |
| { |
| const VertexAttribute &attrib = mData.getVertexAttribute(attribIndex); |
| ASSERT(attribIndex == attrib.bindingIndex); |
| |
| GLuint bindingIndex = attrib.bindingIndex; |
| const VertexBinding &binding = mData.getVertexBinding(bindingIndex); |
| |
| if (mAppliedAttributes[attribIndex] == attrib && mAppliedBindings[bindingIndex] == binding) |
| { |
| return; |
| } |
| |
| updateNeedsStreaming(attribIndex); |
| |
| // If we need to stream, defer the attribPointer to the draw call. |
| if (mAttributesNeedStreaming[attribIndex]) |
| { |
| return; |
| } |
| |
| // Skip the attribute that is disabled and uses a client memory pointer. |
| const Buffer *arrayBuffer = binding.buffer.get(); |
| if (arrayBuffer == nullptr) |
| { |
| ASSERT(!attrib.enabled); |
| |
| // Mark the applied attribute as dirty by setting an invalid size so that if it doesn't |
| // use a client memory pointer later, there is no chance that the caching will skip it. |
| mAppliedAttributes[attribIndex].size = static_cast<GLuint>(-1); |
| return; |
| } |
| |
| mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID()); |
| |
| // Since ANGLE always uses a non-zero VAO, we cannot use a client memory pointer on it: |
| // [OpenGL ES 3.0.2] Section 2.8 page 24: |
| // An INVALID_OPERATION error is generated when a non-zero vertex array object is bound, |
| // zero is bound to the ARRAY_BUFFER buffer object binding point, and the pointer argument |
| // is not NULL. |
| ASSERT(arrayBuffer != nullptr); |
| const BufferGL *arrayBufferGL = GetImplAs<BufferGL>(arrayBuffer); |
| mStateManager->bindBuffer(GL_ARRAY_BUFFER, arrayBufferGL->getBufferID()); |
| const void *inputPointer = |
| reinterpret_cast<const uint8_t *>(binding.offset + attrib.relativeOffset); |
| |
| if (attrib.pureInteger) |
| { |
| mFunctions->vertexAttribIPointer(static_cast<GLuint>(attribIndex), attrib.size, attrib.type, |
| binding.stride, inputPointer); |
| } |
| else |
| { |
| mFunctions->vertexAttribPointer(static_cast<GLuint>(attribIndex), attrib.size, attrib.type, |
| attrib.normalized, binding.stride, inputPointer); |
| } |
| mAppliedAttributes[attribIndex].size = attrib.size; |
| mAppliedAttributes[attribIndex].type = attrib.type; |
| mAppliedAttributes[attribIndex].normalized = attrib.normalized; |
| mAppliedAttributes[attribIndex].pureInteger = attrib.pureInteger; |
| mAppliedAttributes[attribIndex].pointer = attrib.pointer; |
| mAppliedAttributes[attribIndex].relativeOffset = attrib.relativeOffset; |
| mAppliedAttributes[attribIndex].vertexAttribArrayStride = attrib.vertexAttribArrayStride; |
| |
| mAppliedBindings[bindingIndex].stride = binding.stride; |
| mAppliedBindings[bindingIndex].offset = binding.offset; |
| mAppliedBindings[bindingIndex].buffer = binding.buffer; |
| } |
| |
| void VertexArrayGL::updateAttribDivisor(size_t attribIndex) |
| { |
| ASSERT(attribIndex == mData.getBindingIndexFromAttribIndex(attribIndex)); |
| |
| const VertexBinding &binding = mData.getVertexBinding(attribIndex); |
| if (mAppliedBindings[attribIndex].divisor != binding.divisor) |
| { |
| mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID()); |
| mFunctions->vertexAttribDivisor(static_cast<GLuint>(attribIndex), binding.divisor); |
| |
| mAppliedBindings[attribIndex].divisor = binding.divisor; |
| } |
| } |
| |
| void VertexArrayGL::syncState(ContextImpl *contextImpl, const VertexArray::DirtyBits &dirtyBits) |
| { |
| for (size_t dirtyBit : dirtyBits) |
| { |
| if (dirtyBit == VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER) |
| { |
| // TODO(jmadill): Element array buffer bindings |
| continue; |
| } |
| |
| size_t index = VertexArray::GetAttribIndex(dirtyBit); |
| if (dirtyBit >= VertexArray::DIRTY_BIT_ATTRIB_0_ENABLED && |
| dirtyBit < VertexArray::DIRTY_BIT_ATTRIB_MAX_ENABLED) |
| { |
| updateAttribEnabled(index); |
| } |
| else if (dirtyBit >= VertexArray::DIRTY_BIT_ATTRIB_0_POINTER && |
| dirtyBit < VertexArray::DIRTY_BIT_ATTRIB_MAX_POINTER) |
| { |
| updateAttribPointer(index); |
| } |
| else if (dirtyBit >= VertexArray::DIRTY_BIT_ATTRIB_0_FORMAT && |
| dirtyBit < VertexArray::DIRTY_BIT_BINDING_MAX_BUFFER) |
| { |
| // TODO(jiawei.shao@intel.com): Vertex Attrib Bindings |
| ASSERT(index == mData.getBindingIndexFromAttribIndex(index)); |
| } |
| else if (dirtyBit >= VertexArray::DIRTY_BIT_BINDING_0_DIVISOR && |
| dirtyBit < VertexArray::DIRTY_BIT_BINDING_MAX_DIVISOR) |
| { |
| updateAttribDivisor(index); |
| } |
| else |
| UNREACHABLE(); |
| } |
| } |
| |
| } // rx |