Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class F extends AllFeaturesMaxLimitsGPUTest {
copyExtent: Required<GPUExtent3DDict>;
},
srcCopyLevel: number,
dstCopyLevel: number
dstCopyLevel: number,
requestedMipLevelCount?: number
): void {
this.skipIfTextureFormatNotSupported(srcFormat, dstFormat);
this.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
Expand All @@ -107,7 +108,7 @@ class F extends AllFeaturesMaxLimitsGPUTest {
isCompressedTextureFormat(dstFormat) && this.isCompatibility
? GPUTextureUsage.TEXTURE_BINDING
: 0;
const mipLevelCount = dimension === '1d' ? 1 : 4;
const mipLevelCount = requestedMipLevelCount ?? (dimension === '1d' ? 1 : 4);

// Create srcTexture and dstTexture
const srcTextureDesc: GPUTextureDescriptor = applyTextureBindingViewDimensionForTest({
Expand Down Expand Up @@ -972,6 +973,68 @@ g.test('color_textures,compressed,non_array')
);
});

g.test('color_textures,compressed,unaligned,non_array')
.desc(
`
Validate the correctness of copyTextureToTexture for block-compressed textures whose mip level 0
size is NOT a multiple of the texel block size, using the 'texture-compression-unaligned' feature.

This mirrors color_textures,compressed,non_array but creates textures with an unaligned mip level 0
(so the top mip level itself has partial edge blocks) and copies at mip level 0. As with non-zero
mip levels, the copy is validated and performed against the physical (rounded-up) size, so the copy
accesses the texture blocks at the edge which are not fully inside the texture.

Tests for all pairs of valid source/destination formats, with the partial edge block in the width,
height, or both dimensions.
`
)
.params(u =>
u
.combine('srcFormat', kCompressedTextureFormats)
.combine('dstFormat', kCompressedTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
dstBaseFormat !== undefined &&
srcBaseFormat === dstBaseFormat)
);
})
.beginSubcases()
// Which dimension(s) of mip level 0 have a partial edge block.
.combine('partialEdge', ['width', 'height', 'both'] as const)
.combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth)
)
.fn(t => {
const { partialEdge, srcFormat, dstFormat, copyBoxOffsets } = t.params;
t.skipIfDeviceDoesNotHaveFeature('texture-compression-unaligned' as GPUFeatureName);
t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);

// The source and destination formats share the same base format, so they have the same texel
// block size.
const { blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(srcFormat);

// Mip level 0 size: a few full blocks plus a one-texel partial edge block in the selected
// dimension(s). An unaligned mip level 0 is only valid with 'texture-compression-unaligned'.
const width = partialEdge === 'height' ? 4 * blockWidth : 3 * blockWidth + 1;
const height = partialEdge === 'width' ? 4 * blockHeight : 3 * blockHeight + 1;
const size = { width, height, depthOrArrayLayers: 1 };

t.doCopyTextureToTextureTest(
'2d',
size,
size,
srcFormat,
dstFormat,
copyBoxOffsets,
0, // srcCopyLevel: the top (and only) mip level.
0, // dstCopyLevel
1 // mipLevelCount: a single, unaligned mip level.
);
});

