Add testing and implementation for lazy init compressed textures

Bug: dawn:145
Change-Id: I176ac2fb4c7db708bb147e2ad0118538907b43d3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/33040
Commit-Queue: Natasha Lee <natlee@microsoft.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index a1909f9..0cac08b 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -851,11 +851,6 @@
     MaybeError Texture::ClearTexture(CommandRecordingContext* commandContext,
                                      const SubresourceRange& range,
                                      TextureBase::ClearValue clearValue) {
-        // TODO(jiawei.shao@intel.com): initialize the textures in compressed formats with copies.
-        if (GetFormat().isCompressed) {
-            SetIsSubresourceContentInitialized(true, range);
-            return {};
-        }
 
         ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
 
@@ -951,12 +946,7 @@
 
                 uint32_t bytesPerRow = Align((GetWidth() / blockInfo.width) * blockInfo.byteSize,
                                              kTextureBytesPerRowAlignment);
-                uint64_t bufferSize64 = bytesPerRow * (GetHeight() / blockInfo.height);
-                if (bufferSize64 > std::numeric_limits<uint32_t>::max()) {
-                    return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer.");
-                }
-                uint32_t bufferSize = static_cast<uint32_t>(bufferSize64);
-
+                uint64_t bufferSize = bytesPerRow * (GetHeight() / blockInfo.height);
                 DynamicUploader* uploader = device->GetDynamicUploader();
                 UploadHandle uploadHandle;
                 DAWN_TRY_ASSIGN(uploadHandle,
@@ -967,7 +957,7 @@
                 for (uint32_t level = range.baseMipLevel;
                      level < range.baseMipLevel + range.levelCount; ++level) {
                     // compute d3d12 texture copy locations for texture and buffer
-                    Extent3D copySize = GetMipLevelVirtualSize(level);
+                    Extent3D copySize = GetMipLevelPhysicalSize(level);
 
                     uint32_t rowsPerImage = GetHeight() / blockInfo.height;
                     Texture2DCopySplit copySplit = ComputeTextureCopySplit(
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 1b4840d..81b3d3b 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -1006,65 +1006,116 @@
         imageRange.levelCount = 1;
         imageRange.layerCount = 1;
 
-        for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
-             ++level) {
-            imageRange.baseMipLevel = level;
-            for (uint32_t layer = range.baseArrayLayer;
-                 layer < range.baseArrayLayer + range.layerCount; ++layer) {
-                Aspect aspects = Aspect::None;
-                for (Aspect aspect : IterateEnumMask(range.aspects)) {
+        if (GetFormat().isCompressed) {
+            if (range.aspects == Aspect::None) {
+                return {};
+            }
+            // need to clear the texture with a copy from buffer
+            ASSERT(range.aspects == Aspect::Color);
+            const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(range.aspects).block;
+
+            uint32_t bytesPerRow = Align((GetWidth() / blockInfo.width) * blockInfo.byteSize,
+                                         device->GetOptimalBytesPerRowAlignment());
+            uint64_t bufferSize = bytesPerRow * (GetHeight() / blockInfo.height);
+            DynamicUploader* uploader = device->GetDynamicUploader();
+            UploadHandle uploadHandle;
+            DAWN_TRY_ASSIGN(uploadHandle,
+                            uploader->Allocate(bufferSize, device->GetPendingCommandSerial(),
+                                               blockInfo.byteSize));
+            memset(uploadHandle.mappedBuffer, uClearColor, bufferSize);
+
+            std::vector<VkBufferImageCopy> regions;
+            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
+                 ++level) {
+                imageRange.baseMipLevel = level;
+                for (uint32_t layer = range.baseArrayLayer;
+                     layer < range.baseArrayLayer + range.layerCount; ++layer) {
                     if (clearValue == TextureBase::ClearValue::Zero &&
                         IsSubresourceContentInitialized(
-                            SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
+                            SubresourceRange::SingleMipAndLayer(level, layer, range.aspects))) {
                         // Skip lazy clears if already initialized.
                         continue;
                     }
-                    aspects |= aspect;
+
+                    TextureDataLayout dataLayout;
+                    dataLayout.offset = uploadHandle.startOffset;
+                    dataLayout.rowsPerImage = GetHeight() / blockInfo.height;
+                    dataLayout.bytesPerRow = bytesPerRow;
+                    TextureCopy textureCopy;
+                    textureCopy.aspect = range.aspects;
+                    textureCopy.mipLevel = level;
+                    textureCopy.origin = {0, 0, layer};
+                    textureCopy.texture = this;
+
+                    regions.push_back(ComputeBufferImageCopyRegion(dataLayout, textureCopy,
+                                                                   GetMipLevelPhysicalSize(level)));
                 }
-
-                if (aspects == Aspect::None) {
-                    continue;
-                }
-
-                imageRange.aspectMask = VulkanAspectMask(aspects);
-                imageRange.baseArrayLayer = layer;
-
-                if (aspects & (Aspect::Depth | Aspect::Stencil)) {
-                    VkClearDepthStencilValue clearDepthStencilValue[1];
-                    clearDepthStencilValue[0].depth = fClearColor;
-                    clearDepthStencilValue[0].stencil = uClearColor;
-                    device->fn.CmdClearDepthStencilImage(recordingContext->commandBuffer,
-                                                         GetHandle(),
-                                                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                                                         clearDepthStencilValue, 1, &imageRange);
-                } else {
-                    ASSERT(aspects == Aspect::Color);
-                    VkClearColorValue clearColorValue;
-                    switch (GetFormat().GetAspectInfo(Aspect::Color).baseType) {
-                        case wgpu::TextureComponentType::Float:
-                            clearColorValue.float32[0] = fClearColor;
-                            clearColorValue.float32[1] = fClearColor;
-                            clearColorValue.float32[2] = fClearColor;
-                            clearColorValue.float32[3] = fClearColor;
-                            break;
-                        case wgpu::TextureComponentType::Sint:
-                            clearColorValue.int32[0] = sClearColor;
-                            clearColorValue.int32[1] = sClearColor;
-                            clearColorValue.int32[2] = sClearColor;
-                            clearColorValue.int32[3] = sClearColor;
-                            break;
-                        case wgpu::TextureComponentType::Uint:
-                            clearColorValue.uint32[0] = uClearColor;
-                            clearColorValue.uint32[1] = uClearColor;
-                            clearColorValue.uint32[2] = uClearColor;
-                            clearColorValue.uint32[3] = uClearColor;
-                            break;
-                        case wgpu::TextureComponentType::DepthComparison:
-                            UNREACHABLE();
+            }
+            device->fn.CmdCopyBufferToImage(
+                recordingContext->commandBuffer,
+                ToBackend(uploadHandle.stagingBuffer)->GetBufferHandle(), GetHandle(),
+                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, regions.data());
+        } else {
+            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
+                 ++level) {
+                imageRange.baseMipLevel = level;
+                for (uint32_t layer = range.baseArrayLayer;
+                     layer < range.baseArrayLayer + range.layerCount; ++layer) {
+                    Aspect aspects = Aspect::None;
+                    for (Aspect aspect : IterateEnumMask(range.aspects)) {
+                        if (clearValue == TextureBase::ClearValue::Zero &&
+                            IsSubresourceContentInitialized(
+                                SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
+                            // Skip lazy clears if already initialized.
+                            continue;
+                        }
+                        aspects |= aspect;
                     }
-                    device->fn.CmdClearColorImage(recordingContext->commandBuffer, GetHandle(),
-                                                  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                                                  &clearColorValue, 1, &imageRange);
+
+                    if (aspects == Aspect::None) {
+                        continue;
+                    }
+
+                    imageRange.aspectMask = VulkanAspectMask(aspects);
+                    imageRange.baseArrayLayer = layer;
+
+                    if (aspects & (Aspect::Depth | Aspect::Stencil)) {
+                        VkClearDepthStencilValue clearDepthStencilValue[1];
+                        clearDepthStencilValue[0].depth = fClearColor;
+                        clearDepthStencilValue[0].stencil = uClearColor;
+                        device->fn.CmdClearDepthStencilImage(
+                            recordingContext->commandBuffer, GetHandle(),
+                            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, clearDepthStencilValue, 1,
+                            &imageRange);
+                    } else {
+                        ASSERT(aspects == Aspect::Color);
+                        VkClearColorValue clearColorValue;
+                        switch (GetFormat().GetAspectInfo(Aspect::Color).baseType) {
+                            case wgpu::TextureComponentType::Float:
+                                clearColorValue.float32[0] = fClearColor;
+                                clearColorValue.float32[1] = fClearColor;
+                                clearColorValue.float32[2] = fClearColor;
+                                clearColorValue.float32[3] = fClearColor;
+                                break;
+                            case wgpu::TextureComponentType::Sint:
+                                clearColorValue.int32[0] = sClearColor;
+                                clearColorValue.int32[1] = sClearColor;
+                                clearColorValue.int32[2] = sClearColor;
+                                clearColorValue.int32[3] = sClearColor;
+                                break;
+                            case wgpu::TextureComponentType::Uint:
+                                clearColorValue.uint32[0] = uClearColor;
+                                clearColorValue.uint32[1] = uClearColor;
+                                clearColorValue.uint32[2] = uClearColor;
+                                clearColorValue.uint32[3] = uClearColor;
+                                break;
+                            case wgpu::TextureComponentType::DepthComparison:
+                                UNREACHABLE();
+                        }
+                        device->fn.CmdClearColorImage(recordingContext->commandBuffer, GetHandle(),
+                                                      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                                                      &clearColorValue, 1, &imageRange);
+                    }
                 }
             }
         }
@@ -1082,12 +1133,6 @@
             return;
         }
         if (!IsSubresourceContentInitialized(range)) {
-            // TODO(jiawei.shao@intel.com): initialize textures in BC formats with Buffer-to-Texture
-            // copies.
-            if (GetFormat().isCompressed) {
-                return;
-            }
-
             // If subresource has not been initialized, clear it to black as it could contain dirty
             // bits from recycled memory
             GetDevice()->ConsumedError(
diff --git a/src/tests/end2end/CompressedTextureFormatTests.cpp b/src/tests/end2end/CompressedTextureFormatTests.cpp
index 453da66..d0b0c30 100644
--- a/src/tests/end2end/CompressedTextureFormatTests.cpp
+++ b/src/tests/end2end/CompressedTextureFormatTests.cpp
@@ -419,16 +419,7 @@
         return sizeAtLevel;
     }
 
-    const std::array<wgpu::TextureFormat, 14> kBCFormats = {
-        wgpu::TextureFormat::BC1RGBAUnorm, wgpu::TextureFormat::BC1RGBAUnormSrgb,
-        wgpu::TextureFormat::BC2RGBAUnorm, wgpu::TextureFormat::BC2RGBAUnormSrgb,
-        wgpu::TextureFormat::BC3RGBAUnorm, wgpu::TextureFormat::BC3RGBAUnormSrgb,
-        wgpu::TextureFormat::BC4RSnorm,    wgpu::TextureFormat::BC4RUnorm,
-        wgpu::TextureFormat::BC5RGSnorm,   wgpu::TextureFormat::BC5RGUnorm,
-        wgpu::TextureFormat::BC6HRGBFloat, wgpu::TextureFormat::BC6HRGBUfloat,
-        wgpu::TextureFormat::BC7RGBAUnorm, wgpu::TextureFormat::BC7RGBAUnormSrgb};
-
-    // Tthe block width and height in texels are 4 for all BC formats.
+    // The block width and height in texels are 4 for all BC formats.
     static constexpr uint32_t kBCBlockWidthInTexels = 4;
     static constexpr uint32_t kBCBlockHeightInTexels = 4;
 
@@ -454,7 +445,7 @@
     config.textureDescriptor.size = {8, 8, 1};
     config.copyExtent3D = config.textureDescriptor.size;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -477,7 +468,7 @@
     config.copyOrigin3D = kOrigin;
     config.copyExtent3D = kExtent3D;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -503,7 +494,7 @@
     config.textureDescriptor.size.depth = kArrayLayerCount;
     config.copyOrigin3D.z = kArrayLayerCount - 1;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -543,7 +534,7 @@
 
     config.copyExtent3D = {kCopyWidthAtLevel, kCopyHeightAtLevel, 1};
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -580,7 +571,7 @@
     ASSERT_NE(0u, kVirtualSize.height % kBCBlockHeightInTexels);
 
     config.copyExtent3D = kPhysicalSize;
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC
         // compressed data.
         config.textureDescriptor.format = format;
@@ -643,7 +634,7 @@
     ASSERT_LT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width);
     ASSERT_LT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height);
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC
         // compressed data.
         srcConfig.textureDescriptor.format = format;
@@ -705,7 +696,7 @@
     ASSERT_GT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width);
     ASSERT_GT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height);
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         srcConfig.textureDescriptor.format = dstConfig.textureDescriptor.format = format;
         srcConfig.textureDescriptor.usage =
             wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
@@ -773,7 +764,7 @@
     ASSERT_NE(0u, dstVirtualSizes[1].width % kBCBlockWidthInTexels);
     ASSERT_NE(0u, dstVirtualSizes[1].height % kBCBlockHeightInTexels);
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         std::array<wgpu::Texture, kTotalCopyCount> bcSrcTextures;
         std::array<wgpu::Texture, kTotalCopyCount> bcDstTextures;
 
@@ -832,7 +823,7 @@
 
     const uint32_t blockCountPerRow = config.textureDescriptor.size.width / kBCBlockWidthInTexels;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
 
         const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format);
@@ -868,7 +859,7 @@
     const uint32_t slicePitchInBytes =
         config.bytesPerRowAlignment * (textureSizeLevel0.height / kBCBlockHeightInTexels);
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
 
         const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format);
@@ -902,7 +893,7 @@
 
     constexpr uint32_t kExceedRowBlockCount = 1;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
 
         const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format);
