Commit 375ddfc5 by Jamie Madill Committed by Commit Bot

Signal different dirty bit for vertex buffer change.

We use new logic to compare if the attribute format changes before setting dirty bits. This improves performance of VBO-only state changes significantly. On the VBO change Vulkan microbenchmark gives about a 30% improvement. Bug: angleproject:3256 Change-Id: Ifaf1c92ed7a09422156ef79b5983e7349de63346 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1684294Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>
parent 2a4f36b1
......@@ -21,6 +21,11 @@
namespace angle
{
template <typename BitsT, typename ParamT>
constexpr static BitsT Bit(ParamT x)
{
return (static_cast<BitsT>(1) << static_cast<size_t>(x));
}
template <size_t N, typename BitsT, typename ParamT = std::size_t>
class BitSetT final
......@@ -128,14 +133,10 @@ class BitSetT final
Iterator end() const { return Iterator(BitSetT()); }
private:
constexpr static BitsT Bit(ParamT x)
{
return (static_cast<BitsT>(1) << static_cast<size_t>(x));
}
// Produces a mask of ones up to the "x"th bit.
constexpr static BitsT Mask(std::size_t x)
{
return ((Bit(static_cast<ParamT>(x - 1)) - 1) << 1) + 1;
return ((Bit<BitsT>(static_cast<ParamT>(x - 1)) - 1) << 1) + 1;
}
BitsT mBits;
......@@ -283,7 +284,7 @@ constexpr bool BitSetT<N, BitsT, ParamT>::operator[](ParamT pos) const
template <size_t N, typename BitsT, typename ParamT>
bool BitSetT<N, BitsT, ParamT>::test(ParamT pos) const
{
return (mBits & Bit(pos)) != 0;
return (mBits & Bit<BitsT>(pos)) != 0;
}
template <size_t N, typename BitsT, typename ParamT>
......@@ -401,7 +402,7 @@ BitSetT<N, BitsT, ParamT> &BitSetT<N, BitsT, ParamT>::set(ParamT pos, bool value
ASSERT(mBits == (mBits & Mask(N)));
if (value)
{
mBits |= Bit(pos) & Mask(N);
mBits |= Bit<BitsT>(pos) & Mask(N);
}
else
{
......@@ -422,7 +423,7 @@ template <size_t N, typename BitsT, typename ParamT>
BitSetT<N, BitsT, ParamT> &BitSetT<N, BitsT, ParamT>::reset(ParamT pos)
{
ASSERT(mBits == (mBits & Mask(N)));
mBits &= ~Bit(pos);
mBits &= ~Bit<BitsT>(pos);
return *this;
}
......@@ -438,7 +439,7 @@ template <size_t N, typename BitsT, typename ParamT>
BitSetT<N, BitsT, ParamT> &BitSetT<N, BitsT, ParamT>::flip(ParamT pos)
{
ASSERT(mBits == (mBits & Mask(N)));
mBits ^= Bit(pos) & Mask(N);
mBits ^= Bit<BitsT>(pos) & Mask(N);
return *this;
}
......
......@@ -291,7 +291,7 @@ ANGLE_INLINE void VertexArray::updateCachedTransformFeedbackBindingValidation(si
mCachedTransformFeedbackConflictedBindingsMask.set(bindingIndex, hasConflict);
}
void VertexArray::bindVertexBufferImpl(const Context *context,
bool VertexArray::bindVertexBufferImpl(const Context *context,
size_t bindingIndex,
Buffer *boundBuffer,
GLintptr offset,
......@@ -302,7 +302,17 @@ void VertexArray::bindVertexBufferImpl(const Context *context,
VertexBinding *binding = &mState.mVertexBindings[bindingIndex];
Buffer *oldBuffer = binding->getBuffer().get();
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);
......@@ -336,6 +346,8 @@ void VertexArray::bindVertexBufferImpl(const Context *context,
mState.mClientMemoryAttribsMask |= binding->getBoundAttributesMask();
updateCachedMappedArrayBuffers(false, binding->getBoundAttributesMask());
}
return true;
}
void VertexArray::bindVertexBuffer(const Context *context,
......@@ -344,8 +356,10 @@ void VertexArray::bindVertexBuffer(const Context *context,
GLintptr offset,
GLsizei stride)
{
bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride);
setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER);
if (bindVertexBufferImpl(context, bindingIndex, boundBuffer, offset, stride))
{
setDirtyBindingBit(bindingIndex, DIRTY_BINDING_BUFFER);
}
}
void VertexArray::setVertexAttribBinding(const Context *context,
......@@ -385,7 +399,7 @@ void VertexArray::setVertexBindingDivisor(size_t bindingIndex, GLuint divisor)
}
}
ANGLE_INLINE void VertexArray::setVertexAttribFormatImpl(VertexAttribute *attrib,
ANGLE_INLINE bool VertexArray::setVertexAttribFormatImpl(VertexAttribute *attrib,
GLint size,
VertexAttribType type,
bool normalized,
......@@ -393,8 +407,15 @@ ANGLE_INLINE void VertexArray::setVertexAttribFormatImpl(VertexAttribute *attrib
GLuint relativeOffset)
{
angle::FormatID formatID = gl::GetVertexFormatID(type, normalized, size, pureInteger);
attrib->format = &angle::Format::Get(formatID);
attrib->relativeOffset = relativeOffset;
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,
......@@ -409,8 +430,10 @@ void VertexArray::setVertexAttribFormat(size_t attribIndex,
ComponentType componentType = GetVertexAttributeComponentType(pureInteger, type);
SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask);
setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, relativeOffset);
setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_FORMAT);
if (setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, relativeOffset))
{
setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_FORMAT);
}
attrib.updateCachedElementLimit(mState.mVertexBindings[attrib.bindingIndex]);
}
......@@ -463,17 +486,33 @@ ANGLE_INLINE void VertexArray::setVertexAttribPointerImpl(const Context *context
SetComponentTypeMask(componentType, attribIndex, &mState.mVertexAttributesTypeMask);
setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, 0);
setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex));
bool attribDirty = setVertexAttribFormatImpl(&attrib, size, type, normalized, pureInteger, 0);
if (attrib.bindingIndex != attribIndex)
{
setVertexAttribBinding(context, attribIndex, static_cast<GLuint>(attribIndex));
}
GLsizei effectiveStride =
stride != 0 ? stride : static_cast<GLsizei>(ComputeVertexAttributeTypeSize(attrib));
if (pointer != attrib.pointer || attrib.vertexAttribArrayStride != static_cast<GLuint>(stride))
{
attribDirty = true;
}
attrib.pointer = pointer;
attrib.vertexAttribArrayStride = stride;
bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride);
setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER);
if (bindVertexBufferImpl(context, attribIndex, boundBuffer, offset, effectiveStride) &&
!attribDirty)
{
setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER_BUFFER);
}
else if (attribDirty)
{
setDirtyAttribBit(attribIndex, DIRTY_ATTRIB_POINTER);
}
mState.mNullPointerClientMemoryAttribsMask.set(attribIndex,
boundBuffer == nullptr && pointer == nullptr);
......
......@@ -100,6 +100,65 @@ class VertexArray final : public angle::ObserverInterface,
public angle::Subject
{
public:
// Dirty bits for VertexArrays use a heirarchical design. At the top level, each attribute
// has a single dirty bit. Then an array of MAX_ATTRIBS dirty bits each has a dirty bit for
// enabled/pointer/format/binding. Bindings are handled similarly. Note that because the
// total number of dirty bits is 33, it will not be as fast on a 32-bit machine, which
// can't support the advanced 64-bit scanning intrinsics. We could consider packing the
// binding and attribute bits together if this becomes a problem.
//
// Special note on "DIRTY_ATTRIB_POINTER_BUFFER": this is a special case when the app
// calls glVertexAttribPointer but only changes a VBO and/or offset binding. This allows
// the Vulkan back-end to skip performing a pipeline change for performance.
enum DirtyBitType
{
DIRTY_BIT_ELEMENT_ARRAY_BUFFER,
DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA,
// Dirty bits for attributes.
DIRTY_BIT_ATTRIB_0,
DIRTY_BIT_ATTRIB_MAX = DIRTY_BIT_ATTRIB_0 + gl::MAX_VERTEX_ATTRIBS,
// Dirty bits for bindings.
DIRTY_BIT_BINDING_0 = DIRTY_BIT_ATTRIB_MAX,
DIRTY_BIT_BINDING_MAX = DIRTY_BIT_BINDING_0 + gl::MAX_VERTEX_ATTRIB_BINDINGS,
// We keep separate dirty bits for bound buffers whose data changed since last update.
DIRTY_BIT_BUFFER_DATA_0 = DIRTY_BIT_BINDING_MAX,
DIRTY_BIT_BUFFER_DATA_MAX = DIRTY_BIT_BUFFER_DATA_0 + gl::MAX_VERTEX_ATTRIB_BINDINGS,
DIRTY_BIT_UNKNOWN = DIRTY_BIT_BUFFER_DATA_MAX,
DIRTY_BIT_MAX = DIRTY_BIT_UNKNOWN,
};
// We want to keep the number of dirty bits within 64 to keep iteration times fast.
static_assert(DIRTY_BIT_MAX <= 64, "Too many vertex array dirty bits.");
enum DirtyAttribBitType
{
DIRTY_ATTRIB_ENABLED,
DIRTY_ATTRIB_POINTER,
DIRTY_ATTRIB_FORMAT,
DIRTY_ATTRIB_BINDING,
DIRTY_ATTRIB_POINTER_BUFFER,
DIRTY_ATTRIB_UNKNOWN,
DIRTY_ATTRIB_MAX = DIRTY_ATTRIB_UNKNOWN,
};
enum DirtyBindingBitType
{
DIRTY_BINDING_BUFFER,
DIRTY_BINDING_DIVISOR,
DIRTY_BINDING_UNKNOWN,
DIRTY_BINDING_MAX = DIRTY_BINDING_UNKNOWN,
};
using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
using DirtyAttribBits = angle::BitSet<DIRTY_ATTRIB_MAX>;
using DirtyBindingBits = angle::BitSet<DIRTY_BINDING_MAX>;
using DirtyAttribBitsArray = std::array<DirtyAttribBits, gl::MAX_VERTEX_ATTRIBS>;
using DirtyBindingBitsArray = std::array<DirtyBindingBits, gl::MAX_VERTEX_ATTRIB_BINDINGS>;
VertexArray(rx::GLImplFactory *factory, GLuint id, size_t maxAttribs, size_t maxAttribBindings);
void onDestroy(const Context *context);
......@@ -152,17 +211,6 @@ class VertexArray final : public angle::ObserverInterface,
GLsizei stride);
void setVertexAttribBinding(const Context *context, size_t attribIndex, GLuint bindingIndex);
void setVertexBindingDivisor(size_t bindingIndex, GLuint divisor);
void setVertexAttribFormatImpl(VertexAttribute *attrib,
GLint size,
VertexAttribType type,
bool normalized,
bool pureInteger,
GLuint relativeOffset);
void bindVertexBufferImpl(const Context *context,
size_t bindingIndex,
Buffer *boundBuffer,
GLintptr offset,
GLsizei stride);
Buffer *getElementArrayBuffer() const { return mState.getElementArrayBuffer(); }
size_t getMaxAttribs() const { return mState.getMaxAttribs(); }
......@@ -199,60 +247,6 @@ class VertexArray final : public angle::ObserverInterface,
// Observer implementation
void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;
// Dirty bits for VertexArrays use a heirarchical design. At the top level, each attribute
// has a single dirty bit. Then an array of MAX_ATTRIBS dirty bits each has a dirty bit for
// enabled/pointer/format/binding. Bindings are handled similarly. Note that because the
// total number of dirty bits is 33, it will not be as fast on a 32-bit machine, which
// can't support the advanced 64-bit scanning intrinsics. We could consider packing the
// binding and attribute bits together if this becomes a problem.
enum DirtyBitType
{
DIRTY_BIT_ELEMENT_ARRAY_BUFFER,
DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA,
// Dirty bits for attributes.
DIRTY_BIT_ATTRIB_0,
DIRTY_BIT_ATTRIB_MAX = DIRTY_BIT_ATTRIB_0 + gl::MAX_VERTEX_ATTRIBS,
// Dirty bits for bindings.
DIRTY_BIT_BINDING_0 = DIRTY_BIT_ATTRIB_MAX,
DIRTY_BIT_BINDING_MAX = DIRTY_BIT_BINDING_0 + gl::MAX_VERTEX_ATTRIB_BINDINGS,
// We keep separate dirty bits for bound buffers whose data changed since last update.
DIRTY_BIT_BUFFER_DATA_0 = DIRTY_BIT_BINDING_MAX,
DIRTY_BIT_BUFFER_DATA_MAX = DIRTY_BIT_BUFFER_DATA_0 + gl::MAX_VERTEX_ATTRIB_BINDINGS,
DIRTY_BIT_UNKNOWN = DIRTY_BIT_BUFFER_DATA_MAX,
DIRTY_BIT_MAX = DIRTY_BIT_UNKNOWN,
};
// We want to keep the number of dirty bits within 64 to keep iteration times fast.
static_assert(DIRTY_BIT_MAX <= 64, "Too many vertex array dirty bits.");
enum DirtyAttribBitType
{
DIRTY_ATTRIB_ENABLED,
DIRTY_ATTRIB_POINTER,
DIRTY_ATTRIB_FORMAT,
DIRTY_ATTRIB_BINDING,
DIRTY_ATTRIB_UNKNOWN,
DIRTY_ATTRIB_MAX = DIRTY_ATTRIB_UNKNOWN,
};
enum DirtyBindingBitType
{
DIRTY_BINDING_BUFFER,
DIRTY_BINDING_DIVISOR,
DIRTY_BINDING_UNKNOWN,
DIRTY_BINDING_MAX = DIRTY_BINDING_UNKNOWN,
};
using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
using DirtyAttribBits = angle::BitSet<DIRTY_ATTRIB_MAX>;
using DirtyBindingBits = angle::BitSet<DIRTY_BINDING_MAX>;
using DirtyAttribBitsArray = std::array<DirtyAttribBits, gl::MAX_VERTEX_ATTRIBS>;
using DirtyBindingBitsArray = std::array<DirtyBindingBits, gl::MAX_VERTEX_ATTRIB_BINDINGS>;
static size_t GetVertexIndexFromDirtyBit(size_t dirtyBit);
angle::Result syncState(const Context *context);
......@@ -319,6 +313,19 @@ class VertexArray final : public angle::ObserverInterface,
GLsizei stride,
const void *pointer);
// These two functions return true if the state was dirty.
bool setVertexAttribFormatImpl(VertexAttribute *attrib,
GLint size,
VertexAttribType type,
bool normalized,
bool pureInteger,
GLuint relativeOffset);
bool bindVertexBufferImpl(const Context *context,
size_t bindingIndex,
Buffer *boundBuffer,
GLintptr offset,
GLsizei stride);
GLuint mId;
VertexArrayState mState;
......
......@@ -606,6 +606,7 @@ void VertexArrayGL::syncDirtyAttrib(const gl::Context *context,
updateAttribEnabled(attribIndex);
break;
case VertexArray::DIRTY_ATTRIB_POINTER_BUFFER:
case VertexArray::DIRTY_ATTRIB_POINTER:
updateAttribPointer(context, attribIndex);
break;
......
......@@ -182,6 +182,8 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::CommandBuff
mDirtyBits.set(DIRTY_BIT_INDEX_BUFFER);
}
ANGLE_INLINE void invalidateVertexBuffers() { mDirtyBits.set(DIRTY_BIT_VERTEX_BUFFERS); }
ANGLE_INLINE void onVertexAttributeChange(size_t attribIndex,
GLuint stride,
GLuint divisor,
......
......@@ -319,14 +319,10 @@ angle::Result VertexArrayVk::syncState(const gl::Context *context,
{
ASSERT(dirtyBits.any());
bool invalidateContext = false;
ContextVk *contextVk = vk::GetImpl(context);
// Rebuild current attribute buffers cache. This will fail horribly if the buffer changes.
// TODO(jmadill): Handle buffer storage changes.
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings();
for (size_t dirtyBit : dirtyBits)
{
......@@ -359,12 +355,19 @@ angle::Result VertexArrayVk::syncState(const gl::Context *context,
mDirtyLineLoopTranslation = true;
break;
#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX) \
case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \
ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX], \
bindings[attribs[INDEX].bindingIndex], INDEX)); \
invalidateContext = true; \
(*attribBits)[INDEX].reset(); \
#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX) \
case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \
if ((*attribBits)[INDEX].to_ulong() == \
angle::Bit<unsigned long>(gl::VertexArray::DIRTY_ATTRIB_POINTER_BUFFER)) \
{ \
syncDirtyBuffer(contextVk, bindings[INDEX], INDEX); \
} \
else \
{ \
ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX], \
bindings[attribs[INDEX].bindingIndex], INDEX)); \
} \
(*attribBits)[INDEX].reset(); \
break;
ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC)
......@@ -373,7 +376,6 @@ angle::Result VertexArrayVk::syncState(const gl::Context *context,
case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX: \
ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX], \
bindings[attribs[INDEX].bindingIndex], INDEX)); \
invalidateContext = true; \
(*bindingBits)[INDEX].reset(); \
break;
......@@ -393,14 +395,13 @@ angle::Result VertexArrayVk::syncState(const gl::Context *context,
}
}
if (invalidateContext)
{
contextVk->invalidateVertexAndIndexBuffers();
}
return angle::Result::Continue;
}
#undef ANGLE_VERTEX_DIRTY_ATTRIB_FUNC
#undef ANGLE_VERTEX_DIRTY_BINDING_FUNC
#undef ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC
ANGLE_INLINE void VertexArrayVk::setDefaultPackedInput(ContextVk *contextVk, size_t attribIndex)
{
const gl::State &glState = contextVk->getState();
......@@ -472,9 +473,10 @@ angle::Result VertexArrayVk::syncDirtyAttrib(ContextVk *contextVk,
}
else
{
mCurrentArrayBuffers[attribIndex] = &bufferVk->getBuffer();
mCurrentArrayBufferHandles[attribIndex] =
bufferVk->getBuffer().getBuffer().getHandle();
vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
mCurrentArrayBuffers[attribIndex] = &bufferHelper;
mCurrentArrayBufferHandles[attribIndex] = bufferHelper.getBuffer().getHandle();
mCurrentArrayBufferOffsets[attribIndex] = binding.getOffset();
stride = binding.getStride();
}
......@@ -511,6 +513,30 @@ angle::Result VertexArrayVk::syncDirtyAttrib(ContextVk *contextVk,
return angle::Result::Continue;
}
void VertexArrayVk::syncDirtyBuffer(ContextVk *contextVk,
const gl::VertexBinding &binding,
size_t bindingIndex)
{
gl::Buffer *bufferGL = binding.getBuffer().get();
if (bufferGL)
{
BufferVk *bufferVk = vk::GetImpl(bufferGL);
vk::BufferHelper &bufferHelper = bufferVk->getBuffer();
mCurrentArrayBuffers[bindingIndex] = &bufferHelper;
mCurrentArrayBufferHandles[bindingIndex] = bufferHelper.getBuffer().getHandle();
mCurrentArrayBufferOffsets[bindingIndex] = binding.getOffset();
}
else
{
mCurrentArrayBuffers[bindingIndex] = &mTheNullBuffer;
mCurrentArrayBufferHandles[bindingIndex] = mTheNullBuffer.getBuffer().getHandle();
mCurrentArrayBufferOffsets[bindingIndex] = 0;
}
contextVk->invalidateVertexBuffers();
}
angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context,
GLint firstVertex,
GLsizei vertexOrIndexCount,
......
......@@ -108,6 +108,10 @@ class VertexArrayVk : public VertexArrayImpl
const gl::VertexBinding &binding,
size_t attribIndex);
void syncDirtyBuffer(ContextVk *contextVk,
const gl::VertexBinding &binding,
size_t bindingIndex);
gl::AttribArray<VkBuffer> mCurrentArrayBufferHandles;
gl::AttribArray<VkDeviceSize> mCurrentArrayBufferOffsets;
gl::AttribArray<vk::BufferHelper *> mCurrentArrayBuffers;
......
......@@ -1723,7 +1723,7 @@ TEST_P(VertexAttributeCachingTest, BufferMulticaching)
ASSERT_GL_NO_ERROR();
for (const auto &data : mTestData)
for (const AttribData &data : mTestData)
{
const auto &expected =
(data.normalized) ? mNormExpectedData[data.type] : mExpectedData[data.type];
......@@ -1738,7 +1738,7 @@ TEST_P(VertexAttributeCachingTest, BufferMulticaching)
sizeof(GLfloat) * baseStride, expected.data());
drawQuad(mProgram, "position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 255, 255, 255);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment