[Compat] Fix Cube/CubeArray texture and view binding

Remove copy path for Cube/CubeArray textures. Add missing
textureViewDimension support and various GL calls for
GL_TEXTURE_CUBE_MAP(_ARRAY). Add related tests.

Also fixes blit texture to buffer workaround for stencil
cube texture where textureSampleLevel doesn't support
texture_cube<u32>, use textureGather instead.

This fix the Cube textureDimension CTS failure along
the way.

Bug: dawn:2131, dawn:2442, dawn:2182
Change-Id: I87229d7a0f43f9496738eeee69a73c2a8c8ab81b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/184840
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Commit-Queue: Shrek Shao <shrekshao@google.com>
diff --git a/src/dawn/native/BlitTextureToBuffer.cpp b/src/dawn/native/BlitTextureToBuffer.cpp
index 9a3e3e0..3af39de 100644
--- a/src/dawn/native/BlitTextureToBuffer.cpp
+++ b/src/dawn/native/BlitTextureToBuffer.cpp
@@ -133,11 +133,15 @@
 @group(0) @binding(0) var src_tex : texture_2d_array<u32>;
 )";
 
+// textureSampleLevel doesn't support texture_cube<u32>
+// Use textureGather as a workaround.
+// Always choose the texel with the smallest coord (stored in w component).
+// Since this is only used for Stencil8 (1 channel), we only care component idx == 0.
 constexpr std::string_view kUintTextureCube = R"(
 @group(1) @binding(0) var default_sampler: sampler;
 fn textureLoadGeneral(tex: texture_cube<u32>, coords: vec3u, level: u32) -> vec4<u32> {
     let sample_coords = coordToCubeSampleST(coords, params.levelSize);
-    return textureSampleLevel(tex, default_sampler, sample_coords, f32(level));
+    return vec4<u32>(textureGather(0, tex, default_sampler, sample_coords).w);
 }
 @group(0) @binding(0) var src_tex : texture_cube<u32>;
 )";
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 057d36d..a004327 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -69,10 +69,6 @@
     DAWN_UNREACHABLE();
 }
 
-bool Is1DOr2D(wgpu::TextureDimension dimension) {
-    return dimension == wgpu::TextureDimension::e1D || dimension == wgpu::TextureDimension::e2D;
-}
-
 GLenum VertexFormatType(wgpu::VertexFormat format) {
     switch (format) {
         case wgpu::VertexFormat::Uint8x2:
@@ -738,7 +734,7 @@
                                           copySize.height, glFormat, glType, offset);
                             break;
                         }
-                        // Implementation for 2D array is the same as 3D.
+                        // Implementation for 2D array and cube map is the same as 3D.
                         [[fallthrough]];
                     }
 
@@ -1441,7 +1437,7 @@
             gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, blockInfo.height);
             gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 1);
 
