Bladeren bron

WebGPURenderer: Initial commit.

Mugen87 4 jaren geleden
bovenliggende
commit
8f4ae892e9

+ 3 - 0
examples/files.js

@@ -321,6 +321,9 @@ var files = {
 		"webgl2_volume_instancing",
 		"webgl2_volume_perlin"
 	],
+	"webgpu": [
+		"webgpu_sandbox"
+	],
 	"webaudio": [
 		"webaudio_orientation",
 		"webaudio_sandbox",

+ 108 - 0
examples/jsm/renderers/webgpu/WebGPUAttributes.js

@@ -0,0 +1,108 @@
+class WebGPUAttributes {
+
+	constructor( device ) {
+
+		this.buffers = new WeakMap();
+		this.device = device;
+
+	}
+
+	get( attribute ) {
+
+		return this.buffers.get( attribute );
+
+	}
+
+	remove( attribute ) {
+
+		const data = this.buffers.get( attribute );
+
+		if ( data ) {
+
+			data.buffer.destroy();
+
+			this.buffers.delete( attribute );
+
+		}
+
+	}
+
+	update( attribute, isIndex = false ) {
+
+		let data = this.buffers.get( attribute );
+
+		if ( data === undefined ) {
+
+			const usage = ( isIndex === true ) ? GPUBufferUsage.INDEX : GPUBufferUsage.VERTEX;
+
+			data = this._createBuffer( attribute, usage );
+
+			this.buffers.set( attribute, data );
+
+		} else if ( data.version < attribute.version ) {
+
+			this._writeBuffer( data.buffer, attribute );
+
+			data.version = attribute.version;
+
+		}
+
+	}
+
+	_createBuffer( attribute, usage ) {
+
+		const array = attribute.array;
+		const size = array.byteLength + ( array.byteLength % 4 ); // ensure 4 byte alignment
+
+		const buffer = this.device.createBuffer( {
+			size: size,
+			usage: usage | GPUBufferUsage.COPY_DST,
+			mappedAtCreation: true,
+		} );
+
+		new array.constructor( buffer.getMappedRange() ).set( array );
+
+		buffer.unmap();
+
+		return {
+			version: 0,
+			buffer: buffer
+		};
+
+	}
+
+	_writeBuffer( buffer, attribute ) {
+
+		const array = attribute.array;
+		const updateRange = attribute.updateRange;
+
+		if ( updateRange.count === - 1 ) {
+
+			// Not using update ranges
+
+			this.device.defaultQueue.writeBuffer(
+				buffer,
+				0,
+				array,
+				0
+			);
+
+		} else {
+
+			this.device.defaultQueue.writeBuffer(
+				buffer,
+				0,
+				array,
+				updateRange.offset * array.BYTES_PER_ELEMENT,
+				updateRange.count * array.BYTES_PER_ELEMENT
+			);
+
+			updateRange.count = - 1; // reset range
+
+		}
+
+	}
+
+}
+
+export default WebGPUAttributes;

+ 13 - 0
examples/jsm/renderers/webgpu/WebGPUBinding.js

@@ -0,0 +1,13 @@
+class WebGPUBinding {
+
+	constructor() {
+
+		this.bindingPoint = 0;
+		this.type = null;
+		this.visibility = null;
+
+	}
+
+}
+
+export default WebGPUBinding;

+ 355 - 0
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -0,0 +1,355 @@
+import WebGPUUniformsGroup from './WebGPUUniformsGroup.js';
+import WebGPUSampler from './WebGPUSampler.js';
+import WebGPUSampledTexture from './WebGPUSampledTexture.js';
+import { Matrix4 } from '../../../../build/three.module.js';
+
+class WebGPUBindings {
+
+	constructor( device, info, properties, textures ) {
+
+		this.device = device;
+		this.info = info;
+		this.properties = properties;
+		this.textures = textures;
+
+		this.uniformsData = new WeakMap();
+
+		this.sharedUniformsGroups = new Map();
+
+		this.updateMap = new WeakMap();
+
+		this._setupSharedUniformsGroups();
+
+	}
+
+	get( object ) {
+
+		let data = this.uniformsData.get( object );
+
+		if ( data === undefined ) {
+
+			const material = object.material;
+			let bindings;
+
+			// each material defines an array of bindings (ubos, textures, samplers etc.)
+
+			if ( material.isMeshBasicMaterial ) {
+
+				bindings = this._getMeshBasicBindings();
+
+			} else if ( material.isPointsMaterial ) {
+
+				bindings = this._getPointsBasicBindings();
+
+			} else if ( material.isLineBasicMaterial ) {
+
+				bindings = this._getLinesBasicBindings();
+
+			} else {
+
+				console.error( 'WebGPURenderer: Unknwon shader type' );
+
+			}
+
+			// setup (static) binding layout and (dynamic) binding group
+
+			const bindLayout = this._createBindLayout( bindings );
+			const bindGroup = this._createBindGroup( bindings, bindLayout );
+
+			data = {
+				layout: bindLayout,
+				group: bindGroup,
+				bindings: bindings
+			};
+
+			this.uniformsData.set( object, data );
+
+		}
+
+		return data;
+
+	}
+
+	update( object, camera ) {
+
+		const data = this.get( object );
+		const bindings = data.bindings;
+
+		const updateMap = this.updateMap;
+		const frame = this.info.render.frame;
+		const sharedUniformsGroups = this.sharedUniformsGroups;
+
+		let needsBindGroupRefresh = false;
+
+		// iterate over all bindings and check if buffer updates or a new binding group is required
+
+		for ( const binding of bindings ) {
+
+			if ( binding.isUniformsGroup ) {
+
+				const isShared = sharedUniformsGroups.has( binding.name );
+				const isUpdated = updateMap.get( binding ) === frame;
+
+				if ( isShared && isUpdated ) continue;
+
+				const array = binding.array;
+				const bufferGPU = binding.bufferGPU;
+
+				const needsBufferWrite = binding.update( array, object, camera );
+
+				if ( needsBufferWrite === true ) {
+
+					this.device.defaultQueue.writeBuffer(
+						bufferGPU,
+						0,
+						array,
+						0
+					);
+
+				}
+
+				updateMap.set( binding, frame );
+
+			} else if ( binding.isSampler ) {
+
+				const material = object.material;
+				const texture = material[ binding.name ];
+
+				needsBindGroupRefresh = this.textures.updateSampler( texture );
+
+				if ( needsBindGroupRefresh === true ) {
+
+					binding.samplerGPU = this.textures.getSampler( texture );
+
+				}
+
+			} else if ( binding.isSampledTexture ) {
+
+				const material = object.material;
+				const texture = material[ binding.name ];
+
+				needsBindGroupRefresh = this.textures.updateTexture( texture );
+
+				if ( needsBindGroupRefresh === true ) {
+
+					binding.textureGPU = this.textures.getTextureGPU( texture );
+
+				}
+
+			}
+
+		}
+
+		if ( needsBindGroupRefresh === true ) {
+
+			data.group = this._createBindGroup( bindings, data.layout );
+
+		}
+
+	}
+
+	dispose() {
+
+		this.uniformsData = new WeakMap();
+		this.updateMap = new WeakMap();
+
+	}
+
+	_createBindLayout( bindings ) {
+
+		let bindingPoint = 0;
+		const entries = [];
+
+		for ( const binding of bindings ) {
+
+			entries.push( { binding: bindingPoint, visibility: binding.visibility, type: binding.type } );
+
+			bindingPoint ++;
+
+		}
+
+		return this.device.createBindGroupLayout( { entries: entries } );
+
+	}
+
+	_createBindGroup( bindings, layout ) {
+
+		let bindingPoint = 0;
+		const entries = [];
+
+		for ( const binding of bindings ) {
+
+			if ( binding.isUniformsGroup ) {
+
+				if ( binding.bufferGPU === null ) {
+
+					const byteLength = binding.getByteLength();
+
+					binding.array = new Float32Array( new ArrayBuffer( byteLength ) );
+
+					binding.bufferGPU = this.device.createBuffer( {
+						size: byteLength,
+						usage: binding.usage,
+					} );
+
+				}
+
+				entries.push( { binding: bindingPoint, resource: { buffer: binding.bufferGPU } } );
+
+			} else if ( binding.isSampler ) {
+
+				if ( binding.samplerGPU === null ) {
+
+					binding.samplerGPU = this.textures.getDefaultSampler();
+
+				}
+
+				entries.push( { binding: bindingPoint, resource: binding.samplerGPU } );
+
+			} else if ( binding.isSampledTexture ) {
+
+				if ( binding.textureGPU === null ) {
+
+					binding.textureGPU = this.textures.getDefaultTexture();
+
+				}
+
+				entries.push( { binding: bindingPoint, resource: binding.textureGPU.createView() } );
+
+			}
+
+			bindingPoint ++;
+
+		}
+
+		return this.device.createBindGroup( {
+			layout: layout,
+			entries: entries
+		} );
+
+	}
+
+	_getMeshBasicBindings() {
+
+		const bindings = [];
+
+		// ubos
+
+		const modelGroup = new WebGPUUniformsGroup();
+		modelGroup.setName( 'modelUniforms' );
+		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
+		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
+		modelGroup.setUpdateCallback( function ( array, object/*, camera */ ) {
+
+			array.set( object.matrixWorld.elements, 0 );
+			array.set( object.modelViewMatrix.elements, 16 );
+
+			return true; // TODO: implement caching (return false when cache hits occurs)
+
+		} );
+
+		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
+
+		// samplers
+
+		const diffuseSampler = new WebGPUSampler();
+		diffuseSampler.setName( 'map' );
+
+		// textures
+
+		const diffuseTexture = new WebGPUSampledTexture();
+		diffuseTexture.setName( 'map' );
+
+		//
+
+		bindings.push( modelGroup );
+		bindings.push( cameraGroup );
+		bindings.push( diffuseSampler );
+		bindings.push( diffuseTexture );
+
+		return bindings;
+
+	}
+
+	_getPointsBasicBindings() {
+
+		const bindings = [];
+
+		// ubos
+
+		const modelGroup = new WebGPUUniformsGroup();
+		modelGroup.setName( 'modelUniforms' );
+		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
+		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
+		modelGroup.setUpdateCallback( function ( array, object/*, camera */ ) {
+
+			array.set( object.matrixWorld.elements, 0 );
+			array.set( object.modelViewMatrix.elements, 16 );
+
+			return true; // TODO: implement caching (return false when cache hits occurs)
+
+		} );
+
+		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
+
+		//
+
+		bindings.push( modelGroup );
+		bindings.push( cameraGroup );
+
+		return bindings;
+
+	}
+
+	_getLinesBasicBindings() {
+
+		const bindings = [];
+
+		// ubos
+
+		const modelGroup = new WebGPUUniformsGroup();
+		modelGroup.setName( 'modelUniforms' );
+		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
+		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
+		modelGroup.setUpdateCallback( function ( array, object/*, camera */ ) {
+
+			array.set( object.matrixWorld.elements, 0 );
+			array.set( object.modelViewMatrix.elements, 16 );
+
+			return true; // TODO: implement caching (return false when cache hits occurs)
+
+		} );
+
+		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
+
+		//
+
+		bindings.push( modelGroup );
+		bindings.push( cameraGroup );
+
+		return bindings;
+
+	}
+
+	_setupSharedUniformsGroups() {
+
+		const cameraGroup = new WebGPUUniformsGroup();
+		cameraGroup.setName( 'cameraUniforms' );
+		cameraGroup.setUniform( 'projectionMatrix', new Matrix4() );
+		cameraGroup.setUniform( 'viewMatrix', new Matrix4() );
+		cameraGroup.setUpdateCallback( function ( array, object, camera ) {
+
+			array.set( camera.projectionMatrix.elements, 0 );
+			array.set( camera.matrixWorldInverse.elements, 16 );
+
+			return true; // TODO: implement caching (return false when cache hits occurs)
+
+		} );
+
+		this.sharedUniformsGroups.set( cameraGroup.name, cameraGroup );
+
+	}
+
+}
+
+export default WebGPUBindings;

+ 76 - 0
examples/jsm/renderers/webgpu/WebGPUGeometries.js

@@ -0,0 +1,76 @@
+class WebGPUGeometries {
+
+	constructor( attributes, info ) {
+
+		this.attributes = attributes;
+		this.info = info;
+
+		this.geometries = new WeakMap();
+
+	}
+
+	update( geometry ) {
+
+		if ( this.geometries.has( geometry ) === false ) {
+
+			const disposeCallback = onGeometryDispose.bind( this );
+
+			this.geometries.set( geometry, disposeCallback );
+
+			this.info.memory.geometries ++;
+
+			geometry.addEventListener( 'dispose', disposeCallback );
+
+		}
+
+		const geometryAttributes = geometry.attributes;
+
+		for ( const name in geometryAttributes ) {
+
+			this.attributes.update( geometryAttributes[ name ] );
+
+		}
+
+		const index = geometry.index;
+
+		if ( index !== null ) {
+
+			this.attributes.update( index, true );
+
+		}
+
+	}
+
+}
+
+function onGeometryDispose( event ) {
+
+	const geometry = event.target;
+	const disposeCallback = this.geometries.get( geometry );
+
+	this.geometries.delete( geometry );
+
+	this.info.memory.geometries --;
+
+	geometry.removeEventListener( 'dispose', disposeCallback );
+
+	//
+
+	const index = geometry.index;
+	const geometryAttributes = geometry.attributes;
+
+	if ( index !== null ) {
+
+		this.attributes.remove( index );
+
+	}
+
+	for ( const name in geometryAttributes ) {
+
+		this.attributes.remove( geometryAttributes[ name ] );
+
+	}
+
+}
+
+export default WebGPUGeometries;

+ 72 - 0
examples/jsm/renderers/webgpu/WebGPUInfo.js

@@ -0,0 +1,72 @@
+class WebGPUInfo {
+
+	constructor() {
+
+		this.autoReset = true;
+
+		this.render = {
+			frame: 0,
+			drawCalls: 0,
+			triangles: 0,
+			points: 0,
+			lines: 0
+		};
+
+		this.memory = {
+			geometries: 0
+		};
+
+	}
+
+	update( object, count ) {
+
+		this.render.drawCalls ++;
+
+		if ( object.isMesh ) {
+
+			this.render.triangles += ( count / 3 );
+
+		} else if ( object.isPoints ) {
+
+			this.render.points += count;
+
+		} else if ( object.isLineSegments ) {
+
+			this.render.lines += ( count / 2 );
+
+		} else if ( object.isLine ) {
+
+			this.render.lines += ( count - 1 );
+
+		} else {
+
+			console.error( 'THREE.WebGPUInfo: Unknown object type.' );
+
+		}
+
+	}
+
+	reset() {
+
+		this.render.frame ++;
+		this.render.drawCalls = 0;
+		this.render.triangles = 0;
+		this.render.points = 0;
+		this.render.lines = 0;
+
+	}
+
+	dispose() {
+
+		this.reset();
+
+		this.render.frame = 0;
+
+		this.memory.geometries = 0;
+
+	}
+
+}
+
+
+export default WebGPUInfo;

+ 37 - 0
examples/jsm/renderers/webgpu/WebGPUObjects.js

@@ -0,0 +1,37 @@
+
+class WebGPUObjects {
+
+	constructor( geometries, info ) {
+
+		this.geometries = geometries;
+		this.info = info;
+
+		this.updateMap = new WeakMap();
+
+	}
+
+	update( object ) {
+
+		const geometry = object.geometry;
+		const updateMap = this.updateMap;
+		const frame = this.info.render.frame;
+
+		if ( updateMap.get( geometry ) !== frame ) {
+
+			this.geometries.update( geometry );
+
+			updateMap.set( geometry, frame );
+
+		}
+
+	}
+
+	dispose() {
+
+		this.updateMap = new WeakMap();
+
+	}
+
+}
+
+export default WebGPUObjects;

+ 40 - 0
examples/jsm/renderers/webgpu/WebGPUProperties.js

@@ -0,0 +1,40 @@
+class WebGPUProperties {
+
+	constructor() {
+
+		this.properties = new WeakMap();
+
+	}
+
+	get( object ) {
+
+		let map = this.properties.get( object );
+
+		if ( map === undefined ) {
+
+			map = {};
+			this.properties.set( object, map );
+
+		}
+
+		return map;
+
+
+
+	}
+
+	remove( object ) {
+
+		this.properties.delete( object );
+
+	}
+
+	dispose() {
+
+		this.properties = new WeakMap();
+
+	}
+
+}
+
+export default WebGPUProperties;

+ 200 - 0
examples/jsm/renderers/webgpu/WebGPURenderLists.js

@@ -0,0 +1,200 @@
+function painterSortStable( a, b ) {
+
+	if ( a.groupOrder !== b.groupOrder ) {
+
+		return a.groupOrder - b.groupOrder;
+
+	} else if ( a.renderOrder !== b.renderOrder ) {
+
+		return a.renderOrder - b.renderOrder;
+
+	} else if ( a.material.id !== b.material.id ) {
+
+		return a.material.id - b.material.id;
+
+	} else if ( a.z !== b.z ) {
+
+		return a.z - b.z;
+
+	} else {
+
+		return a.id - b.id;
+
+	}
+
+}
+
+function reversePainterSortStable( a, b ) {
+
+	if ( a.groupOrder !== b.groupOrder ) {
+
+		return a.groupOrder - b.groupOrder;
+
+	} else if ( a.renderOrder !== b.renderOrder ) {
+
+		return a.renderOrder - b.renderOrder;
+
+	} else if ( a.z !== b.z ) {
+
+		return b.z - a.z;
+
+	} else {
+
+		return a.id - b.id;
+
+	}
+
+}
+
+class WebGPURenderList {
+
+	constructor() {
+
+		this.renderItems = [];
+		this.renderItemsIndex = 0;
+
+		this.opaque = [];
+		this.transparent = [];
+
+	}
+
+	init() {
+
+		this.renderItemsIndex = 0;
+
+		this.opaque.length = 0;
+		this.transparent.length = 0;
+
+	}
+
+	getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
+
+		let renderItem = this.renderItems[ this.renderItemsIndex ];
+
+		if ( renderItem === undefined ) {
+
+			renderItem = {
+				id: object.id,
+				object: object,
+				geometry: geometry,
+				material: material,
+				groupOrder: groupOrder,
+				renderOrder: object.renderOrder,
+				z: z,
+				group: group
+			};
+
+			this.renderItems[ this.renderItemsIndex ] = renderItem;
+
+		} else {
+
+			renderItem.id = object.id;
+			renderItem.object = object;
+			renderItem.geometry = geometry;
+			renderItem.material = material;
+			renderItem.groupOrder = groupOrder;
+			renderItem.renderOrder = object.renderOrder;
+			renderItem.z = z;
+			renderItem.group = group;
+
+		}
+
+		this.renderItemsIndex ++;
+
+		return renderItem;
+
+	}
+
+	push( object, geometry, material, groupOrder, z, group ) {
+
+		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+		( material.transparent === true ? this.transparent : this.opaque ).push( renderItem );
+
+	}
+
+	unshift( object, geometry, material, groupOrder, z, group ) {
+
+		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+		( material.transparent === true ? this.transparent : this.opaque ).unshift( renderItem );
+
+	}
+
+	sort( customOpaqueSort, customTransparentSort ) {
+
+		if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable );
+		if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable );
+
+	}
+
+	finish() {
+
+		// Clear references from inactive renderItems in the list
+
+		for ( let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i ++ ) {
+
+			const renderItem = this.renderItems[ i ];
+
+			if ( renderItem.id === null ) break;
+
+			renderItem.id = null;
+			renderItem.object = null;
+			renderItem.geometry = null;
+			renderItem.material = null;
+			renderItem.program = null;
+			renderItem.group = null;
+
+		}
+
+	}
+
+}
+
+class WebGPURenderLists {
+
+	constructor() {
+
+		this.lists = new WeakMap();
+
+	}
+
+	get( scene, camera ) {
+
+		const lists = this.lists;
+		const properties = this.properties;
+
+		const cameras = lists.get( scene );
+		let list;
+
+		if ( cameras === undefined ) {
+
+			list = new WebGPURenderList( properties );
+			lists.set( scene, new WeakMap() );
+			lists.get( scene ).set( camera, list );
+
+		} else {
+
+			list = cameras.get( camera );
+			if ( list === undefined ) {
+
+				list = new WebGPURenderList( properties );
+				cameras.set( camera, list );
+
+			}
+
+		}
+
+		return list;
+
+	}
+
+	dispose() {
+
+		this.lists = new WeakMap();
+
+	}
+
+}
+
+export default WebGPURenderLists;

