HELP: How to properly and effectively destroy Sprite objects when using WebGPURenderer?

As the title indicates,
using the traditional method to destroy the material and geometry objects does not properly remove them, and an error will be logged in the console:

[Buffer (unlabeled)] used in submit while destroyed.

While calling [Queue].Submit([[CommandBuffer from CommandEncoder “renderContext_0”]])

Reproduction Steps:

  1. Use the webgpu_sprites example.
  2. Bind the group to the window, window.group = group.
  3. Destroy any Sprite object.
  4. Observe the view and console.

The complete code is as follows:

import * as THREE from 'three';
import { texture, uv, userData, fog, rangeFogFactor, color } from 'three/tsl';

const raycaster = new THREE.Raycaster();

const pointer = new THREE.Vector2(Infinity, Infinity);

let camera, scene, renderer;

let map;

let group;

let imageWidth = 1, imageHeight = 1;

init();

function init() {

	const width = window.innerWidth;
	const height = window.innerHeight;

	camera = new THREE.PerspectiveCamera(60, width / height, 1, 2100);
	camera.position.z = 1500;

	scene = new THREE.Scene();
	scene.fogNode = fog(color(0x0000ff), rangeFogFactor(1500, 2100));

	// create sprites

	const amount = 200;
	const radius = 500;

	const textureLoader = new THREE.TextureLoader();

	map = textureLoader.load('textures/sprite1.png', (map) => {

		imageWidth = map.image.width;
		imageHeight = map.image.height;

	});

	group = new THREE.Group();
	window.group = group;

	const textureNode = texture(map);

	const material = new THREE.SpriteNodeMaterial();
	material.colorNode = textureNode.mul(uv()).mul(2).saturate();
	material.opacityNode = textureNode.a;
	material.rotationNode = userData('rotation', 'float'); // get value of: sprite.userData.rotation

	for (let a = 0; a < amount; a++) {

		const x = Math.random() - 0.5;
		const y = Math.random() - 0.5;
		const z = Math.random() - 0.5;

		const sprite = new THREE.Sprite(material);

		sprite.position.set(x, y, z);
		sprite.position.normalize();
		sprite.position.multiplyScalar(radius);

		// individual rotation per sprite
		sprite.userData.rotation = 0;

		group.add(sprite);

	}

	scene.add(group);

	//

	renderer = new THREE.WebGPURenderer({ antialias: true });
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.setAnimationLoop(render);
	document.body.appendChild(renderer.domElement);


	//

	document.addEventListener('click', onPointerMove);
	window.addEventListener('resize', onWindowResize);

}

function onWindowResize() {

	const width = window.innerWidth;
	const height = window.innerHeight;

	camera.aspect = width / height;
	camera.updateProjectionMatrix();


	renderer.setSize(window.innerWidth, window.innerHeight);

}

function onPointerMove(event) {

	pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
	pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;

	raycaster.setFromCamera(pointer, camera);

	const intersects = raycaster.intersectObject(group);

	if (intersects.length > 0) {
		console.log('intersects: ', intersects)
		intersects.forEach(s => {
			s.object.position.set(100, 100, 100)
			// 	if (s.object.material) {
			// 		s.object.material.color = new THREE.Color(0xff0000)
			// 		s.object.material.needsUpdate = true;
			// 	}
		})
	}

}

function render() {


	const time = 1000;

	// for (let i = 0, l = group.children.length; i < l; i++) {

	// 	const sprite = group.children[i];
	// 	// const scale = Math.sin( time + sprite.position.x * 0.01 ) * 0.3 + 1.0;
	// 	const scale = 1

	// 	// sprite.userData.rotation += 0.1 * ( i / l );
	// 	sprite.scale.set(scale * imageWidth, scale * imageHeight, 1.0);

	// }

	// group.rotation.x = time * 0.5;
	// group.rotation.y = time * 0.75;
	// group.rotation.z = time * 1.0;

	renderer.render(scene, camera);

}

After successful rendering, execute the following in the console:

group.children.forEach(item => {
    item.material.dispose();
    item.geometry.dispose();
})

item.geometry.dispose();

It’s this line. The geometry is shared for all sprites and internal so you are not supposed to call dispose() on it.

In general, call dispose() on objects that you create on app level by yourself (like the material).