Re-re-land "D3D11: Implement dirty bits for VertexArray11.""

Translated attributes are now stored in the VertexArray11 in a cache,
and only updated when dirty bits change. Currently dynamic attributes
must be re-translated every call, so these are stored in a list and
processed repeatedly.

This skips doing a lot of the VertexDataManager work for vertex
attributes that don't change between draw calls.

Current value attributes, which correspond to disabled attributes that
the program will pulls vertex data from, are owned by the Context, so
these need to be handled outside of the VertexArray11.

Further changes will be necessary to reduce the redundant work we do in
the InputLayoutCache. We shouldn't need to re-check the cache if
nothing relevant changed.

This give about a 23% performance improvement on the draw call
benchmark on my machine.

Re-land with a fix for the start vertex offset.

Re-re-land with a fix for using XFB with deleted buffers.

BUG=angleproject:1327

Change-Id: I0fba49515375c149bbf54d933f8d1f747fbb8158
Reviewed-on: https://chromium-review.googlesource.com/338003
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/VertexArray.cpp b/src/libANGLE/VertexArray.cpp
index 8d51e9b..a1ca19a 100644
--- a/src/libANGLE/VertexArray.cpp
+++ b/src/libANGLE/VertexArray.cpp
@@ -29,9 +29,7 @@
 }
 
 VertexArray::VertexArray(rx::ImplFactory *factory, GLuint id, size_t maxAttribs)
-    : mId(id),
-      mVertexArray(factory->createVertexArray(mData)),
-      mData(maxAttribs)
+    : mId(id), mData(maxAttribs), mVertexArray(factory->createVertexArray(mData))
 {
 }
 
diff --git a/src/libANGLE/VertexArray.h b/src/libANGLE/VertexArray.h
index 6bc267d..fe262fd 100644
--- a/src/libANGLE/VertexArray.h
+++ b/src/libANGLE/VertexArray.h
@@ -114,10 +114,10 @@
   private:
     GLuint mId;
 
-    rx::VertexArrayImpl *mVertexArray;
-
     Data mData;
     DirtyBits mDirtyBits;
+
+    rx::VertexArrayImpl *mVertexArray;
 };
 
 }
diff --git a/src/libANGLE/renderer/d3d/BufferD3D.cpp b/src/libANGLE/renderer/d3d/BufferD3D.cpp
index d88dad7..7139937 100644
--- a/src/libANGLE/renderer/d3d/BufferD3D.cpp
+++ b/src/libANGLE/renderer/d3d/BufferD3D.cpp
@@ -161,13 +161,13 @@
 // Creates static buffers if sufficient used data has been left unmodified
 void BufferD3D::promoteStaticUsage(int dataSize)
 {
-    if (mStaticVertexBuffers.empty() && !mStaticIndexBuffer)
+    if (mUsage == D3DBufferUsage::DYNAMIC)
     {
         mUnmodifiedDataUse += dataSize;
 
         if (mUnmodifiedDataUse > 3 * getSize())
         {
-            initializeStaticData();
+            updateD3DBufferUsage(GL_STATIC_DRAW);
         }
     }
 }
diff --git a/src/libANGLE/renderer/d3d/BufferD3D.h b/src/libANGLE/renderer/d3d/BufferD3D.h
index 4dc3626..3c43c01 100644
--- a/src/libANGLE/renderer/d3d/BufferD3D.h
+++ b/src/libANGLE/renderer/d3d/BufferD3D.h
@@ -43,8 +43,8 @@
     StaticVertexBufferInterface *getStaticVertexBuffer(const gl::VertexAttribute &attribute);
     StaticIndexBufferInterface *getStaticIndexBuffer();
 
-    void initializeStaticData();
-    void invalidateStaticData();
+    virtual void initializeStaticData();
+    virtual void invalidateStaticData();
 
     void promoteStaticUsage(int dataSize);
 
