// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <algorithm>
#include <array>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>

#include "src/dawn/common/Constants.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/tests/DawnTest.h"
#include "src/dawn/utils/TestUtils.h"
#include "src/dawn/utils/TextureUtils.h"
#include "src/dawn/utils/WGPUHelpers.h"

namespace dawn {
namespace {

// For MinimumBufferSpec bytesPerRow and rowsPerImage, compute a default from the copy extent.
constexpr uint32_t kStrideComputeDefault = 0xFFFF'FFFEul;

constexpr wgpu::TextureFormat kDefaultFormat = wgpu::TextureFormat::RGBA8Unorm;

bool IsSnorm(wgpu::TextureFormat format) {
    return format == wgpu::TextureFormat::RGBA8Snorm || format == wgpu::TextureFormat::RG8Snorm ||
           format == wgpu::TextureFormat::R8Snorm;
}

template <typename T, size_t NumComponents>
struct Color {
    static constexpr size_t kNumComponents = NumComponents;
    static constexpr size_t kDataSize = sizeof(T) * NumComponents;
    using ComponentRepresentation = T;

    // Get representation of one component.
    T GetCompRep(size_t idx) const { return components[idx]; }

    T components[NumComponents] = {};
};

template <size_t NumComponents>
struct ColorF16 : public Color<uint16_t, NumComponents> {
    using ComponentRepresentation = float;

    // Get representation of one component.
    float GetCompRep(size_t idx) const { return Float16ToFloat32(this->components[idx]); }
};

struct ColorRGB10A2 {
    static constexpr size_t kNumComponents = 4;
    static constexpr size_t kDataSize = sizeof(uint32_t);
    using ComponentRepresentation = uint32_t;

    // Get representation of one component.
    uint32_t GetCompRep(size_t idx) const { return (mPackedColor >> (10 * idx)) & 0x3ff; }

    uint32_t mPackedColor = 0;
};

static_assert(sizeof(Color<uint8_t, 1>) == 1, "Unexpected padding");
static_assert(sizeof(Color<uint8_t, 2>) == 2, "Unexpected padding");
static_assert(sizeof(Color<uint16_t, 1>) == 2, "Unexpected padding");
static_assert(sizeof(ColorRGB10A2) == 4, "Unexpected padding");

template <typename ColorType>
class ColorExpectation : public detail::CustomTextureExpectation {
  public:
    static constexpr size_t kNumComponents = ColorType::kNumComponents;
    using CompRepType = ColorType::ComponentRepresentation;

    ColorExpectation(const ColorType* expected, size_t count, CompRepType tolerance)
        : mTolerance(tolerance) {
        mExpected.assign(expected, expected + count);
    }

    uint32_t DataSize() override { return ColorType::kDataSize; }

    testing::AssertionResult Check(const void* data, size_t size) override {
        DAWN_ASSERT(size == sizeof(ColorType) * mExpected.size());
        const ColorType* actual = static_cast<const ColorType*>(data);

        for (size_t i = 0; i < mExpected.size(); ++i) {
            if (!AreEqual(mExpected[i], actual[i])) {
                return testing::AssertionFailure()
                       << "Expected data[" << i << "] to be " << ToString(mExpected[i])
                       << ", actual " << ToString(actual[i]) << "\n";
            }
        }
        return testing::AssertionSuccess();
    }

  private:
    static CompRepType GetComponent(const ColorType& color, size_t idx) {
        DAWN_ASSERT(idx <= kNumComponents);
        return color.GetCompRep(idx);
    }

    static CompRepType Diff(CompRepType lhs, CompRepType rhs) {
        if constexpr (std::is_integral_v<CompRepType>) {
            return std::abs(static_cast<int64_t>(lhs) - static_cast<int64_t>(rhs));
        } else {
            return std::abs(lhs - rhs);
        }
    }

    static std::ostream& Print(std::ostream& stream, CompRepType component) {
        if constexpr (std::is_same_v<CompRepType, uint8_t>) {
            return stream << static_cast<int>(component);
        } else {
            return stream << component;
        }
    }

    static std::string ToString(const ColorType& color) {
        std::ostringstream ss;
        for (size_t i = 0; i < kNumComponents; ++i) {
            Print(ss, GetComponent(color, i));

            if (i < kNumComponents - 1) {
                ss << " ";
            }
        }

        return ss.str();
    }

    bool AreEqual(const ColorType& lhs, const ColorType& rhs) const {
        for (size_t i = 0; i < kNumComponents; ++i) {
            if (Diff(GetComponent(lhs, i), GetComponent(rhs, i)) > mTolerance) {
                return false;
            }
        }
        return true;
    }

    std::vector<ColorType> mExpected;
    const CompRepType mTolerance;
};

class CopyTests {
  protected:
    struct TextureSpec {
        wgpu::TextureFormat format = kDefaultFormat;
        wgpu::Origin3D copyOrigin = {0, 0, 0};
        wgpu::Extent3D textureSize;
        uint32_t copyLevel = 0;
        uint32_t levelCount = 1;
    };

    struct BufferSpec {
        uint64_t size;
        uint64_t offset;
        uint32_t bytesPerRow;
        uint32_t rowsPerImage;
    };

    static std::vector<uint8_t> GetExpectedTextureData(wgpu::TextureFormat format,
                                                       const utils::TextureDataCopyLayout& layout) {
        switch (format) {
            case wgpu::TextureFormat::R16Float:
            case wgpu::TextureFormat::RG16Float:
            case wgpu::TextureFormat::RGBA16Float:
                return GetExpectedTextureData16Float(layout);
            case wgpu::TextureFormat::RGB9E5Ufloat:
                return GetExpectedTextureDataRGB9E5Ufloat(layout);
            case wgpu::TextureFormat::RG11B10Ufloat:
                return GetExpectedTextureDataRG11B10Ufloat(layout);
            default:
                return GetExpectedTextureDataGeneral(layout);
        }
    }

    static std::vector<uint8_t> GetExpectedTextureDataGeneral(
        const utils::TextureDataCopyLayout& layout) {
        uint32_t bytesPerTexelBlock = layout.bytesPerRow / layout.texelBlocksPerRow;
        std::vector<uint8_t> textureData(layout.byteLength);
        for (uint32_t layer = 0; layer < layout.mipSize.depthOrArrayLayers; ++layer) {
            const uint32_t byteOffsetPerSlice = layout.bytesPerImage * layer;
            for (uint32_t y = 0; y < layout.mipSize.height; ++y) {
                for (uint32_t x = 0; x < layout.mipSize.width * bytesPerTexelBlock; ++x) {
                    uint32_t i = x + y * layout.bytesPerRow;
                    uint8_t v = static_cast<uint8_t>((x + 1 + (layer + 1) * y) % 256);
                    if (v == 0x80u) {
                        // Some texture copy is implemented via textureLoad from compute pass.
                        // For 8 bit Snorm texture when read in shader, 0x80 (-128) becomes 0x81
                        // (-127) As Snorm value range mapped to [-1, 1]. To avoid failure in buffer
                        // comparison stage, simply avoid writing 0x81 instead of 0x80 to the
                        // texture.
                        v = 0x81u;
                    }
                    textureData[byteOffsetPerSlice + i] = v;
                }
            }
        }
        return textureData;
    }

    // Special function to generate test data for *16Float to workaround nonunique encoding
    // issue.
    static std::vector<uint8_t> GetExpectedTextureData16Float(
        const utils::TextureDataCopyLayout& layout) {
        // These are some known 16 bit float values that always unpack and pack to the same bytes.
        // Pick test data from these values to provide some level of test coverage for *16Float.
        constexpr uint8_t goodBytes[] = {
            0x30, 0x00, 0x49, 0x00, 0x56, 0x40, 0x20, 0x00, 0x37, 0x4C, 0x42, 0x00, 0x3F, 0x6C,
        };
        constexpr uint32_t formatByteSize = 2;
        constexpr uint32_t numGoodValues = sizeof(goodBytes) / sizeof(uint8_t) / formatByteSize;

        uint32_t bytesPerTexelBlock = layout.bytesPerRow / layout.texelBlocksPerRow;
        std::vector<uint8_t> textureData(layout.byteLength);
        for (uint32_t layer = 0; layer < layout.mipSize.depthOrArrayLayers; ++layer) {
            const uint32_t byteOffsetPerSlice = layout.bytesPerImage * layer;
            for (uint32_t y = 0; y < layout.mipSize.height; ++y) {
                for (uint32_t x = 0; x < layout.mipSize.width * bytesPerTexelBlock; ++x) {
                    uint32_t i = x + y * layout.bytesPerRow;

                    uint32_t pixelId = (x + 1 + (layer + 1) * y);
                    uint8_t v = goodBytes[pixelId % numGoodValues + pixelId % formatByteSize];

                    textureData[byteOffsetPerSlice + i] = v;
                }
            }
        }
        return textureData;
    }

    // Special function to generate test data for RGB9E5Ufloat to workaround nonunique encoding
    // issue.
    static std::vector<uint8_t> GetExpectedTextureDataRGB9E5Ufloat(
        const utils::TextureDataCopyLayout& layout) {
        // These are some known 4-byte RGB9E5Ufloat values that always unpack and pack to the same
        // bytes. Pick test data from these values to provide some level of test coverage for
        // RGB9E5Ufloat.
        constexpr uint8_t goodBytes[] = {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
            0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
            0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28};
        constexpr uint32_t formatByteSize = 4;
        constexpr uint32_t numGoodValues = sizeof(goodBytes) / sizeof(uint8_t) / formatByteSize;
        uint32_t bytesPerTexelBlock = layout.bytesPerRow / layout.texelBlocksPerRow;
        std::vector<uint8_t> textureData(layout.byteLength);
        for (uint32_t layer = 0; layer < layout.mipSize.depthOrArrayLayers; ++layer) {
            const uint32_t byteOffsetPerSlice = layout.bytesPerImage * layer;
            for (uint32_t y = 0; y < layout.mipSize.height; ++y) {
                for (uint32_t x = 0; x < layout.mipSize.width; ++x) {
                    uint32_t o =
                        x * bytesPerTexelBlock + y * layout.bytesPerRow + byteOffsetPerSlice;
                    uint32_t idx = formatByteSize * ((x + 1 + (layer + 1) * y) % numGoodValues);
                    textureData[o + 0] = goodBytes[idx + 0];
                    textureData[o + 1] = goodBytes[idx + 1];
                    textureData[o + 2] = goodBytes[idx + 2];
                    textureData[o + 3] = goodBytes[idx + 3];
                }
            }
        }
        return textureData;
    }

    // Special function to generate test data for RG11B10Ufloat to workaround nonunique encoding
    // issue.
    static std::vector<uint8_t> GetExpectedTextureDataRG11B10Ufloat(
        const utils::TextureDataCopyLayout& layout) {
        // These are some known 4-byte RG11B10Ufloat values that always unpack and pack to the same
        // bytes. Pick test data from these values to provide some level of test coverage for
        // RG11B10Ufloat.
        constexpr uint8_t goodBytes[] = {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
            0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
            0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28};
        constexpr uint32_t formatByteSize = 4;
        constexpr uint32_t numGoodValues = sizeof(goodBytes) / sizeof(uint8_t) / formatByteSize;
        uint32_t bytesPerTexelBlock = layout.bytesPerRow / layout.texelBlocksPerRow;
        std::vector<uint8_t> textureData(layout.byteLength);
        for (uint32_t layer = 0; layer < layout.mipSize.depthOrArrayLayers; ++layer) {
            const uint32_t byteOffsetPerSlice = layout.bytesPerImage * layer;
            for (uint32_t y = 0; y < layout.mipSize.height; ++y) {
                for (uint32_t x = 0; x < layout.mipSize.width; ++x) {
                    uint32_t o =
                        x * bytesPerTexelBlock + y * layout.bytesPerRow + byteOffsetPerSlice;
                    uint32_t idx = formatByteSize * ((x + 1 + (layer + 1) * y) % numGoodValues);
                    textureData[o + 0] = goodBytes[idx + 0];
                    textureData[o + 1] = goodBytes[idx + 1];
                    textureData[o + 2] = goodBytes[idx + 2];
                    textureData[o + 3] = goodBytes[idx + 3];
                }
            }
        }
        return textureData;
    }

    static BufferSpec MinimumBufferSpec(
        uint32_t width,
        uint32_t height,
        uint32_t depth = 1,
        wgpu::TextureFormat format = kDefaultFormat,
        uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment) {
        return MinimumBufferSpec({width, height, depth}, kStrideComputeDefault,
                                 depth == 1 ? wgpu::kCopyStrideUndefined : kStrideComputeDefault,
                                 format, textureBytesPerRowAlignment);
    }

    static BufferSpec MinimumBufferSpec(
        wgpu::Extent3D copyExtent,
        uint32_t overrideBytesPerRow = kStrideComputeDefault,
        uint32_t overrideRowsPerImage = kStrideComputeDefault,
        wgpu::TextureFormat format = kDefaultFormat,
        uint32_t textureBytesPerRowAlignment = kTextureBytesPerRowAlignment) {
        uint32_t bytesPerRow =
            utils::GetMinimumBytesPerRow(format, copyExtent.width, textureBytesPerRowAlignment);
        if (overrideBytesPerRow != kStrideComputeDefault) {
            bytesPerRow = overrideBytesPerRow;
        }
        uint32_t rowsPerImage = copyExtent.height;
        if (overrideRowsPerImage != kStrideComputeDefault) {
            rowsPerImage = overrideRowsPerImage;
        }

        // Align with 4 byte, not actually "minimum" but is needed when check buffer content.
        uint32_t totalDataSize =
            Align(utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, copyExtent, format), 4);
        return {totalDataSize, 0, bytesPerRow, rowsPerImage};
    }
    static void CopyTextureData(uint32_t bytesPerTexelBlock,
                                const void* srcData,
                                uint32_t widthInBlocks,
                                uint32_t heightInBlocks,
                                uint32_t depthInBlocks,
                                uint32_t srcBytesPerRow,
                                uint32_t srcRowsPerImage,
                                void* dstData,
                                uint32_t dstBytesPerRow,
                                uint32_t dstRowsPerImage) {
        for (unsigned int z = 0; z < depthInBlocks; ++z) {
            uint32_t srcDepthOffset = z * srcBytesPerRow * srcRowsPerImage;
            uint32_t dstDepthOffset = z * dstBytesPerRow * dstRowsPerImage;
            for (unsigned int y = 0; y < heightInBlocks; ++y) {
                memcpy(static_cast<uint8_t*>(dstData) + dstDepthOffset + y * dstBytesPerRow,
                       static_cast<const uint8_t*>(srcData) + srcDepthOffset + y * srcBytesPerRow,
                       widthInBlocks * bytesPerTexelBlock);
            }
        }
    }
};

namespace {
using TextureFormat = wgpu::TextureFormat;
DAWN_TEST_PARAM_STRUCT(CopyTextureFormatParams, TextureFormat);

class CopyTests_WithFormatParam : public CopyTests,
                                  public DawnTestWithParams<CopyTextureFormatParams> {
  protected:
    struct TextureSpec : CopyTests::TextureSpec {
        TextureSpec() { format = GetParam().mTextureFormat; }
    };

    void SetUp() override {
        DawnTestWithParams<CopyTextureFormatParams>::SetUp();
        switch (GetParam().mTextureFormat) {
            case wgpu::TextureFormat::R16Unorm:
            case wgpu::TextureFormat::RG16Unorm:
            case wgpu::TextureFormat::RGBA16Unorm:
                DAWN_TEST_UNSUPPORTED_IF(
                    !device.HasFeature(wgpu::FeatureName::Unorm16TextureFormats));
                break;
            default:
                break;
        }
    }

    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        std::vector<wgpu::FeatureName> requiredFeatures = {};

        switch (GetParam().mTextureFormat) {
            case wgpu::TextureFormat::R16Unorm:
            case wgpu::TextureFormat::RG16Unorm:
            case wgpu::TextureFormat::RGBA16Unorm:
                if (SupportsFeatures({wgpu::FeatureName::Unorm16TextureFormats})) {
                    requiredFeatures.push_back(wgpu::FeatureName::Unorm16TextureFormats);
                }
                break;
            default:
                break;
        }

        if (SupportsFeatures({wgpu::FeatureName::DawnTexelCopyBufferRowAlignment})) {
            requiredFeatures.push_back(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment);
        }
        return requiredFeatures;
    }

    void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
                           dawn::utils::ComboLimits& required) override {
        // Some backends use compute shaders to implement copies. Adjust storage buffers' alignment
        // to make sure the copies still work with non-default alignments.
        required.minStorageBufferOffsetAlignment = supported.minStorageBufferOffsetAlignment;
    }

