Source code
Revision control
Copy as Markdown
Other Tools
//
// 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