diff --git a/src/libANGLE/renderer/d3d/RendererD3D.cpp b/src/libANGLE/renderer/d3d/RendererD3D.cpp
index b26b305..da86a49 100644
--- a/src/libANGLE/renderer/d3d/RendererD3D.cpp
+++ b/src/libANGLE/renderer/d3d/RendererD3D.cpp
@@ -55,7 +55,6 @@
 
 void RendererD3D::cleanup()
 {
-    mTranslatedAttribCache.clear();
     mScratchMemoryBuffer.resize(0);
     for (auto &incompleteTexture : mIncompleteTextures)
     {
@@ -199,7 +198,7 @@
 
     if (!skipDraw(data, mode))
     {
-        ANGLE_TRY(drawArraysImpl(data, mode, count, instances));
+        ANGLE_TRY(drawArraysImpl(data, mode, first, count, instances));
 
         if (data.state->isTransformFeedbackActiveUnpaused())
         {
diff --git a/src/libANGLE/renderer/d3d/RendererD3D.h b/src/libANGLE/renderer/d3d/RendererD3D.h
index 3cb8e91..b21c7e8 100644
--- a/src/libANGLE/renderer/d3d/RendererD3D.h
+++ b/src/libANGLE/renderer/d3d/RendererD3D.h
@@ -285,8 +285,6 @@
     void initializeDebugAnnotator();
     gl::DebugAnnotator *mAnnotator;
 
-    std::vector<TranslatedAttribute> mTranslatedAttribCache;
-
     bool mPresentPathFastEnabled;
 
   private:
@@ -306,6 +304,7 @@
 
     virtual gl::Error drawArraysImpl(const gl::Data &data,
                                      GLenum mode,
+                                     GLint startVertex,
                                      GLsizei count,
                                      GLsizei instances) = 0;
     virtual gl::Error drawElementsImpl(const gl::Data &data,
diff --git a/src/libANGLE/renderer/d3d/VertexBuffer.h b/src/libANGLE/renderer/d3d/VertexBuffer.h
index b460748..b3d9824 100644
--- a/src/libANGLE/renderer/d3d/VertexBuffer.h
+++ b/src/libANGLE/renderer/d3d/VertexBuffer.h
@@ -80,7 +80,7 @@
 
     unsigned int getSerial() const;
 
-    VertexBuffer* getVertexBuffer() const;
+    VertexBuffer *getVertexBuffer() const;
 
   protected:
     gl::Error discard();
diff --git a/src/libANGLE/renderer/d3d/VertexDataManager.cpp b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
index 724a58b..3cd94a8 100644
--- a/src/libANGLE/renderer/d3d/VertexDataManager.cpp
+++ b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
@@ -9,6 +9,7 @@
 
 #include "libANGLE/renderer/d3d/VertexDataManager.h"
 
+#include "common/BitSetIterator.h"
 #include "libANGLE/Buffer.h"
 #include "libANGLE/formatutils.h"
 #include "libANGLE/Program.h"
@@ -105,7 +106,8 @@
     : active(false),
       attribute(nullptr),
       currentValueType(GL_NONE),
-      offset(0),
+      baseOffset(0),
+      usesFirstVertexOffset(false),
       stride(0),
       vertexBuffer(),
       storage(nullptr),
@@ -114,6 +116,31 @@
 {
 }
 
+gl::ErrorOrResult<unsigned int> TranslatedAttribute::computeOffset(GLint startVertex) const
+{
+    unsigned int offset = baseOffset;
+    if (usesFirstVertexOffset)
+    {
+        unsigned int startVertexUnsigned = static_cast<unsigned int>(startVertex);
+
+        if (!IsUnsignedMultiplicationSafe(stride, startVertexUnsigned))
+        {
+            return gl::Error(GL_INVALID_OPERATION,
+                             "Multiplication overflow in TranslatedAttribute::computeOffset");
+        }
+
+        unsigned int strideOffset = stride * startVertexUnsigned;
+        if (!IsUnsignedAdditionSafe(offset, strideOffset))
+        {
+            return gl::Error(GL_INVALID_OPERATION,
+                             "Addition overflow in TranslatedAttribute::computeOffset");
+        }
+
+        offset += strideOffset;
+    }
+    return offset;
+}
+
 VertexStorageType ClassifyAttributeStorage(const gl::VertexAttribute &attrib)
 {
     // If attribute is disabled, we use the current value.
@@ -185,11 +212,6 @@
     SafeDelete(mStreamingBuffer);
 }
 
-void VertexDataManager::unmapStreamingBuffer()
-{
-    mStreamingBuffer->getVertexBuffer()->hintUnmapResource();
-}
-
 gl::Error VertexDataManager::prepareVertexData(const gl::State &state,
                                                GLint start,
                                                GLsizei count,
@@ -201,7 +223,7 @@
     const gl::VertexArray *vertexArray = state.getVertexArray();
     const auto &vertexAttributes       = vertexArray->getVertexAttributes();
 
-    mDynamicAttributeIndexesCache.clear();
+    mDynamicAttribsMaskCache.reset();
     const gl::Program *program = state.getProgram();
 
     translatedAttribs->clear();
@@ -232,28 +254,20 @@
             case VertexStorageType::STATIC:
             {
                 // Store static attribute.
-                gl::Error error = StoreStaticAttrib(translated, start, count, instances);
-                if (error.isError())
-                {
-                    return error;
-                }
+                ANGLE_TRY(StoreStaticAttrib(translated, count, instances));
                 break;
             }
             case VertexStorageType::DYNAMIC:
                 // Dynamic attributes must be handled together.
-                mDynamicAttributeIndexesCache.push_back(attribIndex);
+                mDynamicAttribsMaskCache.set(attribIndex);
                 break;
             case VertexStorageType::DIRECT:
                 // Update translated data for direct attributes.
-                StoreDirectAttrib(translated, start);
+                StoreDirectAttrib(translated);
                 break;
             case VertexStorageType::CURRENT_VALUE:
             {
-                gl::Error error = storeCurrentValue(currentValueData, translated, attribIndex);
-                if (error.isError())
-                {
-                    return error;
-                }
+                ANGLE_TRY(storeCurrentValue(currentValueData, translated, attribIndex));
                 break;
             }
             default:
@@ -262,37 +276,39 @@
         }
     }
 
-    if (mDynamicAttributeIndexesCache.empty())
+    if (mDynamicAttribsMaskCache.none())
     {
-        gl::Error(GL_NO_ERROR);
+        return gl::NoError();
     }
 
-    return storeDynamicAttribs(translatedAttribs, mDynamicAttributeIndexesCache, start, count,
-                               instances);
+    ANGLE_TRY(
+        storeDynamicAttribs(translatedAttribs, mDynamicAttribsMaskCache, start, count, instances));
+
+    PromoteDynamicAttribs(*translatedAttribs, mDynamicAttribsMaskCache, count);
+
+    return gl::NoError();
 }
 
 // static
-void VertexDataManager::StoreDirectAttrib(TranslatedAttribute *directAttrib, GLint start)
+void VertexDataManager::StoreDirectAttrib(TranslatedAttribute *directAttrib)
 {
     const auto &attrib   = *directAttrib->attribute;
     gl::Buffer *buffer   = attrib.buffer.get();
     BufferD3D *bufferD3D = buffer ? GetImplAs<BufferD3D>(buffer) : nullptr;
 
-    // Instanced vertices do not apply the 'start' offset
-    GLint firstVertexIndex = (attrib.divisor > 0 ? 0 : start);
-
     ASSERT(DirectStoragePossible(attrib));
     directAttrib->vertexBuffer.set(nullptr);
     directAttrib->storage = bufferD3D;
     directAttrib->serial  = bufferD3D->getSerial();
     directAttrib->stride = static_cast<unsigned int>(ComputeVertexAttributeStride(attrib));
-    directAttrib->offset =
-        static_cast<unsigned int>(attrib.offset + directAttrib->stride * firstVertexIndex);
+    directAttrib->baseOffset = static_cast<unsigned int>(attrib.offset);
+
+    // Instanced vertices do not apply the 'start' offset
+    directAttrib->usesFirstVertexOffset = (attrib.divisor == 0);
 }
 
 // static
 gl::Error VertexDataManager::StoreStaticAttrib(TranslatedAttribute *translated,
-                                               GLint start,
                                                GLsizei count,
                                                GLsizei instances)
 {
@@ -302,29 +318,17 @@
     ASSERT(buffer && attrib.enabled && !DirectStoragePossible(attrib));
     BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);
 
-    // Instanced vertices do not apply the 'start' offset
-    GLint firstVertexIndex = (attrib.divisor > 0 ? 0 : start);
-
     // Compute source data pointer
     const uint8_t *sourceData = nullptr;
 
-    gl::Error error = bufferD3D->getData(&sourceData);
-    if (error.isError())
-    {
-        return error;
-    }
+    ANGLE_TRY(bufferD3D->getData(&sourceData));
     sourceData += static_cast<int>(attrib.offset);
 
     unsigned int streamOffset = 0;
 
-    auto errorOrOutputElementSize = bufferD3D->getFactory()->getVertexSpaceRequired(attrib, 1, 0);
-    if (errorOrOutputElementSize.isError())
-    {
-        return errorOrOutputElementSize.getError();
-    }
-
     translated->storage = nullptr;
-    translated->stride  = errorOrOutputElementSize.getResult();
+    ANGLE_TRY_RESULT(bufferD3D->getFactory()->getVertexSpaceRequired(attrib, 1, 0),
+                     translated->stride);
 
     auto *staticBuffer = bufferD3D->getStaticVertexBuffer(attrib);
     ASSERT(staticBuffer);
@@ -336,74 +340,91 @@
         int startIndex = static_cast<int>(attrib.offset) /
                          static_cast<int>(ComputeVertexAttributeStride(attrib));
 
-        error = staticBuffer->storeStaticAttribute(attrib, -startIndex, totalCount, 0, sourceData);
-        if (error.isError())
-        {
-            return error;
-        }
+        ANGLE_TRY(
+            staticBuffer->storeStaticAttribute(attrib, -startIndex, totalCount, 0, sourceData));
     }
 
     unsigned int firstElementOffset =
         (static_cast<unsigned int>(attrib.offset) /
          static_cast<unsigned int>(ComputeVertexAttributeStride(attrib))) *
         translated->stride;
-    ASSERT(attrib.divisor == 0 || firstVertexIndex == 0);
-    unsigned int startOffset = firstVertexIndex * translated->stride;
-    if (streamOffset + firstElementOffset + startOffset < streamOffset)
-    {
-        return gl::Error(GL_OUT_OF_MEMORY);
-    }
 
     VertexBuffer *vertexBuffer = staticBuffer->getVertexBuffer();
 
+    if (!IsUnsignedAdditionSafe(streamOffset, firstElementOffset))
+    {
+        return gl::Error(GL_INVALID_OPERATION,
+                         "Integer overflow in VertexDataManager::StoreStaticAttrib");
+    }
+
     translated->vertexBuffer.set(vertexBuffer);
     translated->serial = vertexBuffer->getSerial();
-    translated->offset = streamOffset + firstElementOffset + startOffset;
+    translated->baseOffset = streamOffset + firstElementOffset;
 
-    return gl::Error(GL_NO_ERROR);
+    // Instanced vertices do not apply the 'start' offset
+    translated->usesFirstVertexOffset = (attrib.divisor == 0);
+
+    return gl::NoError();
 }
 
 gl::Error VertexDataManager::storeDynamicAttribs(
     std::vector<TranslatedAttribute> *translatedAttribs,
-    const std::vector<size_t> &dynamicAttribIndexes,
+    const gl::AttributesMask &dynamicAttribsMask,
     GLint start,
     GLsizei count,
     GLsizei instances)
 {
+    // Instantiating this class will ensure the streaming buffer is never left mapped.
+    class StreamingBufferUnmapper final : angle::NonCopyable
+    {
+      public:
+        StreamingBufferUnmapper(StreamingVertexBufferInterface *streamingBuffer)
+            : mStreamingBuffer(streamingBuffer)
+        {
+            ASSERT(mStreamingBuffer);
+        }
+        ~StreamingBufferUnmapper() { mStreamingBuffer->getVertexBuffer()->hintUnmapResource(); }
+
+      private:
+        StreamingVertexBufferInterface *mStreamingBuffer;
+    };
+
+    // Will trigger unmapping on return.
+    StreamingBufferUnmapper localUnmapper(mStreamingBuffer);
+
     // Reserve the required space for the dynamic buffers.
-    for (size_t attribIndex : dynamicAttribIndexes)
+    for (auto attribIndex : angle::IterateBitSet(dynamicAttribsMask))
     {
         const auto &dynamicAttrib = (*translatedAttribs)[attribIndex];
-        gl::Error error = reserveSpaceForAttrib(dynamicAttrib, count, instances);
-        if (error.isError())
-        {
-            return error;
-        }
+        ANGLE_TRY(reserveSpaceForAttrib(dynamicAttrib, count, instances));
     }
 
     // Store dynamic attributes
-    for (size_t attribIndex : dynamicAttribIndexes)
+    for (auto attribIndex : angle::IterateBitSet(dynamicAttribsMask))
     {
         auto *dynamicAttrib = &(*translatedAttribs)[attribIndex];
-        gl::Error error = storeDynamicAttrib(dynamicAttrib, start, count, instances);
-        if (error.isError())
-        {
-            unmapStreamingBuffer();
-            return error;
-        }
+        ANGLE_TRY(storeDynamicAttrib(dynamicAttrib, start, count, instances));
+    }
 
-        // Promote static usage of dynamic buffers.
-        gl::Buffer *buffer = dynamicAttrib->attribute->buffer.get();
+    return gl::NoError();
+}
+
+void VertexDataManager::PromoteDynamicAttribs(
+    const std::vector<TranslatedAttribute> &translatedAttribs,
+    const gl::AttributesMask &dynamicAttribsMask,
+    GLsizei count)
+{
+    for (auto attribIndex : angle::IterateBitSet(dynamicAttribsMask))
+    {
+        const auto &dynamicAttrib = translatedAttribs[attribIndex];
+        gl::Buffer *buffer = dynamicAttrib.attribute->buffer.get();
         if (buffer)
         {
             BufferD3D *bufferD3D = GetImplAs<BufferD3D>(buffer);
-            size_t typeSize = ComputeVertexAttributeTypeSize(*dynamicAttrib->attribute);
+            size_t typeSize = ComputeVertexAttributeTypeSize(*dynamicAttrib.attribute);
             bufferD3D->promoteStaticUsage(count * static_cast<int>(typeSize));
         }
     }
-
-    unmapStreamingBuffer();
-    return gl::Error(GL_NO_ERROR);
 }
 
 gl::Error VertexDataManager::reserveSpaceForAttrib(const TranslatedAttribute &translatedAttrib,
@@ -448,11 +469,7 @@
 
     if (buffer)
     {
-        gl::Error error = storage->getData(&sourceData);
-        if (error.isError())
-        {
-            return error;
-        }
+        ANGLE_TRY(storage->getData(&sourceData));
         sourceData += static_cast<int>(attrib.offset);
     }
     else
@@ -462,32 +479,23 @@
 
     unsigned int streamOffset = 0;
 
-    auto errorOrOutputElementSize = mFactory->getVertexSpaceRequired(attrib, 1, 0);
-    if (errorOrOutputElementSize.isError())
-    {
-        return errorOrOutputElementSize.getError();
-    }
-
     translated->storage = nullptr;
-    translated->stride  = errorOrOutputElementSize.getResult();
+    ANGLE_TRY_RESULT(mFactory->getVertexSpaceRequired(attrib, 1, 0), translated->stride);
 
     size_t totalCount = ComputeVertexAttributeElementCount(attrib, count, instances);
 
-    gl::Error error = mStreamingBuffer->storeDynamicAttribute(
+    ANGLE_TRY(mStreamingBuffer->storeDynamicAttribute(
         attrib, translated->currentValueType, firstVertexIndex, static_cast<GLsizei>(totalCount),
-        instances, &streamOffset, sourceData);
-    if (error.isError())
-    {
-        return error;
-    }
+        instances, &streamOffset, sourceData));
 
     VertexBuffer *vertexBuffer = mStreamingBuffer->getVertexBuffer();
 
     translated->vertexBuffer.set(vertexBuffer);
     translated->serial = vertexBuffer->getSerial();
-    translated->offset = streamOffset;
+    translated->baseOffset            = streamOffset;
+    translated->usesFirstVertexOffset = false;
 
-    return gl::Error(GL_NO_ERROR);
+    return gl::NoError();
 }
 
 gl::Error VertexDataManager::storeCurrentValue(const gl::VertexAttribCurrentValueData &currentValue,
@@ -506,20 +514,12 @@
     {
         const gl::VertexAttribute &attrib = *translated->attribute;
 
-        gl::Error error = buffer->reserveVertexSpace(attrib, 1, 0);
-        if (error.isError())
-        {
-            return error;
-        }
+        ANGLE_TRY(buffer->reserveVertexSpace(attrib, 1, 0));
 
         const uint8_t *sourceData = reinterpret_cast<const uint8_t*>(currentValue.FloatValues);
         unsigned int streamOffset;
-        error = buffer->storeDynamicAttribute(attrib, currentValue.Type, 0, 1, 0, &streamOffset,
-                                              sourceData);
-        if (error.isError())
-        {
-            return error;
-        }
+        ANGLE_TRY(buffer->storeDynamicAttribute(attrib, currentValue.Type, 0, 1, 0, &streamOffset,
+                                                sourceData));
 
         buffer->getVertexBuffer()->hintUnmapResource();
 
@@ -533,9 +533,10 @@
     translated->serial  = buffer->getSerial();
     translated->divisor = 0;
     translated->stride  = 0;
-    translated->offset  = static_cast<unsigned int>(cachedState->offset);
+    translated->baseOffset            = static_cast<unsigned int>(cachedState->offset);
+    translated->usesFirstVertexOffset = false;
 
-    return gl::Error(GL_NO_ERROR);
+    return gl::NoError();
 }
 
 // VertexBufferBinding implementation
diff --git a/src/libANGLE/renderer/d3d/VertexDataManager.h b/src/libANGLE/renderer/d3d/VertexDataManager.h
index fe1e9d3..da46c7a 100644
--- a/src/libANGLE/renderer/d3d/VertexDataManager.h
+++ b/src/libANGLE/renderer/d3d/VertexDataManager.h
@@ -10,9 +10,10 @@
 #ifndef LIBANGLE_RENDERER_D3D_VERTEXDATAMANAGER_H_
 #define LIBANGLE_RENDERER_D3D_VERTEXDATAMANAGER_H_
 
+#include "common/angleutils.h"
+#include "libANGLE/angletypes.h"
 #include "libANGLE/Constants.h"
 #include "libANGLE/VertexAttribute.h"
-#include "common/angleutils.h"
 
 namespace gl
 {
@@ -47,11 +48,16 @@
 {
     TranslatedAttribute();
 
+    // Computes the correct offset from baseOffset, usesFirstVertexOffset, stride and startVertex.
+    // Can throw an error on integer overflow.
+    gl::ErrorOrResult<unsigned int> computeOffset(GLint startVertex) const;
+
     bool active;
 
     const gl::VertexAttribute *attribute;
     GLenum currentValueType;
-    unsigned int offset;
+    unsigned int baseOffset;
+    bool usesFirstVertexOffset;
     unsigned int stride;   // 0 means not to advance the read pointer at all
 
     VertexBufferBinding vertexBuffer;
@@ -84,19 +90,23 @@
                                 std::vector<TranslatedAttribute> *translatedAttribs,
                                 GLsizei instances);
 
-    static void StoreDirectAttrib(TranslatedAttribute *directAttrib, GLint start);
+    static void StoreDirectAttrib(TranslatedAttribute *directAttrib);
 
     static gl::Error StoreStaticAttrib(TranslatedAttribute *translated,
-                                       GLint start,
                                        GLsizei count,
                                        GLsizei instances);
 
     gl::Error storeDynamicAttribs(std::vector<TranslatedAttribute> *translatedAttribs,
-                                  const std::vector<size_t> &dynamicAttribIndexes,
+                                  const gl::AttributesMask &dynamicAttribsMask,
                                   GLint start,
                                   GLsizei count,
                                   GLsizei instances);
 
+    // Promote static usage of dynamic buffers.
+    static void PromoteDynamicAttribs(const std::vector<TranslatedAttribute> &translatedAttribs,
+                                      const gl::AttributesMask &dynamicAttribsMask,
+                                      GLsizei count);
+
     gl::Error storeCurrentValue(const gl::VertexAttribCurrentValueData &currentValue,
                                 TranslatedAttribute *translated,
                                 size_t attribIndex);
@@ -121,13 +131,11 @@
                                  GLsizei count,
                                  GLsizei instances);
 
-    void unmapStreamingBuffer();
-
     BufferFactoryD3D *const mFactory;
 
     StreamingVertexBufferInterface *mStreamingBuffer;
     std::vector<CurrentValueState> mCurrentValueCache;
-    std::vector<size_t> mDynamicAttributeIndexesCache;
+    gl::AttributesMask mDynamicAttribsMaskCache;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
index de882ba..038753e 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
@@ -138,7 +138,7 @@
 class Buffer11::NativeStorage : public Buffer11::BufferStorage
 {
   public:
-    NativeStorage(Renderer11 *renderer, BufferUsage usage);
+    NativeStorage(Renderer11 *renderer, BufferUsage usage, const NotificationSet *onStorageChanged);
     ~NativeStorage() override;
 
     bool isMappable() const override { return mUsage == BUFFER_USAGE_STAGING; }
@@ -163,6 +163,7 @@
                                unsigned int bufferSize);
 
     ID3D11Buffer *mNativeStorage;
+    const NotificationSet *mOnStorageChanged;
 };
 
 // A emulated indexed buffer storage represents an underlying D3D11 buffer for data
@@ -177,7 +178,8 @@
     bool isMappable() const override { return true; }
 
     gl::ErrorOrResult<ID3D11Buffer *> getNativeStorage(SourceIndexData *indexInfo,
-                                                       const TranslatedAttribute &attribute);
+                                                       const TranslatedAttribute &attribute,
+                                                       GLint startVertex);
 
     gl::ErrorOrResult<CopyResult> copyFromStorage(BufferStorage *source,
                                                   size_t sourceOffset,
@@ -521,7 +523,8 @@
 
 gl::ErrorOrResult<ID3D11Buffer *> Buffer11::getEmulatedIndexedBuffer(
     SourceIndexData *indexInfo,
-    const TranslatedAttribute &attribute)
+    const TranslatedAttribute &attribute,
+    GLint startVertex)
 {
     ASSERT(indexInfo);
 
@@ -533,7 +536,8 @@
     EmulatedIndexedStorage *emulatedStorage = GetAs<EmulatedIndexedStorage>(untypedStorage);
 
     ID3D11Buffer *nativeStorage = nullptr;
-    ANGLE_TRY_RESULT(emulatedStorage->getNativeStorage(indexInfo, attribute), nativeStorage);
+    ANGLE_TRY_RESULT(emulatedStorage->getNativeStorage(indexInfo, attribute, startVertex),
+                     nativeStorage);
 
     return nativeStorage;
 }
