Add texture creation validation rules for 3D texture

In order to support 3D texture, new validation rules are added:
- to say that multisample 3D texture is not supported.
- to distinguish 3D texture from 2D array texture via texture type,
  and its impact on max values of size.depth, mipmap levels,
  array layer count, etc.
- to say that 3D compressed texture is not supported.

This change also adds validation tests for zero-sized textures,
in addition to validation tests for the validation rules above.

Bug: dawn:558
Change-Id: Ib7d398fdab49a702eaa798f6353639d3721747e6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/34922
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index bba6bce..211abfd 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -48,8 +48,10 @@
                 case wgpu::TextureViewDimension::CubeArray:
                     return textureDimension == wgpu::TextureDimension::e2D;
 
-                case wgpu::TextureViewDimension::e1D:
                 case wgpu::TextureViewDimension::e3D:
+                    return textureDimension == wgpu::TextureDimension::e3D;
+
+                case wgpu::TextureViewDimension::e1D:
                 case wgpu::TextureViewDimension::Undefined:
                     UNREACHABLE();
             }
@@ -106,11 +108,12 @@
                         "The mipmap level count of a multisampled texture must be 1.");
                 }
 
+                // Multisampled 1D and 3D textures are not supported in D3D12/Metal/Vulkan.
                 // Multisampled 2D array texture is not supported because on Metal it requires the
                 // version of macOS be greater than 10.14.
-                if (descriptor->size.depth > 1) {
-                    return DAWN_VALIDATION_ERROR(
-                        "Multisampled textures with depth > 1 are not supported.");
+                if (descriptor->dimension != wgpu::TextureDimension::e2D ||
+                    descriptor->size.depth > 1) {
+                    return DAWN_VALIDATION_ERROR("Multisampled texture must be 2D with depth=1");
                 }
 
                 if (format->isCompressed) {
@@ -154,16 +157,40 @@
         }
 
         MaybeError ValidateTextureSize(const TextureDescriptor* descriptor, const Format* format) {
-            ASSERT(descriptor->size.width != 0 && descriptor->size.height != 0);
-            if (descriptor->size.width > kMaxTextureDimension2D ||
-                descriptor->size.height > kMaxTextureDimension2D) {
-                return DAWN_VALIDATION_ERROR("Texture max size exceeded");
+            ASSERT(descriptor->size.width != 0 && descriptor->size.height != 0 &&
+                   descriptor->size.depth != 0);
+
+            Extent3D maxExtent;
+            switch (descriptor->dimension) {
+                case wgpu::TextureDimension::e2D:
+                    maxExtent = {kMaxTextureDimension2D, kMaxTextureDimension2D,
+                                 kMaxTextureArrayLayers};
+                    break;
+                case wgpu::TextureDimension::e3D:
+                    maxExtent = {kMaxTextureDimension3D, kMaxTextureDimension3D,
+                                 kMaxTextureDimension3D};
+                    break;
+                case wgpu::TextureDimension::e1D:
+                default:
+                    UNREACHABLE();
+            }
+            if (descriptor->size.width > maxExtent.width ||
+                descriptor->size.height > maxExtent.height ||
+                descriptor->size.depth > maxExtent.depth) {
+                return DAWN_VALIDATION_ERROR("Texture dimension (width, height or depth) exceeded");
             }
 
-            if (Log2(std::max(descriptor->size.width, descriptor->size.height)) + 1 <
-                descriptor->mipLevelCount) {
+            uint32_t maxMippedDimension = descriptor->size.width;
+            if (descriptor->dimension != wgpu::TextureDimension::e1D) {
+                maxMippedDimension = std::max(maxMippedDimension, descriptor->size.height);
+            }
+            if (descriptor->dimension == wgpu::TextureDimension::e3D) {
+                maxMippedDimension = std::max(maxMippedDimension, descriptor->size.depth);
+            }
+            if (Log2(maxMippedDimension) + 1 < descriptor->mipLevelCount) {
                 return DAWN_VALIDATION_ERROR("Texture has too many mip levels");
             }
+            ASSERT(descriptor->mipLevelCount <= kMaxTexture2DMipLevels);
 
             if (format->isCompressed) {
                 const TexelBlockInfo& blockInfo =
@@ -175,14 +202,6 @@
                 }
             }
 
-            if (descriptor->dimension == wgpu::TextureDimension::e2D &&
-                descriptor->size.depth > kMaxTextureArrayLayers) {
-                return DAWN_VALIDATION_ERROR("Texture 2D array layer count exceeded");
-            }
-            if (descriptor->mipLevelCount > kMaxTexture2DMipLevels) {
-                return DAWN_VALIDATION_ERROR("Max texture 2D mip level exceeded");
-            }
-
             return {};
         }
 
@@ -221,6 +240,9 @@
         if (descriptor->nextInChain != nullptr) {
             return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
         }
+        if (descriptor->dimension == wgpu::TextureDimension::e1D) {
+            return DAWN_VALIDATION_ERROR("1D textures aren't supported (yet).");
+        }
 
         const Format* format;
         DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format));
