Skip to content

WebGPURenderer: pipeline not rebuilt after swapping to a geometry with a different vertex layout (StorageBufferAttribute -> BufferAttribute) #34722

Description

@PTRKMRTN

Description

With WebGPURenderer, swapping a mesh's geometry can leave the render object drawing with a pipeline built for a previous geometry's vertex layout. The draw then fails WebGPU validation and nothing is drawn.

It happens after two swaps: a geometry whose attributes are StorageBufferAttributes (which the WebGPU backend pads from itemSize 3 to 4, a 16-byte stride), then another such geometry, then an ordinary BufferAttribute geometry (12-byte stride). The third draw binds the new 12-byte buffer to the 16-byte-stride pipeline:

Vertex range (first: 0, count: 6) requires a larger buffer (96) than the bound buffer size (72) of the vertex buffer at slot 0 with stride 16.

A single swap (storage → plain, or plain → storage) does not show it. Replacing the mesh object instead of swapping its geometry avoids it.

Reproduction steps

  1. Draw a mesh whose geometry uses StorageBufferAttribute for position.
  2. Set mesh.geometry to a second geometry that also uses StorageBufferAttribute, and draw.
  3. Set mesh.geometry to an ordinary geometry (BufferAttribute), and draw.

Code

import * as THREE from 'three/webgpu';

const renderer = new THREE.WebGPURenderer();
await renderer.init();
const device = renderer.backend.device;
device.addEventListener( 'uncapturederror', ( e ) => console.error( e.error.message ) );

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 50, 1, 0.1, 10 );
camera.position.z = 3;
const target = new THREE.RenderTarget( 64, 64 );

const tri = [ - 1, - 1, 0, 1, - 1, 0, 0, 1, 0 ];
function makeGeometry( Attribute, triangles ) {

	const g = new THREE.BufferGeometry();
	const p = [];
	for ( let i = 0; i < triangles; i ++ ) p.push( ...tri );
	g.setAttribute( 'position', new Attribute( new Float32Array( p ), 3 ) );
	return g;

}

const mesh = new THREE.Mesh( makeGeometry( THREE.StorageBufferAttribute, 1 ), new THREE.MeshBasicMaterial() );
scene.add( mesh );

async function draw() {

	renderer.setRenderTarget( target );
	renderer.render( scene, camera );
	renderer.setRenderTarget( null );
	await device.queue.onSubmittedWorkDone();

}

await draw(); // fine
mesh.geometry = makeGeometry( THREE.StorageBufferAttribute, 3 );
await draw(); // fine
mesh.geometry = makeGeometry( THREE.BufferAttribute, 2 );
await draw(); // validation error: 12-byte buffer bound to the 16-byte-stride pipeline

The same happens with InstancedMesh and with MeshStandardMaterial.

Live example

The code above; no hosted example.

Screenshots

No response

Version

r186.1 (three@0.186.1)

Device

Desktop

Browser

Chrome

OS

Windows

Notes

Chrome 152 (and Edge), Windows 11, NVIDIA RTX 4070. From reading RenderObject / RenderObjects: setGeometry() runs when needsGeometryUpdate is true, but the pipeline is only rebuilt inside the renderObject.version !== material.version || renderObject.needsUpdate branch, and a geometry swap alone doesn't seem to reach it, even though getGeometryCacheKey() includes itemSize. That part is a guess.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions