From 54d0d43e58e97f2fdff3ff597dbcef4fde848bf8 Mon Sep 17 00:00:00 2001 From: Yunchao He Date: Thu, 28 Jan 2021 20:37:01 +0000 Subject: [PATCH] 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 Reviewed-by: Kai Ninomiya Reviewed-by: Corentin Wallez --- src/dawn_native/Texture.cpp | 69 +++++--- .../validation/TextureValidationTests.cpp | 167 +++++++++++++++--- 2 files changed, 193 insertions(+), 43 deletions(-) diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp index bba6bceec9..211abfda89 100644 --- a/src/dawn_native/Texture.cpp +++ b/src/dawn_native/Texture.cpp @@ -48,8 +48,10 @@ namespace dawn_native { 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 @@ namespace dawn_native { "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 @@ namespace dawn_native { } 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 @@ namespace dawn_native { } } - 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 @@ namespace dawn_native { 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 @@ namespace dawn_native { 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 458ce00d91..1a5eb96b09 100644 --- a/src/tests/unittests/validation/TextureValidationTests.cpp +++ b/src/tests/unittests/validation/TextureValidationTests.cpp @@ -92,7 +92,20 @@ namespace { 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 @@ namespace { 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 @@ namespace { 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 @@ namespace { 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 @@ namespace { }; // 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 @@ namespace { } // 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 @@ namespace { } // 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 @@ namespace { } } - // 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 @@ namespace { } } + // 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