// // 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 { // VertexArrayState implementation. VertexArrayState::VertexArrayState(VertexArrayID vertexArrayID, size_t maxAttribs, size_t maxAttribBindings) : mId(vertexArrayID) { ASSERT(maxAttribs <= maxAttribBindings); for (size_t i = 0; i < maxAttribs; i++) { mVertexAttributes.emplace_back(static_cast(i)); mVertexBindings.emplace_back(static_cast(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(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; mEnabledAttributesMask.set(attribIndex, attrib.enabled); } bool VertexArrayState::isDefault() const { return mId.value == 0; } // VertexArrayPrivate implementation. VertexArrayPrivate::VertexArrayPrivate(rx::GLImplFactory *factory, VertexArrayID id, size_t maxAttribs, size_t maxAttribBindings) : mId(id), mState(mId, maxAttribs, maxAttribBindings), mRobustBufferAccessEnabled(false) {} VertexArrayPrivate::~VertexArrayPrivate() {} void VertexArrayPrivate::setVertexAttribBinding(size_t attribIndex, GLuint newBindingIndex) { ASSERT(attribIndex < getMaxAttribs() && newBindingIndex < getMaxBindings()); if (mState.mVertexAttributes[attribIndex].bindingIndex == newBindingIndex) { return; } mState.setAttribBinding(attribIndex, newBindingIndex); if (mRobustBufferAccessEnabled) { VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; attrib.updateCachedElementLimit(mState.mVertexBindings[newBindingIndex], mCachedBufferSize[newBindingIndex]); } setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_BINDING); // Update client attribs mask. mState.mClientMemoryAttribsMask.set(attribIndex, !mBufferBindingMask[newBindingIndex]); mCachedMappedArrayBuffers.set(attribIndex, mCachedBufferPropertyMapped.test(newBindingIndex)); mCachedMutableOrImpersistentArrayBuffers.set( attribIndex, mCachedBufferPropertyMutableOrImpersistent.test(newBindingIndex)); mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask & mCachedMutableOrImpersistentArrayBuffers; } const VertexAttribute &VertexArrayPrivate::getVertexAttribute(size_t attribIndex) const { ASSERT(attribIndex < getMaxAttribs()); return mState.mVertexAttributes[attribIndex]; } const VertexBinding &VertexArrayPrivate::getVertexBinding(size_t bindingIndex) const { ASSERT(bindingIndex < getMaxBindings()); return mState.mVertexBindings[bindingIndex]; } void VertexArrayPrivate::setDirtyAttribBit(size_t attribIndex, DirtyAttribBitType dirtyAttribBit) { mDirtyBits.set(DIRTY_BIT_ATTRIB_0 + attribIndex); mDirtyAttribBits[attribIndex].set(dirtyAttribBit); } ANGLE_INLINE void VertexArrayPrivate::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 VertexArrayPrivate::setDirtyBindingBit(size_t bindingIndex, DirtyBindingBitType dirtyBindingBit) { mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex); mDirtyBindingBits[bindingIndex].set(dirtyBindingBit); } ANGLE_INLINE void VertexArrayPrivate::updateCachedElementLimit(const VertexBinding &binding, GLint64 bufferSize) { ASSERT(mRobustBufferAccessEnabled); for (size_t boundAttribute : binding.getBoundAttributesMask()) { mState.mVertexAttributes[boundAttribute].updateCachedElementLimit(binding, bufferSize); } } ANGLE_INLINE void VertexArrayPrivate::updateCachedArrayBuffersMasks( bool isMapped, bool isImmutable, bool isPersistent, const AttributesMask &boundAttributesMask) { if (isMapped) { mCachedMappedArrayBuffers |= boundAttributesMask; } else { mCachedMappedArrayBuffers &= ~boundAttributesMask; } if (!isImmutable || !isPersistent) { mCachedMutableOrImpersistentArrayBuffers |= boundAttributesMask; } else { mCachedMutableOrImpersistentArrayBuffers &= ~boundAttributesMask; } mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask & mCachedMutableOrImpersistentArrayBuffers; } void VertexArrayPrivate::setVertexBindingDivisor(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); } bool VertexArrayPrivate::setVertexAttribFormatImpl(VertexAttribute *attrib, GLint size, VertexAttribType type, bool normalized, bool pureInteger, GLuint relativeOffset) { angle::FormatID formatID = 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 VertexArrayPrivate::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); } if (mRobustBufferAccessEnabled) { attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex], mCachedBufferSize[attrib.bindingIndex]); } } void VertexArrayPrivate::setVertexAttribDivisor(size_t attribIndex, GLuint divisor) { ASSERT(attribIndex < getMaxAttribs()); setVertexAttribBinding(attribIndex, static_cast(attribIndex)); setVertexBindingDivisor(attribIndex, divisor); } void VertexArrayPrivate::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); } mCachedInvalidMappedArrayBuffer = mCachedMappedArrayBuffers & mState.mEnabledAttributesMask & mCachedMutableOrImpersistentArrayBuffers; } bool VertexArrayPrivate::hasTransformFeedbackBindingConflict(const Context *context) const { // Fast check first. if (!mCachedBufferPropertyTransformFeedbackConflict.any()) { return false; } const AttributesMask &activeAttribues = context->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 (mCachedBufferPropertyTransformFeedbackConflict[attrib.bindingIndex]) { return true; } } return false; } // VertexArray implementation. VertexArray::VertexArray(rx::GLImplFactory *factory, VertexArrayID id, size_t maxAttribs, size_t maxAttribBindings) : VertexArrayPrivate(factory, id, maxAttribs, maxAttribBindings), mVertexArray(factory->createVertexArray(mState, mVertexArrayBuffers)) {} void VertexArray::onDestroy(const Context *context) { bool isBound = context->isCurrentVertexArray(this); for (size_t bindingIndex : mBufferBindingMask) { Buffer *buffer = mVertexArrayBuffers[bindingIndex].get(); ASSERT(buffer != nullptr); if (isBound) { buffer->onNonTFBindingChanged(-1); buffer->removeVertexArrayBinding(context, bindingIndex); } mVertexArrayBuffers[bindingIndex].set(context, nullptr); } mBufferBindingMask.reset(); 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.getLabel(); } bool VertexArray::detachBuffer(const Context *context, BufferID bufferID) { bool isBound = context->isCurrentVertexArray(this); bool anyBufferDetached = false; for (size_t bindingIndex : mBufferBindingMask) { Buffer *buffer = mVertexArrayBuffers[bindingIndex].get(); ASSERT(buffer != nullptr); if (buffer->id() == bufferID) { if (isBound) { buffer->onNonTFBindingChanged(-1); } buffer->removeVertexArrayBinding(context, bindingIndex); mVertexArrayBuffers[bindingIndex].set(context, nullptr); mBufferBindingMask.reset(bindingIndex); if (bindingIndex == kElementArrayBufferIndex) { mDirtyBits.set(DIRTY_BIT_ELEMENT_ARRAY_BUFFER); } else { VertexBinding &binding = mState.mVertexBindings[bindingIndex]; if (context->getClientVersion() >= ES_3_1 && !mState.isDefault()) { setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER); } else { static_assert(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(1 << bindingIndex))); setDirtyAttribBit(bindingIndex, DIRTY_ATTRIB_POINTER); } for (size_t attribIndex : binding.getBoundAttributesMask()) { VertexAttribute &attrib = mState.mVertexAttributes[attribIndex]; attrib.pointer = nullptr; } mState.mClientMemoryAttribsMask |= binding.getBoundAttributesMask(); } anyBufferDetached = true; } } return anyBufferDetached; } ANGLE_INLINE void VertexArray::updateCachedMappedArrayBuffersBinding(size_t bindingIndex) { const VertexBinding &binding = mState.mVertexBindings[bindingIndex]; const Buffer *buffer = mVertexArrayBuffers[bindingIndex].get(); ASSERT(mBufferBindingMask.test(bindingIndex)); ASSERT(buffer != nullptr); bool isMapped = buffer->isMapped() == GL_TRUE; bool isImmutable = buffer->isImmutable() == GL_TRUE; bool isPersistent = (buffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) != 0; mCachedBufferPropertyMapped.set(bindingIndex, isMapped); mCachedBufferPropertyMutableOrImpersistent.set(bindingIndex, !isImmutable || !isPersistent); return updateCachedArrayBuffersMasks(isMapped, isImmutable, isPersistent, binding.getBoundAttributesMask()); } void VertexArray::bindElementBuffer(const Context *context, Buffer *boundBuffer) { Buffer *oldBuffer = getElementArrayBuffer(); if (oldBuffer) { oldBuffer->removeVertexArrayBinding(context, kElementArrayBufferIndex); if (context->isWebGL()) { oldBuffer->onNonTFBindingChanged(-1); } oldBuffer->release(context); mBufferBindingMask.reset(kElementArrayBufferIndex); } mVertexArrayBuffers[kElementArrayBufferIndex].assign(boundBuffer); if (boundBuffer) { boundBuffer->addVertexArrayBinding(context, kElementArrayBufferIndex); if (context->isWebGL()) { boundBuffer->onNonTFBindingChanged(1); } boundBuffer->addRef(); mBufferBindingMask.set(kElementArrayBufferIndex); } mDirtyBits.set(VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER); mIndexRangeInlineCache = {}; } ANGLE_INLINE VertexArray::DirtyBindingBits 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 = mVertexArrayBuffers[bindingIndex].get(); DirtyBindingBits dirtyBindingBits; dirtyBindingBits.set(DIRTY_BINDING_BUFFER, oldBuffer != boundBuffer); dirtyBindingBits.set(DIRTY_BINDING_STRIDE, static_cast(stride) != binding->getStride()); dirtyBindingBits.set(DIRTY_BINDING_OFFSET, offset != binding->getOffset()); if (mRobustBufferAccessEnabled) { GLint64 bufferSize = boundBuffer ? boundBuffer->getSize() : 0; dirtyBindingBits.set(DIRTY_BINDING_SIZE, bufferSize != mCachedBufferSize[bindingIndex]); mCachedBufferSize[bindingIndex] = bufferSize; } if (dirtyBindingBits.none()) { return dirtyBindingBits; } if (boundBuffer != oldBuffer) { // Several nullptr checks are combined here for optimization purposes. if (oldBuffer) { oldBuffer->onNonTFBindingChanged(-1); oldBuffer->removeVertexArrayBinding(context, bindingIndex); oldBuffer->release(context); mBufferBindingMask.reset(bindingIndex); } mVertexArrayBuffers[bindingIndex].assign(boundBuffer); // Update client memory attribute pointers. Affects all bound attributes. if (boundBuffer) { boundBuffer->addRef(); boundBuffer->onNonTFBindingChanged(1); boundBuffer->addVertexArrayBinding(context, bindingIndex); if (context->isWebGL()) { mCachedBufferPropertyTransformFeedbackConflict.set( bindingIndex, boundBuffer->hasWebGLXFBBindingConflict(true)); } mBufferBindingMask.set(bindingIndex); mState.mClientMemoryAttribsMask &= ~binding->getBoundAttributesMask(); updateCachedMappedArrayBuffersBinding(bindingIndex); } else { if (context->isWebGL()) { mCachedBufferPropertyTransformFeedbackConflict.set(bindingIndex, false); } mState.mClientMemoryAttribsMask |= binding->getBoundAttributesMask(); mCachedBufferPropertyMapped.set(bindingIndex, false); mCachedBufferPropertyMutableOrImpersistent.set(bindingIndex, false); updateCachedArrayBuffersMasks(false, false, false, binding->getBoundAttributesMask()); } } binding->setOffset(offset); binding->setStride(stride); if (mRobustBufferAccessEnabled) { updateCachedElementLimit(*binding, mCachedBufferSize[bindingIndex]); } return dirtyBindingBits; } void VertexArray::bindVertexBuffer(const Context *context, size_t bindingIndex, Buffer *boundBuffer, GLintptr offset, GLsizei stride) { const VertexArray::DirtyBindingBits dirtyBindingBits = bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride); if (!dirtyBindingBits.test(DIRTY_BINDING_BUFFER) && context->isSharedContext() && boundBuffer != nullptr) { ASSERT(boundBuffer == mVertexArrayBuffers[bindingIndex].get()); VertexArrayBufferBindingMask bindingMask = boundBuffer->getVertexArrayBinding(context); ASSERT(!bindingMask.none()); onSharedBufferBind(context, boundBuffer, bindingMask); } if (dirtyBindingBits.any()) { mDirtyBits.set(DIRTY_BIT_BINDING_0 + bindingIndex); mDirtyBindingBits[bindingIndex] |= dirtyBindingBits; } } 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, bool *isVertexAttribDirtyOut) { ASSERT(isVertexAttribDirtyOut); 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(attribIndex, static_cast(attribIndex)); } GLsizei effectiveStride = stride == 0 ? static_cast(ComputeVertexAttributeTypeSize(attrib)) : stride; if (attrib.vertexAttribArrayStride != static_cast(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. Buffer *oldBuffer = mVertexArrayBuffers[attrib.bindingIndex].get(); if ((boundBuffer == nullptr) != (oldBuffer == nullptr)) { attribDirty = true; } // If using client arrays and the pointer changes, set the attribute as dirty if (boundBuffer == nullptr && attrib.pointer != pointer) { 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(pointer) : 0; const VertexArray::DirtyBindingBits dirtyBindingBits = bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride); if (attribDirty) { setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER); *isVertexAttribDirtyOut = true; } else if (dirtyBindingBits.any()) { setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER_BUFFER); *isVertexAttribDirtyOut = true; } mState.mNullPointerClientMemoryAttribsMask.set(attribIndex, boundBuffer == nullptr && pointer == nullptr); } void VertexArray::setVertexAttribPointer(const Context *context, size_t attribIndex, Buffer *boundBuffer, GLint size, VertexAttribType type, bool normalized, GLsizei stride, const void *pointer, bool *isVertexAttribDirtyOut) { setVertexAttribPointerImpl(context, ComponentType::Float, false, attribIndex, boundBuffer, size, type, normalized, stride, pointer, isVertexAttribDirtyOut); } void VertexArray::setVertexAttribIPointer(const Context *context, size_t attribIndex, Buffer *boundBuffer, GLint size, VertexAttribType type, GLsizei stride, const void *pointer, bool *isVertexAttribDirtyOut) { ComponentType componentType = GetVertexAttributeComponentType(true, type); setVertexAttribPointerImpl(context, componentType, true, attribIndex, boundBuffer, size, type, false, stride, pointer, isVertexAttribDirtyOut); } 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; } bool VertexArray::bufferMaskBitsPointToTheSameBuffer( VertexArrayBufferBindingMask bufferBindingMask) const { const Buffer *buffer = nullptr; for (size_t bindingIndex : bufferBindingMask) { if (buffer == nullptr) { buffer = mVertexArrayBuffers[bindingIndex].get(); } else if (buffer != mVertexArrayBuffers[bindingIndex].get()) { return false; } } return true; } void VertexArray::updateBindingSizeIfChanged(const size_t bindingIndex, const GLint64 bufferSize) { ASSERT(mRobustBufferAccessEnabled); if (mCachedBufferSize[bindingIndex] != bufferSize) { setDirtyBindingBit(bindingIndex, DIRTY_BINDING_SIZE); mCachedBufferSize[bindingIndex] = bufferSize; } } // This becomes current vertex array on the context void VertexArray::onBind(const Context *context) { VertexArrayBufferBindingMask bufferBindingMask = mBufferBindingMask; if (bufferBindingMask[kElementArrayBufferIndex]) { Buffer *bufferGL = getElementArrayBuffer(); ASSERT(bufferGL != nullptr); bufferGL->addVertexArrayBinding(context, kElementArrayBufferIndex); bufferBindingMask.reset(kElementArrayBufferIndex); } else { ASSERT(getElementArrayBuffer() == nullptr); } // 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 : bufferBindingMask) { Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get(); ASSERT(bufferGL != nullptr); ASSERT(bindingIndex != kElementArrayBufferIndex); bufferGL->addVertexArrayBinding(context, bindingIndex); updateCachedMappedArrayBuffersBinding(bindingIndex); } if (mRobustBufferAccessEnabled) { for (size_t bindingIndex : bufferBindingMask) { Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get(); updateBindingSizeIfChanged(bindingIndex, bufferGL->getSize()); updateCachedElementLimit(mState.mVertexBindings[bindingIndex], mCachedBufferSize[bindingIndex]); } } if (context->isWebGL()) { for (size_t bindingIndex : bufferBindingMask) { bool hasConflict = mVertexArrayBuffers[bindingIndex]->hasWebGLXFBBindingConflict(true); mCachedBufferPropertyTransformFeedbackConflict.set(bindingIndex, hasConflict); } } // Buffers may have changed while vertex array was not current, we need to check buffer's // internal storage and set proper dirty bits if buffer has changed since last syncState. mDirtyBits |= mVertexArray->checkBufferForDirtyBits(context, mBufferBindingMask); // Always reset mIndexRangeInlineCache since we lost buffer observation while unbind mIndexRangeInlineCache = {}; 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, we remove it from the // buffers' observer list so that the cost of buffer sending signal to observers will not be too // expensive. for (size_t bindingIndex : mBufferBindingMask) { Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get(); ASSERT(bufferGL != nullptr); bufferGL->removeVertexArrayBinding(context, 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()) { for (size_t bindingIndex : mBufferBindingMask) { ASSERT(mVertexArrayBuffers[bindingIndex].get()); mVertexArrayBuffers[bindingIndex]->onNonTFBindingChanged(incr); } } } void VertexArray::setDependentDirtyBits(bool contentsChanged, VertexArrayBufferBindingMask bufferBindingMask) { DirtyBits dirtyBits(contentsChanged ? (bufferBindingMask.bits() << DIRTY_BIT_BUFFER_DATA_0) : (bufferBindingMask.bits() << DIRTY_BIT_BINDING_0)); ASSERT(!mDirtyBitsGuard.valid() || (mDirtyBitsGuard.value() & dirtyBits) == dirtyBits); mDirtyBits |= dirtyBits; if (bufferBindingMask.test(kElementArrayBufferIndex)) { mIndexRangeInlineCache = {}; } onStateChange(angle::SubjectMessage::ContentsChanged); } void VertexArray::onSharedBufferBind(const Context *context, const Buffer *buffer, VertexArrayBufferBindingMask bufferBindingMask) { bufferBindingMask &= mBufferBindingMask; ASSERT(bufferBindingMask.any()); // vertexBufferBindingMask is bufferBindingMask without elementBuffer. VertexArrayBufferBindingMask vertexBufferBindingMask = bufferBindingMask; vertexBufferBindingMask.reset(kElementArrayBufferIndex); for (size_t bindingIndex : vertexBufferBindingMask) { updateCachedMappedArrayBuffersBinding(bindingIndex); } if (mRobustBufferAccessEnabled) { for (size_t bindingIndex : vertexBufferBindingMask) { ASSERT(buffer == mVertexArrayBuffers[bindingIndex].get()); updateBindingSizeIfChanged(bindingIndex, buffer->getSize()); updateCachedElementLimit(mState.mVertexBindings[bindingIndex], mCachedBufferSize[bindingIndex]); } } if (context->isWebGL()) { if (buffer->hasWebGLXFBBindingConflict(true)) { mCachedBufferPropertyTransformFeedbackConflict |= vertexBufferBindingMask; } else { mCachedBufferPropertyTransformFeedbackConflict &= ~vertexBufferBindingMask; } } // Set proper dirty bits on VertexArray mDirtyBits |= mVertexArray->checkBufferForDirtyBits(context, bufferBindingMask); // mIndexRangeInlineCache is no longer invalid mIndexRangeInlineCache = {}; } void VertexArray::onBufferChanged(const Context *context, const Buffer *buffer, angle::SubjectMessage message, VertexArrayBufferBindingMask vertexArrayBufferBindingMask) { VertexArrayBufferBindingMask bufferBindingMask = vertexArrayBufferBindingMask & mBufferBindingMask; ASSERT(buffer); ASSERT(bufferBindingMask.any()); ASSERT(bufferMaskBitsPointToTheSameBuffer(bufferBindingMask)); switch (message) { case angle::SubjectMessage::SubjectChanged: if (mRobustBufferAccessEnabled) { VertexArrayBufferBindingMask VertexBufferBindingMask = bufferBindingMask; VertexBufferBindingMask.reset(kElementArrayBufferIndex); for (size_t bindingIndex : VertexBufferBindingMask) { updateBindingSizeIfChanged(bindingIndex, buffer->getSize()); updateCachedElementLimit(mState.mVertexBindings[bindingIndex], mCachedBufferSize[bindingIndex]); } } // This has to be called after updateCachedElementLimit due to // mCachedElementLimit dependency setDependentDirtyBits(false, bufferBindingMask); break; case angle::SubjectMessage::BindingChanged: if (context->isWebGL()) { bufferBindingMask.reset(kElementArrayBufferIndex); bool hasConflict = buffer->hasWebGLXFBBindingConflict(true); if (hasConflict) { mCachedBufferPropertyTransformFeedbackConflict |= bufferBindingMask; } else { mCachedBufferPropertyTransformFeedbackConflict &= ~bufferBindingMask; } } break; case angle::SubjectMessage::SubjectMapped: bufferBindingMask.reset(kElementArrayBufferIndex); for (size_t bindingIndex : bufferBindingMask) { updateCachedMappedArrayBuffersBinding(bindingIndex); } onStateChange(angle::SubjectMessage::SubjectMapped); break; case angle::SubjectMessage::SubjectUnmapped: { VertexArrayBufferBindingMask VertexBufferBindingMask = bufferBindingMask; VertexBufferBindingMask.reset(kElementArrayBufferIndex); for (size_t bindingIndex : VertexBufferBindingMask) { updateCachedMappedArrayBuffersBinding(bindingIndex); } setDependentDirtyBits(true, bufferBindingMask); onStateChange(angle::SubjectMessage::SubjectUnmapped); } break; case angle::SubjectMessage::InternalMemoryAllocationChanged: setDependentDirtyBits(false, bufferBindingMask); break; case angle::SubjectMessage::ContentsChanged: { VertexArrayBufferBindingMask bufferContentObserverBindingMask = vertexArrayBufferBindingMask & mVertexArray->getContentObserversBindingMask(); if (bufferContentObserverBindingMask.any()) { setDependentDirtyBits(true, bufferBindingMask); } } break; default: UNREACHABLE(); break; } } } // namespace gl