@@ -235,8 +257,15 @@
             return DAWN_VALIDATION_ERROR("Cannot create an empty texture");
         }
 
-        if (descriptor->dimension != wgpu::TextureDimension::e2D) {
-            return DAWN_VALIDATION_ERROR("Texture dimension must be 2D (for now)");
+        // Disallow 1D and 3D textures as unsafe until they are fully implemented.
+        if (descriptor->dimension != wgpu::TextureDimension::e2D &&
+            device->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) {
+            return DAWN_VALIDATION_ERROR(
+                "1D and 3D textures are disallowed because they are not fully implemented ");
+        }
+
+        if (descriptor->dimension != wgpu::TextureDimension::e2D && format->isCompressed) {
+            return DAWN_VALIDATION_ERROR("Compressed texture must be 2D");
         }
 
         DAWN_TRY(ValidateTextureSize(descriptor, format));
diff --git a/src/tests/unittests/validation/TextureValidationTests.cpp b/src/tests/unittests/validation/TextureValidationTests.cpp
index 458ce00..1a5eb96 100644
--- a/src/tests/unittests/validation/TextureValidationTests.cpp
+++ b/src/tests/unittests/validation/TextureValidationTests.cpp
@@ -92,7 +92,20 @@
             ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
         }
 
-        // Currently we do not support multisampled 2D array textures.
+        // It is an error to create a multisampled 1D or 3D texture.
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.sampleCount = 4;
+
+            descriptor.size.height = 1;
+            descriptor.dimension = wgpu::TextureDimension::e1D;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
+
+        // Currently we do not support multisampled 2D textures with depth>1.
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.sampleCount = 4;
@@ -188,7 +201,7 @@
             ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
         }
 
-        // Non square mip map halves the resolution until a 1x1 dimension.
+        // Non square mip map halves the resolution until a 1x1 dimension
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.width = 32;
@@ -199,6 +212,36 @@
             device.CreateTexture(&descriptor);
         }
 
+        // Non square mip map for a 3D textures
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.size.width = 32;
+            descriptor.size.height = 8;
+            descriptor.size.depth = 64;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+            // Non square mip map halves width, height and depth until a 1x1x1 dimension for a 3D
+            // texture. So there are 7 mipmaps at most: 32 * 8 * 64, 16 * 4 * 32, 8 * 2 * 16,
+            // 4 * 1 * 8, 2 * 1 * 4, 1 * 1 * 2, 1 * 1 * 1.
+            descriptor.mipLevelCount = 7;
+            device.CreateTexture(&descriptor);
+        }
+
+        // Non square mip map for 2D textures with depth > 1
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.size.width = 32;
+            descriptor.size.height = 8;
+            descriptor.size.depth = 64;
+            // Non square mip map halves width and height until a 1x1 dimension for a 2D texture,
+            // even its depth > 1. So there are 6 mipmaps at most: 32 * 8, 16 * 4, 8 * 2, 4 * 1, 2 *
+            // 1, 1 * 1.
+            descriptor.dimension = wgpu::TextureDimension::e2D;
+            descriptor.mipLevelCount = 7;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+            descriptor.mipLevelCount = 6;
+            device.CreateTexture(&descriptor);
+        }
+
         // Mip level exceeding kMaxTexture2DMipLevels not allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
@@ -209,63 +252,132 @@
             ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
         }
     }
+
     // Test the validation of array layer count
     TEST_F(TextureValidationTest, ArrayLayerCount) {
         wgpu::TextureDescriptor defaultDescriptor = CreateDefaultTextureDescriptor();
 
-        // Array layer count exceeding kMaxTextureArrayLayers is not allowed
+        // Array layer count exceeding kMaxTextureArrayLayers is not allowed for 2D texture
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
-            descriptor.size.depth = kMaxTextureArrayLayers + 1u;
 
+            descriptor.size.depth = kMaxTextureArrayLayers + 1u;
             ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
         }
 
-        // Array layer count less than kMaxTextureArrayLayers is allowed;
+        // Array layer count less than kMaxTextureArrayLayers is allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.depth = kMaxTextureArrayLayers >> 1;
-
             device.CreateTexture(&descriptor);
         }
 
-        // Array layer count equal to kMaxTextureArrayLayers is allowed;
+        // Array layer count equal to kMaxTextureArrayLayers is allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.depth = kMaxTextureArrayLayers;
-
             device.CreateTexture(&descriptor);
         }
     }
 
-    // Test the validation of texture size
-    TEST_F(TextureValidationTest, TextureSize) {
+    // Test the validation of 2D texture size
+    TEST_F(TextureValidationTest, 2DTextureSize) {
         wgpu::TextureDescriptor defaultDescriptor = CreateDefaultTextureDescriptor();
 
-        // Texture size exceeding kMaxTextureDimension2D is not allowed
+        // Out-of-bound texture dimension is not allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.width = kMaxTextureDimension2D + 1u;
-            descriptor.size.height = kMaxTextureDimension2D + 1u;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
 
+            descriptor.size.width = 1;
+            descriptor.size.height = kMaxTextureDimension2D + 1u;
             ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
         }
 
-        // Texture size less than kMaxTextureDimension2D is allowed
+        // Zero-sized texture is not allowed
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.size = {0, 1, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, 0, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, 1, 0};
+            // 2D texture with depth=0 is not allowed
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
+
+        // Texture size less than max dimension is allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.width = kMaxTextureDimension2D >> 1;
             descriptor.size.height = kMaxTextureDimension2D >> 1;
-
             device.CreateTexture(&descriptor);
         }
 
