Vulkan: convert/align vertex data from buffers

When we get a buffer with vertex data in an unsupported format or
alignment, correct that as we copy it to a DynamicBuffer, then use
the copy.  Enable tests.

BUG=angleproject:2405

Change-Id: I2132abea4d936f6b53d9209be7f99a0e2d8de219
Reviewed-on: https://chromium-review.googlesource.com/1141277
Commit-Queue: Frank Henigman <fjhenigman@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
index 596e869..4b18414 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
@@ -25,8 +25,18 @@
 {
 constexpr size_t kDynamicVertexDataSize = 1024 * 1024;
 constexpr size_t kDynamicIndexDataSize  = 1024 * 8;
+
+bool BindingIsAligned(const gl::VertexBinding &binding, unsigned componentSize)
+{
+    return (binding.getOffset() % componentSize == 0) && (binding.getStride() % componentSize == 0);
+}
 }  // anonymous namespace
 
+#define INIT                                        \
+    {                                               \
+        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1024 * 8 \
+    }
+
 VertexArrayVk::VertexArrayVk(const gl::VertexArrayState &state, RendererVk *renderer)
     : VertexArrayImpl(state),
       mCurrentArrayBufferHandles{},
@@ -34,6 +44,11 @@
       mCurrentArrayBufferResources{},
       mCurrentArrayBufferFormats{},
       mCurrentArrayBufferStrides{},
+      mCurrentArrayBufferConversion{{
+          INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT, INIT,
+          INIT,
+      }},
+      mCurrentArrayBufferConversionCanRelease{},
       mCurrentElementArrayBufferHandle(VK_NULL_HANDLE),
       mCurrentElementArrayBufferOffset(0),
       mCurrentElementArrayBufferResource(nullptr),
@@ -53,6 +68,10 @@
     mPackedInputBindings.fill({0, 0});
     mPackedInputAttributes.fill({0, 0, 0});
 
+    for (vk::DynamicBuffer &buffer : mCurrentArrayBufferConversion)
+    {
+        buffer.init(1, renderer);
+    }
     mDynamicVertexData.init(1, renderer);
     mDynamicIndexData.init(1, renderer);
     mTranslatedByteIndexData.init(1, renderer);
@@ -65,6 +84,10 @@
 void VertexArrayVk::destroy(const gl::Context *context)
 {
     VkDevice device = vk::GetImpl(context)->getRenderer()->getDevice();
+    for (vk::DynamicBuffer &buffer : mCurrentArrayBufferConversion)
+    {
+        buffer.destroy(device);
+    }
     mDynamicVertexData.destroy(device);
     mDynamicIndexData.destroy(device);
     mTranslatedByteIndexData.destroy(device);
@@ -147,20 +170,83 @@
     return angle::Result::Continue();
 }
 
-#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                                     \
-    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                                             \
-        syncDirtyAttrib(contextVk, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], INDEX); \
-        invalidatePipeline = true;                                                                \
+// We assume the buffer is completely full of the same kind of data and convert
+// and/or align it as we copy it to a DynamicBuffer. The assumption could be wrong
+// but the alternative of copying it piecemeal on each draw would have a lot more
+// overhead.
+angle::Result VertexArrayVk::convertVertexBuffer(ContextVk *context,
+                                                 BufferVk *srcBuffer,
+                                                 const gl::VertexBinding &binding,
+                                                 size_t attribIndex)
+{
+
+    // Preparation for mapping source buffer.
+    ANGLE_TRY(context->getRenderer()->finish(context));
+
+    unsigned srcFormatSize = mCurrentArrayBufferFormats[attribIndex]->angleFormat().pixelBytes;
+    unsigned dstFormatSize = mCurrentArrayBufferFormats[attribIndex]->bufferFormat().pixelBytes;
+
+    // Bytes usable for vertex data.
+    GLint64 bytes = srcBuffer->getSize() - binding.getOffset();
+    if (bytes < srcFormatSize)
+        return angle::Result::Continue();
+
+    // Count the last vertex.  It may occupy less than a full stride.
+    size_t numVertices = 1;
+    bytes -= srcFormatSize;
+
+    // Count how many strides fit remaining space.
+    if (bytes > 0)
+        numVertices += static_cast<size_t>(bytes) / binding.getStride();
+
+    void *src       = nullptr;
+    uint8_t *dst    = nullptr;
+    uint32_t offset = 0;
+
+    ANGLE_TRY(mCurrentArrayBufferConversion[attribIndex].allocate(
+        context, numVertices * dstFormatSize, &dst, &mCurrentArrayBufferHandles[attribIndex],
+        &offset, nullptr));
+    ANGLE_TRY(srcBuffer->mapImpl(context, &src));
+    mCurrentArrayBufferFormats[attribIndex]->vertexLoadFunction(
+        static_cast<const uint8_t *>(src) + binding.getOffset(), binding.getStride(), numVertices,
+        dst);
+    ANGLE_TRY(srcBuffer->unmapImpl(context));
+    ANGLE_TRY(mCurrentArrayBufferConversion[attribIndex].flush(context));
+
+    mCurrentArrayBufferConversionCanRelease[attribIndex] = true;
+    mCurrentArrayBufferOffsets[attribIndex]              = offset;
+    mCurrentArrayBufferStrides[attribIndex]              = dstFormatSize;
+
+    return angle::Result::Continue();
+}
+
+void VertexArrayVk::ensureConversionReleased(RendererVk *renderer, size_t attribIndex)
+{
+    if (mCurrentArrayBufferConversionCanRelease[attribIndex])
+    {
+        mCurrentArrayBufferConversion[attribIndex].release(renderer);
+        mCurrentArrayBufferConversionCanRelease[attribIndex] = false;
+    }
+}
+
+#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                     \
+    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                             \
+        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
+                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
+        invalidatePipeline = true;                                                \
         break;
 
