| export const description = ` |
| copyTextureToTexture tests. |
| `; |
| |
| import { makeTestGroup } from '../../../../../common/framework/test_group.js'; |
| import { kTextureUsages, kTextureDimensions } from '../../../../capability_info.js'; |
| import { |
| kAllTextureFormats, |
| kCompressedTextureFormats, |
| kDepthStencilFormats, |
| textureFormatAndDimensionPossiblyCompatible, |
| getBlockInfoForTextureFormat, |
| getBaseFormatForTextureFormat, |
| canCopyFromAllAspectsOfTextureFormat, |
| canCopyToAllAspectsOfTextureFormat, |
| ColorTextureFormat, |
| } from '../../../../format_info.js'; |
| import { kResourceStates, AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js'; |
| import { align, lcm } from '../../../../util/math.js'; |
| import * as vtu from '../../validation_test_utils.js'; |
| |
| class F extends AllFeaturesMaxLimitsGPUTest { |
| testCopyTextureToTexture( |
| source: GPUTexelCopyTextureInfo, |
| destination: GPUTexelCopyTextureInfo, |
| copySize: GPUExtent3D, |
| expectation: 'Success' | 'FinishError' | 'SubmitError' |
| ): void { |
| const commandEncoder = this.device.createCommandEncoder(); |
| commandEncoder.copyTextureToTexture(source, destination, copySize); |
| |
| if (expectation === 'FinishError') { |
| this.expectValidationError(() => { |
| commandEncoder.finish(); |
| }); |
| } else { |
| const cmd = commandEncoder.finish(); |
| this.expectValidationError(() => { |
| this.device.queue.submit([cmd]); |
| }, expectation === 'SubmitError'); |
| } |
| } |
| |
| getPhysicalSubresourceSize( |
| dimension: GPUTextureDimension, |
| textureSize: Required<GPUExtent3DDict>, |
| format: GPUTextureFormat, |
| mipLevel: number |
| ): Required<GPUExtent3DDict> { |
| const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format); |
| const virtualWidthAtLevel = Math.max(textureSize.width >> mipLevel, 1); |
| const virtualHeightAtLevel = Math.max(textureSize.height >> mipLevel, 1); |
| const physicalWidthAtLevel = align(virtualWidthAtLevel, blockWidth); |
| const physicalHeightAtLevel = align(virtualHeightAtLevel, blockHeight); |
| |
| switch (dimension) { |
| case '1d': |
| return { width: physicalWidthAtLevel, height: 1, depthOrArrayLayers: 1 }; |
| case '2d': |
| return { |
| width: physicalWidthAtLevel, |
| height: physicalHeightAtLevel, |
| depthOrArrayLayers: textureSize.depthOrArrayLayers, |
| }; |
| case '3d': |
| return { |
| width: physicalWidthAtLevel, |
| height: physicalHeightAtLevel, |
| depthOrArrayLayers: Math.max(textureSize.depthOrArrayLayers >> mipLevel, 1), |
| }; |
| } |
| } |
| } |
| |
| export const g = makeTestGroup(F); |
| |
| g.test('copy_with_invalid_or_destroyed_texture') |
| .desc('Test copyTextureToTexture is an error when one of the textures is invalid or destroyed.') |
| .paramsSubcasesOnly(u => |
| u // |
| .combine('srcState', kResourceStates) |
| .combine('dstState', kResourceStates) |
| ) |
| .fn(t => { |
| const { srcState, dstState } = t.params; |
| |
| const textureDesc: GPUTextureDescriptor = { |
| size: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, |
| }; |
| |
| const srcTexture = vtu.createTextureWithState(t, srcState, textureDesc); |
| const dstTexture = vtu.createTextureWithState(t, dstState, textureDesc); |
| |
| const isSubmitSuccess = srcState === 'valid' && dstState === 'valid'; |
| const isFinishSuccess = srcState !== 'invalid' && dstState !== 'invalid'; |
| const expectation = isFinishSuccess |
| ? isSubmitSuccess |
| ? 'Success' |
| : 'SubmitError' |
| : 'FinishError'; |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture }, |
| { texture: dstTexture }, |
| { width: 1, height: 1, depthOrArrayLayers: 1 }, |
| expectation |
| ); |
| }); |
| |
| g.test('texture,device_mismatch') |
| .desc( |
| 'Tests copyTextureToTexture cannot be called with src texture or dst texture created from another device.' |
| ) |
| .paramsSubcasesOnly([ |
| { srcMismatched: false, dstMismatched: false }, // control case |
| { srcMismatched: true, dstMismatched: false }, |
| { srcMismatched: false, dstMismatched: true }, |
| ] as const) |
| .beforeAllSubcases(t => t.usesMismatchedDevice()) |
| .fn(t => { |
| const { srcMismatched, dstMismatched } = t.params; |
| |
| const size = { width: 4, height: 4, depthOrArrayLayers: 1 }; |
| const format = 'rgba8unorm'; |
| |
| const srcTextureDevice = srcMismatched ? t.mismatchedDevice : t.device; |
| const srcTexture = t.trackForCleanup( |
| srcTextureDevice.createTexture({ |
| size, |
| format, |
| usage: GPUTextureUsage.COPY_SRC, |
| }) |
| ); |
| |
| const dstTextureDevice = dstMismatched ? t.mismatchedDevice : t.device; |
| const dstTexture = t.trackForCleanup( |
| dstTextureDevice.createTexture({ |
| size, |
| format, |
| usage: GPUTextureUsage.COPY_DST, |
| }) |
| ); |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture }, |
| { texture: dstTexture }, |
| { width: 1, height: 1, depthOrArrayLayers: 1 }, |
| srcMismatched || dstMismatched ? 'FinishError' : 'Success' |
| ); |
| }); |
| |
| g.test('mipmap_level') |
| .desc( |
| ` |
| Test copyTextureToTexture must specify mipLevels that are in range. |
| - for various dimensions |
| - for various mip level count in the texture |
| - for various copy target mip level (in range and not in range) |
| ` |
| ) |
| .params(u => |
| u // |
| .combine('dimension', kTextureDimensions) |
| .beginSubcases() |
| .combineWithParams([ |
| { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 0 }, |
| { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 1, dstCopyLevel: 0 }, |
| { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 1 }, |
| { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 0 }, |
| { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 2, dstCopyLevel: 0 }, |
| { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 3, dstCopyLevel: 0 }, |
| { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 2 }, |
| { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 3 }, |
| ] as const) |
| .unless(p => p.dimension === '1d' && (p.srcLevelCount !== 1 || p.dstLevelCount !== 1)) |
| ) |
| |
| .fn(t => { |
| const { srcLevelCount, dstLevelCount, srcCopyLevel, dstCopyLevel, dimension } = t.params; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: { width: 32, height: 1, depthOrArrayLayers: 1 }, |
| dimension, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_SRC, |
| mipLevelCount: srcLevelCount, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: { width: 32, height: 1, depthOrArrayLayers: 1 }, |
| dimension, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_DST, |
| mipLevelCount: dstLevelCount, |
| }); |
| |
| const isSuccess = srcCopyLevel < srcLevelCount && dstCopyLevel < dstLevelCount; |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, mipLevel: dstCopyLevel }, |
| { width: 1, height: 1, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('texture_usage') |
| .desc( |
| ` |
| Test that copyTextureToTexture source/destination need COPY_SRC/COPY_DST usages. |
| - for all possible source texture usages |
| - for all possible destination texture usages |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u // |
| .combine('srcUsage', kTextureUsages) |
| .combine('dstUsage', kTextureUsages) |
| ) |
| .fn(t => { |
| const { srcUsage, dstUsage } = t.params; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: srcUsage, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: dstUsage, |
| }); |
| |
| const isSuccess = |
| srcUsage === GPUTextureUsage.COPY_SRC && dstUsage === GPUTextureUsage.COPY_DST; |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture }, |
| { texture: dstTexture }, |
| { width: 1, height: 1, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('sample_count') |
| .desc( |
| ` |
| Test that textures in copyTextureToTexture must have the same sample count. |
| - for various source texture sample count |
| - for various destination texture sample count |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u // |
| .combine('srcSampleCount', [1, 4]) |
| .combine('dstSampleCount', [1, 4]) |
| ) |
| .beforeAllSubcases(t => { |
| t.skipIf( |
| t.isCompatibility && (t.params.srcSampleCount !== 1 || t.params.dstSampleCount !== 1), |
| 'multisample textures are not copyable in compatibility mode' |
| ); |
| }) |
| .fn(t => { |
| const { srcSampleCount, dstSampleCount } = t.params; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, |
| sampleCount: srcSampleCount, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, |
| sampleCount: dstSampleCount, |
| }); |
| |
| const isSuccess = srcSampleCount === dstSampleCount; |
| t.testCopyTextureToTexture( |
| { texture: srcTexture }, |
| { texture: dstTexture }, |
| { width: 4, height: 4, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('multisampled_copy_restrictions') |
| .desc( |
| ` |
| Test that copyTextureToTexture of multisampled texture must copy a whole subresource to a whole subresource. |
| - for various origin for the source and destination of the copies. |
| |
| Note: this is only tested for 2D textures as it is the only dimension compatible with multisampling. |
| TODO: Check the source and destination constraints separately. |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u // |
| .combine('srcCopyOrigin', [ |
| { x: 0, y: 0, z: 0 }, |
| { x: 1, y: 0, z: 0 }, |
| { x: 0, y: 1, z: 0 }, |
| { x: 1, y: 1, z: 0 }, |
| ]) |
| .combine('dstCopyOrigin', [ |
| { x: 0, y: 0, z: 0 }, |
| { x: 1, y: 0, z: 0 }, |
| { x: 0, y: 1, z: 0 }, |
| { x: 1, y: 1, z: 0 }, |
| ]) |
| .expand('copyWidth', p => [32 - Math.max(p.srcCopyOrigin.x, p.dstCopyOrigin.x), 16]) |
| .expand('copyHeight', p => [16 - Math.max(p.srcCopyOrigin.y, p.dstCopyOrigin.y), 8]) |
| ) |
| .beforeAllSubcases(t => { |
| t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode'); |
| }) |
| .fn(t => { |
| const { srcCopyOrigin, dstCopyOrigin, copyWidth, copyHeight } = t.params; |
| |
| const kWidth = 32; |
| const kHeight = 16; |
| |
| // Currently we don't support multisampled 2D array textures and the mipmap level count of the |
| // multisampled textures must be 1. |
| const srcTexture = t.createTextureTracked({ |
| size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, |
| sampleCount: 4, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, |
| sampleCount: 4, |
| }); |
| |
| const isSuccess = copyWidth === kWidth && copyHeight === kHeight; |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: srcCopyOrigin }, |
| { texture: dstTexture, origin: dstCopyOrigin }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('texture_format_compatibility') |
| .desc( |
| ` |
| Test the formats of textures in copyTextureToTexture must be copy-compatible. |
| - for all source texture formats |
| - for all destination texture formats |
| ` |
| ) |
| .params(u => |
| u |
| .combine('srcFormat', kAllTextureFormats) |
| .filter(t => canCopyFromAllAspectsOfTextureFormat(t.srcFormat)) |
| .combine('dstFormat', kAllTextureFormats) |
| .filter(t => canCopyToAllAspectsOfTextureFormat(t.dstFormat)) |
| .filter(t => { |
| const srcInfo = getBlockInfoForTextureFormat(t.srcFormat); |
| const dstInfo = getBlockInfoForTextureFormat(t.dstFormat); |
| return ( |
| srcInfo.blockWidth === dstInfo.blockWidth && srcInfo.blockHeight === dstInfo.blockHeight |
| ); |
| }) |
| ) |
| .fn(t => { |
| const { srcFormat, dstFormat } = t.params; |
| |
| t.skipIfTextureFormatNotSupported(srcFormat, dstFormat); |
| t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat); |
| |
| const srcFormatInfo = getBlockInfoForTextureFormat(srcFormat); |
| const dstFormatInfo = getBlockInfoForTextureFormat(dstFormat); |
| |
| const textureSize = { |
| width: lcm(srcFormatInfo.blockWidth, dstFormatInfo.blockWidth), |
| height: lcm(srcFormatInfo.blockHeight, dstFormatInfo.blockHeight), |
| depthOrArrayLayers: 1, |
| }; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: textureSize, |
| format: srcFormat, |
| usage: GPUTextureUsage.COPY_SRC, |
| }); |
| |
| const dstTexture = t.createTextureTracked({ |
| size: textureSize, |
| format: dstFormat, |
| usage: GPUTextureUsage.COPY_DST, |
| }); |
| |
| // Allow copy between compatible format textures. |
| const srcBaseFormat = |
| getBaseFormatForTextureFormat(srcFormat as ColorTextureFormat) ?? srcFormat; |
| const dstBaseFormat = |
| getBaseFormatForTextureFormat(dstFormat as ColorTextureFormat) ?? dstFormat; |
| const isSuccess = srcBaseFormat === dstBaseFormat; |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture }, |
| { texture: dstTexture }, |
| textureSize, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('depth_stencil_copy_restrictions') |
| .desc( |
| ` |
| Test that depth textures subresources must be entirely copied in copyTextureToTexture |
| - for various depth-stencil formats |
| - for various copy origin and size offsets |
| - for various source and destination texture sizes |
| - for various source and destination mip levels |
| |
| Note: this is only tested for 2D textures as it is the only dimension compatible with depth-stencil. |
| ` |
| ) |
| .params(u => |
| u |
| .combine('format', kDepthStencilFormats) |
| .beginSubcases() |
| .combine('copyBoxOffsets', [ |
| { x: 0, y: 0, width: 0, height: 0 }, |
| { x: 1, y: 0, width: 0, height: 0 }, |
| { x: 0, y: 1, width: 0, height: 0 }, |
| { x: 0, y: 0, width: -1, height: 0 }, |
| { x: 0, y: 0, width: 0, height: -1 }, |
| ]) |
| .combine('srcTextureSize', [ |
| { width: 64, height: 64, depthOrArrayLayers: 1 }, |
| { width: 64, height: 32, depthOrArrayLayers: 1 }, |
| { width: 32, height: 32, depthOrArrayLayers: 1 }, |
| ]) |
| .combine('dstTextureSize', [ |
| { width: 64, height: 64, depthOrArrayLayers: 1 }, |
| { width: 64, height: 32, depthOrArrayLayers: 1 }, |
| { width: 32, height: 32, depthOrArrayLayers: 1 }, |
| ]) |
| .combine('srcCopyLevel', [1, 2]) |
| .combine('dstCopyLevel', [0, 1]) |
| ) |
| .fn(t => { |
| const { format, copyBoxOffsets, srcTextureSize, dstTextureSize, srcCopyLevel, dstCopyLevel } = |
| t.params; |
| t.skipIfTextureFormatNotSupported(format); |
| const kMipLevelCount = 3; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: { width: srcTextureSize.width, height: srcTextureSize.height, depthOrArrayLayers: 1 }, |
| format, |
| mipLevelCount: kMipLevelCount, |
| usage: GPUTextureUsage.COPY_SRC, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: { width: dstTextureSize.width, height: dstTextureSize.height, depthOrArrayLayers: 1 }, |
| format, |
| mipLevelCount: kMipLevelCount, |
| usage: GPUTextureUsage.COPY_DST, |
| }); |
| |
| const srcSizeAtLevel = t.getPhysicalSubresourceSize('2d', srcTextureSize, format, srcCopyLevel); |
| const dstSizeAtLevel = t.getPhysicalSubresourceSize('2d', dstTextureSize, format, dstCopyLevel); |
| |
| const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: 0 }; |
| |
| const copyWidth = |
| Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x; |
| const copyHeight = |
| Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y; |
| |
| // Depth/stencil copies must copy whole subresources. |
| const isSuccess = |
| copyOrigin.x === 0 && |
| copyOrigin.y === 0 && |
| copyWidth === srcSizeAtLevel.width && |
| copyHeight === srcSizeAtLevel.height && |
| copyWidth === dstSizeAtLevel.width && |
| copyHeight === dstSizeAtLevel.height; |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('copy_ranges') |
| .desc( |
| ` |
| Test that copyTextureToTexture copy boxes must be in range of the subresource. |
| - for various dimensions |
| - for various offsets to a full copy for the copy origin/size |
| - for various copy mip levels |
| ` |
| ) |
| .params(u => |
| u |
| .combine('dimension', kTextureDimensions) |
| //.beginSubcases() |
| .combine('copyBoxOffsets', [ |
| { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 1, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 1, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 1, width: 0, height: 1, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 2, width: 0, height: 1, depthOrArrayLayers: 0 }, |
| { x: 0, y: 0, z: 0, width: 1, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: 0, height: 1, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 1 }, |
| { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 }, |
| { x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 }, |
| { x: 0, y: 0, z: 2, width: 0, height: 0, depthOrArrayLayers: -1 }, |
| ]) |
| .unless( |
| p => |
| p.dimension === '1d' && |
| (p.copyBoxOffsets.y !== 0 || |
| p.copyBoxOffsets.z !== 0 || |
| p.copyBoxOffsets.height !== 0 || |
| p.copyBoxOffsets.depthOrArrayLayers !== 0) |
| ) |
| .combine('srcCopyLevel', [0, 1, 3]) |
| .combine('dstCopyLevel', [0, 1, 3]) |
| .unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0)) |
| ) |
| .fn(t => { |
| const { dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; |
| |
| const textureSize = { width: 16, height: 8, depthOrArrayLayers: 3 }; |
| let mipLevelCount = 4; |
| if (dimension === '1d') { |
| mipLevelCount = 1; |
| textureSize.height = 1; |
| textureSize.depthOrArrayLayers = 1; |
| } |
| const kFormat = 'rgba8unorm'; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: textureSize, |
| format: kFormat, |
| dimension, |
| mipLevelCount, |
| usage: GPUTextureUsage.COPY_SRC, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: textureSize, |
| format: kFormat, |
| dimension, |
| mipLevelCount, |
| usage: GPUTextureUsage.COPY_DST, |
| }); |
| |
| const srcSizeAtLevel = t.getPhysicalSubresourceSize( |
| dimension, |
| textureSize, |
| kFormat, |
| srcCopyLevel |
| ); |
| const dstSizeAtLevel = t.getPhysicalSubresourceSize( |
| dimension, |
| textureSize, |
| kFormat, |
| dstCopyLevel |
| ); |
| |
| const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z }; |
| |
| const copyWidth = Math.max( |
| Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x, |
| 0 |
| ); |
| const copyHeight = Math.max( |
| Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y, |
| 0 |
| ); |
| const copyDepth = |
| textureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z; |
| |
| { |
| let isSuccess = |
| copyWidth <= srcSizeAtLevel.width && |
| copyHeight <= srcSizeAtLevel.height && |
| copyOrigin.x + copyWidth <= dstSizeAtLevel.width && |
| copyOrigin.y + copyHeight <= dstSizeAtLevel.height; |
| |
| if (dimension === '3d') { |
| isSuccess = |
| isSuccess && |
| copyDepth <= srcSizeAtLevel.depthOrArrayLayers && |
| copyOrigin.z + copyDepth <= dstSizeAtLevel.depthOrArrayLayers; |
| } else { |
| isSuccess = |
| isSuccess && |
| copyDepth <= textureSize.depthOrArrayLayers && |
| copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers; |
| } |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| } |
| |
| { |
| let isSuccess = |
| copyOrigin.x + copyWidth <= srcSizeAtLevel.width && |
| copyOrigin.y + copyHeight <= srcSizeAtLevel.height && |
| copyWidth <= dstSizeAtLevel.width && |
| copyHeight <= dstSizeAtLevel.height; |
| |
| if (dimension === '3d') { |
| isSuccess = |
| isSuccess && |
| copyDepth <= dstSizeAtLevel.depthOrArrayLayers && |
| copyOrigin.z + copyDepth <= srcSizeAtLevel.depthOrArrayLayers; |
| } else { |
| isSuccess = |
| isSuccess && |
| copyDepth <= textureSize.depthOrArrayLayers && |
| copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers; |
| } |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| } |
| }); |
| |
| g.test('copy_within_same_texture') |
| .desc( |
| ` |
| Test that it is an error to use copyTextureToTexture from one subresource to itself. |
| - for various starting source/destination array layers. |
| - for various copy sizes in number of array layers |
| |
| TODO: Extend to check the copy is allowed between different mip levels. |
| TODO: Extend to 1D and 3D textures.` |
| ) |
| .paramsSubcasesOnly(u => |
| u // |
| .combine('srcCopyOriginZ', [0, 2, 4]) |
| .combine('dstCopyOriginZ', [0, 2, 4]) |
| .combine('copyExtentDepth', [1, 2, 3]) |
| ) |
| .fn(t => { |
| const { srcCopyOriginZ, dstCopyOriginZ, copyExtentDepth } = t.params; |
| |
| const kArrayLayerCount = 7; |
| |
| const testTexture = t.createTextureTracked({ |
| size: { width: 16, height: 16, depthOrArrayLayers: kArrayLayerCount }, |
| format: 'rgba8unorm', |
| usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, |
| }); |
| |
| const isSuccess = |
| Math.min(srcCopyOriginZ, dstCopyOriginZ) + copyExtentDepth <= |
| Math.max(srcCopyOriginZ, dstCopyOriginZ); |
| t.testCopyTextureToTexture( |
| { texture: testTexture, origin: { x: 0, y: 0, z: srcCopyOriginZ } }, |
| { texture: testTexture, origin: { x: 0, y: 0, z: dstCopyOriginZ } }, |
| { width: 16, height: 16, depthOrArrayLayers: copyExtentDepth }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('copy_aspects') |
| .desc( |
| ` |
| Test the validations on the member 'aspect' of GPUTexelCopyTextureInfo in CopyTextureToTexture(). |
| - for all the color and depth-stencil formats: the texture copy aspects must be both 'all'. |
| - for all the depth-only formats: the texture copy aspects must be either 'all' or 'depth-only'. |
| - for all the stencil-only formats: the texture copy aspects must be either 'all' or 'stencil-only'. |
| ` |
| ) |
| .params(u => |
| u |
| .combine('format', ['rgba8unorm', ...kDepthStencilFormats] as const) |
| .beginSubcases() |
| .combine('sourceAspect', ['all', 'depth-only', 'stencil-only'] as const) |
| .combine('destinationAspect', ['all', 'depth-only', 'stencil-only'] as const) |
| ) |
| .fn(t => { |
| const { format, sourceAspect, destinationAspect } = t.params; |
| t.skipIfTextureFormatNotSupported(format); |
| |
| const kTextureSize = { width: 16, height: 8, depthOrArrayLayers: 1 }; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: kTextureSize, |
| format, |
| usage: GPUTextureUsage.COPY_SRC, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: kTextureSize, |
| format, |
| usage: GPUTextureUsage.COPY_DST, |
| }); |
| |
| // MAINTENANCE_TODO: get the valid aspects from capability_info.ts. |
| const kValidAspectsForFormat = { |
| rgba8unorm: ['all'], |
| |
| // kUnsizedDepthStencilFormats |
| depth24plus: ['all', 'depth-only'], |
| 'depth24plus-stencil8': ['all'], |
| 'depth32float-stencil8': ['all'], |
| |
| // kSizedDepthStencilFormats |
| depth32float: ['all', 'depth-only'], |
| stencil8: ['all', 'stencil-only'], |
| depth16unorm: ['all', 'depth-only'], |
| }; |
| |
| const isSourceAspectValid = kValidAspectsForFormat[format].includes(sourceAspect); |
| const isDestinationAspectValid = kValidAspectsForFormat[format].includes(destinationAspect); |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, aspect: sourceAspect }, |
| { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, aspect: destinationAspect }, |
| kTextureSize, |
| isSourceAspectValid && isDestinationAspectValid ? 'Success' : 'FinishError' |
| ); |
| }); |
| |
| g.test('copy_ranges_with_compressed_texture_formats') |
| .desc( |
| ` |
| Test that copyTextureToTexture copy boxes must be in range of the subresource and aligned to the block size |
| - for various dimensions |
| - for various offsets to a full copy for the copy origin/size |
| - for various copy mip levels |
| |
| TODO: Express the offsets in "block size" so as to be able to test non-4x4 compressed formats |
| ` |
| ) |
| .params(u => |
| u |
| .combine('format', kCompressedTextureFormats) |
| .combine('dimension', kTextureDimensions) |
| .filter(({ dimension, format }) => |
| textureFormatAndDimensionPossiblyCompatible(dimension, format) |
| ) |
| .beginSubcases() |
| .combine('copyBoxOffsets', [ |
| { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 4, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: -4, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 4, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: 0, height: -4, depthOrArrayLayers: -2 }, |
| { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 }, |
| { x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 }, |
| ]) |
| .combine('srcCopyLevel', [0, 1, 2]) |
| .combine('dstCopyLevel', [0, 1, 2]) |
| ) |
| .fn(t => { |
| const { format, dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params; |
| |
| t.skipIfTextureFormatNotSupported(format); |
| t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension); |
| t.skipIfCopyTextureToTextureNotSupportedForFormat(format); |
| |
| const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format); |
| |
| const kTextureSize = { |
| width: 15 * blockWidth, |
| height: 12 * blockHeight, |
| depthOrArrayLayers: 3, |
| }; |
| const kMipLevelCount = 4; |
| |
| const srcTexture = t.createTextureTracked({ |
| size: kTextureSize, |
| format, |
| dimension, |
| mipLevelCount: kMipLevelCount, |
| usage: GPUTextureUsage.COPY_SRC, |
| }); |
| const dstTexture = t.createTextureTracked({ |
| size: kTextureSize, |
| format, |
| dimension, |
| mipLevelCount: kMipLevelCount, |
| usage: GPUTextureUsage.COPY_DST, |
| }); |
| |
| const srcSizeAtLevel = t.getPhysicalSubresourceSize( |
| dimension, |
| kTextureSize, |
| format, |
| srcCopyLevel |
| ); |
| const dstSizeAtLevel = t.getPhysicalSubresourceSize( |
| dimension, |
| kTextureSize, |
| format, |
| dstCopyLevel |
| ); |
| |
| const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z }; |
| |
| const copyWidth = Math.max( |
| Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x, |
| 0 |
| ); |
| const copyHeight = Math.max( |
| Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y, |
| 0 |
| ); |
| const copyDepth = Math.max( |
| Math.min(srcSizeAtLevel.depthOrArrayLayers, dstSizeAtLevel.depthOrArrayLayers) + |
| copyBoxOffsets.depthOrArrayLayers - |
| copyOrigin.z, |
| 0 |
| ); |
| |
| const isSuccessForCompressedFormats = |
| copyOrigin.x % blockWidth === 0 && |
| copyOrigin.y % blockHeight === 0 && |
| copyWidth % blockWidth === 0 && |
| copyHeight % blockHeight === 0; |
| |
| { |
| const isSuccess = |
| isSuccessForCompressedFormats && |
| copyWidth <= srcSizeAtLevel.width && |
| copyHeight <= srcSizeAtLevel.height && |
| copyOrigin.x + copyWidth <= dstSizeAtLevel.width && |
| copyOrigin.y + copyHeight <= dstSizeAtLevel.height && |
| copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers; |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| } |
| |
| { |
| const isSuccess = |
| isSuccessForCompressedFormats && |
| copyOrigin.x + copyWidth <= srcSizeAtLevel.width && |
| copyOrigin.y + copyHeight <= srcSizeAtLevel.height && |
| copyWidth <= dstSizeAtLevel.width && |
| copyHeight <= dstSizeAtLevel.height && |
| copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers; |
| |
| t.testCopyTextureToTexture( |
| { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel }, |
| { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel }, |
| { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth }, |
| isSuccess ? 'Success' : 'FinishError' |
| ); |
| } |
| }); |