GLES: Fix a bug in the impl of CopyBufferToTexture() with compressed formats

In OpenGL ES glPixelStorei() doesn't affect the execution of
glCompressedTexSubImage*D(), and GL_UNPACK_COMPRESSED_BLOCK_* is not
defined in OpenGL ES, so on the OpenGL ES backends, to implement
CopyBufferToTexture() with compressed texture formats, we can only copy
the compressed texture data once per compressed block row.

With this patch CompressedTextureBCFormatTest/* can pass on Intel Mesa
OpenGL ES driver.

BUG=dawn:42, dawn:580
TEST=dawn_end2end_tests

Change-Id: Ied84a187beaf9105d3664c4e874b3b7ddda4e4b0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/36020
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 7d1a274..0b07b87 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -546,28 +546,84 @@
                     gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, src.rowsPerImage * blockInfo.height);
 
                     if (formatInfo.isCompressed) {
-                        gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, blockInfo.byteSize);
-                        gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH, blockInfo.width);
-                        gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, blockInfo.height);
-                        gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1);
-
                         ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
-                        uint64_t copyDataSize = (copySize.width / blockInfo.width) *
-                                                (copySize.height / blockInfo.height) *
-                                                blockInfo.byteSize * copySize.depth;
+
                         Extent3D copyExtent = ComputeTextureCopyExtent(dst, copySize);
 
-                        if (texture->GetArrayLayers() > 1) {
-                            gl.CompressedTexSubImage3D(
-                                target, dst.mipLevel, dst.origin.x, dst.origin.y, dst.origin.z,
-                                copyExtent.width, copyExtent.height, copyExtent.depth,
-                                format.internalFormat, copyDataSize,
-                                reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                        // In GLES glPixelStorei() doesn't affect CompressedTexSubImage*D() and
+                        // GL_UNPACK_COMPRESSED_BLOCK_* isn't defined, so we have to workaround
+                        // this limitation by copying the compressed texture data once per row.
+                        // See OpenGL ES 3.2 SPEC Chapter 8.4.1, "Pixel Storage Modes and Pixel
+                        // Buffer Objects" for more details.
+                        if (gl.GetVersion().IsES()) {
+                            uint64_t copyDataSizePerBlockRow =
+                                (copySize.width / blockInfo.width) * blockInfo.byteSize;
+                            size_t copyBlockRowsPerImage = copySize.height / blockInfo.height;
+
+                            if (texture->GetArrayLayers() > 1) {
+                                // TODO(jiawei.shao@intel.com): do a single copy when the data is
+                                // correctly packed.
+                                for (size_t copyZ = 0; copyZ < copyExtent.depth; ++copyZ) {
+                                    uintptr_t offsetPerImage = static_cast<uintptr_t>(
+                                        src.offset + copyZ * src.bytesPerRow * src.rowsPerImage);
+                                    uint32_t dstOriginY = dst.origin.y;
+                                    uint32_t dstOriginZ = dst.origin.z + copyZ;
+
+                                    for (size_t copyBlockRow = 0;
+                                         copyBlockRow < copyBlockRowsPerImage; ++copyBlockRow) {
+                                        gl.CompressedTexSubImage3D(
+                                            target, dst.mipLevel, dst.origin.x, dstOriginY,
+                                            dstOriginZ, copyExtent.width, blockInfo.height, 1,
+                                            format.internalFormat, copyDataSizePerBlockRow,
+                                            reinterpret_cast<void*>(
+                                                static_cast<uintptr_t>(offsetPerImage)));
+
+                                        offsetPerImage += src.bytesPerRow;
+                                        dstOriginY += blockInfo.height;
+                                    }
+                                }
+                            } else {
+                                uintptr_t offset = static_cast<uintptr_t>(src.offset);
+                                uint32_t dstOriginY = dst.origin.y;
+
+                                // TODO(jiawei.shao@intel.com): do a single copy when the data is
+                                // correctly packed.
+                                for (size_t copyBlockRow = 0; copyBlockRow < copyBlockRowsPerImage;
+                                     ++copyBlockRow) {
+                                    gl.CompressedTexSubImage2D(
+                                        target, dst.mipLevel, dst.origin.x, dstOriginY,
+                                        copyExtent.width, blockInfo.height, format.internalFormat,
+                                        copyDataSizePerBlockRow,
+                                        reinterpret_cast<void*>(static_cast<uintptr_t>(offset)));
+
+                                    offset += src.bytesPerRow;
+                                    dstOriginY += blockInfo.height;
+                                }
+                            }
+
                         } else {
-                            gl.CompressedTexSubImage2D(
-                                target, dst.mipLevel, dst.origin.x, dst.origin.y, copyExtent.width,
-                                copyExtent.height, format.internalFormat, copyDataSize,
-                                reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                            gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_SIZE, blockInfo.byteSize);
+                            gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_WIDTH, blockInfo.width);
+                            gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, blockInfo.height);
+                            gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1);
+
+                            uint64_t copyDataSize = (copySize.width / blockInfo.width) *
+                                                    (copySize.height / blockInfo.height) *
+                                                    blockInfo.byteSize * copySize.depth;
+
+                            if (texture->GetArrayLayers() > 1) {
+                                gl.CompressedTexSubImage3D(
+                                    target, dst.mipLevel, dst.origin.x, dst.origin.y, dst.origin.z,
+                                    copyExtent.width, copyExtent.height, copyExtent.depth,
+                                    format.internalFormat, copyDataSize,
+                                    reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                            } else {
+                                gl.CompressedTexSubImage2D(
+                                    target, dst.mipLevel, dst.origin.x, dst.origin.y,
+                                    copyExtent.width, copyExtent.height, format.internalFormat,
+                                    copyDataSize,
+                                    reinterpret_cast<void*>(static_cast<uintptr_t>(src.offset)));
+                            }
                         }
                     } else {
                         switch (texture->GetDimension()) {