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

    Proposal details
    Proposal overview

    This proposal addresses the difficulty of accessing interleaved data in TypedArrays by adding a stride parameter to ArrayBuffer view constructors, allowing views to skip elements. It provides a new API shape new Float32Array(buffer, byteOffset, length, stride) with a default stride of 1, enabling separate access to color channels or vertex attributes without manual indexing.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    ArrayBufferView stride parameter

    Author: Shu-yu Guo, Surma

    Champion: Shu-yu Guo

    Stage: Stage 1 of the TC39 process.

    Motivation

    ArrayBuffer views (like Uint8Array, Float32Array, etc) provide views onto ArrayBuffers. These views allow the same chunk of memory to be interpreted as different kinds of data and allow developers to manipulate binary data in-place.

    All constructors to a view conform to the this API shape:

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

    *) All concrete references to Float32Array are a placeholder for any of the ArrayBuffer views.

    The use-cases for ArrayBuffers on the platform include, but are not limited to, WebGLBuffers and ImageDatas.

    ImageData Example

    ImageData is a Uint8ClampedArray representing an image as a one-dimensional array containing the data in the RGBA order, with integer values between 0 and 255.

    // ctx is a CanvasRenderingContext2D
    const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    // imageData.data is a Uint8ClampedArray containing data in the scheme
    // [ r0, g0, b0, a0, r1, g1, b1, a1, ... ]

    Accessing one color channel at a time is tedious due to the interleaved representation.

    WebGL Example

    In the WebGL, vertices can be assigned mutiple sets of data. The obvious ones are 3-dimensional vectors like position, normals or color, but can also be extended to texture IDs and other auxilliary data. Each of these components is called a vertex attribute and is assigned a unique ID. Using this ID you can access the data on a per-vertex basis in shaders.

    You could create a buffer for each of these components and upload them with to the GPU with multiple calls. Alternatively (and arguably more commonly), you create one big buffer so you can update all the data at once. To tell WebGL how to interpret a buffer you can use vertexAttribPointer, which has a stride parameter:

    // prettier-ignore
    const arrayBuffer = new Float32Array([
       0,  0,  0, // Position for vertex 0
       0,  1,  0, // Normal for vertex 0
       1,  0,  0, // Color for vertex 0
    
      -1,  0, -1, // Position for vertex 1
       0,  1,  0, // Normal for vertex 1
       0,  1,  0, // Color for vertex 1
      // ...
    ])
    const vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW);
    gl.vertexAttribPointer(
      0, // Vertex Attribute 0 for position
      3, // 3 dimensional
      gl.FLOAT, // Type (could also be gl.BYTE, gl.SHORT, etc)
      false, // Should the GPU normalize the data?
      9 * Float32Array.BYTES_PER_ELEMENT, // Stride
      0 // Offset
    );
    gl.vertexAttribPointer(
      1, // Vertex Attribute 1 for normals
      3,
      gl.FLOAT,
      false,
      9 * Float32Array.BYTES_PER_ELEMENT,
      3 * Float32Array.BYTES_PER_ELEMENT
    );
    // ...

    Manipulating all the vertex attributes for a given vertex is again tedious due to the interleaved data representation.

    Proposed solution

    The ArrayBuffer view constructors for creating a view onto an existing buffer already accept offset and length.

    new Float32Array(buffer, byteOffset, length);

    This allows developers to work on subset of ArrayBuffers with a specific view type.

    The proposal is to add an additional parameter for stride to the constructor.

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

    The default value and lower bound for stride is 1, and it is expressed as a multiple of elements.

    High-level API

    // prettier-ignore
    const { buffer } = new Float32Array([
        0, 10, 20,
        1, 11, 21,
        2, 12, 22
    ]);
    const view1 = new Float32Array(
      buffer, // buffer
      0 * Float32Array.BYTES_PER_ELEMENT, // offset
      3, // length
      3 // stride (new!)
    );
    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];

    Feature detection

    To detect support for stride, developers can check if an instance of any ArrayBuffer view exposes a stride property:

    if ("stride" in new Float32Array()) {
      // `stride` is supported
    } else {
      // `stride` is not supported
    }

    Example

    As an example, this would allow developers to separately access the individual color channels of a ImageData image as continuous arrays:

    // ctx is a 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 & open questions

    Is an options object is better than yet another parameter?

    I think an options object is preferable, but requires a more invasive change to the spec. The benefit is that it would allow developers to define a stride without having to explicitly define an offset or a length, both of which already have well-defined default values.

    For example:

    const view1 = new Float32Array(buffer, {
      offset: 0 * Float32Array.BYTES_PER_ELEMENT,
      length: 6,
      stride: 3
    });
    Supporting unaligned offsets

    Currently TypedArrays are always aligned, as passing a byte offset that results in unaligned access throws. At the cost of expressivity, guaranteeing alignment by construction is advantageous for several reasons:

    • Faster performance
    • Easier machine code generation
    • Easier to use on top of SharedArrayBuffers, especially with Atomics

    In light of this, we are not proposing to relax the alignment requirements for integer TypedArrays.

    This is perhaps more of an open question for for Float32Array and Float64Array.

    TC39 meeting notes

    TBD

    Specification

    TBD

    Implementations

    Polyfill

    There is work-in-progress polyfill in polyfill.js using ES6 Proxies. It is not written with performance in mind but for ease of iteration.

    Browsers

    None