export const description = `createTexture validation tests.`;
import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, hasFeature, makeValueTestVariant } from '../../../common/util/util.js';
import {
kTextureDimensions,
kTextureUsages,
isValidTextureUsageCombination,
kValidCombinationsOfOneOrTwoTextureUsages,
kAllTextureUsages,
kSomeBogusTextureUsage,
} from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
kAllTextureFormats,
kCompressedTextureFormats,
kUncompressedTextureFormats,
kRegularTextureFormats,
kFeaturesForFormats,
filterFormatsByFeature,
textureFormatAndDimensionPossiblyCompatible,
getBlockInfoForTextureFormat,
isTextureFormatMultisampled,
isTextureFormatColorRenderable,
isTextureFormatPossiblyUsableAsColorRenderAttachment,
isTextureFormatPossiblyStorageReadable,
isColorTextureFormat,
textureFormatsAreViewCompatible,
textureDimensionAndFormatCompatibleForDevice,
getMaxValidTextureSizeForFormatAndDimension,
isTextureFormatUsableWithStorageAccessMode,
} from '../../format_info.js';
import { maxMipLevelCount } from '../../util/texture/base.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
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(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
)
.fn(t => {
const { dimension, zeroArgument, format } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
const info = getBlockInfoForTextureFormat(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.createTextureTracked(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 dimension types while it depends on the format for 3D types.`
)
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
.combine('format', kAllTextureFormats)
)
.fn(t => {
const { dimension, format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const info = getBlockInfoForTextureFormat(format);
const descriptor: GPUTextureDescriptor = {
size: [info.blockWidth, info.blockHeight, 1],
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
t.expectValidationError(
() => {
t.createTextureTracked(descriptor);
},
!textureDimensionAndFormatCompatibleForDevice(t.device.features, 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', kAllTextureFormats)
.beginSubcases()
.combine('mipLevelCount', [1, 2, 3, 6, 7])
.filter(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
.combine('largestDimension', [0, 1, 2])
.unless(({ dimension, largestDimension }) => dimension === '1d' && largestDimension > 0)
)
.fn(t => {
const { dimension, format, mipLevelCount, largestDimension } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
const info = getBlockInfoForTextureFormat(format);
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);
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.createTextureTracked(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] },
{ size: [31, 32] },
{ size: [28, 32] },
{ size: [32, 31] },
{ size: [32, 28] },
{ size: [31, 31] },
{ size: [32], dimension: '1d' as const },
{ size: [31], dimension: '1d' as const },
{ size: [32, 32, 32], dimension: '3d' as const },
{ size: [32, 31, 31], dimension: '3d' as const },
{ size: [31, 32, 31], dimension: '3d' as const },
{ size: [31, 31, 32], dimension: '3d' as const },
{ size: [31, 31, 31], dimension: '3d' as const },
{ size: [32, 8] },
{ size: [32, 32, 64] },
{ size: [32, 32, 64], dimension: '3d' as const },
])
.unless(
({ format, size, dimension }) =>
format === 'bc1-rgba-unorm' &&
(dimension === '1d' ||
dimension === '3d' ||
size[0] % getBlockInfoForTextureFormat(format).blockWidth !== 0 ||
size[1] % getBlockInfoForTextureFormat(format).blockHeight !== 0)
)
)
.fn(t => {
const { format, size, dimension } = t.params;
t.skipIfTextureFormatNotSupported(format);
const descriptor = {
size,
mipLevelCount: 0,
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const mipLevelCount = maxMipLevelCount(descriptor);
descriptor.mipLevelCount = mipLevelCount;
t.createTextureTracked(descriptor);
descriptor.mipLevelCount = mipLevelCount + 1;
t.expectValidationError(() => {
t.createTextureTracked(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(t => {
const descriptor = {
size: [32, 32],
mipLevelCount: 100,
format: 'rgba8unorm' as const,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
t.expectValidationError(() => {
t.createTextureTracked(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', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256])
)
.fn(t => {
const { dimension, sampleCount, format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const info = getBlockInfoForTextureFormat(format);
const usage =
sampleCount > 1
? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
: GPUTextureUsage.TEXTURE_BINDING;
const descriptor = {
size: [32 * info.blockWidth, 32 * info.blockHeight, 1],
sampleCount,
dimension,
format,
usage,
};
const success =
sampleCount === 1 ||
(sampleCount === 4 && isTextureFormatMultisampled(t.device.features, format));
t.expectValidationError(() => {
t.createTextureTracked(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', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [1, 4])
.combine('arrayLayerCount', [1, 2])
.unless(
({ dimension, arrayLayerCount }) =>
arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined
)
.combine('mipLevelCount', [1, 2])
.combine('usage', kValidCombinationsOfOneOrTwoTextureUsages)
.filter(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
.unless(({ usage, format, arrayLayerCount, mipLevelCount, dimension }) => {
return (
((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
(!isTextureFormatPossiblyUsableAsColorRenderAttachment(format) ||
dimension !== '2d')) ||
((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 &&
!isTextureFormatPossiblyStorageReadable(format)) ||
(mipLevelCount !== 1 && dimension === '1d') ||
((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0 &&
(usage !==
(GPUConst.TextureUsage.RENDER_ATTACHMENT |
GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) ||
mipLevelCount !== 1 ||
arrayLayerCount !== 1))
);
})
)
.fn(t => {
const { dimension, sampleCount, format, mipLevelCount, arrayLayerCount, usage } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
if ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0) {
t.skipIfTextureFormatNotUsableAsRenderAttachment(format);
}
if ((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0) {
t.skipIfTransientAttachmentNotSupported();
}
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(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 satisfyWithStorageUsageRequirement =
(usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0 ||
isTextureFormatUsableWithStorageAccessMode(t.device.features, format, 'write-only');
const success =
(sampleCount === 1 && satisfyWithStorageUsageRequirement) ||
(sampleCount === 4 &&
isTextureFormatMultisampled(t.device.features, format) &&
(dimension === '2d' || dimension === undefined) &&
mipLevelCount === 1 &&
arrayLayerCount === 1 &&
(usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
(usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0);
t.expectValidationError(() => {
t.createTextureTracked(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(t => {
const { dimension, size, shouldError } = t.params;
t.expectValidationError(() => {
t.createTextureTracked({
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(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
)
.fn(t => {
const { dimension, format, size } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
const descriptor: GPUTextureDescriptor = {
size,
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
t.createTextureTracked(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
.combine('dimension', [undefined, '2d', '3d'] as const)
.combine('format', kCompressedTextureFormats)
.filter(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
.beginSubcases()
.expandWithParams(p => {
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(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 },
];
})
)
.fn(t => {
const { dimension, format, size, _success } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
const descriptor: GPUTextureDescriptor = {
size,
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success = supportsUnaligned || _success;
t.expectValidationError(() => {
t.createTextureTracked(descriptor);
}, !success);
});
g.test('texture_size,1d_texture')
.desc(`Test texture size requirement for 1D texture`)
.params(u =>
u
.combine('format', kRegularTextureFormats)
.beginSubcases()
.combine('widthVariant', [
{ mult: 1, add: -1 },
{ mult: 1, add: 0 },
{ mult: 1, add: 1 },
])
.combine('height', [1, 2])
.combine('depthOrArrayLayers', [1, 2])
)
.fn(t => {
const { format, widthVariant, height, depthOrArrayLayers } = t.params;
t.skipIfTextureFormatNotSupported(format);
const width = t.makeLimitVariant('maxTextureDimension1D', widthVariant);
const descriptor: GPUTextureDescriptor = {
size: [width, height, depthOrArrayLayers],
dimension: '1d' as const,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const success =
width <= t.device.limits.maxTextureDimension1D && height === 1 && depthOrArrayLayers === 1;
t.expectValidationError(() => {
t.createTextureTracked(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(
'sizeVariant',
[
[{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 1, add: 1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 1 }],
]
)
)
.fn(t => {
const { dimension, format, sizeVariant } = t.params;
t.skipIfTextureFormatNotSupported(format);
const size = [
t.device.limits.maxTextureDimension2D,
t.device.limits.maxTextureDimension2D,
t.device.limits.maxTextureArrayLayers,
].map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));
const descriptor: GPUTextureDescriptor = {
size,
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const success =
size[0] <= t.device.limits.maxTextureDimension2D &&
size[1] <= t.device.limits.maxTextureDimension2D &&
size[2] <= t.device.limits.maxTextureArrayLayers;
t.expectValidationError(() => {
t.createTextureTracked(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)
.beginSubcases()
.expand('sizeVariant', p => {
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(p.format);
return [
[
{ mult: 1, add: -1 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: -blockWidth },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: -blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: 0 },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: 1 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: -blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: -blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: +blockWidth },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: +blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: +1 },
],
];
})
)
.fn(t => {
const { dimension, format, sizeVariant } = t.params;
t.skipIfTextureFormatNotSupported(format);
const info = getBlockInfoForTextureFormat(format);
const size = getMaxValidTextureSizeForFormatAndDimension(t.device, format, '2d').map(
(limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx])
);
const descriptor: GPUTextureDescriptor = {
size,
dimension,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success =
(supportsUnaligned ||
(size[0] % info.blockWidth === 0 && size[1] % info.blockHeight === 0)) &&
size[0] <= t.device.limits.maxTextureDimension2D &&
size[1] <= t.device.limits.maxTextureDimension2D &&
size[2] <= t.device.limits.maxTextureArrayLayers;
t.expectValidationError(() => {
t.createTextureTracked(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(
'sizeVariant',
[
[{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 1, add: +1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 1, add: +1 }, { mult: 0, add: 1 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
[{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: +1 }],
]
)
)
.fn(t => {
const { format, sizeVariant } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, '3d');
const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
const size = sizeVariant.map(variant => t.makeLimitVariant('maxTextureDimension3D', variant));
const descriptor: GPUTextureDescriptor = {
size,
dimension: '3d' as const,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const success =
size[0] <= maxTextureDimension3D &&
size[1] <= maxTextureDimension3D &&
size[2] <= maxTextureDimension3D;
t.expectValidationError(() => {
t.createTextureTracked(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('sizeVariant', p => {
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(p.format);
return [
[
{ mult: 1, add: -1 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: -blockWidth },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: -blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: 0 },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: +1 },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: +blockWidth },
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
],
[
{ mult: 1, add: +blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: -blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: -blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: 0 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 1, add: +blockWidth },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 1, add: +blockHeight },
{ mult: 0, add: 1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: -1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: 0 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: 1 },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: 1 },
{ mult: 0, add: blockHeight },
{ mult: 1, add: +1 },
],
[
{ mult: 0, add: blockWidth },
{ mult: 0, add: blockHeight },
{ mult: 1, add: +1 },
],
];
})
)
.fn(t => {
const { format, sizeVariant } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, '3d');
const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
const info = getBlockInfoForTextureFormat(format);
const size = getMaxValidTextureSizeForFormatAndDimension(t.device, format, '3d').map(
(limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx])
);
const descriptor: GPUTextureDescriptor = {
size,
dimension: '3d' as const,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success =
(supportsUnaligned ||
(size[0] % info.blockWidth === 0 && size[1] % info.blockHeight === 0)) &&
size[0] <= maxTextureDimension3D &&
size[1] <= maxTextureDimension3D &&
size[2] <= maxTextureDimension3D &&
textureDimensionAndFormatCompatibleForDevice(t.device.features, '3d', format);
t.expectValidationError(() => {
t.createTextureTracked(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', kAllTextureFormats)
.beginSubcases()
.combine('usage0', kTextureUsages)
.combine('usage1', kTextureUsages)
.filter(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
.unless(({ usage0, usage1 }) => {
return !isValidTextureUsageCombination(usage0 | usage1);
})
)
.fn(t => {
const { dimension, format, usage0, usage1 } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
const info = getBlockInfoForTextureFormat(format);
const size = [info.blockWidth, info.blockHeight, 1];
const usage = usage0 | usage1;
const descriptor = {
size,
dimension,
format,
usage,
};
if ((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0) {
t.skipIfTransientAttachmentNotSupported();
}
let success = true;
const appliedDimension = dimension ?? '2d';
if (usage & GPUTextureUsage.STORAGE_BINDING) {
if (!isTextureFormatUsableWithStorageAccessMode(t.device.features, format, 'write-only'))
success = false;
}
if (usage & GPUTextureUsage.RENDER_ATTACHMENT) {
if (appliedDimension === '1d') success = false;
if (
isColorTextureFormat(format) &&
!isTextureFormatColorRenderable(t.device.features, format)
)
success = false;
}
if (usage & GPUTextureUsage.TRANSIENT_ATTACHMENT) {
if (appliedDimension !== '2d') success = false;
}
t.expectValidationError(() => {
t.createTextureTracked(descriptor);
}, !success);
});
g.test('depthOrArrayLayers_and_mipLevelCount_for_transient_attachments')
.desc(`Test depthOrArrayLayers and mipLevelCount must be 1 for transient attachments`)
.params(u =>
u
.combine('format', ['rgba8unorm', 'depth24plus'] as const)
.beginSubcases()
.combine('depthOrArrayLayers', [1, 2])
.combine('mipLevelCount', [1, 2])
)
.fn(t => {
t.skipIfTransientAttachmentNotSupported();
const { format, depthOrArrayLayers, mipLevelCount } = t.params;
const info = getBlockInfoForTextureFormat(format);
const size = [info.blockWidth, info.blockHeight, depthOrArrayLayers];
const descriptor = {
size,
mipLevelCount,
format,
usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.TRANSIENT_ATTACHMENT,
};
let success = true;
if (depthOrArrayLayers !== 1) success = false;
if (mipLevelCount !== 1) success = false;
t.expectValidationError(() => {
t.createTextureTracked(descriptor);
}, !success);
});
g.test('usage')
.desc('Test combinations of zero to two usage flags are validated to be valid.')
.params(u =>
u
.combine('usage1', [0, ...kTextureUsages, kSomeBogusTextureUsage])
.combine('usage2', [0, ...kTextureUsages, kSomeBogusTextureUsage])
.filter(p => p.usage1 <= p.usage2)
)
.fn(t => {
const { usage1, usage2 } = t.params;
const usage = usage1 | usage2;
if ((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0) {
t.skipIfTransientAttachmentNotSupported();
}
const isValid =
usage !== 0 &&
(usage & ~kAllTextureUsages) === 0 &&
((usage & GPUTextureUsage.TRANSIENT_ATTACHMENT) === 0 ||
usage === (GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TRANSIENT_ATTACHMENT));
t.expectGPUError(
'validation',
() => {
t.createTextureTracked({ format: 'rgba8unorm', size: [1, 1], usage });
},
!isValid
);
});
g.test('new_usages')
.desc(`Valid usages not present in GPUTextureUsage shouldn't be accepted by createTexture().`)
.params(u =>
u
.combine('usage', [
...kTextureUsages,
GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.TRANSIENT_ATTACHMENT,
])
.filter(p => isValidTextureUsageCombination(p.usage))
)
.fn(t => {
const { usage } = t.params;
let exposedUsages = 0;
for (const v of Object.values(GPUTextureUsage)) {
exposedUsages |= v;
}
const success = (usage & exposedUsages) === usage;
t.expectGPUError(
'validation',
() => {
t.createTextureTracked({ format: 'rgba8unorm', size: [1, 1], usage });
},
!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, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.fn(t => {
const { format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(format, viewFormat);
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
const compatible = textureFormatsAreViewCompatible(t.device.features, format, viewFormat);
t.expectValidationError(() => {
t.createTextureTracked({
format,
size: [blockWidth, blockHeight],
usage: GPUTextureUsage.TEXTURE_BINDING,
viewFormats: [viewFormat],
});
}, !compatible);
t.expectValidationError(() => {
t.createTextureTracked({
format,
size: [blockWidth, blockHeight],
usage: GPUTextureUsage.TEXTURE_BINDING,
viewFormats: [viewFormat, format],
});
}, !compatible);
t.expectValidationError(() => {
t.createTextureTracked({
format,
size: [blockWidth, blockHeight],
usage: GPUTextureUsage.TEXTURE_BINDING,
viewFormats: [viewFormat, viewFormat],
});
}, !compatible);
});
g.test('transient_viewFormats')
.desc(`Test that viewFormats is not allowed with TRANSIENT_ATTACHMENT textures.`)
.params(u =>
u
.combineWithParams([
{ format: 'rgba8unorm', _otherFormat: 'rgba8unorm-srgb' },
{ format: 'rgba8unorm-srgb', _otherFormat: 'rgba8unorm' },
] as const)
.beginSubcases()
.expandWithParams(({ format, _otherFormat }) => [
{ useTransient: true, viewFormat: undefined },
{ useTransient: false, viewFormat: format },
{ useTransient: false, viewFormat: _otherFormat },
{ useTransient: true, viewFormat: format },
{ useTransient: true, viewFormat: _otherFormat },
])
)
.fn(t => {
const { format, viewFormat, useTransient } = t.params;
if (viewFormat && !textureFormatsAreViewCompatible(t.device.features, format, viewFormat)) {
t.skip(`"${format}" and "${viewFormat}" are not view-compatible`);
}
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
const invalid = useTransient && viewFormat !== undefined;
let tex: GPUTexture;
t.expectValidationError(() => {
tex = t.createTextureTracked({
format,
size: [blockWidth, blockHeight],
usage:
GPUConst.TextureUsage.RENDER_ATTACHMENT |
(useTransient ? GPUConst.TextureUsage.TRANSIENT_ATTACHMENT : 0),
viewFormats: viewFormat ? [viewFormat] : undefined,
});
}, invalid);
if (invalid) {
t.expectValidationError(() => {
tex.createView({ format: viewFormat });
}, true);
}
});