function packComponents()

in src/webgpu/util/texture/texel_data.ts [167:281]


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;
}