export const description = `copyTextureToTexture operation tests`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, ErrorWithExtra, memcpy } from '../../../../common/util/util.js';
import {
kBufferSizeAlignment,
kMinDynamicBufferOffsetAlignment,
kTextureDimensions,
} from '../../../capability_info.js';
import {
ColorTextureFormat,
DepthStencilFormat,
depthStencilFormatAspectSize,
getBaseFormatForTextureFormat,
getBlockInfoForColorTextureFormat,
isCompressedTextureFormat,
isDepthTextureFormat,
isRegularTextureFormat,
isStencilTextureFormat,
kCompressedTextureFormats,
kDepthStencilFormats,
kRegularTextureFormats,
RegularTextureFormat,
textureFormatAndDimensionPossiblyCompatible,
textureFormatsAreViewCompatible,
} from '../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import * as ttu from '../../../texture_test_utils.js';
import { checkElementsEqual } from '../../../util/check_contents.js';
import { align } from '../../../util/math.js';
import { physicalMipSize } from '../../../util/texture/base.js';
import { DataArrayGenerator } from '../../../util/texture/data_generation.js';
import { kBytesPerRowAlignment, dataBytesForCopyOrFail } from '../../../util/texture/layout.js';
import { TexelView } from '../../../util/texture/texel_view.js';
import { findFailedPixels } from '../../../util/texture/texture_ok.js';
import { reifyExtent3D } from '../../../util/unions.js';
const dataGenerator = new DataArrayGenerator();
function applyTextureBindingViewDimensionForTest(descriptor: GPUTextureDescriptor) {
const size = reifyExtent3D(descriptor.size);
if (
descriptor.textureBindingViewDimension === undefined &&
descriptor.dimension === '2d' &&
size.width === size.height &&
size.depthOrArrayLayers === 6
) {
descriptor.textureBindingViewDimension = 'cube';
}
return descriptor;
}
class F extends AllFeaturesMaxLimitsGPUTest {
getInitialDataPerMipLevel(
dimension: GPUTextureDimension,
textureSize: Required<GPUExtent3DDict>,
format: ColorTextureFormat,
mipLevel: number
): Uint8Array {
const textureSizeAtLevel = physicalMipSize(textureSize, format, dimension, mipLevel);
const { bytesPerBlock, blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(format);
const blocksPerSubresource =
(textureSizeAtLevel.width / blockWidth) * (textureSizeAtLevel.height / blockHeight);
const byteSize = bytesPerBlock * blocksPerSubresource * textureSizeAtLevel.depthOrArrayLayers;
return dataGenerator.generateView(byteSize);
}
getInitialStencilDataPerMipLevel(
textureSize: Required<GPUExtent3DDict>,
format: DepthStencilFormat,
mipLevel: number
): Uint8Array {
const textureSizeAtLevel = physicalMipSize(textureSize, format, '2d', mipLevel);
const aspectBytesPerBlock = depthStencilFormatAspectSize(format, 'stencil-only');
const byteSize =
aspectBytesPerBlock *
textureSizeAtLevel.width *
textureSizeAtLevel.height *
textureSizeAtLevel.depthOrArrayLayers;
return dataGenerator.generateView(byteSize);
}
doCopyTextureToTextureTest(
dimension: GPUTextureDimension,
srcTextureSize: Required<GPUExtent3DDict>,
dstTextureSize: Required<GPUExtent3DDict>,
srcFormat: ColorTextureFormat,
dstFormat: ColorTextureFormat,
copyBoxOffsets: {
srcOffset: { x: number; y: number; z: number };
dstOffset: { x: number; y: number; z: number };
copyExtent: Required<GPUExtent3DDict>;
},
srcCopyLevel: number,
dstCopyLevel: number,
requestedMipLevelCount?: number
): void {
this.skipIfTextureFormatNotSupported(srcFormat, dstFormat);
this.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
this.skipIfTextureFormatAndDimensionNotCompatible(srcFormat, dimension);
this.skipIfTextureFormatAndDimensionNotCompatible(dstFormat, dimension);
const extraTextureUsageFlags =
isCompressedTextureFormat(dstFormat) && this.isCompatibility
? GPUTextureUsage.TEXTURE_BINDING
: 0;
const mipLevelCount = requestedMipLevelCount ?? (dimension === '1d' ? 1 : 4);
const srcTextureDesc: GPUTextureDescriptor = applyTextureBindingViewDimensionForTest({
dimension,
size: srcTextureSize,
format: srcFormat,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
mipLevelCount,
});
const srcTexture = this.createTextureTracked(srcTextureDesc);
const dstTextureDesc: GPUTextureDescriptor = applyTextureBindingViewDimensionForTest({
dimension,
size: dstTextureSize,
format: dstFormat,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | extraTextureUsageFlags,
mipLevelCount,
});
const dstTexture = this.createTextureTracked(dstTextureDesc);
const initialSrcData = this.getInitialDataPerMipLevel(
dimension,
srcTextureSize,
srcFormat,
srcCopyLevel
);
const srcTextureSizeAtLevel = physicalMipSize(
srcTextureSize,
srcFormat,
dimension,
srcCopyLevel
);
const { bytesPerBlock, blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(srcFormat);
const srcBlocksPerRow = srcTextureSizeAtLevel.width / blockWidth;
const srcBlockRowsPerImage = srcTextureSizeAtLevel.height / blockHeight;
this.device.queue.writeTexture(
{ texture: srcTexture, mipLevel: srcCopyLevel },
initialSrcData,
{
bytesPerRow: srcBlocksPerRow * bytesPerBlock,
rowsPerImage: srcBlockRowsPerImage,
},
srcTextureSizeAtLevel
);
const dstTextureSizeAtLevel = physicalMipSize(
dstTextureSize,
dstFormat,
dimension,
dstCopyLevel
);
const minWidth = Math.min(srcTextureSizeAtLevel.width, dstTextureSizeAtLevel.width);
const minHeight = Math.min(srcTextureSizeAtLevel.height, dstTextureSizeAtLevel.height);
const minDepth = Math.min(
srcTextureSizeAtLevel.depthOrArrayLayers,
dstTextureSizeAtLevel.depthOrArrayLayers
);
const appliedSrcOffset = {
x: Math.min(copyBoxOffsets.srcOffset.x * blockWidth, minWidth),
y: Math.min(copyBoxOffsets.srcOffset.y * blockHeight, minHeight),
z: Math.min(copyBoxOffsets.srcOffset.z, minDepth),
};
const appliedDstOffset = {
x: Math.min(copyBoxOffsets.dstOffset.x * blockWidth, minWidth),
y: Math.min(copyBoxOffsets.dstOffset.y * blockHeight, minHeight),
z: Math.min(copyBoxOffsets.dstOffset.z, minDepth),
};
const appliedCopyWidth = Math.max(
minWidth +
copyBoxOffsets.copyExtent.width * blockWidth -
Math.max(appliedSrcOffset.x, appliedDstOffset.x),
0
);
const appliedCopyHeight = Math.max(
minHeight +
copyBoxOffsets.copyExtent.height * blockHeight -
Math.max(appliedSrcOffset.y, appliedDstOffset.y),
0
);
assert(appliedCopyWidth % blockWidth === 0 && appliedCopyHeight % blockHeight === 0);
const appliedCopyDepth = Math.max(
0,
minDepth +
copyBoxOffsets.copyExtent.depthOrArrayLayers -
Math.max(appliedSrcOffset.z, appliedDstOffset.z)
);
assert(appliedCopyDepth >= 0);
const appliedSize = {
width: appliedCopyWidth,
height: appliedCopyHeight,
depthOrArrayLayers: appliedCopyDepth,
};
{
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToTexture(
{ texture: srcTexture, mipLevel: srcCopyLevel, origin: appliedSrcOffset },
{ texture: dstTexture, mipLevel: dstCopyLevel, origin: appliedDstOffset },
appliedSize
);
this.device.queue.submit([encoder.finish()]);
}
const dstBlocksPerRow = dstTextureSizeAtLevel.width / blockWidth;
const dstBlockRowsPerImage = dstTextureSizeAtLevel.height / blockHeight;
const bytesPerDstAlignedBlockRow = align(dstBlocksPerRow * bytesPerBlock, 256);
const dstBufferSize =
(dstBlockRowsPerImage * dstTextureSizeAtLevel.depthOrArrayLayers - 1) *
bytesPerDstAlignedBlockRow +
align(dstBlocksPerRow * bytesPerBlock, 4);
if (isCompressedTextureFormat(dstTexture.format) && this.isCompatibility) {
assert(textureFormatsAreViewCompatible(this.device.features, srcFormat, dstFormat));
const expectedTexture = this.createTextureTracked({
size: [dstTexture.width, dstTexture.height, dstTexture.depthOrArrayLayers],
mipLevelCount: dstTexture.mipLevelCount,
format: dstTexture.format,
usage: dstTexture.usage,
});
const expectedData = new Uint8Array(dstBufferSize);
ttu.updateLinearTextureDataSubBox(this, dstFormat, appliedSize, {
src: {
dataLayout: {
bytesPerRow: srcBlocksPerRow * bytesPerBlock,
rowsPerImage: srcBlockRowsPerImage,
offset: 0,
},
origin: appliedSrcOffset,
data: initialSrcData,
},
dest: {
dataLayout: {
bytesPerRow: dstBlocksPerRow * bytesPerBlock,
rowsPerImage: dstBlockRowsPerImage,
offset: 0,
},
origin: appliedDstOffset,
data: expectedData,
},
});
this.queue.writeTexture(
{ texture: expectedTexture, mipLevel: dstCopyLevel },
expectedData,
{
bytesPerRow: dstBlocksPerRow * bytesPerBlock,
rowsPerImage: dstBlockRowsPerImage,
},
dstTextureSizeAtLevel
);
ttu.expectTexturesToMatchByRendering(
this,
dstTexture,
expectedTexture,
dstCopyLevel,
appliedDstOffset,
appliedSize
);
return;
}
const dstBufferDesc: GPUBufferDescriptor = {
size: dstBufferSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
};
const dstBuffer = this.createBufferTracked(dstBufferDesc);
{
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToBuffer(
{ texture: dstTexture, mipLevel: dstCopyLevel },
{
buffer: dstBuffer,
bytesPerRow: bytesPerDstAlignedBlockRow,
rowsPerImage: dstBlockRowsPerImage,
},
dstTextureSizeAtLevel
);
this.device.queue.submit([encoder.finish()]);
}
const expectedUint8DataWithPadding = new Uint8Array(dstBufferSize);
const expectedUint8Data = new Uint8Array(initialSrcData);
const appliedCopyBlocksPerRow = appliedCopyWidth / blockWidth;
const appliedCopyBlockRowsPerImage = appliedCopyHeight / blockHeight;
const srcCopyOffsetInBlocks = {
x: appliedSrcOffset.x / blockWidth,
y: appliedSrcOffset.y / blockHeight,
z: appliedSrcOffset.z,
};
const dstCopyOffsetInBlocks = {
x: appliedDstOffset.x / blockWidth,
y: appliedDstOffset.y / blockHeight,
z: appliedDstOffset.z,
};
const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
for (let z = 0; z < appliedCopyDepth; ++z) {
const srcOffsetZ = srcCopyOffsetInBlocks.z + z;
const dstOffsetZ = dstCopyOffsetInBlocks.z + z;
for (let y = 0; y < appliedCopyBlockRowsPerImage; ++y) {
const dstOffsetYInBlocks = dstCopyOffsetInBlocks.y + y;
const expectedDataWithPaddingOffset =
bytesPerDstAlignedBlockRow * (dstBlockRowsPerImage * dstOffsetZ + dstOffsetYInBlocks) +
dstCopyOffsetInBlocks.x * bytesPerBlock;
const srcOffsetYInBlocks = srcCopyOffsetInBlocks.y + y;
const expectedDataOffset =
bytesPerBlock *
srcBlocksPerRow *
(srcBlockRowsPerImage * srcOffsetZ + srcOffsetYInBlocks) +
srcCopyOffsetInBlocks.x * bytesPerBlock;
memcpy(
{ src: expectedUint8Data, start: expectedDataOffset, length: bytesInRow },
{ dst: expectedUint8DataWithPadding, start: expectedDataWithPaddingOffset }
);
}
}
if (isCompressedTextureFormat(dstFormat)) {
this.expectGPUBufferValuesPassCheck(
dstBuffer,
vals => checkElementsEqual(vals, expectedUint8DataWithPadding),
{
srcByteOffset: 0,
type: Uint8Array,
typedLength: expectedUint8DataWithPadding.length,
}
);
return;
}
assert(isRegularTextureFormat(dstFormat));
const regularDstFormat = dstFormat as RegularTextureFormat;
const checkByTextureFormat = (actual: Uint8Array) => {
const zero = { x: 0, y: 0, z: 0 };
const actTexelView = TexelView.fromTextureDataByReference(regularDstFormat, actual, {
bytesPerRow: bytesInRow,
rowsPerImage: dstBlockRowsPerImage,
subrectOrigin: zero,
subrectSize: dstTextureSizeAtLevel,
});
const expTexelView = TexelView.fromTextureDataByReference(
regularDstFormat,
expectedUint8DataWithPadding,
{
bytesPerRow: bytesInRow,
rowsPerImage: dstBlockRowsPerImage,
subrectOrigin: zero,
subrectSize: dstTextureSizeAtLevel,
}
);
const failedPixelsMessage = findFailedPixels(
regularDstFormat,
zero,
dstTextureSizeAtLevel,
{ actTexelView, expTexelView },
{
maxFractionalDiff: 0,
}
);
if (failedPixelsMessage !== undefined) {
const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
return new ErrorWithExtra(msg, () => ({
expTexelView,
actTexelView,
}));
}
return undefined;
};
this.expectGPUBufferValuesPassCheck(dstBuffer, checkByTextureFormat, {
srcByteOffset: 0,
type: Uint8Array,
typedLength: expectedUint8DataWithPadding.length,
});
}
initializeStencilAspect(
sourceTexture: GPUTexture,
initialStencilData: Uint8Array,
srcCopyLevel: number,
srcCopyBaseArrayLayer: number,
copySize: readonly [number, number, number]
): void {
this.queue.writeTexture(
{
texture: sourceTexture,
mipLevel: srcCopyLevel,
aspect: 'stencil-only',
origin: { x: 0, y: 0, z: srcCopyBaseArrayLayer },
},
initialStencilData,
{ bytesPerRow: copySize[0], rowsPerImage: copySize[1] },
copySize
);
}
verifyStencilAspect(
destinationTexture: GPUTexture,
initialStencilData: Uint8Array,
dstCopyLevel: number,
dstCopyBaseArrayLayer: number,
copySize: readonly [number, number, number]
): void {
const bytesPerRow = align(copySize[0], kBytesPerRowAlignment);
const rowsPerImage = copySize[1];
const outputBufferSize = align(
dataBytesForCopyOrFail({
layout: { bytesPerRow, rowsPerImage },
format: 'stencil8',
copySize,
method: 'CopyT2B',
}),
kBufferSizeAlignment
);
const outputBuffer = this.createBufferTracked({
size: outputBufferSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToBuffer(
{
texture: destinationTexture,
aspect: 'stencil-only',
mipLevel: dstCopyLevel,
origin: { x: 0, y: 0, z: dstCopyBaseArrayLayer },
},
{ buffer: outputBuffer, bytesPerRow, rowsPerImage },
copySize
);
this.queue.submit([encoder.finish()]);
const expectedStencilData = new Uint8Array(outputBufferSize);
for (let z = 0; z < copySize[2]; ++z) {
const initialOffsetPerLayer = z * copySize[0] * copySize[1];
const expectedOffsetPerLayer = z * bytesPerRow * rowsPerImage;
for (let y = 0; y < copySize[1]; ++y) {
const initialOffsetPerRow = initialOffsetPerLayer + y * copySize[0];
const expectedOffsetPerRow = expectedOffsetPerLayer + y * bytesPerRow;
memcpy(
{ src: initialStencilData, start: initialOffsetPerRow, length: copySize[0] },
{ dst: expectedStencilData, start: expectedOffsetPerRow }
);
}
}
this.expectGPUBufferValuesEqual(outputBuffer, expectedStencilData);
}
getRenderPipelineForT2TCopyWithDepthTests(
bindGroupLayout: GPUBindGroupLayout,
hasColorAttachment: boolean,
depthStencil: GPUDepthStencilState
): GPURenderPipeline {
const renderPipelineDescriptor: GPURenderPipelineDescriptor = {
layout: this.device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
vertex: {
module: this.device.createShaderModule({
code: `
struct Params {
copyLayer: f32
};
@group(0) @binding(0) var<uniform> param: Params;
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
var depthValue = 0.5 + 0.2 * sin(param.copyLayer);
var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
vec3<f32>(-1.0, 1.0, depthValue),
vec3<f32>(-1.0, -1.0, 0.0),
vec3<f32>( 1.0, 1.0, 1.0),
vec3<f32>(-1.0, -1.0, 0.0),
vec3<f32>( 1.0, 1.0, 1.0),
vec3<f32>( 1.0, -1.0, depthValue));
return vec4<f32>(pos[VertexIndex], 1.0);
}`,
}),
entryPoint: 'main',
},
depthStencil,
};
if (hasColorAttachment) {
renderPipelineDescriptor.fragment = {
module: this.device.createShaderModule({
code: `
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
targets: [{ format: 'rgba8unorm' }],
};
}
return this.device.createRenderPipeline(renderPipelineDescriptor);
}
getBindGroupLayoutForT2TCopyWithDepthTests(): GPUBindGroupLayout {
return this.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: {
type: 'uniform',
minBindingSize: 4,
hasDynamicOffset: true,
},
},
],
});
}
getBindGroupForT2TCopyWithDepthTests(
bindGroupLayout: GPUBindGroupLayout,
totalCopyArrayLayers: number
): GPUBindGroup {
assert(totalCopyArrayLayers > 0);
const uniformBufferSize = kMinDynamicBufferOffsetAlignment * (totalCopyArrayLayers - 1) + 4;
const uniformBufferData = new Float32Array(uniformBufferSize / 4);
for (let i = 1; i < totalCopyArrayLayers; ++i) {
uniformBufferData[(kMinDynamicBufferOffsetAlignment / 4) * i] = i;
}
const uniformBuffer = this.makeBufferWithContents(
uniformBufferData,
GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
);
return this.device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: uniformBuffer,
size: 4,
},
},
],
});
}
initializeDepthAspect(
sourceTexture: GPUTexture,
depthFormat: GPUTextureFormat,
srcCopyLevel: number,
srcCopyBaseArrayLayer: number,
copySize: readonly [number, number, number]
): void {
const bindGroupLayout = this.getBindGroupLayoutForT2TCopyWithDepthTests();
const renderPipeline = this.getRenderPipelineForT2TCopyWithDepthTests(bindGroupLayout, false, {
format: depthFormat,
depthWriteEnabled: true,
depthCompare: 'always',
});
const bindGroup = this.getBindGroupForT2TCopyWithDepthTests(bindGroupLayout, copySize[2]);
const hasStencil = isStencilTextureFormat(sourceTexture.format);
const encoder = this.device.createCommandEncoder();
for (let srcCopyLayer = 0; srcCopyLayer < copySize[2]; ++srcCopyLayer) {
const renderPass = encoder.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
view: sourceTexture.createView({
baseArrayLayer: srcCopyLayer + srcCopyBaseArrayLayer,
arrayLayerCount: 1,
baseMipLevel: srcCopyLevel,
mipLevelCount: 1,
}),
depthClearValue: 0.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
stencilLoadOp: hasStencil ? 'load' : undefined,
stencilStoreOp: hasStencil ? 'store' : undefined,
},
});
renderPass.setBindGroup(0, bindGroup, [srcCopyLayer * kMinDynamicBufferOffsetAlignment]);
renderPass.setPipeline(renderPipeline);
renderPass.draw(6);
renderPass.end();
}
this.queue.submit([encoder.finish()]);
}
verifyDepthAspect(
destinationTexture: GPUTexture,
depthFormat: GPUTextureFormat,
dstCopyLevel: number,
dstCopyBaseArrayLayer: number,
copySize: [number, number, number]
): void {
const bindGroupLayout = this.getBindGroupLayoutForT2TCopyWithDepthTests();
const renderPipeline = this.getRenderPipelineForT2TCopyWithDepthTests(bindGroupLayout, true, {
format: depthFormat,
depthWriteEnabled: false,
depthCompare: 'equal',
});
const bindGroup = this.getBindGroupForT2TCopyWithDepthTests(bindGroupLayout, copySize[2]);
const outputColorTexture = this.createTextureTracked({
format: 'rgba8unorm',
size: copySize,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});
const hasStencil = isStencilTextureFormat(destinationTexture.format);
const encoder = this.device.createCommandEncoder();
for (let dstCopyLayer = 0; dstCopyLayer < copySize[2]; ++dstCopyLayer) {
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
view: outputColorTexture.createView({
baseArrayLayer: dstCopyLayer,
arrayLayerCount: 1,
}),
clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: destinationTexture.createView({
baseArrayLayer: dstCopyLayer + dstCopyBaseArrayLayer,
arrayLayerCount: 1,
baseMipLevel: dstCopyLevel,
mipLevelCount: 1,
}),
depthLoadOp: 'load',
depthStoreOp: 'store',
stencilLoadOp: hasStencil ? 'load' : undefined,
stencilStoreOp: hasStencil ? 'store' : undefined,
},
});
renderPass.setBindGroup(0, bindGroup, [dstCopyLayer * kMinDynamicBufferOffsetAlignment]);
renderPass.setPipeline(renderPipeline);
renderPass.draw(6);
renderPass.end();
}
this.queue.submit([encoder.finish()]);
this.expectSingleColor(outputColorTexture, 'rgba8unorm', {
size: copySize,
exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
});
}
}
const kCopyBoxOffsetsForWholeDepth = [
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 1, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 1, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 1, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 1, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 1, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: -1, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 1, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: -1, depthOrArrayLayers: 0 },
},
] as const;
const kCopyBoxOffsetsFor2DArrayTextures = [
...kCopyBoxOffsetsForWholeDepth,
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -2 },
},
{
srcOffset: { x: 0, y: 0, z: 1 },
dstOffset: { x: 0, y: 0, z: 1 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -3 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 1 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 },
},
{
srcOffset: { x: 0, y: 0, z: 1 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -3 },
},
{
srcOffset: { x: 0, y: 0, z: 1 },
dstOffset: { x: 0, y: 0, z: 1 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 },
},
] as const;
export const g = makeTestGroup(F);
g.test('color_textures,non_compressed,non_array')
.desc(
`
Validate the correctness of the copy by filling the srcTexture with testable data and any
non-compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying
the content of the whole dstTexture.
Copy {1 texel block, part of, the whole} srcTexture to the dstTexture {with, without} a non-zero
valid srcOffset that
- covers the whole dstTexture subresource
- covers the corners of the dstTexture
- doesn't cover any texels that are on the edge of the dstTexture
- covers the mipmap level > 0
Tests for all pairs of valid source/destination formats, and all texture dimensions.
`
)
.params(u =>
u
.combine('srcFormat', kRegularTextureFormats)
.combine('dstFormat', kRegularTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
dstBaseFormat !== undefined &&
srcBaseFormat === dstBaseFormat)
);
})
.combine('dimension', kTextureDimensions)
.filter(
({ dimension, srcFormat, dstFormat }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, srcFormat) &&
textureFormatAndDimensionPossiblyCompatible(dimension, dstFormat)
)
.beginSubcases()
.expandWithParams(p => {
const params = [
{
srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
dstTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
},
{
srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 },
dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 },
},
{
srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
dstTextureSize: { width: 64, height: 64, depthOrArrayLayers: 1 },
},
{
srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
dstTextureSize: { width: 63, height: 61, depthOrArrayLayers: 1 },
},
];
if (p.dimension === '1d') {
for (const param of params) {
param.srcTextureSize.height = 1;
param.dstTextureSize.height = 1;
}
}
return params;
})
.combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth)
.unless(
p =>
p.dimension === '1d' &&
(p.copyBoxOffsets.copyExtent.height !== 0 ||
p.copyBoxOffsets.srcOffset.y !== 0 ||
p.copyBoxOffsets.dstOffset.y !== 0)
)
.combine('srcCopyLevel', [0, 3])
.combine('dstCopyLevel', [0, 3])
.unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
)
.fn(t => {
const {
dimension,
srcTextureSize,
dstTextureSize,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel,
} = t.params;
t.doCopyTextureToTextureTest(
dimension,
srcTextureSize,
dstTextureSize,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel
);
});
g.test('color_textures,compressed,non_array')
.desc(
`
Validate the correctness of the copy by filling the srcTexture with testable data and any
compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying
the content of the whole dstTexture.
Tests for all pairs of valid source/destination formats, and all texture 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)
);
})
.combine('dimension', kTextureDimensions)
.beginSubcases()
.combine('textureSizeInBlocks', [
{ src: { width: 16, height: 8 }, dst: { width: 16, height: 8 } },
{ src: { width: 15, height: 8 }, dst: { width: 16, height: 8 } },
{ src: { width: 16, height: 8 }, dst: { width: 15, height: 8 } },
{ src: { width: 16, height: 13 }, dst: { width: 16, height: 8 } },
{ src: { width: 16, height: 8 }, dst: { width: 16, height: 13 } },
{ src: { width: 15, height: 13 }, dst: { width: 15, height: 13 } },
])
.combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth)
.combine('srcCopyLevel', [0, 2])
.combine('dstCopyLevel', [0, 2])
)
.fn(t => {
const {
dimension,
textureSizeInBlocks,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel,
} = t.params;
t.skipIfTextureFormatAndDimensionNotCompatible(srcFormat, dimension);
t.skipIfTextureFormatAndDimensionNotCompatible(dstFormat, dimension);
t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
const { blockWidth: srcBlockWidth, blockHeight: srcBlockHeight } =
getBlockInfoForColorTextureFormat(srcFormat);
const { blockWidth: dstBlockWidth, blockHeight: dstBlockHeight } =
getBlockInfoForColorTextureFormat(dstFormat);
t.doCopyTextureToTextureTest(
dimension,
{
width: textureSizeInBlocks.src.width * srcBlockWidth,
height: textureSizeInBlocks.src.height * srcBlockHeight,
depthOrArrayLayers: 1,
},
{
width: textureSizeInBlocks.dst.width * dstBlockWidth,
height: textureSizeInBlocks.dst.height * dstBlockHeight,
depthOrArrayLayers: 1,
},
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel
);
});
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()
.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);
const { blockWidth, blockHeight } = getBlockInfoForColorTextureFormat(srcFormat);
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,
0,
1
);
});
g.test('color_textures,non_compressed,array')
.desc(
`
Validate the correctness of the texture-to-texture copy on 2D array textures by filling the
srcTexture with testable data and any non-compressed color format supported by WebGPU, doing
CopyTextureToTexture() copy, and verifying the content of the whole dstTexture.
`
)
.params(u =>
u
.combine('srcFormat', kRegularTextureFormats)
.combine('dstFormat', kRegularTextureFormats)
.filter(({ srcFormat, dstFormat }) => {
const srcBaseFormat = getBaseFormatForTextureFormat(srcFormat);
const dstBaseFormat = getBaseFormatForTextureFormat(dstFormat);
return (
srcFormat === dstFormat ||
(srcBaseFormat !== undefined &&
dstBaseFormat !== undefined &&
srcBaseFormat === dstBaseFormat)
);
})
.combine('dimension', ['2d', '3d'] as const)
.filter(
({ dimension, srcFormat, dstFormat }) =>
textureFormatAndDimensionPossiblyCompatible(dimension, srcFormat) &&
textureFormatAndDimensionPossiblyCompatible(dimension, dstFormat)
)
.beginSubcases()
.combine('textureSize', [
{
srcTextureSize: { width: 64, height: 32, depthOrArrayLayers: 5 },
dstTextureSize: { width: 64, height: 32, depthOrArrayLayers: 5 },
},
{
srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 },
dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 },
},
{
srcTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 },
dstTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 },
},
{
srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 6 },
dstTextureSize: { width: 32, height: 32, depthOrArrayLayers: 6 },
},
])
.combine('copyBoxOffsets', kCopyBoxOffsetsFor2DArrayTextures)
.combine('srcCopyLevel', [0, 3])
.combine('dstCopyLevel', [0, 3])
)
.fn(t => {
const {
dimension,
textureSize,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel,
} = t.params;
t.doCopyTextureToTextureTest(
dimension,
textureSize.srcTextureSize,
textureSize.dstTextureSize,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel
);
});
g.test('color_textures,compressed,array')
.desc(
`
Validate the correctness of the texture-to-texture copy on 2D array textures by filling the
srcTexture with testable data and any compressed color format supported by WebGPU, doing
CopyTextureToTexture() copy, and verifying the content of the whole dstTexture.
Tests for all pairs of valid source/destination formats, and all texture 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)
);
})
.combine('dimension', ['2d', '3d'] as const)
.beginSubcases()
.combine('textureSizeInBlocks', [
{ src: { width: 2, height: 2 }, dst: { width: 2, height: 2 } },
{ src: { width: 15, height: 13 }, dst: { width: 15, height: 13 } },
])
.combine('copyBoxOffsets', kCopyBoxOffsetsFor2DArrayTextures)
.combine('srcCopyLevel', [0, 2])
.combine('dstCopyLevel', [0, 2])
)
.fn(t => {
const {
dimension,
textureSizeInBlocks,
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel,
} = t.params;
const { blockWidth: srcBlockWidth, blockHeight: srcBlockHeight } =
getBlockInfoForColorTextureFormat(srcFormat);
const { blockWidth: dstBlockWidth, blockHeight: dstBlockHeight } =
getBlockInfoForColorTextureFormat(dstFormat);
t.doCopyTextureToTextureTest(
dimension,
{
width: textureSizeInBlocks.src.width * srcBlockWidth,
height: textureSizeInBlocks.src.height * srcBlockHeight,
depthOrArrayLayers: 5,
},
{
width: textureSizeInBlocks.dst.width * dstBlockWidth,
height: textureSizeInBlocks.dst.height * dstBlockHeight,
depthOrArrayLayers: 5,
},
srcFormat,
dstFormat,
copyBoxOffsets,
srcCopyLevel,
dstCopyLevel
);
});
g.test('zero_sized')
.desc(
`
Validate the correctness of zero-sized copies (should be no-ops).
- For each texture dimension.
- Copies that are zero-sized in only one dimension {x, y, z}, each touching the {lower, upper} end
of that dimension.
`
)
.paramsSubcasesOnly(u =>
u
.combineWithParams([
{ dimension: '1d', textureSize: { width: 32, height: 1, depthOrArrayLayers: 1 } },
{ dimension: '2d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } },
{ dimension: '3d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } },
] as const)
.combine('copyBoxOffset', [
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 64, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 64, y: 0, z: 0 },
copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 32, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 32, z: 0 },
copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: -5 },
},
{
srcOffset: { x: 0, y: 0, z: 5 },
dstOffset: { x: 0, y: 0, z: 0 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
{
srcOffset: { x: 0, y: 0, z: 0 },
dstOffset: { x: 0, y: 0, z: 5 },
copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 },
},
])
.unless(
p =>
p.dimension === '1d' &&
(p.copyBoxOffset.copyExtent.height !== 0 ||
p.copyBoxOffset.srcOffset.y !== 0 ||
p.copyBoxOffset.dstOffset.y !== 0)
)
.combine('srcCopyLevel', [0, 3])
.combine('dstCopyLevel', [0, 3])
.unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
)
.fn(t => {
const { dimension, textureSize, copyBoxOffset, srcCopyLevel, dstCopyLevel } = t.params;
const srcFormat = 'rgba8unorm';
const dstFormat = 'rgba8unorm';
t.doCopyTextureToTextureTest(
dimension,
textureSize,
textureSize,
srcFormat,
dstFormat,
copyBoxOffset,
srcCopyLevel,
dstCopyLevel
);
});
g.test('copy_depth_stencil')
.desc(
`
Validate the correctness of copyTextureToTexture() with depth and stencil aspect.
For all the texture formats with stencil aspect:
- Initialize the stencil aspect of the source texture with writeTexture().
- Copy the stencil aspect from the source texture into the destination texture
- Copy the stencil aspect of the destination texture into another staging buffer and check its
content
- Test the copies from / into zero / non-zero array layer / mipmap levels
- Test copying multiple array layers
For all the texture formats with depth aspect:
- Initialize the depth aspect of the source texture with a draw call
- Copy the depth aspect from the source texture into the destination texture
- Validate the content in the destination texture with the depth comparison function 'equal'
`
)
.params(u =>
u
.combine('format', kDepthStencilFormats)
.beginSubcases()
.combine('srcTextureSize', [
{ width: 32, height: 16, depthOrArrayLayers: 1 },
{ width: 32, height: 16, depthOrArrayLayers: 4 },
{ width: 24, height: 48, depthOrArrayLayers: 5 },
])
.combine('srcCopyLevel', [0, 2])
.combine('dstCopyLevel', [0, 2])
.combine('srcCopyBaseArrayLayer', [0, 1])
.combine('dstCopyBaseArrayLayer', [0, 1])
.filter(t => {
return (
t.srcTextureSize.depthOrArrayLayers > t.srcCopyBaseArrayLayer &&
t.srcTextureSize.depthOrArrayLayers > t.dstCopyBaseArrayLayer
);
})
)
.fn(t => {
const {
format,
srcTextureSize,
srcCopyLevel,
dstCopyLevel,
srcCopyBaseArrayLayer,
dstCopyBaseArrayLayer,
} = t.params;
t.skipIfTextureFormatNotSupported(format);
const copySize: [number, number, number] = [
srcTextureSize.width >> srcCopyLevel,
srcTextureSize.height >> srcCopyLevel,
srcTextureSize.depthOrArrayLayers - Math.max(srcCopyBaseArrayLayer, dstCopyBaseArrayLayer),
];
const sourceTexture = t.createTextureTracked({
format,
size: srcTextureSize,
usage:
GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
mipLevelCount: srcCopyLevel + 1,
});
const destinationTexture = t.createTextureTracked({
format,
size: [
copySize[0] << dstCopyLevel,
copySize[1] << dstCopyLevel,
srcTextureSize.depthOrArrayLayers,
] as const,
usage:
GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
mipLevelCount: dstCopyLevel + 1,
});
let initialStencilData: undefined | Uint8Array = undefined;
if (isStencilTextureFormat(format)) {
initialStencilData = t.getInitialStencilDataPerMipLevel(srcTextureSize, format, srcCopyLevel);
t.initializeStencilAspect(
sourceTexture,
initialStencilData,
srcCopyLevel,
srcCopyBaseArrayLayer,
copySize
);
}
if (isDepthTextureFormat(format)) {
t.initializeDepthAspect(sourceTexture, format, srcCopyLevel, srcCopyBaseArrayLayer, copySize);
}
const encoder = t.device.createCommandEncoder();
encoder.copyTextureToTexture(
{
texture: sourceTexture,
mipLevel: srcCopyLevel,
origin: { x: 0, y: 0, z: srcCopyBaseArrayLayer },
},
{
texture: destinationTexture,
mipLevel: dstCopyLevel,
origin: { x: 0, y: 0, z: dstCopyBaseArrayLayer },
},
copySize
);
t.queue.submit([encoder.finish()]);
if (isStencilTextureFormat(format)) {
assert(initialStencilData !== undefined);
t.verifyStencilAspect(
destinationTexture,
initialStencilData,
dstCopyLevel,
dstCopyBaseArrayLayer,
copySize
);
}
if (isDepthTextureFormat(format)) {
t.verifyDepthAspect(
destinationTexture,
format,
dstCopyLevel,
dstCopyBaseArrayLayer,
copySize
);
}
});
g.test('copy_multisampled_color')
.desc(
`
Validate the correctness of copyTextureToTexture() with multisampled color formats.
- Initialize the source texture with a triangle in a render pass.
- Copy from the source texture into the destination texture with CopyTextureToTexture().
- Compare every sub-pixel of source texture and destination texture in another render pass:
- If they are different, then output RED; otherwise output GREEN
- Verify the pixels in the output texture are all GREEN.
- Note that in current WebGPU SPEC the mipmap level count and array layer count of a multisampled
texture can only be 1.
`
)
.fn(t => {
t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
const textureSize = [32, 16, 1] as const;
const kColorFormat = 'rgba8unorm';
const kSampleCount = 4;
const sourceTexture = t.createTextureTracked({
format: kColorFormat,
size: textureSize,
usage:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const destinationTexture = t.createTextureTracked({
format: kColorFormat,
size: textureSize,
usage:
GPUTextureUsage.COPY_DST |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const renderPipelineForInit = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
vec2<f32>(-1.0, 1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, -1.0)
);
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}`,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: `
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.3, 0.5, 0.8, 1.0);
}`,
}),
entryPoint: 'main',
targets: [{ format: kColorFormat }],
},
multisample: {
count: kSampleCount,
},
});
const initEncoder = t.device.createCommandEncoder();
const renderPassForInit = initEncoder.beginRenderPass({
colorAttachments: [
{
view: sourceTexture.createView(),
clearValue: [1.0, 0.0, 0.0, 1.0],
loadOp: 'clear',
storeOp: 'store',
},
],
});
renderPassForInit.setPipeline(renderPipelineForInit);
renderPassForInit.draw(3);
renderPassForInit.end();
t.queue.submit([initEncoder.finish()]);
const copyEncoder = t.device.createCommandEncoder();
copyEncoder.copyTextureToTexture(
{
texture: sourceTexture,
},
{
texture: destinationTexture,
},
textureSize
);
t.queue.submit([copyEncoder.finish()]);
const renderPipelineForValidation = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 6>(
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>( 1.0, -1.0));
return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
}`,
}),
entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
code: `
@group(0) @binding(0) var sourceTexture : texture_multisampled_2d<f32>;
@group(0) @binding(1) var destinationTexture : texture_multisampled_2d<f32>;
@fragment
fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
var coord_in_vec2 = vec2<i32>(i32(coord_in.x), i32(coord_in.y));
for (var sampleIndex = 0; sampleIndex < ${kSampleCount};
sampleIndex = sampleIndex + 1) {
var sourceSubPixel : vec4<f32> =
textureLoad(sourceTexture, coord_in_vec2, sampleIndex);
var destinationSubPixel : vec4<f32> =
textureLoad(destinationTexture, coord_in_vec2, sampleIndex);
if (!all(sourceSubPixel == destinationSubPixel)) {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
}
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
targets: [{ format: kColorFormat }],
},
});
const bindGroup = t.device.createBindGroup({
layout: renderPipelineForValidation.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: sourceTexture.createView(),
},
{
binding: 1,
resource: destinationTexture.createView(),
},
],
});
const expectedOutputTexture = t.createTextureTracked({
format: kColorFormat,
size: textureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const validationEncoder = t.device.createCommandEncoder();
const renderPassForValidation = validationEncoder.beginRenderPass({
colorAttachments: [
{
view: expectedOutputTexture.createView(),
clearValue: [1.0, 0.0, 0.0, 1.0],
loadOp: 'clear',
storeOp: 'store',
},
],
});
renderPassForValidation.setPipeline(renderPipelineForValidation);
renderPassForValidation.setBindGroup(0, bindGroup);
renderPassForValidation.draw(6);
renderPassForValidation.end();
t.queue.submit([validationEncoder.finish()]);
t.expectSingleColor(expectedOutputTexture, 'rgba8unorm', {
size: [textureSize[0], textureSize[1], textureSize[2]],
exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
});
});
g.test('copy_multisampled_depth')
.desc(
`
Validate the correctness of copyTextureToTexture() with multisampled depth formats.
- Initialize the source texture with a triangle in a render pass.
- Copy from the source texture into the destination texture with CopyTextureToTexture().
- Validate the content in the destination texture with the depth comparison function 'equal'.
- Note that in current WebGPU SPEC the mipmap level count and array layer count of a multisampled
texture can only be 1.
`
)
.params(u =>
u.combine('format', kDepthStencilFormats).filter(t => isDepthTextureFormat(t.format))
)
.fn(t => {
const { format } = t.params;
t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
t.skipIfTextureFormatNotSupported(format);
const textureSize = [32, 16, 1] as const;
const kSampleCount = 4;
const sourceTexture = t.createTextureTracked({
format,
size: textureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const destinationTexture = t.createTextureTracked({
format,
size: textureSize,
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const vertexState: GPUVertexState = {
module: t.device.createShaderModule({
code: `
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
vec3<f32>(-1.0, 1.0, 0.5),
vec3<f32>(-1.0, -1.0, 0.0),
vec3<f32>( 1.0, 1.0, 1.0),
vec3<f32>(-1.0, -1.0, 0.0),
vec3<f32>( 1.0, 1.0, 1.0),
vec3<f32>( 1.0, -1.0, 0.5));
return vec4<f32>(pos[VertexIndex], 1.0);
}`,
}),
entryPoint: 'main',
};
const renderPipelineForInit = t.device.createRenderPipeline({
layout: 'auto',
vertex: vertexState,
depthStencil: {
format,
depthCompare: 'always',
depthWriteEnabled: true,
},
multisample: {
count: kSampleCount,
},
});
const encoderForInit = t.device.createCommandEncoder();
const renderPassForInit = encoderForInit.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
view: sourceTexture.createView(),
depthClearValue: 0.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
...(isStencilTextureFormat(format) && {
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}),
},
});
renderPassForInit.setPipeline(renderPipelineForInit);
renderPassForInit.draw(6);
renderPassForInit.end();
t.queue.submit([encoderForInit.finish()]);
const copyEncoder = t.device.createCommandEncoder();
copyEncoder.copyTextureToTexture(
{
texture: sourceTexture,
},
{
texture: destinationTexture,
},
textureSize
);
t.queue.submit([copyEncoder.finish()]);
const kColorFormat = 'rgba8unorm';
const renderPipelineForVerify = t.device.createRenderPipeline({
layout: 'auto',
vertex: vertexState,
fragment: {
module: t.device.createShaderModule({
code: `
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
}`,
}),
entryPoint: 'main',
targets: [{ format: kColorFormat }],
},
depthStencil: {
format,
depthCompare: 'equal',
depthWriteEnabled: false,
},
multisample: {
count: kSampleCount,
},
});
const multisampledColorTexture = t.createTextureTracked({
format: kColorFormat,
size: textureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: kSampleCount,
});
const colorTextureAsResolveTarget = t.createTextureTracked({
format: kColorFormat,
size: textureSize,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const encoderForVerify = t.device.createCommandEncoder();
const renderPassForVerify = encoderForVerify.beginRenderPass({
colorAttachments: [
{
view: multisampledColorTexture.createView(),
clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'discard',
resolveTarget: colorTextureAsResolveTarget.createView(),
},
],
depthStencilAttachment: {
view: destinationTexture.createView(),
depthLoadOp: 'load',
depthStoreOp: 'store',
...(isStencilTextureFormat(format) && {
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}),
},
});
renderPassForVerify.setPipeline(renderPipelineForVerify);
renderPassForVerify.draw(6);
renderPassForVerify.end();
t.queue.submit([encoderForVerify.finish()]);
t.expectSingleColor(colorTextureAsResolveTarget, kColorFormat, {
size: [textureSize[0], textureSize[1], textureSize[2]],
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();