diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts | 918 |
1 files changed, 918 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts new file mode 100644 index 0000000000..9651f336eb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts @@ -0,0 +1,918 @@ +import { assert, unreachable } from '../../../common/util/util.js'; +import { EncodableTextureFormat, UncompressedTextureFormat } from '../../capability_info.js'; +import { + assertInIntegerRange, + float32ToFloatBits, + float32ToFloat16Bits, + floatAsNormalizedInteger, + gammaCompress, + gammaDecompress, + normalizedIntegerAsFloat, + packRGB9E5UFloat, + floatBitsToNumber, + float16BitsToFloat32, + floatBitsToNormalULPFromZero, + kFloat32Format, + kFloat16Format, + numberToFloat32Bits, + float32BitsToNumber, + numberToFloatBits, +} from '../conversion.js'; +import { clamp, signExtend } from '../math.js'; + +/** A component of a texture format: R, G, B, A, Depth, or Stencil. */ +export const enum TexelComponent { + R = 'R', + G = 'G', + B = 'B', + A = 'A', + Depth = 'Depth', + Stencil = 'Stencil', +} + +/** Arbitrary data, per component of a texel format. */ +export type PerTexelComponent<T> = { [c in TexelComponent]?: T }; + +/** How a component is encoded in its bit range of a texel format. */ +export type ComponentDataType = 'uint' | 'sint' | 'unorm' | 'snorm' | 'float' | 'ufloat' | null; + +/** + * Maps component values to component values + * @param {PerTexelComponent<number>} components - The input components. + * @returns {PerTexelComponent<number>} The new output components. + */ +type ComponentMapFn = (components: PerTexelComponent<number>) => PerTexelComponent<number>; + +/** + * Packs component values as an ArrayBuffer + * @param {PerTexelComponent<number>} components - The input components. + * @returns {ArrayBuffer} The packed data. + */ +type ComponentPackFn = (components: PerTexelComponent<number>) => ArrayBuffer; + +/** Unpacks component values from a Uint8Array */ +type ComponentUnpackFn = (data: Uint8Array) => PerTexelComponent<number>; + +/** + * Create a PerTexelComponent object filled with the same value for all components. + * @param {TexelComponent[]} components - The component names. + * @param {T} value - The value to assign to each component. + * @returns {PerTexelComponent<T>} + */ +function makePerTexelComponent<T>(components: TexelComponent[], value: T): PerTexelComponent<T> { + const values: PerTexelComponent<T> = {}; + for (const c of components) { + values[c] = value; + } + return values; +} + +/** + * Create a function which applies clones a `PerTexelComponent<number>` and then applies the + * function `fn` to each component of `components`. + * @param {(value: number) => number} fn - The mapping function to apply to component values. + * @param {TexelComponent[]} components - The component names. + * @returns {ComponentMapFn} The map function which clones the input component values, and applies + * `fn` to each of component of `components`. + */ +function applyEach(fn: (value: number) => number, components: TexelComponent[]): ComponentMapFn { + return (values: PerTexelComponent<number>) => { + values = Object.assign({}, values); + for (const c of components) { + assert(values[c] !== undefined); + values[c] = fn(values[c]!); + } + return values; + }; +} + +/** + * A `ComponentMapFn` for encoding sRGB. + * @param {PerTexelComponent<number>} components - The input component values. + * @returns {TexelComponent<number>} Gamma-compressed copy of `components`. + */ +const encodeSRGB: ComponentMapFn = components => { + assert( + components.R !== undefined && components.G !== undefined && components.B !== undefined, + 'sRGB requires all of R, G, and B components' + ); + return applyEach(gammaCompress, kRGB)(components); +}; + +/** + * A `ComponentMapFn` for decoding sRGB. + * @param {PerTexelComponent<number>} components - The input component values. + * @returns {TexelComponent<number>} Gamma-decompressed copy of `components`. + */ +const decodeSRGB: ComponentMapFn = components => { + components = Object.assign({}, components); + assert( + components.R !== undefined && components.G !== undefined && components.B !== undefined, + 'sRGB requires all of R, G, and B components' + ); + return applyEach(gammaDecompress, kRGB)(components); +}; + +/** + * Makes a `ComponentMapFn` for clamping values to the specified range. + */ +export function makeClampToRange(format: EncodableTextureFormat): ComponentMapFn { + const repr = kTexelRepresentationInfo[format]; + assert(repr.numericRange !== null, 'Format has unknown numericRange'); + return applyEach(x => clamp(x, repr.numericRange!), repr.componentOrder); +} + +// MAINTENANCE_TODO: Look into exposing this map to the test fixture so that it can be GCed at the +// end of each test group. That would allow for caching of larger buffers (though it's unclear how +// ofter larger buffers are used by packComponents.) +const smallComponentDataViews = new Map(); +function getComponentDataView(byteLength: number): DataView { + if (byteLength > 32) { + const buffer = new ArrayBuffer(byteLength); + return new DataView(buffer); + } + let dataView = smallComponentDataViews.get(byteLength); + if (!dataView) { + const buffer = new ArrayBuffer(byteLength); + dataView = new DataView(buffer); + smallComponentDataViews.set(byteLength, dataView); + } + return dataView; +} + +/** + * Helper function to pack components as an ArrayBuffer. + * @param {TexelComponent[]} componentOrder - The order of the component data. + * @param {PerTexelComponent<number>} components - The input component values. + * @param {number | PerTexelComponent<number>} bitLengths - The length in bits of each component. + * If a single number, all components are the same length, otherwise this is a dictionary of + * per-component bit lengths. + * @param {ComponentDataType | PerTexelComponent<ComponentDataType>} componentDataTypes - + * The type of the data in `components`. If a single value, all components have the same value. + * Otherwise, this is a dictionary of per-component data types. + * @returns {ArrayBuffer} The packed component data. + */ +function packComponents( + componentOrder: TexelComponent[], + components: PerTexelComponent<number>, + bitLengths: number | PerTexelComponent<number>, + componentDataTypes: ComponentDataType | PerTexelComponent<ComponentDataType> +): ArrayBuffer { + let bitLengthMap; + let totalBitLength; + if (typeof bitLengths === 'number') { + bitLengthMap = makePerTexelComponent(componentOrder, bitLengths); + totalBitLength = bitLengths * componentOrder.length; + } else { + bitLengthMap = bitLengths; + totalBitLength = Object.entries(bitLengthMap).reduce((acc, [, value]) => { + assert(value !== undefined); + return acc + value; + }, 0); + } + assert(totalBitLength % 8 === 0); + + const componentDataTypeMap = + typeof componentDataTypes === 'string' || componentDataTypes === null + ? makePerTexelComponent(componentOrder, componentDataTypes) + : componentDataTypes; + + const dataView = getComponentDataView(totalBitLength / 8); + let bitOffset = 0; + for (const c of componentOrder) { + const value = components[c]; + const type = componentDataTypeMap[c]; + const bitLength = bitLengthMap[c]; + assert(value !== undefined); + assert(type !== undefined); + assert(bitLength !== undefined); + + const byteOffset = Math.floor(bitOffset / 8); + const byteLength = Math.ceil(bitLength / 8); + switch (type) { + case 'uint': + case 'unorm': + if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) { + switch (byteLength) { + case 1: + dataView.setUint8(byteOffset, value); + break; + case 2: + dataView.setUint16(byteOffset, value, true); + break; + case 4: + dataView.setUint32(byteOffset, value, true); + break; + default: + unreachable(); + } + } else { + // Packed representations are all 32-bit and use Uint as the data type. + // ex.) rg10b11float, rgb10a2unorm + switch (dataView.byteLength) { + case 4: { + const currentValue = dataView.getUint32(0, true); + + let mask = 0xffffffff; + const bitsToClearRight = bitOffset; + const bitsToClearLeft = 32 - (bitLength + bitOffset); + + mask = (mask >>> bitsToClearRight) << bitsToClearRight; + mask = (mask << bitsToClearLeft) >>> bitsToClearLeft; + + const newValue = (currentValue & ~mask) | (value << bitOffset); + + dataView.setUint32(0, newValue, true); + break; + } + default: + unreachable(); + } + } + break; + case 'sint': + case 'snorm': + assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8); + switch (byteLength) { + case 1: + dataView.setInt8(byteOffset, value); + break; + case 2: + dataView.setInt16(byteOffset, value, true); + break; + case 4: + dataView.setInt32(byteOffset, value, true); + break; + default: + unreachable(); + } + break; + case 'float': + assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8); + switch (byteLength) { + case 4: + dataView.setFloat32(byteOffset, value, true); + break; + default: + unreachable(); + } + break; + case 'ufloat': + case null: + unreachable(); + } + + bitOffset += bitLength; + } + + return dataView.buffer; +} + +/** + * Unpack substrings of bits from a Uint8Array, e.g. [8,8,8,8] or [9,9,9,5]. + */ +function unpackComponentsBits( + componentOrder: TexelComponent[], + byteView: Uint8Array, + bitLengths: number | PerTexelComponent<number> +): PerTexelComponent<number> { + const components = makePerTexelComponent(componentOrder, 0); + + let bitLengthMap; + let totalBitLength; + if (typeof bitLengths === 'number') { + let index = 0; + // Optimized cases for when the bit lengths are all a well aligned value. + switch (bitLengths) { + case 8: + for (const c of componentOrder) { + components[c] = byteView[index++]; + } + return components; + case 16: { + const shortView = new Uint16Array(byteView.buffer, byteView.byteOffset); + for (const c of componentOrder) { + components[c] = shortView[index++]; + } + return components; + } + case 32: { + const longView = new Uint32Array(byteView.buffer, byteView.byteOffset); + for (const c of componentOrder) { + components[c] = longView[index++]; + } + return components; + } + } + + bitLengthMap = makePerTexelComponent(componentOrder, bitLengths); + totalBitLength = bitLengths * componentOrder.length; + } else { + bitLengthMap = bitLengths; + totalBitLength = Object.entries(bitLengthMap).reduce((acc, [, value]) => { + assert(value !== undefined); + return acc + value; + }, 0); + } + + assert(totalBitLength % 8 === 0); + + const dataView = new DataView(byteView.buffer, byteView.byteOffset, byteView.byteLength); + let bitOffset = 0; + for (const c of componentOrder) { + const bitLength = bitLengthMap[c]; + assert(bitLength !== undefined); + + let value: number; + + const byteOffset = Math.floor(bitOffset / 8); + const byteLength = Math.ceil(bitLength / 8); + if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) { + switch (byteLength) { + case 1: + value = dataView.getUint8(byteOffset); + break; + case 2: + value = dataView.getUint16(byteOffset, true); + break; + case 4: + value = dataView.getUint32(byteOffset, true); + break; + default: + unreachable(); + } + } else { + // Packed representations are all 32-bit and use Uint as the data type. + // ex.) rg10b11float, rgb10a2unorm + assert(dataView.byteLength === 4); + const word = dataView.getUint32(0, true); + value = (word >>> bitOffset) & ((1 << bitLength) - 1); + } + + bitOffset += bitLength; + components[c] = value; + } + + return components; +} + +/** + * Create an entry in `kTexelRepresentationInfo` for normalized integer texel data with constant + * bitlength. + * @param {TexelComponent[]} componentOrder - The order of the component data. + * @param {number} bitLength - The number of bits in each component. + * @param {{signed: boolean; sRGB: boolean}} opt - Boolean flags for `signed` and `sRGB`. + */ +function makeNormalizedInfo( + componentOrder: TexelComponent[], + bitLength: number, + opt: { signed: boolean; sRGB: boolean } +): TexelRepresentationInfo { + const encodeNonSRGB = applyEach( + (n: number) => floatAsNormalizedInteger(n, bitLength, opt.signed), + componentOrder + ); + const decodeNonSRGB = applyEach( + (n: number) => normalizedIntegerAsFloat(n, bitLength, opt.signed), + componentOrder + ); + + const numberToBitsNonSRGB = applyEach( + n => floatAsNormalizedInteger(n, bitLength, opt.signed), + componentOrder + ); + let bitsToNumberNonSRGB: ComponentMapFn; + if (opt.signed) { + bitsToNumberNonSRGB = applyEach( + n => normalizedIntegerAsFloat(signExtend(n, bitLength), bitLength, opt.signed), + componentOrder + ); + } else { + bitsToNumberNonSRGB = applyEach( + n => normalizedIntegerAsFloat(n, bitLength, opt.signed), + componentOrder + ); + } + + let encode: ComponentMapFn; + let decode: ComponentMapFn; + let numberToBits: ComponentMapFn; + let bitsToNumber: ComponentMapFn; + if (opt.sRGB) { + encode = components => encodeNonSRGB(encodeSRGB(components)); + decode = components => decodeSRGB(decodeNonSRGB(components)); + numberToBits = components => numberToBitsNonSRGB(encodeSRGB(components)); + bitsToNumber = components => decodeSRGB(bitsToNumberNonSRGB(components)); + } else { + encode = encodeNonSRGB; + decode = decodeNonSRGB; + numberToBits = numberToBitsNonSRGB; + bitsToNumber = bitsToNumberNonSRGB; + } + + let bitsToULPFromZero: ComponentMapFn; + if (opt.signed) { + const maxValue = (1 << (bitLength - 1)) - 1; // e.g. 127 for snorm8 + bitsToULPFromZero = applyEach( + n => Math.max(-maxValue, signExtend(n, bitLength)), + componentOrder + ); + } else { + bitsToULPFromZero = components => components; + } + + const dataType: ComponentDataType = opt.signed ? 'snorm' : 'unorm'; + return { + componentOrder, + componentInfo: makePerTexelComponent(componentOrder, { + dataType, + bitLength, + }), + encode, + decode, + pack: (components: PerTexelComponent<number>) => + packComponents(componentOrder, components, bitLength, dataType), + unpackBits: (data: Uint8Array) => unpackComponentsBits(componentOrder, data, bitLength), + numberToBits, + bitsToNumber, + bitsToULPFromZero, + numericRange: { min: opt.signed ? -1 : 0, max: 1 }, + }; +} + +/** + * Create an entry in `kTexelRepresentationInfo` for integer texel data with constant bitlength. + * @param {TexelComponent[]} componentOrder - The order of the component data. + * @param {number} bitLength - The number of bits in each component. + * @param {{signed: boolean}} opt - Boolean flag for `signed`. + */ +function makeIntegerInfo( + componentOrder: TexelComponent[], + bitLength: number, + opt: { signed: boolean } +): TexelRepresentationInfo { + assert(bitLength <= 32); + const encode = applyEach( + (n: number) => (assertInIntegerRange(n, bitLength, opt.signed), n), + componentOrder + ); + const decode = applyEach( + (n: number) => (assertInIntegerRange(n, bitLength, opt.signed), n), + componentOrder + ); + + let bitsToULPFromZero: ComponentMapFn; + if (opt.signed) { + bitsToULPFromZero = applyEach(n => signExtend(n, bitLength), componentOrder); + } else { + bitsToULPFromZero = components => components; + } + + const dataType: ComponentDataType = opt.signed ? 'sint' : 'uint'; + const bitMask = (1 << bitLength) - 1; + return { + componentOrder, + componentInfo: makePerTexelComponent(componentOrder, { + dataType, + bitLength, + }), + encode, + decode, + pack: (components: PerTexelComponent<number>) => + packComponents(componentOrder, components, bitLength, dataType), + unpackBits: (data: Uint8Array) => unpackComponentsBits(componentOrder, data, bitLength), + numberToBits: applyEach(v => v & bitMask, componentOrder), + bitsToNumber: decode, + bitsToULPFromZero, + numericRange: opt.signed + ? { min: -(2 ** (bitLength - 1)), max: 2 ** (bitLength - 1) - 1 } + : { min: 0, max: 2 ** bitLength - 1 }, + }; +} + +/** + * Create an entry in `kTexelRepresentationInfo` for floating point texel data with constant + * bitlength. + * @param {TexelComponent[]} componentOrder - The order of the component data. + * @param {number} bitLength - The number of bits in each component. + */ +function makeFloatInfo( + componentOrder: TexelComponent[], + bitLength: number, + { restrictedDepth = false }: { restrictedDepth?: boolean } = {} +): TexelRepresentationInfo { + let encode: ComponentMapFn; + let numberToBits; + let bitsToNumber; + let bitsToULPFromZero; + switch (bitLength) { + case 32: + if (restrictedDepth) { + encode = applyEach(v => { + assert(v >= 0.0 && v <= 1.0, 'depth out of range'); + return new Float32Array([v])[0]; + }, componentOrder); + } else { + encode = applyEach(v => new Float32Array([v])[0], componentOrder); + } + numberToBits = applyEach(numberToFloat32Bits, componentOrder); + bitsToNumber = applyEach(float32BitsToNumber, componentOrder); + bitsToULPFromZero = applyEach( + v => floatBitsToNormalULPFromZero(v, kFloat32Format), + componentOrder + ); + break; + case 16: + if (restrictedDepth) { + encode = applyEach(v => { + assert(v >= 0.0 && v <= 1.0, 'depth out of range'); + return float16BitsToFloat32(float32ToFloat16Bits(v)); + }, componentOrder); + } else { + encode = applyEach(v => float16BitsToFloat32(float32ToFloat16Bits(v)), componentOrder); + } + numberToBits = applyEach(float32ToFloat16Bits, componentOrder); + bitsToNumber = applyEach(float16BitsToFloat32, componentOrder); + bitsToULPFromZero = applyEach( + v => floatBitsToNormalULPFromZero(v, kFloat16Format), + componentOrder + ); + break; + default: + unreachable(); + } + const decode = applyEach(identity, componentOrder); + + return { + componentOrder, + componentInfo: makePerTexelComponent(componentOrder, { + dataType: 'float' as const, + bitLength, + }), + encode, + decode, + pack: (components: PerTexelComponent<number>) => { + switch (bitLength) { + case 16: + components = applyEach(float32ToFloat16Bits, componentOrder)(components); + return packComponents(componentOrder, components, 16, 'uint'); + case 32: + return packComponents(componentOrder, components, bitLength, 'float'); + default: + unreachable(); + } + }, + unpackBits: (data: Uint8Array) => unpackComponentsBits(componentOrder, data, bitLength), + numberToBits, + bitsToNumber, + bitsToULPFromZero, + numericRange: restrictedDepth + ? { min: 0, max: 1 } + : { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY }, + }; +} + +const kR = [TexelComponent.R]; +const kRG = [TexelComponent.R, TexelComponent.G]; +const kRGB = [TexelComponent.R, TexelComponent.G, TexelComponent.B]; +const kRGBA = [TexelComponent.R, TexelComponent.G, TexelComponent.B, TexelComponent.A]; +const kBGRA = [TexelComponent.B, TexelComponent.G, TexelComponent.R, TexelComponent.A]; + +const identity = (n: number) => n; + +const kFloat11Format = { signed: 0, exponentBits: 5, mantissaBits: 6, bias: 15 } as const; +const kFloat10Format = { signed: 0, exponentBits: 5, mantissaBits: 5, bias: 15 } as const; +const kFloat9e5Format = { signed: 0, exponentBits: 5, mantissaBits: 9, bias: 15 } as const; + +export type TexelRepresentationInfo = { + /** Order of components in the packed representation. */ + readonly componentOrder: TexelComponent[]; + /** Data type and bit length of each component in the format. */ + readonly componentInfo: PerTexelComponent<{ + dataType: ComponentDataType; + bitLength: number; + }>; + /** Encode shader values into their data representation. ex.) float 1.0 -> unorm8 255 */ + // MAINTENANCE_TODO: Replace with numberToBits? + readonly encode: ComponentMapFn; + /** Decode the data representation into the shader values. ex.) unorm8 255 -> float 1.0 */ + // MAINTENANCE_TODO: Replace with bitsToNumber? + readonly decode: ComponentMapFn; + /** Pack texel component values into an ArrayBuffer. ex.) rg8unorm `{r: 0, g: 255}` -> 0xFF00 */ + // MAINTENANCE_TODO: Replace with packBits? + readonly pack: ComponentPackFn; + + /** Convert integer bit representations into numeric values, e.g. unorm8 255 -> numeric 1.0 */ + readonly bitsToNumber: ComponentMapFn; + /** Convert numeric values into integer bit representations, e.g. numeric 1.0 -> unorm8 255 */ + readonly numberToBits: ComponentMapFn; + /** Unpack integer bit representations from an ArrayBuffer, e.g. 0xFF00 -> rg8unorm [0,255] */ + readonly unpackBits: ComponentUnpackFn; + /** Convert integer bit representations into ULPs-from-zero, e.g. unorm8 255 -> 255 ULPs */ + readonly bitsToULPFromZero: ComponentMapFn; + /** The valid range of numeric "color" values, e.g. [0, Infinity] for ufloat. */ + readonly numericRange: null | { min: number; max: number }; + + // Add fields as needed +}; +export const kTexelRepresentationInfo: { + readonly [k in UncompressedTextureFormat]: TexelRepresentationInfo; +} = { + .../* prettier-ignore */ { + 'r8unorm': makeNormalizedInfo( kR, 8, { signed: false, sRGB: false }), + 'r8snorm': makeNormalizedInfo( kR, 8, { signed: true, sRGB: false }), + 'r8uint': makeIntegerInfo( kR, 8, { signed: false }), + 'r8sint': makeIntegerInfo( kR, 8, { signed: true }), + 'r16uint': makeIntegerInfo( kR, 16, { signed: false }), + 'r16sint': makeIntegerInfo( kR, 16, { signed: true }), + 'r16float': makeFloatInfo( kR, 16), + 'rg8unorm': makeNormalizedInfo( kRG, 8, { signed: false, sRGB: false }), + 'rg8snorm': makeNormalizedInfo( kRG, 8, { signed: true, sRGB: false }), + 'rg8uint': makeIntegerInfo( kRG, 8, { signed: false }), + 'rg8sint': makeIntegerInfo( kRG, 8, { signed: true }), + 'r32uint': makeIntegerInfo( kR, 32, { signed: false }), + 'r32sint': makeIntegerInfo( kR, 32, { signed: true }), + 'r32float': makeFloatInfo( kR, 32), + 'rg16uint': makeIntegerInfo( kRG, 16, { signed: false }), + 'rg16sint': makeIntegerInfo( kRG, 16, { signed: true }), + 'rg16float': makeFloatInfo( kRG, 16), + 'rgba8unorm': makeNormalizedInfo(kRGBA, 8, { signed: false, sRGB: false }), + 'rgba8unorm-srgb': makeNormalizedInfo(kRGBA, 8, { signed: false, sRGB: true }), + 'rgba8snorm': makeNormalizedInfo(kRGBA, 8, { signed: true, sRGB: false }), + 'rgba8uint': makeIntegerInfo( kRGBA, 8, { signed: false }), + 'rgba8sint': makeIntegerInfo( kRGBA, 8, { signed: true }), + 'bgra8unorm': makeNormalizedInfo(kBGRA, 8, { signed: false, sRGB: false }), + 'bgra8unorm-srgb': makeNormalizedInfo(kBGRA, 8, { signed: false, sRGB: true }), + 'rg32uint': makeIntegerInfo( kRG, 32, { signed: false }), + 'rg32sint': makeIntegerInfo( kRG, 32, { signed: true }), + 'rg32float': makeFloatInfo( kRG, 32), + 'rgba16uint': makeIntegerInfo( kRGBA, 16, { signed: false }), + 'rgba16sint': makeIntegerInfo( kRGBA, 16, { signed: true }), + 'rgba16float': makeFloatInfo( kRGBA, 16), + 'rgba32uint': makeIntegerInfo( kRGBA, 32, { signed: false }), + 'rgba32sint': makeIntegerInfo( kRGBA, 32, { signed: true }), + 'rgba32float': makeFloatInfo( kRGBA, 32), + }, + ...{ + rgb10a2unorm: { + componentOrder: kRGBA, + componentInfo: { + R: { dataType: 'unorm', bitLength: 10 }, + G: { dataType: 'unorm', bitLength: 10 }, + B: { dataType: 'unorm', bitLength: 10 }, + A: { dataType: 'unorm', bitLength: 2 }, + }, + encode: components => { + return { + R: floatAsNormalizedInteger(components.R ?? unreachable(), 10, false), + G: floatAsNormalizedInteger(components.G ?? unreachable(), 10, false), + B: floatAsNormalizedInteger(components.B ?? unreachable(), 10, false), + A: floatAsNormalizedInteger(components.A ?? unreachable(), 2, false), + }; + }, + decode: components => { + return { + R: normalizedIntegerAsFloat(components.R ?? unreachable(), 10, false), + G: normalizedIntegerAsFloat(components.G ?? unreachable(), 10, false), + B: normalizedIntegerAsFloat(components.B ?? unreachable(), 10, false), + A: normalizedIntegerAsFloat(components.A ?? unreachable(), 2, false), + }; + }, + pack: components => + packComponents( + kRGBA, + components, + { + R: 10, + G: 10, + B: 10, + A: 2, + }, + 'uint' + ), + unpackBits: (data: Uint8Array) => + unpackComponentsBits(kRGBA, data, { R: 10, G: 10, B: 10, A: 2 }), + numberToBits: components => ({ + R: floatAsNormalizedInteger(components.R ?? unreachable(), 10, false), + G: floatAsNormalizedInteger(components.G ?? unreachable(), 10, false), + B: floatAsNormalizedInteger(components.B ?? unreachable(), 10, false), + A: floatAsNormalizedInteger(components.A ?? unreachable(), 2, false), + }), + bitsToNumber: components => ({ + R: normalizedIntegerAsFloat(components.R!, 10, false), + G: normalizedIntegerAsFloat(components.G!, 10, false), + B: normalizedIntegerAsFloat(components.B!, 10, false), + A: normalizedIntegerAsFloat(components.A!, 2, false), + }), + bitsToULPFromZero: components => components, + numericRange: { min: 0, max: 1 }, + }, + rg11b10ufloat: { + componentOrder: kRGB, + encode: applyEach(identity, kRGB), + decode: applyEach(identity, kRGB), + componentInfo: { + R: { dataType: 'ufloat', bitLength: 11 }, + G: { dataType: 'ufloat', bitLength: 11 }, + B: { dataType: 'ufloat', bitLength: 10 }, + }, + pack: components => { + const componentsBits = { + R: float32ToFloatBits(components.R ?? unreachable(), 0, 5, 6, 15), + G: float32ToFloatBits(components.G ?? unreachable(), 0, 5, 6, 15), + B: float32ToFloatBits(components.B ?? unreachable(), 0, 5, 5, 15), + }; + return packComponents( + kRGB, + componentsBits, + { + R: 11, + G: 11, + B: 10, + }, + 'uint' + ); + }, + unpackBits: (data: Uint8Array) => unpackComponentsBits(kRGB, data, { R: 11, G: 11, B: 10 }), + numberToBits: components => ({ + R: numberToFloatBits(components.R ?? unreachable(), kFloat11Format), + G: numberToFloatBits(components.G ?? unreachable(), kFloat11Format), + B: numberToFloatBits(components.B ?? unreachable(), kFloat10Format), + }), + bitsToNumber: components => ({ + R: floatBitsToNumber(components.R!, kFloat11Format), + G: floatBitsToNumber(components.G!, kFloat11Format), + B: floatBitsToNumber(components.B!, kFloat10Format), + }), + bitsToULPFromZero: components => ({ + R: floatBitsToNormalULPFromZero(components.R!, kFloat11Format), + G: floatBitsToNormalULPFromZero(components.G!, kFloat11Format), + B: floatBitsToNormalULPFromZero(components.B!, kFloat10Format), + }), + numericRange: { min: 0, max: Number.POSITIVE_INFINITY }, + }, + rgb9e5ufloat: { + componentOrder: kRGB, + componentInfo: makePerTexelComponent(kRGB, { + dataType: 'ufloat', + bitLength: -1, // Components don't really have a bitLength since the format is packed. + }), + encode: applyEach(identity, kRGB), + decode: applyEach(identity, kRGB), + pack: components => + new Uint32Array([ + packRGB9E5UFloat( + components.R ?? unreachable(), + components.G ?? unreachable(), + components.B ?? unreachable() + ), + ]).buffer, + // For the purpose of unpacking, expand into three "ufloat14" values. + unpackBits: (data: Uint8Array) => { + // Pretend the exponent part is A so we can use unpackComponentsBits. + const parts = unpackComponentsBits(kRGBA, data, { R: 9, G: 9, B: 9, A: 5 }); + return { + R: (parts.A! << 9) | parts.R!, + G: (parts.A! << 9) | parts.G!, + B: (parts.A! << 9) | parts.B!, + }; + }, + numberToBits: components => ({ + R: float32ToFloatBits(components.R ?? unreachable(), 0, 5, 9, 15), + G: float32ToFloatBits(components.G ?? unreachable(), 0, 5, 9, 15), + B: float32ToFloatBits(components.B ?? unreachable(), 0, 5, 9, 15), + }), + bitsToNumber: components => ({ + R: floatBitsToNumber(components.R!, kFloat9e5Format), + G: floatBitsToNumber(components.G!, kFloat9e5Format), + B: floatBitsToNumber(components.B!, kFloat9e5Format), + }), + bitsToULPFromZero: components => ({ + R: floatBitsToNormalULPFromZero(components.R!, kFloat9e5Format), + G: floatBitsToNormalULPFromZero(components.G!, kFloat9e5Format), + B: floatBitsToNormalULPFromZero(components.B!, kFloat9e5Format), + }), + numericRange: { min: 0, max: Number.POSITIVE_INFINITY }, + }, + depth32float: makeFloatInfo([TexelComponent.Depth], 32, { restrictedDepth: true }), + depth16unorm: makeNormalizedInfo([TexelComponent.Depth], 16, { signed: false, sRGB: false }), + depth24plus: { + componentOrder: [TexelComponent.Depth], + componentInfo: { Depth: { dataType: null, bitLength: 24 } }, + encode: applyEach(() => unreachable('depth24plus cannot be encoded'), [TexelComponent.Depth]), + decode: applyEach(() => unreachable('depth24plus cannot be decoded'), [TexelComponent.Depth]), + pack: () => unreachable('depth24plus data cannot be packed'), + unpackBits: () => unreachable('depth24plus data cannot be unpacked'), + numberToBits: () => unreachable('depth24plus has no representation'), + bitsToNumber: () => unreachable('depth24plus has no representation'), + bitsToULPFromZero: () => unreachable('depth24plus has no representation'), + numericRange: { min: 0, max: 1 }, + }, + stencil8: makeIntegerInfo([TexelComponent.Stencil], 8, { signed: false }), + 'depth32float-stencil8': { + componentOrder: [TexelComponent.Depth, TexelComponent.Stencil], + componentInfo: { + Depth: { + dataType: 'float', + bitLength: 32, + }, + Stencil: { + dataType: 'uint', + bitLength: 8, + }, + }, + encode: components => { + assert(components.Stencil !== undefined); + assertInIntegerRange(components.Stencil, 8, false); + return components; + }, + decode: components => { + assert(components.Stencil !== undefined); + assertInIntegerRange(components.Stencil, 8, false); + return components; + }, + pack: () => unreachable('depth32float-stencil8 data cannot be packed'), + unpackBits: () => unreachable('depth32float-stencil8 data cannot be unpacked'), + numberToBits: () => unreachable('not implemented'), + bitsToNumber: () => unreachable('not implemented'), + bitsToULPFromZero: () => unreachable('not implemented'), + numericRange: null, + }, + 'depth24plus-stencil8': { + componentOrder: [TexelComponent.Depth, TexelComponent.Stencil], + componentInfo: { + Depth: { + dataType: null, + bitLength: 24, + }, + Stencil: { + dataType: 'uint', + bitLength: 8, + }, + }, + encode: components => { + assert(components.Depth === undefined, 'depth24plus cannot be encoded'); + assert(components.Stencil !== undefined); + assertInIntegerRange(components.Stencil, 8, false); + return components; + }, + decode: components => { + assert(components.Depth === undefined, 'depth24plus cannot be decoded'); + assert(components.Stencil !== undefined); + assertInIntegerRange(components.Stencil, 8, false); + return components; + }, + pack: () => unreachable('depth24plus-stencil8 data cannot be packed'), + unpackBits: () => unreachable('depth24plus-stencil8 data cannot be unpacked'), + numberToBits: () => unreachable('depth24plus-stencil8 has no representation'), + bitsToNumber: () => unreachable('depth24plus-stencil8 has no representation'), + bitsToULPFromZero: () => unreachable('depth24plus-stencil8 has no representation'), + numericRange: null, + }, + }, +}; + +/** + * Get the `ComponentDataType` for a format. All components must have the same type. + * @param {UncompressedTextureFormat} format - The input format. + * @returns {ComponentDataType} The data of the components. + */ +export function getSingleDataType(format: UncompressedTextureFormat): ComponentDataType { + const infos = Object.values(kTexelRepresentationInfo[format].componentInfo); + assert(infos.length > 0); + return infos.reduce((acc, cur) => { + assert(cur !== undefined); + assert(acc === undefined || acc === cur.dataType); + return cur.dataType; + }, infos[0]!.dataType); +} + +/** + * Get traits for generating code to readback data from a component. + * @param {ComponentDataType} dataType - The input component data type. + * @returns A dictionary containing the respective `ReadbackTypedArray` and `shaderType`. + */ +export function getComponentReadbackTraits(dataType: ComponentDataType) { + switch (dataType) { + case 'ufloat': + case 'float': + case 'unorm': + case 'snorm': + return { + ReadbackTypedArray: Float32Array, + shaderType: 'f32' as const, + }; + case 'uint': + return { + ReadbackTypedArray: Uint32Array, + shaderType: 'u32' as const, + }; + case 'sint': + return { + ReadbackTypedArray: Int32Array, + shaderType: 'i32' as const, + }; + default: + unreachable(); + } +} |