@@ -634,23 +638,7 @@
 
     if (!newStorage)
     {
-        if (usage == BUFFER_USAGE_PIXEL_PACK)
-        {
-            newStorage = new PackStorage(mRenderer);
-        }
-        else if (usage == BUFFER_USAGE_SYSTEM_MEMORY)
-        {
-            newStorage = new SystemMemoryStorage(mRenderer);
-        }
-        else if (usage == BUFFER_USAGE_EMULATED_INDEXED_VERTEX)
-        {
-            newStorage = new EmulatedIndexedStorage(mRenderer);
-        }
-        else
-        {
-            // buffer is not allocated, create it
-            newStorage = new NativeStorage(mRenderer, usage);
-        }
+        newStorage = allocateStorage(usage);
     }
 
     // resize buffer
@@ -664,6 +652,23 @@
     return newStorage;
 }
 
+Buffer11::BufferStorage *Buffer11::allocateStorage(BufferUsage usage) const
+{
+    switch (usage)
+    {
+        case BUFFER_USAGE_PIXEL_PACK:
+            return new PackStorage(mRenderer);
+        case BUFFER_USAGE_SYSTEM_MEMORY:
+            return new SystemMemoryStorage(mRenderer);
+        case BUFFER_USAGE_EMULATED_INDEXED_VERTEX:
+            return new EmulatedIndexedStorage(mRenderer);
+        case BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK:
+            return new NativeStorage(mRenderer, usage, &mDirectBufferDirtyCallbacks);
+        default:
+            return new NativeStorage(mRenderer, usage, nullptr);
+    }
+}
+
 gl::ErrorOrResult<Buffer11::BufferStorage *> Buffer11::getConstantBufferRangeStorage(
     GLintptr offset,
     GLsizeiptr size)
@@ -677,7 +682,7 @@
 
         if (!cacheEntry->storage)
         {
-            cacheEntry->storage  = new NativeStorage(mRenderer, BUFFER_USAGE_UNIFORM);
+            cacheEntry->storage  = allocateStorage(BUFFER_USAGE_UNIFORM);
             cacheEntry->lruCount = ++mMaxConstantBufferLruCount;
         }
 
@@ -806,8 +811,43 @@
 {
     // Do not support direct buffers for dynamic data. The streaming buffer
     // offers better performance for data which changes every frame.
-    // Check for absence of static buffer interfaces to detect dynamic data.
-    return (!mStaticVertexBuffers.empty() && mStaticIndexBuffer);
+    return (mUsage == D3DBufferUsage::STATIC);
+}
+
+void Buffer11::initializeStaticData()
+{
+    BufferD3D::initializeStaticData();
+
+    // Notify when static data changes.
+    mStaticBufferDirtyCallbacks.signal();
+}
+
+void Buffer11::invalidateStaticData()
+{
+    BufferD3D::invalidateStaticData();
+
+    // Notify when static data changes.
+    mStaticBufferDirtyCallbacks.signal();
+}
+
+void Buffer11::addStaticBufferDirtyCallback(const NotificationCallback *callback)
+{
+    mStaticBufferDirtyCallbacks.add(callback);
+}
+
+void Buffer11::removeStaticBufferDirtyCallback(const NotificationCallback *callback)
+{
+    mStaticBufferDirtyCallbacks.remove(callback);
+}
+
+void Buffer11::addDirectBufferDirtyCallback(const NotificationCallback *callback)
+{
+    mDirectBufferDirtyCallbacks.add(callback);
+}
+
+void Buffer11::removeDirectBufferDirtyCallback(const NotificationCallback *callback)
+{
+    mDirectBufferDirtyCallbacks.remove(callback);
 }
 
 Buffer11::BufferStorage::BufferStorage(Renderer11 *renderer, BufferUsage usage)
@@ -829,8 +869,10 @@
     return gl::NoError();
 }
 
-Buffer11::NativeStorage::NativeStorage(Renderer11 *renderer, BufferUsage usage)
-    : BufferStorage(renderer, usage), mNativeStorage(nullptr)
+Buffer11::NativeStorage::NativeStorage(Renderer11 *renderer,
+                                       BufferUsage usage,
+                                       const NotificationSet *onStorageChanged)
+    : BufferStorage(renderer, usage), mNativeStorage(nullptr), mOnStorageChanged(onStorageChanged)
 {
 }
 
@@ -945,6 +987,12 @@
 
     mBufferSize = bufferDesc.ByteWidth;
 
+    // Notify that the storage has changed.
+    if (mOnStorageChanged)
+    {
+        mOnStorageChanged->signal();
+    }
+
     return gl::NoError();
 }
 