g.test('color_textures,non_compressed,array')
.desc(
`
Expand Down
82 changes: 82 additions & 0 deletions src/webgpu/api/operation/command_buffer/image_copy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,88 @@ TODO: Make a variant for depth-stencil formats.
});
});

g.test('compressed_textures,unaligned_mip_level_0')
.desc(
`
Test image copies (writeTexture / copyBufferToTexture / copyTextureToBuffer) that touch the partial
edge blocks of mip level 0 of a block-compressed texture whose mip level 0 size is NOT a multiple
of the texel block size, using the 'texture-compression-unaligned' feature.

This mirrors the 'mip_levels' test (which exercises the physical-size != logical-size path at
non-zero mip levels), but here the partial edge blocks are at mip level 0 itself. As elsewhere, the
copy is performed against the physical (rounded-up) size, so block-aligned copies reach the edge
blocks which are not fully inside the texture.

Covers the full copy as well as just the edge column / row / corner blocks, for the WriteTexture,
CopyB2T and CopyT2B methods.
`
)
.params(u =>
u
.combineWithParams(kMethodsToTest)
.combine('format', kColorTextureFormats)
.filter(formatCanBeTested)
.filter(({ format }) => isCompressedTextureFormat(format))
.beginSubcases()
// Copy box in block units. The mip level 0 size below is 3 full blocks + 1 partial-edge block
// (physical size 4 blocks) in each dimension, so x/y of 3 selects the partial edge block.
.combine('copyCase', [
// Full copy of the physical extent (touches every partial edge block).
{ originInBlocks: { x: 0, y: 0 }, copySizeInBlocks: { width: 4, height: 4 } },
// Just the partial edge column / row / corner.
{ originInBlocks: { x: 3, y: 0 }, copySizeInBlocks: { width: 1, height: 4 } },
{ originInBlocks: { x: 0, y: 3 }, copySizeInBlocks: { width: 4, height: 1 } },
{ originInBlocks: { x: 3, y: 3 }, copySizeInBlocks: { width: 1, height: 1 } },
// Interior block that does not touch a partial edge block.
{ originInBlocks: { x: 0, y: 0 }, copySizeInBlocks: { width: 1, height: 1 } },
] as const)
)
.fn(t => {
const { format, initMethod, checkMethod, copyCase } = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfDeviceDoesNotHaveFeature('texture-compression-unaligned' as GPUFeatureName);

const info = getBlockInfoForColorTextureFormat(format);

// Unaligned mip level 0: three full blocks plus a one-texel partial edge block in each dimension
// (physical size four blocks). This is only valid with 'texture-compression-unaligned'.
const textureSize = [3 * info.blockWidth + 1, 3 * info.blockHeight + 1, 1] as const;

const origin = {
x: copyCase.originInBlocks.x * info.blockWidth,
y: copyCase.originInBlocks.y * info.blockHeight,
z: 0,
};
const copySize = {
width: copyCase.copySizeInBlocks.width * info.blockWidth,
height: copyCase.copySizeInBlocks.height * info.blockHeight,
depthOrArrayLayers: 1,
};

const rowsPerImage = copyCase.copySizeInBlocks.height + 1;
const bytesPerRow = align(copySize.width, 256);

const dataSize = dataBytesForCopyOrFail({
layout: { offset: 0, bytesPerRow, rowsPerImage },
format,
copySize,
method: initMethod,
});

t.uploadTextureAndVerifyCopy({
textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
copySize,
dataSize,
origin,
mipLevel: 0,
textureSize,
format,
dimension: '2d',
initMethod,
checkMethod,
});
});

const UND = undefined;
g.test('undefined_params')
.desc(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export const description = `
Tests for capability checking for the 'texture-compression-unaligned' feature.

When the feature is not enabled, the behavior is unchanged: the size of mip level 0 of a
block-compressed texture must be a multiple of the texel block size. When the feature is enabled,
mip level 0 is allowed to have a size that is not a multiple of the texel block size (i.e. partial
edge blocks).
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import {
kCompressedTextureFormats,
getBlockInfoForTextureFormat,
getRequiredFeatureForTextureFormat,
} from '../../../../format_info.js';
import { UniqueFeaturesOrLimitsGPUTest } from '../../../../gpu_test.js';

export const g = makeTestGroup(UniqueFeaturesOrLimitsGPUTest);

const kTextureCompressionUnaligned = 'texture-compression-unaligned' as GPUFeatureName;

g.test('createTexture,unaligned_size')
.desc(
`Test that creating a compressed texture whose mip level 0 size is not a multiple of the texel
block size succeeds if and only if 'texture-compression-unaligned' is enabled.`
)
.params(u =>
u
.combine('format', kCompressedTextureFormats)
.combine('enable_feature', [true, false])
// The unaligned dimension(s) of mip level 0 being tested.
.combine('sizeCase', ['width', 'height', 'both', 'single'] as const)
)
.beforeAllSubcases(t => {
const { format, enable_feature } = t.params;

const requiredFeatures: GPUFeatureName[] = [];
const formatFeature = getRequiredFeatureForTextureFormat(format);
if (formatFeature) {
requiredFeatures.push(formatFeature);
}
if (enable_feature) {
requiredFeatures.push(kTextureCompressionUnaligned);
}

t.selectDeviceOrSkipTestCase({ requiredFeatures });
})
.fn(t => {
const { format, enable_feature, sizeCase } = t.params;
t.skipIfTextureFormatNotSupported(format);

const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);

// Construct a mip level 0 size that is not a multiple of the texel block size.
const size = (() => {
switch (sizeCase) {
case 'width':
return [blockWidth + 1, blockHeight, 1];
case 'height':
return [blockWidth, blockHeight + 1, 1];
case 'both':
return [blockWidth + 1, blockHeight + 1, 1];
case 'single':
// A single partial edge block in both dimensions.
return [1, 1, 1];
}
})();

const descriptor: GPUTextureDescriptor = {
size,
format,
usage: GPUTextureUsage.TEXTURE_BINDING,
};

// Without the feature, an unaligned mip level 0 size is a validation error.
t.expectValidationError(() => {
t.createTextureTracked(descriptor);
}, !enable_feature);
});
32 changes: 26 additions & 6 deletions src/webgpu/api/validation/createTexture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const description = `createTexture validation tests.`;