-        // Texture equal to kMaxTextureDimension2D is allowed
+        // Texture size equal to max dimension is allowed
         {
             wgpu::TextureDescriptor descriptor = defaultDescriptor;
             descriptor.size.width = kMaxTextureDimension2D;
             descriptor.size.height = kMaxTextureDimension2D;
+            descriptor.dimension = wgpu::TextureDimension::e2D;
+            device.CreateTexture(&descriptor);
+        }
+    }
 
+    // Test the validation of 3D texture size
+    TEST_F(TextureValidationTest, 3DTextureSize) {
+        wgpu::TextureDescriptor defaultDescriptor = CreateDefaultTextureDescriptor();
+
+        // Out-of-bound texture dimension is not allowed
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+
+            descriptor.size = {kMaxTextureDimension3D + 1u, 1, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, kMaxTextureDimension3D + 1u, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, 1, kMaxTextureDimension3D + 1u};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
+
+        // Zero-sized texture is not allowed
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+
+            descriptor.size = {0, 1, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, 0, 1};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+
+            descriptor.size = {1, 1, 0};
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
+
+        // Texture size less than max dimension is allowed
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+
+            descriptor.size = {kMaxTextureDimension3D >> 1, kMaxTextureDimension3D >> 1,
+                               kMaxTextureDimension3D >> 1};
+            device.CreateTexture(&descriptor);
+        }
+
+        // Texture size equal to max dimension is allowed
+        {
+            wgpu::TextureDescriptor descriptor = defaultDescriptor;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+
+            descriptor.size = {kMaxTextureDimension3D, kMaxTextureDimension3D,
+                               kMaxTextureDimension3D};
             device.CreateTexture(&descriptor);
         }
     }
@@ -409,9 +521,9 @@
     };
 
     // Test the validation of texture size when creating textures in compressed texture formats.
+    // It is invalid to use a number that is not a multiple of 4 (the compressed block width and
+    // height of all BC formats) as the width or height of textures in BC formats.
     TEST_F(CompressedTextureFormatsValidationTests, TextureSize) {
-        // Test that it is invalid to use a number that is not a multiple of 4 (the compressed block
-        // width and height of all BC formats) as the width or height of textures in BC formats.
         for (wgpu::TextureFormat format : utils::kBCFormats) {
             {
                 wgpu::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
@@ -445,9 +557,9 @@
     }
 
     // Test the validation of texture usages when creating textures in compressed texture formats.
+    // Only CopySrc, CopyDst and Sampled are accepted as the texture usage of the textures in BC
+    // formats.
     TEST_F(CompressedTextureFormatsValidationTests, TextureUsage) {
-        // Test that only CopySrc, CopyDst and Sampled are accepted as the texture usage of the
-        // textures in BC formats.
         wgpu::TextureUsage invalidUsages[] = {
             wgpu::TextureUsage::RenderAttachment,
             wgpu::TextureUsage::Storage,
@@ -464,9 +576,8 @@
     }
 
     // Test the validation of sample count when creating textures in compressed texture formats.
+    // It is invalid to specify SampleCount > 1 when we create a texture in BC formats.
     TEST_F(CompressedTextureFormatsValidationTests, SampleCount) {
-        // Test that it is invalid to specify SampleCount > 1 when we create a texture in BC
-        // formats.
         for (wgpu::TextureFormat format : utils::kBCFormats) {
             wgpu::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
             descriptor.format = format;
@@ -475,9 +586,8 @@
         }
     }
 
-    // Test the validation of creating 2D array textures in compressed texture formats.
+    // Test that it is allowed to create a 2D texture with depth>1 in BC formats.
     TEST_F(CompressedTextureFormatsValidationTests, 2DArrayTexture) {
-        // Test that it is allowed to create a 2D array texture in BC formats.
         for (wgpu::TextureFormat format : utils::kBCFormats) {
             wgpu::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
             descriptor.format = format;
@@ -486,4 +596,15 @@
         }
     }
 
+    // Test that it is not allowed to create a 3D texture in BC formats.
+    TEST_F(CompressedTextureFormatsValidationTests, 3DTexture) {
+        for (wgpu::TextureFormat format : utils::kBCFormats) {
+            wgpu::TextureDescriptor descriptor = CreateDefaultTextureDescriptor();
+            descriptor.format = format;
+            descriptor.size.depth = 4;
+            descriptor.dimension = wgpu::TextureDimension::e3D;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+        }
+    }
+
 }  // namespace