@@ -1050,7 +1098,8 @@
 
 gl::ErrorOrResult<ID3D11Buffer *> Buffer11::EmulatedIndexedStorage::getNativeStorage(
     SourceIndexData *indexInfo,
-    const TranslatedAttribute &attribute)
+    const TranslatedAttribute &attribute,
+    GLint startVertex)
 {
     // If a change in the indices applied from the last draw call is detected, then the emulated
     // indexed buffer needs to be invalidated.  After invalidation, the change detected flag should
@@ -1092,9 +1141,12 @@
 
     if (!mNativeStorage)
     {
+        unsigned int offset = 0;
+        ANGLE_TRY_RESULT(attribute.computeOffset(startVertex), offset);
+
         // Expand the memory storage upon request and cache the results.
         unsigned int expandedDataSize =
-            static_cast<unsigned int>((indexInfo->srcCount * attribute.stride) + attribute.offset);
+            static_cast<unsigned int>((indexInfo->srcCount * attribute.stride) + offset);
         MemoryBuffer expandedData;
         if (!expandedData.resize(expandedDataSize))
         {
@@ -1111,7 +1163,7 @@
 
         // Ensure that we start in the correct place for the emulated data copy operation to
         // maintain offset behaviors.
-        curr += attribute.offset;
+        curr += offset;
 
         ReadIndexValueFunction readIndexValue = ReadIndexValueFromIndices<GLushort>;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
index bef7740..db73ca4 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
@@ -13,6 +13,7 @@
 
 #include "libANGLE/angletypes.h"
 #include "libANGLE/renderer/d3d/BufferD3D.h"
+#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h"
 
 namespace gl
 {
@@ -63,9 +64,9 @@
     virtual ~Buffer11();
 
     gl::ErrorOrResult<ID3D11Buffer *> getBuffer(BufferUsage usage);
-    gl::ErrorOrResult<ID3D11Buffer *> getEmulatedIndexedBuffer(
-        SourceIndexData *indexInfo,
-        const TranslatedAttribute &attribute);
+    gl::ErrorOrResult<ID3D11Buffer *> getEmulatedIndexedBuffer(SourceIndexData *indexInfo,
+                                                               const TranslatedAttribute &attribute,
+                                                               GLint startVertex);
     gl::ErrorOrResult<ID3D11Buffer *> getConstantBufferRange(GLintptr offset, GLsizeiptr size);
     gl::ErrorOrResult<ID3D11ShaderResourceView *> getSRV(DXGI_FORMAT srvFormat);
     bool isMapped() const { return mMappedStorage != nullptr; }
@@ -74,18 +75,32 @@
     size_t getTotalCPUBufferMemoryBytes() const;
 
     // BufferD3D implementation
-    virtual size_t getSize() const { return mSize; }
-    virtual bool supportsDirectBinding() const;
+    size_t getSize() const override { return mSize; }
+    bool supportsDirectBinding() const override;
     gl::Error getData(const uint8_t **outData) override;
+    void initializeStaticData() override;
+    void invalidateStaticData() override;
 
     // BufferImpl implementation
-    virtual gl::Error setData(const void* data, size_t size, GLenum usage);
-    virtual gl::Error setSubData(const void* data, size_t size, size_t offset);
-    virtual gl::Error copySubData(BufferImpl* source, GLintptr sourceOffset, GLintptr destOffset, GLsizeiptr size);
-    virtual gl::Error map(GLenum access, GLvoid **mapPtr);
-    virtual gl::Error mapRange(size_t offset, size_t length, GLbitfield access, GLvoid **mapPtr);
-    virtual gl::Error unmap(GLboolean *result);
-    virtual gl::Error markTransformFeedbackUsage();
+    gl::Error setData(const void *data, size_t size, GLenum usage) override;
+    gl::Error setSubData(const void *data, size_t size, size_t offset) override;
+    gl::Error copySubData(BufferImpl *source,
+                          GLintptr sourceOffset,
+                          GLintptr destOffset,
+                          GLsizeiptr size) override;
+    gl::Error map(GLenum access, GLvoid **mapPtr) override;
+    gl::Error mapRange(size_t offset, size_t length, GLbitfield access, GLvoid **mapPtr) override;
+    gl::Error unmap(GLboolean *result) override;
+    gl::Error markTransformFeedbackUsage() override;
+
+    // We use two set of dirty callbacks for two events. Static buffers are marked dirty whenever
+    // the data is changed, because they must be re-translated. Direct buffers only need to be
+    // updated when the underlying ID3D11Buffer pointer changes - hopefully far less often.
+    void addStaticBufferDirtyCallback(const NotificationCallback *callback);
+    void removeStaticBufferDirtyCallback(const NotificationCallback *callback);
+
+    void addDirectBufferDirtyCallback(const NotificationCallback *callback);
+    void removeDirectBufferDirtyCallback(const NotificationCallback *callback);
 
   private:
     class BufferStorage;
@@ -94,13 +109,6 @@
     class PackStorage;
     class SystemMemoryStorage;
 
-    Renderer11 *mRenderer;
-    size_t mSize;
-
-    BufferStorage *mMappedStorage;
-
-    std::vector<BufferStorage*> mBufferStorages;
-
     struct ConstantBufferCacheEntry
     {
         ConstantBufferCacheEntry() : storage(nullptr), lruCount(0) { }
@@ -109,6 +117,27 @@
         unsigned int lruCount;
     };
 
+    gl::Error markBufferUsage();
+    gl::ErrorOrResult<NativeStorage *> getStagingStorage();
+    gl::ErrorOrResult<PackStorage *> getPackStorage();
+    gl::ErrorOrResult<SystemMemoryStorage *> getSystemMemoryStorage();
+
+    gl::Error updateBufferStorage(BufferStorage *storage, size_t sourceOffset, size_t storageSize);
+    gl::ErrorOrResult<BufferStorage *> getBufferStorage(BufferUsage usage);
+    gl::ErrorOrResult<BufferStorage *> getLatestBufferStorage() const;
+
+    gl::ErrorOrResult<BufferStorage *> getConstantBufferRangeStorage(GLintptr offset,
+                                                                     GLsizeiptr size);
+
+    BufferStorage *allocateStorage(BufferUsage usage) const;
+
+    Renderer11 *mRenderer;
+    size_t mSize;
+
+    BufferStorage *mMappedStorage;
+
+    std::vector<BufferStorage *> mBufferStorages;
+
     // Cache of D3D11 constant buffer for specific ranges of buffer data.
     // This is used to emulate UBO ranges on 11.0 devices.
     // Constant buffers are indexed by there start offset.
@@ -122,17 +151,8 @@
 
     unsigned int mReadUsageCount;
 
-    gl::Error markBufferUsage();
-    gl::ErrorOrResult<NativeStorage *> getStagingStorage();
-    gl::ErrorOrResult<PackStorage *> getPackStorage();
-    gl::ErrorOrResult<SystemMemoryStorage *> getSystemMemoryStorage();
-
-    gl::Error updateBufferStorage(BufferStorage *storage, size_t sourceOffset, size_t storageSize);
-    gl::ErrorOrResult<BufferStorage *> getBufferStorage(BufferUsage usage);
-    gl::ErrorOrResult<BufferStorage *> getLatestBufferStorage() const;
-
-    gl::ErrorOrResult<BufferStorage *> getConstantBufferRangeStorage(GLintptr offset,
-                                                                     GLsizeiptr size);
+    NotificationSet mStaticBufferDirtyCallbacks;
+    NotificationSet mDirectBufferDirtyCallbacks;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
index 157c05d..02ae26d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -94,7 +94,8 @@
 }
 
 void SortAttributesByLayout(const gl::Program *program,
-                            const std::vector<TranslatedAttribute> &unsortedAttributes,
+                            const std::vector<TranslatedAttribute> &vertexArrayAttribs,
+                            const std::vector<TranslatedAttribute> &currentValueAttribs,
                             AttribIndexArray *sortedD3DSemanticsOut,
                             std::vector<const TranslatedAttribute *> *sortedAttributesOut)
 {
@@ -112,7 +113,17 @@
         }
 
         (*sortedD3DSemanticsOut)[d3dSemantic] = d3dSemantic;
-        (*sortedAttributesOut)[d3dSemantic] = &unsortedAttributes[locationIndex];
+
+        const auto *arrayAttrib = &vertexArrayAttribs[locationIndex];
+        if (arrayAttrib->attribute && arrayAttrib->attribute->enabled)
+        {
+            (*sortedAttributesOut)[d3dSemantic] = arrayAttrib;
+        }
+        else
+        {
+            ASSERT(currentValueAttribs[locationIndex].attribute);
+            (*sortedAttributesOut)[d3dSemantic] = &currentValueAttribs[locationIndex];
+        }
     }
 }
 
@@ -209,8 +220,10 @@
 
 gl::Error InputLayoutCache::applyVertexBuffers(
     const gl::State &state,
-    const std::vector<TranslatedAttribute> &unsortedAttributes,
+    const std::vector<TranslatedAttribute> &vertexArrayAttribs,
+    const std::vector<TranslatedAttribute> &currentValueAttribs,
     GLenum mode,
+    GLint start,
     TranslatedIndexData *indexInfo,
     GLsizei numIndicesPerInstance)
 {
@@ -223,7 +236,7 @@
     bool instancedPointSpritesActive = programUsesInstancedPointSprites && (mode == GL_POINTS);
 
     AttribIndexArray sortedSemanticIndices;
-    SortAttributesByLayout(program, unsortedAttributes, &sortedSemanticIndices,
+    SortAttributesByLayout(program, vertexArrayAttribs, currentValueAttribs, &sortedSemanticIndices,
                            &mCurrentAttributes);
 
     // If we are using FL 9_3, make sure the first attribute is not instanced
@@ -285,9 +298,9 @@
                     indexInfo->srcIndexData.srcIndices = bufferData + offset;
                 }
 
-                ANGLE_TRY_RESULT(
-                    bufferStorage->getEmulatedIndexedBuffer(&indexInfo->srcIndexData, attrib),
-                    buffer);
+                ANGLE_TRY_RESULT(bufferStorage->getEmulatedIndexedBuffer(&indexInfo->srcIndexData,
+                                                                         attrib, start),
+                                 buffer);
             }
             else
             {
@@ -296,7 +309,7 @@
             }
 
             vertexStride = attrib.stride;
-            vertexOffset = attrib.offset;
+            ANGLE_TRY_RESULT(attrib.computeOffset(start), vertexOffset);
         }
 
         size_t bufferIndex = reservedBuffers + attribIndex;
@@ -414,7 +427,8 @@
     return gl::NoError();
 }
 
-gl::Error InputLayoutCache::updateVertexOffsetsForPointSpritesEmulation(GLsizei emulatedInstanceId)
+gl::Error InputLayoutCache::updateVertexOffsetsForPointSpritesEmulation(GLint startVertex,
+                                                                        GLsizei emulatedInstanceId)
 {
     size_t reservedBuffers = GetReservedBufferCount(true);
     for (size_t attribIndex = 0; attribIndex < mCurrentAttributes.size(); ++attribIndex)
@@ -424,8 +438,10 @@
 
         if (attrib.divisor > 0)
         {
+            unsigned int offset = 0;
+            ANGLE_TRY_RESULT(attrib.computeOffset(startVertex), offset);
             mCurrentVertexOffsets[bufferIndex] =
-                attrib.offset + (attrib.stride * (emulatedInstanceId / attrib.divisor));
+                offset + (attrib.stride * (emulatedInstanceId / attrib.divisor));
         }
     }
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
index f3f6c99..62a1020 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
@@ -46,12 +46,15 @@
     void markDirty();
 
     gl::Error applyVertexBuffers(const gl::State &state,
-                                 const std::vector<TranslatedAttribute> &attributes,
+                                 const std::vector<TranslatedAttribute> &vertexArrayAttribs,
+                                 const std::vector<TranslatedAttribute> &currentValueAttribs,
                                  GLenum mode,
+                                 GLint start,
                                  TranslatedIndexData *indexInfo,
                                  GLsizei numIndicesPerInstance);
 
-    gl::Error updateVertexOffsetsForPointSpritesEmulation(GLsizei emulatedInstanceId);
+    gl::Error updateVertexOffsetsForPointSpritesEmulation(GLint startVertex,
+                                                          GLsizei emulatedInstanceId);
 
     // Useful for testing
     void setCacheSize(unsigned int cacheSize) { mCacheSize = cacheSize; }
diff --git a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
index 39125a1..d25cdf3 100644
--- a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
@@ -189,23 +189,17 @@
 
 void RenderTarget11::addDirtyCallback(const NotificationCallback *callback)
 {
-    mDirtyCallbacks.insert(callback);
+    mDirtyCallbacks.add(callback);
 }
 
 void RenderTarget11::removeDirtyCallback(const NotificationCallback *callback)
 {
-    mDirtyCallbacks.erase(callback);
+    mDirtyCallbacks.remove(callback);
 }
 
 void RenderTarget11::signalDirty()
 {
-    if (mDirtyCallbacks.empty())
-        return;
-
-    for (const auto &callback : mDirtyCallbacks)
-    {
-        (*callback)();
-    }
+    mDirtyCallbacks.signal();
 
     // Clear the signal list. We can't do this in the callback because it mutates the iterator.
     mDirtyCallbacks.clear();
diff --git a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
index 2088dbb..aa470cc 100644
--- a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
@@ -41,8 +41,7 @@
     d3d11::ANGLEFormat getANGLEFormat() const { return mANGLEFormat; }
 
   protected:
-    std::set<const NotificationCallback *> mDirtyCallbacks;
-
+    NotificationSet mDirtyCallbacks;
     d3d11::ANGLEFormat mANGLEFormat;
 };
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 6e37ea2..424aa27 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -835,9 +835,6 @@
     ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.D3D11FeatureLevel",
                                 angleFeatureLevel,
                                 NUM_ANGLE_FEATURE_LEVELS);
-
-    // TODO(jmadill): use context caps, and place in common D3D location
-    mTranslatedAttribCache.resize(getRendererCaps().maxVertexAttributes);
 }
 
 void Renderer11::populateRenderer11DeviceCaps()