    uint32_t GetTextureBytesPerRowAlignment() const {
        if (!device.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) {
            return kTextureBytesPerRowAlignment;
        }
        dawn::utils::ComboLimits limits;
        wgpu::DawnTexelCopyBufferRowAlignmentLimits alignmentLimits{};
        device.GetLimits(limits.GetLinked(&alignmentLimits));
        return alignmentLimits.minTexelCopyBufferRowAlignment;
    }
    BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height, uint32_t depth = 1) {
        return CopyTests::MinimumBufferSpec(width, height, depth, GetParam().mTextureFormat,
                                            GetTextureBytesPerRowAlignment());
    }
    BufferSpec MinimumBufferSpec(wgpu::Extent3D copyExtent,
                                 uint32_t overrideBytesPerRow = kStrideComputeDefault,
                                 uint32_t overrideRowsPerImage = kStrideComputeDefault) {
        return CopyTests::MinimumBufferSpec(copyExtent, overrideBytesPerRow, overrideRowsPerImage,
                                            GetParam().mTextureFormat,
                                            GetTextureBytesPerRowAlignment());
    }
};

}  // namespace

class CopyTests_T2B : public CopyTests_WithFormatParam {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        std::vector<wgpu::FeatureName> requiredFeatures =
            CopyTests_WithFormatParam::GetRequiredFeatures();
        if (SupportsFeatures({wgpu::FeatureName::FlexibleTextureViews})) {
            requiredFeatures.push_back(wgpu::FeatureName::FlexibleTextureViews);
        }
        return requiredFeatures;
    }

    void SetUp() override {
        CopyTests_WithFormatParam::SetUp();

        // TODO(crbug.com/40238674): Fails on Pixel 10 gles on compilation so it must be skipped at
        // this level.
        DAWN_SUPPRESS_TEST_IF(IsImgTec() && IsOpenGLES());

        auto format = GetParam().mTextureFormat;

        // TODO(crbug.com/dawn/2294): diagnose BGRA T2B failures on Pixel 4 OpenGLES
        DAWN_SUPPRESS_TEST_IF(format == wgpu::TextureFormat::BGRA8Unorm && IsOpenGLES() &&
                              IsAndroid() && IsQualcomm());

        // TODO(dawn:1913): Many float formats tests failing for Metal backend on Mac Intel.
        DAWN_SUPPRESS_TEST_IF((format == wgpu::TextureFormat::R32Float ||
                               format == wgpu::TextureFormat::RG32Float ||
                               format == wgpu::TextureFormat::RGBA32Float ||
                               format == wgpu::TextureFormat::RGBA16Float ||
                               format == wgpu::TextureFormat::RG11B10Ufloat) &&
                              IsMacOS() && IsIntel() && IsMetal());

        // TODO(dawn:1935): Many 16 float formats tests failing for D3D11 and OpenGLES backends on
        // Intel Gen12.
        DAWN_SUPPRESS_TEST_IF((format == wgpu::TextureFormat::R16Float ||
                               format == wgpu::TextureFormat::RGBA16Float ||
                               format == wgpu::TextureFormat::RG11B10Ufloat) &&
                              (IsD3D11() || IsOpenGLES()) && IsIntelGen12());

        // TODO(crbug.com/463661449): Flaky on Snapdragon X Elite SoCs w/ D3D11.
        DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11());
    }

    void DoTest(
        const TextureSpec& textureSpec,
        const BufferSpec& bufferSpec,
        const wgpu::Extent3D& copySize,
        wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D,
        wgpu::TextureViewDimension bindingViewDimension = wgpu::TextureViewDimension::Undefined,
        bool useMappableBuffer = false) {
        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
        // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
        wgpu::TextureDescriptor descriptor;
        descriptor.dimension = dimension;
        descriptor.size = textureSpec.textureSize;
        descriptor.sampleCount = 1;
        descriptor.format = textureSpec.format;
        descriptor.mipLevelCount = textureSpec.levelCount;
        descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;

        // Test cube texture copy for compat.
        wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;
        if (IsCompatibilityMode() &&
            bindingViewDimension != wgpu::TextureViewDimension::Undefined) {
            textureBindingViewDimensionDesc.textureBindingViewDimension = bindingViewDimension;
            descriptor.nextInChain = &textureBindingViewDimensionDesc;
        }

        wgpu::Texture texture = device.CreateTexture(&descriptor);

        // Layout for initial data upload to texture.
        // Some parts of this result are also reused later.
        const utils::TextureDataCopyLayout copyLayout =
            utils::GetTextureDataCopyLayoutForTextureAtLevel(
                textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel, dimension);

        const std::vector<uint8_t> textureArrayData =
            GetExpectedTextureData(textureSpec.format, copyLayout);
        {
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
                utils::CreateTexelCopyTextureInfo(texture, textureSpec.copyLevel, {0, 0, 0});
            wgpu::TexelCopyBufferLayout texelCopyBufferLayout = utils::CreateTexelCopyBufferLayout(
                0, copyLayout.bytesPerRow, copyLayout.rowsPerImage);
            queue.WriteTexture(&texelCopyTextureInfo, textureArrayData.data(),
                               copyLayout.byteLength, &texelCopyBufferLayout, &copyLayout.mipSize);
        }

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        // Create a buffer of `size` and populate it with empty data (0,0,0,0) Note:
        // Prepopulating the buffer with empty data ensures that there is not random data in the
        // expectation and helps ensure that the padding due to the bytes per row is not modified
        // by the copy.
        wgpu::BufferDescriptor bufferDesc;
        bufferDesc.size = bufferSpec.size;
        bufferDesc.usage = wgpu::BufferUsage::CopyDst;
        if (useMappableBuffer) {
            bufferDesc.usage |= wgpu::BufferUsage::MapRead;
        } else {
            bufferDesc.usage |= wgpu::BufferUsage::CopySrc;
        }
        wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);

        {
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                texture, textureSpec.copyLevel, textureSpec.copyOrigin);
            wgpu::TexelCopyBufferInfo texelCopyBufferInfo = utils::CreateTexelCopyBufferInfo(
                buffer, bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
            encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &copySize);
        }

        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);

        uint64_t bufferOffset = bufferSpec.offset;

        uint32_t copyLayer = copySize.depthOrArrayLayers;
        uint32_t copyDepth = 1;
        if (dimension == wgpu::TextureDimension::e3D) {
            copyLayer = 1;
            copyDepth = copySize.depthOrArrayLayers;
        }

        const wgpu::Extent3D copySizePerLayer = {copySize.width, copySize.height, copyDepth};
        const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copyLayer;
        std::vector<uint8_t> expected(utils::RequiredBytesInCopy(
            bufferSpec.bytesPerRow, bufferSpec.rowsPerImage, copySizePerLayer, textureSpec.format));

        if (useMappableBuffer) {
            bool done = false;
            buffer.MapAsync(wgpu::MapMode::Read, 0, buffer.GetSize(),
                            wgpu::CallbackMode::AllowProcessEvents,
                            [&](wgpu::MapAsyncStatus status, wgpu::StringView) {
                                ASSERT_EQ(wgpu::MapAsyncStatus::Success, status);
                                done = true;
                            });
            while (!done) {
                WaitABit();
            }
        }

        for (uint32_t layer = textureSpec.copyOrigin.z; layer < maxArrayLayer; ++layer) {
            // Copy the data used to create the upload buffer in the specified copy region to have
            // the same format as the expected buffer data.
            std::fill(expected.begin(), expected.end(), 0x00);

            const uint32_t texelIndexOffset = copyLayout.bytesPerImage * layer;
            const uint32_t expectedTexelArrayDataStartIndex =
                texelIndexOffset +
                bytesPerTexel * (textureSpec.copyOrigin.x +
                                 textureSpec.copyOrigin.y * copyLayout.texelBlocksPerRow);

            CopyTextureData(bytesPerTexel,
                            textureArrayData.data() + expectedTexelArrayDataStartIndex,
                            copySize.width, copySize.height, copyDepth, copyLayout.bytesPerRow,
                            copyLayout.rowsPerImage, expected.data(), bufferSpec.bytesPerRow,
                            bufferSpec.rowsPerImage);

            std::ostringstream errorMsgSs;
            errorMsgSs << "Texture to Buffer copy failed copying region [("
                       << textureSpec.copyOrigin.x << ", " << textureSpec.copyOrigin.y << ", "
                       << textureSpec.copyOrigin.z << "), ("
                       << textureSpec.copyOrigin.x + copySize.width << ", "
                       << textureSpec.copyOrigin.y + copySize.height << ", "
                       << textureSpec.copyOrigin.z + copySize.depthOrArrayLayers << ")) from "
                       << textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
                       << " texture at mip level " << textureSpec.copyLevel << " layer " << layer
                       << " to " << bufferSpec.size << "-byte buffer with offset " << bufferOffset
                       << " and bytes per row " << bufferSpec.bytesPerRow << "\n";

            if (useMappableBuffer) {
                const auto* mappedPtr = static_cast<const uint8_t*>(buffer.GetConstMappedRange());
                for (size_t i = 0; i < expected.size(); ++i) {
                    if (mappedPtr[bufferOffset + i] != expected[i]) {
                        EXPECT_EQ(mappedPtr[bufferOffset + i], expected[i])
                            << "with i=" << i << "\n"
                            << errorMsgSs.str();
                        break;
                    }
                }
            } else {
                EXPECT_BUFFER_U8_RANGE_EQ(reinterpret_cast<const uint8_t*>(expected.data()), buffer,
                                          bufferOffset, expected.size())
                    << errorMsgSs.str();
            }

            bufferOffset += bufferSpec.bytesPerRow * bufferSpec.rowsPerImage;
        }

        if (useMappableBuffer) {
            buffer.Unmap();
        }
    }
};