+ 339 - 0
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -0,0 +1,339 @@
+import { GPUPrimitiveTopology, GPUIndexFormat } from './constants.js';
+import { FrontSide, BackSide, DoubleSide } from '../../../../build/three.module.js';
+
+class WebGPURenderPipelines {
+
+	constructor( device, glslang, bindings ) {
+
+		this.device = device;
+		this.glslang = glslang;
+		this.bindings = bindings;
+
+		this.pipelines = new WeakMap();
+		this.shaderModules = {
+			vertex: new WeakMap(),
+			fragment: new WeakMap()
+		};
+
+	}
+
+	get( object ) {
+
+		let pipeline = this.pipelines.get( object );
+
+		if ( pipeline === undefined ) {
+
+			const device = this.device;
+			const material = object.material;
+
+			// shader source
+
+			let shader;
+
+			if ( material.isMeshBasicMaterial ) {
+
+				shader = ShaderLib.mesh_basic;
+
+			} else if ( material.isPointsMaterial ) {
+
+				shader = ShaderLib.points_basic;
+
+			} else if ( material.isLineBasicMaterial ) {
+
+				shader = ShaderLib.line_basic;
+
+			} else {
+
+				console.error( 'WebGPURenderer: Unknwon shader type' );
+
+			}
+
+			// shader modules
+
+			const glslang = this.glslang;
+
+			let moduleVertex = this.shaderModules.vertex.get( shader );
+
+			if ( moduleVertex === undefined ) {
+
+				const byteCodeVertex = glslang.compileGLSL( shader.vertexShader, "vertex" );
+
+				moduleVertex = {
+					module: device.createShaderModule( { code: byteCodeVertex } ),
+					entryPoint: "main"
+				};
+
+				this.shaderModules.vertex.set( shader, moduleVertex );
+
+			}
+
+			let moduleFragment = this.shaderModules.fragment.get( shader );
+
+			if ( moduleFragment === undefined ) {
+
+				const byteCodeFragment = glslang.compileGLSL( shader.fragmentShader, "fragment" );
+
+				moduleFragment = {
+					module: device.createShaderModule( { code: byteCodeFragment } ),
+					entryPoint: "main"
+				};
+
+				this.shaderModules.fragment.set( shader, moduleFragment );
+
+			}
+
+			// layout
+
+			const bindLayout = this.bindings.get( object ).layout;
+			const layout = device.createPipelineLayout( { bindGroupLayouts: [ bindLayout ] } );
+
+			// vertex buffers
+
+			const geometry = object.geometry;
+
+			const attributes = geometry.attributes;
+			const vertexBuffers = [];
+
+			let shaderLocation = 0;
+
+			for ( const name in attributes ) {
+
+				const attribute = attributes[ name ];
+
+				const arrayStride = this._getArrayStride( attribute );
+				const vertexFormat = this._getVertexFormat( attribute );
+
+				vertexBuffers.push( {
+					arrayStride: arrayStride,
+					attributes: [ { shaderLocation: shaderLocation, offset: 0, format: vertexFormat } ]
+				} );
+
+				shaderLocation ++;
+
+			}
+
+			let indexFormat;
+
+			if ( object.isLine ) {
+
+				const count = ( geometry.index ) ? geometry.index.count : geometry.attributes.position.count;
+
+				indexFormat = ( count > 65535 ) ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16; // define data type the primitive restart value
+
+			}
+
+			// pipeline
+
+			const primitiveTopology = this._getPrimitiveTopology( object );
+			const rasterizationState = this._getRasterizationStateDescriptor( object );
+
+			pipeline = device.createRenderPipeline( {
+				layout: layout,
+				vertexStage: moduleVertex,
+				fragmentStage: moduleFragment,
+				primitiveTopology: primitiveTopology,
+				rasterizationState: rasterizationState,
+				colorStates: [ { format: 'bgra8unorm' } ],
+				depthStencilState: {
+					depthWriteEnabled: material.depthWrite,
+					depthCompare: "less",
+					format: 'depth24plus-stencil8',
+				},
+				vertexState: {
+					indexFormat: indexFormat,
+					vertexBuffers: vertexBuffers
+				}
+			} );
+
+			this.pipelines.set( object, pipeline );
+
+		}
+
+		return pipeline;
+
+	}
+
+	dispose() {
+
+		this.pipelines = new WeakMap();
+		this.shaderModules = {
+			vertex: new WeakMap(),
+			fragment: new WeakMap()
+		};
+
+	}
+
+	_getArrayStride( attribute ) {
+
+		const array = attribute.array;
+
+		if ( array instanceof Float32Array || array instanceof Uint32Array || array instanceof Int32Array ) {
+
+			return attribute.itemSize * 4;
+
+		}
+
+	}
+
+	_getPrimitiveTopology( object ) {
+
+		if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
+		else if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
+		else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
+		else if ( object.isLineSegments ) return GPUPrimitiveTopology.LineList;
+
+	}
+
+	_getRasterizationStateDescriptor( object ) {
+
+		const descriptor = {};
+		const material = object.material;
+
+		switch ( material.side ) {
+
+			case FrontSide:
+				descriptor.frontFace = 'ccw';
+				descriptor.cullMode = 'back';
+				break;
+
+			case BackSide:
+				descriptor.frontFace = 'cw';
+				descriptor.cullMode = 'back';
+				break;
+
+			case DoubleSide:
+				descriptor.frontFace = 'ccw';
+				descriptor.cullMode = 'none';
+				break;
+
+			default:
+				console.warn( 'WebGPURenderer: Unknown material.side value.', material.side );
+				break;
+
+		}
+
+		return descriptor;
+
+	}
+
+	_getVertexFormat( attribute ) {
+
+		const array = attribute.array;
+
+		if ( array instanceof Float32Array ) {
+
+			if ( attribute.itemSize === 1 ) return 'float';
+			if ( attribute.itemSize === 2 ) return 'float2';
+			if ( attribute.itemSize === 3 ) return 'float3';
+			if ( attribute.itemSize === 4 ) return 'float4';
+
+		} else if ( array instanceof Uint32Array ) {
+
+			if ( attribute.itemSize === 1 ) return 'uint';
+			if ( attribute.itemSize === 2 ) return 'uint2';
+			if ( attribute.itemSize === 3 ) return 'uint3';
+			if ( attribute.itemSize === 4 ) return 'uint4';
+
+		} else if ( array instanceof Int32Array ) {
+
+			if ( attribute.itemSize === 1 ) return 'int';
+			if ( attribute.itemSize === 2 ) return 'int2';
+			if ( attribute.itemSize === 3 ) return 'int3';
+			if ( attribute.itemSize === 4 ) return 'int4';
+
+		}
+
+	}
+
+}
+
+const ShaderLib = {
+	mesh_basic: {
+		vertexShader: `#version 450
+
+		layout(location = 0) in vec3 position;
+		layout(location = 1) in vec3 normal;
+		layout(location = 2) in vec2 uv;
+
+		layout(location = 0) out vec2 vUv;
+
+		layout(set = 0, binding = 0) uniform ModelUniforms {
+			mat4 modelMatrix;
+			mat4 modelViewMatrix;
+		} modelUniforms;
+
+		layout(set = 0, binding = 1) uniform CameraUniforms {
+			mat4 projectionMatrix;
+			mat4 viewMatrix;
+		} cameraUniforms;
+
+		void main(){
+			vUv = uv;
+			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
+		}`,
+		fragmentShader: `#version 450
+		layout(set = 0, binding = 2) uniform sampler mySampler;
+		layout(set = 0, binding = 3) uniform texture2D myTexture;
+
+		layout(location = 0) in vec2 vUv;
+		layout(location = 0) out vec4 outColor;
+
+		void main() {
+			outColor = texture( sampler2D( myTexture, mySampler ), vUv );
+		}`
+	},
+	points_basic: {
+		vertexShader: `#version 450
+
+		layout(location = 0) in vec3 position;
+
+		layout(set = 0, binding = 0) uniform ModelUniforms {
+			mat4 modelMatrix;
+			mat4 modelViewMatrix;
+		} modelUniforms;
+
+		layout(set = 0, binding = 1) uniform CameraUniforms {
+			mat4 projectionMatrix;
+			mat4 viewMatrix;
+		} cameraUniforms;
+
+		void main(){
+			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
+		}`,
+		fragmentShader: `#version 450
+
+		layout(location = 0) out vec4 outColor;
+
+		void main() {
+			outColor = vec4( 1.0, 0.0, 0.0, 1.0 );
+		}`
+	},
+	line_basic: {
+		vertexShader: `#version 450
+
+		layout(location = 0) in vec3 position;
+
+		layout(set = 0, binding = 0) uniform ModelUniforms {
+			mat4 modelMatrix;
+			mat4 modelViewMatrix;
+		} modelUniforms;
+
+		layout(set = 0, binding = 1) uniform CameraUniforms {
+			mat4 projectionMatrix;
+			mat4 viewMatrix;
+		} cameraUniforms;
+
+		void main(){
+			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
+		}`,
+		fragmentShader: `#version 450
+
+		layout(location = 0) out vec4 outColor;
+
+		void main() {
+			outColor = vec4( 1.0, 0.0, 0.0, 1.0 );
+		}`
+	}
+};
+
+export default WebGPURenderPipelines;

