summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
diff options
context:
space:
mode:
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.ts918
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();
+ }
+}