| // | |
| // 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<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(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), mBufferAccessValidationEnabled(false) | |
| {} | |
| VertexArrayPrivate::~VertexArrayPrivate() {} | |
| // 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<uint32_t>(1 << bindingIndex))); | |
| setDirtyAttribBit(bindingIndex, DIRTY_ATTRIB_POINTER); | |
| } | |
| mState.mClientMemoryAttribsMask |= binding.getBoundAttributesMask(); | |
| } | |
| anyBufferDetached = true; | |
| } | |
| } | |
| return anyBufferDetached; | |
| } | |
| 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 (mBufferAccessValidationEnabled) | |
| { | |
| 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]; | |
| } | |
| ANGLE_INLINE 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(mBufferAccessValidationEnabled); | |
| 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; | |
| } | |
| 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<GLuint>(stride) != binding->getStride()); | |
| dirtyBindingBits.set(DIRTY_BINDING_OFFSET, offset != binding->getOffset()); | |
| 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 (mBufferAccessValidationEnabled) | |
| { | |
| mCachedBufferSize[bindingIndex] = boundBuffer ? boundBuffer->getSize() : 0; | |
| 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; | |
| } | |
| } | |
| 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); | |
| } | |
| ANGLE_INLINE 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 (mBufferAccessValidationEnabled) | |
| { | |
| attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex], | |
| mCachedBufferSize[attrib.bindingIndex]); | |
| } | |
| } | |
| void VertexArrayPrivate::setVertexAttribDivisor(size_t attribIndex, GLuint divisor) | |
| { | |
| ASSERT(attribIndex < getMaxAttribs()); | |
| setVertexAttribBinding(attribIndex, static_cast<GLuint>(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; | |
| } | |
| 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<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. | |
| 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<GLintptr>(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; | |
| } | |
| // 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 (mBufferAccessValidationEnabled) | |
| { | |
| for (size_t bindingIndex : bufferBindingMask) | |
| { | |
| Buffer *bufferGL = mVertexArrayBuffers[bindingIndex].get(); | |
| mCachedBufferSize[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); | |
| } | |
| 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; | |
| } | |
| 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 (mBufferAccessValidationEnabled) | |
| { | |
| for (size_t bindingIndex : vertexBufferBindingMask) | |
| { | |
| ASSERT(buffer == mVertexArrayBuffers[bindingIndex].get()); | |
| mCachedBufferSize[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 (mBufferAccessValidationEnabled) | |
| { | |
| VertexArrayBufferBindingMask VertexBufferBindingMask = bufferBindingMask; | |
| VertexBufferBindingMask.reset(kElementArrayBufferIndex); | |
| for (size_t bindingIndex : VertexBufferBindingMask) | |
| { | |
| mCachedBufferSize[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 |