class CopyTests_B2T : public CopyTests_WithFormatParam {
  protected:
    void DoTest(const TextureSpec& textureSpec,
                const BufferSpec& bufferSpec,
                const wgpu::Extent3D& copySize,
                wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) {
        switch (textureSpec.format) {
            case wgpu::TextureFormat::R8Unorm:
                DoTestImpl<Color<uint8_t, 1>>(textureSpec, bufferSpec, copySize, dimension,
                                              /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::RG8Unorm:
                DoTestImpl<Color<uint8_t, 2>>(textureSpec, bufferSpec, copySize, dimension,
                                              /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::RGBA8Unorm:
            case wgpu::TextureFormat::BGRA8Unorm:
                DoTestImpl<Color<uint8_t, 4>>(textureSpec, bufferSpec, copySize, dimension,
                                              /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::RGB10A2Unorm:
                DoTestImpl<ColorRGB10A2>(textureSpec, bufferSpec, copySize, dimension,
                                         /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::R16Float:
                DoTestImpl<ColorF16<1>>(textureSpec, bufferSpec, copySize, dimension,
                                        /*tolerance=*/0.001f);
                break;
            case wgpu::TextureFormat::R16Unorm:
                DoTestImpl<Color<uint16_t, 1>>(textureSpec, bufferSpec, copySize, dimension,
                                               /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::RG16Float:
                DoTestImpl<ColorF16<2>>(textureSpec, bufferSpec, copySize, dimension,
                                        /*tolerance=*/0.001f);
                break;
            case wgpu::TextureFormat::RG16Unorm:
                DoTestImpl<Color<uint16_t, 2>>(textureSpec, bufferSpec, copySize, dimension,
                                               /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::RGBA16Float:
                DoTestImpl<ColorF16<4>>(textureSpec, bufferSpec, copySize, dimension,
                                        /*tolerance=*/0.001f);
                break;
            case wgpu::TextureFormat::RGBA16Unorm:
                DoTestImpl<Color<uint16_t, 4>>(textureSpec, bufferSpec, copySize, dimension,
                                               /*tolerance=*/1);
                break;
            case wgpu::TextureFormat::R32Float:
                DoTestImpl<Color<float, 1>>(textureSpec, bufferSpec, copySize, dimension,
                                            /*tolerance=*/0.0001f);
                break;
            case wgpu::TextureFormat::RG32Float:
                DoTestImpl<Color<float, 2>>(textureSpec, bufferSpec, copySize, dimension,
                                            /*tolerance=*/0.0001f);
                break;
            case wgpu::TextureFormat::RGBA32Float:
                DoTestImpl<Color<float, 4>>(textureSpec, bufferSpec, copySize, dimension,
                                            /*tolerance=*/0.0001f);
                break;
            default:
                DAWN_UNREACHABLE();
        }
    }

    template <class PixelType>
    void DoTestImpl(const TextureSpec& textureSpec,
                    const BufferSpec& bufferSpec,
                    const wgpu::Extent3D& copySize,
                    wgpu::TextureDimension dimension,
                    PixelType::ComponentRepresentation tolerance = {}) {
        const uint32_t bytesPerTexel = PixelType::kDataSize;
        DAWN_ASSERT(bytesPerTexel == utils::GetTexelBlockSizeInBytes(textureSpec.format));
        const utils::TextureDataCopyLayout copyLayout =
            utils::GetTextureDataCopyLayoutForTextureAtLevel(
                textureSpec.format, textureSpec.textureSize, textureSpec.copyLevel, dimension,
                bufferSpec.rowsPerImage, GetTextureBytesPerRowAlignment());

        // Create a buffer and populate it with data
        wgpu::Buffer buffer;
        std::vector<uint8_t> bufferData(bufferSpec.offset, 0xff);
        {
            const std::vector<uint8_t> copyData =
                GetExpectedTextureData(textureSpec.format, copyLayout);

            bufferData.insert(bufferData.end(), copyData.begin(), copyData.end());
            bufferData.resize(Align(bufferSpec.size, 4));

            wgpu::BufferDescriptor descriptor;
            descriptor.size = bufferData.size();
            descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
            descriptor.mappedAtCreation = true;
            buffer = device.CreateBuffer(&descriptor);
            memcpy(buffer.GetMappedRange(), bufferData.data(), bufferData.size());
            buffer.Unmap();
        }

        // Create a texture that is `width` x `height` with (`level` + 1) mip levels.
        wgpu::TextureDescriptor descriptor;
        descriptor.dimension = dimension;
        descriptor.size = textureSpec.textureSize;
        descriptor.sampleCount = 1;
        descriptor.format = textureSpec.format;
        descriptor.mipLevelCount = textureSpec.levelCount;
        descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
        wgpu::Texture texture = device.CreateTexture(&descriptor);

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        wgpu::TexelCopyBufferInfo texelCopyBufferInfo = utils::CreateTexelCopyBufferInfo(
            buffer, bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
            texture, textureSpec.copyLevel, textureSpec.copyOrigin);
        encoder.CopyBufferToTexture(&texelCopyBufferInfo, &texelCopyTextureInfo, &copySize);

        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);

        uint32_t copyLayer = copySize.depthOrArrayLayers;
        uint32_t copyDepth = 1;
        if (dimension == wgpu::TextureDimension::e3D) {
            copyLayer = 1;
            copyDepth = copySize.depthOrArrayLayers;
        }

        uint64_t bufferOffset = bufferSpec.offset;
        const uint32_t blockWidth = utils::GetTextureFormatBlockWidth(textureSpec.format);
        const uint32_t blockHeight = utils::GetTextureFormatBlockHeight(textureSpec.format);
        const uint32_t texelCountPerLayer = copyDepth * (copyLayout.mipSize.width / blockWidth) *
                                            (copyLayout.mipSize.height / blockHeight) *
                                            bytesPerTexel;
        for (uint32_t layer = 0; layer < copyLayer; ++layer) {
            // Copy and pack the data used to create the buffer in the specified copy region to have
            // the same format as the expected texture data.
            std::vector<PixelType> expected(texelCountPerLayer);
            CopyTextureData(bytesPerTexel, bufferData.data() + bufferOffset, copySize.width,
                            copySize.height, copyDepth, bufferSpec.bytesPerRow,
                            bufferSpec.rowsPerImage, expected.data(),
                            copySize.width * bytesPerTexel, copySize.height);

            EXPECT_TEXTURE_EQ(
                new ColorExpectation<PixelType>(
                    expected.data(), copySize.width * copySize.height * copyDepth, tolerance),
                texture,
                {textureSpec.copyOrigin.x, textureSpec.copyOrigin.y,
                 textureSpec.copyOrigin.z + layer},
                {copySize.width, copySize.height, copyDepth}, textureSpec.copyLevel)
                << "Buffer to Texture copy failed copying " << bufferSpec.size
                << "-byte buffer with offset " << bufferSpec.offset << " and bytes per row "
                << bufferSpec.bytesPerRow << " to [(" << textureSpec.copyOrigin.x << ", "
                << textureSpec.copyOrigin.y << "), (" << textureSpec.copyOrigin.x + copySize.width
                << ", " << textureSpec.copyOrigin.y + copySize.height << ")) region of "
                << textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
                << " texture at mip level " << textureSpec.copyLevel << " layer " << layer << "\n";
            bufferOffset += copyLayout.bytesPerImage;
        }
    }
};

template <typename Parent>
class CopyTests_T2TBase : public CopyTests, public Parent {
  protected:
    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
        return {wgpu::FeatureName::DawnInternalUsages};
    }

    void DoTest(
        const TextureSpec& srcSpec,
        const TextureSpec& dstSpec,
        const wgpu::Extent3D& copySize,
        wgpu::TextureDimension srcDimension,
        wgpu::TextureDimension dstDimension,
        bool copyWithinSameTexture = false,
        wgpu::TextureViewDimension srcBindingViewDimension = wgpu::TextureViewDimension::Undefined,
        wgpu::TextureViewDimension dstBindingViewDimension =
            wgpu::TextureViewDimension::Undefined) {
        const wgpu::TextureFormat format = srcSpec.format;

        wgpu::TextureDescriptor srcDescriptor;
        srcDescriptor.dimension = srcDimension;
        srcDescriptor.size = srcSpec.textureSize;
        srcDescriptor.sampleCount = 1;
        srcDescriptor.format = format;
        srcDescriptor.mipLevelCount = srcSpec.levelCount;
        srcDescriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;

        // Test cube texture copy for compat.
        wgpu::TextureBindingViewDimension srcTextureBindingViewDimensionDesc;
        if (srcBindingViewDimension != wgpu::TextureViewDimension::Undefined) {
            srcTextureBindingViewDimensionDesc.textureBindingViewDimension =
                srcBindingViewDimension;
            srcDescriptor.nextInChain = &srcTextureBindingViewDimensionDesc;
        }

        wgpu::Texture srcTexture = this->device.CreateTexture(&srcDescriptor);

        wgpu::Texture dstTexture;
        if (copyWithinSameTexture) {
            dstTexture = srcTexture;
        } else {
            wgpu::TextureDescriptor dstDescriptor;
            dstDescriptor.dimension = dstDimension;
            dstDescriptor.size = dstSpec.textureSize;
            dstDescriptor.sampleCount = 1;
            dstDescriptor.format = dstSpec.format;
            dstDescriptor.mipLevelCount = dstSpec.levelCount;
            dstDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;

            // Test cube texture copy for compat.
            wgpu::TextureBindingViewDimension dstTextureBindingViewDimension;
            if (dstBindingViewDimension != wgpu::TextureViewDimension::Undefined) {
                dstTextureBindingViewDimension.textureBindingViewDimension =
                    dstBindingViewDimension;
                dstDescriptor.nextInChain = &dstTextureBindingViewDimension;
            }

            dstTexture = this->device.CreateTexture(&dstDescriptor);
        }

        // Create an upload buffer and use it to completely populate the subresources of the src
        // texture that will be copied from at the given mip level.
        const utils::TextureDataCopyLayout srcDataCopyLayout =
            utils::GetTextureDataCopyLayoutForTextureAtLevel(
                format,
                {srcSpec.textureSize.width, srcSpec.textureSize.height,
                 srcDimension == wgpu::TextureDimension::e3D
                     ? srcSpec.textureSize.depthOrArrayLayers
                     : copySize.depthOrArrayLayers},
                srcSpec.copyLevel, srcDimension);

        // Initialize the source texture
        const std::vector<uint8_t> srcTextureCopyData =
            GetExpectedTextureData(format, srcDataCopyLayout);
        {
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(
                srcTexture, srcSpec.copyLevel, {0, 0, srcSpec.copyOrigin.z});
            wgpu::TexelCopyBufferLayout texelCopyBufferLayout = utils::CreateTexelCopyBufferLayout(
                0, srcDataCopyLayout.bytesPerRow, srcDataCopyLayout.rowsPerImage);
            this->queue.WriteTexture(&texelCopyTextureInfo, srcTextureCopyData.data(),
                                     srcDataCopyLayout.byteLength, &texelCopyBufferLayout,
                                     &srcDataCopyLayout.mipSize);
        }

        wgpu::CommandEncoder encoder = this->device.CreateCommandEncoder();

        // Perform the texture to texture copy
        wgpu::TexelCopyTextureInfo srcTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(srcTexture, srcSpec.copyLevel, srcSpec.copyOrigin);
        wgpu::TexelCopyTextureInfo dstTexelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(dstTexture, dstSpec.copyLevel, dstSpec.copyOrigin);
        // (Off-topic) spot-test for defaulting of .aspect.
        srcTexelCopyTextureInfo.aspect = wgpu::TextureAspect::Undefined;
        dstTexelCopyTextureInfo.aspect = wgpu::TextureAspect::Undefined;
        encoder.CopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &copySize);

        // Create an output buffer and use it to completely populate the subresources of the dst
        // texture that will be copied to at the given mip level.
        const utils::TextureDataCopyLayout dstDataCopyLayout =
            utils::GetTextureDataCopyLayoutForTextureAtLevel(
                format,
                {dstSpec.textureSize.width, dstSpec.textureSize.height,
                 dstDimension == wgpu::TextureDimension::e3D
                     ? dstSpec.textureSize.depthOrArrayLayers
                     : copySize.depthOrArrayLayers},
                dstSpec.copyLevel, dstDimension);
        wgpu::BufferDescriptor outputBufferDescriptor;
        outputBufferDescriptor.size = dstDataCopyLayout.byteLength;
        outputBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
        wgpu::Buffer outputBuffer = this->device.CreateBuffer(&outputBufferDescriptor);
        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(format);
        const uint32_t expectedDstDataOffset = dstSpec.copyOrigin.x * bytesPerTexel +
                                               dstSpec.copyOrigin.y * dstDataCopyLayout.bytesPerRow;
        wgpu::TexelCopyBufferInfo outputTexelCopyBufferInfo = utils::CreateTexelCopyBufferInfo(
            outputBuffer, expectedDstDataOffset, dstDataCopyLayout.bytesPerRow,
            dstDataCopyLayout.rowsPerImage);
        encoder.CopyTextureToBuffer(&dstTexelCopyTextureInfo, &outputTexelCopyBufferInfo,
                                    &copySize);

        wgpu::CommandBuffer commands = encoder.Finish();
        this->queue.Submit(1, &commands);

        // Validate if the data in outputBuffer is what we expected, including the untouched data
        // outside of the copy.
        {
            // Validate the output buffer slice-by-slice regardless of whether the destination
            // texture is 3D or 2D. The dimension here doesn't matter - we're only populating the
            // CPU data to verify against.
            uint32_t copyLayer = copySize.depthOrArrayLayers;
            uint32_t copyDepth = 1;

            const uint64_t validDataSizePerDstTextureLayer = utils::RequiredBytesInCopy(
                dstDataCopyLayout.bytesPerRow, dstDataCopyLayout.mipSize.height,
                dstDataCopyLayout.mipSize.width, dstDataCopyLayout.mipSize.height, copyDepth,
                bytesPerTexel);

            // expectedDstDataPerSlice stores one layer of the destination texture.
            std::vector<uint8_t> expectedDstDataPerSlice(validDataSizePerDstTextureLayer);
            for (uint32_t slice = 0; slice < copyLayer; ++slice) {
                // For each source texture array slice involved in the copy, emulate the T2T copy
                // on the CPU side by "copying" the copy data from the "source texture"
                // (srcTextureCopyData) to the "destination texture" (expectedDstDataPerSlice).
                std::fill(expectedDstDataPerSlice.begin(), expectedDstDataPerSlice.end(), 0);

                const uint32_t srcBytesOffset = srcDataCopyLayout.bytesPerImage * slice;

                // Get the offset of the srcTextureCopyData that contains the copy data on the
                // slice-th texture array layer of the source texture.
                const uint32_t srcTexelDataOffset =
                    srcBytesOffset + (srcSpec.copyOrigin.x * bytesPerTexel +
                                      srcSpec.copyOrigin.y * srcDataCopyLayout.bytesPerRow);
                // Do the T2T "copy" on the CPU side to get the expected texel value at the
                CopyTextureData(bytesPerTexel, &srcTextureCopyData[srcTexelDataOffset],
                                copySize.width, copySize.height, copyDepth,
                                srcDataCopyLayout.bytesPerRow, srcDataCopyLayout.rowsPerImage,
                                &expectedDstDataPerSlice[expectedDstDataOffset],
                                dstDataCopyLayout.bytesPerRow, dstDataCopyLayout.rowsPerImage);

                // Compare the content of the destination texture at the (dstSpec.copyOrigin.z +
                // slice)-th layer to its expected data after the copy (the outputBuffer contains
                // the data of the destination texture since the dstSpec.copyOrigin.z-th layer).
                uint64_t outputBufferExpectationBytesOffset =
                    dstDataCopyLayout.bytesPerImage * slice;
                EXPECT_BUFFER_U32_RANGE_EQ(
                    reinterpret_cast<const uint32_t*>(expectedDstDataPerSlice.data()), outputBuffer,
                    outputBufferExpectationBytesOffset,
                    validDataSizePerDstTextureLayer / sizeof(uint32_t));
            }
        }
    }

    void DoTest(const TextureSpec& srcSpec,
                const TextureSpec& dstSpec,
                const wgpu::Extent3D& copySize,
                bool copyWithinSameTexture = false,
                wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) {
        DoTest(srcSpec, dstSpec, copySize, dimension, dimension, copyWithinSameTexture);
    }
};

class CopyTests_T2T : public CopyTests_T2TBase<DawnTestWithParams<CopyTextureFormatParams>> {
  protected:
    struct TextureSpec : CopyTests::TextureSpec {
        TextureSpec() { format = GetParam().mTextureFormat; }
    };

    void SetUp() override { DawnTestWithParams<CopyTextureFormatParams>::SetUp(); }
};

class CopyTests_T2T_Srgb : public CopyTests_T2TBase<DawnTestWithParams<CopyTextureFormatParams>> {
  protected:
    void SetUp() override {
        DawnTestWithParams<CopyTextureFormatParams>::SetUp();

        const auto format = GetParam().mTextureFormat;
        // BGRA8UnormSrgb is unsupported in Compatibility mode.
        DAWN_SUPPRESS_TEST_IF((format == wgpu::TextureFormat::BGRA8UnormSrgb ||
                               format == wgpu::TextureFormat::BGRA8Unorm) &&
                              IsCompatibilityMode());
    }

    // Texture format is compatible and could be copied to each other if the only diff is srgb-ness.
    wgpu::TextureFormat GetCopyCompatibleFormat(wgpu::TextureFormat format) {
        switch (format) {
            case wgpu::TextureFormat::RGBA8Unorm:
                return wgpu::TextureFormat::RGBA8UnormSrgb;
            case wgpu::TextureFormat::RGBA8UnormSrgb:
                return wgpu::TextureFormat::RGBA8Unorm;
            case wgpu::TextureFormat::BGRA8Unorm:
                return wgpu::TextureFormat::BGRA8UnormSrgb;
            case wgpu::TextureFormat::BGRA8UnormSrgb:
                return wgpu::TextureFormat::BGRA8Unorm;
            default:
                DAWN_UNREACHABLE();
        }
    }

    void DoTest(TextureSpec srcSpec,
                TextureSpec dstSpec,
                const wgpu::Extent3D& copySize,
                wgpu::TextureDimension srcDimension = wgpu::TextureDimension::e2D,
                wgpu::TextureDimension dstDimension = wgpu::TextureDimension::e2D) {
        srcSpec.format = GetParam().mTextureFormat;
        dstSpec.format = GetCopyCompatibleFormat(srcSpec.format);

        CopyTests_T2TBase<DawnTestWithParams<CopyTextureFormatParams>>::DoTest(
            srcSpec, dstSpec, copySize, srcDimension, dstDimension);
    }
};

class CopyTests_B2B : public DawnTest {
  protected:
    // This is the same signature as CopyBufferToBuffer except that the buffers are replaced by
    // only their size.
    void DoTest(uint64_t sourceSize,
                uint64_t sourceOffset,
                uint64_t destinationSize,
                uint64_t destinationOffset,
                uint64_t copySize) {
        DAWN_ASSERT(sourceSize % 4 == 0);
        DAWN_ASSERT(destinationSize % 4 == 0);

        // Create our two test buffers, destination filled with zeros, source filled with non-zeroes
        std::vector<uint32_t> zeroes(static_cast<size_t>(destinationSize / sizeof(uint32_t)));
        wgpu::Buffer destination =
            utils::CreateBufferFromData(device, zeroes.data(), zeroes.size() * sizeof(uint32_t),
                                        wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);

        std::vector<uint32_t> sourceData(static_cast<size_t>(sourceSize / sizeof(uint32_t)));
        for (size_t i = 0; i < sourceData.size(); i++) {
            sourceData[i] = i + 1;
        }
        wgpu::Buffer source = utils::CreateBufferFromData(device, sourceData.data(),
                                                          sourceData.size() * sizeof(uint32_t),
                                                          wgpu::BufferUsage::CopySrc);

        // Submit the copy
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.CopyBufferToBuffer(source, sourceOffset, destination, destinationOffset, copySize);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);

        // Check destination is exactly the expected content.
        EXPECT_BUFFER_U32_RANGE_EQ(zeroes.data(), destination, 0,
                                   destinationOffset / sizeof(uint32_t));
        EXPECT_BUFFER_U32_RANGE_EQ(sourceData.data() + sourceOffset / sizeof(uint32_t), destination,
                                   destinationOffset, copySize / sizeof(uint32_t));
        uint64_t copyEnd = destinationOffset + copySize;
        EXPECT_BUFFER_U32_RANGE_EQ(zeroes.data(), destination, copyEnd,
                                   (destinationSize - copyEnd) / sizeof(uint32_t));
    }
};

class ClearBufferTests : public DawnTest {
  protected:
    // This is the same signature as ClearBuffer except that the buffers are replaced by
    // only their size.
    void DoTest(uint64_t bufferSize, uint64_t clearOffset, uint64_t clearSize) {
        DAWN_ASSERT(bufferSize % 4 == 0);
        DAWN_ASSERT(clearSize % 4 == 0);

        // Create our test buffer, filled with non-zeroes
        std::vector<uint32_t> bufferData(static_cast<size_t>(bufferSize / sizeof(uint32_t)));
        for (size_t i = 0; i < bufferData.size(); i++) {
            bufferData[i] = i + 1;
        }
        wgpu::Buffer buffer = utils::CreateBufferFromData(
            device, bufferData.data(), bufferData.size() * sizeof(uint32_t),
            wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);

        std::vector<uint8_t> fillData(static_cast<size_t>(clearSize), 0u);

        // Submit the fill
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        encoder.ClearBuffer(buffer, clearOffset, clearSize);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);

        // Check destination is exactly the expected content.
        EXPECT_BUFFER_U32_RANGE_EQ(bufferData.data(), buffer, 0, clearOffset / sizeof(uint32_t));
        EXPECT_BUFFER_U8_RANGE_EQ(fillData.data(), buffer, clearOffset, clearSize);
        uint64_t clearEnd = clearOffset + clearSize;
        EXPECT_BUFFER_U32_RANGE_EQ(bufferData.data() + clearEnd / sizeof(uint32_t), buffer,
                                   clearEnd, (bufferSize - clearEnd) / sizeof(uint32_t));
    }
};

// Test that copying an entire texture with 256-byte aligned dimensions works
TEST_P(CopyTests_T2B, FullTextureAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
}

// Test noop copies
TEST_P(CopyTests_T2B, ZeroSizedCopy) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {0, kHeight, 1});
    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, 0, 1});
    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 0});
}

// Test that copying an entire texture without 256-byte aligned dimensions works
TEST_P(CopyTests_T2B, FullTextureUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
}

// Test that reading pixels from a 256-byte aligned texture works
TEST_P(CopyTests_T2B, PixelReadAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);

    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = kTextureSize;

    {
        TextureSpec textureSpec = defaultTextureSpec;
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {0, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }
}

// Test that copying pixels from a texture that is not 256-byte aligned works
TEST_P(CopyTests_T2B, PixelReadUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;
    BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);

    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = kTextureSize;

    {
        TextureSpec textureSpec = defaultTextureSpec;
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {0, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }
}

// Test that copying regions with 256-byte aligned sizes works
TEST_P(CopyTests_T2B, TextureRegionAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    for (unsigned int w : {64, 128, 256}) {
        for (unsigned int h : {16, 32, 48}) {
            TextureSpec textureSpec;
            textureSpec.textureSize = {kWidth, kHeight, 1};
            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
        }
    }
}

// Test that copying regions without 256-byte aligned sizes works
TEST_P(CopyTests_T2B, TextureRegionUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int w : {13, 63, 65}) {
        for (unsigned int h : {17, 19, 63}) {
            TextureSpec textureSpec = defaultTextureSpec;
            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
        }
    }
}

// Test that copying mips with 256-byte aligned sizes works
TEST_P(CopyTests_T2B, TextureMipAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;
        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
               {kWidth >> i, kHeight >> i, 1});
    }
}