@@ -1524,7 +1521,17 @@
                                         GLsizei instances,
                                         TranslatedIndexData *indexInfo)
 {
-    gl::Error error = mVertexDataManager->prepareVertexData(state, first, count, &mTranslatedAttribCache, instances);
+    const auto &vertexArray = state.getVertexArray();
+    auto *vertexArray11     = GetImplAs<VertexArray11>(vertexArray);
+
+    gl::Error error = vertexArray11->updateDirtyAndDynamicAttribs(mVertexDataManager, state, first,
+                                                                  count, instances);
+    if (error.isError())
+    {
+        return error;
+    }
+
+    error = mStateManager.updateCurrentValueAttribs(state, mVertexDataManager);
     if (error.isError())
     {
         return error;
@@ -1541,8 +1548,21 @@
     {
         numIndicesPerInstance = count;
     }
-    return mInputLayoutCache.applyVertexBuffers(state, mTranslatedAttribCache, mode, indexInfo,
-                                                numIndicesPerInstance);
+    const auto &vertexArrayAttribs  = vertexArray11->getTranslatedAttribs();
+    const auto &currentValueAttribs = mStateManager.getCurrentValueAttribs();
+    ANGLE_TRY(mInputLayoutCache.applyVertexBuffers(state, vertexArrayAttribs, currentValueAttribs,
+                                                   mode, first, indexInfo, numIndicesPerInstance));
+
+    // InputLayoutCache::applyVertexBuffers calls through to the Bufer11 to get the native vertex
+    // buffer (ID3D11Buffer *). Because we allocate these buffers lazily, this will trigger
+    // allocation. This in turn will signal that the buffer is dirty. Since we just resolved the
+    // dirty-ness in VertexArray11::updateDirtyAndDynamicAttribs, this can make us do a needless
+    // update on the second draw call.
+    // Hence we clear the flags here, after we've applied vertex data, since we know everything
+    // is clean. This is a bit of a hack.
+    vertexArray11->clearDirtyAndPromoteDynamicAttribs(state, count);
+
+    return gl::NoError();
 }
 
 gl::Error Renderer11::applyIndexBuffer(const gl::Data &data,
@@ -1669,6 +1689,7 @@
 
 gl::Error Renderer11::drawArraysImpl(const gl::Data &data,
                                      GLenum mode,
+                                     GLint startVertex,
                                      GLsizei count,
                                      GLsizei instances)
 {
@@ -1761,7 +1782,8 @@
             // offsets.
             for (GLsizei i = 0; i < instances; i++)
             {
-                gl::Error error = mInputLayoutCache.updateVertexOffsetsForPointSpritesEmulation(i);
+                gl::Error error =
+                    mInputLayoutCache.updateVertexOffsetsForPointSpritesEmulation(startVertex, i);
                 if (error.isError())
                 {
                     return error;
@@ -1827,7 +1849,8 @@
             // offsets.
             for (GLsizei i = 0; i < instances; i++)
             {
-                gl::Error error = mInputLayoutCache.updateVertexOffsetsForPointSpritesEmulation(i);
+                gl::Error error =
+                    mInputLayoutCache.updateVertexOffsetsForPointSpritesEmulation(minIndex, i);
                 if (error.isError())
                 {
                     return error;
@@ -2505,6 +2528,7 @@
 
 void Renderer11::releaseDeviceResources()
 {
+    mStateManager.deinitialize();
     mStateCache.clear();
     mInputLayoutCache.clear();
 
@@ -4340,4 +4364,5 @@
     *device = static_cast<DeviceImpl *>(mEGLDevice);
     return egl::Error(EGL_SUCCESS);
 }
-}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index 18eebd3..a23cc29 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -306,6 +306,7 @@
   private:
     gl::Error drawArraysImpl(const gl::Data &data,
                              GLenum mode,
+                             GLint startVertex,
                              GLsizei count,
                              GLsizei instances) override;
     gl::Error drawElementsImpl(const gl::Data &data,
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index 4aab12d..c3b3e5e 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -150,7 +150,9 @@
       mCurNear(0.0f),
       mCurFar(0.0f),
       mViewportBounds(),
-      mRenderTargetIsDirty(false)
+      mRenderTargetIsDirty(false),
+      mDirtyCurrentValueAttribs(),
+      mCurrentValueAttribs()
 {
     mCurBlendState.blend                 = false;
     mCurBlendState.sourceBlendRGB        = GL_ONE;
@@ -191,6 +193,9 @@
     mCurRasterState.polygonOffsetUnits  = 0.0f;
     mCurRasterState.pointDrawMode       = false;
     mCurRasterState.multiSample         = false;
+
+    // Initially all current value attributes must be updated on first use.
+    mDirtyCurrentValueAttribs.flip();
 }
 
 StateManager11::~StateManager11()
@@ -241,7 +246,7 @@
         return;
     }
 
-    for (unsigned int dirtyBit : angle::IterateBitSet(dirtyBits))
+    for (auto dirtyBit : angle::IterateBitSet(dirtyBits))
     {
         switch (dirtyBit)
         {
@@ -461,6 +466,13 @@
                 mRenderTargetIsDirty = true;
                 break;
             default:
+                if (dirtyBit >= gl::State::DIRTY_BIT_CURRENT_VALUE_0 &&
+                    dirtyBit < gl::State::DIRTY_BIT_CURRENT_VALUE_MAX)
+                {
+                    size_t attribIndex =
+                        static_cast<size_t>(dirtyBit - gl::State::DIRTY_BIT_CURRENT_VALUE_0);
+                    mDirtyCurrentValueAttribs.set(attribIndex);
+                }
                 break;
         }
     }
@@ -958,6 +970,13 @@
 
     // Initialize cached NULL SRV block
     mNullSRVs.resize(caps.maxTextureImageUnits, nullptr);
+
+    mCurrentValueAttribs.resize(caps.maxVertexAttributes);
+}
+
+void StateManager11::deinitialize()
+{
+    mCurrentValueAttribs.clear();
 }
 
 gl::Error StateManager11::syncFramebuffer(const gl::Framebuffer *framebuffer)
@@ -1078,4 +1097,40 @@
     return gl::Error(GL_NO_ERROR);
 }
 
+gl::Error StateManager11::updateCurrentValueAttribs(const gl::State &state,
+                                                    VertexDataManager *vertexDataManager)
+{
+    const auto &activeAttribsMask  = state.getProgram()->getActiveAttribLocationsMask();
+    const auto &dirtyActiveAttribs = (activeAttribsMask & mDirtyCurrentValueAttribs);
+    const auto &vertexAttributes   = state.getVertexArray()->getVertexAttributes();
+
+    for (auto attribIndex : angle::IterateBitSet(dirtyActiveAttribs))
+    {
+        if (vertexAttributes[attribIndex].enabled)
+            continue;
+
+        mDirtyCurrentValueAttribs.reset(attribIndex);
+
+        const auto &currentValue =
+            state.getVertexAttribCurrentValue(static_cast<unsigned int>(attribIndex));
+        auto currentValueAttrib              = &mCurrentValueAttribs[attribIndex];
+        currentValueAttrib->currentValueType = currentValue.Type;
+        currentValueAttrib->attribute        = &vertexAttributes[attribIndex];
+
+        gl::Error error = vertexDataManager->storeCurrentValue(currentValue, currentValueAttrib,
+                                                               static_cast<size_t>(attribIndex));
+        if (error.isError())
+        {
+            return error;
+        }
+    }
+
+    return gl::Error(GL_NO_ERROR);
+}
+
+const std::vector<TranslatedAttribute> &StateManager11::getCurrentValueAttribs() const
+{
+    return mCurrentValueAttribs;
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
index 5cc2b19..c926ccd 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
@@ -48,6 +48,7 @@
     ~StateManager11();
 
     void initialize(const gl::Caps &caps);
+    void deinitialize();
     void syncState(const gl::State &state, const gl::State::DirtyBits &dirtyBits);
 
     gl::Error setBlendState(const gl::Framebuffer *framebuffer,
@@ -91,6 +92,11 @@
     void onDeleteQueryObject(Query11 *query);
     gl::Error onMakeCurrent(const gl::Data &data);
 
+    gl::Error updateCurrentValueAttribs(const gl::State &state,
+                                        VertexDataManager *vertexDataManager);
+
+    const std::vector<TranslatedAttribute> &getCurrentValueAttribs() const;
+
   private:
     void setViewportBounds(const int width, const int height);
     void unsetConflictingSRVs(gl::SamplerType shaderType,
@@ -186,6 +192,10 @@
 
     // A block of NULL pointers, cached so we don't re-allocate every draw call
     std::vector<ID3D11ShaderResourceView *> mNullSRVs;
+
+    // Current translations of "Current-Value" data - owned by Context, not VertexArray.
+    gl::AttributesMask mDirtyCurrentValueAttribs;
+    std::vector<TranslatedAttribute> mCurrentValueAttribs;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
new file mode 100644
index 0000000..7bb2721
--- /dev/null
+++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
@@ -0,0 +1,265 @@
+//
+// Copyright 2016 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.
+//
+// VertexArray11:
+//   Implementation of rx::VertexArray11.
+//
+
+#include "libANGLE/renderer/d3d/d3d11/VertexArray11.h"
+
+#include "common/BitSetIterator.h"
+#include "libANGLE/renderer/d3d/d3d11/Buffer11.h"
+
+namespace rx
+{
+
+namespace
+{
+size_t GetAttribIndex(unsigned long dirtyBit)
+{
+    if (dirtyBit >= gl::VertexArray::DIRTY_BIT_ATTRIB_0_ENABLED &&
+        dirtyBit < gl::VertexArray::DIRTY_BIT_ATTRIB_MAX_ENABLED)
+    {
+        return dirtyBit - gl::VertexArray::DIRTY_BIT_ATTRIB_0_ENABLED;
+    }
+
+    if (dirtyBit >= gl::VertexArray::DIRTY_BIT_ATTRIB_0_POINTER &&
+        dirtyBit < gl::VertexArray::DIRTY_BIT_ATTRIB_MAX_POINTER)
+    {
+        return dirtyBit - gl::VertexArray::DIRTY_BIT_ATTRIB_0_POINTER;
+    }
+
+    ASSERT(dirtyBit >= gl::VertexArray::DIRTY_BIT_ATTRIB_0_DIVISOR &&
+           dirtyBit < gl::VertexArray::DIRTY_BIT_ATTRIB_MAX_DIVISOR);
+    return static_cast<size_t>(dirtyBit) - gl::VertexArray::DIRTY_BIT_ATTRIB_0_DIVISOR;
+}
+}  // anonymous namespace
+
+VertexArray11::VertexArray11(const gl::VertexArray::Data &data)
+    : VertexArrayImpl(data),
+      mAttributeStorageTypes(data.getVertexAttributes().size(), VertexStorageType::CURRENT_VALUE),
+      mTranslatedAttribs(data.getVertexAttributes().size()),
+      mCurrentBuffers(data.getVertexAttributes().size())
+{
+    for (size_t attribIndex = 0; attribIndex < mCurrentBuffers.size(); ++attribIndex)
+    {
+        auto callback = [this, attribIndex]()
+        {
+            this->markBufferDataDirty(attribIndex);
+        };
+        mOnBufferDataDirty.push_back(callback);
+    }
+}
+
+VertexArray11::~VertexArray11()
+{
+    for (size_t attribIndex = 0; attribIndex < mCurrentBuffers.size(); ++attribIndex)
+    {
+        if (mCurrentBuffers[attribIndex].get())
+        {
+            unlinkBuffer(attribIndex, mAttributeStorageTypes[attribIndex]);
+            mCurrentBuffers[attribIndex].set(nullptr);
+        }
+    }
+}
+
+void VertexArray11::syncState(const gl::VertexArray::DirtyBits &dirtyBits)
+{
+    for (auto dirtyBit : angle::IterateBitSet(dirtyBits))
+    {
+        if (dirtyBit == gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER)
+            continue;
+
+        size_t attribIndex = GetAttribIndex(dirtyBit);
+        mAttribsToUpdate.set(attribIndex);
+    }
+}
+
+void VertexArray11::updateVertexAttribStorage(size_t attribIndex)
+{
+    const auto &attrib = mData.getVertexAttribute(attribIndex);
+
+    // Note: having an unchanged storage type doesn't mean the attribute is clean.
+    auto oldStorageType = mAttributeStorageTypes[attribIndex];
+    auto newStorageType = ClassifyAttributeStorage(attrib);
+
+    mAttributeStorageTypes[attribIndex] = newStorageType;
+
+    if (newStorageType == VertexStorageType::DYNAMIC)
+    {
+        if (oldStorageType != VertexStorageType::DYNAMIC)
+        {
+            // Sync dynamic attribs in a different set.
+            mAttribsToTranslate.reset(attribIndex);
+            mDynamicAttribsMask.set(attribIndex);
+        }
+    }
+    else
+    {
+        mAttribsToTranslate.set(attribIndex);
+
+        if (oldStorageType == VertexStorageType::DYNAMIC)
+        {
+            ASSERT(mDynamicAttribsMask[attribIndex]);
+            mDynamicAttribsMask.reset(attribIndex);
+        }
+    }
+
+    gl::Buffer *oldBufferGL = mCurrentBuffers[attribIndex].get();
+    gl::Buffer *newBufferGL = attrib.buffer.get();
+    Buffer11 *oldBuffer11   = oldBufferGL ? GetImplAs<Buffer11>(oldBufferGL) : nullptr;
+    Buffer11 *newBuffer11   = newBufferGL ? GetImplAs<Buffer11>(newBufferGL) : nullptr;
+
+    if (oldBuffer11 != newBuffer11 || oldStorageType != newStorageType)
+    {
+        // Note that for static callbacks, promotion to a static buffer from a dynamic buffer means
+        // we need to tag dynamic buffers with static callbacks.
+        if (oldBuffer11 != nullptr)
+        {
+            unlinkBuffer(attribIndex, oldStorageType);
+        }
+        if (newBuffer11 != nullptr)
+        {
+            if (newStorageType == VertexStorageType::DIRECT)
+            {
+                newBuffer11->addDirectBufferDirtyCallback(&mOnBufferDataDirty[attribIndex]);
+            }
+            else if (newStorageType == VertexStorageType::STATIC ||
+                     newStorageType == VertexStorageType::DYNAMIC)
+            {
+                newBuffer11->addStaticBufferDirtyCallback(&mOnBufferDataDirty[attribIndex]);
+            }
+        }
+        mCurrentBuffers[attribIndex] = attrib.buffer;
+    }
+}
+
+gl::Error VertexArray11::updateDirtyAndDynamicAttribs(VertexDataManager *vertexDataManager,
+                                                      const gl::State &state,
+                                                      GLint start,
+                                                      GLsizei count,
+                                                      GLsizei instances)
+{
+    const gl::Program *program  = state.getProgram();
+    const auto &activeLocations = program->getActiveAttribLocationsMask();
+
+    if (mAttribsToUpdate.any())
+    {
+        // Skip attrib locations the program doesn't use.
+        const auto &activeToUpdate = (mAttribsToUpdate & activeLocations);
+
+        for (auto toUpdateIndex : angle::IterateBitSet(activeToUpdate))
+        {
+            mAttribsToUpdate.reset(toUpdateIndex);
+            updateVertexAttribStorage(toUpdateIndex);
+        }
+    }
+
+    const auto &attribs = mData.getVertexAttributes();
+
+    if (mAttribsToTranslate.any())
+    {
+        // Skip attrib locations the program doesn't use, saving for the next frame.
+        const auto &dirtyActiveAttribs = (mAttribsToTranslate & activeLocations);
+
+        for (auto dirtyAttribIndex : angle::IterateBitSet(dirtyActiveAttribs))
+        {
+            mAttribsToTranslate.reset(dirtyAttribIndex);
+
+            auto *translatedAttrib = &mTranslatedAttribs[dirtyAttribIndex];
+            const auto &currentValue =
+                state.getVertexAttribCurrentValue(static_cast<unsigned int>(dirtyAttribIndex));
+
+            // Record basic attrib info
+            translatedAttrib->attribute        = &attribs[dirtyAttribIndex];
+            translatedAttrib->currentValueType = currentValue.Type;
+            translatedAttrib->divisor          = translatedAttrib->attribute->divisor;
+
+            switch (mAttributeStorageTypes[dirtyAttribIndex])
+            {
+                case VertexStorageType::DIRECT:
+                    VertexDataManager::StoreDirectAttrib(translatedAttrib);
+                    break;
+                case VertexStorageType::STATIC:
+                {
+                    auto error =
+                        VertexDataManager::StoreStaticAttrib(translatedAttrib, count, instances);
+                    if (error.isError())
+                    {
+                        return error;
+                    }
+                    break;
+                }
+                case VertexStorageType::CURRENT_VALUE:
+                    // Current value attribs are managed by the StateManager11.
+                    break;
+                default:
+                    UNREACHABLE();
+                    break;
+            }
+        }
+    }
+
+    if (mDynamicAttribsMask.any())
+    {
+        auto activeDynamicAttribs = (mDynamicAttribsMask & activeLocations);
+
+        for (auto dynamicAttribIndex : angle::IterateBitSet(activeDynamicAttribs))
+        {
+            auto *dynamicAttrib = &mTranslatedAttribs[dynamicAttribIndex];
+            const auto &currentValue =
+                state.getVertexAttribCurrentValue(static_cast<unsigned int>(dynamicAttribIndex));
+
+            // Record basic attrib info
+            dynamicAttrib->attribute        = &attribs[dynamicAttribIndex];
+            dynamicAttrib->currentValueType = currentValue.Type;
+            dynamicAttrib->divisor          = dynamicAttrib->attribute->divisor;
+        }
+
+        return vertexDataManager->storeDynamicAttribs(&mTranslatedAttribs, activeDynamicAttribs,
+                                                      start, count, instances);
+    }
+
+    return gl::Error(GL_NO_ERROR);
+}
+
+const std::vector<TranslatedAttribute> &VertexArray11::getTranslatedAttribs() const
+{
+    return mTranslatedAttribs;
+}
+
+void VertexArray11::markBufferDataDirty(size_t attribIndex)
+{
+    ASSERT(mAttributeStorageTypes[attribIndex] != VertexStorageType::CURRENT_VALUE);
+
+    // This can change a buffer's storage, we'll need to re-check.
+    mAttribsToUpdate.set(attribIndex);
+}
+
+void VertexArray11::clearDirtyAndPromoteDynamicAttribs(const gl::State &state, GLsizei count)
+{
+    const gl::Program *program  = state.getProgram();
+    const auto &activeLocations = program->getActiveAttribLocationsMask();
+    mAttribsToUpdate &= ~activeLocations;
+
+    // Promote to static after we clear the dirty attributes, otherwise we can lose dirtyness.
+    auto activeDynamicAttribs = (mDynamicAttribsMask & activeLocations);
+    VertexDataManager::PromoteDynamicAttribs(mTranslatedAttribs, activeDynamicAttribs, count);
+}
+
+void VertexArray11::unlinkBuffer(size_t attribIndex, VertexStorageType storageType)
+{
+    Buffer11 *buffer = GetImplAs<Buffer11>(mCurrentBuffers[attribIndex].get());
+    if (storageType == VertexStorageType::DIRECT)
+    {
+        buffer->removeDirectBufferDirtyCallback(&mOnBufferDataDirty[attribIndex]);
+    }
+    else if (storageType == VertexStorageType::STATIC || storageType == VertexStorageType::DYNAMIC)
+    {
+        buffer->removeStaticBufferDirtyCallback(&mOnBufferDataDirty[attribIndex]);
+    }
+}
+
+}  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
index b397140..f2b4c13 100644
--- a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
@@ -19,13 +19,42 @@
 class VertexArray11 : public VertexArrayImpl
 {
   public:
-    VertexArray11(const gl::VertexArray::Data &data)
-        : VertexArrayImpl(data)
-    {
-    }
-    virtual ~VertexArray11() {}
+    VertexArray11(const gl::VertexArray::Data &data);
+    ~VertexArray11() override;
+
+    void syncState(const gl::VertexArray::DirtyBits &dirtyBits) override;
+    gl::Error updateDirtyAndDynamicAttribs(VertexDataManager *vertexDataManager,
+                                           const gl::State &state,
+                                           GLint start,
+                                           GLsizei count,
+                                           GLsizei instances);
+    void clearDirtyAndPromoteDynamicAttribs(const gl::State &state, GLsizei count);
+
+    const std::vector<TranslatedAttribute> &getTranslatedAttribs() const;
+
+  private:
+    void updateVertexAttribStorage(size_t attribIndex);
+    void markBufferDataDirty(size_t attribIndex);
+    void unlinkBuffer(size_t attribIndex, VertexStorageType storageType);
+
+    std::vector<VertexStorageType> mAttributeStorageTypes;
+    std::vector<TranslatedAttribute> mTranslatedAttribs;
+
+    // The mask of attributes marked as dynamic.
+    gl::AttributesMask mDynamicAttribsMask;
+
+    // A mask of attributes that need to be re-evaluated.
+    gl::AttributesMask mAttribsToUpdate;
+
+    // A set of attributes we know are dirty, and need to be re-translated.
+    gl::AttributesMask mAttribsToTranslate;
+
+    // We need to keep a safe pointer to the Buffer so we can attach the correct dirty callbacks.
+    std::vector<BindingPointer<gl::Buffer>> mCurrentBuffers;
+
+    std::vector<NotificationCallback> mOnBufferDataDirty;
 };
 
-}
+}  // namespace rx
 
 #endif // LIBANGLE_RENDERER_D3D_D3D11_VERTEXARRAY11_H_
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index 7836abe..89a574b 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -1716,4 +1716,40 @@
             renderer->presentPathFastEnabled());
 }
 
+NotificationSet::NotificationSet()
+{
+}
+
+NotificationSet::~NotificationSet()
+{
+}
+
+void NotificationSet::add(const NotificationCallback *callback)
+{
+    ASSERT(mCallbacks.count(callback) == 0);
+    mCallbacks.insert(callback);
+}
+
+void NotificationSet::remove(const NotificationCallback *callback)
+{
+    ASSERT(mCallbacks.count(callback) == 1);
+    mCallbacks.erase(callback);
+}
+
+void NotificationSet::signal() const
+{
+    if (mCallbacks.empty())
+        return;
+
+    for (const auto *callback : mCallbacks)
+    {
+        (*callback)();
+    }
+}
+
+void NotificationSet::clear()
+{
+    mCallbacks.clear();
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
index 9126f56..49718c6 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
@@ -406,6 +406,21 @@
 
 using NotificationCallback = std::function<void()>;
 
+class NotificationSet final : angle::NonCopyable
+{
+  public:
+    NotificationSet();
+    ~NotificationSet();
+
+    void add(const NotificationCallback *callback);
+    void remove(const NotificationCallback *callback);
+    void signal() const;
+    void clear();
+
+  private:
+    std::set<const NotificationCallback *> mCallbacks;
+};
+
 }  // namespace rx
 
 #endif // LIBANGLE_RENDERER_D3D_D3D11_RENDERER11_UTILS_H_
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index da22638..9abb200 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -154,6 +154,8 @@
 {
     RendererD3D::cleanup();
 
+    mTranslatedAttribCache.clear();
+
     releaseDeviceResources();
 
     SafeDelete(mEGLDevice);
@@ -364,7 +366,6 @@
     mVertexDataManager = new VertexDataManager(this);
     mIndexDataManager = new IndexDataManager(this, getRendererClass());
 
-    // TODO(jmadill): use context caps, and place in common D3D location
     mTranslatedAttribCache.resize(getRendererCaps().maxVertexAttributes);
 }
 
@@ -1191,7 +1192,8 @@
         return error;
     }
 
-    return mVertexDeclarationCache.applyDeclaration(mDevice, mTranslatedAttribCache, state.getProgram(), instances, &mRepeatDraw);
+    return mVertexDeclarationCache.applyDeclaration(
+        mDevice, mTranslatedAttribCache, state.getProgram(), first, instances, &mRepeatDraw);
 }
 
 // Applies the indices and element array bindings to the Direct3D 9 device
@@ -1233,6 +1235,7 @@
 
 gl::Error Renderer9::drawArraysImpl(const gl::Data &data,
                                     GLenum mode,
+                                    GLint startVertex,
                                     GLsizei count,
                                     GLsizei instances)
 {
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
index c8d2f48..b6456ae 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
@@ -275,6 +275,7 @@
   private:
     gl::Error drawArraysImpl(const gl::Data &data,
                              GLenum mode,
+                             GLint startVertex,
                              GLsizei count,
                              GLsizei instances) override;
     gl::Error drawElementsImpl(const gl::Data &data,
@@ -396,6 +397,7 @@
     UINT mMaxNullColorbufferLRU;
 
     DeviceD3D *mEGLDevice;
+    std::vector<TranslatedAttribute> mTranslatedAttribCache;
 };
 
 }
diff --git a/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.cpp b/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.cpp
index bd5f317..afae518 100644
--- a/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.cpp
@@ -42,11 +42,13 @@
     }
 }
 
