Implement a separate last row texture unpack buffer upload workaround

When uploading textures from an unpack buffer, some drivers expect an
extra row paading, causing them to think the pixel buffer is not big enough.
We work around this by uploading the last row separately.

BUG=angleproject:1512

Change-Id: I52fb8b35dc450b957f1fafb0b405c81bf0504157
Reviewed-on: https://chromium-review.googlesource.com/385193
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/libANGLE/formatutils.cpp b/src/libANGLE/formatutils.cpp
index e7c0e77..0d52428 100644
--- a/src/libANGLE/formatutils.cpp
+++ b/src/libANGLE/formatutils.cpp
@@ -831,6 +831,13 @@
     }
 }
 
+GLuint InternalFormat::computePixelBytes(GLenum formatType) const
+{
+    const auto &typeInfo = GetTypeInfo(formatType);
+    GLuint components    = typeInfo.specialInterpretation ? 1u : componentCount;
+    return components * typeInfo.bytes;
+}
+
 gl::ErrorOrResult<GLuint> InternalFormat::computeRowPitch(GLenum formatType,
                                                           GLsizei width,
                                                           GLint alignment,
@@ -843,11 +850,8 @@
         return computeCompressedImageSize(formatType, gl::Extents(width, 1, 1));
     }
 
-    const auto &typeInfo = GetTypeInfo(formatType);
-    CheckedNumeric<GLuint> checkedComponents(typeInfo.specialInterpretation ? 1u : componentCount);
-    CheckedNumeric<GLuint> checkedTypeBytes(typeInfo.bytes);
     CheckedNumeric<GLuint> checkedWidth(rowLength > 0 ? rowLength : width);
-    CheckedNumeric<GLuint> checkedRowBytes = checkedWidth * checkedComponents * checkedTypeBytes;
+    CheckedNumeric<GLuint> checkedRowBytes = checkedWidth * computePixelBytes(formatType);
 
     ASSERT(alignment > 0 && isPow2(alignment));
     CheckedNumeric<GLuint> checkedAlignment(alignment);
@@ -926,25 +930,53 @@
         return computeCompressedImageSize(formatType, size);
     }
 
-    base::CheckedNumeric<GLuint> checkedGroups(unpack.rowLength > 0 ? unpack.rowLength
-                                                                    : size.width);
-    base::CheckedNumeric<GLuint> checkedRows(unpack.imageHeight > 0 ? unpack.imageHeight
-                                                                    : size.height);
+    CheckedNumeric<GLuint> rowPitch;
+    CheckedNumeric<GLuint> depthPitch;
+    ANGLE_TRY_RESULT(computeRowPitch(formatType, size.width, unpack.alignment, unpack.rowLength),
+                     rowPitch);
+    ANGLE_TRY_RESULT(computeDepthPitch(formatType, size.width, size.height, unpack.alignment,
+                                       unpack.rowLength, unpack.imageHeight),
+                     depthPitch);
 
-    // Compute the groups of all the layers in (0,depth-1)
-    auto layerGroups = checkedGroups * checkedRows * (size.depth - 1);
+    CheckedNumeric<GLuint> depthMinusOne  = size.depth - 1;
+    CheckedNumeric<GLuint> heightMinusOne = size.height - 1;
+    CheckedNumeric<GLuint> pixelBytes     = computePixelBytes(formatType);
 
-    // Compute the groups in the last layer (for non-3D textures, the only one)
-    auto lastLayerGroups = checkedGroups * (size.height - 1) + size.width;
-
-    // The total size is the sum times the bytes per pixel.
-    auto totalSize = (layerGroups + lastLayerGroups) * pixelBytes;
+    CheckedNumeric<GLuint> totalSize = depthMinusOne * depthPitch;
+    totalSize += heightMinusOne * rowPitch;
+    totalSize += size.width * pixelBytes;
 
     ANGLE_TRY_CHECKED_MATH(totalSize);
 
     return totalSize.ValueOrDie();
 }
 
+gl::ErrorOrResult<GLuint> InternalFormat::computeUnpackEndByte(GLenum formatType,
+                                                               const gl::Extents &size,
+                                                               const gl::PixelUnpackState &unpack,
+                                                               bool applySkipImages) const
+{
+    GLuint rowPitch;
+    GLuint depthPitch;
+    CheckedNumeric<GLuint> checkedSkipBytes;
+    CheckedNumeric<GLuint> checkedCopyBytes;
+
+    ANGLE_TRY_RESULT(computeRowPitch(formatType, size.width, unpack.alignment, unpack.rowLength),
+                     rowPitch);
+    ANGLE_TRY_RESULT(computeDepthPitch(formatType, size.width, size.height, unpack.alignment,
+                                       unpack.rowLength, unpack.imageHeight),
+                     depthPitch);
+    ANGLE_TRY_RESULT(computeSkipBytes(rowPitch, depthPitch, unpack.skipImages, unpack.skipRows,
+                                      unpack.skipPixels, applySkipImages),
+                     checkedSkipBytes);
+    ANGLE_TRY_RESULT(computeUnpackSize(formatType, size, unpack), checkedCopyBytes);
+
+    CheckedNumeric<GLuint> endByte = checkedCopyBytes + checkedSkipBytes;
+
+    ANGLE_TRY_CHECKED_MATH(endByte);
+    return endByte.ValueOrDie();
+}
+
 GLenum GetSizedInternalFormat(GLenum internalFormat, GLenum type)
 {
     const InternalFormat &formatInfo = GetInternalFormatInfo(internalFormat);