// Test that copying mips when one dimension is 256-byte aligned and another dimension reach one
// works
TEST_P(CopyTests_T2B, TextureMipDimensionReachOne) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t mipLevelCount = 4;
    constexpr uint32_t kWidth = 256 << mipLevelCount;
    constexpr uint32_t kHeight = 2;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    TextureSpec textureSpec = defaultTextureSpec;
    textureSpec.levelCount = mipLevelCount;

    for (unsigned int i = 0; i < 4; ++i) {
        textureSpec.copyLevel = i;
        DoTest(textureSpec,
               MinimumBufferSpec(std::max(kWidth >> i, 1u), std::max(kHeight >> i, 1u)),
               {std::max(kWidth >> i, 1u), std::max(kHeight >> i, 1u), 1});
    }
}

// Test that copying mips without 256-byte aligned sizes works
TEST_P(CopyTests_T2B, TextureMipUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(dawn:1880): suppress failing on Windows Intel Vulkan backend with
    // blit path toggles on. These toggles are only turned on for this
    // backend in the test so the defect won't impact the production code directly. But something is
    // wrong with the specific hardware.
    DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("use_blit_for_snorm_texture_to_buffer_copy") &&
                          IsSnorm(GetParam().mTextureFormat) && IsVulkan() && IsIntel() &&
                          IsWindows());
    DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("use_blit_for_bgra8unorm_texture_to_buffer_copy") &&
                          (GetParam().mTextureFormat == wgpu::TextureFormat::BGRA8Unorm) &&
                          IsVulkan() && IsIntel() && IsWindows());
    DAWN_SUPPRESS_TEST_IF(HasToggleEnabled("use_blit_for_rgb9e5ufloat_texture_copy") &&
                          (GetParam().mTextureFormat == wgpu::TextureFormat::RGB9E5Ufloat) &&
                          IsVulkan() && IsIntel() && IsWindows());

    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;
        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
               {kWidth >> i, kHeight >> i, 1});
    }
}

// Test that copying with a 512-byte aligned buffer offset works
TEST_P(CopyTests_T2B, OffsetBufferAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm() &&
                          GetParam().mTextureFormat == wgpu::TextureFormat::R16Float);

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 0; i < 3; ++i) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        uint64_t offset = 512 * i;
        bufferSpec.size += offset;
        bufferSpec.offset += offset;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying without a 512-byte aligned buffer offset works
TEST_P(CopyTests_T2B, OffsetBufferUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 128;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    for (uint32_t i = bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        bufferSpec.size += i;
        bufferSpec.offset += i;
        bufferSpec.size = Align(bufferSpec.size, 4);
        bufferSpec.offset = Align(bufferSpec.offset, 4);
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying without a 512-byte aligned buffer offset works. Note: the buffer is mappable.
TEST_P(CopyTests_T2B, MappableBufferWithOffsetUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr wgpu::Extent2D kSizes[] = {
        {4, 4},
        {17, 17},
        {128, 128},
        {256, 256},
    };

    for (const auto size : kSizes) {
        TextureSpec textureSpec;
        textureSpec.textureSize = {size.width, size.height, 1};

        const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
        for (uint32_t i = bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
            BufferSpec bufferSpec = MinimumBufferSpec(size.width, size.height);
            bufferSpec.size += i;
            bufferSpec.offset += i;
            bufferSpec.size = Align(bufferSpec.size, 4);
            bufferSpec.offset = Align(bufferSpec.offset, 4);
            DoTest(textureSpec, bufferSpec, textureSpec.textureSize, wgpu::TextureDimension::e2D,
                   wgpu::TextureViewDimension::Undefined, /*useMappableBuffer=*/true);
        }
    }
}

// Test that copying from a texture to a mappable buffer won't overwrite the buffer's bytes
// before and after the copied region.
TEST_P(CopyTests_T2B, MappableBufferBeforeAndAfterBytesNotOverwritten) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr uint32_t kWidth = 256;

    wgpu::TextureDescriptor texDesc;
    texDesc.dimension = wgpu::TextureDimension::e2D;
    texDesc.size = {kWidth, 1, 1};
    texDesc.sampleCount = 1;
    texDesc.format = GetParam().mTextureFormat;
    texDesc.mipLevelCount = 1;
    texDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
    wgpu::Texture texture = device.CreateTexture(&texDesc);

    // Layout for initial data upload to texture.
    // Some parts of this result are also reused later.
    const utils::TextureDataCopyLayout copyLayout =
        utils::GetTextureDataCopyLayoutForTextureAtLevel(texDesc.format, texDesc.size, 0,
                                                         wgpu::TextureDimension::e2D);

    const std::vector<uint8_t> textureArrayData =
        GetExpectedTextureData(texDesc.format, copyLayout);
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(texDesc.format);
    {
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
        wgpu::TexelCopyBufferLayout texelCopyBufferLayout =
            utils::CreateTexelCopyBufferLayout(0, copyLayout.bytesPerRow, copyLayout.rowsPerImage);
        queue.WriteTexture(&texelCopyTextureInfo, textureArrayData.data(), copyLayout.byteLength,
                           &texelCopyBufferLayout, &copyLayout.mipSize);
    }

    // Create a destination buffer and fill its before & after bytes with random data
    const uint32_t kCopyOffset = bytesPerTexel;
    const auto kPastCopyOffset = kCopyOffset + textureArrayData.size();
    wgpu::BufferDescriptor bufferDesc;
    bufferDesc.size = Align(kPastCopyOffset + 16, 4);
    bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
    bufferDesc.mappedAtCreation = true;
    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);

    const auto kNumPastCopyBytes = bufferDesc.size - kPastCopyOffset;
    const std::vector<uint8_t> kExpectedFirstBytes(kCopyOffset, 97);
    const std::vector<uint8_t> kExpectedLastBytes(kNumPastCopyBytes, 99);
    {
        auto ptr = static_cast<uint8_t*>(buffer.GetMappedRange());
        memcpy(ptr, kExpectedFirstBytes.data(), kExpectedFirstBytes.size());
        memcpy(ptr + kPastCopyOffset, kExpectedLastBytes.data(), kExpectedLastBytes.size());
        buffer.Unmap();
    }

    // Copy the texture to buffer at offset=bytesPerTexel and with tightly packed rows.
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
        wgpu::TexelCopyBufferInfo texelCopyBufferInfo = utils::CreateTexelCopyBufferInfo(
            buffer, /*offset=*/kCopyOffset,
            /*bytesPerRow=*/256 * bytesPerTexel, /*rowsPerImage=*/1);
        encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &texDesc.size);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    {
        bool done = false;
        buffer.MapAsync(wgpu::MapMode::Read, 0, buffer.GetSize(),
                        wgpu::CallbackMode::AllowProcessEvents,
                        [&](wgpu::MapAsyncStatus status, wgpu::StringView) {
                            ASSERT_EQ(wgpu::MapAsyncStatus::Success, status);
                            done = true;
                        });
        while (!done) {
            WaitABit();
        }
    }

    // Check copied bytes
    const auto* bufferReadPtr = static_cast<const uint8_t*>(buffer.GetConstMappedRange());
    for (size_t i = 0; i < textureArrayData.size(); ++i) {
        EXPECT_EQ(bufferReadPtr[kCopyOffset + i], textureArrayData[i])
            << "failed at [" << kCopyOffset + i << "]";
    }

    // Check that the first & last bytes outside copied region remain intact after the copy.
    for (size_t i = 0; i < kCopyOffset; ++i) {
        EXPECT_EQ(bufferReadPtr[i], kExpectedFirstBytes[i]) << "failed at [" << i << "]";
    }

    for (size_t i = 0; i < kNumPastCopyBytes; ++i) {
        const size_t idx = kPastCopyOffset + i;
        EXPECT_EQ(bufferReadPtr[idx], kExpectedLastBytes[i]) << "failed at [" << idx << "]";
    }

    buffer.Unmap();
}

// Test that copying without a 512-byte aligned buffer offset that is greater than the bytes per row
// works
TEST_P(CopyTests_T2B, OffsetBufferUnalignedSmallBytesPerRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    for (uint32_t i = 256 + bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        bufferSpec.size += i;
        bufferSpec.offset += i;
        bufferSpec.size = Align(bufferSpec.size, 4);
        bufferSpec.offset = Align(bufferSpec.offset, 4);
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with a greater bytes per row than needed on a 256-byte aligned texture works
TEST_P(CopyTests_T2B, BytesPerRowAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
    for (unsigned int i = 1; i < 4; ++i) {
        bufferSpec.bytesPerRow += 256;
        bufferSpec.size += 256 * kHeight;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with a greater bytes per row than needed on a texture that is not 256-byte
// aligned works
TEST_P(CopyTests_T2B, BytesPerRowUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
    for (unsigned int i = 1; i < 4; ++i) {
        bufferSpec.bytesPerRow += 256;
        bufferSpec.size += 256 * kHeight;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with bytesPerRow = 0 and bytesPerRow < bytesInACompleteRow works
// when we're copying one row only
TEST_P(CopyTests_T2B, BytesPerRowWithOneRowCopy) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    {
        BufferSpec bufferSpec = MinimumBufferSpec(5, 1);

        // bytesPerRow undefined
        bufferSpec.bytesPerRow = wgpu::kCopyStrideUndefined;
        DoTest(textureSpec, bufferSpec, {5, 1, 1});
    }
}

TEST_P(CopyTests_T2B, StrideSpecialCases) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    TextureSpec textureSpec;
    textureSpec.textureSize = {4, 4, 4};

    // bytesPerRow 0
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 0, 2), copyExtent);
    }

    // bytesPerRow undefined
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, wgpu::kCopyStrideUndefined, 2),
               copyExtent);
    }

    // rowsPerImage 0
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, 0), copyExtent);
    }

    // rowsPerImage undefined
    for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, wgpu::kCopyStrideUndefined),
               copyExtent);
    }
}

// Test copying a single slice with rowsPerImage larger than copy height and rowsPerImage will not
// take effect. If rowsPerImage takes effect, it looks like the copy may go past the end of the
// buffer.
TEST_P(CopyTests_T2B, RowsPerImageShouldNotCauseBufferOOBIfDepthOrArrayLayersIsOne) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr uint32_t kWidth = 250;
    constexpr uint32_t kHeight = 3;
    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);

    // Check various offsets to cover each code path in the 2D split code in TextureCopySplitter.
    for (uint32_t offset : {0u, Align(bytesPerTexel, 4u), Align(bytesPerTexel * 8u, 4u)}) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        bufferSpec.rowsPerImage = 2 * kHeight;
        bufferSpec.offset = offset;
        bufferSpec.size += offset;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1}, wgpu::TextureDimension::e3D);
    }
}

// Test copying a single row with bytesPerRow larger than copy width and bytesPerRow will not
// take effect. If bytesPerRow takes effect, it looks like the copy may go past the end of the
// buffer.
TEST_P(CopyTests_T2B, BytesPerRowShouldNotCauseBufferOOBIfCopyHeightIsOne) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 250;
    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, 1, 1};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);

    // Check various offsets to cover each code path in the 2D split code in TextureCopySplitter.
    for (uint32_t offset : {0u, Align(bytesPerTexel, 4u), Align(bytesPerTexel * 25u, 4u)}) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, 1);
        bufferSpec.bytesPerRow =
            Align(kWidth * bytesPerTexel + 99, 256);  // the default bytesPerRow is 1024.
        bufferSpec.offset = offset;
        bufferSpec.size += offset;
        DoTest(textureSpec, bufferSpec, {kWidth, 1, 1});
        DoTest(textureSpec, bufferSpec, {kWidth, 1, 1}, wgpu::TextureDimension::e3D);
    }
}

// Test copying rows with bytesPerRow extremely large (32k texels). This is to test a special case
// in Metal where we need to split the copy to be row-by-row in that case.
TEST_P(CopyTests_T2B, ReallyLargeBytesPerRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(https://issues.chromium.org/481934465): Fails on Intel D3D12 / Vulkan which don't have
    // the workaround.
    DAWN_SUPPRESS_TEST_IF(IsIntel() && (IsD3D12() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    TextureSpec textureSpec;
    textureSpec.textureSize = {2, 2, 2};

    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    const uint32_t bytesPerRow = 32 * 1024 * bytesPerTexel;
    BufferSpec bufferSpec = MinimumBufferSpec({2, 2, 2}, /*overrideBytesPerRow=*/bytesPerRow);

    // Check various offsets to cover each code path in the 2D split code in TextureCopySplitter.
    DoTest(textureSpec, bufferSpec, {2, 2, 2});
}

// Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B, Texture2DArrayFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
}

// Test that copying a range of texture 2D array layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B, Texture2DArraySubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 3u;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyLayers),
           {kWidth, kHeight, kCopyLayers});
}

// Test that copying texture 2D array mips with 256-byte aligned sizes works
TEST_P(CopyTests_T2B, Texture2DArrayMip) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kLayers),
               {kWidth >> i, kHeight >> i, kLayers});
    }
}

// Test that copying from a range of texture 2D array layers in one texture-to-buffer-copy when
// RowsPerImage is not equal to the height of the texture works.
TEST_P(CopyTests_T2B, Texture2DArrayRegionNonzeroRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 3u;

    constexpr uint32_t kRowsPerImage = kHeight * 2;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is not a
// multiple of 512.
TEST_P(CopyTests_T2B, Texture2DArrayRegionWithOffsetOddRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 64;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 8u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 5u;

    constexpr uint32_t kRowsPerImage = kHeight + 1;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.offset += 128u;
    bufferSpec.size += 128u;
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is a multiple
// of 512.
TEST_P(CopyTests_T2B, Texture2DArrayRegionWithOffsetEvenRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));

    // TODO(crbug.com/500445353): Fails to copy region on Win/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD() && IsD3D11());

    constexpr uint32_t kWidth = 64;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 8u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 4u;

    constexpr uint32_t kRowsPerImage = kHeight + 2;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.offset += 128u;
    bufferSpec.size += 128u;
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Testing a series of params for 1D texture texture-to-buffer-copy.
TEST_P(CopyTests_T2B, Texture1D) {
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(dawn:1705): support 1d texture.
    DAWN_SUPPRESS_TEST_IF(IsD3D11());

    // TODO(crbug.com/dawn/2408): Re-enable on this configuration.
    DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsNvidia());

    struct Param {
        uint32_t textureWidth;
        uint32_t copyWidth;
        uint32_t copyOrigin;
    };
    constexpr Param params[] = {
        {256, 256, 0}, {256, 16, 128}, {256, 16, 0},  {8, 8, 0},
        {8, 1, 7},     {259, 259, 0},  {259, 255, 3},
    };

    constexpr uint32_t bufferOffsets[] = {0, 16, 256};

    for (const auto& p : params) {
        for (const uint32_t offset : bufferOffsets) {
            TextureSpec textureSpec;
            textureSpec.copyOrigin = {p.copyOrigin, 0, 0};
            textureSpec.textureSize = {p.textureWidth, 1, 1};

            BufferSpec bufferSpec = MinimumBufferSpec(p.copyWidth, 1, 1);
            bufferSpec.offset = offset;
            bufferSpec.size += offset;

            DoTest(textureSpec, bufferSpec, {p.copyWidth, 1, 1}, wgpu::TextureDimension::e1D);
        }
    }
}

// Test that copying whole 3D texture in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B, Texture3DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth},
           wgpu::TextureDimension::e3D);
}

// Test that copying a range of texture 3D depths in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B, Texture3DSubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6;
    constexpr uint32_t kBaseDepth = 2u;
    constexpr uint32_t kCopyDepth = 3u;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseDepth};
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyDepth),
           {kWidth / 2, kHeight / 2, kCopyDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_T2B, Texture3DNoSplitRowDataWithEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 2;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // Base: no split for a row + no empty first row
    bufferSpec.offset = Align(60u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: no split for a row + empty first row
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_T2B, Texture3DSplitRowDataWithoutEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The test below is designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + no empty first row for both split regions
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_T2B, Texture3DSplitRowDataWithEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 39;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + empty first row for the head block
    bufferSpec.offset = 400;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: split for a row + empty first row for the tail block
    bufferSpec.offset = 160;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_T2B, Texture3DCopyHeightIsOneCopyWidthIsTiny) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 2;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // Base: no split for a row, no empty row, and copy height is 1
    bufferSpec.offset = Align(60u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: no split for a row + empty first row, and copy height is 1
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_T2B, Texture3DCopyHeightIsOneCopyWidthIsSmall) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr uint32_t kWidth = 39;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + empty first row for the head block, and copy height
    // is 1
    bufferSpec.offset = 400;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: split for a row + empty first row for the tail block, and copy height
    // is 1
    bufferSpec.offset = 160;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

// Test that copying texture 3D array mips with 256-byte aligned sizes works
TEST_P(CopyTests_T2B, Texture3DMipAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 64u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kDepth >> i),
               {kWidth >> i, kHeight >> i, kDepth >> i}, wgpu::TextureDimension::e3D);
    }
}

