Allow unaligned source offset in writeTexture

With some refactoring of the relevant validation code.

Bug: dawn:520
Change-Id: Iedda0f7b1b67c20d3a88f2c4183dcc8eeae2096f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/30742
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 0df7870..5468387 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -101,36 +101,20 @@
             return {};
         }
 
-        MaybeError ValidateTextureToBufferCopyRestrictions(const TextureCopyView& src) {
-            const Format& format = src.texture->GetFormat();
-
-            bool depthSelected = false;
-            switch (src.aspect) {
-                case wgpu::TextureAspect::All:
-                    switch (format.aspects) {
-                        case Aspect::Color:
-                        case Aspect::Stencil:
-                            break;
-                        case Aspect::Depth:
-                            depthSelected = true;
-                            break;
-                        default:
-                            return DAWN_VALIDATION_ERROR(
-                                "A single aspect must be selected for multi planar formats in "
-                                "texture to buffer copies");
-                    }
-                    break;
-                case wgpu::TextureAspect::DepthOnly:
-                    ASSERT(format.aspects & Aspect::Depth);
-                    depthSelected = true;
-                    break;
-                case wgpu::TextureAspect::StencilOnly:
-                    ASSERT(format.aspects & Aspect::Stencil);
-                    break;
+        MaybeError ValidateLinearTextureCopyOffset(const TextureDataLayout& layout,
+                                                   const TexelBlockInfo& blockInfo) {
+            if (layout.offset % blockInfo.byteSize != 0) {
+                return DAWN_VALIDATION_ERROR(
+                    "offset must be a multiple of the texel block byte size.");
             }
+            return {};
+        }
 
-            if (depthSelected) {
-                switch (format.format) {
+        MaybeError ValidateTextureDepthStencilToBufferCopyRestrictions(const TextureCopyView& src) {
+            Aspect aspectUsed;
+            DAWN_TRY_ASSIGN(aspectUsed, SingleAspectUsedByTextureCopyView(src));
+            if (aspectUsed == Aspect::Depth) {
+                switch (src.texture->GetFormat().format) {
                     case wgpu::TextureFormat::Depth24Plus:
                     case wgpu::TextureFormat::Depth24PlusStencil8:
                         return DAWN_VALIDATION_ERROR(
@@ -640,7 +624,7 @@
                 DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst));
                 DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(destination->texture));
 
-                DAWN_TRY(ValidateBufferToTextureCopyRestrictions(*destination));
+                DAWN_TRY(ValidateLinearToDepthStencilCopyRestrictions(*destination));
                 // We validate texture copy range before validating linear texture data,
                 // because in the latter we divide copyExtent.width by blockWidth and
                 // copyExtent.height by blockHeight while the divisibility conditions are
@@ -650,6 +634,7 @@
             const TexelBlockInfo& blockInfo =
                 destination->texture->GetFormat().GetAspectInfo(destination->aspect).block;
             if (GetDevice()->IsValidationEnabled()) {
+                DAWN_TRY(ValidateLinearTextureCopyOffset(source->layout, blockInfo));
                 DAWN_TRY(ValidateLinearTextureData(source->layout, source->buffer->GetSize(),
                                                    blockInfo, *copySize));
 
@@ -700,11 +685,11 @@
                 DAWN_TRY(ValidateTextureCopyView(GetDevice(), *source, *copySize));
                 DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc));
                 DAWN_TRY(ValidateTextureSampleCountInBufferCopyCommands(source->texture));
+                DAWN_TRY(ValidateTextureDepthStencilToBufferCopyRestrictions(*source));
 
                 DAWN_TRY(ValidateBufferCopyView(GetDevice(), *destination));
                 DAWN_TRY(ValidateCanUseAs(destination->buffer, wgpu::BufferUsage::CopyDst));
 
-                DAWN_TRY(ValidateTextureToBufferCopyRestrictions(*source));
                 // We validate texture copy range before validating linear texture data,
                 // because in the latter we divide copyExtent.width by blockWidth and
                 // copyExtent.height by blockHeight while the divisibility conditions are
@@ -714,6 +699,7 @@
             const TexelBlockInfo& blockInfo =
                 source->texture->GetFormat().GetAspectInfo(source->aspect).block;
             if (GetDevice()->IsValidationEnabled()) {
+                DAWN_TRY(ValidateLinearTextureCopyOffset(destination->layout, blockInfo));
                 DAWN_TRY(ValidateLinearTextureData(
                     destination->layout, destination->buffer->GetSize(), blockInfo, *copySize));
 
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 7e978f2..4a19243 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -446,11 +446,6 @@
                                          uint64_t byteSize,
                                          const TexelBlockInfo& blockInfo,
                                          const Extent3D& copyExtent) {
-        // Validation for the texel block alignments:
-        if (layout.offset % blockInfo.byteSize != 0) {
-            return DAWN_VALIDATION_ERROR("Offset must be a multiple of the texel or block size");
-        }
-
         ASSERT(copyExtent.width % blockInfo.width == 0);
         uint32_t widthInBlocks = copyExtent.width / blockInfo.width;
         ASSERT(copyExtent.height % blockInfo.height == 0);
@@ -589,36 +584,35 @@
         return {};
     }
 
-    MaybeError ValidateBufferToTextureCopyRestrictions(const TextureCopyView& dst) {
-        const Format& format = dst.texture->GetFormat();
-
-        bool depthSelected = false;
-        switch (dst.aspect) {
+    // Always returns a single aspect (color, stencil, or depth).
+    ResultOrError<Aspect> SingleAspectUsedByTextureCopyView(const TextureCopyView& view) {
+        const Format& format = view.texture->GetFormat();
+        switch (view.aspect) {
             case wgpu::TextureAspect::All:
-                switch (format.aspects) {
-                    case Aspect::Color:
-                    case Aspect::Stencil:
-                        break;
-                    case Aspect::Depth:
-                        depthSelected = true;
-                        break;
-                    default:
-                        return DAWN_VALIDATION_ERROR(
-                            "A single aspect must be selected for multi planar formats in buffer "
-                            "to texture copies");
+                if (HasOneBit(format.aspects)) {
+                    return Aspect{format.aspects};
+                } else {
+                    return DAWN_VALIDATION_ERROR(
+                        "A single aspect must be selected for multi-planar formats in "
+                        "texture <-> linear data copies");
                 }
                 break;
             case wgpu::TextureAspect::DepthOnly:
                 ASSERT(format.aspects & Aspect::Depth);
-                depthSelected = true;
-                break;
+                return Aspect::Depth;
             case wgpu::TextureAspect::StencilOnly:
                 ASSERT(format.aspects & Aspect::Stencil);
-                break;
+                return Aspect::Stencil;
         }
-        if (depthSelected) {
+    }
+
+    MaybeError ValidateLinearToDepthStencilCopyRestrictions(const TextureCopyView& dst) {
+        Aspect aspectUsed;
+        DAWN_TRY_ASSIGN(aspectUsed, SingleAspectUsedByTextureCopyView(dst));
+        if (aspectUsed == Aspect::Depth) {
             return DAWN_VALIDATION_ERROR("Cannot copy into the depth aspect of a texture");
         }
+
         return {};
     }
 
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 3853b98..faf6f12 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -57,7 +57,8 @@
                                          const Extent3D& copyExtent);
     MaybeError ValidateTextureCopyRange(const TextureCopyView& textureCopyView,
                                         const Extent3D& copySize);
-    MaybeError ValidateBufferToTextureCopyRestrictions(const TextureCopyView& dst);
+    ResultOrError<Aspect> SingleAspectUsedByTextureCopyView(const TextureCopyView& view);
+    MaybeError ValidateLinearToDepthStencilCopyRestrictions(const TextureCopyView& dst);
 
     MaybeError ValidateBufferCopyView(DeviceBase const* device,
                                       const BufferCopyView& bufferCopyView);
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 519cf93..cd3a135 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -415,7 +415,7 @@
             return DAWN_VALIDATION_ERROR("The sample count of textures must be 1");
         }
 
-        DAWN_TRY(ValidateBufferToTextureCopyRestrictions(*destination));
+        DAWN_TRY(ValidateLinearToDepthStencilCopyRestrictions(*destination));
         // We validate texture copy range before validating linear texture data,
         // because in the latter we divide copyExtent.width by blockWidth and
         // copyExtent.height by blockHeight while the divisibility conditions are
diff --git a/src/tests/end2end/QueueTests.cpp b/src/tests/end2end/QueueTests.cpp
index d74ec00..bcda12f 100644
--- a/src/tests/end2end/QueueTests.cpp
+++ b/src/tests/end2end/QueueTests.cpp
@@ -446,9 +446,8 @@
     textureSpec.textureSize = {kWidth, kHeight, 1};
     textureSpec.level = 0;
 
-    for (unsigned int i : {1, 2, 4, 17, 64, 128, 300}) {
+    for (uint64_t offset : {1, 2, 4, 17, 64, 128, 300}) {
         DataSpec dataSpec = MinimumDataSpec({kWidth, kHeight, 1});
-        uint64_t offset = i * utils::GetTexelBlockSizeInBytes(kTextureFormat);
         dataSpec.size += offset;
         dataSpec.offset += offset;
         DoTest(textureSpec, dataSpec, {kWidth, kHeight, 1});
diff --git a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
index 277c6f2..6b1c8de 100644
--- a/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
+++ b/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
@@ -278,18 +278,20 @@
             TestWriteTexture(dataSize, 0, 256, 3, destination, 0, {0, 0, 0}, {4, 4, 1}));
     }
 
-    // Test WriteTexture with incorrect data offset usage
-    TEST_F(QueueWriteTextureValidationTest, IncorrectDataOffset) {
+    // Test WriteTexture with data offset
+    TEST_F(QueueWriteTextureValidationTest, DataOffset) {
         uint64_t dataSize =
             utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
         wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
                                                     wgpu::TextureUsage::CopyDst);
 
-        // Correct usage
+        // Offset aligned
         TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
-
+        // Offset not aligned
+        TestWriteTexture(dataSize, dataSize - 5, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
+        // Offset+size too large
         ASSERT_DEVICE_ERROR(
-            TestWriteTexture(dataSize, dataSize - 6, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1}));
+            TestWriteTexture(dataSize, dataSize - 3, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1}));
     }
 
     // Test multisampled textures can be used in WriteTexture.
@@ -551,23 +553,21 @@
         static constexpr uint32_t kHeight = 16;
     };
 
-    // Tests to verify that data offset must be a multiple of the compressed texture blocks in bytes
+    // Tests to verify that data offset may not be a multiple of the compressed texture block size
     TEST_F(WriteTextureTest_CompressedTextureFormats, DataOffset) {
         for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
             wgpu::Texture texture = Create2DTexture(bcFormat);
 
-            // Valid usages of data offset.
+            // Valid if aligned.
             {
-                uint32_t validDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat);
-                QueueWriteTextureValidationTest::TestWriteTexture(512, validDataOffset, 256, 4,
-                                                                  texture, 0, {0, 0, 0}, {4, 4, 1});
+                uint32_t kAlignedOffset = utils::GetTexelBlockSizeInBytes(bcFormat);
+                TestWriteTexture(1024, kAlignedOffset, 256, 4, texture, 0, {0, 0, 0}, {4, 16, 1});
             }
 
-            // Failures on invalid data offset.
+            // Still valid if not aligned.
             {
-                uint32_t kInvalidDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat) / 2;
-                ASSERT_DEVICE_ERROR(TestWriteTexture(512, kInvalidDataOffset, 256, 4, texture, 0,
-                                                     {0, 0, 0}, {4, 4, 1}));
+                uint32_t kUnalignedOffset = utils::GetTexelBlockSizeInBytes(bcFormat) - 1;
+                TestWriteTexture(1024, kUnalignedOffset, 256, 4, texture, 0, {0, 0, 0}, {4, 16, 1});
             }
         }
     }