Skip to content

Commit eba794d

Browse files
authored
Add copyBufferToTexture and copyTextureToBuffer validation tests (#4420)
In particular 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
1 parent 55d56ef commit eba794d

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed

src/webgpu/api/validation/image_copy/buffer_texture_copies.spec.ts

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ the general image_copy tests, or by destroyed,*.
55

66
import { makeTestGroup } from '../../../../common/framework/test_group.js';
77
import { assert, unreachable } from '../../../../common/util/util.js';
8-
import { kBufferUsages, kTextureUsages } from '../../../capability_info.js';
8+
import { kBufferUsages, kTextureDimensions, kTextureUsages } from '../../../capability_info.js';
99
import { GPUConst } from '../../../constants.js';
1010
import {
1111
kDepthStencilFormats,
1212
depthStencilBufferTextureCopySupported,
1313
depthStencilFormatAspectSize,
14+
kColorTextureFormats,
15+
canCopyFromAllAspectsOfTextureFormat,
16+
canCopyToAllAspectsOfTextureFormat,
17+
textureFormatAndDimensionPossiblyCompatible,
18+
getBlockInfoForColorTextureFormat,
1419
} from '../../../format_info.js';
1520
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
1621
import { align } from '../../../util/math.js';
@@ -442,3 +447,158 @@ g.test('device_mismatch')
442447
t.testCopyTextureToBuffer({ texture }, { buffer }, textureSize, isValid);
443448
}
444449
});
450+
451+
g.test('offset_and_bytesPerRow')
452+
.desc(
453+
`Test that for copyBufferToTexture, and copyTextureToBuffer
454+
* bytesPerRow must be a multiple of 256
455+
* offset must be a multiple of bytesPerBlock
456+
* the last row does not need to be a multiple of 256
457+
In other words, If the copy size is 4x2 of a r8unorm texture that's 4 bytes per row.
458+
To get from row 0 to row 1 in the buffer, bytesPerRow must be a multiple of 256.
459+
But, the size requirement for the buffer is only 256 + 4, not 256 * 2
460+
* origin.x must be a multiple of blockWidth
461+
* origin.y must be a multiple of blockHeight
462+
* copySize.width must be a multiple of blockWidth
463+
* copySize.height must be a multiple of blockHeight
464+
`
465+
)
466+
.params(u =>
467+
u
468+
.combine('format', kColorTextureFormats)
469+
.combine('copyType', ['CopyB2T', 'CopyT2B'] as const)
470+
.filter(
471+
({ format }) =>
472+
canCopyToAllAspectsOfTextureFormat(format) && canCopyFromAllAspectsOfTextureFormat(format)
473+
)
474+
.combine('dimension', kTextureDimensions)
475+
.filter(({ dimension, format }) =>
476+
textureFormatAndDimensionPossiblyCompatible(dimension, format)
477+
)
478+
.beginSubcases()
479+
.combineWithParams(
480+
/* prettier-ignore */ [
481+
{ xInBlocks: 1 , yInBlocks: 1 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 1 , bytesPerRowAlign: 256 }, // good
482+
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 1.5, bytesPerRowAlign: 256 }, // bad as offset is not blockSize
483+
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 128 }, // bad as bytesPerBlock is not multiple of 256
484+
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 384 }, // bad as bytesPerBlock is not multiple of 256
485+
{ xInBlocks: 1.5, yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as origin.x is not multiple of blockSize
486+
{ xInBlocks: 0 , yInBlocks: 1.5, copyWidthInBlocks: 64 , copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as origin.y is not multiple of blockSize
487+
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64.5, copyHeightInBlocks: 2 , offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as copySize.width is not multiple of blockSize
488+
{ xInBlocks: 0 , yInBlocks: 0 , copyWidthInBlocks: 64 , copyHeightInBlocks: 2.5, offsetInBlocks: 0 , bytesPerRowAlign: 256 }, // bad as copySize.height is not multiple of blockSize
489+
] as const
490+
)
491+
// Remove non-integer offsetInBlocks, copyWidthInBlocks, copyHeightInBlocks if bytesPerBlock === 1
492+
.unless(
493+
t =>
494+
(t.offsetInBlocks % 1 !== 0 ||
495+
t.copyWidthInBlocks % 1 !== 0 ||
496+
t.copyHeightInBlocks % 1 !== 0) &&
497+
getBlockInfoForColorTextureFormat(t.format).bytesPerBlock > 1
498+
)
499+
// Remove yInBlocks > 0 if dimension is 1d
500+
.unless(t => t.dimension === '1d' && t.yInBlocks > 0)
501+
)
502+
.fn(t => {
503+
const {
504+
copyType,
505+
format,
506+
dimension,
507+
xInBlocks,
508+
yInBlocks,
509+
offsetInBlocks,
510+
copyWidthInBlocks,
511+
copyHeightInBlocks,
512+
bytesPerRowAlign,
513+
} = t.params;
514+
t.skipIfTextureFormatNotSupported(format);
515+
t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
516+
if (copyType === 'CopyT2B') {
517+
t.skipIfTextureFormatDoesNotSupportCopyTextureToBuffer(format);
518+
}
519+
520+
const info = getBlockInfoForColorTextureFormat(format);
521+
522+
// make a texture big enough that we have room for our copySize and our origin.
523+
// Note that xxxInBlocks may be factional so that we test origins and sizes not aligned to blocks.
524+
const widthBlocks = Math.ceil(xInBlocks) + Math.ceil(copyWidthInBlocks);
525+
const heightBlocks = Math.ceil(yInBlocks) + Math.ceil(copyHeightInBlocks);
526+
let copySizeBlocks = [copyWidthInBlocks, copyHeightInBlocks, 1];
527+
let texSizeBlocks = [widthBlocks, heightBlocks, 1];
528+
if (dimension === '1d') {
529+
copySizeBlocks = [copyWidthInBlocks, 1, 1];
530+
texSizeBlocks = [widthBlocks, 1, 1];
531+
}
532+
533+
const origin = [
534+
Math.ceil(xInBlocks * info.blockWidth),
535+
Math.ceil(yInBlocks * info.blockHeight),
536+
0,
537+
];
538+
const copySize = [
539+
Math.ceil(copySizeBlocks[0] * info.blockWidth),
540+
Math.ceil(copySizeBlocks[1] * info.blockHeight),
541+
copySizeBlocks[2],
542+
];
543+
const textureSize = [
544+
texSizeBlocks[0] * info.blockWidth,
545+
texSizeBlocks[1] * info.blockHeight,
546+
texSizeBlocks[2],
547+
] as const;
548+
const textureBytePerRow = info.bytesPerBlock * texSizeBlocks[0];
549+
const rowsPerImage = Math.ceil(copySizeBlocks[1]);
550+
const offset = Math.ceil(offsetInBlocks * info.bytesPerBlock);
551+
const bytesPerRow = align(textureBytePerRow, bytesPerRowAlign);
552+
553+
// Make sure our buffer is big enough for the required alignment
554+
// and offset but no bigger.
555+
const totalRows = rowsPerImage * copySizeBlocks[2];
556+
const bufferSize = offset + (totalRows - 1) * bytesPerRow + textureBytePerRow;
557+
558+
const buffer = t.createBufferTracked({
559+
label: `buffer(${bufferSize})`,
560+
size: bufferSize,
561+
usage: copyType === 'CopyB2T' ? GPUBufferUsage.COPY_SRC : GPUBufferUsage.COPY_DST,
562+
});
563+
564+
const texture = t.createTextureTracked({
565+
size: textureSize,
566+
format,
567+
dimension,
568+
usage: copyType === 'CopyB2T' ? GPUTextureUsage.COPY_DST : GPUTextureUsage.COPY_SRC,
569+
});
570+
571+
const shouldSucceed =
572+
offset % info.bytesPerBlock === 0 &&
573+
bytesPerRow % 256 === 0 &&
574+
origin[0] % info.blockWidth === 0 &&
575+
origin[1] % info.blockHeight === 0 &&
576+
copySize[0] % info.blockWidth === 0 &&
577+
copySize[1] % info.blockHeight === 0;
578+
579+
t.debug(
580+
() =>
581+
`offset: ${offset}, bytesPerRow: ${bytesPerRow}, copySize: ${copySize}, origin: ${origin}`
582+
);
583+
584+
switch (copyType) {
585+
case 'CopyB2T': {
586+
t.testCopyBufferToTexture(
587+
{ buffer, offset, bytesPerRow },
588+
{ texture, origin },
589+
copySize,
590+
shouldSucceed
591+
);
592+
break;
593+
}
594+
case 'CopyT2B': {
595+
t.testCopyTextureToBuffer(
596+
{ texture, origin },
597+
{ buffer, offset, bytesPerRow },
598+
copySize,
599+
shouldSucceed
600+
);
601+
break;
602+
}
603+
}
604+
});

src/webgpu/gpu_test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,13 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
637637
}
638638
}
639639

640+
skipIfTextureFormatDoesNotSupportCopyTextureToBuffer(format: GPUTextureFormat) {
641+
this.skipIf(
642+
!this.canCallCopyTextureToBufferWithTextureFormat(format),
643+
`can not use copyTextureToBuffer with ${format}`
644+
);
645+
}
646+
640647
/** Skips this test case if the `langFeature` is *not* supported. */
641648
skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
642649
if (!this.hasLanguageFeature(langFeature)) {

0 commit comments

Comments
 (0)