// Test that copying texture 3D array mips with 256-byte unaligned sizes works
TEST_P(CopyTests_T2B, Texture3DMipUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10 gles and vulkan.
    DAWN_SUPPRESS_TEST_IF(IsImgTec() && (IsOpenGLES() || IsVulkan()));
    constexpr uint32_t kWidth = 261;
    constexpr uint32_t kHeight = 123;
    constexpr uint32_t kDepth = 69u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kDepth >> i),
               {kWidth >> i, kHeight >> i, kDepth >> i}, wgpu::TextureDimension::e3D);
    }
}

DAWN_INSTANTIATE_TEST_P(CopyTests_T2B,
                        {D3D11Backend(), D3D11Backend({"d3d11_disable_map_on_default_buffers"}),
                         D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(),
                         OpenGLESBackend({"gl_defer"}), VulkanBackend(),
                         VulkanBackend({"use_blit_for_snorm_texture_to_buffer_copy",
                                        "use_blit_for_bgra8unorm_texture_to_buffer_copy"}),
                         WebGPUBackend()},
                        {
                            wgpu::TextureFormat::R8Unorm,
                            wgpu::TextureFormat::RG8Unorm,
                            wgpu::TextureFormat::RGBA8Unorm,

                            wgpu::TextureFormat::R8Uint,
                            wgpu::TextureFormat::R8Sint,

                            wgpu::TextureFormat::R16Uint,
                            wgpu::TextureFormat::R16Sint,
                            wgpu::TextureFormat::R16Float,

                            wgpu::TextureFormat::RG16Uint,
                            wgpu::TextureFormat::RG16Sint,
                            wgpu::TextureFormat::RG16Float,

                            wgpu::TextureFormat::R32Uint,
                            wgpu::TextureFormat::R32Sint,
                            wgpu::TextureFormat::R32Float,

                            wgpu::TextureFormat::RG32Float,
                            wgpu::TextureFormat::RG32Uint,
                            wgpu::TextureFormat::RG32Sint,

                            wgpu::TextureFormat::RGBA16Uint,
                            wgpu::TextureFormat::RGBA16Sint,
                            wgpu::TextureFormat::RGBA16Float,

                            wgpu::TextureFormat::RGBA32Float,

                            wgpu::TextureFormat::RGB10A2Unorm,
                            wgpu::TextureFormat::RG11B10Ufloat,

                            // Testing OpenGL compat Toggle::UseBlitForRGB9E5UfloatTextureCopy
                            wgpu::TextureFormat::RGB9E5Ufloat,

                            // Testing OpenGL compat Toggle::UseBlitForSnormTextureToBufferCopy
                            wgpu::TextureFormat::R8Snorm,
                            wgpu::TextureFormat::RG8Snorm,
                            wgpu::TextureFormat::RGBA8Snorm,

                            // Testing OpenGL compat Toggle::UseBlitForBGRA8UnormTextureToBufferCopy
                            wgpu::TextureFormat::BGRA8Unorm,
                        });

class CopyTests_T2B_No_Format_Param : public CopyTests, public DawnTest {};

// A regression test for a bug on D3D12 backend that causes crash when doing texture-to-texture
// copy one row with the texture format Depth32Float.
TEST_P(CopyTests_T2B_No_Format_Param, CopyOneRowWithDepth32Float) {
    // TODO(42242119): fail on Qualcomm Adreno X1.
    DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm());

    constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::Depth32Float;
    constexpr uint32_t kPixelsPerRow = 4u;

    wgpu::TextureDescriptor textureDescriptor;
    textureDescriptor.format = kFormat;
    textureDescriptor.size = {kPixelsPerRow, 1, 1};
    textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

    // Initialize the depth texture with 0.5f.
    constexpr float kClearDepthValue = 0.5f;
    utils::ComboRenderPassDescriptor renderPass({}, texture.CreateView());
    renderPass.UnsetDepthStencilLoadStoreOpsForFormat(kFormat);
    renderPass.cDepthStencilAttachmentInfo.depthClearValue = kClearDepthValue;
    renderPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
    renderPass.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
    wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPass);
    renderPassEncoder.End();

    constexpr uint32_t kBufferCopyOffset = kTextureBytesPerRowAlignment;
    const uint32_t kBufferSize =
        kBufferCopyOffset + utils::GetTexelBlockSizeInBytes(kFormat) * kPixelsPerRow;
    wgpu::BufferDescriptor bufferDescriptor;
    bufferDescriptor.size = kBufferSize;
    bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
    wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);

    wgpu::TexelCopyBufferInfo texelCopyBufferInfo =
        utils::CreateTexelCopyBufferInfo(buffer, kBufferCopyOffset, kTextureBytesPerRowAlignment);
    wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
        utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
    // (Off-topic) spot-test for defaulting of .aspect.
    texelCopyTextureInfo.aspect = wgpu::TextureAspect::Undefined;

    wgpu::Extent3D copySize = textureDescriptor.size;
    encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &copySize);
    wgpu::CommandBuffer commandBuffer = encoder.Finish();
    queue.Submit(1, &commandBuffer);

    std::array<float, kPixelsPerRow> expectedValues;
    std::fill(expectedValues.begin(), expectedValues.end(), kClearDepthValue);
    EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedValues.data(), buffer, kBufferCopyOffset, kPixelsPerRow);
}

DAWN_INSTANTIATE_TEST(CopyTests_T2B_No_Format_Param,
                      D3D11Backend(),
                      D3D11Backend({"d3d11_disable_map_on_default_buffers"}),
                      D3D12Backend(),
                      MetalBackend(),
                      OpenGLBackend(),
                      OpenGLESBackend(),
                      OpenGLESBackend({"gl_defer"}),
                      VulkanBackend(),
                      VulkanBackend({"use_blit_for_depth32float_texture_to_buffer_copy"}),
                      WebGPUBackend());

class CopyTests_T2B_Compat : public CopyTests_T2B {
  protected:
    void SetUp() override {
        CopyTests_T2B::SetUp();
        DAWN_TEST_UNSUPPORTED_IF(!IsCompatibilityMode());
        DAWN_SUPPRESS_TEST_IF(IsANGLESwiftShader());
    }
};

// Test that copying 2d texture array with binding view dimension set to cube.
TEST_P(CopyTests_T2B_Compat, TextureCubeFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers},
           wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::Cube);
}

// Test that copying a range of cube texture layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B_Compat, TextureCubeSubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;
    constexpr uint32_t kBaseLayer = 2;
    constexpr uint32_t kCopyLayers = 3;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyLayers),
           {kWidth, kHeight, kCopyLayers}, wgpu::TextureDimension::e2D,
           wgpu::TextureViewDimension::Cube);
}

// Test that copying texture 2D array mips with 256-byte aligned sizes works
TEST_P(CopyTests_T2B_Compat, TextureCubeMip) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kLayers),
               {kWidth >> i, kHeight >> i, kLayers}, wgpu::TextureDimension::e2D,
               wgpu::TextureViewDimension::Cube);
    }
}

// Test that copying from a range of texture 2D array layers in one texture-to-buffer-copy when
// RowsPerImage is not equal to the height of the texture works.
TEST_P(CopyTests_T2B_Compat, TextureCubeRegionNonzeroRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;
    constexpr uint32_t kBaseLayer = 2;
    constexpr uint32_t kCopyLayers = 3;

    constexpr uint32_t kRowsPerImage = kHeight * 2;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers}, wgpu::TextureDimension::e2D,
           wgpu::TextureViewDimension::Cube);
}

DAWN_INSTANTIATE_TEST_P(CopyTests_T2B_Compat,
                        {
                            D3D11Backend(),
                            D3D11Backend({"d3d11_disable_map_on_default_buffers"}),
                            OpenGLBackend(),
                            OpenGLESBackend(),
                            OpenGLESBackend({"gl_defer"}),
                        },
                        {
                            // Control case: format not using blit workaround
                            wgpu::TextureFormat::RGBA8Unorm,

                            // Testing OpenGL compat Toggle::UseBlitForSnormTextureToBufferCopy
                            wgpu::TextureFormat::R8Snorm,
                            wgpu::TextureFormat::RG8Snorm,
                            wgpu::TextureFormat::RGBA8Snorm,

                            // Testing OpenGL compat Toggle::UseBlitForBGRA8UnormTextureToBufferCopy
                            wgpu::TextureFormat::BGRA8Unorm,

                            // Testing OpenGL compat Toggle::UseBlitForRGB9E5UfloatTextureCopy
                            wgpu::TextureFormat::RGB9E5Ufloat,
                        });

// Test that copying an entire texture with 256-byte aligned dimensions works
TEST_P(CopyTests_B2T, FullTextureAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
}

// Test noop copies.
TEST_P(CopyTests_B2T, ZeroSizedCopy) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {0, kHeight, 1});
    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, 0, 1});
    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 0});
}

// Test that copying an entire texture without 256-byte aligned dimensions works
TEST_P(CopyTests_B2T, FullTextureUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight), {kWidth, kHeight, 1});
}

// Test that reading pixels from a 256-byte aligned texture works
TEST_P(CopyTests_B2T, PixelReadAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);

    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = kTextureSize;

    {
        TextureSpec textureSpec = defaultTextureSpec;
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {0, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }
}

// Test that copying pixels from a texture that is not 256-byte aligned works
TEST_P(CopyTests_B2T, PixelReadUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;
    BufferSpec pixelBuffer = MinimumBufferSpec(1, 1);

    constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
    constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = kTextureSize;

    {
        TextureSpec textureSpec = defaultTextureSpec;
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, 0, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {0, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth - 1, kHeight - 1, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 3, kHeight / 7, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }

    {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyOrigin = {kWidth / 7, kHeight / 3, 0};
        DoTest(textureSpec, pixelBuffer, kCopySize);
    }
}

// Test that copying regions with 256-byte aligned sizes works
TEST_P(CopyTests_B2T, TextureRegionAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    for (unsigned int w : {64, 128, 256}) {
        for (unsigned int h : {16, 32, 48}) {
            TextureSpec textureSpec;
            textureSpec.textureSize = {kWidth, kHeight, 1};
            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
        }
    }
}

// Test that copying regions without 256-byte aligned sizes works
TEST_P(CopyTests_B2T, TextureRegionUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int w : {13, 63, 65}) {
        for (unsigned int h : {17, 19, 63}) {
            TextureSpec textureSpec = defaultTextureSpec;
            DoTest(textureSpec, MinimumBufferSpec(w, h), {w, h, 1});
        }
    }
}

// Test that copying mips with 256-byte aligned sizes works
TEST_P(CopyTests_B2T, TextureMipAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;
        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
               {kWidth >> i, kHeight >> i, 1});
    }
}

// Test that copying mips without 256-byte aligned sizes works
TEST_P(CopyTests_B2T, TextureMipUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;
        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i),
               {kWidth >> i, kHeight >> i, 1});
    }
}

// Test that copying with a 512-byte aligned buffer offset works
TEST_P(CopyTests_B2T, OffsetBufferAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 0; i < 3; ++i) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        uint64_t offset = 512 * i;
        bufferSpec.size += offset;
        bufferSpec.offset += offset;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying without a 512-byte aligned buffer offset works
TEST_P(CopyTests_B2T, OffsetBufferUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/459848482): Flaky on Win/Snapdragon X Elite w/ D3D11 and
    // backend validation.
    DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11() && IsBackendValidationEnabled());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    for (uint32_t i = bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        bufferSpec.size += i;
        bufferSpec.offset += i;
        bufferSpec.size = Align(bufferSpec.size, bytesPerTexel);
        bufferSpec.offset = Align(bufferSpec.offset, bytesPerTexel);
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying without a 512-byte aligned buffer offset that is greater than the bytes per row
// works
TEST_P(CopyTests_B2T, OffsetBufferUnalignedSmallBytesPerRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/459848482): Flaky on Win/Snapdragon X Elite w/ D3D11 and
    // backend validation.
    DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11() && IsBackendValidationEnabled());

    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    for (uint32_t i = 256 + bytesPerTexel; i < 512; i += bytesPerTexel * 9) {
        BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
        bufferSpec.size += i;
        bufferSpec.offset += i;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with a greater bytes per row than needed on a 256-byte aligned texture works
TEST_P(CopyTests_B2T, BytesPerRowAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
    for (unsigned int i = 1; i < 4; ++i) {
        bufferSpec.bytesPerRow += 256;
        bufferSpec.size += 256 * kHeight;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with a greater bytes per row than needed on a texture that is not 256-byte
// aligned works
TEST_P(CopyTests_B2T, BytesPerRowUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
    for (unsigned int i = 1; i < 4; ++i) {
        bufferSpec.bytesPerRow += 256;
        bufferSpec.size += 256 * kHeight;
        DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
    }
}

// Test that copying with bytesPerRow = 0 and bytesPerRow < bytesInACompleteRow works
// when we're copying one row only
TEST_P(CopyTests_B2T, BytesPerRowWithOneRowCopy) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};

    {
        BufferSpec bufferSpec = MinimumBufferSpec(5, 1);

        // bytesPerRow undefined
        bufferSpec.bytesPerRow = wgpu::kCopyStrideUndefined;
        DoTest(textureSpec, bufferSpec, {5, 1, 1});
    }
}

TEST_P(CopyTests_B2T, StrideSpecialCases) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    TextureSpec textureSpec;
    textureSpec.textureSize = {4, 4, 4};

    // bytesPerRow 0
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 0, 2), copyExtent);
    }

    // bytesPerRow undefined
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, wgpu::kCopyStrideUndefined, 2),
               copyExtent);
    }

    // rowsPerImage 0
    for (const wgpu::Extent3D copyExtent :
         {wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, 0), copyExtent);
    }

    // rowsPerImage undefined
    for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
        DoTest(textureSpec, MinimumBufferSpec(copyExtent, 256, wgpu::kCopyStrideUndefined),
               copyExtent);
    }
}

// Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_B2T, Texture2DArrayFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kLayers), {kWidth, kHeight, kLayers});
}

// Test that copying a range of texture 2D array layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_B2T, Texture2DArraySubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 3u;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyLayers),
           {kWidth, kHeight, kCopyLayers});
}

// Test that copying into a range of texture 2D array layers in one texture-to-buffer-copy when
// RowsPerImage is not equal to the height of the texture works.
TEST_P(CopyTests_B2T, Texture2DArrayRegionNonzeroRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 3u;

    constexpr uint32_t kRowsPerImage = kHeight * 2;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is not a
// multiple of 512.
TEST_P(CopyTests_B2T, Texture2DArrayRegionWithOffsetOddRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 64;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 8u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 5u;

    constexpr uint32_t kRowsPerImage = kHeight + 1;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.offset += 128u;
    bufferSpec.size += 128u;
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Test a special code path in the D3D12 backends when (BytesPerRow * RowsPerImage) is a multiple
// of 512.
TEST_P(CopyTests_B2T, Texture2DArrayRegionWithOffsetEvenRowsPerImage) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 64;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 8u;
    constexpr uint32_t kBaseLayer = 2u;
    constexpr uint32_t kCopyLayers = 5u;

    constexpr uint32_t kRowsPerImage = kHeight + 2;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseLayer};
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kRowsPerImage, kCopyLayers);
    bufferSpec.offset += 128u;
    bufferSpec.size += 128u;
    bufferSpec.rowsPerImage = kRowsPerImage;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kCopyLayers});
}

// Test that copying whole texture 3D in one buffer-to-texture-copy works.
TEST_P(CopyTests_B2T, Texture3DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth},
           wgpu::TextureDimension::e3D);
}

// Test that copying a range of texture 3D Depths in one texture-to-buffer-copy works.
TEST_P(CopyTests_B2T, Texture3DSubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6;
    constexpr uint32_t kBaseDepth = 2u;
    constexpr uint32_t kCopyDepth = 3u;

    TextureSpec textureSpec;
    textureSpec.copyOrigin = {0, 0, kBaseDepth};
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kCopyDepth),
           {kWidth / 2, kHeight / 2, kCopyDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_B2T, Texture3DNoSplitRowDataWithEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 2;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // Base: no split for a row + no empty first row
    bufferSpec.offset = Align(60u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: no split for a row + empty first row
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_B2T, Texture3DSplitRowDataWithoutEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 259;
    constexpr uint32_t kHeight = 127;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The test below is designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + no empty first row for both split regions
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_B2T, Texture3DSplitRowDataWithEmptyFirstRow) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 39;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + empty first row for the head block
    bufferSpec.offset = 400;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: split for a row + empty first row for the tail block
    bufferSpec.offset = 160;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_B2T, Texture3DCopyHeightIsOneCopyWidthIsTiny) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr uint32_t kWidth = 2;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(textureSpec.format);
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // Base: no split for a row, no empty row, and copy height is 1
    bufferSpec.offset = Align(60u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: no split for a row + empty first row, and copy height is 1
    bufferSpec.offset = Align(260u, bytesPerTexel);
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

TEST_P(CopyTests_B2T, Texture3DCopyHeightIsOneCopyWidthIsSmall) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 39;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 3;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight, kDepth);

    // The tests below are designed to test TextureCopySplitter for 3D textures on D3D12.
    // This test will cover: split for a row + empty first row for the head block, and copy height
    // is 1
    bufferSpec.offset = 400;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);

    // This test will cover: split for a row + empty first row for the tail block, and copy height
    // is 1
    bufferSpec.offset = 160;
    bufferSpec.size += bufferSpec.offset;
    DoTest(textureSpec, bufferSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D);
}

