blob: 5335852c4eb7dd67dff2f7b9342aef8a2b9e2317 [file] [log] [blame] [edit]
export const description = `createView validation tests.`;
import { AllFeaturesMaxLimitsGPUTest, kResourceStates } from '../.././gpu_test.js';
import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { unreachable } from '../../../common/util/util.js';
import {
kTextureAspects,
kTextureDimensions,
kTextureUsages,
kTextureViewDimensions,
} from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
kAllTextureFormats,
kFeaturesForFormats,
filterFormatsByFeature,
textureFormatsAreViewCompatible,
isDepthTextureFormat,
isTextureFormatUsableWithStorageAccessMode,
isTextureFormatColorRenderable,
isColorTextureFormat,
isStencilTextureFormat,
getBlockInfoForTextureFormat,
isTextureFormatPossiblyUsableAsRenderAttachment,
} from '../../format_info.js';
import {
getTextureDimensionFromView,
reifyTextureViewDescriptor,
viewDimensionsForTextureDimension,
} from '../../util/texture/base.js';
import { reifyExtent3D } from '../../util/unions.js';
import * as vtu from './validation_test_utils.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
const kLevels = 6;
g.test('format')
.desc(
`Views must have the view format compatible with the base texture, for all {texture format}x{view format}.`
)
.params(u =>
u
.combine('textureFormatFeature', kFeaturesForFormats)
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('textureFormat', ({ textureFormatFeature }) =>
filterFormatsByFeature(textureFormatFeature, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
filterFormatsByFeature(viewFormatFeature, [undefined, ...kAllTextureFormats])
)
.combine('useViewFormatList', [false, true])
)
.fn(t => {
const { textureFormat, viewFormat, useViewFormatList } = t.params;
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(textureFormat);
t.skipIfTextureFormatNotSupported(textureFormat, viewFormat);
const compatible =
viewFormat === undefined ||
textureFormatsAreViewCompatible(t.device, textureFormat, viewFormat);
const texture = t.createTextureTracked({
format: textureFormat,
size: [blockWidth, blockHeight],
usage: GPUTextureUsage.TEXTURE_BINDING,
// This is a test of createView, not createTexture. Don't pass viewFormats here that
// are not compatible, as that is tested in createTexture.spec.ts.
viewFormats:
useViewFormatList && compatible && viewFormat !== undefined ? [viewFormat] : undefined,
});
// Successful if there is no view format, no reinterpretation was required, or the formats are compatible
// and is was specified in the viewFormats list.
const success =
viewFormat === undefined || viewFormat === textureFormat || (compatible && useViewFormatList);
t.expectValidationError(() => {
texture.createView({ format: viewFormat });
}, !success);
});
g.test('dimension')
.desc(
`For all {texture dimension}, {view dimension}, test that they must be compatible:
- 1d -> 1d
- 2d -> 2d, 2d-array, cube, or cube-array
- 3d -> 3d`
)
.params(u =>
u
.combine('textureDimension', kTextureDimensions)
.combine('viewDimension', [...kTextureViewDimensions, undefined])
)
.fn(t => {
const { textureDimension, viewDimension } = t.params;
t.skipIfTextureViewDimensionNotSupported(t.params.viewDimension);
const size = textureDimension === '1d' ? [4] : [4, 4, 6];
const textureDescriptor = {
format: 'rgba8unorm' as const,
dimension: textureDimension,
size,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const texture = t.createTextureTracked(textureDescriptor);
const view = { dimension: viewDimension };
const reified = reifyTextureViewDescriptor(textureDescriptor, view);
const success = getTextureDimensionFromView(reified.dimension) === textureDimension;
t.expectValidationError(() => {
texture.createView(view);
}, !success);
});
g.test('aspect')
.desc(
`For every {format}x{aspect}, test that the view aspect must exist in the format:
- "all" is allowed for any format
- "depth-only" is allowed only for depth and depth-stencil formats
- "stencil-only" is allowed only for stencil and depth-stencil formats`
)
.params(u =>
u //
.combine('format', kAllTextureFormats)
.combine('aspect', kTextureAspects)
)
.fn(t => {
const { format, aspect } = t.params;
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
t.skipIfTextureFormatNotSupported(format);
const texture = t.createTextureTracked({
format,
size: [blockWidth, blockHeight, 1],
usage: GPUTextureUsage.TEXTURE_BINDING,
});
const success =
aspect === 'all' ||
(aspect === 'depth-only' && isDepthTextureFormat(format)) ||
(aspect === 'stencil-only' && isStencilTextureFormat(format));
t.expectValidationError(() => {
texture.createView({ aspect });
}, !success);
});
const kTextureAndViewDimensions = kUnitCaseParamsBuilder
.combine('textureDimension', kTextureDimensions)
.expand('viewDimension', p => [
undefined,
...viewDimensionsForTextureDimension(p.textureDimension),
]);
function validateCreateViewLayersLevels(tex: GPUTextureDescriptor, view: GPUTextureViewDescriptor) {
const textureLevels = tex.mipLevelCount ?? 1;
const textureLayers = tex.dimension === '2d' ? reifyExtent3D(tex.size).depthOrArrayLayers : 1;
const reified = reifyTextureViewDescriptor(tex, view);
let success =
reified.mipLevelCount > 0 &&
reified.baseMipLevel < textureLevels &&
reified.baseMipLevel + reified.mipLevelCount <= textureLevels &&
reified.arrayLayerCount > 0 &&
reified.baseArrayLayer < textureLayers &&
reified.baseArrayLayer + reified.arrayLayerCount <= textureLayers;
if (reified.dimension === '1d' || reified.dimension === '2d' || reified.dimension === '3d') {
success &&= reified.arrayLayerCount === 1;
} else if (reified.dimension === 'cube') {
success &&= reified.arrayLayerCount === 6;
} else if (reified.dimension === 'cube-array') {
success &&= reified.arrayLayerCount % 6 === 0;
}
return success;
}
g.test('array_layers')
.desc(
`For each texture dimension {1d,2d,3d}, for each possible view dimension for that texture
dimension (or undefined, which defaults to the texture dimension), test validation of layer
counts:
- 1d, 2d, and 3d must have exactly 1 layer
- 2d-array must have 1 or more layers
- cube must have 6 layers
- cube-array must have a positive multiple of 6 layers
- Defaulting of baseArrayLayer and arrayLayerCount
- baseArrayLayer+arrayLayerCount must be within the texture`
)
.params(
kTextureAndViewDimensions
.beginSubcases()
.expand('textureLayers', ({ textureDimension: d }) => (d === '2d' ? [1, 6, 18] : [1]))
.combine('textureLevels', [1, kLevels])
.unless(p => p.textureDimension === '1d' && p.textureLevels !== 1)
.expand(
'baseArrayLayer',
({ textureLayers: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1])
)
.expand('arrayLayerCount', function* ({ textureLayers: l, baseArrayLayer = 0 }) {
yield undefined;
for (const lastArrayLayer of new Set([0, 1, 5, 6, 7, l - 1, l, l + 1])) {
if (baseArrayLayer <= lastArrayLayer) yield lastArrayLayer - baseArrayLayer;
}
})
)
.fn(t => {
const {
textureDimension,
viewDimension,
textureLayers,
textureLevels,
baseArrayLayer,
arrayLayerCount,
} = t.params;
t.skipIfTextureViewDimensionNotSupported(viewDimension);
const kWidth = 1 << (kLevels - 1); // 32
const textureDescriptor: GPUTextureDescriptor = {
format: 'rgba8unorm',
dimension: textureDimension,
size:
textureDimension === '1d'
? [kWidth]
: textureDimension === '2d'
? [kWidth, kWidth, textureLayers]
: textureDimension === '3d'
? [kWidth, kWidth, kWidth]
: unreachable(),
mipLevelCount: textureLevels,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const viewDescriptor = { dimension: viewDimension, baseArrayLayer, arrayLayerCount };
const success = validateCreateViewLayersLevels(textureDescriptor, viewDescriptor);
const texture = t.createTextureTracked(textureDescriptor);
t.expectValidationError(() => {
texture.createView(viewDescriptor);
}, !success);
});
g.test('mip_levels')
.desc(
`Views must have at least one level, and must be within the level of the base texture.
- mipLevelCount=0 at various baseMipLevel values
- Cases where baseMipLevel+mipLevelCount goes past the end of the texture
- Cases with baseMipLevel or mipLevelCount undefined (compares against reference defaulting impl)
`
)
.params(
kTextureAndViewDimensions
.beginSubcases()
.combine('textureLevels', [1, kLevels - 2, kLevels])
.unless(p => p.textureDimension === '1d' && p.textureLevels !== 1)
.expand(
'baseMipLevel',
({ textureLevels: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1])
)
.expand('mipLevelCount', function* ({ textureLevels: l, baseMipLevel = 0 }) {
yield undefined;
for (const lastMipLevel of new Set([0, 1, 5, 6, 7, l - 1, l, l + 1])) {
if (baseMipLevel <= lastMipLevel) yield lastMipLevel - baseMipLevel;
}
})
)
.fn(t => {
const { textureDimension, viewDimension, textureLevels, baseMipLevel, mipLevelCount } =
t.params;
t.skipIfTextureViewDimensionNotSupported(viewDimension);
const textureDescriptor: GPUTextureDescriptor = {
format: 'rgba8unorm',
dimension: textureDimension,
size:
textureDimension === '1d' ? [32] : textureDimension === '3d' ? [32, 32, 32] : [32, 32, 18],
mipLevelCount: textureLevels,
usage: GPUTextureUsage.TEXTURE_BINDING,
};
const viewDescriptor = { dimension: viewDimension, baseMipLevel, mipLevelCount };
const success = validateCreateViewLayersLevels(textureDescriptor, viewDescriptor);
const texture = t.createTextureTracked(textureDescriptor);
t.debug(`${mipLevelCount} ${success}`);
t.expectValidationError(() => {
texture.createView(viewDescriptor);
}, !success);
});
g.test('cube_faces_square')
.desc(
`Test that the X/Y dimensions of cube and cube array textures must be square.
- {2d (control case), cube, cube-array}`
)
.params(u =>
u //
.combine('dimension', ['2d', 'cube', 'cube-array'] as const)
.combine('size', [
[4, 4, 6],
[5, 5, 6],
[4, 5, 6],
[4, 8, 6],
[8, 4, 6],
])
)
.fn(t => {
const { dimension, size } = t.params;
t.skipIfTextureViewDimensionNotSupported(dimension);
const texture = t.createTextureTracked({
format: 'rgba8unorm',
size,
usage: GPUTextureUsage.TEXTURE_BINDING,
});
const success = dimension === '2d' || size[0] === size[1];
t.expectValidationError(() => {
texture.createView({ dimension });
}, !success);
});
g.test('texture_state')
.desc(`createView should fail if the texture is invalid (but succeed if it is destroyed)`)
.paramsSubcasesOnly(u => u.combine('state', kResourceStates))
.fn(t => {
const { state } = t.params;
const texture = vtu.createTextureWithState(t, state);
t.expectValidationError(() => {
texture.createView();
}, state === 'invalid');
});
g.test('texture_view_usage')
.desc(
`Test texture view usage (single, combined, inherited) for every texture format and texture usage`
)
.params(u =>
u //
.combine('format', kAllTextureFormats)
.combine('textureUsage', kTextureUsages)
.unless(({ format, textureUsage }) => {
return (
(textureUsage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
!isTextureFormatPossiblyUsableAsRenderAttachment(format)
);
})
.beginSubcases()
.combine('textureViewUsage', [0, ...kTextureUsages])
)
.fn(t => {
const { format, textureUsage, textureViewUsage } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatDoesNotSupportUsage(textureUsage, format);
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
const texture = t.createTextureTracked({
size: [blockWidth, blockHeight, 1],
format,
usage: textureUsage,
});
let success = true;
// Texture view usage must be a subset of texture usage
if ((~textureUsage & textureViewUsage) !== 0) success = false;
t.expectValidationError(() => {
texture.createView({
usage: textureViewUsage,
});
}, !success);
});
g.test('texture_view_usage_with_view_format')
.desc(
`Test that the texture view usage must be supported by the view's format. Checks for every view format possible, and every usage supported by the texture's format`
)
.params(u =>
u
.combine('textureFormat', kAllTextureFormats)
.combine('usage', kTextureUsages)
.beginSubcases()
.combine('viewFormat', kAllTextureFormats)
)
.fn(t => {
const { textureFormat, viewFormat, usage } = t.params;
t.skipIfTextureFormatNotSupported(textureFormat, viewFormat);
t.skipIfTextureFormatDoesNotSupportUsage(usage, textureFormat);
if (!textureFormatsAreViewCompatible(t.device, textureFormat, viewFormat)) {
t.skip(`"${textureFormat}" and "${viewFormat}" are not view-compatible`);
}
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(textureFormat);
const texture = t.createTextureTracked({
size: [blockWidth, blockHeight, 1],
format: textureFormat,
usage,
viewFormats: [viewFormat],
});
let success = true;
// Texture view usage must be a subset of texture usage
if (usage & GPUTextureUsage.STORAGE_BINDING) {
if (!isTextureFormatUsableWithStorageAccessMode(t.device, viewFormat, 'write-only'))
success = false;
}
if (usage & GPUTextureUsage.RENDER_ATTACHMENT) {
if (isColorTextureFormat(viewFormat) && !isTextureFormatColorRenderable(t.device, viewFormat))
success = false;
}
// Test with explicitly setting the view usage.
t.expectValidationError(() => {
texture.createView({
usage,
format: viewFormat,
});
}, !success);
// Test with inheriting the view usage.
t.expectValidationError(() => {
texture.createView({
format: viewFormat,
});
}, !success);
});