@@ -932,7 +923,7 @@
     const uint32_t blockCountPerRow = config.textureDescriptor.size.width / kBCBlockWidthInTexels;
     const uint32_t slicePitchInBytes = config.bytesPerRowAlignment;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
 
         const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format);
@@ -965,7 +956,7 @@
 
     config.rowsPerImage = config.textureDescriptor.size.height * 2 / kBCBlockHeightInTexels;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -1008,7 +999,7 @@
 
     config.rowsPerImage = kCopyHeightAtLevel * 2 / kBCBlockHeightInTexels;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -1036,7 +1027,7 @@
     config.copyExtent3D = config.textureDescriptor.size;
     config.copyExtent3D.depth = kArrayLayerCount;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -1066,7 +1057,7 @@
     config.copyExtent3D = config.textureDescriptor.size;
     config.copyExtent3D.depth = kCopyLayerCount;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestCopyRegionIntoBCFormatTextures(config);
     }
@@ -1158,7 +1149,7 @@
     config.bytesPerRowAlignment = 511;
     config.rowsPerImage = 5;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestWriteRegionIntoBCFormatTextures(config);
     }
@@ -1179,7 +1170,7 @@
     config.bytesPerRowAlignment = 511;
     config.rowsPerImage = 5;
 
-    for (wgpu::TextureFormat format : kBCFormats) {
+    for (wgpu::TextureFormat format : utils::kBCFormats) {
         config.textureDescriptor.format = format;
         TestWriteRegionIntoBCFormatTextures(config);
     }
@@ -1199,7 +1190,7 @@
     // the texture physical size, but doesn't fit in the virtual size.
     for (unsigned int w : {12, 16}) {
         for (unsigned int h : {12, 16}) {
-            for (wgpu::TextureFormat format : kBCFormats) {
+            for (wgpu::TextureFormat format : utils::kBCFormats) {
                 CopyConfig config;
                 config.textureDescriptor.usage = kDefaultBCFormatTextureUsage;
                 config.textureDescriptor.size = {60, 60, 1};
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index e8ec0cc..352d970 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -31,7 +31,6 @@
         }                                                                                     \
     } while (0)
 
-// TODO(natlee@microsoft.com): test compressed textures are cleared
 class TextureZeroInitTest : public DawnTest {
   protected:
     void SetUp() override {
@@ -53,10 +52,12 @@
         descriptor.usage = usage;
         return descriptor;
     }
-    wgpu::TextureViewDescriptor CreateTextureViewDescriptor(uint32_t baseMipLevel,
-                                                            uint32_t baseArrayLayer) {
+    wgpu::TextureViewDescriptor CreateTextureViewDescriptor(
+        uint32_t baseMipLevel,
+        uint32_t baseArrayLayer,
+        wgpu::TextureFormat format = kColorFormat) {
         wgpu::TextureViewDescriptor descriptor;
-        descriptor.format = kColorFormat;
+        descriptor.format = format;
         descriptor.baseArrayLayer = baseArrayLayer;
         descriptor.arrayLayerCount = 1;
         descriptor.baseMipLevel = baseMipLevel;
@@ -108,10 +109,11 @@
                 fragColor = texelFetch(sampler2D(texture0, sampler0), ivec2(gl_FragCoord), 0);
             })");
     }
+
     constexpr static uint32_t kSize = 128;
     constexpr static uint32_t kUnalignedSize = 127;
-    // All three texture formats used (RGBA8Unorm, Depth24PlusStencil8, and RGBA8Snorm) have the
-    // same byte size of 4.
+    // All texture formats used (RGBA8Unorm, Depth24PlusStencil8, and RGBA8Snorm, BC formats)
+    // have the same block byte size of 4.
     constexpr static uint32_t kFormatBlockByteSize = 4;
     constexpr static wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
     constexpr static wgpu::TextureFormat kDepthStencilFormat =
@@ -1644,3 +1646,342 @@
                       OpenGLBackend({"nonzero_clear_resources_on_creation_for_testing"}),
                       MetalBackend({"nonzero_clear_resources_on_creation_for_testing"}),
                       VulkanBackend({"nonzero_clear_resources_on_creation_for_testing"}));
+
+class CompressedTextureZeroInitTest : public TextureZeroInitTest {
+  protected:
+    void SetUp() override {
+        DawnTest::SetUp();
+        DAWN_SKIP_TEST_IF(UsesWire());
+        DAWN_SKIP_TEST_IF(!IsBCFormatSupported());
+        // TODO: find out why this test is flaky on Windows Intel Vulkan bots.
+        DAWN_SKIP_TEST_IF(IsIntel() && IsVulkan() && IsWindows());
+    }
+
+    std::vector<const char*> GetRequiredExtensions() override {
+        mIsBCFormatSupported = SupportsExtensions({"texture_compression_bc"});
+        if (!mIsBCFormatSupported) {
+            return {};
+        }
+
+        return {"texture_compression_bc"};
+    }
+
+    bool IsBCFormatSupported() const {
+        return mIsBCFormatSupported;
+    }
+
+    // Copy the compressed texture data into the destination texture.
+    void InitializeDataInCompressedTextureAndExpectLazyClear(
+        wgpu::Texture bcCompressedTexture,
+        wgpu::TextureDescriptor textureDescriptor,
+        wgpu::Extent3D copyExtent3D,
+        uint32_t viewMipmapLevel,
+        uint32_t baseArrayLayer,
+        size_t lazyClearCount) {
+        uint32_t copyWidthInBlock = copyExtent3D.width / kFormatBlockByteSize;
+        uint32_t copyHeightInBlock = copyExtent3D.height / kFormatBlockByteSize;
+        uint32_t copyBytesPerRow =
+            Align(copyWidthInBlock * utils::GetTexelBlockSizeInBytes(textureDescriptor.format),
+                  kTextureBytesPerRowAlignment);
+
+        std::vector<uint8_t> data(
+            utils::RequiredBytesInCopy(copyBytesPerRow, copyHeightInBlock, copyExtent3D,
+                                       textureDescriptor.format),
+            1);
+
+        // Copy texture data from a staging buffer to the destination texture.
+        wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(device, data.data(), data.size(),
+                                                                 wgpu::BufferUsage::CopySrc);
+        wgpu::BufferCopyView bufferCopyView =
+            utils::CreateBufferCopyView(stagingBuffer, 0, copyBytesPerRow, copyHeightInBlock);
+
+        wgpu::TextureCopyView textureCopyView = utils::CreateTextureCopyView(
+            bcCompressedTexture, viewMipmapLevel, {0, 0, baseArrayLayer});
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &copyExtent3D);
+        wgpu::CommandBuffer copy = encoder.Finish();
+        EXPECT_LAZY_CLEAR(lazyClearCount, queue.Submit(1, &copy));
+    }
+
+    // Run the tests that copies pre-prepared BC format data into a BC texture and verifies if we
+    // can render correctly with the pixel values sampled from the BC texture.
+    // Expect that the texture subresource is initialized
+    void TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        wgpu::TextureDescriptor textureDescriptor,
+        wgpu::Extent3D copyExtent3D,
+        wgpu::Extent3D nonPaddedCopyExtent,
+        uint32_t viewMipmapLevel,
+        uint32_t baseArrayLayer,
+        size_t lazyClearCount,
+        bool halfCopyTest = false) {
+        wgpu::Texture bcTexture = device.CreateTexture(&textureDescriptor);
+        InitializeDataInCompressedTextureAndExpectLazyClear(bcTexture, textureDescriptor,
+                                                            copyExtent3D, viewMipmapLevel,
+                                                            baseArrayLayer, lazyClearCount);
+
+        SampleCompressedTextureAndVerifyColor(bcTexture, textureDescriptor, copyExtent3D,
+                                              nonPaddedCopyExtent, viewMipmapLevel, baseArrayLayer,
+                                              halfCopyTest);
+    }
+
+    void SampleCompressedTextureAndVerifyColor(wgpu::Texture bcTexture,
+                                               wgpu::TextureDescriptor textureDescriptor,
+                                               wgpu::Extent3D copyExtent3D,
+                                               wgpu::Extent3D nonPaddedCopyExtent,
+                                               uint32_t viewMipmapLevel,
+                                               uint32_t baseArrayLayer,
+                                               bool halfCopyTest = false) {
+        // Sample the compressed texture and verify the texture colors in the render target
+        utils::BasicRenderPass renderPass =
+            utils::CreateBasicRenderPass(device, textureDescriptor.size.width >> viewMipmapLevel,
+                                         textureDescriptor.size.height >> viewMipmapLevel);
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        {
+            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+            utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
+            renderPipelineDescriptor.cColorStates[0].format = kColorFormat;
+            renderPipelineDescriptor.vertexStage.module = CreateBasicVertexShaderForTest();
+            renderPipelineDescriptor.cFragmentStage.module =
+                CreateSampledTextureFragmentShaderForTest();
+            wgpu::RenderPipeline renderPipeline =
+                device.CreateRenderPipeline(&renderPipelineDescriptor);
+            pass.SetPipeline(renderPipeline);
+
+            wgpu::SamplerDescriptor samplerDesc = {};
+            wgpu::Sampler sampler = device.CreateSampler(&samplerDesc);
+
+            wgpu::TextureViewDescriptor textureViewDescriptor = CreateTextureViewDescriptor(
+                viewMipmapLevel, baseArrayLayer, textureDescriptor.format);
+            wgpu::BindGroup bindGroup = utils::MakeBindGroup(
+                device, renderPipeline.GetBindGroupLayout(0),
+                {{0, sampler}, {1, bcTexture.CreateView(&textureViewDescriptor)}});
+            pass.SetBindGroup(0, bindGroup);
+            pass.Draw(6);
+            pass.EndPass();
+        }
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        std::vector<RGBA8> expected(nonPaddedCopyExtent.width * nonPaddedCopyExtent.height,
+                                    {0x00, 0x20, 0x08, 0xFF});
+        EXPECT_TEXTURE_RGBA8_EQ(expected.data(), renderPass.color, 0, 0, nonPaddedCopyExtent.width,
+                                nonPaddedCopyExtent.height, 0, 0);
+        EXPECT_TRUE(dawn_native::IsTextureSubresourceInitialized(bcTexture.Get(), viewMipmapLevel,
+                                                                 1, baseArrayLayer, 1));
+
+        // If we only copied to half the texture, check the other half is initialized to black
+        if (halfCopyTest) {
+            std::vector<RGBA8> expectBlack(nonPaddedCopyExtent.width * nonPaddedCopyExtent.height,
+                                           {0x00, 0x00, 0x00, 0xFF});
+            EXPECT_TEXTURE_RGBA8_EQ(expectBlack.data(), renderPass.color, copyExtent3D.width, 0,
+                                    nonPaddedCopyExtent.width, nonPaddedCopyExtent.height, 0, 0);
+        }
+    }
+
+    bool mIsBCFormatSupported = false;
+};
+
+//  Test that the clearing is skipped when we use a full mip copy (with the physical size different
+//  than the virtual mip size)
+TEST_P(CompressedTextureZeroInitTest, FullMipCopy) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    textureDescriptor.size = {60, 60, 1};
+    textureDescriptor.mipLevelCount = 1;
+    textureDescriptor.format = utils::kBCFormats[0];
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, textureDescriptor.size, textureDescriptor.size, 0, 0, 0u);
+}
+
+// Test that 1 lazy clear count happens when we copy to half the texture
+TEST_P(CompressedTextureZeroInitTest, HalfCopyBufferToTexture) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    constexpr static uint32_t kSize = 16;
+    textureDescriptor.size = {kSize, kSize, 1};
+    textureDescriptor.mipLevelCount = 1;
+    textureDescriptor.format = utils::kBCFormats[0];
+
+    wgpu::Extent3D copyExtent3D = {kSize / 2, kSize, 1};
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, copyExtent3D, copyExtent3D, 0, 0, 1u, true);
+}
+
+// Test that 0 lazy clear count happens when we copy buffer to texture to a nonzero mip level
+// (with physical size different from the virtual mip size)
+TEST_P(CompressedTextureZeroInitTest, FullCopyToNonZeroMipLevel) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    constexpr static uint32_t kSize = 60;
+    textureDescriptor.size = {kSize, kSize, 1};
+    textureDescriptor.mipLevelCount = 3;
+    textureDescriptor.format = utils::kBCFormats[0];
+    const uint32_t kViewMipLevel = 2;
+    const uint32_t kActualSizeAtLevel = kSize >> kViewMipLevel;
+
+    const uint32_t kCopySizeAtLevel = Align(kActualSizeAtLevel, kFormatBlockByteSize);
+
+    wgpu::Extent3D copyExtent3D = {kCopySizeAtLevel, kCopySizeAtLevel, 1};
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, copyExtent3D, {kActualSizeAtLevel, kActualSizeAtLevel, 1}, kViewMipLevel,
+        0, 0u);
+}
+
+// Test that 1 lazy clear count happens when we copy buffer to half texture to a nonzero mip level
+// (with physical size different from the virtual mip size)
+TEST_P(CompressedTextureZeroInitTest, HalfCopyToNonZeroMipLevel) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    constexpr static uint32_t kSize = 60;
+    textureDescriptor.size = {kSize, kSize, 1};
+    textureDescriptor.mipLevelCount = 3;
+    textureDescriptor.format = utils::kBCFormats[0];
+    const uint32_t kViewMipLevel = 2;
+    const uint32_t kActualSizeAtLevel = kSize >> kViewMipLevel;
+
+    const uint32_t kCopySizeAtLevel = Align(kActualSizeAtLevel, kFormatBlockByteSize);
+
+    wgpu::Extent3D copyExtent3D = {kCopySizeAtLevel / 2, kCopySizeAtLevel, 1};
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, copyExtent3D, {kActualSizeAtLevel / 2, kActualSizeAtLevel, 1},
+        kViewMipLevel, 0, 1u, true);
+}
+
+// Test that 0 lazy clear count happens when we copy buffer to nonzero array layer
+TEST_P(CompressedTextureZeroInitTest, FullCopyToNonZeroArrayLayer) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    constexpr static uint32_t kSize = 16;
+    constexpr static uint32_t kArrayLayers = 4;
+    textureDescriptor.size = {kSize, kSize, kArrayLayers};
+    textureDescriptor.mipLevelCount = 1;
+    textureDescriptor.format = utils::kBCFormats[0];
+
+    wgpu::Extent3D copyExtent3D = {kSize, kSize, 1};
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, copyExtent3D, copyExtent3D, 0, kArrayLayers - 2, 0u);
+}
+
+// Test that 1 lazy clear count happens when we copy buffer to half texture to a nonzero array layer
+TEST_P(CompressedTextureZeroInitTest, HalfCopyToNonZeroArrayLayer) {
+    wgpu::TextureDescriptor textureDescriptor;
+    textureDescriptor.usage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled;
+    constexpr static uint32_t kSize = 16;
+    constexpr static uint32_t kArrayLayers = 4;
+    textureDescriptor.size = {kSize, kSize, kArrayLayers};
+    textureDescriptor.mipLevelCount = 3;
+    textureDescriptor.format = utils::kBCFormats[0];
+
+    wgpu::Extent3D copyExtent3D = {kSize / 2, kSize, 1};
+
+    TestCopyRegionIntoBCFormatTexturesAndCheckSubresourceIsInitialized(
+        textureDescriptor, copyExtent3D, copyExtent3D, 0, kArrayLayers - 2, 1u, true);
+}
+
+// full copy texture to texture, 0 lazy clears are needed
+TEST_P(CompressedTextureZeroInitTest, FullCopyTextureToTextureMipLevel) {
+    // create srcTexture and fill it with data
+    wgpu::TextureDescriptor srcDescriptor = CreateTextureDescriptor(
+        3, 1,
+        wgpu::TextureUsage::Sampled | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst,
+        utils::kBCFormats[0]);
+    wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor);
+
+    const uint32_t kViewMipLevel = 2;
+    const uint32_t kActualSizeAtLevel = kSize >> kViewMipLevel;
+
+    const uint32_t kCopySizeAtLevel = Align(kActualSizeAtLevel, kFormatBlockByteSize);
+
+    wgpu::Extent3D copyExtent3D = {kCopySizeAtLevel, kCopySizeAtLevel, 1};
+
+    // fill srcTexture with data
+    InitializeDataInCompressedTextureAndExpectLazyClear(srcTexture, srcDescriptor, copyExtent3D,
+                                                        kViewMipLevel, 0, 0u);
+
+    wgpu::TextureCopyView srcTextureCopyView =
+        utils::CreateTextureCopyView(srcTexture, kViewMipLevel, {0, 0, 0});
+
+    // create dstTexture that we will copy to
+    wgpu::TextureDescriptor dstDescriptor = CreateTextureDescriptor(
+        3, 1,
+        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::Sampled,
+        utils::kBCFormats[0]);
+    wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor);
+
+    wgpu::TextureCopyView dstTextureCopyView =
+        utils::CreateTextureCopyView(dstTexture, kViewMipLevel, {0, 0, 0});
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &copyExtent3D);
+    wgpu::CommandBuffer commands = encoder.Finish();
+    // the dstTexture does not need to be lazy cleared since it's fully copied to
+    EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commands));
+
+    SampleCompressedTextureAndVerifyColor(dstTexture, dstDescriptor, copyExtent3D,
+                                          {kActualSizeAtLevel, kActualSizeAtLevel, 1},
+                                          kViewMipLevel, 0);
+}
+
+// half copy texture to texture, lazy clears are needed for noncopied half
+TEST_P(CompressedTextureZeroInitTest, HalfCopyTextureToTextureMipLevel) {
+    // create srcTexture with data
+    wgpu::TextureDescriptor srcDescriptor = CreateTextureDescriptor(
+        3, 1,
+        wgpu::TextureUsage::Sampled | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst,
+        utils::kBCFormats[0]);
+    wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor);
+
+    const uint32_t kViewMipLevel = 2;
+    const uint32_t kActualSizeAtLevel = kSize >> kViewMipLevel;
+
+    const uint32_t kCopySizeAtLevel = Align(kActualSizeAtLevel, kFormatBlockByteSize);
+
+    wgpu::Extent3D copyExtent3D = {kCopySizeAtLevel / 2, kCopySizeAtLevel, 1};
+
+    // fill srcTexture with data
+    InitializeDataInCompressedTextureAndExpectLazyClear(srcTexture, srcDescriptor, copyExtent3D,
+                                                        kViewMipLevel, 0, 1u);
+
+    wgpu::TextureCopyView srcTextureCopyView =
+        utils::CreateTextureCopyView(srcTexture, kViewMipLevel, {0, 0, 0});
+
+    // create dstTexture that we will copy to
+    wgpu::TextureDescriptor dstDescriptor = CreateTextureDescriptor(
+        3, 1,
+        wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::Sampled,
+        utils::kBCFormats[0]);
+    wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor);
+
+    wgpu::TextureCopyView dstTextureCopyView =
+        utils::CreateTextureCopyView(dstTexture, kViewMipLevel, {0, 0, 0});
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &copyExtent3D);
+    wgpu::CommandBuffer commands = encoder.Finish();
+    // expect 1 lazy clear count since the dstTexture needs to be lazy cleared when we only copy to
+    // half texture
+    EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commands));
+
+    SampleCompressedTextureAndVerifyColor(dstTexture, dstDescriptor, copyExtent3D,
+                                          {kActualSizeAtLevel / 2, kActualSizeAtLevel, 1},
+                                          kViewMipLevel, 0, true);
+}
+
+DAWN_INSTANTIATE_TEST(CompressedTextureZeroInitTest,
+                      D3D12Backend({"nonzero_clear_resources_on_creation_for_testing"}),
+                      MetalBackend({"nonzero_clear_resources_on_creation_for_testing"}),
+                      VulkanBackend({"nonzero_clear_resources_on_creation_for_testing"}));