// Test that copying texture 3D array mips with 256-byte aligned sizes works
TEST_P(CopyTests_B2T, Texture3DMipAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 64u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kDepth >> i),
               {kWidth >> i, kHeight >> i, kDepth >> i}, wgpu::TextureDimension::e3D);
    }
}

// Test that copying texture 3D array mips with 256-byte unaligned sizes works
TEST_P(CopyTests_B2T, Texture3DMipUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 261;
    constexpr uint32_t kHeight = 123;
    constexpr uint32_t kDepth = 69u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, MinimumBufferSpec(kWidth >> i, kHeight >> i, kDepth >> i),
               {kWidth >> i, kHeight >> i, kDepth >> i}, wgpu::TextureDimension::e3D);
    }
}

// Test that copying a texture 1D works.
TEST_P(CopyTests_B2T, Texture1DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 1;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, MinimumBufferSpec(kWidth, kHeight, kDepth), {kWidth, kHeight, kDepth},
           wgpu::TextureDimension::e1D);
}

DAWN_INSTANTIATE_TEST_P(CopyTests_B2T,
                        {D3D11Backend(), D3D11Backend({"d3d11_disable_cpu_buffers"}),
                         D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(),
                         OpenGLESBackend({"gl_defer"}), VulkanBackend(), WebGPUBackend()},
                        {
                            wgpu::TextureFormat::R8Unorm,
                            wgpu::TextureFormat::RG8Unorm,
                            wgpu::TextureFormat::RGBA8Unorm,

                            wgpu::TextureFormat::RGB10A2Unorm,

                            wgpu::TextureFormat::R16Float,
                            wgpu::TextureFormat::R16Unorm,

                            wgpu::TextureFormat::RG16Float,
                            wgpu::TextureFormat::RG16Unorm,

                            wgpu::TextureFormat::R32Float,

                            wgpu::TextureFormat::RG32Float,

                            wgpu::TextureFormat::RGBA16Float,
                            wgpu::TextureFormat::RGBA16Unorm,

                            wgpu::TextureFormat::RGBA32Float,

                            wgpu::TextureFormat::BGRA8Unorm,
                        });

TEST_P(CopyTests_T2T, Texture) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};
    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
}

// Test noop copies.
TEST_P(CopyTests_T2T, ZeroSizedCopy) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};
    DoTest(textureSpec, textureSpec, {0, kHeight, 1});
    DoTest(textureSpec, textureSpec, {kWidth, 0, 1});
    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 0});
}

TEST_P(CopyTests_T2T, TextureRegion) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int w : {64, 128, 256}) {
        for (unsigned int h : {16, 32, 48}) {
            TextureSpec textureSpec = defaultTextureSpec;
            DoTest(textureSpec, textureSpec, {w, h, 1});
        }
    }
}

TEST_P(CopyTests_T2T, TextureMip) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, textureSpec, {kWidth >> i, kHeight >> i, 1});
    }
}

TEST_P(CopyTests_T2T, SingleMipSrcMultipleMipDst) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec srcTextureSpec = defaultTextureSpec;
        srcTextureSpec.textureSize = {kWidth >> i, kHeight >> i, 1};

        TextureSpec dstTextureSpec = defaultTextureSpec;
        dstTextureSpec.textureSize = {kWidth, kHeight, 1};
        dstTextureSpec.copyLevel = i;
        dstTextureSpec.levelCount = i + 1;

        DoTest(srcTextureSpec, dstTextureSpec, {kWidth >> i, kHeight >> i, 1});
    }
}

TEST_P(CopyTests_T2T, MultipleMipSrcSingleMipDst) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;

    for (unsigned int i = 1; i < 4; ++i) {
        TextureSpec srcTextureSpec = defaultTextureSpec;
        srcTextureSpec.textureSize = {kWidth, kHeight, 1};
        srcTextureSpec.copyLevel = i;
        srcTextureSpec.levelCount = i + 1;

        TextureSpec dstTextureSpec = defaultTextureSpec;
        dstTextureSpec.textureSize = {kWidth >> i, kHeight >> i, 1};

        DoTest(srcTextureSpec, dstTextureSpec, {kWidth >> i, kHeight >> i, 1});
    }
}

// Test that copying from one mip level to another mip level within the same 2D texture works.
TEST_P(CopyTests_T2T, Texture2DSameTextureDifferentMipLevels) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
    defaultTextureSpec.levelCount = 6;

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec srcSpec = defaultTextureSpec;
        srcSpec.copyLevel = i - 1;
        TextureSpec dstSpec = defaultTextureSpec;
        dstSpec.copyLevel = i;

        DoTest(srcSpec, dstSpec, {kWidth >> i, kHeight >> i, 1}, true);
    }
}

// Test copying the whole 2D array texture.
TEST_P(CopyTests_T2T, Texture2DArrayFull) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kLayers};

    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kLayers});
}

// Test copying a subresource region of the 2D array texture.
TEST_P(CopyTests_T2T, Texture2DArrayRegion) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (unsigned int w : {64, 128, 256}) {
        for (unsigned int h : {16, 32, 48}) {
            TextureSpec textureSpec = defaultTextureSpec;
            DoTest(textureSpec, textureSpec, {w, h, kLayers});
        }
    }
}

// Test copying one slice of a 2D array texture.
TEST_P(CopyTests_T2T, Texture2DArrayCopyOneSlice) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kSrcBaseLayer = 1u;
    constexpr uint32_t kDstBaseLayer = 3u;
    constexpr uint32_t kCopyArrayLayerCount = 1u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    TextureSpec srcTextureSpec = defaultTextureSpec;
    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};

    TextureSpec dstTextureSpec = defaultTextureSpec;
    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};

    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount});
}

// Test copying multiple contiguous slices of a 2D array texture.
TEST_P(CopyTests_T2T, Texture2DArrayCopyMultipleSlices) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kSrcBaseLayer = 0u;
    constexpr uint32_t kDstBaseLayer = 3u;
    constexpr uint32_t kCopyArrayLayerCount = 3u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    TextureSpec srcTextureSpec = defaultTextureSpec;
    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};

    TextureSpec dstTextureSpec = defaultTextureSpec;
    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};

    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount});
}

// Test copying one texture slice within the same texture.
TEST_P(CopyTests_T2T, CopyWithinSameTextureOneSlice) {
    constexpr uint32_t kWidth = 256u;
    constexpr uint32_t kHeight = 128u;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kSrcBaseLayer = 0u;
    constexpr uint32_t kDstBaseLayer = 3u;
    constexpr uint32_t kCopyArrayLayerCount = 1u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    TextureSpec srcTextureSpec = defaultTextureSpec;
    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};

    TextureSpec dstTextureSpec = defaultTextureSpec;
    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};

    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount}, true);
}

// Test copying multiple contiguous texture slices within the same texture with non-overlapped
// slices.
TEST_P(CopyTests_T2T, CopyWithinSameTextureNonOverlappedSlices) {
    constexpr uint32_t kWidth = 256u;
    constexpr uint32_t kHeight = 128u;
    constexpr uint32_t kLayers = 6u;
    constexpr uint32_t kSrcBaseLayer = 0u;
    constexpr uint32_t kDstBaseLayer = 3u;
    constexpr uint32_t kCopyArrayLayerCount = 3u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    TextureSpec srcTextureSpec = defaultTextureSpec;
    srcTextureSpec.copyOrigin = {0, 0, kSrcBaseLayer};

    TextureSpec dstTextureSpec = defaultTextureSpec;
    dstTextureSpec.copyOrigin = {0, 0, kDstBaseLayer};

    DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyArrayLayerCount}, true);
}

// A regression test (from WebGPU CTS) for an Intel D3D12 driver bug about T2T copy with specific
// texture formats. See http://crbug.com/1161355 for more details.
TEST_P(CopyTests_T2T, CopyFromNonZeroMipLevelWithTexelBlockSizeLessThan4Bytes) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/473582006): [Capture] issue calling ResolveDeferredExpectationsNow.
    DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled());

    constexpr std::array<wgpu::TextureFormat, 11> kFormats = {
        {wgpu::TextureFormat::RG8Sint, wgpu::TextureFormat::RG8Uint, wgpu::TextureFormat::RG8Snorm,
         wgpu::TextureFormat::RG8Unorm, wgpu::TextureFormat::R16Float, wgpu::TextureFormat::R16Sint,
         wgpu::TextureFormat::R16Uint, wgpu::TextureFormat::R8Snorm, wgpu::TextureFormat::R8Unorm,
         wgpu::TextureFormat::R8Sint, wgpu::TextureFormat::R8Uint}};

    constexpr uint32_t kSrcLevelCount = 4;
    constexpr uint32_t kDstLevelCount = 5;
    constexpr uint32_t kSrcSize = 2 << kSrcLevelCount;
    constexpr uint32_t kDstSize = 2 << kDstLevelCount;
    ASSERT_LE(kSrcSize, kTextureBytesPerRowAlignment);
    ASSERT_LE(kDstSize, kTextureBytesPerRowAlignment);

    // The copyLayer to test:
    // 1u (non-array texture), 3u (copyLayer < copyWidth), 5u (copyLayer > copyWidth)
    constexpr std::array<uint32_t, 3> kTestTextureLayer = {1u, 3u, 5u};

    for (wgpu::TextureFormat format : kFormats) {
        // TODO(dawn:1877): Snorm copy failing ANGLE Swiftshader, need further investigation.
        if (IsANGLESwiftShader() &&
            (format == wgpu::TextureFormat::RG8Snorm || format == wgpu::TextureFormat::R8Snorm)) {
            continue;
        }

        for (uint32_t textureLayer : kTestTextureLayer) {
            const wgpu::Extent3D kUploadSize = {4u, 4u, textureLayer};

            for (uint32_t srcLevel = 0; srcLevel < kSrcLevelCount; ++srcLevel) {
                for (uint32_t dstLevel = 0; dstLevel < kDstLevelCount; ++dstLevel) {
                    TextureSpec srcSpec;
                    srcSpec.levelCount = kSrcLevelCount;
                    srcSpec.format = format;
                    srcSpec.copyLevel = srcLevel;
                    srcSpec.textureSize = {kSrcSize, kSrcSize, textureLayer};

                    TextureSpec dstSpec = srcSpec;
                    dstSpec.levelCount = kDstLevelCount;
                    dstSpec.copyLevel = dstLevel;
                    dstSpec.textureSize = {kDstSize, kDstSize, textureLayer};

                    DoTest(srcSpec, dstSpec, kUploadSize);
                }
            }

            // Resolve all the deferred expectations now to avoid allocating too much memory
            // in mDeferredExpectations.
            ResolveDeferredExpectationsNow();
        }
    }
}

// Test that copying from one mip level to another mip level within the same 2D array texture works.
TEST_P(CopyTests_T2T, Texture2DArraySameTextureDifferentMipLevels) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kLayers = 8u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};
    defaultTextureSpec.levelCount = 6;

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec srcSpec = defaultTextureSpec;
        srcSpec.copyLevel = i - 1;
        TextureSpec dstSpec = defaultTextureSpec;
        dstSpec.copyLevel = i;

        DoTest(srcSpec, dstSpec, {kWidth >> i, kHeight >> i, kLayers}, true);
    }
}

// Test that copying whole 1D texture in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture1DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 1;
    constexpr uint32_t kDepth = 1;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kDepth}, false, wgpu::TextureDimension::e1D);
}

// Test that copying whole 3D texture in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture3DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kDepth}, false, wgpu::TextureDimension::e3D);
}

// Test that copying from one mip level to another mip level within the same 3D texture works.
TEST_P(CopyTests_T2T, Texture3DSameTextureDifferentMipLevels) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};
    textureSpec.levelCount = 2;

    TextureSpec dstSpec = textureSpec;
    dstSpec.copyLevel = 1;

    DoTest(textureSpec, dstSpec, {kWidth >> 1, kHeight >> 1, kDepth >> 1}, true,
           wgpu::TextureDimension::e3D);
}

// Test that copying whole 3D texture to a 2D array in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture3DTo2DArrayFull) {
    // TODO(crbug.com/dawn/1425): Remove this suppression.
    DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows() && IsIntel());

    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
}

// Test that copying between 3D texture and 2D array textures works. It includes partial copy
// for src and/or dst texture, non-zero offset (copy origin), non-zero mip level.
TEST_P(CopyTests_T2T, Texture3DAnd2DArraySubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    // TODO(crbug.com/dawn/2294): diagnose T2B failures on Pixel 4 OpenGLES
    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());

    // TODO(crbug.com/dawn/1426): Remove this suppression.
    DAWN_SUPPRESS_TEST_IF(IsANGLE() && IsWindows() && IsIntel());

    constexpr uint32_t kWidth = 8;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth = 2u;

    TextureSpec baseSpec;
    baseSpec.textureSize = {kWidth, kHeight, kDepth};

    TextureSpec srcSpec = baseSpec;
    TextureSpec dstSpec = baseSpec;

    // dst texture is a partial copy
    dstSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);

    // src texture is a partial copy
    srcSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    dstSpec = baseSpec;
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);

    // Both src and dst texture is a partial copy
    srcSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    dstSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);

    // Non-zero offset (copy origin)
    srcSpec = baseSpec;
    dstSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    dstSpec.copyOrigin = {kWidth, kHeight, kDepth};
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);

    // Non-zero mip level
    srcSpec = baseSpec;
    dstSpec.textureSize = {kWidth * 2, kHeight * 2, kDepth * 2};
    dstSpec.copyOrigin = {0, 0, 0};
    dstSpec.copyLevel = 1;
    dstSpec.levelCount = 2;
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e3D,
           wgpu::TextureDimension::e2D);
    DoTest(srcSpec, dstSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);
}

// Test that copying whole 2D array to a 3D texture in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture2DArrayTo3DFull) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth, kHeight, kDepth}, wgpu::TextureDimension::e2D,
           wgpu::TextureDimension::e3D);
}

// Test that copying subregion of a 3D texture in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture3DSubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth / 2, kHeight / 2, kDepth / 2}, false,
           wgpu::TextureDimension::e3D);
}

// Test that copying subregion of a 3D texture to a 2D array in one texture-to-texture-copy works.
TEST_P(CopyTests_T2T, Texture3DTo2DArraySubRegion) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth / 2, kHeight / 2, kDepth / 2},
           wgpu::TextureDimension::e3D, wgpu::TextureDimension::e2D);
}

// Test that copying subregion of a 2D array to a 3D texture to in one texture-to-texture-copy
// works.
TEST_P(CopyTests_T2T, Texture2DArrayTo3DSubRegion) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 6u;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, kDepth};

    DoTest(textureSpec, textureSpec, {kWidth / 2, kHeight / 2, kDepth / 2},
           wgpu::TextureDimension::e2D, wgpu::TextureDimension::e3D);
}

// Test that copying texture 3D array mips in one texture-to-texture-copy works
TEST_P(CopyTests_T2T, Texture3DMipAligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;
    constexpr uint32_t kDepth = 64u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, textureSpec, {kWidth >> i, kHeight >> i, kDepth >> i},
               wgpu::TextureDimension::e3D, wgpu::TextureDimension::e3D);
    }
}

// Test that copying texture 3D array mips in one texture-to-texture-copy works
TEST_P(CopyTests_T2T, Texture3DMipUnaligned) {
    // TODO(crbug.com/40238674): Fails on Pixel 10.
    DAWN_SUPPRESS_TEST_IF(IsImgTec());
    constexpr uint32_t kWidth = 261;
    constexpr uint32_t kHeight = 123;
    constexpr uint32_t kDepth = 69u;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kDepth};

    for (unsigned int i = 1; i < 6; ++i) {
        TextureSpec textureSpec = defaultTextureSpec;
        textureSpec.copyLevel = i;
        textureSpec.levelCount = i + 1;

        DoTest(textureSpec, textureSpec, {kWidth >> i, kHeight >> i, kDepth >> i},
               wgpu::TextureDimension::e3D, wgpu::TextureDimension::e3D);
    }
}

// TODO(dawn:1705): enable this test for D3D11
DAWN_INSTANTIATE_TEST_P(
    CopyTests_T2T,
    {D3D12Backend(),
     D3D12Backend({"use_temp_buffer_in_small_format_texture_to_texture_copy_from_greater_to_less_"
                   "mip_level"}),
     D3D12Backend(
         {"d3d12_use_temp_buffer_in_texture_to_texture_copy_between_different_dimensions"}),
     MetalBackend(), OpenGLBackend(), OpenGLESBackend(), OpenGLESBackend({"gl_defer"}),
     VulkanBackend(), WebGPUBackend()},
    {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGB9E5Ufloat});