+ 601 - 0
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -0,0 +1,601 @@
+import { GPUPrimitiveTopology, GPUIndexFormat } from './constants.js';
+import WebGPUObjects from './WebGPUObjects.js';
+import WebGPUAttributes from './WebGPUAttributes.js';
+import WebGPUGeometries from './WebGPUGeometries.js';
+import WebGPUInfo from './WebGPUInfo.js';
+import WebGPUProperties from './WebGPUProperties.js';
+import WebGPURenderPipelines from './WebGPURenderPipelines.js';
+import WebGPUBindings from './WebGPUBindings.js';
+import WebGPURenderLists from './WebGPURenderLists.js';
+import WebGPUTextures from './WebGPUTextures.js';
+
+import { Frustum, Matrix4, Vector3 } from '../../../../build/three.module.js';
+
+const _frustum = new Frustum();
+const _projScreenMatrix = new Matrix4();
+const _vector3 = new Vector3();
+
+class WebGPURenderer {
+
+	constructor( parameters = {} ) {
+
+		// public
+
+		this.domElement = ( parameters.canvas !== undefined ) ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+		this.parameters = parameters;
+
+		this.sortObjects = true;
+
+		// internals
+
+		this._pixelRatio = 1;
+		this._width = this.domElement.width;
+		this._height = this.domElement.height;
+
+		this._viewport = null;
+		this._scissor = null;
+
+		this._adapter = null;
+		this._device = null;
+		this._context = null;
+		this._swapChain = null;
+		this._depthBuffer = null;
+
+		this._info = null;
+		this._properties = null;
+		this._attributes = null;
+		this._geometries = null;
+		this._bindings = null;
+		this._objects = null;
+		this._renderPipelines = null;
+		this._renderLists = null;
+		this._textures = null;
+
+		this._renderPassDescriptor = null;
+
+		this._currentRenderList = null;
+		this._opaqueSort = null;
+		this._transparentSort = null;
+
+	}
+
+	init( parameters ) {
+
+		return initWebGPU( this, parameters );
+
+	}
+
+	render( scene, camera ) {
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+		if ( camera.parent === null ) camera.updateMatrixWorld();
+
+		if ( this._info.autoReset === true ) this._info.reset();
+
+		_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+		_frustum.setFromProjectionMatrix( _projScreenMatrix );
+
+		this._currentRenderList = this._renderLists.get( scene, camera );
+		this._currentRenderList.init();
+
+		this._projectObject( scene, camera, 0 );
+
+		this._currentRenderList.finish();
+
+		if ( this.sortObjects === true ) {
+
+			this._currentRenderList.sort( this._opaqueSort, this._transparentSort );
+
+		}
+
+		const colorAttachment = this._renderPassDescriptor.colorAttachments[ 0 ];
+		colorAttachment.attachment = this._swapChain.getCurrentTexture().createView();
+		colorAttachment.loadValue = 'load';
+
+		const depthStencilAttachment = this._renderPassDescriptor.depthStencilAttachment;
+		depthStencilAttachment.attachment = this._depthBuffer.createView();
+
+		const opaqueObjects = this._currentRenderList.opaque;
+		const transparentObjects = this._currentRenderList.transparent;
+
+		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera );
+		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera );
+
+	}
+
+	getContext() {
+
+		return this._context;
+
+	}
+
+	getPixelRatio() {
+
+		return this._pixelRatio;
+
+	}
+
+	getDrawingBufferSize( target ) {
+
+		return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor();
+
+	}
+
+	getSize( target ) {
+
+		return target.set( this._width, this._height );
+
+	}
+
+	setPixelRatio( value = 1 ) {
+
+		this._pixelRatio = value;
+
+		this.setSize( this._width, this._height, false );
+
+	}
+
+	setDrawingBufferSize( width, height, pixelRatio ) {
+
+		this._width = width;
+		this._height = height;
+
+		this._pixelRatio = pixelRatio;
+
+		this.domElement.width = Math.floor( width * pixelRatio );
+		this.domElement.height = Math.floor( height * pixelRatio );
+
+		this._setupDepthBuffer();
+
+	}
+
+	setSize( width, height, updateStyle = true ) {
+
+		this._width = width;
+		this._height = height;
+
+		this.domElement.width = Math.floor( width * this._pixelRatio );
+		this.domElement.height = Math.floor( height * this._pixelRatio );
+
+		if ( updateStyle === true ) {
+
+			this.domElement.style.width = width + 'px';
+			this.domElement.style.height = height + 'px';
+
+		}
+
+		this._setupDepthBuffer();
+
+	}
+
+	setOpaqueSort( method ) {
+
+		this._opaqueSort = method;
+
+	}
+
+	setTransparentSort( method ) {
+
+		this._transparentSort = method;
+
+	}
+
+	getScissor( target ) {
+
+		const scissor = this._scissor;
+
+		target.x = scissor.x;
+		target.y = scissor.y;
+		target.width = scissor.width;
+		target.height = scissor.height;
+
+		return target;
+
+	}
+
+	setScissor( x, y, width, height ) {
+
+		if ( x === null ) {
+
+			this._scissor = null;
+
+		} else {
+
+			this._scissor = {
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			};
+
+		}
+
+	}
+
+	getViewport( target ) {
+
+		const viewport = this._viewport;
+
+		target.x = viewport.x;
+		target.y = viewport.y;
+		target.width = viewport.width;
+		target.height = viewport.height;
+		target.minDepth = viewport.minDepth;
+		target.maxDepth = viewport.maxDepth;
+
+		return target;
+
+	}
+
+	setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) {
+
+		if ( x === null ) {
+
+			this._viewport = null;
+
+		} else {
+
+			this._viewport = {
+				x: x,
+				y: y,
+				width: width,
+				height: height,
+				minDepth: minDepth,
+				maxDepth: maxDepth
+			};
+
+		}
+
+	}
+
+	dispose() {
+
+		this._objects.dispose();
+		this._properties.dispose();
+		this._renderPipelines.dispose();
+		this._bindings.dispose();
+		this._info.dispose();
+		this._renderLists.dispose();
+
+	}
+
+	_projectObject( object, camera, groupOrder ) {
+
+		const info = this._info;
+		const currentRenderList = this._currentRenderList;
+
+		if ( object.visible === false ) return;
+
+		const visible = object.layers.test( camera.layers );
+
+		if ( visible ) {
+
+			if ( object.isGroup ) {
+
+				groupOrder = object.renderOrder;
+
+			} else if ( object.isLOD ) {
+
+				if ( object.autoUpdate === true ) object.update( camera );
+
+			} else if ( object.isLight ) {
+
+				//currentRenderState.pushLight( object );
+
+				if ( object.castShadow ) {
+
+					//currentRenderState.pushShadow( object );
+
+				}
+
+			} else if ( object.isSprite ) {
+
+				if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
+
+					if ( this.sortObjects === true ) {
+
+						_vector3.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix );
+
+					}
+
+					const geometry = object.geometry;
+					const material = object.material;
+
+					if ( material.visible ) {
+
+						currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+					}
+
+				}
+
+			} else if ( object.isMesh || object.isLine || object.isPoints ) {
+
+				if ( object.isSkinnedMesh ) {
+
+					// update skeleton only once in a frame
+
+					if ( object.skeleton.frame !== info.render.frame ) {
+
+						object.skeleton.update();
+						object.skeleton.frame = info.render.frame;
+
+					}
+
+				}
+
+				if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
+
+					if ( this.sortObjects === true ) {
+
+						_vector3.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix );
+
+					}
+
+					const geometry = object.geometry;
+					const material = object.material;
+
+					if ( Array.isArray( material ) ) {
+
+						const groups = geometry.groups;
+
+						for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+							const group = groups[ i ];
+							const groupMaterial = material[ group.materialIndex ];
+
+							if ( groupMaterial && groupMaterial.visible ) {
+
+								currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
+
+							}
+
+						}
+
+					} else if ( material.visible ) {
+
+						currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+					}
+
+				}
+
+			}
+
+		}
+
+		const children = object.children;
+
+		for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+			this._projectObject( children[ i ], camera, groupOrder );
+
+		}
+
+	}
+
+	_renderObjects( renderList, camera ) {
+
+		for ( let i = 0, l = renderList.length; i < l; i ++ ) {
+
+			const renderItem = renderList[ i ];
+
+			const object = renderItem.object;
+
+			object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+			object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+
+			this._objects.update( object );
+			this._bindings.update( object, camera );
+
+			this._renderObject( object );
+
+		}
+
+	}
+
+	_renderObject( object ) {
+
+		const device = this._device;
+		const info = this._info;
+
+		// begin
+
+		const cmdEncoder = device.createCommandEncoder( {} );
+		const passEncoder = cmdEncoder.beginRenderPass( this._renderPassDescriptor );
+
+		// pipeline
+
+		const pipeline = this._renderPipelines.get( object );
+		passEncoder.setPipeline( pipeline );
+
+		// rasterization
+
+		const vp = this._viewport;
+
+		if ( vp !== null ) {
+
+			const width = Math.floor( vp.width * this._pixelRatio );
+			const height = Math.floor( vp.height * this._pixelRatio );
+
+			passEncoder.setViewport( vp.x, vp.y, width, height, vp.minDepth, vp.maxDepth );
+
+		}
+
+		const sc = this._scissor;
+
+		if ( sc !== null ) {
+
+			const width = Math.floor( sc.width * this._pixelRatio );
+			const height = Math.floor( sc.height * this._pixelRatio );
+
+			passEncoder.setScissorRect( sc.x, sc.y, width, height );
+
+		}
+
+		// bind group
+
+		const bindGroup = this._bindings.get( object ).group;
+		passEncoder.setBindGroup( 0, bindGroup );
+
+		// index
+
+		const geometry = object.geometry;
+		const index = geometry.index;
+
+		const hasIndex = ( index !== null );
+
+		if ( hasIndex === true ) {
+
+			this._setupIndexBuffer( passEncoder, index );
+
+		}
+
+		// vertex buffers
+
+		this._setupVertexBuffers( passEncoder, geometry.attributes );
+
+		// draw
+
+		const drawRange = geometry.drawRange;
+		const firstVertex = drawRange.start;
+
+		if ( hasIndex === true ) {
+
+			const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count;
+
+			passEncoder.drawIndexed( indexCount, 1, firstVertex, 0, 0 );
+
+			info.update( object, indexCount );
+
+		} else {
+
+			const positionAttribute = geometry.attributes.position;
+			const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count;
+
+			passEncoder.draw( vertexCount, 1, firstVertex, 0 );
+
+			info.update( object, vertexCount );
+
+		}
+
+		// end
+
+		passEncoder.endPass();
+		device.defaultQueue.submit( [ cmdEncoder.finish() ] );
+
+	}
+
+	_setupIndexBuffer( encoder, index ) {
+
+		const buffer = this._attributes.get( index ).buffer;
+		const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
+
+		encoder.setIndexBuffer( buffer, indexFormat );
+
+	}
+
+	_setupVertexBuffers( encoder, geometryAttributes ) {
+
+		let slot = 0;
+
+		for ( const name in geometryAttributes ) {
+
+			const attribute = geometryAttributes[ name ];
+			const buffer = this._attributes.get( attribute ).buffer;
+
+			encoder.setVertexBuffer( slot, buffer );
+
+			slot ++;
+
+		}
+
+	}
+
+	_setupDepthBuffer() {
+
+		const device = this._device;
+
+		if ( device ) {
+
+			if ( this._depthBuffer ) this._depthBuffer.destroy();
+
+			this._depthBuffer = this._device.createTexture( {
+				size: {
+					width: this._width * this._pixelRatio,
+					height: this._height * this._pixelRatio,
+					depth: 1
+				},
+				format: 'depth24plus-stencil8',
+				usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+			} );
+
+		}
+
+	}
+
+}
+
+async function initWebGPU( scope ) {
+
+	const parameters = scope.parameters;
+
+	const adapterOptions = {
+		powerPreference: ( parameters.powerPreference !== undefined ) ? parameters.powerPreference : undefined
+	};
+
+	const adapter = await navigator.gpu.requestAdapter( adapterOptions );
+
+	const deviceDescriptor = {
+		enabledExtensions: ( parameters.enabledExtensions !== undefined ) ? parameters.enabledExtensions : [],
+		limits: ( parameters.limits !== undefined ) ? parameters.limits : {}
+	};
+
+	const device = await adapter.requestDevice( deviceDescriptor );
+
+	const glslang = await import( 'https://cdn.jsdelivr.net/npm/@webgpu/[email protected]/dist/web-devel/glslang.js' );
+	const compiler = await glslang.default();
+
+	const context = ( parameters.context !== undefined ) ? parameters.context : scope.domElement.getContext( 'gpupresent' );
+
+	const swapChain = context.configureSwapChain( {
+		device: device,
+		format: 'bgra8unorm'
+	} );
+
+	scope._adapter = adapter;
+	scope._device = device;
+	scope._context = context;
+	scope._swapChain = swapChain;
+
+	scope._info = new WebGPUInfo();
+	scope._properties = new WebGPUProperties();
+	scope._attributes = new WebGPUAttributes( device );
+	scope._geometries = new WebGPUGeometries( scope._attributes, scope._info );
+	scope._textures = new WebGPUTextures( device, scope._properties );
+	scope._bindings = new WebGPUBindings( device, scope._info, scope._properties, scope._textures );
+	scope._objects = new WebGPUObjects( scope._geometries, scope._info );
+	scope._renderPipelines = new WebGPURenderPipelines( device, compiler, scope._bindings );
+	scope._renderLists = new WebGPURenderLists();
+
+	//
+
+	scope._renderPassDescriptor = {
+		colorAttachments: [ {
+			attachment: null
+		} ],
+		 depthStencilAttachment: {
+			attachment: null,
+			depthLoadValue: 1,
+			depthStoreOp: 'store',
+			stencilLoadValue: 0,
+			stencilStoreOp: 'store'
+		}
+	};
+
+	scope._setupDepthBuffer();
+
+}
+
+export default WebGPURenderer;