-gl::Error VertexDeclarationCache::applyDeclaration(IDirect3DDevice9 *device,
-                                                   const std::vector<TranslatedAttribute> &attributes,
-                                                   gl::Program *program,
-                                                   GLsizei instances,
-                                                   GLsizei *repeatDraw)
+gl::Error VertexDeclarationCache::applyDeclaration(
+    IDirect3DDevice9 *device,
+    const std::vector<TranslatedAttribute> &attributes,
+    gl::Program *program,
+    GLint start,
+    GLsizei instances,
+    GLsizei *repeatDraw)
 {
     ASSERT(gl::MAX_VERTEX_ATTRIBS >= attributes.size());
 
@@ -149,14 +151,18 @@
 
             VertexBuffer9 *vertexBuffer = GetAs<VertexBuffer9>(attributes[i].vertexBuffer.get());
 
+            unsigned int offset = 0;
+            ANGLE_TRY_RESULT(attributes[i].computeOffset(start), offset);
+
             if (mAppliedVBs[stream].serial != attributes[i].serial ||
                 mAppliedVBs[stream].stride != attributes[i].stride ||
-                mAppliedVBs[stream].offset != attributes[i].offset)
+                mAppliedVBs[stream].offset != offset)
             {
-                device->SetStreamSource(stream, vertexBuffer->getBuffer(), attributes[i].offset, attributes[i].stride);
+                device->SetStreamSource(stream, vertexBuffer->getBuffer(), offset,
+                                        attributes[i].stride);
                 mAppliedVBs[stream].serial = attributes[i].serial;
                 mAppliedVBs[stream].stride = attributes[i].stride;
-                mAppliedVBs[stream].offset = attributes[i].offset;
+                mAppliedVBs[stream].offset = offset;
             }
 
             gl::VertexFormatType vertexformatType = gl::GetVertexFormatType(*attributes[i].attribute, GL_FLOAT);