// Test copying between textures that have srgb compatible texture formats;
TEST_P(CopyTests_T2T_Srgb, FullCopy) {
    constexpr uint32_t kWidth = 256;
    constexpr uint32_t kHeight = 128;

    TextureSpec textureSpec;
    textureSpec.textureSize = {kWidth, kHeight, 1};
    DoTest(textureSpec, textureSpec, {kWidth, kHeight, 1});
}

DAWN_INSTANTIATE_TEST_P(CopyTests_T2T_Srgb,
                        {D3D11Backend(), D3D12Backend(), MetalBackend(), OpenGLBackend(),
                         OpenGLESBackend(), OpenGLESBackend({"gl_defer"}), VulkanBackend(),
                         WebGPUBackend()},
                        {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA8UnormSrgb,
                         wgpu::TextureFormat::BGRA8Unorm, wgpu::TextureFormat::BGRA8UnormSrgb});

// Test copying 2d texture arrays with binding view dimension set to cube.
class CopyTests_T2T_Compat : public CopyTests_T2T {
  protected:
    void SetUp() override {
        CopyTests_T2T::SetUp();
        DAWN_TEST_UNSUPPORTED_IF(!IsCompatibilityMode());
    }
};

TEST_P(CopyTests_T2T_Compat, TextureCubeToCubeOffset) {
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;
    constexpr uint32_t kCopyLayerCount = 2;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (uint32_t i = 0; i < kLayers - kCopyLayerCount; ++i) {
        TextureSpec srcTextureSpec = defaultTextureSpec;
        srcTextureSpec.copyOrigin = {0, 0, i};

        for (uint32_t j = 0; j < kLayers - kCopyLayerCount; ++j) {
            TextureSpec dstTextureSpec = defaultTextureSpec;
            dstTextureSpec.copyOrigin = {0, 0, j};

            DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyLayerCount},
                   wgpu::TextureDimension::e2D, wgpu::TextureDimension::e2D, false,
                   wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::Cube);
        }
    }
}

TEST_P(CopyTests_T2T_Compat, Texture2DToCubeOffset) {
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;
    constexpr uint32_t kCopyLayerCount = 2;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (uint32_t i = 0; i < kLayers - kCopyLayerCount; ++i) {
        TextureSpec srcTextureSpec = defaultTextureSpec;
        srcTextureSpec.copyOrigin = {0, 0, i};

        for (uint32_t j = 0; j < kLayers - kCopyLayerCount; ++j) {
            TextureSpec dstTextureSpec = defaultTextureSpec;
            dstTextureSpec.copyOrigin = {0, 0, j};

            DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyLayerCount},
                   wgpu::TextureDimension::e2D, wgpu::TextureDimension::e2D, false,
                   wgpu::TextureViewDimension::Undefined, wgpu::TextureViewDimension::Cube);
        }
    }
}

TEST_P(CopyTests_T2T_Compat, TextureCubeTo2DOffset) {
    constexpr uint32_t kWidth = 32;
    constexpr uint32_t kHeight = 32;
    constexpr uint32_t kLayers = 6;
    constexpr uint32_t kCopyLayerCount = 2;

    TextureSpec defaultTextureSpec;
    defaultTextureSpec.textureSize = {kWidth, kHeight, kLayers};

    for (uint32_t i = 0; i < kLayers - kCopyLayerCount; ++i) {
        TextureSpec srcTextureSpec = defaultTextureSpec;
        srcTextureSpec.copyOrigin = {0, 0, i};

        for (uint32_t j = 0; j < kLayers - kCopyLayerCount; ++j) {
            TextureSpec dstTextureSpec = defaultTextureSpec;
            dstTextureSpec.copyOrigin = {0, 0, j};

            DoTest(srcTextureSpec, dstTextureSpec, {kWidth, kHeight, kCopyLayerCount},
                   wgpu::TextureDimension::e2D, wgpu::TextureDimension::e2D, false,
                   wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::Undefined);
        }
    }
}

DAWN_INSTANTIATE_TEST_P(CopyTests_T2T_Compat,
                        {
                            D3D11Backend(),
                            D3D11Backend({"d3d11_disable_map_on_default_buffers"}),
                            OpenGLBackend(),
                            OpenGLESBackend(),
                            OpenGLESBackend({"gl_defer"}),
                        },
                        {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::BGRA8Unorm});

static constexpr uint64_t kSmallBufferSize = 4;
static constexpr uint64_t kLargeBufferSize = 1 << 16;

// Test copying full buffers
TEST_P(CopyTests_B2B, FullCopy) {
    DoTest(kSmallBufferSize, 0, kSmallBufferSize, 0, kSmallBufferSize);
    DoTest(kLargeBufferSize, 0, kLargeBufferSize, 0, kLargeBufferSize);
}

// Test copying small pieces of a buffer at different corner case offsets
TEST_P(CopyTests_B2B, SmallCopyInBigBuffer) {
    constexpr uint64_t kEndOffset = kLargeBufferSize - kSmallBufferSize;
    DoTest(kLargeBufferSize, 0, kLargeBufferSize, 0, kSmallBufferSize);
    DoTest(kLargeBufferSize, kEndOffset, kLargeBufferSize, 0, kSmallBufferSize);
    DoTest(kLargeBufferSize, 0, kLargeBufferSize, kEndOffset, kSmallBufferSize);
    DoTest(kLargeBufferSize, kEndOffset, kLargeBufferSize, kEndOffset, kSmallBufferSize);
}

// Test zero-size copies
TEST_P(CopyTests_B2B, ZeroSizedCopy) {
    DoTest(kLargeBufferSize, 0, kLargeBufferSize, 0, 0);
    DoTest(kLargeBufferSize, 0, kLargeBufferSize, kLargeBufferSize, 0);
    DoTest(kLargeBufferSize, kLargeBufferSize, kLargeBufferSize, 0, 0);
    DoTest(kLargeBufferSize, kLargeBufferSize, kLargeBufferSize, kLargeBufferSize, 0);
}

DAWN_INSTANTIATE_TEST(CopyTests_B2B,
                      D3D11Backend(),
                      D3D12Backend(),
                      MetalBackend(),
                      OpenGLBackend(),
                      OpenGLESBackend(),
                      OpenGLESBackend({"gl_defer"}),
                      VulkanBackend(),
                      WebGPUBackend());

// Test clearing full buffers
TEST_P(ClearBufferTests, FullClear) {
    DoTest(kSmallBufferSize, 0, kSmallBufferSize);
    DoTest(kLargeBufferSize, 0, kLargeBufferSize);
}

// Test clearing small pieces of a buffer at different corner case offsets
TEST_P(ClearBufferTests, SmallClearInBigBuffer) {
    constexpr uint64_t kEndOffset = kLargeBufferSize - kSmallBufferSize;
    DoTest(kLargeBufferSize, 0, kSmallBufferSize);
    DoTest(kLargeBufferSize, kSmallBufferSize, kSmallBufferSize);
    DoTest(kLargeBufferSize, kEndOffset, kSmallBufferSize);
}

// Test zero-size clears
TEST_P(ClearBufferTests, ZeroSizedClear) {
    DoTest(kLargeBufferSize, 0, 0);
    DoTest(kLargeBufferSize, kSmallBufferSize, 0);
    DoTest(kLargeBufferSize, kLargeBufferSize, 0);
}

DAWN_INSTANTIATE_TEST(ClearBufferTests,
                      D3D11Backend(),
                      D3D12Backend(),
                      MetalBackend(),
                      OpenGLBackend(),
                      OpenGLESBackend(),
                      OpenGLESBackend({"gl_defer"}),
                      VulkanBackend(),
                      WebGPUBackend());

// Regression tests to reproduce a flaky failure when running whole WebGPU CTS on Intel GPUs.
// See crbug.com/dawn/1487 for more details.
namespace {
using TextureFormat = wgpu::TextureFormat;

enum class InitializationMethod {
    CopyBufferToTexture,
    WriteTexture,
    CopyTextureToTexture,
};

std::ostream& operator<<(std::ostream& o, InitializationMethod method) {
    switch (method) {
        case InitializationMethod::CopyBufferToTexture:
            o << "CopyBufferToTexture";
            break;
        case InitializationMethod::WriteTexture:
            o << "WriteTexture";
            break;
        case InitializationMethod::CopyTextureToTexture:
            o << "CopyTextureToTexture";
            break;
        default:
            DAWN_UNREACHABLE();
            break;
    }

    return o;
}

using AddRenderAttachmentUsage = bool;

DAWN_TEST_PARAM_STRUCT(CopyToDepthStencilTextureAfterDestroyingBigBufferTestsParams,
                       TextureFormat,
                       InitializationMethod,
                       AddRenderAttachmentUsage);

}  // anonymous namespace

class CopyToDepthStencilTextureAfterDestroyingBigBufferTests
    : public DawnTestWithParams<CopyToDepthStencilTextureAfterDestroyingBigBufferTestsParams> {};

TEST_P(CopyToDepthStencilTextureAfterDestroyingBigBufferTests, DoTest) {
    // Copies to stencil textures are unsupported on the OpenGL backend.
    DAWN_TEST_UNSUPPORTED_IF(GetParam().mTextureFormat == wgpu::TextureFormat::Stencil8 &&
                             (IsOpenGL() || IsOpenGLES()));

    // TODO(crbug.com/468035609): Fails on Win11/NVIDIA GTX 1660.
    DAWN_SUPPRESS_TEST_IF(GetParam().mTextureFormat == wgpu::TextureFormat::Stencil8 &&
                          IsWindows11() && IsNvidia() && IsD3D12() && IsBackendValidationEnabled());

    // TODO(crbug.com/468035609): Fails on Win11/AMD RX 5500 XT.
    DAWN_SUPPRESS_TEST_IF(GetParam().mTextureFormat == wgpu::TextureFormat::Stencil8 &&
                          IsWindows11() && IsAMD() && IsD3D12() && IsBackendValidationEnabled());

    wgpu::TextureFormat format = GetParam().mTextureFormat;

    const uint32_t texelBlockSize = utils::GetTexelBlockSizeInBytes(format);
    const uint32_t expectedDataSize = Align(texelBlockSize, 4u);

    // First, create a big buffer and fill some garbage data on DEFAULT heap.
    constexpr size_t kBigBufferSize = 159740u;
    constexpr uint8_t kGarbageData = 255u;
    std::array<uint8_t, kBigBufferSize> garbageData;
    garbageData.fill(kGarbageData);

    wgpu::Buffer bigBuffer =
        utils::CreateBufferFromData(device, garbageData.data(), garbageData.size(),
                                    wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
    // Next, destroy the buffer. Its heap is still alive and contains the garbage data.
    bigBuffer.Destroy();

    // Ensure the underlying ID3D12Resource of bigBuffer is deleted.
    bool submittedWorkDone = false;
    queue.OnSubmittedWorkDone(
        wgpu::CallbackMode::AllowProcessEvents,
        [&submittedWorkDone](wgpu::QueueWorkDoneStatus status, wgpu::StringView) {
            EXPECT_EQ(status, wgpu::QueueWorkDoneStatus::Success);
            submittedWorkDone = true;
        });
    while (!submittedWorkDone) {
        WaitABit();
    }

    // Then, create a small texture, which should be allocated on the heap that contains the
    // garbage data.
    wgpu::TextureDescriptor textureDescriptor = {};
    textureDescriptor.format = format;
    textureDescriptor.size = {1, 1, 1};
    textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
    if (GetParam().mAddRenderAttachmentUsage) {
        textureDescriptor.usage |= wgpu::TextureUsage::RenderAttachment;
    }
    wgpu::Texture texture = device.CreateTexture(&textureDescriptor);

    // Finally, upload valid data into the texture and validate its contents.
    std::vector<uint8_t> expectedData(expectedDataSize);
    constexpr uint8_t kBaseValue = 204u;
    for (uint32_t i = 0; i < texelBlockSize; ++i) {
        expectedData[i] = static_cast<uint8_t>(i + kBaseValue);
    }

    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    wgpu::Extent3D copySize = {1, 1, 1};

    auto EncodeUploadDataToTexture = [this, copySize](wgpu::CommandEncoder encoder,
                                                      wgpu::Texture destinationTexture,
                                                      const std::vector<uint8_t>& expectedData) {
        wgpu::BufferDescriptor bufferDescriptor = {};
        bufferDescriptor.size = expectedData.size();
        bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
        wgpu::Buffer uploadBuffer = device.CreateBuffer(&bufferDescriptor);
        bool done = false;
        uploadBuffer.MapAsync(wgpu::MapMode::Write, 0, static_cast<uint32_t>(expectedData.size()),
                              wgpu::CallbackMode::AllowProcessEvents,
                              [&done](wgpu::MapAsyncStatus status, wgpu::StringView) {
                                  ASSERT_EQ(wgpu::MapAsyncStatus::Success, status);
                                  done = true;
                              });
        while (!done) {
            WaitABit();
        }

        uint8_t* uploadData = static_cast<uint8_t*>(uploadBuffer.GetMappedRange());
        memcpy(uploadData, expectedData.data(), expectedData.size());
        uploadBuffer.Unmap();

        wgpu::TexelCopyBufferInfo texelCopyBufferInfo =
            utils::CreateTexelCopyBufferInfo(uploadBuffer);
        wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
            utils::CreateTexelCopyTextureInfo(destinationTexture);
        // (Off-topic) spot-test for defaulting of .aspect.
        texelCopyTextureInfo.aspect = wgpu::TextureAspect::Undefined;
        encoder.CopyBufferToTexture(&texelCopyBufferInfo, &texelCopyTextureInfo, &copySize);
    };

    switch (GetParam().mInitializationMethod) {
        case InitializationMethod::CopyBufferToTexture: {
            EncodeUploadDataToTexture(encoder, texture, expectedData);
            break;
        }
        case InitializationMethod::WriteTexture: {
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
                utils::CreateTexelCopyTextureInfo(texture);
            wgpu::TexelCopyBufferLayout layout;
            queue.WriteTexture(&texelCopyTextureInfo, expectedData.data(), texelBlockSize, &layout,
                               &copySize);
            break;
        }
        case InitializationMethod::CopyTextureToTexture: {
            wgpu::Texture stagingTexture = device.CreateTexture(&textureDescriptor);
            EncodeUploadDataToTexture(encoder, stagingTexture, expectedData);

            wgpu::TexelCopyTextureInfo imageCopyStagingTexture =
                utils::CreateTexelCopyTextureInfo(stagingTexture);
            wgpu::TexelCopyTextureInfo texelCopyTextureInfo =
                utils::CreateTexelCopyTextureInfo(texture);
            encoder.CopyTextureToTexture(&imageCopyStagingTexture, &texelCopyTextureInfo,
                                         &copySize);
            break;
        }
        default:
            DAWN_UNREACHABLE();
            break;
    }

    wgpu::BufferDescriptor destinationBufferDescriptor = {};
    destinationBufferDescriptor.size = expectedDataSize;
    destinationBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
    wgpu::Buffer destinationBuffer = device.CreateBuffer(&destinationBufferDescriptor);

    wgpu::TexelCopyTextureInfo texelCopyTextureInfo = utils::CreateTexelCopyTextureInfo(texture);
    wgpu::TexelCopyBufferInfo imageCopyDestinationBuffer =
        utils::CreateTexelCopyBufferInfo(destinationBuffer);
    encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &imageCopyDestinationBuffer, &copySize);

    wgpu::CommandBuffer commandBuffer = encoder.Finish();
    queue.Submit(1, &commandBuffer);

    EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data(), destinationBuffer, 0, expectedDataSize);
}

DAWN_INSTANTIATE_TEST_P(
    CopyToDepthStencilTextureAfterDestroyingBigBufferTests,
    {D3D11Backend(), D3D12Backend(),
     D3D12Backend({"d3d12_force_clear_copyable_depth_stencil_texture_on_creation"}), MetalBackend(),
     OpenGLBackend(), OpenGLESBackend(), OpenGLESBackend({"gl_defer"}), VulkanBackend(),
     WebGPUBackend()},
    {wgpu::TextureFormat::Depth16Unorm, wgpu::TextureFormat::Stencil8},
    {InitializationMethod::CopyBufferToTexture, InitializationMethod::WriteTexture,
     InitializationMethod::CopyTextureToTexture},
    {true, false});

