OpenGL: Refactor texture uploads.

Move texture uploads into CommandBufferGL::DoTexSubImage() and use it
for both CommandBuffer CopyBufferToTexture and
QueueGL::WriteTextureImpl(). On the CB side, For now this is only used
for compressed ES textures. Desktop GL has a fast-path for compressed
textures that isn't currently implemented.

Bug: dawn:684

Change-Id: I4da02e9c96c13fd71d133778168a5597efa7b59a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/47123
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Stephen White <senorblanco@chromium.org>
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 6573d12..aed04f1 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -628,6 +628,11 @@
                     }
 
                     gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
+
+                    TextureDataLayout dataLayout;
+                    dataLayout.offset = 0;
+                    dataLayout.bytesPerRow = src.bytesPerRow;
+                    dataLayout.rowsPerImage = src.rowsPerImage;
                     gl.ActiveTexture(GL_TEXTURE0);
                     gl.BindTexture(target, texture->GetHandle());
 
@@ -645,52 +650,8 @@
                         // 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.depthOrArrayLayers;
-                                     ++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;
-                                }
-                            }
-
+                            DoTexSubImage(gl, dst, reinterpret_cast<void*>(src.offset), dataLayout,
+                                          copySize);
                         } else {
                             gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
                                            src.bytesPerRow / blockInfo.byteSize * blockInfo.width);
@@ -1397,4 +1358,104 @@
         UNREACHABLE();
     }
 
+    void DoTexSubImage(const OpenGLFunctions& gl,
+                       const TextureCopy& destination,
+                       const void* data,
+                       const TextureDataLayout& dataLayout,
+                       const Extent3D& writeSizePixel) {
+        Texture* texture = ToBackend(destination.texture.Get());
+        SubresourceRange range(Aspect::Color,
+                               {destination.origin.z, writeSizePixel.depthOrArrayLayers},
+                               {destination.mipLevel, 1});
+        if (IsCompleteSubresourceCopiedTo(texture, writeSizePixel, destination.mipLevel)) {
+            texture->SetIsSubresourceContentInitialized(true, range);
+        } else {
+            texture->EnsureSubresourceContentInitialized(range);
+        }
+
+        const GLFormat& format = texture->GetGLFormat();
+        GLenum target = texture->GetGLTarget();
+        data = static_cast<const uint8_t*>(data) + dataLayout.offset;
+        gl.BindTexture(target, texture->GetHandle());
+        const TexelBlockInfo& blockInfo =
+            texture->GetFormat().GetAspectInfo(destination.aspect).block;
+
+        if (texture->GetFormat().isCompressed) {
+            size_t imageSize = writeSizePixel.width / blockInfo.width * blockInfo.byteSize;
+            Extent3D virtSize = texture->GetMipLevelVirtualSize(destination.mipLevel);
+            uint32_t width = std::min(writeSizePixel.width, virtSize.width - destination.origin.x);
+            uint32_t x = destination.origin.x;
+
+            // For now, we use row-by-row texture uploads of compressed textures in all cases.
+            // TODO(crbug.com/dawn/684): For contiguous cases, we should be able to use a single
+            // texture upload per layer, as we do in the non-compressed case.
+            if (texture->GetArrayLayers() == 1) {
+                const uint8_t* d = static_cast<const uint8_t*>(data);
+
+                for (uint32_t y = destination.origin.y;
+                     y < destination.origin.y + writeSizePixel.height; y += blockInfo.height) {
+                    uint32_t height = std::min(blockInfo.height, virtSize.height - y);
+                    gl.CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
+                                               format.internalFormat, imageSize, d);
+                    d += dataLayout.bytesPerRow;
+                }
+            } else {
+                const uint8_t* slice = static_cast<const uint8_t*>(data);
+
+                for (uint32_t z = destination.origin.z;
+                     z < destination.origin.z + writeSizePixel.depthOrArrayLayers; ++z) {
+                    const uint8_t* d = slice;
+
+                    for (uint32_t y = destination.origin.y;
+                         y < destination.origin.y + writeSizePixel.height; y += blockInfo.height) {
+                        uint32_t height = std::min(blockInfo.height, virtSize.height - y);
+                        gl.CompressedTexSubImage3D(target, destination.mipLevel, x, y, z, width,
+                                                   height, 1, format.internalFormat, imageSize, d);
+                        d += dataLayout.bytesPerRow;
+                    }
+
+                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                }
+            }
+        } else if (dataLayout.bytesPerRow % blockInfo.byteSize == 0) {
+            gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
+                           dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width);
+            if (texture->GetArrayLayers() == 1) {
+                gl.TexSubImage2D(target, destination.mipLevel, destination.origin.x,
+                                 destination.origin.y, writeSizePixel.width, writeSizePixel.height,
+                                 format.format, format.type, data);
+            } else {
+                gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, dataLayout.rowsPerImage * blockInfo.height);
+                gl.TexSubImage3D(target, destination.mipLevel, destination.origin.x,
+                                 destination.origin.y, destination.origin.z, writeSizePixel.width,
+                                 writeSizePixel.height, writeSizePixel.depthOrArrayLayers,
+                                 format.format, format.type, data);
+                gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
+            }
+            gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        } else {
+            if (texture->GetArrayLayers() == 1) {
+                const uint8_t* d = static_cast<const uint8_t*>(data);
+                for (uint32_t y = 0; y < writeSizePixel.height; ++y) {
+                    gl.TexSubImage2D(target, destination.mipLevel, destination.origin.x,
+                                     destination.origin.y + y, writeSizePixel.width, 1,
+                                     format.format, format.type, d);
+                    d += dataLayout.bytesPerRow;
+                }
+            } else {
+                const uint8_t* slice = static_cast<const uint8_t*>(data);
+                for (uint32_t z = 0; z < writeSizePixel.depthOrArrayLayers; ++z) {
+                    const uint8_t* d = slice;
+                    for (uint32_t y = 0; y < writeSizePixel.height; ++y) {
+                        gl.TexSubImage3D(target, destination.mipLevel, destination.origin.x,
+                                         destination.origin.y + y, destination.origin.z + z,
+                                         writeSizePixel.width, 1, 1, format.format, format.type, d);
+                        d += dataLayout.bytesPerRow;
+                    }
+                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                }
+            }
+        }
+    }
+
 }}  // namespace dawn_native::opengl