+ 28 - 0
examples/jsm/renderers/webgpu/WebGPUSampledTexture.js

@@ -0,0 +1,28 @@
+import WebGPUBinding from './WebGPUBinding.js';
+
+class WebGPUSampledTexture extends WebGPUBinding {
+
+	constructor() {
+
+		super();
+
+		this.name = '';
+
+		this.type = 'sampled-texture';
+		this.visibility = GPUShaderStage.FRAGMENT;
+
+		this.textureGPU = null; // set by the renderer
+
+		Object.defineProperty( this, 'isSampledTexture', { value: true } );
+
+	}
+
+	setName( name ) {
+
+		this.name = name;
+
+	}
+
+}
+
+export default WebGPUSampledTexture;

+ 28 - 0
examples/jsm/renderers/webgpu/WebGPUSampler.js

@@ -0,0 +1,28 @@
+import WebGPUBinding from './WebGPUBinding.js';
+
+class WebGPUSampler extends WebGPUBinding {
+
+	constructor() {
+
+		super();
+
+		this.name = '';
+
+		this.type = 'sampler';
+		this.visibility = GPUShaderStage.FRAGMENT;
+
+		this.samplerGPU = null; // set by the renderer
+
+		Object.defineProperty( this, 'isSampler', { value: true } );
+
+	}
+
+	setName( name ) {
+
+		this.name = name;
+
+	}
+
+}
+
+export default WebGPUSampler;

