Handle client data for draw calls with RendererGL.

BUG=angleproject:880

Change-Id: I67839d4934767db77cff7b501002c5aafbcbaef2
Reviewed-on: https://chromium-review.googlesource.com/257672
Tested-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/renderer/gl/VertexArrayGL.cpp b/src/libANGLE/renderer/gl/VertexArrayGL.cpp
index 31c264e..8de2201 100644
--- a/src/libANGLE/renderer/gl/VertexArrayGL.cpp
+++ b/src/libANGLE/renderer/gl/VertexArrayGL.cpp
@@ -9,8 +9,10 @@
 #include "libANGLE/renderer/gl/VertexArrayGL.h"
 
 #include "common/debug.h"
+#include "common/mathutil.h"
 #include "libANGLE/Buffer.h"
 #include "libANGLE/angletypes.h"
+#include "libANGLE/formatutils.h"
 #include "libANGLE/renderer/gl/BufferGL.h"
 #include "libANGLE/renderer/gl/FunctionsGL.h"
 #include "libANGLE/renderer/gl/StateManagerGL.h"
@@ -26,7 +28,11 @@
       mElementArrayBuffer(),
       mAttributes(),
       mAppliedElementArrayBuffer(0),
-      mAppliedAttributes()
+      mAppliedAttributes(),
+      mStreamingElementArrayBufferSize(0),
+      mStreamingElementArrayBuffer(0),
+      mStreamingArrayBufferSize(0),
+      mStreamingArrayBuffer(0)
 {
     ASSERT(mFunctions);
     ASSERT(mStateManager);
@@ -47,7 +53,26 @@
         mVertexArrayID = 0;
     }
 
+    if (mStreamingElementArrayBuffer != 0)
+    {
+        mFunctions->deleteBuffers(1, &mStreamingElementArrayBuffer);
+        mStreamingElementArrayBufferSize = 0;
+        mStreamingElementArrayBuffer = 0;
+    }
+
+    if (mStreamingArrayBuffer != 0)
+    {
+        mFunctions->deleteBuffers(1, &mStreamingArrayBuffer);
+        mStreamingArrayBufferSize = 0;
+        mStreamingArrayBuffer = 0;
+    }
+
     mElementArrayBuffer.set(nullptr);
+    for (size_t idx = 0; idx < mAttributes.size(); idx++)
+    {
+        mAttributes[idx].buffer.set(NULL);
+    }
+
     for (size_t idx = 0; idx < mAppliedAttributes.size(); idx++)
     {
         mAppliedAttributes[idx].buffer.set(NULL);
@@ -74,26 +99,88 @@
     mAttributes[idx].enabled = enabledState;
 }
 
-void VertexArrayGL::syncState() const
+gl::Error VertexArrayGL::syncDrawArraysState(GLint first, GLsizei count) const
+{
+    return syncDrawState(first, count, GL_NONE, nullptr, nullptr);
+}
+
+gl::Error VertexArrayGL::syncDrawElementsState(GLsizei count, GLenum type, const GLvoid *indices, const GLvoid **outIndices) const
+{
+    return syncDrawState(0, count, type, indices, outIndices);
+}
+
+gl::Error VertexArrayGL::syncDrawState(GLint first, GLsizei count, GLenum type, const GLvoid *indices, const GLvoid **outIndices) const
 {
     mStateManager->bindVertexArray(mVertexArrayID);
 
-    GLuint elementArrayBufferID = 0;
-    if (mElementArrayBuffer.get() != nullptr)
+    // Check if any attributes need to be streamed, determines if the index range needs to be computed
+    bool attributesNeedStreaming = doAttributesNeedStreaming();
+
+    // Determine if an index buffer needs to be streamed and the range of vertices that need to be copied
+    RangeUI indexRange(0, 0);
+    if (type != GL_NONE)
     {
-        const BufferGL *bufferGL = GetImplAs<BufferGL>(mElementArrayBuffer.get());
-        elementArrayBufferID = bufferGL->getBufferID();
+        gl::Error error = syncIndexData(count, type, indices, attributesNeedStreaming, &indexRange, outIndices);
+        if (error.isError())
+        {
+            return error;
+        }
+    }
+    else
+    {
+        // Not an indexed call, set the range to [first, first + count)
+        indexRange.start = first;
+        indexRange.end = first + count;
     }
 
-    if (elementArrayBufferID != mAppliedElementArrayBuffer)
+    // Sync the vertex attribute state and track what data needs to be streamed
+    size_t streamingDataSize = 0;
+    size_t maxAttributeDataSize = 0;
+    gl::Error error = syncAttributeState(attributesNeedStreaming, indexRange, &streamingDataSize, &maxAttributeDataSize);
+    if (error.isError())
     {
-        mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBufferID);
-        mAppliedElementArrayBuffer = elementArrayBufferID;
+        return error;
     }
 
