conversion.spec.ts (25087B)
1 export const description = `Unit tests for conversion`; 2 3 import { mergeParams } from '../common/internal/params_utils.js'; 4 import { makeTestGroup } from '../common/internal/test_group.js'; 5 import { keysOf } from '../common/util/data_tables.js'; 6 import { assert, objectEquals } from '../common/util/util.js'; 7 import { kValue } from '../webgpu/util/constants.js'; 8 import { 9 bool, 10 concreteTypeOf, 11 f16Bits, 12 f32, 13 f32Bits, 14 float16BitsToFloat32, 15 float32ToFloat16Bits, 16 float32ToFloatBits, 17 floatBitsToNormalULPFromZero, 18 floatBitsToNumber, 19 i32, 20 kFloat16Format, 21 kFloat32Format, 22 MatrixValue, 23 numbersApproximatelyEqual, 24 pack2x16float, 25 pack2x16snorm, 26 pack2x16unorm, 27 pack4x8snorm, 28 pack4x8unorm, 29 packRGB9E5UFloat, 30 ScalarValue, 31 toMatrix, 32 u32, 33 unpackRGB9E5UFloat, 34 vec2, 35 vec3, 36 vec4, 37 stringToType, 38 Type, 39 VectorValue, 40 } from '../webgpu/util/conversion.js'; 41 42 import { UnitTest } from './unit_test.js'; 43 44 export const g = makeTestGroup(UnitTest); 45 46 const kFloat16BitsToNumberCases = [ 47 [0b0_01111_0000000000, 1], 48 [0b0_00001_0000000000, 0.00006103515625], 49 [0b0_01101_0101010101, 0.33325195], 50 [0b0_11110_1111111111, 65504], 51 [0b0_00000_0000000000, 0], 52 [0b1_00000_0000000000, -0.0], // -0.0 compares as equal to 0.0 53 [0b0_01110_0000000000, 0.5], 54 [0b0_01100_1001100110, 0.1999512], 55 [0b0_01111_0000000001, 1.00097656], 56 [0b0_10101_1001000000, 100], 57 [0b1_01100_1001100110, -0.1999512], 58 [0b1_10101_1001000000, -100], 59 [0b0_11111_1111111111, Number.NaN], 60 [0b0_11111_0000000000, Number.POSITIVE_INFINITY], 61 [0b1_11111_0000000000, Number.NEGATIVE_INFINITY], 62 ]; 63 64 g.test('float16BitsToFloat32').fn(t => { 65 for (const [bits, number] of [ 66 ...kFloat16BitsToNumberCases, 67 [0b0_00000_1111111111, 0.00006104], // subnormal f16 input 68 [0b1_00000_1111111111, -0.00006104], 69 ]) { 70 const actual = float16BitsToFloat32(bits); 71 t.expect( 72 // some loose check 73 numbersApproximatelyEqual(actual, number, 0.00001), 74 `for ${bits.toString(2)}, expected ${number}, got ${actual}` 75 ); 76 } 77 }); 78 79 g.test('float32ToFloat16Bits').fn(t => { 80 for (const [bits, number] of [ 81 ...kFloat16BitsToNumberCases, 82 [0b0_00000_0000000000, 0.00001], // input that becomes subnormal in f16 is rounded to 0 83 [0b1_00000_0000000000, -0.00001], // and sign is preserved 84 ]) { 85 // some loose check 86 const actual = float32ToFloat16Bits(number); 87 t.expect( 88 Math.abs(actual - bits) <= 1, 89 `for ${number}, expected ${bits.toString(2)}, got ${actual.toString(2)}` 90 ); 91 } 92 }); 93 94 g.test('float32ToFloatBits_floatBitsToNumber') 95 .paramsSubcasesOnly(u => 96 u 97 .combine('signed', [0, 1] as const) 98 .combine('exponentBits', [5, 8]) 99 .combine('mantissaBits', [10, 23]) 100 ) 101 .fn(t => { 102 const { signed, exponentBits, mantissaBits } = t.params; 103 const bias = (1 << (exponentBits - 1)) - 1; 104 105 for (const [, value] of kFloat16BitsToNumberCases) { 106 if (value < 0 && signed === 0) continue; 107 const bits = float32ToFloatBits(value, signed, exponentBits, mantissaBits, bias); 108 const reconstituted = floatBitsToNumber(bits, { signed, exponentBits, mantissaBits, bias }); 109 t.expect( 110 numbersApproximatelyEqual(reconstituted, value, 0.0000001), 111 `${reconstituted} vs ${value}` 112 ); 113 } 114 }); 115 116 g.test('floatBitsToULPFromZero,16').fn(t => { 117 const test = (bits: number, ulpFromZero: number) => 118 t.expect(floatBitsToNormalULPFromZero(bits, kFloat16Format) === ulpFromZero, bits.toString(2)); 119 // Zero 120 test(0b0_00000_0000000000, 0); 121 test(0b1_00000_0000000000, 0); 122 // Subnormal 123 test(0b0_00000_0000000001, 0); 124 test(0b1_00000_0000000001, 0); 125 test(0b0_00000_1111111111, 0); 126 test(0b1_00000_1111111111, 0); 127 // Normal 128 test(0b0_00001_0000000000, 1); // 0 + 1ULP 129 test(0b1_00001_0000000000, -1); // 0 - 1ULP 130 test(0b0_00001_0000000001, 2); // 0 + 2ULP 131 test(0b1_00001_0000000001, -2); // 0 - 2ULP 132 test(0b0_01110_0000000000, 0b01101_0000000001); // 0.5 133 test(0b1_01110_0000000000, -0b01101_0000000001); // -0.5 134 test(0b0_01110_1111111110, 0b01101_1111111111); // 1.0 - 2ULP 135 test(0b1_01110_1111111110, -0b01101_1111111111); // -(1.0 - 2ULP) 136 test(0b0_01110_1111111111, 0b01110_0000000000); // 1.0 - 1ULP 137 test(0b1_01110_1111111111, -0b01110_0000000000); // -(1.0 - 1ULP) 138 test(0b0_01111_0000000000, 0b01110_0000000001); // 1.0 139 test(0b1_01111_0000000000, -0b01110_0000000001); // -1.0 140 test(0b0_01111_0000000001, 0b01110_0000000010); // 1.0 + 1ULP 141 test(0b1_01111_0000000001, -0b01110_0000000010); // -(1.0 + 1ULP) 142 test(0b0_10000_0000000000, 0b01111_0000000001); // 2.0 143 test(0b1_10000_0000000000, -0b01111_0000000001); // -2.0 144 145 const testThrows = (b: number) => 146 t.shouldThrow('Error', () => floatBitsToNormalULPFromZero(b, kFloat16Format)); 147 // Infinity 148 testThrows(0b0_11111_0000000000); 149 testThrows(0b1_11111_0000000000); 150 // NaN 151 testThrows(0b0_11111_1111111111); 152 testThrows(0b1_11111_1111111111); 153 }); 154 155 g.test('floatBitsToULPFromZero,32').fn(t => { 156 const test = (bits: number, ulpFromZero: number) => 157 t.expect(floatBitsToNormalULPFromZero(bits, kFloat32Format) === ulpFromZero, bits.toString(2)); 158 // Zero 159 test(0b0_00000000_00000000000000000000000, 0); 160 test(0b1_00000000_00000000000000000000000, 0); 161 // Subnormal 162 test(0b0_00000000_00000000000000000000001, 0); 163 test(0b1_00000000_00000000000000000000001, 0); 164 test(0b0_00000000_11111111111111111111111, 0); 165 test(0b1_00000000_11111111111111111111111, 0); 166 // Normal 167 test(0b0_00000001_00000000000000000000000, 1); // 0 + 1ULP 168 test(0b1_00000001_00000000000000000000000, -1); // 0 - 1ULP 169 test(0b0_00000001_00000000000000000000001, 2); // 0 + 2ULP 170 test(0b1_00000001_00000000000000000000001, -2); // 0 - 2ULP 171 test(0b0_01111110_00000000000000000000000, 0b01111101_00000000000000000000001); // 0.5 172 test(0b1_01111110_00000000000000000000000, -0b01111101_00000000000000000000001); // -0.5 173 test(0b0_01111110_11111111111111111111110, 0b01111101_11111111111111111111111); // 1.0 - 2ULP 174 test(0b1_01111110_11111111111111111111110, -0b01111101_11111111111111111111111); // -(1.0 - 2ULP) 175 test(0b0_01111110_11111111111111111111111, 0b01111110_00000000000000000000000); // 1.0 - 1ULP 176 test(0b1_01111110_11111111111111111111111, -0b01111110_00000000000000000000000); // -(1.0 - 1ULP) 177 test(0b0_01111111_00000000000000000000000, 0b01111110_00000000000000000000001); // 1.0 178 test(0b1_01111111_00000000000000000000000, -0b01111110_00000000000000000000001); // -1.0 179 test(0b0_01111111_00000000000000000000001, 0b01111110_00000000000000000000010); // 1.0 + 1ULP 180 test(0b1_01111111_00000000000000000000001, -0b01111110_00000000000000000000010); // -(1.0 + 1ULP) 181 test(0b0_11110000_00000000000000000000000, 0b11101111_00000000000000000000001); // 2.0 182 test(0b1_11110000_00000000000000000000000, -0b11101111_00000000000000000000001); // -2.0 183 184 const testThrows = (b: number) => 185 t.shouldThrow('Error', () => floatBitsToNormalULPFromZero(b, kFloat32Format)); 186 // Infinity 187 testThrows(0b0_11111111_00000000000000000000000); 188 testThrows(0b1_11111111_00000000000000000000000); 189 // NaN 190 testThrows(0b0_11111111_11111111111111111111111); 191 testThrows(0b0_11111111_00000000000000000000001); 192 testThrows(0b1_11111111_11111111111111111111111); 193 testThrows(0b1_11111111_00000000000000000000001); 194 }); 195 196 g.test('scalarWGSL').fn(t => { 197 const cases: Array<[ScalarValue, string]> = [ 198 [f32(0.0), '0.0f'], 199 // The number -0.0 can be remapped to 0.0 when stored in a Scalar 200 // object. It is not possible to guarantee that '-0.0f' will 201 // be emitted. So the WGSL scalar value printing does not try 202 // to handle this case. 203 [f32(-0.0), '0.0f'], // -0.0 can be remapped to 0.0 204 [f32(1.0), '1.0f'], 205 [f32(-1.0), '-1.0f'], 206 [f32Bits(0x70000000), '1.5845632502852868e+29f'], 207 [f32Bits(0xf0000000), '-1.5845632502852868e+29f'], 208 [f16Bits(0), '0.0h'], 209 [f16Bits(0x3c00), '1.0h'], 210 [f16Bits(0xbc00), '-1.0h'], 211 [u32(0), '0u'], 212 [u32(1), '1u'], 213 [u32(2000000000), '2000000000u'], 214 [u32(-1), '4294967295u'], 215 [i32(0), 'i32(0)'], 216 [i32(1), 'i32(1)'], 217 [i32(-1), 'i32(-1)'], 218 [bool(true), 'true'], 219 [bool(false), 'false'], 220 ]; 221 for (const [value, expect] of cases) { 222 const got = value.wgsl(); 223 t.expect( 224 got === expect, 225 `[value: ${value.value}, type: ${value.type}] 226 got: ${got} 227 expect: ${expect}` 228 ); 229 } 230 }); 231 232 g.test('vectorWGSL').fn(t => { 233 const cases: Array<[VectorValue, string]> = [ 234 [vec2(f32(42.0), f32(24.0)), 'vec2(42.0f, 24.0f)'], 235 [vec2(f16Bits(0x5140), f16Bits(0x4e00)), 'vec2(42.0h, 24.0h)'], 236 [vec2(u32(42), u32(24)), 'vec2(42u, 24u)'], 237 [vec2(i32(42), i32(24)), 'vec2(i32(42), i32(24))'], 238 [vec2(bool(false), bool(true)), 'vec2(false, true)'], 239 240 [vec3(f32(0.0), f32(1.0), f32(-1.0)), 'vec3(0.0f, 1.0f, -1.0f)'], 241 [vec3(f16Bits(0), f16Bits(0x3c00), f16Bits(0xbc00)), 'vec3(0.0h, 1.0h, -1.0h)'], 242 [vec3(u32(0), u32(1), u32(-1)), 'vec3(0u, 1u, 4294967295u)'], 243 [vec3(i32(0), i32(1), i32(-1)), 'vec3(i32(0), i32(1), i32(-1))'], 244 [vec3(bool(true), bool(false), bool(true)), 'vec3(true, false, true)'], 245 246 [vec4(f32(1.0), f32(-2.0), f32(4.0), f32(-8.0)), 'vec4(1.0f, -2.0f, 4.0f, -8.0f)'], 247 [ 248 vec4(f16Bits(0xbc00), f16Bits(0x4000), f16Bits(0xc400), f16Bits(0x4800)), 249 'vec4(-1.0h, 2.0h, -4.0h, 8.0h)', 250 ], 251 [vec4(u32(1), u32(-2), u32(4), u32(-8)), 'vec4(1u, 4294967294u, 4u, 4294967288u)'], 252 [vec4(i32(1), i32(-2), i32(4), i32(-8)), 'vec4(i32(1), i32(-2), i32(4), i32(-8))'], 253 [vec4(bool(false), bool(true), bool(true), bool(false)), 'vec4(false, true, true, false)'], 254 ]; 255 for (const [value, expect] of cases) { 256 const got = value.wgsl(); 257 t.expect( 258 got === expect, 259 `[values: ${value.elements}, type: ${value.type}] 260 got: ${got} 261 expect: ${expect}` 262 ); 263 } 264 }); 265 266 g.test('matrixWGSL').fn(t => { 267 const cases: Array<[MatrixValue, string]> = [ 268 [ 269 toMatrix( 270 [ 271 [0.0, 1.0], 272 [2.0, 3.0], 273 ], 274 f32 275 ), 276 'mat2x2(0.0f, 1.0f, 2.0f, 3.0f)', 277 ], 278 [ 279 toMatrix( 280 [ 281 [0.0, 1.0, 2.0], 282 [3.0, 4.0, 5.0], 283 ], 284 f32 285 ), 286 'mat2x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f)', 287 ], 288 [ 289 toMatrix( 290 [ 291 [0.0, 1.0, 2.0, 3.0], 292 [4.0, 5.0, 6.0, 7.0], 293 ], 294 f32 295 ), 296 'mat2x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)', 297 ], 298 [ 299 toMatrix( 300 [ 301 [0.0, 1.0], 302 [2.0, 3.0], 303 [4.0, 5.0], 304 ], 305 f32 306 ), 307 'mat3x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f)', 308 ], 309 [ 310 toMatrix( 311 [ 312 [0.0, 1.0, 2.0], 313 [3.0, 4.0, 5.0], 314 [6.0, 7.0, 8.0], 315 ], 316 f32 317 ), 318 'mat3x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f)', 319 ], 320 [ 321 toMatrix( 322 [ 323 [0.0, 1.0, 2.0, 3.0], 324 [4.0, 5.0, 6.0, 7.0], 325 [8.0, 9.0, 10.0, 11.0], 326 ], 327 f32 328 ), 329 'mat3x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f)', 330 ], 331 [ 332 toMatrix( 333 [ 334 [0.0, 1.0], 335 [2.0, 3.0], 336 [4.0, 5.0], 337 [6.0, 7.0], 338 ], 339 f32 340 ), 341 'mat4x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)', 342 ], 343 [ 344 toMatrix( 345 [ 346 [0.0, 1.0, 2.0], 347 [3.0, 4.0, 5.0], 348 [6.0, 7.0, 8.0], 349 [9.0, 10.0, 11.0], 350 ], 351 f32 352 ), 353 'mat4x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f)', 354 ], 355 [ 356 toMatrix( 357 [ 358 [0.0, 1.0, 2.0, 3.0], 359 [4.0, 5.0, 6.0, 7.0], 360 [8.0, 9.0, 10.0, 11.0], 361 [12.0, 13.0, 14.0, 15.0], 362 ], 363 f32 364 ), 365 'mat4x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f)', 366 ], 367 ]; 368 for (const [value, expect] of cases) { 369 const got = value.wgsl(); 370 t.expect( 371 got === expect, 372 `[values: ${value.elements}, type: ${value.type}] 373 got: ${got} 374 expect: ${expect}` 375 ); 376 } 377 }); 378 379 g.test('constructorMatrix') 380 .params(u => 381 u 382 .combine('cols', [2, 3, 4] as const) 383 .combine('rows', [2, 3, 4] as const) 384 .combine('type', ['f32'] as const) 385 ) 386 .fn(t => { 387 const cols = t.params.cols; 388 const rows = t.params.rows; 389 const type = t.params.type; 390 const scalar_builder = type === 'f32' ? f32 : undefined; 391 assert(scalar_builder !== undefined, `Unexpected type param '${type}' provided`); 392 393 const elements = [...Array(cols).keys()].map(c => { 394 return [...Array(rows).keys()].map(r => scalar_builder(c * cols + r)); 395 }); 396 397 const got = new MatrixValue(elements); 398 const got_type = got.type; 399 t.expect( 400 got_type.cols === cols, 401 `expected Matrix to have ${cols} columns, received ${got_type.cols} instead` 402 ); 403 t.expect( 404 got_type.rows === rows, 405 `expected Matrix to have ${rows} columns, received ${got_type.rows} instead` 406 ); 407 t.expect( 408 got_type.elementType.kind === type, 409 `expected Matrix to have ${type} elements, received ${got_type.elementType.kind} instead` 410 ); 411 t.expect( 412 objectEquals(got.elements, elements), 413 `Matrix did not have expected elements (${JSON.stringify(elements)}), instead had (${ 414 got.elements 415 })` 416 ); 417 }); 418 419 g.test('pack2x16float') 420 .paramsSimple([ 421 // f16 normals 422 { inputs: [0, 0], result: [0x00000000, 0x80000000, 0x00008000, 0x80008000] }, 423 { inputs: [1, 0], result: [0x00003c00, 0x80003c00] }, 424 { inputs: [1, 1], result: [0x3c003c00] }, 425 { inputs: [-1, -1], result: [0xbc00bc00] }, 426 { inputs: [10, 1], result: [0x3c004900] }, 427 { inputs: [-10, 1], result: [0x3c00c900] }, 428 429 // f32 normal, but not f16 precise 430 { inputs: [1.00000011920928955078125, 1], result: [0x3c003c00, 0x3c003c01] }, 431 432 // f32 subnormals 433 // prettier-ignore 434 { inputs: [kValue.f32.positive.subnormal.max, 1], result: [0x3c000000, 0x3c008000, 0x3c000001] }, 435 // prettier-ignore 436 { inputs: [kValue.f32.negative.subnormal.min, 1], result: [0x3c008001, 0x3c000000, 0x3c008000] }, 437 438 // f16 subnormals 439 // prettier-ignore 440 { inputs: [kValue.f16.positive.subnormal.max, 1], result: [0x3c0003ff, 0x3c000000, 0x3c008000] }, 441 // prettier-ignore 442 { inputs: [kValue.f16.negative.subnormal.min, 1], result: [0x03c0083ff, 0x3c000000, 0x3c008000] }, 443 444 // f16 out of bounds 445 { inputs: [kValue.f16.positive.max + 1, 1], result: [undefined] }, 446 { inputs: [kValue.f16.negative.min - 1, 1], result: [undefined] }, 447 { inputs: [1, kValue.f16.positive.max + 1], result: [undefined] }, 448 { inputs: [1, kValue.f16.negative.min - 1], result: [undefined] }, 449 ] as const) 450 .fn(test => { 451 const toString = (data: readonly (undefined | number)[]): String[] => { 452 return data.map(d => (d !== undefined ? u32(d).toString() : 'undefined')); 453 }; 454 455 const inputs = test.params.inputs; 456 const got = pack2x16float(inputs[0], inputs[1]); 457 const expect = test.params.result; 458 459 const got_str = toString(got); 460 const expect_str = toString(expect); 461 462 // Using strings of the outputs, so they can be easily sorted, since order of the results doesn't matter. 463 test.expect( 464 objectEquals(got_str.sort(), expect_str.sort()), 465 `pack2x16float(${inputs}) returned [${got_str}]. Expected [${expect_str}]` 466 ); 467 }); 468 469 g.test('pack2x16snorm') 470 .paramsSimple([ 471 // Normals 472 { inputs: [0, 0], result: 0x00000000 }, 473 { inputs: [1, 0], result: 0x00007fff }, 474 { inputs: [0, 1], result: 0x7fff0000 }, 475 { inputs: [1, 1], result: 0x7fff7fff }, 476 { inputs: [-1, -1], result: 0x80018001 }, 477 { inputs: [10, 10], result: 0x7fff7fff }, 478 { inputs: [-10, -10], result: 0x80018001 }, 479 { inputs: [0.1, 0.1], result: 0x0ccd0ccd }, 480 { inputs: [-0.1, -0.1], result: 0xf333f333 }, 481 { inputs: [0.5, 0.5], result: 0x40004000 }, 482 { inputs: [-0.5, -0.5], result: 0xc001c001 }, 483 { inputs: [0.1, 0.5], result: 0x40000ccd }, 484 { inputs: [-0.1, -0.5], result: 0xc001f333 }, 485 486 // Subnormals 487 { inputs: [kValue.f32.positive.subnormal.max, 1], result: 0x7fff0000 }, 488 { inputs: [kValue.f32.negative.subnormal.min, 1], result: 0x7fff0000 }, 489 ] as const) 490 .fn(test => { 491 const inputs = test.params.inputs; 492 const got = pack2x16snorm(inputs[0], inputs[1]); 493 const expect = test.params.result; 494 495 test.expect(got === expect, `pack2x16snorm(${inputs}) returned ${got}. Expected ${expect}`); 496 }); 497 498 g.test('pack2x16unorm') 499 .paramsSimple([ 500 // Normals 501 { inputs: [0, 0], result: 0x00000000 }, 502 { inputs: [1, 0], result: 0x0000ffff }, 503 { inputs: [0, 1], result: 0xffff0000 }, 504 { inputs: [1, 1], result: 0xffffffff }, 505 { inputs: [-1, -1], result: 0x00000000 }, 506 { inputs: [0.1, 0.1], result: 0x199a199a }, 507 { inputs: [0.5, 0.5], result: 0x80008000 }, 508 { inputs: [0.1, 0.5], result: 0x8000199a }, 509 { inputs: [10, 10], result: 0xffffffff }, 510 511 // Subnormals 512 { inputs: [kValue.f32.positive.subnormal.max, 1], result: 0xffff0000 }, 513 ] as const) 514 .fn(test => { 515 const inputs = test.params.inputs; 516 const got = pack2x16unorm(inputs[0], inputs[1]); 517 const expect = test.params.result; 518 519 test.expect(got === expect, `pack2x16unorm(${inputs}) returned ${got}. Expected ${expect}`); 520 }); 521 522 g.test('pack4x8snorm') 523 .paramsSimple([ 524 // Normals 525 { inputs: [0, 0, 0, 0], result: 0x00000000 }, 526 { inputs: [1, 0, 0, 0], result: 0x0000007f }, 527 { inputs: [0, 1, 0, 0], result: 0x00007f00 }, 528 { inputs: [0, 0, 1, 0], result: 0x007f0000 }, 529 { inputs: [0, 0, 0, 1], result: 0x7f000000 }, 530 { inputs: [1, 1, 1, 1], result: 0x7f7f7f7f }, 531 { inputs: [10, 10, 10, 10], result: 0x7f7f7f7f }, 532 { inputs: [-1, 0, 0, 0], result: 0x00000081 }, 533 { inputs: [0, -1, 0, 0], result: 0x00008100 }, 534 { inputs: [0, 0, -1, 0], result: 0x00810000 }, 535 { inputs: [0, 0, 0, -1], result: 0x81000000 }, 536 { inputs: [-1, -1, -1, -1], result: 0x81818181 }, 537 { inputs: [-10, -10, -10, -10], result: 0x81818181 }, 538 { inputs: [0.1, 0.1, 0.1, 0.1], result: 0x0d0d0d0d }, 539 { inputs: [-0.1, -0.1, -0.1, -0.1], result: 0xf3f3f3f3 }, 540 { inputs: [0.1, -0.1, 0.1, -0.1], result: 0xf30df30d }, 541 { inputs: [0.5, 0.5, 0.5, 0.5], result: 0x40404040 }, 542 { inputs: [-0.5, -0.5, -0.5, -0.5], result: 0xc1c1c1c1 }, 543 { inputs: [-0.5, 0.5, -0.5, 0.5], result: 0x40c140c1 }, 544 { inputs: [0.1, 0.5, 0.1, 0.5], result: 0x400d400d }, 545 { inputs: [-0.1, -0.5, -0.1, -0.5], result: 0xc1f3c1f3 }, 546 547 // Subnormals 548 { inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0x7f7f7f00 }, 549 { inputs: [kValue.f32.negative.subnormal.min, 1, 1, 1], result: 0x7f7f7f00 }, 550 ] as const) 551 .fn(test => { 552 const inputs = test.params.inputs; 553 const got = pack4x8snorm(inputs[0], inputs[1], inputs[2], inputs[3]); 554 const expect = test.params.result; 555 556 test.expect(got === expect, `pack4x8snorm(${inputs}) returned ${u32(got)}. Expected ${expect}`); 557 }); 558 559 g.test('pack4x8unorm') 560 .paramsSimple([ 561 // Normals 562 { inputs: [0, 0, 0, 0], result: 0x00000000 }, 563 { inputs: [1, 0, 0, 0], result: 0x000000ff }, 564 { inputs: [0, 1, 0, 0], result: 0x0000ff00 }, 565 { inputs: [0, 0, 1, 0], result: 0x00ff0000 }, 566 { inputs: [0, 0, 0, 1], result: 0xff000000 }, 567 { inputs: [1, 1, 1, 1], result: 0xffffffff }, 568 { inputs: [10, 10, 10, 10], result: 0xffffffff }, 569 { inputs: [-1, -1, -1, -1], result: 0x00000000 }, 570 { inputs: [-10, -10, -10, -10], result: 0x00000000 }, 571 { inputs: [0.1, 0.1, 0.1, 0.1], result: 0x1a1a1a1a }, 572 { inputs: [0.5, 0.5, 0.5, 0.5], result: 0x80808080 }, 573 { inputs: [0.1, 0.5, 0.1, 0.5], result: 0x801a801a }, 574 575 // Subnormals 576 { inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0xffffff00 }, 577 ] as const) 578 .fn(test => { 579 const inputs = test.params.inputs; 580 const got = pack4x8unorm(inputs[0], inputs[1], inputs[2], inputs[3]); 581 const expect = test.params.result; 582 583 test.expect(got === expect, `pack4x8unorm(${inputs}) returned ${got}. Expected ${expect}`); 584 }); 585 586 const kRGB9E5UFloatCommonData = { 587 zero: /* */ { encoded: 0b00000_000000000_000000000_000000000, rgb: [0, 0, 0] }, 588 max: /* */ { encoded: 0b11111_111111111_111111111_111111111, rgb: [65408, 65408, 65408] }, 589 r1: /* */ { encoded: 0b10000_000000000_000000000_100000000, rgb: [1, 0, 0] }, 590 r2: /* */ { encoded: 0b10001_000000000_000000000_100000000, rgb: [2, 0, 0] }, 591 g1: /* */ { encoded: 0b10000_000000000_100000000_000000000, rgb: [0, 1, 0] }, 592 g2: /* */ { encoded: 0b10001_000000000_100000000_000000000, rgb: [0, 2, 0] }, 593 b1: /* */ { encoded: 0b10000_100000000_000000000_000000000, rgb: [0, 0, 1] }, 594 b2: /* */ { encoded: 0b10001_100000000_000000000_000000000, rgb: [0, 0, 2] }, 595 r1_g1_b1: /* */ { encoded: 0b10000_100000000_100000000_100000000, rgb: [1, 1, 1] }, 596 r1_g2_b1: /* */ { encoded: 0b10001_010000000_100000000_010000000, rgb: [1, 2, 1] }, 597 r4_g8_b2: /* */ { encoded: 0b10011_001000000_100000000_010000000, rgb: [4, 8, 2] }, 598 r1_g2_b3: /* */ { encoded: 0b10001_110000000_100000000_010000000, rgb: [1, 2, 3] }, 599 r128_g3968_b65408: { encoded: 0b11111_111111111_000011111_000000001, rgb: [128, 3968, 65408] }, 600 r128_g1984_b30016: { encoded: 0b11110_111010101_000011111_000000010, rgb: [128, 1984, 30016] }, 601 r_5_g_25_b_8: /**/ { encoded: 0b10011_100000000_000001000_000010000, rgb: [0.5, 0.25, 8] }, 602 }; 603 604 const kPackRGB9E5UFloatData = mergeParams(kRGB9E5UFloatCommonData, { 605 clamp_max: /* */ { encoded: 0b11111_111111111_111111111_111111111, rgb: [1e7, 1e10, 1e50] }, 606 subnormals: /* */ { encoded: 0b00000_000000000_000000000_000000000, rgb: [1e-10, 1e-20, 1e-30] }, 607 r57423_g54_b3478: { encoded: 0b11111_000011011_000000000_111000001, rgb: [57423, 54, 3478] }, 608 r6852_g3571_b2356: { encoded: 0b11100_010010011_011011111_110101100, rgb: [6852, 3571, 2356] }, 609 r68312_g12_b8123: { encoded: 0b11111_000111111_000000000_111111111, rgb: [68312, 12, 8123] }, 610 r7321_g846_b32: { encoded: 0b11100_000000010_000110101_111001010, rgb: [7321, 846, 32] }, 611 }); 612 613 function bits5_9_9_9(x: number) { 614 const s = (x >>> 0).toString(2).padStart(32, '0'); 615 return `${s.slice(0, 5)}_${s.slice(5, 14)}_${s.slice(14, 23)}_${s.slice(23, 32)}`; 616 } 617 618 g.test('packRGB9E5UFloat') 619 .params(u => u.combine('case', keysOf(kPackRGB9E5UFloatData))) 620 .fn(test => { 621 const c = kPackRGB9E5UFloatData[test.params.case]; 622 const got = packRGB9E5UFloat(c.rgb[0], c.rgb[1], c.rgb[2]); 623 const expect = c.encoded; 624 625 test.expect( 626 got === expect, 627 `packRGB9E5UFloat(${c.rgb}) returned ${bits5_9_9_9(got)}. Expected ${bits5_9_9_9(expect)}` 628 ); 629 }); 630 631 g.test('unpackRGB9E5UFloat') 632 .params(u => u.combine('case', keysOf(kRGB9E5UFloatCommonData))) 633 .fn(test => { 634 const c = kRGB9E5UFloatCommonData[test.params.case]; 635 const got = unpackRGB9E5UFloat(c.encoded); 636 const expect = c.rgb; 637 638 test.expect( 639 got.R === expect[0] && got.G === expect[1] && got.B === expect[2], 640 `unpackRGB9E5UFloat(${bits5_9_9_9(c.encoded)} ` + 641 `returned ${got.R},${got.G},${got.B}. Expected ${expect}` 642 ); 643 }); 644 645 const kConcreteTypeOfNoAllowedListCases = { 646 bool: Type.bool, 647 i32: Type.i32, 648 u32: Type.u32, 649 f32: Type.f32, 650 f16: Type.f16, 651 abstractInt: Type.i32, 652 abstractFloat: Type.f32, 653 vec2b: Type.vec2b, 654 vec2i: Type.vec2i, 655 vec3u: Type.vec3u, 656 vec4f: Type.vec4f, 657 vec2h: Type.vec2h, 658 vec3ai: Type.vec3i, 659 vec4af: Type.vec4f, 660 mat2x2f: Type.mat2x2f, 661 mat3x4h: Type.mat3x4h, 662 } as const; 663 664 g.test('concreteTypeOf_noAllowedLiist') 665 .params(u => u.combine('src', keysOf(kConcreteTypeOfNoAllowedListCases))) 666 .fn(test => { 667 const src = test.params.src; 668 const dest = kConcreteTypeOfNoAllowedListCases[src]; 669 const got = concreteTypeOf(stringToType(src)); 670 test.expect(got === dest); 671 }); 672 673 const kConcreteTypeOfAllowListCases = { 674 af_f32: { type: Type.abstractFloat, allowed: [Type.f32], expect: Type.f32 }, 675 af_f16: { type: Type.abstractFloat, allowed: [Type.f16], expect: Type.f16 }, 676 af_all: { 677 type: Type.abstractFloat, 678 allowed: [Type.f16, Type.f32, Type.i32, Type.u32], 679 expect: Type.f32, 680 }, 681 ai_i32: { type: Type.abstractInt, allowed: [Type.i32], expect: Type.i32 }, 682 ai_u32: { type: Type.abstractInt, allowed: [Type.u32], expect: Type.u32 }, 683 ai_f32: { type: Type.abstractInt, allowed: [Type.f32], expect: Type.f32 }, 684 ai_f16: { type: Type.abstractInt, allowed: [Type.f16], expect: Type.f16 }, 685 ai_all: { 686 type: Type.abstractInt, 687 allowed: [Type.f16, Type.f32, Type.i32, Type.u32], 688 expect: Type.i32, 689 }, 690 } as const; 691 692 g.test('concreteTypeOf_AllowedLiist') 693 .params(u => u.combine('k', keysOf(kConcreteTypeOfAllowListCases))) 694 .fn(test => { 695 const { type, allowed, expect } = kConcreteTypeOfAllowListCases[test.params.k]; 696 const got = concreteTypeOf(type, allowed as [Type]); 697 test.expect(got === expect); 698 });