diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts | 879 |
1 files changed, 879 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts new file mode 100644 index 0000000000..5628d7a20c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts @@ -0,0 +1,879 @@ +export const description = `createTexture validation tests.`; + +import { SkipTestCase } from '../../../common/framework/fixture.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { assert } from '../../../common/util/util.js'; +import { + kTextureFormats, + kTextureFormatInfo, + kCompressedTextureFormats, + kTextureDimensions, + kTextureUsages, + kUncompressedTextureFormats, + kRegularTextureFormats, + kFeaturesForFormats, + textureDimensionAndFormatCompatible, + kLimitInfo, + viewCompatible, + filterFormatsByFeature, +} from '../../capability_info.js'; +import { GPUConst } from '../../constants.js'; +import { maxMipLevelCount } from '../../util/texture/base.js'; + +import { ValidationTest } from './validation_test.js'; + +export const g = makeTestGroup(ValidationTest); + +g.test('zero_size_and_usage') + .desc( + `Test texture creation with zero or nonzero size of + width, height, depthOrArrayLayers and mipLevelCount, usage for every dimension, and + representative formats. + ` + ) + .params(u => + u + .combine('dimension', [undefined, ...kTextureDimensions]) + .combine('format', [ + 'rgba8unorm', + 'rgb10a2unorm', + 'bc1-rgba-unorm', + 'depth24plus-stencil8', + ] as const) + .beginSubcases() + .combine('zeroArgument', [ + 'none', + 'width', + 'height', + 'depthOrArrayLayers', + 'mipLevelCount', + 'usage', + ] as const) + // Filter out incompatible dimension type and format combinations. + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, zeroArgument, format } = t.params; + const info = kTextureFormatInfo[format]; + + const size = [info.blockWidth, info.blockHeight, 1]; + let mipLevelCount = 1; + let usage = GPUTextureUsage.TEXTURE_BINDING; + + switch (zeroArgument) { + case 'width': + size[0] = 0; + break; + case 'height': + size[1] = 0; + break; + case 'depthOrArrayLayers': + size[2] = 0; + break; + case 'mipLevelCount': + mipLevelCount = 0; + break; + case 'usage': + usage = 0; + break; + default: + break; + } + + const descriptor = { + size, + mipLevelCount, + dimension, + format, + usage, + }; + + const success = zeroArgument === 'none'; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('dimension_type_and_format_compatibility') + .desc( + `Test every dimension type on every format. Note that compressed formats and depth/stencil formats are not valid for 1D/3D dimension types.` + ) + .params(u => + u.combine('dimension', [undefined, ...kTextureDimensions]).combine('format', kTextureFormats) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor: GPUTextureDescriptor = { + size: [info.blockWidth, info.blockHeight, 1], + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !textureDimensionAndFormatCompatible(dimension, format)); + }); + +g.test('mipLevelCount,format') + .desc( + `Test texture creation with no mipmap chain, partial mipmap chain, full mipmap chain, out-of-bounds mipmap chain + for every format with different texture dimension types.` + ) + .params(u => + u + .combine('dimension', [undefined, ...kTextureDimensions]) + .combine('format', kTextureFormats) + .beginSubcases() + .combine('mipLevelCount', [1, 2, 3, 6, 7]) + // Filter out incompatible dimension type and format combinations. + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + .combine('largestDimension', [0, 1, 2]) + .unless(({ dimension, largestDimension }) => dimension === '1d' && largestDimension > 0) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, mipLevelCount, largestDimension } = t.params; + const info = kTextureFormatInfo[format]; + + // Compute dimensions such that the dimensions are in range [17, 32] and aligned with the + // format block size so that there will be exactly 6 mip levels. + const kTargetMipLevelCount = 5; + const kTargetLargeSize = (1 << kTargetMipLevelCount) - 1; + const largeSize = [ + Math.floor(kTargetLargeSize / info.blockWidth) * info.blockWidth, + Math.floor(kTargetLargeSize / info.blockHeight) * info.blockHeight, + kTargetLargeSize, + ]; + assert(17 <= largeSize[0] && largeSize[0] <= 32); + assert(17 <= largeSize[1] && largeSize[1] <= 32); + + // Note that compressed formats are not valid for 1D. They have already been filtered out for 1D + // in this test. So there is no dilemma about size.width equals 1 vs + // size.width % info.blockHeight equals 0 for 1D compressed formats. + const size = [info.blockWidth, info.blockHeight, 1]; + size[largestDimension] = largeSize[largestDimension]; + + const descriptor = { + size, + mipLevelCount, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = mipLevelCount <= maxMipLevelCount(descriptor); + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('mipLevelCount,bound_check') + .desc( + `Test mip level count bound check upon different texture size and different texture dimension types. + The cases below test: 1) there must be no mip levels after a 1 level (1D texture), or 1x1 level (2D texture), or 1x1x1 level (3D texture), 2) array layers are not mip-mapped, 3) power-of-two, non-power-of-two, and non-square sizes.` + ) + .params(u => + u // + .combine('format', ['rgba8unorm', 'bc1-rgba-unorm'] as const) + .beginSubcases() + .combineWithParams([ + { size: [32, 32] }, // Mip level sizes: 32x32, 16x16, 8x8, 4x4, 2x2, 1x1 + { size: [31, 32] }, // Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1 + { size: [28, 32] }, // Mip level sizes: 28x32, 14x16, 7x8, 3x4, 1x2, 1x1 + { size: [32, 31] }, // Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1 + { size: [32, 28] }, // Mip level sizes: 32x28, 16x14, 8x7, 4x3, 2x1, 1x1 + { size: [31, 31] }, // Mip level sizes: 31x31, 15x15, 7x7, 3x3, 1x1 + { size: [32], dimension: '1d' as const }, // Mip level sizes: 32, 16, 8, 4, 2, 1 + { size: [31], dimension: '1d' as const }, // Mip level sizes: 31, 15, 7, 3, 1 + { size: [32, 32, 32], dimension: '3d' as const }, // Mip level sizes: 32x32x32, 16x16x16, 8x8x8, 4x4x4, 2x2x2, 1x1x1 + { size: [32, 31, 31], dimension: '3d' as const }, // Mip level sizes: 32x31x31, 16x15x15, 8x7x7, 4x3x3, 2x1x1, 1x1x1 + { size: [31, 32, 31], dimension: '3d' as const }, // Mip level sizes: 31x32x31, 15x16x15, 7x8x7, 3x4x3, 1x2x1, 1x1x1 + { size: [31, 31, 32], dimension: '3d' as const }, // Mip level sizes: 31x31x32, 15x15x16, 7x7x8, 3x3x4, 1x1x2, 1x1x1 + { size: [31, 31, 31], dimension: '3d' as const }, // Mip level sizes: 31x31x31, 15x15x15, 7x7x7, 3x3x3, 1x1x1 + { size: [32, 8] }, // Mip levels: 32x8, 16x4, 8x2, 4x1, 2x1, 1x1 + { size: [32, 32, 64] }, // Mip levels: 32x32x64, 16x16x64, 8x8x64, 4x4x64, 2x2x64, 1x1x64 + { size: [32, 32, 64], dimension: '3d' as const }, // Mip levels: 32x32x64, 16x16x32, 8x8x16, 4x4x8, 2x2x4, 1x1x2, 1x1x1 + ]) + .unless( + ({ format, size, dimension }) => + format === 'bc1-rgba-unorm' && + (dimension === '1d' || + dimension === '3d' || + size[0] % kTextureFormatInfo[format].blockWidth !== 0 || + size[1] % kTextureFormatInfo[format].blockHeight !== 0) + ) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { format, size, dimension } = t.params; + + const descriptor = { + size, + mipLevelCount: 0, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const mipLevelCount = maxMipLevelCount(descriptor); + descriptor.mipLevelCount = mipLevelCount; + t.device.createTexture(descriptor); + + descriptor.mipLevelCount = mipLevelCount + 1; + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }); + }); + +g.test('mipLevelCount,bound_check,bigger_than_integer_bit_width') + .desc(`Test mip level count bound check when mipLevelCount is bigger than integer bit width`) + .fn(async t => { + const descriptor = { + size: [32, 32], + mipLevelCount: 100, + format: 'rgba8unorm' as const, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }); + }); + +g.test('sampleCount,various_sampleCount_with_all_formats') + .desc( + `Test texture creation with various (valid or invalid) sample count and all formats. Note that 1D and 3D textures can't support multisample.` + ) + .params(u => + u + .combine('dimension', [undefined, '2d'] as const) + .combine('format', kTextureFormats) + .beginSubcases() + .combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, sampleCount, format } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + + const usage = + sampleCount > 1 + ? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT + : GPUTextureUsage.TEXTURE_BINDING; + const descriptor = { + size: [32 * blockWidth, 32 * blockHeight, 1], + sampleCount, + dimension, + format, + usage, + }; + + const success = + sampleCount === 1 || + (sampleCount === 4 && + kTextureFormatInfo[format].multisample && + kTextureFormatInfo[format].renderable); + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('sampleCount,valid_sampleCount_with_other_parameter_varies') + .desc( + `Test texture creation with valid sample count when dimensions, arrayLayerCount, mipLevelCount, + format, and usage varies. Texture can be single sample (sampleCount is 1) or multi-sample + (sampleCount is 4). Multisample texture requires that + 1) its dimension is 2d or undefined, + 2) its format supports multisample, + 3) its mipLevelCount and arrayLayerCount are 1, + 4) its usage doesn't include STORAGE_BINDING, + 5) its usage includes RENDER_ATTACHMENT.` + ) + .params(u => + u + .combine('dimension', [undefined, ...kTextureDimensions]) + .combine('format', kTextureFormats) + .beginSubcases() + .combine('sampleCount', [1, 4]) + .combine('arrayLayerCount', [1, 2]) + .unless( + ({ dimension, arrayLayerCount }) => + arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined + ) + .combine('mipLevelCount', [1, 2]) + .expand('usage', p => { + const usageSet = new Set<number>(); + for (const usage0 of kTextureUsages) { + for (const usage1 of kTextureUsages) { + usageSet.add(usage0 | usage1); + } + } + return usageSet; + }) + // Filter out incompatible dimension type and format combinations. + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + .unless(({ usage, format, mipLevelCount, dimension }) => { + const info = kTextureFormatInfo[format]; + return ( + ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && + (!info.renderable || dimension !== '2d')) || + ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.storage) || + (mipLevelCount !== 1 && dimension === '1d') + ); + }) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, sampleCount, format, mipLevelCount, arrayLayerCount, usage } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + + const size = + dimension === '1d' + ? [32 * blockWidth, 1 * blockHeight, 1] + : dimension === '2d' || dimension === undefined + ? [32 * blockWidth, 32 * blockHeight, arrayLayerCount] + : [32 * blockWidth, 32 * blockHeight, 32]; + const descriptor = { + size, + mipLevelCount, + sampleCount, + dimension, + format, + usage, + }; + + const success = + sampleCount === 1 || + (sampleCount === 4 && + (dimension === '2d' || dimension === undefined) && + kTextureFormatInfo[format].multisample && + mipLevelCount === 1 && + arrayLayerCount === 1 && + (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && + (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0); + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('sample_count,1d_2d_array_3d') + .desc(`Test that you can not create 1d, 2d_array, and 3d multisampled textures`) + .params(u => + u.combineWithParams([ + { dimension: '2d', size: [4, 4, 1], shouldError: false }, + { dimension: '1d', size: [4, 1, 1], shouldError: true }, + { dimension: '2d', size: [4, 4, 4], shouldError: true }, + { dimension: '2d', size: [4, 4, 6], shouldError: true }, + { dimension: '3d', size: [4, 4, 4], shouldError: true }, + ] as const) + ) + .fn(async t => { + const { dimension, size, shouldError } = t.params; + + t.expectValidationError(() => { + t.device.createTexture({ + size, + dimension, + sampleCount: 4, + format: 'rgba8unorm', + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, + }); + }, shouldError); + }); + +g.test('texture_size,default_value_and_smallest_size,uncompressed_format') + .desc( + `Test default values for height and depthOrArrayLayers for every dimension type and every uncompressed format. + It also tests smallest size (lower bound) for every dimension type and every uncompressed format, while other texture_size tests are testing the upper bound.` + ) + .params(u => + u + .combine('dimension', [undefined, ...kTextureDimensions]) + .combine('format', kUncompressedTextureFormats) + .beginSubcases() + .combine('size', [[1], [1, 1], [1, 1, 1]]) + // Filter out incompatible dimension type and format combinations. + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, size } = t.params; + + const descriptor: GPUTextureDescriptor = { + size, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + t.device.createTexture(descriptor); + }); + +g.test('texture_size,default_value_and_smallest_size,compressed_format') + .desc( + `Test default values for height and depthOrArrayLayers for every dimension type and every compressed format. + It also tests smallest size (lower bound) for every dimension type and every compressed format, while other texture_size tests are testing the upper bound.` + ) + .params(u => + u + // Compressed formats are invalid for 1D and 3D. + .combine('dimension', [undefined, '2d'] as const) + .combine('format', kCompressedTextureFormats) + .beginSubcases() + .expandWithParams(p => { + const { blockWidth, blockHeight } = kTextureFormatInfo[p.format]; + return [ + { size: [1], _success: false }, + { size: [blockWidth], _success: false }, + { size: [1, 1], _success: false }, + { size: [blockWidth, blockHeight], _success: true }, + { size: [1, 1, 1], _success: false }, + { size: [blockWidth, blockHeight, 1], _success: true }, + ]; + }) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, size, _success } = t.params; + + const descriptor: GPUTextureDescriptor = { + size, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !_success); + }); + +g.test('texture_size,1d_texture') + .desc(`Test texture size requirement for 1D texture`) + .params(u => + u // + // Compressed and depth-stencil textures are invalid for 1D. + .combine('format', kRegularTextureFormats) + .beginSubcases() + .combine('width', [ + kLimitInfo.maxTextureDimension1D.default - 1, + kLimitInfo.maxTextureDimension1D.default, + kLimitInfo.maxTextureDimension1D.default + 1, + ]) + .combine('height', [1, 2]) + .combine('depthOrArrayLayers', [1, 2]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { format, width, height, depthOrArrayLayers } = t.params; + + const descriptor: GPUTextureDescriptor = { + size: [width, height, depthOrArrayLayers], + dimension: '1d' as const, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = + width <= kLimitInfo.maxTextureDimension1D.default && height === 1 && depthOrArrayLayers === 1; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('texture_size,2d_texture,uncompressed_format') + .desc(`Test texture size requirement for 2D texture with uncompressed format.`) + .params(u => + u + .combine('dimension', [undefined, '2d'] as const) + .combine('format', kUncompressedTextureFormats) + .combine('size', [ + // Test the bound of width + [kLimitInfo.maxTextureDimension2D.default - 1, 1, 1], + [kLimitInfo.maxTextureDimension2D.default, 1, 1], + [kLimitInfo.maxTextureDimension2D.default + 1, 1, 1], + // Test the bound of height + [1, kLimitInfo.maxTextureDimension2D.default - 1, 1], + [1, kLimitInfo.maxTextureDimension2D.default, 1], + [1, kLimitInfo.maxTextureDimension2D.default + 1, 1], + // Test the bound of array layers + [1, 1, kLimitInfo.maxTextureArrayLayers.default - 1], + [1, 1, kLimitInfo.maxTextureArrayLayers.default], + [1, 1, kLimitInfo.maxTextureArrayLayers.default + 1], + ]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, size } = t.params; + + const descriptor: GPUTextureDescriptor = { + size, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = + size[0] <= kLimitInfo.maxTextureDimension2D.default && + size[1] <= kLimitInfo.maxTextureDimension2D.default && + size[2] <= kLimitInfo.maxTextureArrayLayers.default; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('texture_size,2d_texture,compressed_format') + .desc(`Test texture size requirement for 2D texture with compressed format.`) + .params(u => + u + .combine('dimension', [undefined, '2d'] as const) + .combine('format', kCompressedTextureFormats) + .expand('size', p => { + const { blockWidth, blockHeight } = kTextureFormatInfo[p.format]; + return [ + // Test the bound of width + [kLimitInfo.maxTextureDimension2D.default - 1, 1, 1], + [kLimitInfo.maxTextureDimension2D.default - blockWidth, 1, 1], + [kLimitInfo.maxTextureDimension2D.default - blockWidth, blockHeight, 1], + [kLimitInfo.maxTextureDimension2D.default, 1, 1], + [kLimitInfo.maxTextureDimension2D.default, blockHeight, 1], + [kLimitInfo.maxTextureDimension2D.default + 1, 1, 1], + [kLimitInfo.maxTextureDimension2D.default + blockWidth, 1, 1], + [kLimitInfo.maxTextureDimension2D.default + blockWidth, blockHeight, 1], + // Test the bound of height + [1, kLimitInfo.maxTextureDimension2D.default - 1, 1], + [1, kLimitInfo.maxTextureDimension2D.default - blockHeight, 1], + [blockWidth, kLimitInfo.maxTextureDimension2D.default - blockHeight, 1], + [1, kLimitInfo.maxTextureDimension2D.default, 1], + [blockWidth, kLimitInfo.maxTextureDimension2D.default, 1], + [1, kLimitInfo.maxTextureDimension2D.default + 1, 1], + [1, kLimitInfo.maxTextureDimension2D.default + blockWidth, 1], + [blockWidth, kLimitInfo.maxTextureDimension2D.default + blockHeight, 1], + // Test the bound of array layers + [1, 1, kLimitInfo.maxTextureArrayLayers.default - 1], + [blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default - 1], + [1, blockHeight, kLimitInfo.maxTextureArrayLayers.default - 1], + [blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default - 1], + [1, 1, kLimitInfo.maxTextureArrayLayers.default], + [blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default], + [1, blockHeight, kLimitInfo.maxTextureArrayLayers.default], + [blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default], + [1, 1, kLimitInfo.maxTextureArrayLayers.default + 1], + [blockWidth, 1, kLimitInfo.maxTextureArrayLayers.default + 1], + [1, blockHeight, kLimitInfo.maxTextureArrayLayers.default + 1], + [blockWidth, blockHeight, kLimitInfo.maxTextureArrayLayers.default + 1], + ]; + }) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, size } = t.params; + const info = kTextureFormatInfo[format]; + + const descriptor: GPUTextureDescriptor = { + size, + dimension, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = + size[0] % info.blockWidth === 0 && + size[1] % info.blockHeight === 0 && + size[0] <= kLimitInfo.maxTextureDimension2D.default && + size[1] <= kLimitInfo.maxTextureDimension2D.default && + size[2] <= kLimitInfo.maxTextureArrayLayers.default; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('texture_size,3d_texture,uncompressed_format') + .desc( + `Test texture size requirement for 3D texture with uncompressed format. Note that depth/stencil formats are invalid for 3D textures, so we only test regular formats.` + ) + .params(u => + u // + .combine('format', kRegularTextureFormats) + .beginSubcases() + .combine('size', [ + // Test the bound of width + [kLimitInfo.maxTextureDimension3D.default - 1, 1, 1], + [kLimitInfo.maxTextureDimension3D.default, 1, 1], + [kLimitInfo.maxTextureDimension3D.default + 1, 1, 1], + // Test the bound of height + [1, kLimitInfo.maxTextureDimension3D.default - 1, 1], + [1, kLimitInfo.maxTextureDimension3D.default, 1], + [1, kLimitInfo.maxTextureDimension3D.default + 1, 1], + // Test the bound of depth + [1, 1, kLimitInfo.maxTextureDimension3D.default - 1], + [1, 1, kLimitInfo.maxTextureDimension3D.default], + [1, 1, kLimitInfo.maxTextureDimension3D.default + 1], + ]) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { format, size } = t.params; + + const descriptor: GPUTextureDescriptor = { + size, + dimension: '3d' as const, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = + size[0] <= kLimitInfo.maxTextureDimension3D.default && + size[1] <= kLimitInfo.maxTextureDimension3D.default && + size[2] <= kLimitInfo.maxTextureDimension3D.default; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('texture_size,3d_texture,compressed_format') + .desc(`Test texture size requirement for 3D texture with compressed format.`) + .params(u => + u // + .combine('format', kCompressedTextureFormats) + .beginSubcases() + .expand('size', p => { + const { blockWidth, blockHeight } = kTextureFormatInfo[p.format]; + return [ + // Test the bound of width + [kLimitInfo.maxTextureDimension3D.default - 1, 1, 1], + [kLimitInfo.maxTextureDimension3D.default - blockWidth, 1, 1], + [kLimitInfo.maxTextureDimension3D.default - blockWidth, blockHeight, 1], + [kLimitInfo.maxTextureDimension3D.default, 1, 1], + [kLimitInfo.maxTextureDimension3D.default, blockHeight, 1], + [kLimitInfo.maxTextureDimension3D.default + 1, 1, 1], + [kLimitInfo.maxTextureDimension3D.default + blockWidth, 1, 1], + [kLimitInfo.maxTextureDimension3D.default + blockWidth, blockHeight, 1], + // Test the bound of height + [1, kLimitInfo.maxTextureDimension3D.default - 1, 1], + [1, kLimitInfo.maxTextureDimension3D.default - blockHeight, 1], + [blockWidth, kLimitInfo.maxTextureDimension3D.default - blockHeight, 1], + [1, kLimitInfo.maxTextureDimension3D.default, 1], + [blockWidth, kLimitInfo.maxTextureDimension3D.default, 1], + [1, kLimitInfo.maxTextureDimension3D.default + 1, 1], + [1, kLimitInfo.maxTextureDimension3D.default + blockWidth, 1], + [blockWidth, kLimitInfo.maxTextureDimension3D.default + blockHeight, 1], + // Test the bound of depth + [1, 1, kLimitInfo.maxTextureDimension3D.default - 1], + [blockWidth, 1, kLimitInfo.maxTextureDimension3D.default - 1], + [1, blockHeight, kLimitInfo.maxTextureDimension3D.default - 1], + [blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default - 1], + [1, 1, kLimitInfo.maxTextureDimension3D.default], + [blockWidth, 1, kLimitInfo.maxTextureDimension3D.default], + [1, blockHeight, kLimitInfo.maxTextureDimension3D.default], + [blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default], + [1, 1, kLimitInfo.maxTextureDimension3D.default + 1], + [blockWidth, 1, kLimitInfo.maxTextureDimension3D.default + 1], + [1, blockHeight, kLimitInfo.maxTextureDimension3D.default + 1], + [blockWidth, blockHeight, kLimitInfo.maxTextureDimension3D.default + 1], + ]; + }) + ) + .beforeAllSubcases(t => { + // Compressed formats are not supported in 3D in WebGPU v1 because they are complicated but not very useful for now. + throw new SkipTestCase('Compressed 3D texture is not supported'); + + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { format, size } = t.params; + const info = kTextureFormatInfo[format]; + + assert( + kLimitInfo.maxTextureDimension3D.default % info.blockWidth === 0 && + kLimitInfo.maxTextureDimension3D.default % info.blockHeight === 0 + ); + + const descriptor: GPUTextureDescriptor = { + size, + dimension: '3d' as const, + format, + usage: GPUTextureUsage.TEXTURE_BINDING, + }; + + const success = + size[0] % info.blockWidth === 0 && + size[1] % info.blockHeight === 0 && + size[0] <= kLimitInfo.maxTextureDimension3D.default && + size[1] <= kLimitInfo.maxTextureDimension3D.default && + size[2] <= kLimitInfo.maxTextureDimension3D.default; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('texture_usage') + .desc( + `Test texture usage (single usage or combined usages) for every texture format and every dimension type` + ) + .params(u => + u + .combine('dimension', [undefined, ...kTextureDimensions]) + .combine('format', kTextureFormats) + .beginSubcases() + // If usage0 and usage1 are the same, then the usage being test is a single usage. Otherwise, it is a combined usage. + .combine('usage0', kTextureUsages) + .combine('usage1', kTextureUsages) + // Filter out incompatible dimension type and format combinations. + .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)) + ) + .beforeAllSubcases(t => { + const { format } = t.params; + const info = kTextureFormatInfo[format]; + t.selectDeviceOrSkipTestCase(info.feature); + }) + .fn(async t => { + const { dimension, format, usage0, usage1 } = t.params; + const info = kTextureFormatInfo[format]; + + const size = [info.blockWidth, info.blockHeight, 1]; + const usage = usage0 | usage1; + const descriptor = { + size, + dimension, + format, + usage, + }; + + let success = true; + const appliedDimension = dimension ?? '2d'; + // Note that we unconditionally test copy usages for all formats. We don't check copySrc/copyDst in kTextureFormatInfo in capability_info.js + // if (!info.copySrc && (usage & GPUTextureUsage.COPY_SRC) !== 0) success = false; + // if (!info.copyDst && (usage & GPUTextureUsage.COPY_DST) !== 0) success = false; + if (!info.storage && (usage & GPUTextureUsage.STORAGE_BINDING) !== 0) success = false; + if ( + (!info.renderable || appliedDimension !== '2d') && + (usage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0 + ) + success = false; + + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !success); + }); + +g.test('viewFormats') + .desc( + `Test creating a texture with viewFormats list for all {texture format}x{view format}. Only compatible view formats should be valid.` + ) + .params(u => + u + .combine('formatFeature', kFeaturesForFormats) + .combine('viewFormatFeature', kFeaturesForFormats) + .beginSubcases() + .expand('format', ({ formatFeature }) => + filterFormatsByFeature(formatFeature, kTextureFormats) + ) + .expand('viewFormat', ({ viewFormatFeature }) => + filterFormatsByFeature(viewFormatFeature, kTextureFormats) + ) + ) + .beforeAllSubcases(t => { + const { formatFeature, viewFormatFeature } = t.params; + t.selectDeviceOrSkipTestCase([formatFeature, viewFormatFeature]); + }) + .fn(async t => { + const { format, viewFormat } = t.params; + const { blockWidth, blockHeight } = kTextureFormatInfo[format]; + + const compatible = viewCompatible(format, viewFormat); + + // Test the viewFormat in the list. + t.expectValidationError(() => { + t.device.createTexture({ + format, + size: [blockWidth, blockHeight], + usage: GPUTextureUsage.TEXTURE_BINDING, + viewFormats: [viewFormat], + }); + }, !compatible); + + // Test the viewFormat and the texture format in the list. + t.expectValidationError(() => { + t.device.createTexture({ + format, + size: [blockWidth, blockHeight], + usage: GPUTextureUsage.TEXTURE_BINDING, + viewFormats: [viewFormat, format], + }); + }, !compatible); + + // Test the viewFormat multiple times in the list. + t.expectValidationError(() => { + t.device.createTexture({ + format, + size: [blockWidth, blockHeight], + usage: GPUTextureUsage.TEXTURE_BINDING, + viewFormats: [viewFormat, viewFormat], + }); + }, !compatible); + }); |