-            if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
+            if (target == GL_TEXTURE_2D) {
                 gl.CompressedTexSubImage2D(target, destination.mipLevel, x, y, width, height,
                                            format.internalFormat, imageSize, data);
             } else {
@@ -1458,7 +1454,7 @@
             gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_HEIGHT, 0);
             gl.PixelStorei(GL_UNPACK_COMPRESSED_BLOCK_DEPTH, 0);
         } else {
-            if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
+            if (target == GL_TEXTURE_2D) {
                 const uint8_t* d = static_cast<const uint8_t*>(data);
 
                 for (; y < destination.origin.y + copySize.height; y += blockInfo.height) {
@@ -1499,10 +1495,22 @@
             gl.PixelStorei(GL_UNPACK_ALIGNMENT, std::min(8u, blockInfo.byteSize));
             gl.PixelStorei(GL_UNPACK_ROW_LENGTH,
                            dataLayout.bytesPerRow / blockInfo.byteSize * blockInfo.width);
-            if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
+            if (target == GL_TEXTURE_2D) {
                 gl.TexSubImage2D(target, destination.mipLevel, x, y, width, height, adjustedFormat,
                                  format.type, data);
+            } else if (target == GL_TEXTURE_CUBE_MAP) {
+                DAWN_ASSERT(texture->GetArrayLayers() == 6);
+                const uint8_t* pointer = static_cast<const uint8_t*>(data);
+                uint32_t baseLayer = destination.origin.z;
+                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
+                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
+                    gl.TexSubImage2D(cubeMapTarget, destination.mipLevel, x, y, width, height,
+                                     adjustedFormat, format.type, pointer);
+                    pointer += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                }
             } else {
+                DAWN_ASSERT(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY ||
+                            target == GL_TEXTURE_CUBE_MAP_ARRAY);
                 gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, dataLayout.rowsPerImage * blockInfo.height);
                 gl.TexSubImage3D(target, destination.mipLevel, x, y, z, width, height,
                                  copySize.depthOrArrayLayers, adjustedFormat, format.type, data);
@@ -1511,14 +1519,30 @@
             gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
             gl.PixelStorei(GL_UNPACK_ALIGNMENT, 4);  // Reset to default
         } else {
-            if (texture->GetArrayLayers() == 1 && Is1DOr2D(texture->GetDimension())) {
+            if (target == GL_TEXTURE_2D) {
                 const uint8_t* d = static_cast<const uint8_t*>(data);
                 for (; y < destination.origin.y + height; ++y) {
                     gl.TexSubImage2D(target, destination.mipLevel, x, y, width, 1, adjustedFormat,
                                      format.type, d);
                     d += dataLayout.bytesPerRow;
                 }
+            } else if (target == GL_TEXTURE_CUBE_MAP) {
+                DAWN_ASSERT(texture->GetArrayLayers() == 6);
+                const uint8_t* pointer = static_cast<const uint8_t*>(data);
+                uint32_t baseLayer = destination.origin.z;
+                for (uint32_t l = 0; l < copySize.depthOrArrayLayers; ++l) {
+                    const uint8_t* d = pointer;
+                    GLenum cubeMapTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + baseLayer + l;
+                    for (y = destination.origin.y; y < destination.origin.y + height; ++y) {
+                        gl.TexSubImage2D(cubeMapTarget, destination.mipLevel, x, y, width, 1,
+                                         adjustedFormat, format.type, d);
+                        d += dataLayout.bytesPerRow;
+                    }
+                    pointer += dataLayout.rowsPerImage * dataLayout.bytesPerRow;
+                }
             } else {
+                DAWN_ASSERT(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY ||
+                            target == GL_TEXTURE_CUBE_MAP_ARRAY);
                 const uint8_t* slice = static_cast<const uint8_t*>(data);
                 for (; z < destination.origin.z + copySize.depthOrArrayLayers; ++z) {
                     const uint8_t* d = slice;
diff --git a/src/dawn/native/opengl/TextureGL.cpp b/src/dawn/native/opengl/TextureGL.cpp
index fd53496..2f5518a 100644
--- a/src/dawn/native/opengl/TextureGL.cpp
+++ b/src/dawn/native/opengl/TextureGL.cpp
@@ -44,58 +44,31 @@
 
 namespace {
 
-GLenum TargetForTexture(const UnpackedPtr<TextureDescriptor>& descriptor) {
-    switch (descriptor->dimension) {
-        case wgpu::TextureDimension::Undefined:
-            DAWN_UNREACHABLE();
-        case wgpu::TextureDimension::e1D:
-        case wgpu::TextureDimension::e2D:
-            if (descriptor->size.depthOrArrayLayers > 1) {
-                DAWN_ASSERT(descriptor->sampleCount == 1);
-                return GL_TEXTURE_2D_ARRAY;
-            } else {
-                if (descriptor->sampleCount > 1) {
-                    return GL_TEXTURE_2D_MULTISAMPLE;
-                } else {
-                    return GL_TEXTURE_2D;
-                }
-            }
-        case wgpu::TextureDimension::e3D:
-            DAWN_ASSERT(descriptor->sampleCount == 1);
-            return GL_TEXTURE_3D;
-    }
-    DAWN_UNREACHABLE();
-}
-
-GLenum TargetForTextureViewDimension(wgpu::TextureViewDimension dimension,
-                                     uint32_t arrayLayerCount,
-                                     uint32_t sampleCount) {
+GLenum TargetForTextureViewDimension(wgpu::TextureViewDimension dimension, uint32_t sampleCount) {
     switch (dimension) {
         case wgpu::TextureViewDimension::e1D:
         case wgpu::TextureViewDimension::e2D:
             return (sampleCount > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
         case wgpu::TextureViewDimension::e2DArray:
             if (sampleCount > 1) {
-                DAWN_ASSERT(arrayLayerCount == 1);
                 return GL_TEXTURE_2D_MULTISAMPLE;
             }
             DAWN_ASSERT(sampleCount == 1);
             return GL_TEXTURE_2D_ARRAY;
         case wgpu::TextureViewDimension::Cube:
             DAWN_ASSERT(sampleCount == 1);
-            DAWN_ASSERT(arrayLayerCount == 6);
             return GL_TEXTURE_CUBE_MAP;
         case wgpu::TextureViewDimension::CubeArray:
             DAWN_ASSERT(sampleCount == 1);
-            DAWN_ASSERT(arrayLayerCount % 6 == 0);
             return GL_TEXTURE_CUBE_MAP_ARRAY;
         case wgpu::TextureViewDimension::e3D:
+            DAWN_ASSERT(sampleCount == 1);
             return GL_TEXTURE_3D;
 
         case wgpu::TextureViewDimension::Undefined:
-            break;
+        default:
+            DAWN_UNREACHABLE();
     }
-    DAWN_UNREACHABLE();
 }
 
 bool RequiresCreatingNewTextureView(
@@ -141,16 +114,6 @@
         return true;
     }
 
-    // TODO(dawn:2131): remove once compatibility texture binding view dimension is fully
-    // implemented.
-    switch (textureViewDescriptor->dimension) {
-        case wgpu::TextureViewDimension::Cube:
-        case wgpu::TextureViewDimension::CubeArray:
-            return true;
-        default:
-            break;
-    }
-
     return false;
 }
 
@@ -165,6 +128,7 @@
     // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTextureView.xhtml
     switch (target) {
         case GL_TEXTURE_2D_ARRAY:
+        case GL_TEXTURE_CUBE_MAP_ARRAY:
         case GL_TEXTURE_3D:
             gl.TexStorage3D(target, levels, internalFormat, size.width, size.height,
                             size.depthOrArrayLayers);
@@ -226,7 +190,8 @@
 
 Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor, GLuint handle)
     : TextureBase(device, descriptor), mHandle(handle) {
-    mTarget = TargetForTexture(descriptor);
+    mTarget = TargetForTextureViewDimension(GetCompatibilityTextureBindingViewDimension(),
+                                            descriptor->sampleCount);
 }
 
 Texture::~Texture() {}
@@ -570,8 +535,7 @@
 
 TextureView::TextureView(TextureBase* texture, const UnpackedPtr<TextureViewDescriptor>& descriptor)
     : TextureViewBase(texture, descriptor), mOwnsHandle(false) {
-    mTarget = TargetForTextureViewDimension(descriptor->dimension, descriptor->arrayLayerCount,
-                                            texture->GetSampleCount());
+    mTarget = TargetForTextureViewDimension(descriptor->dimension, texture->GetSampleCount());
 
     // Texture could be destroyed by the time we make a view.
     if (GetTexture()->IsDestroyed()) {
@@ -648,10 +612,21 @@
     }
 
     DAWN_ASSERT(handle != 0);
-    if (textarget == GL_TEXTURE_2D_ARRAY || textarget == GL_TEXTURE_3D) {
-        gl.FramebufferTextureLayer(target, attachment, handle, mipLevel, arrayLayer);
-    } else {
-        gl.FramebufferTexture2D(target, attachment, textarget, handle, mipLevel);
+
+    switch (textarget) {
+        case GL_TEXTURE_2D_ARRAY:
+        case GL_TEXTURE_CUBE_MAP_ARRAY:
+        case GL_TEXTURE_3D:
+            gl.FramebufferTextureLayer(target, attachment, handle, mipLevel, arrayLayer);
+            break;
+        case GL_TEXTURE_CUBE_MAP: {
+            GLenum cubeTexTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + arrayLayer;
+            gl.FramebufferTexture2D(target, attachment, cubeTexTarget, handle, mipLevel);
+            break;
+        }
+        default:
+            gl.FramebufferTexture2D(target, attachment, textarget, handle, mipLevel);
+            break;
     }
 }
 
diff --git a/src/dawn/tests/end2end/CopyTests.cpp b/src/dawn/tests/end2end/CopyTests.cpp
index 6d8b523..c9a595d 100644
--- a/src/dawn/tests/end2end/CopyTests.cpp
+++ b/src/dawn/tests/end2end/CopyTests.cpp
@@ -1722,9 +1722,6 @@
         CopyTests_T2B::SetUp();
         DAWN_SUPPRESS_TEST_IF(!IsCompatibilityMode());
         DAWN_SUPPRESS_TEST_IF(IsANGLESwiftShader());
-        // TODO(dawn:2131): remove once fully implemented, so cube texture doesn't require a copy.
-        DAWN_SUPPRESS_TEST_IF((IsOpenGL() || IsOpenGLES()) &&
-                              (GetParam().mTextureFormat == wgpu::TextureFormat::RGB9E5Ufloat));
     }
 };
 
diff --git a/src/dawn/tests/end2end/QueueTests.cpp b/src/dawn/tests/end2end/QueueTests.cpp
index 458a71a..42cd2c6 100644
--- a/src/dawn/tests/end2end/QueueTests.cpp
+++ b/src/dawn/tests/end2end/QueueTests.cpp
@@ -300,13 +300,21 @@
 
     void DoTest(const TextureSpec& textureSpec,
                 const DataSpec& dataSpec,
-                const wgpu::Extent3D& copySize) {
+                const wgpu::Extent3D& copySize,
+                const wgpu::TextureViewDimension bindingViewDimension =
+                    wgpu::TextureViewDimension::Undefined) {
         // Create data of size `size` and populate it
         std::vector<uint8_t> data(dataSpec.size);
         FillData(data.data(), data.size());
 
         // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
         wgpu::TextureDescriptor descriptor = {};
+        wgpu::TextureBindingViewDimensionDescriptor textureBindingViewDimensionDesc;
+        if (IsCompatibilityMode() &&
+            bindingViewDimension != wgpu::TextureViewDimension::Undefined) {
+            textureBindingViewDimensionDesc.textureBindingViewDimension = bindingViewDimension;
+            descriptor.nextInChain = &textureBindingViewDimensionDesc;
+        }
         descriptor.dimension = wgpu::TextureDimension::e2D;
         descriptor.size = textureSpec.textureSize;
         descriptor.format = GetParam().mTextureFormat;
@@ -605,6 +613,43 @@
     TestBody({1, 2, 0}, {17, 19, 1});
 }
 
+// Test with bytesPerRow greater than needed for cube textures.
+// Made for testing compat behavior.
+TEST_P(QueueWriteTextureTests, VaryingBytesPerRowCube) {
+    // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES
+    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());
+    // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 6 OpenGLES
+    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsARM());
+    // TODO(crbug.com/dawn/2131): diagnose this failure on Win Angle D3D11
+    DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
+
+    constexpr uint32_t kWidth = 257;
+    constexpr uint32_t kHeight = 257;
+
+    TextureSpec textureSpec;
+    textureSpec.textureSize = {kWidth, kHeight, 6};
+    textureSpec.level = 0;
+
+    auto TestBody = [&](wgpu::Origin3D copyOrigin, wgpu::Extent3D copyExtent) {
+        textureSpec.copyOrigin = copyOrigin;
+        for (unsigned int b : {1, 2, 3, 4}) {
+            uint32_t bytesPerRow =
+                copyExtent.width * utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat) + b;
+            DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow), copyExtent,
+                   wgpu::TextureViewDimension::Cube);
+        }
+    };
+
+    TestBody({0, 0, 0}, textureSpec.textureSize);
+
+    if (utils::IsDepthOrStencilFormat(GetParam().mTextureFormat)) {
+        // The entire subresource must be copied when the format is a depth/stencil format.
+        return;
+    }
+
+    TestBody({1, 2, 0}, {17, 17, 1});
+}
+
 // Test that writing with bytesPerRow = 0 and bytesPerRow < bytesInACompleteRow works
 // when we're copying one row only
 TEST_P(QueueWriteTextureTests, BytesPerRowWithOneRowCopy) {
diff --git a/src/dawn/tests/end2end/TextureShaderBuiltinTests.cpp b/src/dawn/tests/end2end/TextureShaderBuiltinTests.cpp
index 847bfa0..a21ac29 100644
--- a/src/dawn/tests/end2end/TextureShaderBuiltinTests.cpp
+++ b/src/dawn/tests/end2end/TextureShaderBuiltinTests.cpp
@@ -207,10 +207,10 @@
 TEST_P(TextureShaderBuiltinTests, BaseMipLevelTextureView) {
     // TODO(dawn:2538): failing on OpenGLES Angle backed by D3D11.
     DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
-    constexpr uint32_t kCubeLayers = 1;
+    constexpr uint32_t kLayers = 1;
     constexpr uint32_t kMipLevels = 3;
     wgpu::Texture tex =
-        CreateTexture("tex", kCubeLayers, kMipLevels, 1, wgpu::TextureViewDimension::e2D);
+        CreateTexture("tex", kLayers, kMipLevels, 1, wgpu::TextureViewDimension::e2D);
 
     constexpr uint32_t kBaseMipLevel = 1;
     constexpr uint32_t kViewMipLevelCount = 2;
@@ -271,8 +271,9 @@
 
 // Testing that baseMipLevel is handled correctly for texture_cube.
 TEST_P(TextureShaderBuiltinTests, BaseMipLevelTextureViewCube) {
-    // TODO(dawn:2442): fix texture_cube base mip level bug.
-    DAWN_SUPPRESS_TEST_IF(IsCompatibilityMode());
+    // TODO(crbug.com/dawn/2442): diagnose this failure on Win Angle D3D11
+    DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
+
     constexpr uint32_t kCubeLayers = 6;
     constexpr uint32_t kMipLevels = 3;
     wgpu::Texture texCube =
@@ -288,11 +289,8 @@
     const uint32_t textureWidthLevel0 = 1 << kMipLevels;
     const uint32_t textureWidthLevel1 = textureWidthLevel0 >> 1;
     const uint32_t textureWidthLevel2 = textureWidthLevel1 >> 1;
-    const uint32_t expected[] = {
-        textureWidthLevel1,
-        textureWidthLevel1,
-        textureWidthLevel2,
-    };
+    const uint32_t expected[] = {textureWidthLevel1, textureWidthLevel1, textureWidthLevel2,
+                                 kViewMipLevelCount};
 
     wgpu::BufferDescriptor bufferDesc;
     bufferDesc.size = sizeof(expected);
@@ -309,6 +307,7 @@
         dstBuf[0] = textureDimensions(tex_cube).x;
         dstBuf[1] = textureDimensions(tex_cube, 0).x;
         dstBuf[2] = textureDimensions(tex_cube, 1).x;
+        dstBuf[3] = textureNumLevels(tex_cube);
     }
     )";
     // clang-format on