diff --git a/src/dawn_native/opengl/CommandBufferGL.h b/src/dawn_native/opengl/CommandBufferGL.h
index c21f574..ae53e6a 100644
--- a/src/dawn_native/opengl/CommandBufferGL.h
+++ b/src/dawn_native/opengl/CommandBufferGL.h
@@ -24,6 +24,7 @@
 namespace dawn_native { namespace opengl {
 
     class Device;
+    struct OpenGLFunctions;
 
     class CommandBuffer final : public CommandBufferBase {
       public:
@@ -36,6 +37,13 @@
         MaybeError ExecuteRenderPass(BeginRenderPassCmd* renderPass);
     };
 
+    // Like glTexSubImage*, the "data" argument is either a pointer to image data or
+    // an offset if a PBO is bound.
+    void DoTexSubImage(const OpenGLFunctions& gl,
+                       const TextureCopy& destination,
+                       const void* data,
+                       const TextureDataLayout& dataLayout,
+                       const Extent3D& writeSizePixel);
 }}  // namespace dawn_native::opengl
 
 #endif  // DAWNNATIVE_OPENGL_COMMANDBUFFERGL_H_
diff --git a/src/dawn_native/opengl/QueueGL.cpp b/src/dawn_native/opengl/QueueGL.cpp
index 17260b9..167f8c4 100644
--- a/src/dawn_native/opengl/QueueGL.cpp
+++ b/src/dawn_native/opengl/QueueGL.cpp
@@ -56,101 +56,13 @@
                                        const void* data,
                                        const TextureDataLayout& dataLayout,
                                        const Extent3D& writeSizePixel) {
-        const OpenGLFunctions& gl = ToBackend(GetDevice())->gl;
-
-        Texture* texture = ToBackend(destination.texture);
-        SubresourceRange range(Aspect::Color,
-                               {destination.origin.z, writeSizePixel.depthOrArrayLayers},
-                               {destination.mipLevel, 1});
-        if (IsCompleteSubresourceCopiedTo(texture, writeSizePixel, destination.mipLevel)) {
-            texture->SetIsSubresourceContentInitialized(true, range);
-        } else {
-            texture->EnsureSubresourceContentInitialized(range);
-        }
-
-        const GLFormat& format = texture->GetGLFormat();
-        GLenum target = texture->GetGLTarget();
-        data = static_cast<const uint8_t*>(data) + dataLayout.offset;
-        gl.BindTexture(target, texture->GetHandle());
-        const TexelBlockInfo& blockInfo =
-            texture->GetFormat().GetAspectInfo(destination.aspect).block;
-
-        if (texture->GetFormat().isCompressed) {
-            size_t imageSize = writeSizePixel.width / blockInfo.width * blockInfo.byteSize;
-            Extent3D virtSize = texture->GetMipLevelVirtualSize(destination.mipLevel);
-            uint32_t width = std::min(writeSizePixel.width, virtSize.width - destination.origin.x);
-            uint32_t x = destination.origin.x;
-
-            // For now, we use row-by-row texture uploads of compressed textures in all cases.
-            // TODO(crbug.com/dawn/684): For contiguous cases, we should be able to use a single
-            // texture upload per layer, as we do in the non-compressed case.
-            if (texture->GetArrayLayers() == 1) {
-                const uint8_t* d = static_cast<const uint8_t*>(data);
-
-                for (uint32_t y = destination.origin.y;
-                     y < destination.origin.y + writeSizePixel.height; y += blockInfo.height) {
-                    uint32_t height = std::min(blockInfo.height, virtSize.height - y);
-                    gl.CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
-                                               format.internalFormat, imageSize, d);
-                    d += dataLayout.bytesPerRow;
-                }
-            } else {
-                const uint8_t* slice = static_cast<const uint8_t*>(data);
-
-                for (uint32_t z = destination.origin.z;
-                     z < destination.origin.z + writeSizePixel.depthOrArrayLayers; ++z) {
-                    const uint8_t* d = slice;
-
-                    for (uint32_t y = destination.origin.y;
-                         y < destination.origin.y + writeSizePixel.height; y += blockInfo.height) {
-                        uint32_t height = std::min(blockInfo.height, virtSize.height - y);
-                        gl.CompressedTexSubImage3D(target, destination.mipLevel, x, y, z, width,
-                                                   height, 1, format.internalFormat, imageSize, d);
-                        d += dataLayout.bytesPerRow;
-                    }
-
-                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
-                }
-            }
-        } else if (dataLayout.bytesPerRow % blockInfo.byteSize == 0) {
-            gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
-                           dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width);
-            if (texture->GetArrayLayers() == 1) {
-                gl.TexSubImage2D(target, destination.mipLevel, destination.origin.x,
-                                 destination.origin.y, writeSizePixel.width, writeSizePixel.height,
-                                 format.format, format.type, data);
-            } else {
-                gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, dataLayout.rowsPerImage * blockInfo.height);
-                gl.TexSubImage3D(target, destination.mipLevel, destination.origin.x,
-                                 destination.origin.y, destination.origin.z, writeSizePixel.width,
-                                 writeSizePixel.height, writeSizePixel.depthOrArrayLayers,
-                                 format.format, format.type, data);
-                gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
-            }
-            gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-        } else {
-            if (texture->GetArrayLayers() == 1) {
-                const uint8_t* d = static_cast<const uint8_t*>(data);
-                for (uint32_t y = 0; y < writeSizePixel.height; ++y) {
-                    gl.TexSubImage2D(target, destination.mipLevel, destination.origin.x,
-                                     destination.origin.y + y, writeSizePixel.width, 1,
-                                     format.format, format.type, d);
-                    d += dataLayout.bytesPerRow;
-                }
-            } else {
-                const uint8_t* slice = static_cast<const uint8_t*>(data);
-                for (uint32_t z = 0; z < writeSizePixel.depthOrArrayLayers; ++z) {
-                    const uint8_t* d = slice;
-                    for (uint32_t y = 0; y < writeSizePixel.height; ++y) {
-                        gl.TexSubImage3D(target, destination.mipLevel, destination.origin.x,
-                                         destination.origin.y + y, destination.origin.z + z,
-                                         writeSizePixel.width, 1, 1, format.format, format.type, d);
-                        d += dataLayout.bytesPerRow;
-                    }
-                    slice += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
-                }
-            }
-        }
+        TextureCopy textureCopy;
+        textureCopy.texture = destination.texture;
+        textureCopy.mipLevel = destination.mipLevel;
+        textureCopy.origin = destination.origin;
+        textureCopy.aspect =
+            SelectFormatAspects(destination.texture->GetFormat(), destination.aspect);
+        DoTexSubImage(ToBackend(GetDevice())->gl, textureCopy, data, dataLayout, writeSizePixel);
         return {};
     }