+    if (streamingDataSize > 0)
+    {
+        ASSERT(attributesNeedStreaming);
+
+        gl::Error error = streamAttributes(streamingDataSize, maxAttributeDataSize, indexRange);
+        if (error.isError())
+        {
+            return error;
+        }
+    }
+
+    return gl::Error(GL_NO_ERROR);
+}
+
+bool VertexArrayGL::doAttributesNeedStreaming() const
+{
+    // TODO: if GLES, nothing needs to be streamed
+    for (size_t idx = 0; idx < mAttributes.size(); idx++)
+    {
+        if (mAttributes[idx].enabled && mAttributes[idx].buffer.get() == nullptr)
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+gl::Error VertexArrayGL::syncAttributeState(bool attributesNeedStreaming, const RangeUI &indexRange,
+                                            size_t *outStreamingDataSize, size_t *outMaxAttributeDataSize) const
+{
+    *outStreamingDataSize = 0;
+    *outMaxAttributeDataSize = 0;
+
     for (size_t idx = 0; idx < mAttributes.size(); idx++)
     {
-        if (mAppliedAttributes[idx] != mAttributes[idx])
+        // Always sync the enabled and divisor state, they are required for both streaming and buffered
+        // attributes
+        if (mAppliedAttributes[idx].enabled != mAttributes[idx].enabled)
         {
             if (mAttributes[idx].enabled)
             {
@@ -103,37 +190,230 @@
             {
                 mFunctions->disableVertexAttribArray(idx);
             }
+            mAppliedAttributes[idx].enabled = mAttributes[idx].enabled;
+        }
+        if (mAppliedAttributes[idx].divisor != mAttributes[idx].divisor)
+        {
+            mFunctions->vertexAttribDivisor(idx, mAttributes[idx].divisor);
+            mAppliedAttributes[idx].divisor = mAttributes[idx].divisor;
+        }
 
-            const gl::Buffer *arrayBuffer = mAttributes[idx].buffer.get();
-            if (arrayBuffer != nullptr)
+        if (mAttributes[idx].enabled && mAttributes[idx].buffer.get() == nullptr)
+        {
+            ASSERT(attributesNeedStreaming);
+
+            const size_t streamedVertexCount = indexRange.end - indexRange.start + 1;
+
+            // If streaming is going to be required, compute the size of the required buffer
+            // and how much slack space at the beginning of the buffer will be required by determining
+            // the attribute with the largest data size.
+            size_t typeSize = ComputeVertexAttributeTypeSize(mAttributes[idx]);
+            *outStreamingDataSize += typeSize * streamedVertexCount;
+            *outMaxAttributeDataSize = std::max(*outMaxAttributeDataSize, typeSize);
+        }
+        else
+        {
+            // Sync the attribute with no translation
+            if (mAppliedAttributes[idx] != mAttributes[idx])
             {
+                const gl::Buffer *arrayBuffer = mAttributes[idx].buffer.get();
                 const BufferGL *arrayBufferGL = GetImplAs<BufferGL>(arrayBuffer);
                 mStateManager->bindBuffer(GL_ARRAY_BUFFER, arrayBufferGL->getBufferID());
-            }
-            else
-            {
-                // This will take some extra work, core OpenGL doesn't support binding raw data pointers
-                // to VAOs
-                UNIMPLEMENTED();
-            }
 
-            if (mAttributes[idx].pureInteger)
-            {
-                mFunctions->vertexAttribIPointer(idx, mAttributes[idx].size, mAttributes[idx].type,
-                                                 mAttributes[idx].stride, mAttributes[idx].pointer);
-            }
-            else
-            {
-                mFunctions->vertexAttribPointer(idx, mAttributes[idx].size, mAttributes[idx].type,
-                                                mAttributes[idx].normalized, mAttributes[idx].stride,
-                                                mAttributes[idx].pointer);
-            }
+                if (mAttributes[idx].pureInteger)
+                {
+                    mFunctions->vertexAttribIPointer(idx, mAttributes[idx].size, mAttributes[idx].type,
+                                                     mAttributes[idx].stride, mAttributes[idx].pointer);
+                }
+                else
+                {
+                    mFunctions->vertexAttribPointer(idx, mAttributes[idx].size, mAttributes[idx].type,
+                                                    mAttributes[idx].normalized, mAttributes[idx].stride,
+                                                    mAttributes[idx].pointer);
+                }
 
-            mFunctions->vertexAttribDivisor(idx, mAttributes[idx].divisor);
-
-            mAppliedAttributes[idx] = mAttributes[idx];
+                mAppliedAttributes[idx] = mAttributes[idx];
+            }
         }
     }
+
+    return gl::Error(GL_NO_ERROR);
+}
+
+gl::Error VertexArrayGL::syncIndexData(GLsizei count, GLenum type, const GLvoid *indices, bool attributesNeedStreaming,
+                                       RangeUI *outIndexRange, const GLvoid **outIndices) const
+{
+    ASSERT(outIndices);
+
+    // Need to check the range of indices if attributes need to be streamed
+    if (mElementArrayBuffer.get() != nullptr)
+    {
+        const BufferGL *bufferGL = GetImplAs<BufferGL>(mElementArrayBuffer.get());
+        GLuint elementArrayBufferID = bufferGL->getBufferID();
+        if (elementArrayBufferID != mAppliedElementArrayBuffer)
+        {
+            mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBufferID);
+            mAppliedElementArrayBuffer = elementArrayBufferID;
+        }
+
+        // Only compute the index range if the attributes also need to be streamed
+        if (attributesNeedStreaming)
+        {
+            ptrdiff_t elementArrayBufferOffset = reinterpret_cast<ptrdiff_t>(indices);
+
+            // Find the index range in the buffer
+            const IndexRangeCache *rangeCache = mElementArrayBuffer.get()->getIndexRangeCache();
+
+            unsigned int streamOffset = 0;
+            if (!rangeCache->findRange(type, elementArrayBufferOffset, count, outIndexRange, &streamOffset))
+            {
+                // Need to compute the index range.
+                mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBufferID);
+                uint8_t *elementArrayBufferPointer = reinterpret_cast<uint8_t*>(mFunctions->mapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY));
+
+                *outIndexRange = IndexRangeCache::ComputeRange(type, elementArrayBufferPointer + elementArrayBufferOffset, count);
+
+                // TODO: Store the range cache at the impl level since the gl::Buffer object is supposed to remain constant
+                const_cast<IndexRangeCache*>(rangeCache)->addRange(type, elementArrayBufferOffset, count, *outIndexRange, 0);
+
+                if (!mFunctions->unmapBuffer(GL_ELEMENT_ARRAY_BUFFER))
+                {
+                    return gl::Error(GL_OUT_OF_MEMORY);
+                }
+            }
+        }
+
+        // Indices serves as an offset into the index buffer in this case, use the same value for the draw call
+        *outIndices = indices;
+    }
+    else
+    {
+        // Need to stream the index buffer
+        // TODO: if GLES, nothing needs to be streamed
+
+        // Only compute the index range if the attributes also need to be streamed
+        if (attributesNeedStreaming)
+        {
+            *outIndexRange = IndexRangeCache::ComputeRange(type, indices, count);
+        }
+
+        // Allocate the streaming element array buffer
+        if (mStreamingElementArrayBuffer == 0)
+        {
+            mFunctions->genBuffers(1, &mStreamingElementArrayBuffer);
+            mStreamingElementArrayBufferSize = 0;
+        }
+
+        mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, mStreamingElementArrayBuffer);
+        mAppliedElementArrayBuffer = mStreamingElementArrayBuffer;
+
+        // Make sure the element array buffer is large enough
+        const gl::Type &indexTypeInfo = gl::GetTypeInfo(type);
+        size_t requiredStreamingBufferSize = indexTypeInfo.bytes * count;
+        if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize)
+        {
+            // Copy the indices in while resizing the buffer
+            mFunctions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize, indices, GL_DYNAMIC_DRAW);
+            mStreamingElementArrayBufferSize = requiredStreamingBufferSize;
+        }
+        else
+        {
+            // Put the indices at the beginning of the buffer
+            mFunctions->bufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredStreamingBufferSize, indices);
+        }
+
+        // Set the index offset for the draw call to zero since the supplied index pointer is to client data
+        *outIndices = nullptr;
+    }
+
+    return gl::Error(GL_NO_ERROR);
+}
+
+gl::Error VertexArrayGL::streamAttributes(size_t streamingDataSize, size_t maxAttributeDataSize, const RangeUI &indexRange) const
+{
+    if (mStreamingArrayBuffer == 0)
+    {
+        mFunctions->genBuffers(1, &mStreamingArrayBuffer);
+        mStreamingArrayBufferSize = 0;
+    }
+
+    // If first is greater than zero, a slack space needs to be left at the beginning of the buffer so that
+    // the same 'first' argument can be passed into the draw call.
+    const size_t bufferEmptySpace = maxAttributeDataSize * indexRange.start;
+    const size_t requiredBufferSize = streamingDataSize + bufferEmptySpace;
+
+    mStateManager->bindBuffer(GL_ARRAY_BUFFER, mStreamingArrayBuffer);
+    if (requiredBufferSize > mStreamingArrayBufferSize)
+    {
+        mFunctions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW);
+        mStreamingArrayBufferSize = requiredBufferSize;
+    }
+
+    // Unmapping a buffer can return GL_FALSE to indicate that the system has corrupted the data
+    // somehow (such as by a screen change), retry writing the data a few times and return OUT_OF_MEMORY
+    // if that fails.
+    GLboolean unmapResult = GL_FALSE;
+    size_t unmapRetryAttempts = 5;
+    while (unmapResult != GL_TRUE && --unmapRetryAttempts > 0)
+    {
+        uint8_t *bufferPointer = reinterpret_cast<uint8_t*>(mFunctions->mapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
+        size_t curBufferOffset = bufferEmptySpace;
+
+        const size_t streamedVertexCount = indexRange.end - indexRange.start + 1;
+
+        for (size_t idx = 0; idx < mAttributes.size(); idx++)
+        {
+            if (mAttributes[idx].enabled && mAttributes[idx].buffer.get() == nullptr)
+            {
+                const size_t sourceStride = ComputeVertexAttributeStride(mAttributes[idx]);
+                const size_t destStride = ComputeVertexAttributeTypeSize(mAttributes[idx]);
+
+                const uint8_t *inputPointer = reinterpret_cast<const uint8_t*>(mAttributes[idx].pointer);
+
+                // Pack the data when copying it, user could have supplied a very large stride that would
+                // cause the buffer to be much larger than needed.
+                if (destStride == sourceStride)
+                {
+                    // Can copy in one go, the data is packed
+                    memcpy(bufferPointer + curBufferOffset,
+                           inputPointer + (sourceStride * indexRange.start),
+                           destStride * streamedVertexCount);
+                }
+                else
+                {
+                    // Copy each vertex individually
+                    for (size_t vertexIdx = indexRange.start; vertexIdx <= indexRange.end; vertexIdx++)
+                    {
+                        memcpy(bufferPointer + curBufferOffset + (destStride * vertexIdx),
+                               inputPointer + (sourceStride * vertexIdx),
+                               destStride);
+                    }
+                }
+
+                // Compute where the 0-index vertex would be.
+                const size_t vertexStartOffset = curBufferOffset - (indexRange.start * destStride);
+
+                mFunctions->vertexAttribPointer(idx, mAttributes[idx].size, mAttributes[idx].type,
+                                                mAttributes[idx].normalized, destStride,
+                                                reinterpret_cast<const GLvoid*>(vertexStartOffset));
+
+                curBufferOffset += destStride * streamedVertexCount;
+
+                // Mark the applied attribute as dirty by setting an invalid size so that if it doesn't
+                // need to be streamed later, there is no chance that the caching will skip it.
+                mAppliedAttributes[idx].size = static_cast<GLuint>(-1);
+            }
+        }
+
+        unmapResult = mFunctions->unmapBuffer(GL_ARRAY_BUFFER);
+    }
+
+    if (unmapResult != GL_TRUE)
+    {
+        return gl::Error(GL_OUT_OF_MEMORY, "Failed to unmap the client data streaming buffer.");
+    }
+
+    return gl::Error(GL_NO_ERROR);
 }
 
 GLuint VertexArrayGL::getVertexArrayID() const