diff --git a/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.h b/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.h
index bad4de4..7bd7cab 100644
--- a/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.h
+++ b/src/libANGLE/renderer/d3d/d3d9/VertexDeclarationCache.h
@@ -30,6 +30,7 @@
     gl::Error applyDeclaration(IDirect3DDevice9 *device,
                                const std::vector<TranslatedAttribute> &attributes,
                                gl::Program *program,
+                               GLint start,
                                GLsizei instances,
                                GLsizei *repeatDraw);
 
diff --git a/src/libGLESv2.gypi b/src/libGLESv2.gypi
index a6eb47a..591fa9a 100644
--- a/src/libGLESv2.gypi
+++ b/src/libGLESv2.gypi
@@ -392,6 +392,7 @@
             'libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.cpp',
             'libANGLE/renderer/d3d/d3d11/texture_format_table_autogen.h',
             'libANGLE/renderer/d3d/d3d11/texture_format_table.h',
+            'libANGLE/renderer/d3d/d3d11/VertexArray11.cpp',
             'libANGLE/renderer/d3d/d3d11/VertexArray11.h',
             'libANGLE/renderer/d3d/d3d11/VertexBuffer11.cpp',
             'libANGLE/renderer/d3d/d3d11/VertexBuffer11.h',
diff --git a/src/tests/gl_tests/BufferDataTest.cpp b/src/tests/gl_tests/BufferDataTest.cpp
index 6e23ecce..6235ef6 100644
--- a/src/tests/gl_tests/BufferDataTest.cpp
+++ b/src/tests/gl_tests/BufferDataTest.cpp
@@ -207,6 +207,31 @@
     delete[] data;
 }
 
+// Internally in D3D, we promote dynamic data to static after many draw loops. This code tests
+// path.
+TEST_P(BufferDataTest, RepeatedDrawWithDynamic)
+{
+    std::vector<GLfloat> data;
+    for (int i = 0; i < 16; ++i)
+    {
+        data.push_back(static_cast<GLfloat>(i));
+    }
+
+    glUseProgram(mProgram);
+    glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * data.size(), data.data(), GL_DYNAMIC_DRAW);
+    glVertexAttribPointer(mAttribLocation, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glEnableVertexAttribArray(mAttribLocation);
+
+    for (int drawCount = 0; drawCount < 40; ++drawCount)
+    {
+        drawQuad(mProgram, "position", 0.5f);
+    }
+
+    EXPECT_GL_NO_ERROR();
+}
+
 class IndexedBufferCopyTest : public ANGLETest
 {
   protected:
diff --git a/src/tests/gl_tests/CopyTexImageTest.cpp b/src/tests/gl_tests/CopyTexImageTest.cpp
index 293d114..545ffd6 100644
--- a/src/tests/gl_tests/CopyTexImageTest.cpp
+++ b/src/tests/gl_tests/CopyTexImageTest.cpp
@@ -82,7 +82,7 @@
         glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, x, y, w, h);
     }
 
-    virtual void SetUp()
+    void SetUp() override
     {
         ANGLETest::SetUp();
 
@@ -119,14 +119,14 @@
         ASSERT_GL_NO_ERROR();
     }
 
-    virtual void TearDown()
+    void TearDown() override
     {
         glDeleteProgram(mTextureProgram);
 
         ANGLETest::TearDown();
     }
 
-    void verifyResults(GLuint texture, GLubyte data[4], GLint x, GLint y) const
+    void verifyResults(GLuint texture, GLubyte data[4], GLint x, GLint y)
     {
         glViewport(0, 0, 16, 16);
 
diff --git a/src/tests/gl_tests/D3D11EmulatedIndexedBufferTest.cpp b/src/tests/gl_tests/D3D11EmulatedIndexedBufferTest.cpp
index fe8a722..01c9832 100644
--- a/src/tests/gl_tests/D3D11EmulatedIndexedBufferTest.cpp
+++ b/src/tests/gl_tests/D3D11EmulatedIndexedBufferTest.cpp
@@ -38,7 +38,8 @@
         gl::Error error = mSourceBuffer->setData(testData, sizeof(testData), GL_STATIC_DRAW);
         ASSERT_FALSE(error.isError());
 
-        mTranslatedAttribute.offset = 0;
+        mTranslatedAttribute.baseOffset            = 0;
+        mTranslatedAttribute.usesFirstVertexOffset = false;
         mTranslatedAttribute.stride = sizeof(GLfloat);
 
         GLubyte indices[] = {0, 0, 3, 4, 2, 1, 1};
@@ -107,7 +108,8 @@
 
     void emulateAndCompare(rx::SourceIndexData *srcData)
     {
-        auto bufferOrError = mSourceBuffer->getEmulatedIndexedBuffer(srcData, mTranslatedAttribute);
+        auto bufferOrError =
+            mSourceBuffer->getEmulatedIndexedBuffer(srcData, mTranslatedAttribute, 0);
         ASSERT_FALSE(bufferOrError.isError());
         ID3D11Buffer *emulatedBuffer = bufferOrError.getResult();
         ASSERT_TRUE(emulatedBuffer != nullptr);
diff --git a/src/tests/gl_tests/TransformFeedbackTest.cpp b/src/tests/gl_tests/TransformFeedbackTest.cpp
index b9a000b..d0bf91b 100644
--- a/src/tests/gl_tests/TransformFeedbackTest.cpp
+++ b/src/tests/gl_tests/TransformFeedbackTest.cpp
@@ -707,7 +707,90 @@
     ASSERT_GL_NO_ERROR();
 }
 
+class TransformFeedbackLifetimeTest : public TransformFeedbackTest
+{
+  protected:
+    TransformFeedbackLifetimeTest() : mVertexArray(0) {}
+
+    void SetUp() override
+    {
+        ANGLETest::SetUp();
+
+        glGenVertexArrays(1, &mVertexArray);
+        glBindVertexArray(mVertexArray);
+
+        std::vector<std::string> tfVaryings;
+        tfVaryings.push_back("gl_Position");
+        compileDefaultProgram(tfVaryings, GL_SEPARATE_ATTRIBS);
+
+        glGenBuffers(1, &mTransformFeedbackBuffer);
+        mTransformFeedbackBufferSize = 1 << 24;  // ~16MB
+        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer);
+        glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, NULL,
+                     GL_DYNAMIC_DRAW);
+        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
+
+        glGenTransformFeedbacks(1, &mTransformFeedback);
+
+        ASSERT_GL_NO_ERROR();
+    }
+
+    void TearDown() override
+    {
+        glDeleteVertexArrays(1, &mVertexArray);
+        TransformFeedbackTest::TearDown();
+    }
+
+    GLuint mVertexArray;
+};
+
+// Tests a bug with state syncing and deleted transform feedback buffers.
+TEST_P(TransformFeedbackLifetimeTest, DeletedBuffer)
+{
+    // First stream vertex data to mTransformFeedbackBuffer.
+    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback);
+    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
+
+    glUseProgram(mProgram);
+
+    glBeginTransformFeedback(GL_TRIANGLES);
+    drawQuad(mProgram, "position", 0.5f, 1.0f, true);
+    glEndTransformFeedback();
+
+    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+
+    // TODO(jmadill): Remove this when http://anglebug.com/1351 is fixed.
+    glBindVertexArray(0);
+    drawQuad(mProgram, "position", 0.5f);
+    glBindVertexArray(1);
+
+    // Next, draw vertices with mTransformFeedbackBuffer. This will link to mVertexArray.
+    glBindBuffer(GL_ARRAY_BUFFER, mTransformFeedbackBuffer);
+    GLint loc = glGetAttribLocation(mProgram, "position");
+    ASSERT_NE(-1, loc);
+    glVertexAttribPointer(loc, 1, GL_FLOAT, GL_FALSE, 4, nullptr);
+    glEnableVertexAttribArray(loc);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glDrawArrays(GL_TRIANGLES, 0, 3);
+
+    // Delete resources, making a stranded pointer to mVertexArray in mTransformFeedbackBuffer.
+    glDeleteBuffers(1, &mTransformFeedbackBuffer);
+    mTransformFeedbackBuffer = 0;
+    glDeleteVertexArrays(1, &mVertexArray);
+    mVertexArray = 0;
+
+    // Then draw again with transform feedback, dereferencing the stranded pointer.
+    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedback);
+    glBeginTransformFeedback(GL_TRIANGLES);
+    drawQuad(mProgram, "position", 0.5f, 1.0f, true);
+    glEndTransformFeedback();
+    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
+
+    ASSERT_GL_NO_ERROR();
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(TransformFeedbackTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
+ANGLE_INSTANTIATE_TEST(TransformFeedbackLifetimeTest, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
 
 }  // anonymous namespace
diff --git a/src/tests/gl_tests/VertexAttributeTest.cpp b/src/tests/gl_tests/VertexAttributeTest.cpp
index 510a969..619940e 100644
--- a/src/tests/gl_tests/VertexAttributeTest.cpp
+++ b/src/tests/gl_tests/VertexAttributeTest.cpp
@@ -50,7 +50,8 @@
 class VertexAttributeTest : public ANGLETest
 {
   protected:
-    VertexAttributeTest() : mProgram(0), mTestAttrib(-1), mExpectedAttrib(-1), mBuffer(0)
+    VertexAttributeTest()
+        : mProgram(0), mTestAttrib(-1), mExpectedAttrib(-1), mBuffer(0), mQuadBuffer(0)
     {
         setWindowWidth(128);
         setWindowHeight(128);
@@ -122,6 +123,23 @@
         glEnableVertexAttribArray(mExpectedAttrib);
     }
 
+    void checkPixels()
+    {
+        GLint viewportSize[4];
+        glGetIntegerv(GL_VIEWPORT, viewportSize);
+
+        GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2;
+        GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2;
+
+        // We need to offset our checks from triangle edges to ensure we don't fall on a single tri
+        // Avoid making assumptions of drawQuad with four checks to check the four possible tri
+        // regions
+        EXPECT_PIXEL_EQ((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255);
+        EXPECT_PIXEL_EQ((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255);
+        EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255);
+        EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255);
+    }
+
     void runTest(const TestData &test)
     {
         // TODO(geofflang): Figure out why this is broken on AMD OpenGL
@@ -131,17 +149,6 @@
             return;
         }
 
-        if (mProgram == 0)
-        {
-            initBasicProgram();
-        }
-
-        GLint viewportSize[4];
-        glGetIntegerv(GL_VIEWPORT, viewportSize);
-
-        GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2;
-        GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2;
-
         for (GLint i = 0; i < 4; i++)
         {
             GLint typeSize = i + 1;
@@ -152,12 +159,7 @@
             glDisableVertexAttribArray(mTestAttrib);
             glDisableVertexAttribArray(mExpectedAttrib);
 
-            // We need to offset our checks from triangle edges to ensure we don't fall on a single tri
-            // Avoid making assumptions of drawQuad with four checks to check the four possible tri regions
-            EXPECT_PIXEL_EQ((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255);
-            EXPECT_PIXEL_EQ((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255);
-            EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255);
-            EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255);
+            checkPixels();
         }
     }
 
@@ -178,6 +180,7 @@
     {
         glDeleteProgram(mProgram);
         glDeleteBuffers(1, &mBuffer);
+        glDeleteBuffers(1, &mQuadBuffer);
 
         ANGLETest::TearDown();
     }
@@ -262,6 +265,7 @@
     GLint mTestAttrib;
     GLint mExpectedAttrib;
     GLuint mBuffer;
+    GLuint mQuadBuffer;
 };
 
 TEST_P(VertexAttributeTest, UnsignedByteUnnormalized)