+ 255 - 0
examples/jsm/renderers/webgpu/WebGPUTextures.js

@@ -0,0 +1,255 @@
+import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping } from '../../../../build/three.module.js';
+
+class WebGPUTextures {
+
+	constructor( device, properties ) {
+
+		this.device = device;
+		this.properties = properties;
+
+		this.textures = new WeakMap();
+
+		this.defaultTexture = null;
+		this.defaultSampler = null;
+		this.canvas = null;
+
+		this.samplerCache = new WeakMap();
+
+	}
+
+	getDefaultSampler() {
+
+		if ( this.defaultSampler === null ) {
+
+			this.defaultSampler = this.device.createSampler( {} );
+
+		}
+
+		return this.defaultSampler;
+
+	}
+
+	getDefaultTexture() {
+
+		if ( this.defaultTexture === null ) {
+
+			this.defaultTexture = this._createTexture( new Texture( new Image( 1, 1 ) ) );
+
+		}
+
+		return this.defaultTexture;
+
+	}
+
+	getTextureGPU( texture ) {
+
+		const textureProperties = this.properties.get( texture );
+
+		return textureProperties.textureGPU;
+
+	}
+
+	getSampler( texture ) {
+
+		const textureProperties = this.properties.get( texture );
+
+		return textureProperties.sampler;
+
+	}
+
+	updateTexture( texture ) {
+
+		let updated = false;
+
+		const textureProperties = this.properties.get( texture );
+
+		if ( texture.version > 0 && textureProperties.version !== texture.version ) {
+
+			const image = texture.image;
+
+			if ( image === undefined ) {
+
+				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined' );
+
+			} else if ( image.complete === false ) {
+
+				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete' );
+
+			} else {
+
+				textureProperties.textureGPU = this._createTexture( texture );
+				textureProperties.version = texture.version;
+				updated = true;
+
+			}
+
+		}
+
+		return updated;
+
+	}
+
+	updateSampler( texture ) {
+
+		let updated = false;
+
+		const array = [];
+
+		array.push( texture.magFilter );
+		array.push( texture.minFilter );
+		array.push( texture.anisotropy );
+
+		const newKey = array.join();
+		const key = this.samplerCache.get( texture );
+
+		if ( key !== newKey ) {
+
+			this.samplerCache.set( texture, newKey );
+
+			const sampler = this.device.createSampler( {
+				addressModeU: this._convertAddressMode( texture.wrapS ),
+				addressModeV: this._convertAddressMode( texture.wrapT ),
+				addressModeW: this._convertAddressMode( texture.wrapR ),
+				magFilter: this._convertFilterMode( texture.magFilter ),
+				minFilter: this._convertFilterMode( texture.minFilter ),
+				mipmapFilter: this._convertFilterMode( texture.minFilter ),
+				maxAnisotropy: texture.anisotropy
+			} );
+
+			const textureProperties = this.properties.get( texture );
+			textureProperties.sampler = sampler;
+
+			updated = true;
+
+		}
+
+		return updated;
+
+
+	}
+
+	_convertAddressMode( value ) {
+
+		let addressMode = 'clamp-to-edge';
+
+		if ( value === RepeatWrapping ) {
+
+			addressMode = 'repeat';
+
+		} else if ( value === MirroredRepeatWrapping ) {
+
+			addressMode = 'mirror-repeat';
+
+		}
+
+		return addressMode;
+
+	}
+
+	_convertFilterMode( value ) {
+
+		let filterMode = 'linear';
+
+		if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {
+
+			filterMode = 'nearest';
+
+		}
+
+		return filterMode;
+
+	}
+
+	_createTexture( texture ) {
+
+		const device = this.device;
+		const image = texture.image;
+
+		if ( this.canvas === null ) this.canvas = new OffscreenCanvas( 1, 1 );
+
+		const width = image.width;
+		const height = image.height;
+
+		this.canvas.width = width;
+		this.canvas.height = height;
+
+		const context = this.canvas.getContext( '2d' );
+		context.translate( 0, height );
+		context.scale( 1, - 1 );
+		context.drawImage( image, 0, 0, width, height );
+		const imageData = context.getImageData( 0, 0, width, height );
+
+		let data = null;
+
+		const bytesPerRow = Math.ceil( width * 4 / 256 ) * 256;
+
+		if ( bytesPerRow === width * 4 ) {
+
+			data = imageData.data;
+
+		} else {
+
+			// ensure 4 byte alignment
+
+			data = new Uint8Array( bytesPerRow * height );
+			let imagePixelIndex = 0;
+
+			for ( let y = 0; y < height; ++ y ) {
+
+				for ( let x = 0; x < width; ++ x ) {
+
+					const i = x * 4 + y * bytesPerRow;
+					data[ i ] = imageData.data[ imagePixelIndex ];
+					data[ i + 1 ] = imageData.data[ imagePixelIndex + 1 ];
+					data[ i + 2 ] = imageData.data[ imagePixelIndex + 2 ];
+					data[ i + 3 ] = imageData.data[ imagePixelIndex + 3 ];
+					imagePixelIndex += 4;
+
+				}
+
+			}
+
+		}
+
+		const textureGPU = device.createTexture( {
+			size: {
+				width: image.width,
+				height: image.height,
+				depth: 1,
+			},
+			format: "rgba8unorm",
+			usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST,
+		} );
+
+		const buffer = device.createBuffer( {
+			size: data.byteLength,
+			usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
+			mappedAtCreation: true,
+		} );
+
+		new Uint8Array( buffer.getMappedRange() ).set( data );
+
+		buffer.unmap();
+
+		const commandEncoder = device.createCommandEncoder( {} );
+
+		commandEncoder.copyBufferToTexture(
+			{
+				buffer: buffer, bytesPerRow: bytesPerRow,
+			}, {
+				texture: textureGPU,
+			}, {
+				width: image.width,
+				height: image.height,
+				depth: 1,
+			} );
+		device.defaultQueue.submit( [ commandEncoder.finish() ] );
+		buffer.destroy();
+
+		return textureGPU;
+
+	}
+
+}
+
+export default WebGPUTextures;

