For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-typedarray-stride.md.
  • 简体中文
  • TypedArray stride parameter ?

    中文标题:TypedArray 步长参数

    提案概览
    提案速览

    该提案通过向 ArrayBuffer 视图构造函数添加 stride 参数,解决了 TypedArrays 中交错数据访问困难的问题,允许视图跳过元素。它提供了新的 API 形式 new Float32Array(buffer, byteOffset, length, stride),默认步长为 1,从而无需手动索引即可单独访问颜色通道或顶点属性。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ArrayBufferView 步长参数

    作者:Shu-yu Guo, Surma

    提案发起人:Shu-yu Guo

    阶段TC39 流程中的第 1 阶段。

    动机

    ArrayBuffer 视图(如 Uint8ArrayFloat32Array 等)提供了对 ArrayBuffer 的视图。这些视图允许同一块内存被解释为不同类型的数据,并允许开发者就地操作二进制数据。

    所有视图的构造函数都遵循以下 API 形式:

    new Float32Array(length);
    new Float32Array(typedArray);
    new Float32Array(object);
    new Float32Array(buffer [, byteOffset [, length]]);

    *) 所有具体引用 Float32Array 的地方都是任意 ArrayBuffer 视图 的占位符。

    平台上 ArrayBuffer 的使用场景包括但不限于 WebGLBufferImageData

    ImageData 示例

    ImageData 是一个 Uint8ClampedArray,表示一张图像作为一维数组,包含 RGBA 顺序的数据,整数取值在 0 到 255 之间。

    // ctx 是一个 CanvasRenderingContext2D
    const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    // imageData.data 是一个 Uint8ClampedArray,数据按照以下模式:
    // [ r0, g0, b0, a0, r1, g1, b1, a1, ... ]

    由于交错表示,一次访问一个颜色通道很繁琐。

    WebGL 示例

    在 WebGL 中,顶点可以被分配多组数据。显而易见的是三维向量,比如位置、法线或颜色,但也可以扩展到纹理 ID 和其他辅助数据。这些组件中的每一个都称为顶点属性,并且被分配一个唯一的 ID。使用这个 ID,你可以在着色器中按顶点访问数据。

    你可以为每个组件创建缓冲区,并通过多次调用将它们上传到 GPU。或者(也可以说更常见的是),创建一个大的缓冲区,这样你就可以一次更新所有数据。要告诉 WebGL 如何解释缓冲区,可以使用 vertexAttribPointer,它有一个 stride 参数:

    // prettier-ignore
    const arrayBuffer = new Float32Array([
       0,  0,  0, // 顶点 0 的位置
       0,  1,  0, // 顶点 0 的法线
       1,  0,  0, // 顶点 0 的颜色
    
      -1,  0, -1, // 顶点 1 的位置
       0,  1,  0, // 顶点 1 的法线
       0,  1,  0, // 顶点 1 的颜色
      // ...
    ])
    const vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW);
    gl.vertexAttribPointer(
      0, // 顶点属性 0 用于位置
      3, // 三维
      gl.FLOAT, // 类型(也可以是 gl.BYTE、gl.SHORT 等)
      false, // GPU 是否应该归一化数据?
      9 * Float32Array.BYTES_PER_ELEMENT, // 步长
      0 // 偏移量
    );
    gl.vertexAttribPointer(
      1, // 顶点属性 1 用于法线
      3,
      gl.FLOAT,
      false,
      9 * Float32Array.BYTES_PER_ELEMENT,
      3 * Float32Array.BYTES_PER_ELEMENT
    );
    // ...

    由于交错的数据表示,操作给定顶点的所有顶点属性再次变得繁琐。

    提议的解决方案

    ArrayBuffer 视图构造函数用于在现有缓冲区上创建视图,已经接受 offsetlength

    new Float32Array(buffer, byteOffset, length);

    这允许开发者使用特定的视图类型处理 ArrayBuffer 的子集。

    该提案是向构造函数添加额外的 stride 参数。

    new Float32Array(buffer[, byteOffset[, length[, stride]]]);

    stride 的默认值和下限是 1,并以元素数量的倍数表示。

    高层 API

    // prettier-ignore
    const { buffer } = new Float32Array([
        0, 10, 20,
        1, 11, 21,
        2, 12, 22
    ]);
    const view1 = new Float32Array(
      buffer, // 缓冲区
      0 * Float32Array.BYTES_PER_ELEMENT, // 偏移量
      3, // 长度
      3 // 步长(新增!)
    );
    const view2 = new Float32Array(
      buffer,
      1 * Float32Array.BYTES_PER_ELEMENT,
      3,
      3
    );
    const view3 = new Float32Array(
      buffer,
      2 * Float32Array.BYTES_PER_ELEMENT,
      3,
      3
    );
    
    // view1 == [0, 1, 2];
    // view2 == [10, 11, 12];
    // view2 == [20, 21, 22];

    特性检测

    为了检测 stride 的支持,开发者可以检查任意 ArrayBuffer 视图实例是否暴露 stride 属性:

    if ("stride" in new Float32Array()) {
      // 支持 `stride`
    } else {
      // 不支持 `stride`
    }

    示例

    作为一个例子,这将允许开发者单独访问 ImageData 图像的各个颜色通道作为连续数组:

    // ctx 是一个 CanvasRenderingContext2D
    const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    const redChannel = new Uint8ClampedArray(
      imageData.data.buffer,
      0 * Uint8ClampedArray.BYTES_PER_ELEMNT,
      imageData.width * imageData.height,
      4
    );
    // ...
    const alphaChannel = new Uint8ClampedArray(
      imageData.data.buffer,
      3 * Uint8ClampedArray.BYTES_PER_ELEMNT,
      imageData.width * imageData.height,
      4
    );

    FAQ 和开放问题

    选项对象是否比另一个参数更好?

    我认为选项对象 更可取的,但需要对规范进行更大规模的更改。好处是它将允许开发者定义 stride,而不必显式定义 offsetlength,这两个参数都已经有明确定义的默认值。

    例如:

    const view1 = new Float32Array(buffer, {
      offset: 0 * Float32Array.BYTES_PER_ELEMENT,
      length: 6,
      stride: 3
    });
    支持未对齐的偏移量

    目前 TypedArrays 总是对齐的,因为传递导致未对齐访问的字节偏移量会抛出异常。以表达性为代价,通过构造保证对齐有几个优点:

    • 更快的性能
    • 更容易生成机器代码
    • 更容易在 SharedArrayBuffers 上使用,尤其是与 Atomics 一起使用时

    鉴于这种情况,我们不提议放宽整数 TypedArrays 的对齐要求。

    这也许对于 Float32Array 和 Float64Array 来说更像是一个开放问题。

    TC39 会议记录

    待定

    规范

    待定

    实现

    Polyfill

    polyfill.js 中有一个_进行中_的 polyfill,使用 ES6 代理。它不是为性能而写的,而是为了便于迭代。

    浏览器