diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/angle/checkout/src/libANGLE/VertexArray.cpp | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/libANGLE/VertexArray.cpp b/gfx/angle/checkout/src/libANGLE/VertexArray.cpp new file mode 100644 index 0000000000..2a9bcd7cc0 --- /dev/null +++ b/gfx/angle/checkout/src/libANGLE/VertexArray.cpp @@ -0,0 +1,906 @@ +// +// Copyright 2013 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. +// +// Implementation of the state class for mananging GLES 3 Vertex Array Objects. +// + +#include "libANGLE/VertexArray.h" + +#include "common/utilities.h" +#include "libANGLE/Buffer.h" +#include "libANGLE/Context.h" +#include "libANGLE/renderer/BufferImpl.h" +#include "libANGLE/renderer/GLImplFactory.h" +#include "libANGLE/renderer/VertexArrayImpl.h" + +namespace gl +{ +namespace +{ +constexpr size_t kMaxObserverCountToTriggerUnobserve = 20; + +bool IsElementArrayBufferSubjectIndex(angle::SubjectIndex subjectIndex) +{ + return (subjectIndex == kElementArrayBufferIndex); +} +} // namespace + +// VertexArrayState implementation. +VertexArrayState::VertexArrayState(VertexArray *vertexArray, + size_t maxAttribs, + size_t maxAttribBindings) + : mElementArrayBuffer(vertexArray, kElementArrayBufferIndex) +{ + ASSERT(maxAttribs <= maxAttribBindings); + + for (size_t i = 0; i < maxAttribs; i++) + { + mVertexAttributes.emplace_back(static_cast<GLuint>(i)); + mVertexBindings.emplace_back(static_cast<GLuint>(i)); + } + + // Initially all attributes start as "client" with no buffer bound. + mClientMemoryAttribsMask.set(); +} + +VertexArrayState::~VertexArrayState() {} + +bool VertexArrayState::hasEnabledNullPointerClientArray() const +{ + return (mNullPointerClientMemoryAttribsMask & mEnabledAttributesMask).any(); +} + +AttributesMask VertexArrayState::getBindingToAttributesMask(GLuint bindingIndex) const +{ + ASSERT(bindingIndex < mVertexBindings.size()); + return mVertexBindings[bindingIndex].getBoundAttributesMask(); +} + +// Set an attribute using a new binding. +void VertexArrayState::setAttribBinding(const Context *context, + size_t attribIndex, + GLuint newBindingIndex) +{ + ASSERT(attribIndex < mVertexAttributes.size() && newBindingIndex < mVertexBindings.size()); + + VertexAttribute &attrib = mVertexAttributes[attribIndex]; + + // Update the binding-attribute map. + const GLuint oldBindingIndex = attrib.bindingIndex; + ASSERT(oldBindingIndex != newBindingIndex); + + VertexBinding &oldBinding = mVertexBindings[oldBindingIndex]; + VertexBinding &newBinding = mVertexBindings[newBindingIndex]; + + ASSERT(oldBinding.getBoundAttributesMask().test(attribIndex) && + !newBinding.getBoundAttributesMask().test(attribIndex)); + + oldBinding.resetBoundAttribute(attribIndex); + newBinding.setBoundAttribute(attribIndex); + + // Set the attribute using the new binding. + attrib.bindingIndex = newBindingIndex; + + if (context->isBufferAccessValidationEnabled()) + { + attrib.updateCachedElementLimit(newBinding); + } + + bool isMapped = newBinding.getBuffer().get() && newBinding.getBuffer()->isMapped(); + mCachedMappedArrayBuffers.set(attribIndex, isMapped); + mEnabledAttributesMask.set(attribIndex, attrib.enabled); + updateCachedMutableOrNonPersistentArrayBuffers(attribIndex); + mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mEnabledAttributesMask & + mCachedMutableOrImpersistentArrayBuffers; +} + +void VertexArrayState::updateCachedMutableOrNonPersistentArrayBuffers(size_t index) +{ + const VertexBinding &vertexBinding = mVertexBindings[index]; + const BindingPointer<Buffer> &buffer = vertexBinding.getBuffer(); + bool isMutableOrImpersistentArrayBuffer = + buffer.get() && + (!buffer->isImmutable() || (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) == 0); + mCachedMutableOrImpersistentArrayBuffers.set(index, isMutableOrImpersistentArrayBuffer); +} + +// VertexArray implementation. +VertexArray::VertexArray(rx::GLImplFactory *factory, + VertexArrayID id, + size_t maxAttribs, + size_t maxAttribBindings) + : mId(id), + mState(this, maxAttribs, maxAttribBindings), + mVertexArray(factory->createVertexArray(mState)), + mBufferAccessValidationEnabled(false), + mContentsObservers(this) +{ + for (size_t attribIndex = 0; attribIndex < maxAttribBindings; ++attribIndex) + { + mArrayBufferObserverBindings.emplace_back(this, attribIndex); + } + + mVertexArray->setContentsObservers(&mContentsObservers); +} + +void VertexArray::onDestroy(const Context *context) +{ + bool isBound = context->isCurrentVertexArray(this); + for (uint32_t bindingIndex = 0; bindingIndex < mState.mVertexBindings.size(); ++bindingIndex) + { + VertexBinding &binding = mState.mVertexBindings[bindingIndex]; + Buffer *buffer = binding.getBuffer().get(); + if (isBound) + { + if (buffer) + { + buffer->onNonTFBindingChanged(-1); + } + } + if (buffer) + { + // Note: the non-contents observer is unbound in the ObserverBinding destructor. + buffer->removeContentsObserver(this, bindingIndex); + } + binding.setBuffer(context, nullptr); + } + if (mState.mElementArrayBuffer.get()) + { + if (isBound) + { + mState.mElementArrayBuffer->onNonTFBindingChanged(-1); + } + mState.mElementArrayBuffer->removeContentsObserver(this, kElementArrayBufferIndex); + } + mState.mElementArrayBuffer.bind(context, nullptr); + + // If mDirtyObserverBindingBits is set, it means we have removed it from the buffer's observer + // list. We should unassign subject to avoid assertion. + for (size_t bindingIndex : mDirtyObserverBindingBits) + { + angle::ObserverBinding *observer = &mArrayBufferObserverBindings[bindingIndex]; + observer->assignSubject(nullptr); + } + + mVertexArray->destroy(context); + SafeDelete(mVertexArray); + delete this; +} + +VertexArray::~VertexArray() +{ + ASSERT(!mVertexArray); +} + +angle::Result VertexArray::setLabel(const Context *context, const std::string &label) +{ + mState.mLabel = label; + + if (mVertexArray) + { + return mVertexArray->onLabelUpdate(context); + } + return angle::Result::Continue; +} + +const std::string &VertexArray::getLabel() const +{ + return mState.mLabel; +} + +bool VertexArray::detachBuffer(const Context *context, BufferID bufferID) +{ + bool isBound = context->isCurrentVertexArray(this); + bool anyBufferDetached = false; + for (uint32_t bindingIndex = 0; bindingIndex < mState.mVertexBindings.size(); ++bindingIndex) + { + VertexBinding &binding = mState.mVertexBindings[bindingIndex]; + const BindingPointer<Buffer> &bufferBinding = binding.getBuffer(); + if (bufferBinding.id() == bufferID) + { + if (isBound) + { + if (bufferBinding.get()) + bufferBinding->onNonTFBindingChanged(-1); + } + bufferBinding->removeContentsObserver(this, bindingIndex); + binding.setBuffer(context, nullptr); + mArrayBufferObserverBindings[bindingIndex].reset(); + + if (context->getClientVersion() >= ES_3_1) + { + setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER); + } + else + { + static_assert(gl::MAX_VERTEX_ATTRIB_BINDINGS < 8 * sizeof(uint32_t), + "Not enough bits in bindingIndex"); + // The redundant uint32_t cast here is required to avoid a warning on MSVC. + ASSERT(binding.getBoundAttributesMask() == + AttributesMask(static_cast<uint32_t>(1 << bindingIndex))); + setDirtyAttribBit(bindingIndex, DIRTY_ATTRIB_POINTER); + } + + anyBufferDetached = true; + mState.mClientMemoryAttribsMask |= binding.getBoundAttributesMask(); + } + } + + if (mState.mElementArrayBuffer.get() && mState.mElementArrayBuffer->id() == bufferID) + { + if (isBound && mState.mElementArrayBuffer.get()) + mState.mElementArrayBuffer->onNonTFBindingChanged(-1); + mState.mElementArrayBuffer->removeContentsObserver(this, kElementArrayBufferIndex); + mState.mElementArrayBuffer.bind(context, nullptr); + mDirtyBits.set(DIRTY_BIT_ELEMENT_ARRAY_BUFFER); + anyBufferDetached = true; + } + + return anyBufferDetached; +} + +const VertexAttribute &VertexArray::getVertexAttribute(size_t attribIndex) const +{ + ASSERT(attribIndex < getMaxAttribs()); + return mState.mVertexAttributes[attribIndex]; +} + +const VertexBinding &VertexArray::getVertexBinding(size_t bindingIndex) const +{ + ASSERT(bindingIndex < getMaxBindings()); + return mState.mVertexBindings[bindingIndex]; +} + +size_t VertexArray::GetVertexIndexFromDirtyBit(size_t dirtyBit) +{ + static_assert(gl::MAX_VERTEX_ATTRIBS == gl::MAX_VERTEX_ATTRIB_BINDINGS, + "The stride of vertex attributes should equal to that of vertex bindings."); + ASSERT(dirtyBit > DIRTY_BIT_ELEMENT_ARRAY_BUFFER); + return (dirtyBit - DIRTY_BIT_ATTRIB_0) % gl::MAX_VERTEX_ATTRIBS; +} + +ANGLE_INLINE void VertexArray::setDirtyAttribBit(size_t attribIndex, + DirtyAttribBitType dirtyAttribBit) +{ + mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex); + mDirtyAttribBits[attribIndex].set(dirtyAttribBit); +} + +ANGLE_INLINE void VertexArray::clearDirtyAttribBit(size_t attribIndex, + DirtyAttribBitType dirtyAttribBit) +{ + mDirtyAttribBits[attribIndex].set(dirtyAttribBit, false); + if (mDirtyAttribBits[attribIndex].any()) + { + return; + } + mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex, false); +} + +ANGLE_INLINE void VertexArray::setDirtyBindingBit(size_t bindingIndex, + DirtyBindingBitType dirtyBindingBit) +{ + mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex); + mDirtyBindingBits[bindingIndex].set(dirtyBindingBit); +} + +ANGLE_INLINE void VertexArray::updateCachedBufferBindingSize(VertexBinding *binding) +{ + if (!mBufferAccessValidationEnabled) + return; + + for (size_t boundAttribute : binding->getBoundAttributesMask()) + { + mState.mVertexAttributes[boundAttribute].updateCachedElementLimit(*binding); + } +} + +ANGLE_INLINE void VertexArray::updateCachedArrayBuffersMasks( + bool isMapped, + bool isImmutable, + bool isPersistent, + const AttributesMask &boundAttributesMask) +{ + if (isMapped) + { + mState.mCachedMappedArrayBuffers |= boundAttributesMask; + } + else + { + mState.mCachedMappedArrayBuffers &= ~boundAttributesMask; + } + + if (!isImmutable || !isPersistent) + { + mState.mCachedMutableOrImpersistentArrayBuffers |= boundAttributesMask; + } + else + { + mState.mCachedMutableOrImpersistentArrayBuffers &= ~boundAttributesMask; + } + + mState.mCachedInvalidMappedArrayBuffer = mState.mCachedMappedArrayBuffers & + mState.mEnabledAttributesMask & + mState.mCachedMutableOrImpersistentArrayBuffers; +} + +ANGLE_INLINE void VertexArray::updateCachedMappedArrayBuffersBinding(const VertexBinding &binding) +{ + const Buffer *buffer = binding.getBuffer().get(); + bool isMapped = buffer && buffer->isMapped(); + bool isImmutable = buffer && buffer->isImmutable(); + bool isPersistent = buffer && (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0; + return updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent, + binding.getBoundAttributesMask()); +} + +ANGLE_INLINE void VertexArray::updateCachedTransformFeedbackBindingValidation(size_t bindingIndex, + const Buffer *buffer) +{ + const bool hasConflict = buffer && buffer->hasWebGLXFBBindingConflict(true); + mCachedTransformFeedbackConflictedBindingsMask.set(bindingIndex, hasConflict); +} + +bool VertexArray::bindVertexBufferImpl(const Context *context, + size_t bindingIndex, + Buffer *boundBuffer, + GLintptr offset, + GLsizei stride) +{ + ASSERT(bindingIndex < getMaxBindings()); + ASSERT(context->isCurrentVertexArray(this)); + + VertexBinding *binding = &mState.mVertexBindings[bindingIndex]; + + Buffer *oldBuffer = binding->getBuffer().get(); + + const bool sameBuffer = oldBuffer == boundBuffer; + const bool sameStride = static_cast<GLuint>(stride) == binding->getStride(); + const bool sameOffset = offset == binding->getOffset(); + + if (sameBuffer && sameStride && sameOffset) + { + return false; + } + + angle::ObserverBinding *observer = &mArrayBufferObserverBindings[bindingIndex]; + observer->assignSubject(boundBuffer); + + // Several nullptr checks are combined here for optimization purposes. + if (oldBuffer) + { + oldBuffer->onNonTFBindingChanged(-1); + oldBuffer->removeObserver(observer); + oldBuffer->removeContentsObserver(this, static_cast<uint32_t>(bindingIndex)); + oldBuffer->release(context); + } + + binding->assignBuffer(boundBuffer); + binding->setOffset(offset); + binding->setStride(stride); + updateCachedBufferBindingSize(binding); + + // Update client memory attribute pointers. Affects all bound attributes. + if (boundBuffer) + { + boundBuffer->addRef(); + boundBuffer->onNonTFBindingChanged(1); + boundBuffer->addObserver(observer); + if (context->isWebGL()) + { + mCachedTransformFeedbackConflictedBindingsMask.set( + bindingIndex, boundBuffer->hasWebGLXFBBindingConflict(true)); + } + mState.mClientMemoryAttribsMask &= ~binding->getBoundAttributesMask(); + + bool isMapped = boundBuffer->isMapped() == GL_TRUE; + bool isImmutable = boundBuffer->isImmutable() == GL_TRUE; + bool isPersistent = (boundBuffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0; + updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent, + binding->getBoundAttributesMask()); + } + else + { + if (context->isWebGL()) + { + mCachedTransformFeedbackConflictedBindingsMask.set(bindingIndex, false); + } + mState.mClientMemoryAttribsMask |= binding->getBoundAttributesMask(); + updateCachedArrayBuffersMasks(false, false, false, binding->getBoundAttributesMask()); + } + + return true; +} + +void VertexArray::bindVertexBuffer(const Context *context, + size_t bindingIndex, + Buffer *boundBuffer, + GLintptr offset, + GLsizei stride) +{ + if (bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride)) + { + setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER); + } +} + +void VertexArray::setVertexAttribBinding(const Context *context, + size_t attribIndex, + GLuint bindingIndex) +{ + ASSERT(attribIndex < getMaxAttribs() && bindingIndex < getMaxBindings()); + + if (mState.mVertexAttributes[attribIndex].bindingIndex == bindingIndex) + { + return; + } + + // In ES 3.0 contexts, the binding cannot change, hence the code below is unreachable. + ASSERT(context->getClientVersion() >= ES_3_1); + + mState.setAttribBinding(context, attribIndex, bindingIndex); + + setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_BINDING); + + // Update client attribs mask. + bool hasBuffer = mState.mVertexBindings[bindingIndex].getBuffer().get() != nullptr; + mState.mClientMemoryAttribsMask.set(attribIndex, !hasBuffer); +} + +void VertexArray::setVertexBindingDivisor(const Context *context, + size_t bindingIndex, + GLuint divisor) +{ + ASSERT(bindingIndex < getMaxBindings()); + + VertexBinding &binding = mState.mVertexBindings[bindingIndex]; + + if (binding.getDivisor() == divisor) + { + return; + } + + binding.setDivisor(divisor); + setDirtyBindingBit(bindingIndex, DIRTY_BINDING_DIVISOR); + + // Trigger updates in all bound attributes. + if (context->isBufferAccessValidationEnabled()) + { + for (size_t attribIndex : binding.getBoundAttributesMask()) + { + mState.mVertexAttributes[attribIndex].updateCachedElementLimit(binding); + } + } +} + +ANGLE_INLINE bool VertexArray::setVertexAttribFormatImpl(VertexAttribute *attrib, + GLint size, + VertexAttribType type, + bool normalized, + bool pureInteger, + GLuint relativeOffset) +{ + angle::FormatID formatID = gl::GetVertexFormatID(type, normalized, size, pureInteger); + + if (formatID != attrib->format->id || attrib->relativeOffset != relativeOffset) + { + attrib->relativeOffset = relativeOffset; + attrib->format = &angle::Format::Get(formatID); + return true; + } + + return false; +} + +void VertexArray::setVertexAttribFormat(size_t attribIndex, + GLint size, + VertexAttribType type, + bool normalized, + bool pureInteger, + GLuint relativeOffset) +{ + VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; + + ComponentType componentType = GetVertexAttributeComponentType(pureInteger, type); + SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask); + + if (setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, relativeOffset)) + { + setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_FORMAT); + } + + attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex]); +} + +void VertexArray::setVertexAttribDivisor(const Context *context, size_t attribIndex, GLuint divisor) +{ + ASSERT(attribIndex < getMaxAttribs()); + + setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex)); + setVertexBindingDivisor(context, attribIndex, divisor); +} + +void VertexArray::enableAttribute(size_t attribIndex, bool enabledState) +{ + ASSERT(attribIndex < getMaxAttribs()); + + VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; + + if (mState.mEnabledAttributesMask.test(attribIndex) == enabledState) + { + return; + } + + attrib.enabled = enabledState; + + // Update state cache + mState.mEnabledAttributesMask.set(attribIndex, enabledState); + bool enableChanged = (mState.mEnabledAttributesMask.test(attribIndex) != + mState.mLastSyncedEnabledAttributesMask.test(attribIndex)); + + if (enableChanged) + { + setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_ENABLED); + } + else + { + clearDirtyAttribBit(attribIndex, DIRTY_ATTRIB_ENABLED); + } + + mState.updateCachedMutableOrNonPersistentArrayBuffers(attribIndex); + mState.mCachedInvalidMappedArrayBuffer = mState.mCachedMappedArrayBuffers & + mState.mEnabledAttributesMask & + mState.mCachedMutableOrImpersistentArrayBuffers; +} + +ANGLE_INLINE void VertexArray::setVertexAttribPointerImpl(const Context *context, + ComponentType componentType, + bool pureInteger, + size_t attribIndex, + Buffer *boundBuffer, + GLint size, + VertexAttribType type, + bool normalized, + GLsizei stride, + const void *pointer) +{ + ASSERT(attribIndex < getMaxAttribs()); + + VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; + + SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask); + + bool attribDirty = setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, 0); + + if (attrib.bindingIndex != attribIndex) + { + setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex)); + } + + GLsizei effectiveStride = + stride == 0 ? static_cast<GLsizei>(ComputeVertexAttributeTypeSize(attrib)) : stride; + + if (attrib.vertexAttribArrayStride != static_cast<GLuint>(stride)) + { + attribDirty = true; + } + attrib.vertexAttribArrayStride = stride; + + // If we switch from an array buffer to a client pointer(or vice-versa), we set the whole + // attribute dirty. This notifies the Vulkan back-end to update all its caches. + const VertexBinding &binding = mState.mVertexBindings[attribIndex]; + if ((boundBuffer == nullptr) != (binding.getBuffer().get() == nullptr)) + { + attribDirty = true; + } + + // Change of attrib.pointer is not part of attribDirty. Pointer is actually the buffer offset + // which is handled within bindVertexBufferImpl and reflected in bufferDirty. + attrib.pointer = pointer; + GLintptr offset = boundBuffer ? reinterpret_cast<GLintptr>(pointer) : 0; + const bool bufferDirty = + bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride); + + if (attribDirty) + { + setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER); + } + else if (bufferDirty) + { + setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER_BUFFER); + } + + mState.mNullPointerClientMemoryAttribsMask.set(attribIndex, + boundBuffer == nullptr && pointer == nullptr); +} + +void VertexArray::setVertexAttribPointer(const Context *context, + size_t attribIndex, + gl::Buffer *boundBuffer, + GLint size, + VertexAttribType type, + bool normalized, + GLsizei stride, + const void *pointer) +{ + setVertexAttribPointerImpl(context, ComponentType::Float, false, attribIndex, boundBuffer, size, + type, normalized, stride, pointer); +} + +void VertexArray::setVertexAttribIPointer(const Context *context, + size_t attribIndex, + gl::Buffer *boundBuffer, + GLint size, + VertexAttribType type, + GLsizei stride, + const void *pointer) +{ + ComponentType componentType = GetVertexAttributeComponentType(true, type); + setVertexAttribPointerImpl(context, componentType, true, attribIndex, boundBuffer, size, type, + false, stride, pointer); +} + +angle::Result VertexArray::syncState(const Context *context) +{ + if (mDirtyBits.any()) + { + mDirtyBitsGuard = mDirtyBits; + ANGLE_TRY( + mVertexArray->syncState(context, mDirtyBits, &mDirtyAttribBits, &mDirtyBindingBits)); + mDirtyBits.reset(); + mDirtyBitsGuard.reset(); + + // The dirty bits should be reset in the back-end. To simplify ASSERTs only check attrib 0. + ASSERT(mDirtyAttribBits[0].none()); + ASSERT(mDirtyBindingBits[0].none()); + mState.mLastSyncedEnabledAttributesMask = mState.mEnabledAttributesMask; + } + return angle::Result::Continue; +} + +// This becomes current vertex array on the context +void VertexArray::onBind(const Context *context) +{ + if (mDirtyObserverBindingBits.none()) + { + return; + } + + // This vertex array becoming current. Some of the bindings we may have removed from buffer's + // observer list. We need to add it back to the buffer's observer list and update dirty bits + // that we may have missed while we were not observing. + for (size_t bindingIndex : mDirtyObserverBindingBits) + { + const gl::VertexBinding &binding = mState.getVertexBindings()[bindingIndex]; + gl::Buffer *bufferGL = binding.getBuffer().get(); + ASSERT(bufferGL != nullptr); + + bufferGL->addObserver(&mArrayBufferObserverBindings[bindingIndex]); + updateCachedMappedArrayBuffersBinding(mState.mVertexBindings[bindingIndex]); + + // Assume both data and internal storage has been dirtied. + mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex); + + if (mBufferAccessValidationEnabled) + { + for (size_t boundAttribute : + mState.mVertexBindings[bindingIndex].getBoundAttributesMask()) + { + mState.mVertexAttributes[boundAttribute].updateCachedElementLimit( + mState.mVertexBindings[bindingIndex]); + } + } + + if (context->isWebGL()) + { + updateCachedTransformFeedbackBindingValidation(bindingIndex, bufferGL); + } + } + mDirtyObserverBindingBits.reset(); + + onStateChange(angle::SubjectMessage::ContentsChanged); +} + +// This becomes non-current vertex array on the context +void VertexArray::onUnbind(const Context *context) +{ + // This vertex array becoming non-current. For performance reason, if there are too many + // observers in the buffer, we remove it from the buffers' observer list so that the cost of + // buffer sending signal to observers will be too expensive. + for (uint32_t bindingIndex = 0; bindingIndex < mArrayBufferObserverBindings.size(); + ++bindingIndex) + { + const gl::VertexBinding &binding = mState.getVertexBindings()[bindingIndex]; + gl::Buffer *bufferGL = binding.getBuffer().get(); + if (bufferGL && bufferGL->getObserversCount() > kMaxObserverCountToTriggerUnobserve) + { + bufferGL->removeObserver(&mArrayBufferObserverBindings[bindingIndex]); + mDirtyObserverBindingBits.set(bindingIndex); + } + } +} + +void VertexArray::onBindingChanged(const Context *context, int incr) +{ + // When vertex array gets unbound, we remove it from bound buffers' observer list so that when + // buffer changes, it wont has to loop over all these non-current vertex arrays and set dirty + // bit on them. To compensate for that, when we bind a vertex array, we have to check against + // each bound buffers and see if they have changed and needs to update vertex array's dirty bits + // accordingly + ASSERT(incr == 1 || incr == -1); + if (incr < 0) + { + onUnbind(context); + } + else + { + onBind(context); + } + + if (context->isWebGL()) + { + if (mState.mElementArrayBuffer.get()) + mState.mElementArrayBuffer->onNonTFBindingChanged(incr); + for (auto &binding : mState.mVertexBindings) + { + binding.onContainerBindingChanged(context, incr); + } + } +} + +VertexArray::DirtyBitType VertexArray::getDirtyBitFromIndex(bool contentsChanged, + angle::SubjectIndex index) const +{ + if (IsElementArrayBufferSubjectIndex(index)) + { + mIndexRangeCache.invalidate(); + return contentsChanged ? DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA + : DIRTY_BIT_ELEMENT_ARRAY_BUFFER; + } + else + { + // Note: this currently just gets the top-level dirty bit. + ASSERT(index < mArrayBufferObserverBindings.size()); + return static_cast<DirtyBitType>( + (contentsChanged ? DIRTY_BIT_BUFFER_DATA_0 : DIRTY_BIT_BINDING_0) + index); + } +} + +void VertexArray::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) +{ + switch (message) + { + case angle::SubjectMessage::SubjectChanged: + if (!IsElementArrayBufferSubjectIndex(index)) + { + updateCachedBufferBindingSize(&mState.mVertexBindings[index]); + } + setDependentDirtyBit(false, index); + break; + + case angle::SubjectMessage::BindingChanged: + if (!IsElementArrayBufferSubjectIndex(index)) + { + const Buffer *buffer = mState.mVertexBindings[index].getBuffer().get(); + updateCachedTransformFeedbackBindingValidation(index, buffer); + } + break; + + case angle::SubjectMessage::SubjectMapped: + if (!IsElementArrayBufferSubjectIndex(index)) + { + updateCachedMappedArrayBuffersBinding(mState.mVertexBindings[index]); + } + onStateChange(angle::SubjectMessage::SubjectMapped); + break; + + case angle::SubjectMessage::SubjectUnmapped: + setDependentDirtyBit(true, index); + + if (!IsElementArrayBufferSubjectIndex(index)) + { + updateCachedMappedArrayBuffersBinding(mState.mVertexBindings[index]); + } + onStateChange(angle::SubjectMessage::SubjectUnmapped); + break; + + case angle::SubjectMessage::InternalMemoryAllocationChanged: + setDependentDirtyBit(false, index); + break; + + default: + UNREACHABLE(); + break; + } +} + +void VertexArray::setDependentDirtyBit(bool contentsChanged, angle::SubjectIndex index) +{ + DirtyBitType dirtyBit = getDirtyBitFromIndex(contentsChanged, index); + ASSERT(!mDirtyBitsGuard.valid() || mDirtyBitsGuard.value().test(dirtyBit)); + mDirtyBits.set(dirtyBit); + onStateChange(angle::SubjectMessage::ContentsChanged); +} + +bool VertexArray::hasTransformFeedbackBindingConflict(const gl::Context *context) const +{ + // Fast check first. + if (!mCachedTransformFeedbackConflictedBindingsMask.any()) + { + return false; + } + + const AttributesMask &activeAttribues = context->getStateCache().getActiveBufferedAttribsMask(); + + // Slow check. We must ensure that the conflicting attributes are enabled/active. + for (size_t attribIndex : activeAttribues) + { + const VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; + if (mCachedTransformFeedbackConflictedBindingsMask[attrib.bindingIndex]) + { + return true; + } + } + + return false; +} + +angle::Result VertexArray::getIndexRangeImpl(const Context *context, + DrawElementsType type, + GLsizei indexCount, + const void *indices, + IndexRange *indexRangeOut) const +{ + Buffer *elementArrayBuffer = mState.mElementArrayBuffer.get(); + if (!elementArrayBuffer) + { + *indexRangeOut = ComputeIndexRange(type, indices, indexCount, + context->getState().isPrimitiveRestartEnabled()); + return angle::Result::Continue; + } + + size_t offset = reinterpret_cast<uintptr_t>(indices); + ANGLE_TRY(elementArrayBuffer->getIndexRange(context, type, offset, indexCount, + context->getState().isPrimitiveRestartEnabled(), + indexRangeOut)); + + mIndexRangeCache.put(type, indexCount, offset, *indexRangeOut); + return angle::Result::Continue; +} + +VertexArray::IndexRangeCache::IndexRangeCache() = default; + +void VertexArray::IndexRangeCache::put(DrawElementsType type, + GLsizei indexCount, + size_t offset, + const IndexRange &indexRange) +{ + ASSERT(type != DrawElementsType::InvalidEnum); + + mTypeKey = type; + mIndexCountKey = indexCount; + mOffsetKey = offset; + mPayload = indexRange; +} + +void VertexArray::onBufferContentsChange(uint32_t bufferIndex) +{ + setDependentDirtyBit(true, bufferIndex); +} + +VertexArrayBufferContentsObservers::VertexArrayBufferContentsObservers(VertexArray *vertexArray) + : mVertexArray(vertexArray) +{} + +void VertexArrayBufferContentsObservers::enableForBuffer(Buffer *buffer, uint32_t bufferIndex) +{ + buffer->addContentsObserver(mVertexArray, bufferIndex); +} + +void VertexArrayBufferContentsObservers::disableForBuffer(Buffer *buffer, uint32_t bufferIndex) +{ + buffer->removeContentsObserver(mVertexArray, bufferIndex); +} +} // namespace gl |