-#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                                                    \
-    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                                            \
-        syncDirtyAttrib(contextVk, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], INDEX); \
-        invalidatePipeline = true;                                                                \
+#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                                    \
+    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                            \
+        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
+                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
+        invalidatePipeline = true;                                                \
         break;
 
-#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)         \
-    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \
+#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)                                \
+    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX:                        \
+        ANGLE_TRY(syncDirtyAttrib(contextVk, attribs[INDEX],                      \
+                                  bindings[attribs[INDEX].bindingIndex], INDEX)); \
         break;
 
 gl::Error VertexArrayVk::syncState(const gl::Context *context,
@@ -172,7 +258,6 @@
 
     bool invalidatePipeline = false;
 
-    // Invalidate current pipeline.
     ContextVk *contextVk = vk::GetImpl(context);
 
     // Rebuild current attribute buffers cache. This will fail horribly if the buffer changes.
@@ -232,35 +317,44 @@
     return gl::NoError();
 }
 
-void VertexArrayVk::syncDirtyAttrib(ContextVk *contextVk,
-                                    const gl::VertexAttribute &attrib,
-                                    const gl::VertexBinding &binding,
-                                    size_t attribIndex)
+angle::Result VertexArrayVk::syncDirtyAttrib(ContextVk *contextVk,
+                                             const gl::VertexAttribute &attrib,
+                                             const gl::VertexBinding &binding,
+                                             size_t attribIndex)
 {
     // Invalidate the input description for pipelines.
     mDirtyPackedInputs.set(attribIndex);
 
     RendererVk *renderer = contextVk->getRenderer();
+    bool releaseConversion = true;
 
     if (attrib.enabled)
     {
         gl::Buffer *bufferGL = binding.getBuffer().get();
-
         mCurrentArrayBufferFormats[attribIndex] = &renderer->getFormat(GetVertexFormatID(attrib));
+
         if (bufferGL)
         {
-            if (mCurrentArrayBufferFormats[attribIndex]->vertexLoadRequiresConversion)
-            {
-                // TODO(fjhenigman): Make another buffer and put converted data into it.
-                // anglebug.com/2405
-                UNIMPLEMENTED();
-            }
+            BufferVk *bufferVk = vk::GetImpl(bufferGL);
+            unsigned componentSize =
+                mCurrentArrayBufferFormats[attribIndex]->angleFormat().pixelBytes / attrib.size;
 
-            BufferVk *bufferVk                        = vk::GetImpl(bufferGL);
-            mCurrentArrayBufferResources[attribIndex] = bufferVk;
-            mCurrentArrayBufferHandles[attribIndex]   = bufferVk->getVkBuffer().getHandle();
-            mCurrentArrayBufferOffsets[attribIndex]   = binding.getOffset();
-            mCurrentArrayBufferStrides[attribIndex]   = binding.getStride();
+            if (mCurrentArrayBufferFormats[attribIndex]->vertexLoadRequiresConversion ||
+                !BindingIsAligned(binding, componentSize))
+            {
+                ANGLE_TRY(convertVertexBuffer(contextVk, bufferVk, binding, attribIndex));
+
+                mCurrentArrayBufferConversion[attribIndex].releaseRetainedBuffers(renderer);
+                mCurrentArrayBufferResources[attribIndex] = nullptr;
+                releaseConversion                         = false;
+            }
+            else
+            {
+                mCurrentArrayBufferResources[attribIndex] = bufferVk;
+                mCurrentArrayBufferHandles[attribIndex]   = bufferVk->getVkBuffer().getHandle();
+                mCurrentArrayBufferOffsets[attribIndex]   = binding.getOffset();
+                mCurrentArrayBufferStrides[attribIndex]   = binding.getStride();
+            }
         }
         else
         {
@@ -268,8 +362,7 @@
             mCurrentArrayBufferHandles[attribIndex]   = VK_NULL_HANDLE;
             mCurrentArrayBufferOffsets[attribIndex]   = 0;
             mCurrentArrayBufferStrides[attribIndex] =
-                angle::Format::Get(mCurrentArrayBufferFormats[attribIndex]->bufferFormatID)
-                    .pixelBytes;
+                mCurrentArrayBufferFormats[attribIndex]->bufferFormat().pixelBytes;
         }
     }
     else
@@ -284,6 +377,11 @@
         mCurrentArrayBufferFormats[attribIndex] =
             &renderer->getFormat(angle::FormatID::R32G32B32A32_FLOAT);
     }
+
+    if (releaseConversion)
+        ensureConversionReleased(renderer, attribIndex);
+
+    return angle::Result::Continue();
 }
 
 void VertexArrayVk::updateArrayBufferReadDependencies(vk::CommandGraphResource *drawFramebuffer,