// A series of regression tests for an Intel D3D12 driver issue about creating textures with
// CreatePlacedResource(). See crbug.com/1237175 for more details.
class T2TCopyFromDirtyHeapTests : public DawnTest {
  public:
    void DoTest(uint32_t layerCount, uint32_t levelCount) {
        std::vector<uint32_t> expectedData;
        wgpu::Buffer uploadBuffer = GetUploadBufferAndExpectedData(&expectedData);

        // First, create colorTexture1 and colorTexture2 and fill data into them.
        wgpu::Texture colorTexture1 = Create2DTexture(kTextureSize, layerCount, levelCount);
        Initialize2DTexture(colorTexture1, layerCount, levelCount, uploadBuffer);
        wgpu::Texture colorTexture2 = Create2DTexture(kTextureSize, layerCount, levelCount);
        Initialize2DTexture(colorTexture2, layerCount, levelCount, uploadBuffer);

        // Next, destroy colorTexture1.
        colorTexture1.Destroy();

        // Ensure colorTexture1 has been destroyed on the backend.
        EnsureSubmittedWorkDone();
        // Call an empty queue.Submit to workaround crbug.com/dawn/833 where resources are not
        // recycled until the next serial.
        queue.Submit(0, nullptr);
        EnsureSubmittedWorkDone();

        // Then, try to create destinationTextures which should be allocated on the memory used by
        // colorTexture1 previously, copy data from colorTexture2 into each destinationTexture and
        // verify the data in destinationTexture.
        std::vector<wgpu::Texture> destinationTextures;

        for (uint32_t layer = 0; layer < layerCount; ++layer) {
            for (uint32_t level = 0; level < levelCount; ++level) {
                uint32_t textureSizeAtLevel = kTextureSize >> level;
                wgpu::Texture destinationTexture = Create2DTexture(textureSizeAtLevel, 1, 1);
                // Save all destinationTextures so that they won't be deleted during the test.
                destinationTextures.push_back(destinationTexture);

                CopyIntoStagingTextureAndVerifyTexelData(colorTexture2, level, layer,
                                                         destinationTexture, expectedData);
            }
        }
    }

    wgpu::Buffer GetUploadBufferAndExpectedData(std::vector<uint32_t>* expectedData) {
        const size_t kBufferSize =
            kBytesPerRow * (kTextureSize - 1) + kTextureSize * kBytesPerBlock;

        expectedData->resize(kBufferSize / sizeof(uint32_t));
        for (uint32_t y = 0; y < kTextureSize; ++y) {
            for (uint32_t x = 0; x < kTextureSize * (kBytesPerBlock / sizeof(uint32_t)); ++x) {
                uint32_t index = (kBytesPerRow / sizeof(uint32_t)) * y + x;
                (*expectedData)[index] = x + y * 1000;
            }
        }

        wgpu::BufferDescriptor uploadBufferDesc = {};
        uploadBufferDesc.size = kBufferSize;
        uploadBufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
        uploadBufferDesc.mappedAtCreation = true;
        wgpu::Buffer uploadBuffer = device.CreateBuffer(&uploadBufferDesc);

        memcpy(uploadBuffer.GetMappedRange(), expectedData->data(), kBufferSize);
        uploadBuffer.Unmap();

        return uploadBuffer;
    }

    wgpu::Texture Create2DTexture(uint32_t textureSize, uint32_t layerCount, uint32_t levelCount) {
        wgpu::TextureDescriptor colorTextureDesc = {};
        colorTextureDesc.format = kFormat;
        colorTextureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
                                 wgpu::TextureUsage::RenderAttachment;
        colorTextureDesc.mipLevelCount = levelCount;
        colorTextureDesc.size = {textureSize, textureSize, layerCount};
        return device.CreateTexture(&colorTextureDesc);
    }

    void Initialize2DTexture(wgpu::Texture texture,
                             uint32_t layerCount,
                             uint32_t levelCount,
                             wgpu::Buffer uploadBuffer) {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        for (uint32_t layer = 0; layer < layerCount; ++layer) {
            for (uint32_t level = 0; level < levelCount; ++level) {
                wgpu::TexelCopyBufferInfo uploadCopyBuffer =
                    utils::CreateTexelCopyBufferInfo(uploadBuffer, 0, kBytesPerRow, kTextureSize);
                wgpu::TexelCopyTextureInfo colorCopyTexture =
                    utils::CreateTexelCopyTextureInfo(texture, level, {0, 0, layer});

                wgpu::Extent3D copySize = {kTextureSize >> level, kTextureSize >> level, 1};
                encoder.CopyBufferToTexture(&uploadCopyBuffer, &colorCopyTexture, &copySize);
            }
        }
        wgpu::CommandBuffer commandBuffer = encoder.Finish();
        queue.Submit(1, &commandBuffer);
    }

    void CopyIntoStagingTextureAndVerifyTexelData(wgpu::Texture sourceTexture,
                                                  uint32_t copyLevel,
                                                  uint32_t copyLayer,
                                                  wgpu::Texture stagingTexture,
                                                  const std::vector<uint32_t>& expectedData) {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();

        // Copy from miplevel = copyLevel and arrayLayer = copyLayer of sourceTexture into
        // stagingTexture
        wgpu::TexelCopyTextureInfo colorCopyTexture =
            utils::CreateTexelCopyTextureInfo(sourceTexture, copyLevel, {0, 0, copyLayer});
        uint32_t stagingTextureSize = kTextureSize >> copyLevel;
        wgpu::Extent3D copySize = {stagingTextureSize, stagingTextureSize, 1};
        wgpu::TexelCopyTextureInfo stagingCopyTexture =
            utils::CreateTexelCopyTextureInfo(stagingTexture);
        encoder.CopyTextureToTexture(&colorCopyTexture, &stagingCopyTexture, &copySize);

        // Copy from stagingTexture into readback buffer. Note that we don't use EXPECT_BUFFER_xxx()
        // because the buffers with CopySrc | CopyDst may also be allocated on the same memory of
        // colorTexture1, then stagingTexture will not be able to be allocated at that piece of
        // memory, which is not what we intended to do.
        const size_t kBufferSize =
            kBytesPerRow * (stagingTextureSize - 1) + stagingTextureSize * kBytesPerBlock;
        wgpu::BufferDescriptor readbackBufferDesc = {};
        readbackBufferDesc.size = kBufferSize;
        readbackBufferDesc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
        wgpu::Buffer readbackBuffer = device.CreateBuffer(&readbackBufferDesc);
        wgpu::TexelCopyBufferInfo readbackCopyBuffer =
            utils::CreateTexelCopyBufferInfo(readbackBuffer, 0, kBytesPerRow, kTextureSize);
        encoder.CopyTextureToBuffer(&stagingCopyTexture, &readbackCopyBuffer, &copySize);

        wgpu::CommandBuffer commandBuffer = encoder.Finish();
        queue.Submit(1, &commandBuffer);

        // Check the data in readback buffer
        bool done = false;
        readbackBuffer.MapAsync(wgpu::MapMode::Read, 0, kBufferSize,
                                wgpu::CallbackMode::AllowProcessEvents,
                                [&done](wgpu::MapAsyncStatus status, wgpu::StringView) {
                                    ASSERT_EQ(wgpu::MapAsyncStatus::Success, status);
                                    done = true;
                                });
        while (!done) {
            WaitABit();
        }

        const uint32_t* readbackData =
            static_cast<const uint32_t*>(readbackBuffer.GetConstMappedRange());
        for (uint32_t y = 0; y < stagingTextureSize; ++y) {
            for (uint32_t x = 0; x < stagingTextureSize * (kBytesPerBlock / sizeof(uint32_t));
                 ++x) {
                uint32_t index = y * (kBytesPerRow / sizeof(uint32_t)) + x;
                ASSERT_EQ(expectedData[index], readbackData[index]);
            }
        }

        readbackBuffer.Destroy();
    }

    void EnsureSubmittedWorkDone() {
        bool submittedWorkDone = false;
        queue.OnSubmittedWorkDone(
            wgpu::CallbackMode::AllowProcessEvents,
            [&submittedWorkDone](wgpu::QueueWorkDoneStatus status, wgpu::StringView) {
                EXPECT_EQ(status, wgpu::QueueWorkDoneStatus::Success);
                submittedWorkDone = true;
            });
        while (!submittedWorkDone) {
            WaitABit();
        }
    }

  private:
    const uint32_t kTextureSize = 63;
    const wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA32Uint;
    const uint32_t kBytesPerBlock = 16;
    const uint32_t kBytesPerRow =
        Align(kBytesPerBlock * kTextureSize, kTextureBytesPerRowAlignment);
};

TEST_P(T2TCopyFromDirtyHeapTests, From2DArrayTexture) {
    constexpr uint32_t kLayerCount = 7;
    constexpr uint32_t kLevelCount = 1;
    DoTest(kLayerCount, kLevelCount);
}

TEST_P(T2TCopyFromDirtyHeapTests, From2DMultiMipmapLevelTexture) {
    constexpr uint32_t kLayerCount = 1;
    constexpr uint32_t kLevelCount = 5;
    DoTest(kLayerCount, kLevelCount);
}

DAWN_INSTANTIATE_TEST(T2TCopyFromDirtyHeapTests,
                      D3D12Backend(),
                      MetalBackend(),
                      OpenGLBackend(),
                      OpenGLESBackend(),
                      OpenGLESBackend({"gl_defer"}),
                      VulkanBackend(),
                      WebGPUBackend());

class CopyTests_MemoryLeak : public DawnTest {};

// Test that reproduced a memory leak triggered by the D3D12 temporary buffer workaround
// (D3D12UseTempBufferInDepthStencilTextureAndBufferCopyWithNonZeroBufferOffset) for depth/stencil
// texture-to-buffer copies with a non-zero buffer offset. See crbug.com/500443031
TEST_P(CopyTests_MemoryLeak, T2BLeakUninitializedPadding) {
    // Dirty the GPU heap with a recognizable pattern.
    // Use a large enough size to likely hit the same heap as the upcoming temporary buffer.
    {
        constexpr uint64_t kDirtySize = 64 * 1024;
        constexpr uint32_t kPattern = 0xDEADBEEF;

        wgpu::BufferDescriptor descriptor;
        descriptor.size = kDirtySize;
        descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);

        std::vector<uint32_t> data(kDirtySize / sizeof(uint32_t), kPattern);
        queue.WriteBuffer(buffer, 0, data.data(), data.size() * sizeof(uint32_t));

        // Submit and wait for idle to ensure the data is written and then the buffer can be freed.
        queue.Submit(0, nullptr);
        WaitForAllOperations();
    }

    // Initialize the texture with a known value.
    constexpr float kClearDepthValue = 0.5f;
    constexpr auto kTexFormat = wgpu::TextureFormat::Depth16Unorm;
    constexpr uint32_t kTexWidth = 1;
    constexpr uint32_t kTexHeight = 2;

    // Create a 1x2 depth texture and clear it to kClearDepthValue. If we copy a 1x2 texture, we get
    // padding between row 0 and row 1. We set bytesPerRow high when copying to force padding bytes
    // on row 0.
    wgpu::Texture texture;
    {
        wgpu::TextureDescriptor texDesc = {};
        texDesc.size = {kTexWidth, kTexHeight, 1};
        texDesc.format = kTexFormat;
        texDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
        texture = device.CreateTexture(&texDesc);

        utils::ComboRenderPassDescriptor renderPassDesc({}, texture.CreateView());
        renderPassDesc.UnsetDepthStencilLoadStoreOpsForFormat(kTexFormat);
        renderPassDesc.cDepthStencilAttachmentInfo.depthClearValue = kClearDepthValue;
        renderPassDesc.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;

        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
        pass.End();
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
        WaitForAllOperations();
    }

    // Create a destination buffer with large bytesPerRow to maximize padding.
    // 256KB padding per row (we only have 1 row though)
    constexpr uint32_t kBytesPerRow = 256 * 1024;
    // The D3D12UseTempBufferInDepthStencilTextureAndBufferCopyWithNonZeroBufferOffset workaround
    // triggers when offset is not a multiple of 512.
    constexpr uint32_t kOffset = 4;

    wgpu::Buffer destinationBuffer;
    const uint32_t destinationBufferSize = kOffset + kBytesPerRow * kTexHeight;
    {
        wgpu::BufferDescriptor bufferDesc = {};
        bufferDesc.size = destinationBufferSize;
        bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
        destinationBuffer = device.CreateBuffer(&bufferDesc);
    }

    // Perform the copy texture to buffer
    {
        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
        wgpu::TexelCopyTextureInfo srcInfo = utils::CreateTexelCopyTextureInfo(
            texture, 0, {0, 0, 0}, wgpu::TextureAspect::DepthOnly);
        wgpu::TexelCopyBufferInfo dstInfo =
            utils::CreateTexelCopyBufferInfo(destinationBuffer, kOffset, kBytesPerRow, kTexHeight);
        wgpu::Extent3D copySize = {kTexWidth, kTexHeight, 1};
        encoder.CopyTextureToBuffer(&srcInfo, &dstInfo, &copySize);
        wgpu::CommandBuffer commands = encoder.Finish();
        queue.Submit(1, &commands);
    }

    // Map and inspect the padding.
    {
        MapAsyncAndWait(destinationBuffer, wgpu::MapMode::Read, 0, destinationBufferSize);
        const uint8_t* readbackData =
            static_cast<const uint8_t*>(destinationBuffer.GetConstMappedRange());

        // The first texel is at ptr[kOffset]. Depth16Unorm is 2 bytes.
        // Row 0 texel: ptr[kOffset] ... ptr[kOffset + 1]
        // Padding after row 0: ptr[kOffset + 2] ... ptr[kOffset + kBytesPerRow - 1]
        // Row 1 texel: ptr[kOffset + kBytesPerRow] ... ptr[kOffset + kBytesPerRow + 1]

        for (uint32_t i = kOffset + 2; i < kOffset + kBytesPerRow; ++i) {
            ASSERT_EQ(readbackData[i], 0u);
        }

        destinationBuffer.Unmap();
    }
}

DAWN_INSTANTIATE_TEST(CopyTests_MemoryLeak,
                      D3D12Backend({
                          // clang-format off
        "d3d12_use_temp_buffer_in_depth_stencil_texture_and_buffer_copy_with_non_zero_buffer_offset",
                          // clang-format on
                      }));

class BlitTextureToBufferTest : public DawnTest {};

// Test that encoding a CopyTextureToBuffer but not submitting it doesn't mark the buffer as
// initialized.
TEST_P(BlitTextureToBufferTest, NoSubmitDoesNotMarkInitialized) {
    DAWN_TEST_UNSUPPORTED_IF(UsesWire());

    // Create a buffer that we will use as the destination of a CopyTextureToBuffer.
    wgpu::BufferDescriptor bufferDesc;
    bufferDesc.size = 256;
    bufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);

    // Create a source texture.
    wgpu::TextureDescriptor textureDesc;
    textureDesc.size = {64, 1, 1};
    textureDesc.format = wgpu::TextureFormat::R32Float;
    textureDesc.usage = wgpu::TextureUsage::CopySrc;
    wgpu::Texture texture = device.CreateTexture(&textureDesc);

    wgpu::TexelCopyTextureInfo src = utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
    wgpu::TexelCopyBufferInfo dst = utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 1);
    wgpu::Extent3D copySize = {64, 1, 1};

    // Encode the CopyTextureToBuffer.
    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
    encoder.CopyTextureToBuffer(&src, &dst, &copySize);
    // Finish the encoder, but do NOT submit the command buffer.
    encoder.Finish();

    // Now, if we use the buffer in a way that requires initialization (e.g., as a source of a
    // copy), it should be lazy-cleared because the previous CopyTextureToBuffer was never
    // submitted.
    wgpu::BufferDescriptor dstBufferDesc;
    dstBufferDesc.size = 256;
    dstBufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
    wgpu::Buffer dstBuffer = device.CreateBuffer(&dstBufferDesc);

    wgpu::CommandEncoder encoder2 = device.CreateCommandEncoder();
    encoder2.CopyBufferToBuffer(buffer, 0, dstBuffer, 0, 256);
    wgpu::CommandBuffer cb = encoder2.Finish();

    size_t lazyClearsBefore = native::GetLazyClearCountForTesting(device.Get());
    queue.Submit(1, &cb);
    size_t lazyClearsAfter = native::GetLazyClearCountForTesting(device.Get());

    // If the buffer was incorrectly marked as initialized during encoding of the first command,
    // lazyClearsAfter - lazyClearsBefore will be 0.
    // Otherwise, it should be 1.
    EXPECT_EQ(lazyClearsAfter - lazyClearsBefore, 1u);
}

DAWN_INSTANTIATE_TEST(BlitTextureToBufferTest,
                      D3D11Backend({"use_blit_for_t2b"}),
                      D3D12Backend({"use_blit_for_t2b"}),
                      MetalBackend({"use_blit_for_t2b"}),
                      OpenGLBackend({"use_blit_for_t2b"}),
                      OpenGLESBackend({"use_blit_for_t2b"}),
                      VulkanBackend({"use_blit_for_t2b"}));

}  // anonymous namespace
}  // namespace dawn
