Vulkan: DrawElements with line loops client side memory support

- Also enables 6 new tests in LineLoopTests.cpp in angle_end2end

Bug: angleproject:2458

Change-Id: I4aec12b0ac780e81e6811f1199a5acaf17d9b982
Reviewed-on: https://chromium-review.googlesource.com/1010411
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Luc Ferron <lucferron@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
index d13e6aa..5b34f65 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
@@ -405,22 +405,24 @@
     }
 
     // Handle GL_LINE_LOOP drawElements.
-    gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
-    if (!elementArrayBuffer)
-    {
-        UNIMPLEMENTED();
-        return gl::InternalError() << "Line loop indices in client memory not supported";
-    }
-
-    BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
-
-    VkIndexType indexType = gl_vk::GetIndexType(drawCallParams.type());
-
     if (mDirtyLineLoopTranslation)
     {
-        ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
-            renderer, elementArrayBufferVk, indexType, drawCallParams.indexCount(),
-            &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
+        gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer().get();
+        VkIndexType indexType          = gl_vk::GetIndexType(drawCallParams.type());
+
+        if (!elementArrayBuffer)
+        {
+            ANGLE_TRY(mLineLoopHelper.getIndexBufferForClientElementArray(
+                renderer, drawCallParams.indices(), indexType, drawCallParams.indexCount(),
+                &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
+        }
+        else
+        {
+            BufferVk *elementArrayBufferVk = vk::GetImpl(elementArrayBuffer);
+            ANGLE_TRY(mLineLoopHelper.getIndexBufferForElementArrayBuffer(
+                renderer, elementArrayBufferVk, indexType, drawCallParams.indexCount(),
+                &mCurrentElementArrayBufferHandle, &mCurrentElementArrayBufferOffset));
+        }
     }
 
     ANGLE_TRY(onIndexedDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
@@ -485,7 +487,7 @@
 {
     ANGLE_TRY(onDraw(context, renderer, drawCallParams, drawNode, newCommandBuffer));
 
-    if (!mState.getElementArrayBuffer().get())
+    if (!mState.getElementArrayBuffer().get() && drawCallParams.mode() != GL_LINE_LOOP)
     {
         ANGLE_TRY(drawCallParams.ensureIndexRangeResolved(context));
         ANGLE_TRY(streamIndexData(renderer, drawCallParams));
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index f861153..9306f9e 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -377,6 +377,34 @@
     return gl::NoError();
 }
 
+gl::Error LineLoopHelper::getIndexBufferForClientElementArray(RendererVk *renderer,
+                                                              const void *indicesInput,
+                                                              VkIndexType indexType,
+                                                              int indexCount,
+                                                              VkBuffer *bufferHandleOut,
+                                                              VkDeviceSize *bufferOffsetOut)
+{
+    // TODO(lucferron): we'll eventually need to support uint8, emulated on 16 since Vulkan only
+    // supports 16 / 32.
+    ASSERT(indexType == VK_INDEX_TYPE_UINT16 || indexType == VK_INDEX_TYPE_UINT32);
+
+    uint8_t *indices = nullptr;
+    uint32_t offset  = 0;
+
+    auto unitSize = (indexType == VK_INDEX_TYPE_UINT16 ? sizeof(uint16_t) : sizeof(uint32_t));
+    size_t allocateBytes = unitSize * (indexCount + 1);
+    ANGLE_TRY(mDynamicIndexBuffer.allocate(renderer, allocateBytes,
+                                           reinterpret_cast<uint8_t **>(&indices), bufferHandleOut,
+                                           &offset, nullptr));
+    *bufferOffsetOut = static_cast<VkDeviceSize>(offset);
+
+    memcpy(indices, indicesInput, unitSize * indexCount);
+    memcpy(indices + unitSize * indexCount, indicesInput, unitSize);
+
+    ANGLE_TRY(mDynamicIndexBuffer.flush(renderer->getDevice()));
+    return gl::NoError();
+}
+
 void LineLoopHelper::destroy(VkDevice device)
 {
     mDynamicIndexBuffer.destroy(device);
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 237c85c..c9ca56b 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -128,6 +128,13 @@
                                                   int indexCount,
                                                   VkBuffer *bufferHandleOut,
                                                   VkDeviceSize *bufferOffsetOut);
+    gl::Error getIndexBufferForClientElementArray(RendererVk *renderer,
+                                                  const void *indicesInput,
+                                                  VkIndexType indexType,
+                                                  int indexCount,
+                                                  VkBuffer *bufferHandleOut,
+                                                  VkDeviceSize *bufferOffsetOut);
+
     void destroy(VkDevice device);
 
     static void Draw(int count, CommandBuffer *commandBuffer);
diff --git a/src/tests/gl_tests/LineLoopTest.cpp b/src/tests/gl_tests/LineLoopTest.cpp
index 4566a37..3f0f999 100644
--- a/src/tests/gl_tests/LineLoopTest.cpp
+++ b/src/tests/gl_tests/LineLoopTest.cpp
@@ -112,6 +112,9 @@
 
 TEST_P(LineLoopTest, LineLoopUByteIndices)
 {
+    // TODO(fjhenigman): UByte not yet supported in VertexArrayVk
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details
     // On Win7, the D3D SDK Layers emits a false warning for these tests.
     // This doesn't occur on Windows 10 (Version 1511) though.
@@ -146,6 +149,9 @@
 
 TEST_P(LineLoopTest, LineLoopUByteIndexBuffer)
 {
+    // TODO(fjhenigman): UByte not yet supported in VertexArrayVk
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details
     ignoreD3D11SDKLayersWarnings();
 
@@ -163,6 +169,11 @@
 
 TEST_P(LineLoopTest, LineLoopUShortIndexBuffer)
 {
+    // TODO(lucferron): Looks like we have a bug supporting ushort as index buffers
+    // on line loops drawing.
+    // http://anglebug.com/2473
+    ANGLE_SKIP_TEST_IF(IsVulkan());
+
     // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details
     ignoreD3D11SDKLayersWarnings();
 
@@ -202,4 +213,9 @@
 
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
-ANGLE_INSTANTIATE_TEST(LineLoopTest, ES2_D3D9(), ES2_D3D11(), ES2_OPENGL(), ES2_OPENGLES());
+ANGLE_INSTANTIATE_TEST(LineLoopTest,
+                       ES2_D3D9(),
+                       ES2_D3D11(),
+                       ES2_OPENGL(),
+                       ES2_OPENGLES(),
+                       ES2_VULKAN());
diff --git a/src/tests/gl_tests/SimpleOperationTest.cpp b/src/tests/gl_tests/SimpleOperationTest.cpp
index f51c439..6d5cfc3 100644
--- a/src/tests/gl_tests/SimpleOperationTest.cpp
+++ b/src/tests/gl_tests/SimpleOperationTest.cpp
@@ -747,6 +747,52 @@
     EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
 }
 
+// Draw a line loop using a drawElement call and client side memory.
+TEST_P(SimpleOperationTest, DrawElementsLineLoopUsingClientSideMemory)
+{
+    ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
+    glUseProgram(program);
+
+    // We expect to draw a square with these 4 vertices with a drawArray call.
+    std::vector<Vector3> vertices;
+    CreatePixelCenterWindowCoords({{32, 96}, {32, 32}, {96, 32}, {96, 96}}, getWindowWidth(),
+                                  getWindowHeight(), &vertices);
+
+    // If we use these indices to draw however, we should be drawing an hourglass.
+    std::vector<GLushort> indices{3, 2, 1, 0};
+
+    GLint positionLocation = glGetAttribLocation(program, "position");
+    ASSERT_NE(-1, positionLocation);
+
+    GLBuffer vertexBuffer;
+    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
+                 GL_STATIC_DRAW);
+
+    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glEnableVertexAttribArray(positionLocation);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glDrawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, indices.data());
+    glDisableVertexAttribArray(positionLocation);
+
+    ASSERT_GL_NO_ERROR();
+
+    int quarterWidth  = getWindowWidth() / 4;
+    int quarterHeight = getWindowHeight() / 4;
+
+    // Bottom left
+    EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight, GLColor::green);
+
+    // Top left
+    EXPECT_PIXEL_COLOR_EQ(quarterWidth, (quarterHeight * 3), GLColor::green);
+
+    // Top right
+    EXPECT_PIXEL_COLOR_EQ((quarterWidth * 3), (quarterHeight * 3) - 1, GLColor::green);
+
+    // Verify line is closed between the 2 last vertices
+    EXPECT_PIXEL_COLOR_EQ((quarterWidth * 2), quarterHeight, GLColor::green);
+}
+
 // Tests rendering to a user framebuffer.
 TEST_P(SimpleOperationTest, RenderToTexture)
 {
diff --git a/src/tests/gl_tests/StateChangeTest.cpp b/src/tests/gl_tests/StateChangeTest.cpp
index cf861d6..3136378 100644
--- a/src/tests/gl_tests/StateChangeTest.cpp
+++ b/src/tests/gl_tests/StateChangeTest.cpp
@@ -1008,15 +1008,9 @@
     glUseProgram(program);
 
     // We expect to draw a square with these 4 vertices with a drawArray call.
-    auto pixelPoints =
-        std::vector<Vector2>{{8.5f, 8.5f}, {8.5f, 24.5f}, {24.5f, 8.5f}, {24.5f, 24.5f}};
-
-    auto vertices = std::vector<Vector3>();
-    for (Vector2 pixelPoint : pixelPoints)
-    {
-        vertices.emplace_back(Vector3(pixelPoint[0] * 2 / getWindowWidth() - 1,
-                                      pixelPoint[1] * 2 / getWindowHeight() - 1, 0.0f));
-    }
+    std::vector<Vector3> vertices;
+    CreatePixelCenterWindowCoords({{8, 8}, {8, 24}, {24, 8}, {24, 24}}, getWindowWidth(),
+                                  getWindowHeight(), &vertices);
 
     // If we use these indices to draw however, we should be drawing an hourglass.
     auto indices = std::vector<GLushort>{0, 2, 1, 3};
@@ -1051,15 +1045,9 @@
     glUseProgram(program);
 
     // We expect to draw a square with these 4 vertices with a drawArray call.
-    auto pixelPoints =
-        std::vector<Vector2>{{8.5f, 8.5f}, {8.5f, 24.5f}, {24.5f, 8.5f}, {24.5f, 24.5f}};
-
-    auto vertices = std::vector<Vector3>();
-    for (Vector2 pixelPoint : pixelPoints)
-    {
-        vertices.emplace_back(Vector3(pixelPoint[0] * 2 / getWindowWidth() - 1,
-                                      pixelPoint[1] * 2 / getWindowHeight() - 1, 0.0f));
-    }
+    std::vector<Vector3> vertices;
+    CreatePixelCenterWindowCoords({{8, 8}, {8, 24}, {24, 8}, {24, 24}}, getWindowWidth(),
+                                  getWindowHeight(), &vertices);
 
     // If we use these indices to draw however, we should be drawing an hourglass.
     auto indices = std::vector<GLushort>{0, 2, 1, 3};
diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp
index 3e87c4b..e82fb5f 100644
--- a/src/tests/test_utils/ANGLETest.cpp
+++ b/src/tests/test_utils/ANGLETest.cpp
@@ -139,6 +139,19 @@
     return result;
 }
 
+void CreatePixelCenterWindowCoords(const std::vector<Vector2> &pixelPoints,
+                                   int windowWidth,
+                                   int windowHeight,
+                                   std::vector<Vector3> *outVertices)
+{
+    for (Vector2 pixelPoint : pixelPoints)
+    {
+        outVertices->emplace_back(Vector3((pixelPoint[0] + 0.5f) * 2.0f / windowWidth - 1.0f,
+                                          (pixelPoint[1] + 0.5f) * 2.0f / windowHeight - 1.0f,
+                                          0.0f));
+    }
+}
+
 angle::Vector4 GLColor::toNormalizedVector() const
 {
     return angle::Vector4(ColorNorm(R), ColorNorm(G), ColorNorm(B), ColorNorm(A));
diff --git a/src/tests/test_utils/ANGLETest.h b/src/tests/test_utils/ANGLETest.h
index 82b85fc..7b15dab 100644
--- a/src/tests/test_utils/ANGLETest.h
+++ b/src/tests/test_utils/ANGLETest.h
@@ -111,6 +111,14 @@
 
 struct WorkaroundsD3D;
 
+// The input here for pixelPoints are the expected integer window coordinates, we add .5 to every
+// one of them and re-scale the numbers to be between [-1,1]. Using this technique, we can make
+// sure the rasterization stage will end up drawing pixels at the expected locations.
+void CreatePixelCenterWindowCoords(const std::vector<Vector2> &pixelPoints,
+                                   int windowWidth,
+                                   int windowHeight,
+                                   std::vector<Vector3> *outVertices);
+
 // Useful to cast any type to GLubyte.
 template <typename TR, typename TG, typename TB, typename TA>
 GLColor MakeGLColor(TR r, TG g, TB b, TA a)