1import { poptions } from '../../../../common/framework/params_builder.js'; 2import { assert } from '../../../../common/framework/util/util.js'; 3import { kSizedTextureFormatInfo, SizedTextureFormat } from '../../../capability_info.js'; 4import { ValidationTest } from '../validation_test.js'; 5 6export const kAllTestMethods = [ 7 'WriteTexture', 8 'CopyBufferToTexture', 9 'CopyTextureToBuffer', 10] as const; 11 12export class CopyBetweenLinearDataAndTextureTest extends ValidationTest { 13 bytesInACompleteRow(copyWidth: number, format: SizedTextureFormat): number { 14 const info = kSizedTextureFormatInfo[format]; 15 assert(copyWidth % info.blockWidth === 0); 16 return (info.bytesPerBlock * copyWidth) / info.blockWidth; 17 } 18 19 requiredBytesInCopy( 20 layout: Required<GPUTextureDataLayout>, 21 format: SizedTextureFormat, 22 copyExtent: GPUExtent3DDict 23 ): number { 24 const info = kSizedTextureFormatInfo[format]; 25 assert(layout.rowsPerImage % info.blockHeight === 0); 26 assert(copyExtent.height % info.blockHeight === 0); 27 assert(copyExtent.width % info.blockWidth === 0); 28 if (copyExtent.width === 0 || copyExtent.height === 0 || copyExtent.depth === 0) { 29 return 0; 30 } else { 31 const texelBlockRowsPerImage = layout.rowsPerImage / info.blockHeight; 32 const bytesPerImage = layout.bytesPerRow * texelBlockRowsPerImage; 33 const bytesInLastSlice = 34 layout.bytesPerRow * (copyExtent.height / info.blockHeight - 1) + 35 (copyExtent.width / info.blockWidth) * info.bytesPerBlock; 36 return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice; 37 } 38 } 39 40 testRun( 41 textureCopyView: GPUTextureCopyView, 42 textureDataLayout: GPUTextureDataLayout, 43 size: GPUExtent3D, 44 { 45 dataSize, 46 method, 47 success, 48 submit = false, // If submit is true, the validaton error is expected to come from the submit and encoding should succeed. 49 }: { dataSize: number; method: string; success: boolean; submit?: boolean } 50 ): void { 51 switch (method) { 52 case 'WriteTexture': { 53 const data = new Uint8Array(dataSize); 54 55 this.expectValidationError(() => { 56 this.device.defaultQueue.writeTexture(textureCopyView, data, textureDataLayout, size); 57 }, !success); 58 59 break; 60 } 61 case 'CopyBufferToTexture': { 62 const buffer = this.device.createBuffer({ 63 size: dataSize, 64 usage: GPUBufferUsage.COPY_SRC, 65 }); 66 67 const encoder = this.device.createCommandEncoder(); 68 encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, textureCopyView, size); 69 70 if (submit) { 71 const cmd = encoder.finish(); 72 this.expectValidationError(() => { 73 this.device.defaultQueue.submit([cmd]); 74 }, !success); 75 } else { 76 this.expectValidationError(() => { 77 encoder.finish(); 78 }, !success); 79 } 80 81 break; 82 } 83 case 'CopyTextureToBuffer': { 84 const buffer = this.device.createBuffer({ 85 size: dataSize, 86 usage: GPUBufferUsage.COPY_DST, 87 }); 88 89 const encoder = this.device.createCommandEncoder(); 90 encoder.copyTextureToBuffer(textureCopyView, { buffer, ...textureDataLayout }, size); 91 92 if (submit) { 93 const cmd = encoder.finish(); 94 this.expectValidationError(() => { 95 this.device.defaultQueue.submit([cmd]); 96 }, !success); 97 } else { 98 this.expectValidationError(() => { 99 encoder.finish(); 100 }, !success); 101 } 102 103 break; 104 } 105 } 106 } 107 108 // This is a helper function used for creating a texture when we don't have to be very 109 // precise about its size as long as it's big enough and properly aligned. 110 createAlignedTexture( 111 format: SizedTextureFormat, 112 copySize: GPUExtent3DDict = { width: 1, height: 1, depth: 1 }, 113 origin: Required<GPUOrigin3DDict> = { x: 0, y: 0, z: 0 } 114 ): GPUTexture { 115 const info = kSizedTextureFormatInfo[format]; 116 return this.device.createTexture({ 117 size: { 118 width: Math.max(1, copySize.width + origin.x) * info.blockWidth, 119 height: Math.max(1, copySize.height + origin.y) * info.blockHeight, 120 depth: Math.max(1, copySize.depth + origin.z), 121 }, 122 format, 123 usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST, 124 }); 125 } 126} 127 128// For testing divisibility by a number we test all the values returned by this function: 129function valuesToTestDivisibilityBy(number: number): Iterable<number> { 130 const values = []; 131 for (let i = 0; i <= 2 * number; ++i) { 132 values.push(i); 133 } 134 values.push(3 * number); 135 return values; 136} 137 138interface WithFormat { 139 format: SizedTextureFormat; 140} 141 142interface WithFormatAndCoordinate extends WithFormat { 143 coordinateToTest: keyof GPUOrigin3DDict | keyof GPUExtent3DDict; 144} 145 146interface WithFormatAndMethod extends WithFormat { 147 method: string; 148} 149 150// This is a helper function used for expanding test parameters for texel block alignment tests on offset 151export function texelBlockAlignmentTestExpanderForOffset({ format }: WithFormat) { 152 return poptions( 153 'offset', 154 valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].bytesPerBlock) 155 ); 156} 157 158// This is a helper function used for expanding test parameters for texel block alignment tests on rowsPerImage 159export function texelBlockAlignmentTestExpanderForRowsPerImage({ format }: WithFormat) { 160 return poptions( 161 'rowsPerImage', 162 valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight) 163 ); 164} 165 166// This is a helper function used for expanding test parameters for texel block alignment tests on origin and size 167export function texelBlockAlignmentTestExpanderForValueToCoordinate({ 168 format, 169 coordinateToTest, 170}: WithFormatAndCoordinate) { 171 switch (coordinateToTest) { 172 case 'x': 173 case 'width': 174 return poptions( 175 'valueToCoordinate', 176 valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockWidth!) 177 ); 178 179 case 'y': 180 case 'height': 181 return poptions( 182 'valueToCoordinate', 183 valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight!) 184 ); 185 186 case 'z': 187 case 'depth': 188 return poptions('valueToCoordinate', valuesToTestDivisibilityBy(1)); 189 } 190} 191 192// This is a helper function used for filtering test parameters 193export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod): boolean { 194 if (method === 'CopyTextureToBuffer') { 195 return kSizedTextureFormatInfo[format].copySrc; 196 } else { 197 return kSizedTextureFormatInfo[format].copyDst; 198 } 199} 200