import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, makeValueTestVariant } from '../../../common/util/util.js';
import { assert, hasFeature, makeValueTestVariant } from '../../../common/util/util.js';
import {
kTextureDimensions,
kTextureUsages,
Expand Down Expand Up @@ -494,9 +494,17 @@ g.test('texture_size,default_value_and_smallest_size,compressed_format')
usage: GPUTextureUsage.TEXTURE_BINDING,
};

// With 'texture-compression-unaligned', mip level 0 is no longer required to be a multiple of
Comment thread
Kangz marked this conversation as resolved.
// the texel block size, so every (small, in-range) size in this test becomes valid.
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success = supportsUnaligned || _success;

t.expectValidationError(() => {
t.createTextureTracked(descriptor);
}, !_success);
}, !success);
});

g.test('texture_size,1d_texture')
Expand Down Expand Up @@ -755,9 +763,15 @@ g.test('texture_size,2d_texture,compressed_format')
usage: GPUTextureUsage.TEXTURE_BINDING,
};

// With 'texture-compression-unaligned', mip level 0 of a compressed texture is no longer
// required to be a multiple of the texel block size, so unaligned widths/heights are valid.
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success =
size[0] % info.blockWidth === 0 &&
size[1] % info.blockHeight === 0 &&
(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;
Expand Down Expand Up @@ -989,9 +1003,15 @@ g.test('texture_size,3d_texture,compressed_format')
usage: GPUTextureUsage.TEXTURE_BINDING,
};

// With 'texture-compression-unaligned', mip level 0 of a compressed texture is no longer
// required to be a multiple of the texel block size, so unaligned widths/heights are valid.
const supportsUnaligned = hasFeature(
t.device.features,
'texture-compression-unaligned' as GPUFeatureName
);
const success =
size[0] % info.blockWidth === 0 &&
size[1] % info.blockHeight === 0 &&
(supportsUnaligned ||
(size[0] % info.blockWidth === 0 && size[1] % info.blockHeight === 0)) &&
size[0] <= maxTextureDimension3D &&
size[1] <= maxTextureDimension3D &&
size[2] <= maxTextureDimension3D &&
Expand Down
1 change: 1 addition & 0 deletions src/webgpu/capability_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ export const kFeatureNameInfo: {
'texture-component-swizzle': {},
'subgroup-size-control': {},
['atomic-vec2u-min-max' as GPUFeatureName]: {},
['texture-compression-unaligned' as GPUFeatureName]: {},
};
/** List of all GPUFeatureName values. */
export const kFeatureNames = keysOf(kFeatureNameInfo);
Expand Down
Loading