+ 138 - 0
examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js

@@ -0,0 +1,138 @@
+import WebGPUBinding from './WebGPUBinding.js';
+
+class WebGPUUniformsGroup extends WebGPUBinding {
+
+	constructor() {
+
+		super();
+
+		this.name = '';
+		this.uniforms = new Map();
+
+		this.update = function () {};
+
+		this.type = 'uniform-buffer';
+		this.visibility = GPUShaderStage.VERTEX;
+
+		this.usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
+
+		this.array = null; // set by the renderer
+		this.bufferGPU = null; // set by the renderer
+
+		Object.defineProperty( this, 'isUniformsGroup', { value: true } );
+
+	}
+
+	setName( name ) {
+
+		this.name = name;
+
+	}
+
+	setUniform( name, uniform ) {
+
+		this.uniforms.set( name, uniform );
+
+		return this;
+
+	}
+
+	removeUniform( name ) {
+
+		this.uniforms.delete( name );
+
+		return this;
+
+	}
+
+	setUpdateCallback( callback ) {
+
+		this.update = callback;
+
+		return this;
+
+	}
+
+	copy( source ) {
+
+		this.name = source.name;
+
+		const uniformsSource = source.uniforms;
+
+		this.uniforms.clear();
+
+		for ( const entry of uniformsSource ) {
+
+			this.uniforms.set( ...entry );
+
+		}
+
+		return this;
+
+	}
+
+	clone() {
+
+		return new this.constructor().copy( this );
+
+	}
+
+	getByteLength() {
+
+		let size = 0;
+
+		for ( const uniform of this.uniforms.values() ) {
+
+			size += this.getUniformByteLength( uniform );
+
+		}
+
+		return size;
+
+	}
+
+	getUniformByteLength( uniform ) {
+
+		let size;
+
+		if ( typeof uniform === 'number' ) {
+
+			size = 4;
+
+		} else if ( uniform.isVector2 ) {
+
+			size = 8;
+
+		} else if ( uniform.isVector3 || uniform.isColor ) {
+
+			size = 12;
+
+		} else if ( uniform.isVector4 ) {
+
+			size = 16;
+
+		} else if ( uniform.isMatrix3 ) {
+
+			size = 36;
+
+		} else if ( uniform.isMatrix4 ) {
+
+			size = 64;
+
+		} else if ( uniform.isTexture ) {
+
+			console.warn( 'THREE.UniformsGroup: Texture samplers can not be part of an uniforms group.' );
+
+		} else {
+
+			console.warn( 'THREE.UniformsGroup: Unsupported uniform value type.', uniform );
+
+		}
+
+		return size;
+
+	}
+
+}
+
+export default WebGPUUniformsGroup;

