Skip to content

Commit

Permalink
Refactor copyTextureToTexture test for texture formats (#4195)
Browse files Browse the repository at this point in the history
Also expanded the multisample depth test and add
multisample stencil placeholder.

Issue: #4181
  • Loading branch information
greggman authored Feb 20, 2025
1 parent 6b56a20 commit b1b1ae6
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 63 deletions.
126 changes: 67 additions & 59 deletions src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import {
kTextureDimensions,
} from '../../../capability_info.js';
import {
kTextureFormatInfo,
kRegularTextureFormats,
kCompressedTextureFormats,
kDepthStencilFormats,
textureDimensionAndFormatCompatible,
depthStencilFormatAspectSize,
DepthStencilFormat,
ColorTextureFormat,
DepthStencilFormat,
depthStencilFormatAspectSize,
getBaseFormatForTextureFormat,
getBlockInfoForColorTextureFormat,
isCompressedTextureFormat,
viewCompatibleDeprecated,
RegularTextureFormat,
isDepthTextureFormat,
isRegularTextureFormat,
isStencilTextureFormat,
kCompressedTextureFormats,
kDepthStencilFormats,
kRegularTextureFormats,
RegularTextureFormat,
textureDimensionAndFormatCompatible,
textureFormatsAreViewCompatible,
} from '../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { AllFeaturesMaxLimitsGPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { checkElementsEqual } from '../../../util/check_contents.js';
import { align } from '../../../util/math.js';
import { physicalMipSize } from '../../../util/texture/base.js';
Expand All @@ -32,20 +35,17 @@ import { findFailedPixels } from '../../../util/texture/texture_ok.js';

const dataGenerator = new DataArrayGenerator();

class F extends TextureTestMixin(GPUTest) {
class F extends TextureTestMixin(AllFeaturesMaxLimitsGPUTest) {
GetInitialDataPerMipLevel(
dimension: GPUTextureDimension,
textureSize: Required<GPUExtent3DDict>,
format: ColorTextureFormat,
mipLevel: number
): Uint8Array {
const textureSizeAtLevel = physicalMipSize(textureSize, format, dimension, mipLevel);
const bytesPerBlock = kTextureFormatInfo[format].color.bytes;
const blockWidthInTexel = kTextureFormatInfo[format].blockWidth;
const blockHeightInTexel = kTextureFormatInfo[format].blockHeight;
const { bytesPerBlock, blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(format);
const blocksPerSubresource =
(textureSizeAtLevel.width / blockWidthInTexel) *
(textureSizeAtLevel.height / blockHeightInTexel);
(textureSizeAtLevel.width / blockWidth) * (textureSizeAtLevel.height / blockHeight);

const byteSize = bytesPerBlock * blocksPerSubresource * textureSizeAtLevel.depthOrArrayLayers;
return dataGenerator.generateView(byteSize);
Expand Down Expand Up @@ -121,9 +121,7 @@ class F extends TextureTestMixin(GPUTest) {
dimension,
srcCopyLevel
);
const bytesPerBlock = kTextureFormatInfo[srcFormat].color.bytes;
const blockWidth = kTextureFormatInfo[srcFormat].blockWidth;
const blockHeight = kTextureFormatInfo[srcFormat].blockHeight;
const { bytesPerBlock, blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(srcFormat);
const srcBlocksPerRow = srcTextureSizeAtLevel.width / blockWidth;
const srcBlockRowsPerImage = srcTextureSizeAtLevel.height / blockHeight;
this.device.queue.writeTexture(
Expand Down Expand Up @@ -208,7 +206,7 @@ class F extends TextureTestMixin(GPUTest) {
align(dstBlocksPerRow * bytesPerBlock, 4);

if (isCompressedTextureFormat(dstTexture.format) && this.isCompatibility) {
assert(viewCompatibleDeprecated(this.isCompatibility, srcFormat, dstFormat));
assert(textureFormatsAreViewCompatible(this.device, srcFormat, dstFormat));
// compare by rendering. We need the expected texture to match
// the dstTexture so we'll create a texture where we supply
// all of the data in JavaScript.
Expand Down Expand Up @@ -576,7 +574,7 @@ class F extends TextureTestMixin(GPUTest) {
});
const bindGroup = this.GetBindGroupForT2TCopyWithDepthTests(bindGroupLayout, copySize[2]);

const hasStencil = kTextureFormatInfo[sourceTexture.format].stencil;
const hasStencil = isStencilTextureFormat(sourceTexture.format);
const encoder = this.device.createCommandEncoder();
for (let srcCopyLayer = 0; srcCopyLayer < copySize[2]; ++srcCopyLayer) {
const renderPass = encoder.beginRenderPass({
Expand Down Expand Up @@ -625,7 +623,7 @@ class F extends TextureTestMixin(GPUTest) {
size: copySize,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});
const hasStencil = kTextureFormatInfo[destinationTexture.format].stencil;
const hasStencil = isStencilTextureFormat(destinationTexture.format);
const encoder = this.device.createCommandEncoder();
for (let dstCopyLayer = 0; dstCopyLayer < copySize[2]; ++dstCopyLayer) {
// If the depth value is not expected, the color of outputColorTexture will remain Red after
Expand Down Expand Up @@ -790,8 +788,8 @@ g.test('color_textures,non_compressed,non_array')
.combine('srcFormat', kRegularTextureFormats)
.combine('dstFormat', kRegularTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
Expand Down Expand Up @@ -885,8 +883,8 @@ g.test('color_textures,compressed,non_array')
.combine('srcFormat', kCompressedTextureFormats)
.combine('dstFormat', kCompressedTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
Expand Down Expand Up @@ -924,10 +922,6 @@ g.test('color_textures,compressed,non_array')
.beforeAllSubcases(t => {
const { srcFormat, dstFormat } = t.params;
t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
t.selectDeviceOrSkipTestCase([
kTextureFormatInfo[srcFormat].feature,
kTextureFormatInfo[dstFormat].feature,
]);
})
.fn(t => {
const {
Expand All @@ -939,10 +933,10 @@ g.test('color_textures,compressed,non_array')
srcCopyLevel,
dstCopyLevel,
} = t.params;
const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth;
const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight;
const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth;
const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight;
const { blockWidth: srcBlockWidth, blockHeight: srcBlockHeight } =
getBlockInfoForColorTextureFormat(srcFormat);
const { blockWidth: dstBlockWidth, blockHeight: dstBlockHeight } =
getBlockInfoForColorTextureFormat(dstFormat);

t.DoCopyTextureToTextureTest(
dimension,
Expand Down Expand Up @@ -977,8 +971,8 @@ g.test('color_textures,non_compressed,array')
.combine('srcFormat', kRegularTextureFormats)
.combine('dstFormat', kRegularTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
Expand Down Expand Up @@ -1050,8 +1044,8 @@ g.test('color_textures,compressed,array')
.combine('srcFormat', kCompressedTextureFormats)
.combine('dstFormat', kCompressedTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
Expand Down Expand Up @@ -1079,10 +1073,6 @@ g.test('color_textures,compressed,array')
.beforeAllSubcases(t => {
const { srcFormat, dstFormat } = t.params;
t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
t.selectDeviceOrSkipTestCase([
kTextureFormatInfo[srcFormat].feature,
kTextureFormatInfo[dstFormat].feature,
]);
})
.fn(t => {
const {
Expand All @@ -1094,10 +1084,12 @@ g.test('color_textures,compressed,array')
srcCopyLevel,
dstCopyLevel,
} = t.params;
const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth;
const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight;
const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth;
const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight;
t.skipIfTextureFormatNotSupported(srcFormat, dstFormat);

const { blockWidth: srcBlockWidth, blockHeight: srcBlockHeight } =
getBlockInfoForColorTextureFormat(srcFormat);
const { blockWidth: dstBlockWidth, blockHeight: dstBlockHeight } =
getBlockInfoForColorTextureFormat(dstFormat);

t.DoCopyTextureToTextureTest(
dimension,
Expand Down Expand Up @@ -1260,10 +1252,6 @@ g.test('copy_depth_stencil')
);
})
)
.beforeAllSubcases(t => {
const { format } = t.params;
t.selectDeviceForTextureFormatOrSkipTestCase(format);
})
.fn(t => {
const {
format,
Expand All @@ -1273,6 +1261,7 @@ g.test('copy_depth_stencil')
srcCopyBaseArrayLayer,
dstCopyBaseArrayLayer,
} = t.params;
t.skipIfTextureFormatNotSupported(format);

const copySize: [number, number, number] = [
srcTextureSize.width >> srcCopyLevel,
Expand All @@ -1299,7 +1288,7 @@ g.test('copy_depth_stencil')
});

let initialStencilData: undefined | Uint8Array = undefined;
if (kTextureFormatInfo[format].stencil) {
if (isStencilTextureFormat(format)) {
initialStencilData = t.GetInitialStencilDataPerMipLevel(srcTextureSize, format, srcCopyLevel);
t.InitializeStencilAspect(
sourceTexture,
Expand All @@ -1309,7 +1298,7 @@ g.test('copy_depth_stencil')
copySize
);
}
if (kTextureFormatInfo[format].depth) {
if (isDepthTextureFormat(format)) {
t.InitializeDepthAspect(sourceTexture, format, srcCopyLevel, srcCopyBaseArrayLayer, copySize);
}

Expand All @@ -1329,7 +1318,7 @@ g.test('copy_depth_stencil')
);
t.queue.submit([encoder.finish()]);

if (kTextureFormatInfo[format].stencil) {
if (isStencilTextureFormat(format)) {
assert(initialStencilData !== undefined);
t.VerifyStencilAspect(
destinationTexture,
Expand All @@ -1339,7 +1328,7 @@ g.test('copy_depth_stencil')
copySize
);
}
if (kTextureFormatInfo[format].depth) {
if (isDepthTextureFormat(format)) {
t.VerifyDepthAspect(
destinationTexture,
format,
Expand Down Expand Up @@ -1552,22 +1541,25 @@ g.test('copy_multisampled_depth')
texture can only be 1.
`
)
.params(u =>
u.combine('format', kDepthStencilFormats).filter(t => isDepthTextureFormat(t.format))
)
.beforeAllSubcases(t => {
t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
})
.fn(t => {
const { format } = t.params;
const textureSize = [32, 16, 1] as const;
const kDepthFormat = 'depth24plus';
const kSampleCount = 4;

const sourceTexture = t.createTextureTracked({
format: kDepthFormat,
format,
size: textureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const destinationTexture = t.createTextureTracked({
format: kDepthFormat,
format,
size: textureSize,
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
Expand Down Expand Up @@ -1596,7 +1588,7 @@ g.test('copy_multisampled_depth')
layout: 'auto',
vertex: vertexState,
depthStencil: {
format: kDepthFormat,
format,
depthCompare: 'always',
depthWriteEnabled: true,
},
Expand All @@ -1613,6 +1605,10 @@ g.test('copy_multisampled_depth')
depthClearValue: 0.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
...(isStencilTextureFormat(format) && {
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}),
},
});
renderPassForInit.setPipeline(renderPipelineForInit);
Expand Down Expand Up @@ -1651,7 +1647,7 @@ g.test('copy_multisampled_depth')
targets: [{ format: kColorFormat }],
},
depthStencil: {
format: kDepthFormat,
format,
depthCompare: 'equal',
depthWriteEnabled: false,
},
Expand Down Expand Up @@ -1686,6 +1682,10 @@ g.test('copy_multisampled_depth')
view: destinationTexture.createView(),
depthLoadOp: 'load',
depthStoreOp: 'store',
...(isStencilTextureFormat(format) && {
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}),
},
});
renderPassForVerify.setPipeline(renderPipelineForVerify);
Expand All @@ -1698,3 +1698,11 @@ g.test('copy_multisampled_depth')
exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
});
});

g.test('copy_multisampled_stencil')
.desc(
`
Validate the correctness of copyTextureToTexture() with multisampled stencil formats.
`
)
.unimplemented();
26 changes: 26 additions & 0 deletions src/webgpu/format_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,32 @@ export function textureFormatsAreViewCompatible(
: a === b || a + '-srgb' === b || b + '-srgb' === a;
}

/**
* Gets the block width, height, and bytes per block for a color texture format.
*/
export function getBlockInfoForColorTextureFormat(format: ColorTextureFormat) {
const info = kTextureFormatInfo[format];
return {
blockWidth: info.blockWidth,
blockHeight: info.blockHeight,
bytesPerBlock: info.color.bytes,
};
}

/**
* Gets the baseFormat for a texture format.
*/
export function getBaseFormatForTextureFormat(format: GPUTextureFormat) {
return kTextureFormatInfo[format].baseFormat;
}

/**
* Gets the feature needed for a give texture format or undefined if none.
*/
export function getRequiredFeatureForTextureFormat(format: GPUTextureFormat) {
return kTextureFormatInfo[format].feature;
}

export function getFeaturesForFormats<T>(
formats: readonly (T & (GPUTextureFormat | undefined))[]
): readonly (GPUFeatureName | undefined)[] {
Expand Down
17 changes: 13 additions & 4 deletions src/webgpu/gpu_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
EncodableTextureFormat,
isCompressedTextureFormat,
ColorTextureFormat,
getRequiredFeatureForTextureFormat,
isTextureFormatUsableAsStorageFormatDeprecated,
isMultisampledTextureFormatDeprecated,
isTextureFormatUsableAsStorageFormat,
Expand Down Expand Up @@ -526,12 +527,20 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
* Skips test if any format is not supported.
*/
skipIfTextureFormatNotSupported(...formats: (GPUTextureFormat | undefined)[]) {
if (isCompatibilityDevice(this.device)) {
for (const format of formats) {
if (format === 'bgra8unorm-srgb') {
this.skip(`texture format '${format} is not supported`);
for (const format of formats) {
if (!format) {
continue;
}
if (format === 'bgra8unorm-srgb') {
if (isCompatibilityDevice(this.device)) {
this.skip(`texture format '${format}' is not supported`);
}
}
const feature = getRequiredFeatureForTextureFormat(format);
this.skipIf(
!!feature && !this.device.features.has(feature),
`texture format '${format}' requires feature: '${feature}`
);
}
}

Expand Down

0 comments on commit b1b1ae6

Please sign in to comment.