@@ -545,6 +549,69 @@
     EXPECT_GL_ERROR(GL_INVALID_OPERATION);
 }
 
+// Verify that using a different start vertex doesn't mess up the draw.
+TEST_P(VertexAttributeTest, DrawArraysWithBufferOffset)
+{
+    // TODO(jmadill): Diagnose this failure.
+    if (IsD3D11_FL93())
+    {
+        std::cout << "Test disabled on D3D11 FL 9_3" << std::endl;
+        return;
+    }
+
+    // TODO(geofflang): Figure out why this is broken on AMD OpenGL
+    if (IsAMD() && getPlatformRenderer() == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE)
+    {
+        std::cout << "Test skipped on AMD OpenGL." << std::endl;
+        return;
+    }
+
+    initBasicProgram();
+    glUseProgram(mProgram);
+
+    GLfloat inputData[mVertexCount];
+    GLfloat expectedData[mVertexCount];
+    for (size_t count = 0; count < mVertexCount; ++count)
+    {
+        inputData[count]    = static_cast<GLfloat>(count);
+        expectedData[count] = inputData[count];
+    }
+
+    auto quadVertices        = GetQuadVertices();
+    GLsizei quadVerticesSize = static_cast<GLsizei>(quadVertices.size() * sizeof(quadVertices[0]));
+
+    glGenBuffers(1, &mQuadBuffer);
+    glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer);
+    glBufferData(GL_ARRAY_BUFFER, quadVerticesSize + sizeof(Vector3), nullptr, GL_STATIC_DRAW);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, quadVerticesSize, quadVertices.data());
+
+    GLint positionLocation = glGetAttribLocation(mProgram, "position");
+    ASSERT_NE(-1, positionLocation);
+    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glEnableVertexAttribArray(positionLocation);
+
+    GLsizei dataSize = mVertexCount * TypeStride(GL_FLOAT);
+    glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
+    glBufferData(GL_ARRAY_BUFFER, dataSize + TypeStride(GL_FLOAT), nullptr, GL_STATIC_DRAW);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, dataSize, inputData);
+    glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glEnableVertexAttribArray(mTestAttrib);
+
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData);
+    glEnableVertexAttribArray(mExpectedAttrib);
+
+    // Vertex draw with no start vertex offset (second argument is zero).
+    glDrawArrays(GL_TRIANGLES, 0, 6);
+    checkPixels();
+
+    // Draw offset by one vertex.
+    glDrawArrays(GL_TRIANGLES, 1, 6);
+    checkPixels();
+
+    EXPECT_GL_NO_ERROR();
+}
+
 class VertexAttributeCachingTest : public VertexAttributeTest
 {
   protected:
diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp
index 9aa3dfe..15135a5 100644
--- a/src/tests/test_utils/ANGLETest.cpp
+++ b/src/tests/test_utils/ANGLETest.cpp
@@ -154,6 +154,39 @@
     }
 }
 
+// static
+std::array<Vector3, 6> ANGLETest::GetQuadVertices()
+{
+    std::array<Vector3, 6> vertices;
+    vertices[0] = Vector3(-1.0f, 1.0f, 0.5f);
+    vertices[1] = Vector3(-1.0f, -1.0f, 0.5f);
+    vertices[2] = Vector3(1.0f, -1.0f, 0.5f);
+    vertices[3] = Vector3(-1.0f, 1.0f, 0.5f);
+    vertices[4] = Vector3(1.0f, -1.0f, 0.5f);
+    vertices[5] = Vector3(1.0f, 1.0f, 0.5f);
+    return vertices;
+}
+
+void ANGLETest::setupQuadVertexBuffer(GLfloat positionAttribZ, GLfloat positionAttribXYScale)
+{
+    if (mQuadVertexBuffer == 0)
+    {
+        glGenBuffers(1, &mQuadVertexBuffer);
+    }
+
+    auto quadVertices = GetQuadVertices();
+    for (Vector3 &vertex : quadVertices)
+    {
+        vertex.x *= positionAttribXYScale;
+        vertex.y *= positionAttribXYScale;
+        vertex.z = positionAttribZ;
+    }
+
+    glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
+}
+
+// static
 void ANGLETest::drawQuad(GLuint program,
                          const std::string &positionAttribName,
                          GLfloat positionAttribZ)
@@ -161,11 +194,21 @@
     drawQuad(program, positionAttribName, positionAttribZ, 1.0f);
 }
 
+// static
 void ANGLETest::drawQuad(GLuint program,
                          const std::string &positionAttribName,
                          GLfloat positionAttribZ,
                          GLfloat positionAttribXYScale)
 {
+    drawQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, false);
+}
+
+void ANGLETest::drawQuad(GLuint program,
+                         const std::string &positionAttribName,
+                         GLfloat positionAttribZ,
+                         GLfloat positionAttribXYScale,
+                         bool useVertexBuffer)
+{
     GLint previousProgram = 0;
     glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram);
     if (previousProgram != static_cast<GLint>(program))
@@ -175,17 +218,24 @@
 
     GLint positionLocation = glGetAttribLocation(program, positionAttribName.c_str());
 
-    const GLfloat vertices[] = {
-        -1.0f * positionAttribXYScale,  1.0f * positionAttribXYScale, positionAttribZ,
-        -1.0f * positionAttribXYScale, -1.0f * positionAttribXYScale, positionAttribZ,
-         1.0f * positionAttribXYScale, -1.0f * positionAttribXYScale, positionAttribZ,
+    if (useVertexBuffer)
+    {
+        setupQuadVertexBuffer(positionAttribZ, positionAttribXYScale);
+        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+    }
+    else
+    {
+        auto quadVertices = GetQuadVertices();
+        for (Vector3 &vertex : quadVertices)
+        {
+            vertex.x *= positionAttribXYScale;
+            vertex.y *= positionAttribXYScale;
+            vertex.z = positionAttribZ;
+        }
 
-        -1.0f * positionAttribXYScale,  1.0f * positionAttribXYScale, positionAttribZ,
-         1.0f * positionAttribXYScale, -1.0f * positionAttribXYScale, positionAttribZ,
-         1.0f * positionAttribXYScale,  1.0f * positionAttribXYScale, positionAttribZ,
-    };
-
-    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
+        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
+    }
     glEnableVertexAttribArray(positionLocation);
 
     glDrawArrays(GL_TRIANGLES, 0, 6);
@@ -213,27 +263,17 @@
 {
     GLint positionLocation = glGetAttribLocation(program, positionAttribName.c_str());
 
-    glUseProgram(program);
+    GLint activeProgram = 0;
+    glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram);
+    if (static_cast<GLuint>(activeProgram) != program)
+    {
+        glUseProgram(program);
+    }
 
     GLuint prevBinding = 0;
     glGetIntegerv(GL_ARRAY_BUFFER_BINDING, reinterpret_cast<GLint *>(&prevBinding));
 
-    if (mQuadVertexBuffer == 0)
-    {
-        glGenBuffers(1, &mQuadVertexBuffer);
-        const GLfloat vertices[] = {
-            -1.0f * positionAttribXYScale, 1.0f * positionAttribXYScale,  positionAttribZ,
-            -1.0f * positionAttribXYScale, -1.0f * positionAttribXYScale, positionAttribZ,
-            1.0f * positionAttribXYScale,  -1.0f * positionAttribXYScale, positionAttribZ,
-            1.0f * positionAttribXYScale,  1.0f * positionAttribXYScale,  positionAttribZ,
-        };
-        glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
-        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 4, vertices, GL_STATIC_DRAW);
-    }
-    else
-    {
-        glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
-    }
+    setupQuadVertexBuffer(positionAttribZ, positionAttribXYScale);
 
     glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
     glEnableVertexAttribArray(positionLocation);
@@ -248,7 +288,10 @@
     glDisableVertexAttribArray(positionLocation);
     glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, NULL);
 
-    glUseProgram(0);
+    if (static_cast<GLuint>(activeProgram) != program)
+    {
+        glUseProgram(static_cast<GLuint>(activeProgram));
+    }
 }
 
 GLuint ANGLETest::compileShader(GLenum type, const std::string &source)
diff --git a/src/tests/test_utils/ANGLETest.h b/src/tests/test_utils/ANGLETest.h
index 6afbb2b..6fa5ac7 100644
--- a/src/tests/test_utils/ANGLETest.h
+++ b/src/tests/test_utils/ANGLETest.h
@@ -12,6 +12,7 @@
 
 #include <gtest/gtest.h>
 #include <algorithm>
+#include <array>
 
 #include "angle_gl.h"
 #include "angle_test_configs.h"
@@ -107,13 +108,19 @@
 
     virtual void swapBuffers();
 
-    static void drawQuad(GLuint program,
-                         const std::string &positionAttribName,
-                         GLfloat positionAttribZ);
-    static void drawQuad(GLuint program,
-                         const std::string &positionAttribName,
-                         GLfloat positionAttribZ,
-                         GLfloat positionAttribXYScale);
+    void setupQuadVertexBuffer(GLfloat positionAttribZ, GLfloat positionAttribXYScale);
+
+    void drawQuad(GLuint program, const std::string &positionAttribName, GLfloat positionAttribZ);
+    void drawQuad(GLuint program,
+                  const std::string &positionAttribName,
+                  GLfloat positionAttribZ,
+                  GLfloat positionAttribXYScale);
+    void drawQuad(GLuint program,
+                  const std::string &positionAttribName,
+                  GLfloat positionAttribZ,
+                  GLfloat positionAttribXYScale,
+                  bool useVertexBuffer);
+    static std::array<Vector3, 6> GetQuadVertices();
     void drawIndexedQuad(GLuint program,
                          const std::string &positionAttribName,
                          GLfloat positionAttribZ);