+ 12 - 0
examples/jsm/renderers/webgpu/constants.js

@@ -0,0 +1,12 @@
+export const GPUPrimitiveTopology = {
+	PointList: 'point-list',
+	LineList: 'line-list',
+	LineStrip: 'line-strip',
+	TriangleList: 'triangle-list',
+	TriangleStrip: 'triangle-strip',
+};
+
+export const GPUIndexFormat = {
+	Uint16: 'uint16',
+	Uint32: 'uint32'
+};

BIN
examples/screenshots/webgpu_sandbox.jpg


+ 97 - 0
examples/webgpu_sandbox.html

@@ -0,0 +1,97 @@
+<html lang="en">
+	<head>
+		<title>WebGPU Sandbox</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - sandbox<br/>(Chrome Canary with flag: --enable-unsafe-webgpu)
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
+
+			let camera, scene, renderer;
+
+			let mesh;
+
+			init().then( animate );
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 10 );
+				camera.position.z = 4;
+
+				scene = new THREE.Scene();
+
+				// textured mesh
+
+				const loader = new THREE.TextureLoader();
+				const texture = loader.load( './textures/uv_grid_opengl.jpg' );
+
+				const geometryMesh = new THREE.BoxBufferGeometry();
+				const materialMesh = new THREE.MeshBasicMaterial( { map: texture } );
+
+				mesh = new THREE.Mesh( geometryMesh, materialMesh );
+				mesh.position.set( - 1, 0.5, 0 );
+				scene.add( mesh );
+
+				// points
+
+				const geometryPoints = new THREE.BufferGeometry().setFromPoints( [ new THREE.Vector3( 0, - 1, 0 ), new THREE.Vector3( 1, - 1, 0 ) ] );
+				const materialPoints = new THREE.PointsMaterial();
+
+				const points = new THREE.Points( geometryPoints, materialPoints );
+				scene.add( points );
+
+				// lines
+
+				const geometryLine = new THREE.BufferGeometry().setFromPoints( [
+					new THREE.Vector3( 1, 0, 0 ),
+					new THREE.Vector3( 2, 0, 0 ),
+					new THREE.Vector3( 2, 1, 0 ),
+					new THREE.Vector3( 1, 1, 0 )
+				] );
+				const materialLine = new THREE.LineBasicMaterial();
+				const line = new THREE.Line( geometryLine, materialLine );
+				scene.add( line );
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				return renderer.init();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				mesh.rotation.x += 0.01;
+				mesh.rotation.y += 0.02;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>