1.10 Triangle Strips

Up until now, we've exclusively used triangle lists as our vertex topology. However, triangle lists are not the only way to organize vertices. The GPU pipeline provides other options that are advantageous in terms of data size efficiency. GPU memory is precious, and data transfer between CPU and GPU memory can be slow, so optimizing the amount of data we send is crucial.

Launch Playground - 1_10_triangle_strips

While triangle lists are straightforward, they suffer from redundant vertices. Shared vertices are sent multiple times, unnecessarily inflating data size. But there's a more efficient way—triangle strips.

By using the triangle-strip topology, we can avoid sending repetitive vertices to the GPU, thus conserving bandwidth and computational resources.

Triangle Strip Topology Used in the Sample
Triangle Strip Topology Used in the Sample

Switching the topology doesn't require changes to the shader code. Because, at the shader level, we deal with individual vertices and remain agnostic to how these vertices are organized or connected into triangles.

const positions = new Float32Array([
    100.0, -100.0, 0.0,
    100.0, 100.0, 0.0,
    -100.0, -100.0, 0.0,
    -100.0, 100.0, 0.0
]);

let positionBuffer = createGPUBuffer(device, positions, GPUBufferUsage.VERTEX);

const texCoords = new Float32Array([
    1.0,
    0.0,
    // 🔴

    1.0,
    1.0,

    0.0,
    0.0,

    0.0,
    1.0
]);

let texCoordsBuffer = createGPUBuffer(device, texCoords, GPUBufferUsage.VERTEX);

To make the transition, the JavaScript code where vertex data is generated has to be updated. Previously, we need 6 vertices to form a rectangle, as a rectangle can be divided into two triangles, each consisting of 3 vertices, with two of them shared. Now, with a triangle-strip, we only need four points since we don't duplicate those shared vertices. We have to ensure these vertices are ordered in a zig-zag or triangle-strip pattern. Similar adjustments should be applied to texture coordinates, saving two coordinates.

primitive: {
    topology: 'triangle-strip',
    frontFace: 'ccw',
    cullMode: 'none'
}

When defining the pipeline descriptor, specify triangle-strip as the topology in the primitive section.

passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
passEncoder.setViewport(0, 0, canvas.width, canvas.height, 0, 1);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, uniformBindGroup);
passEncoder.setVertexBuffer(0, positionBuffer);
passEncoder.setVertexBuffer(1, texCoordsBuffer);
passEncoder.draw(4, 1);
passEncoder.end();

Finally, when creating the command buffer, update the number of vertices to be drawn. Even though we're drawing only four vertices this time, the end result remains a rectangle.

This concludes the changes needed to switch primitive topology. Although triangle-strip offer data size optimization, they require adherence to specific mesh patterns. Given it is not alway trivial how to partition a complex mesh into multiple triangle-strips, triangle-list is still the most generic option. In a future section, we'll explore a more universal approach to data optimization using the index buffer, reducing the need to conform to topology patterns.

WebGPU offers three additional topologies that I haven't yet discussed: point-list, line-list, and line-strip. These topologies are specifically used for rendering individual points and lines, rather than triangles. Unlike the previously mentioned triangle-based topologies, these do not define any triangular connectivity between vertices.

Leave a Comment on Github