export const description = `
Execution tests for the 'textureDimensions' builtin function
The dimensions of the texture in texels.
For textures based on cubes, the results are the dimensions of each face of the cube.
Cube faces are square, so the x and y components of the result are equal.
If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import {
getBlockInfoForTextureFormat,
isDepthTextureFormat,
isStencilTextureFormat,
isTextureFormatPossiblyMultisampled,
isTextureFormatPossiblyStorageReadWritable,
kAllTextureFormats,
kDepthTextureFormats,
kPossibleStorageTextureFormats,
sampleTypeForFormatAndAspect,
textureFormatAndDimensionPossiblyCompatible,
} from '../../../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../../gpu_test.js';
import { align } from '../../../../../util/math.js';
import { kShaderStages, ShaderStage } from '../../../../validation/decl/util.js';
import {
executeTextureQueryAndExpectResult,
skipIfNoStorageTexturesInStage,
} from './texture_utils.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
const kMaxMipsForTest = 3;
const kMaxSamplesForTest = 4;
const kAllViewDimensions: readonly GPUTextureViewDimension[] = [
'1d',
'2d',
'2d-array',
'3d',
'cube',
'cube-array',
] as const;
function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] {
if (isDepthTextureFormat(format) && isStencilTextureFormat(format)) {
return ['depth-only', 'stencil-only'];
}
return ['all'];
}
function samplesForFormat(format: GPUTextureFormat): readonly number[] {
return isTextureFormatPossiblyMultisampled(format) ? [1, kMaxSamplesForTest] : [1];
}
* @returns a list of number of texture mipmap levels to test, given the format, view dimensions and
* number of samples.
*/
function textureMipCount(params: {
format: GPUTextureFormat;
dimensions: GPUTextureViewDimension;
samples?: number;
}): readonly number[] {
if (params.samples !== undefined && params.samples !== 1) {
return [1];
}
if (textureDimensionsForViewDimensions(params.dimensions) === '1d') {
return [1];
}
return [1, kMaxMipsForTest];
}
* @returns a list of GPUTextureViewDescriptor.baseMipLevel to test, give the texture mipmap count.
*/
function baseMipLevel(params: { textureMipCount: number }): readonly number[] {
const out: number[] = [];
for (let i = 0; i < params.textureMipCount; i++) {
out.push(i);
}
return out;
}
* @returns the argument values for the textureDimensions() `level` parameter to test.
* An `undefined` represents a call to textureDimensions() without the level argument.
*/
function textureDimensionsLevel(params: {
samples?: number;
textureMipCount: number;
baseMipLevel: number;
}): readonly (number | undefined)[] {
if (params.samples !== undefined && params.samples > 1) {
return [undefined];
}
const out: (number | undefined)[] = [undefined];
for (let i = 0; i < params.textureMipCount - params.baseMipLevel; i++) {
out.push(i);
}
return out;
}
function viewDimensions(params: {
format: GPUTextureFormat;
samples?: number;
}): readonly GPUTextureViewDimension[] {
if (params.samples !== undefined && params.samples > 1) {
return ['2d'];
}
return kAllViewDimensions.filter(dim =>
textureFormatAndDimensionPossiblyCompatible(
textureDimensionsForViewDimensions(dim),
params.format
)
);
}
function textureDimensionsForViewDimensions(dim: GPUTextureViewDimension): GPUTextureDimension {
switch (dim) {
case '1d':
return '1d';
case '2d':
case '2d-array':
case 'cube':
case 'cube-array':
return '2d';
case '3d':
return '3d';
}
}
type TestValues = {
size: number[];
expected: number[];
};
function testValues(params: {
dimensions: GPUTextureViewDimension;
format: GPUTextureFormat;
baseMipLevel: number;
textureDimensionsLevel?: number;
}): TestValues {
const kMinLen = 1 << kMaxMipsForTest;
const kNumCubeFaces = 6;
const formatInfo = getBlockInfoForTextureFormat(params.format);
const bw = formatInfo.blockWidth;
const bh = formatInfo.blockHeight;
let mip = params.baseMipLevel;
if (params.textureDimensionsLevel !== undefined) {
mip += params.textureDimensionsLevel;
}
const kMultipleA = 2;
const kMultipleB = 3;
const kMultipleC = 4;
switch (params.dimensions) {
case '1d': {
const w = align(kMinLen, bw) * kMultipleA;
return { size: [w], expected: [w >>> mip] };
}
case '2d': {
const w = align(kMinLen, bw) * kMultipleA;
const h = align(kMinLen, bh) * kMultipleB;
return { size: [w, h], expected: [w >>> mip, h >>> mip] };
}
case '2d-array': {
const w = align(kMinLen, bw) * kMultipleC;
const h = align(kMinLen, bh) * kMultipleB;
return { size: [w, h, 4], expected: [w >>> mip, h >>> mip] };
}
case '3d': {
const w = align(kMinLen, bw) * kMultipleA;
const h = align(kMinLen, bh) * kMultipleB;
const d = kMinLen * kMultipleC;
return {
size: [w, h, d],
expected: [w >>> mip, h >>> mip, d >>> mip],
};
}
case 'cube': {
const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleB;
return {
size: [l, l, kNumCubeFaces],
expected: [l >>> mip, l >>> mip],
};
}
case 'cube-array': {
const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleC;
return {
size: [l, l, kNumCubeFaces * 3],
expected: [l >>> mip, l >>> mip],
};
}
}
}
* Builds a shader module with the texture view bound to the WGSL texture with the given WGSL type,
* which calls textureDimensions(), assigning the result to an output buffer.
* This shader is executed with a compute shader, and the output buffer is compared to
* `values.expected`.
*/
function run(
t: GPUTest,
stage: ShaderStage,
texture: GPUTexture | GPUExternalTexture,
viewDescriptor: GPUTextureViewDescriptor | undefined,
textureType: string,
levelArg: number | undefined,
values: TestValues
) {
const outputType = values.expected.length > 1 ? `vec${values.expected.length}u` : 'u32';
const allowLet = t.hasLanguageFeature('texture_and_sampler_let');
const decl = allowLet ? 'let t = texture;' : '';
const tex = allowLet ? 't' : 'texture';
const wgsl = `
@group(0) @binding(0) var texture : ${textureType};
fn getValue() -> ${outputType} {
${decl}
return ${
levelArg !== undefined ? `textureDimensions(${tex}, ${levelArg})` : `textureDimensions(${tex})`
};
}
`;
executeTextureQueryAndExpectResult(t, stage, wgsl, texture, viewDescriptor, values.expected);
}
function dimensionsValidForStorage(dimensions: GPUTextureViewDimension) {
switch (dimensions) {
case '1d':
case '2d':
case '2d-array':
case '3d':
return true;
default:
return false;
}
}
g.test('sampled_and_multisampled')
.specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
.desc(
`
T: f32, i32, u32
fn textureDimensions(t: texture_1d<T>) -> u32
fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
Parameters:
* t: the sampled texture
* level:
- The mip level, with level 0 containing a full size version of the texture.
- If omitted, the dimensions of level 0 are returned.
`
)
.params(u =>
u
.combine('format', kAllTextureFormats)
.expand('aspect', u => aspectsForFormat(u.format))
.expand('samples', u => samplesForFormat(u.format))
.beginSubcases()
.combine('stage', kShaderStages)
.expand('dimensions', viewDimensions)
.expand('textureMipCount', textureMipCount)
.expand('baseMipLevel', baseMipLevel)
.expand('textureDimensionsLevel', textureDimensionsLevel)
)
.fn(t => {
t.skipIfTextureFormatNotSupported(t.params.format);
t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
t.skipIfTextureFormatAndDimensionNotCompatible(
t.params.format,
textureDimensionsForViewDimensions(t.params.dimensions)
);
if (t.params.samples > 1) {
t.skipIfTextureFormatNotMultisampled(t.params.format);
}
const values = testValues(t.params);
const texture = t.createTextureTracked({
size: values.size,
dimension: textureDimensionsForViewDimensions(t.params.dimensions),
...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
usage:
t.params.samples === 1
? GPUTextureUsage.TEXTURE_BINDING
: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
format: t.params.format,
sampleCount: t.params.samples,
mipLevelCount: t.params.textureMipCount,
});
const viewDescriptor: GPUTextureViewDescriptor = {
dimension: t.params.dimensions,
aspect: t.params.aspect,
baseMipLevel: t.params.baseMipLevel,
};
function wgslSampledTextureType(): string {
const base = t.params.samples !== 1 ? 'texture_multisampled' : 'texture';
const dimensions = t.params.dimensions.replace('-', '_');
const sampleType = sampleTypeForFormatAndAspect(t.params.format, t.params.aspect);
switch (sampleType) {
case 'depth':
case 'float':
case 'unfilterable-float':
return `${base}_${dimensions}<f32>`;
case 'uint':
return `${base}_${dimensions}<u32>`;
case 'sint':
return `${base}_${dimensions}<i32>`;
}
}
run(
t,
t.params.stage,
texture,
viewDescriptor,
wgslSampledTextureType(),
t.params.textureDimensionsLevel,
values
);
});
g.test('depth')
.specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
.desc(
`
fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
Parameters:
* t: the depth or multisampled texture
* level:
- The mip level, with level 0 containing a full size version of the texture.
- If omitted, the dimensions of level 0 are returned.
`
)
.params(u =>
u
.combine('format', kDepthTextureFormats)
.expand('aspect', u => aspectsForFormat(u.format))
.unless(u => u.aspect === 'stencil-only')
.expand('samples', u => samplesForFormat(u.format))
.beginSubcases()
.combine('stage', kShaderStages)
.expand('dimensions', viewDimensions)
.expand('textureMipCount', textureMipCount)
.expand('baseMipLevel', baseMipLevel)
.expand('textureDimensionsLevel', textureDimensionsLevel)
)
.fn(t => {
t.skipIfTextureFormatNotSupported(t.params.format);
t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
const values = testValues(t.params);
const texture = t.createTextureTracked({
size: values.size,
dimension: textureDimensionsForViewDimensions(t.params.dimensions),
...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
usage:
t.params.samples === 1
? GPUTextureUsage.TEXTURE_BINDING
: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
format: t.params.format,
sampleCount: t.params.samples,
mipLevelCount: t.params.textureMipCount,
});
const viewDescriptor: GPUTextureViewDescriptor = {
dimension: t.params.dimensions,
aspect: t.params.aspect,
baseMipLevel: t.params.baseMipLevel,
};
function wgslDepthTextureType(): string {
const base = t.params.samples !== 1 ? 'texture_depth_multisampled' : 'texture_depth';
const dimensions = t.params.dimensions.replace('-', '_');
return `${base}_${dimensions}`;
}
run(
t,
t.params.stage,
texture,
viewDescriptor,
wgslDepthTextureType(),
t.params.textureDimensionsLevel,
values
);
});
g.test('storage')
.specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
.desc(
`
F: rgba8unorm
rgba8snorm
rgba8uint
rgba8sint
rgba16uint
rgba16sint
rgba16float
r32uint
r32sint
r32float
rg32uint
rg32sint
rg32float
rgba32uint
rgba32sint
rgba32float
A: read, write, read_write
fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
Parameters:
* t: the storage texture
`
)
.params(u =>
u
.combine('format', kPossibleStorageTextureFormats)
.expand('aspect', u => aspectsForFormat(u.format))
.beginSubcases()
.combine('stage', kShaderStages)
.combine('access', ['read', 'write', 'read_write'] as const)
.unless(t => t.stage === 'vertex' && t.access !== 'read')
.unless(
t => !isTextureFormatPossiblyStorageReadWritable(t.format) && t.access === 'read_write'
)
.expand('dimensions', u => viewDimensions(u).filter(dimensionsValidForStorage))
.expand('textureMipCount', textureMipCount)
.expand('baseMipLevel', baseMipLevel)
)
.fn(t => {
skipIfNoStorageTexturesInStage(t, t.params.stage);
t.skipIfTextureFormatNotSupported(t.params.format);
t.skipIfTextureFormatNotUsableWithStorageAccessMode(t.params.access, t.params.format);
const values = testValues(t.params);
const texture = t.createTextureTracked({
size: values.size,
dimension: textureDimensionsForViewDimensions(t.params.dimensions),
usage: GPUTextureUsage.STORAGE_BINDING,
format: t.params.format,
mipLevelCount: t.params.textureMipCount,
});
const viewDescriptor: GPUTextureViewDescriptor = {
dimension: t.params.dimensions,
aspect: t.params.aspect,
mipLevelCount: 1,
baseMipLevel: t.params.baseMipLevel,
};
function wgslStorageTextureType(): string {
const dimensions = t.params.dimensions.replace('-', '_');
return `texture_storage_${dimensions}<${t.params.format}, ${t.params.access}>`;
}
run(t, t.params.stage, texture, viewDescriptor, wgslStorageTextureType(), undefined, values);
});
g.test('external')
.specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
.desc(
`
fn textureDimensions(t: texture_external) -> vec2<u32>
Parameters:
* t: the external texture
`
)
.params(u =>
u
.beginSubcases()
.combine('stage', kShaderStages)
.combine('importExternalTexture', [false, true])
.combine('width', [8, 16, 24] as const)
.combine('height', [8, 16, 24] as const)
)
.fn(t => {
const { stage, importExternalTexture, width, height } = t.params;
const size = [width, height];
t.skipIf(typeof OffscreenCanvas === 'undefined', 'OffscreenCanvas is not supported');
const canvas = new OffscreenCanvas(width, height);
canvas.getContext('2d');
let texture: GPUExternalTexture | GPUTexture;
let videoFrame: VideoFrame | undefined;
if (importExternalTexture) {
t.skipIf(typeof VideoFrame === 'undefined', 'VideoFrames are not supported');
videoFrame = new VideoFrame(canvas, { timestamp: 0 });
texture = t.device.importExternalTexture({ source: videoFrame });
} else {
texture = t.createTextureTracked({
format: 'rgba8unorm',
size,
usage:
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING,
});
t.queue.copyExternalImageToTexture({ source: canvas }, { texture }, size);
}
run(t, stage, texture, undefined, 'texture_external', undefined, {
size,
expected: size,
});
});