Skip to content
Merged
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
162 changes: 161 additions & 1 deletion src/webgpu/api/validation/image_copy/buffer_texture_copies.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ the general image_copy tests, or by destroyed,*.

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, unreachable } from '../../../../common/util/util.js';
import { kBufferUsages, kTextureUsages } from '../../../capability_info.js';
import { kBufferUsages, kTextureDimensions, kTextureUsages } from '../../../capability_info.js';
import { GPUConst } from '../../../constants.js';
import {
kDepthStencilFormats,
depthStencilBufferTextureCopySupported,
depthStencilFormatAspectSize,
kColorTextureFormats,
canCopyFromAllAspectsOfTextureFormat,
canCopyToAllAspectsOfTextureFormat,
textureFormatAndDimensionPossiblyCompatible,
getBlockInfoForColorTextureFormat,
} from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import { align } from '../../../util/math.js';
Expand Down Expand Up @@ -442,3 +447,158 @@ g.test('device_mismatch')
t.testCopyTextureToBuffer({ texture }, { buffer }, textureSize, isValid);
}
});

g.test('offset_and_bytesPerRow')
.desc(
`Test that for copyBufferToTexture, and copyTextureToBuffer
* bytesPerRow must be a multiple of 256
* offset must be a multiple of bytesPerBlock
* the last row does not need to be a multiple of 256
In other words, If the copy size is 4x2 of a r8unorm texture that's 4 bytes per row.
To get from row 0 to row 1 in the buffer, bytesPerRow must be a multiple of 256.
But, the size requirement for the buffer is only 256 + 4, not 256 * 2
* origin.x must be a multiple of blockWidth
* origin.y must be a multiple of blockHeight
* copySize.width must be a multiple of blockWidth
* copySize.height must be a multiple of blockHeight
`
)
.params(u =>
u
.combine('format', kColorTextureFormats)
.combine('copyType', ['CopyB2T', 'CopyT2B'] as const)
.filter(
({ format }) =>
canCopyToAllAspectsOfTextureFormat(format) && canCopyFromAllAspectsOfTextureFormat(format)
)
.combine('dimension', kTextureDimensions)
.filter(({ dimension, format }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, format)
)
.beginSubcases()
.combineWithParams(
/* prettier-ignore */ [
{ xInBlocks: 1 , yInBlocks: 1 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 1 , bytesPerRowAlign: 256 }, // good
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 1.5, bytesPerRowAlign: 256 }, // bad as offset is not blockSize
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 128 }, // bad as bytesPerBlock is not multiple of 256
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 384 }, // bad as bytesPerBlock is not multiple of 256
{ xInBlocks: 1.5, yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as origin.x is not multiple of blockSize
{ xInBlocks: 0 , yInBlocks: 1.5, copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as origin.y is not multiple of blockSize
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64.5, copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as copySize.width is not multiple of blockSize
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2.5, offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as copySize.height is not multiple of blockSize
] as const
)
// Remove non-integer offsetInBlocks, copyWidthInBlocks, copyHeightInBlocks if bytesPerBlock === 1
.unless(
t =>
(t.offsetInBlocks % 1 !== 0 ||
t.copyWidthInBlocks % 1 !== 0 ||
t.copyHeightInBlocks % 1 !== 0) &&
getBlockInfoForColorTextureFormat(t.format).bytesPerBlock > 1
)
// Remove yInBlocks > 0 if dimension is 1d
.unless(t => t.dimension === '1d' && t.yInBlocks > 0)
)
.fn(t => {
const {
copyType,
format,
dimension,
xInBlocks,
yInBlocks,
offsetInBlocks,
copyWidthInBlocks,
copyHeightInBlocks,
bytesPerRowAlign,
} = t.params;
t.skipIfTextureFormatNotSupported(format);
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
if (copyType === 'CopyT2B') {
t.skipIfTextureFormatDoesNotSupportCopyTextureToBuffer(format);
}

const info = getBlockInfoForColorTextureFormat(format);

// make a texture big enough that we have room for our copySize and our origin.
// Note that xxxInBlocks may be factional so that we test origins and sizes not aligned to blocks.
const widthBlocks = Math.ceil(xInBlocks) + Math.ceil(copyWidthInBlocks);
const heightBlocks = Math.ceil(yInBlocks) + Math.ceil(copyHeightInBlocks);
let copySizeBlocks = [copyWidthInBlocks, copyHeightInBlocks, 1];
let texSizeBlocks = [widthBlocks, heightBlocks, 1];
if (dimension === '1d') {
copySizeBlocks = [copyWidthInBlocks, 1, 1];
texSizeBlocks = [widthBlocks, 1, 1];
}

const origin = [
Math.ceil(xInBlocks * info.blockWidth),
Math.ceil(yInBlocks * info.blockHeight),
0,
];
const copySize = [
Math.ceil(copySizeBlocks[0] * info.blockWidth),
Math.ceil(copySizeBlocks[1] * info.blockHeight),
copySizeBlocks[2],
];
const textureSize = [
texSizeBlocks[0] * info.blockWidth,
texSizeBlocks[1] * info.blockHeight,
texSizeBlocks[2],
] as const;
const textureBytePerRow = info.bytesPerBlock * texSizeBlocks[0];
const rowsPerImage = Math.ceil(copySizeBlocks[1]);
const offset = Math.ceil(offsetInBlocks * info.bytesPerBlock);
const bytesPerRow = align(textureBytePerRow, bytesPerRowAlign);

// Make sure our buffer is big enough for the required alignment
// and offset but no bigger.
const totalRows = rowsPerImage * copySizeBlocks[2];
const bufferSize = offset + (totalRows - 1) * bytesPerRow + textureBytePerRow;

const buffer = t.createBufferTracked({
label: `buffer(${bufferSize})`,
size: bufferSize,
usage: copyType === 'CopyB2T' ? GPUBufferUsage.COPY_SRC : GPUBufferUsage.COPY_DST,
});

const texture = t.createTextureTracked({
size: textureSize,
format,
dimension,
usage: copyType === 'CopyB2T' ? GPUTextureUsage.COPY_DST : GPUTextureUsage.COPY_SRC,
});

const shouldSucceed =
offset % info.bytesPerBlock === 0 &&
bytesPerRow % 256 === 0 &&
origin[0] % info.blockWidth === 0 &&
origin[1] % info.blockHeight === 0 &&
copySize[0] % info.blockWidth === 0 &&
copySize[1] % info.blockHeight === 0;

t.debug(
() =>
`offset: ${offset}, bytesPerRow: ${bytesPerRow}, copySize: ${copySize}, origin: ${origin}`
);

switch (copyType) {
case 'CopyB2T': {
t.testCopyBufferToTexture(
{ buffer, offset, bytesPerRow },
{ texture, origin },
copySize,
shouldSucceed
);
break;
}
case 'CopyT2B': {
t.testCopyTextureToBuffer(
{ texture, origin },
{ buffer, offset, bytesPerRow },
copySize,
shouldSucceed
);
break;
}
}
});
7 changes: 7 additions & 0 deletions src/webgpu/gpu_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,13 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
}
}

skipIfTextureFormatDoesNotSupportCopyTextureToBuffer(format: GPUTextureFormat) {
this.skipIf(
!this.canCallCopyTextureToBufferWithTextureFormat(format),
`can not use copyTextureToBuffer with ${format}`
);
}

/** Skips this test case if the `langFeature` is *not* supported. */
skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
if (!this.hasLanguageFeature(langFeature)) {
Expand Down