Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
import {
Matrix4,
Object3D,
Vector3
} from 'three';
class CSS2DObject extends Object3D {
constructor( element = document.createElement( 'div' ) ) {
super();
this.element = element;
this.element.style.position = 'absolute';
this.element.style.userSelect = 'none';
this.element.setAttribute( 'draggable', false );
this.addEventListener( 'removed', function () {
this.traverse( function ( object ) {
if ( object.element instanceof Element && object.element.parentNode !== null ) {
object.element.parentNode.removeChild( object.element );
}
} );
} );
}
copy( source, recursive ) {
super.copy( source, recursive );
this.element = source.element.cloneNode( true );
return this;
}
}
CSS2DObject.prototype.isCSS2DObject = true;
//
const _vector = new Vector3();
const _viewMatrix = new Matrix4();
const _viewProjectionMatrix = new Matrix4();
const _a = new Vector3();
const _b = new Vector3();
class CSS2DRenderer {
constructor( parameters = {} ) {
const _this = this;
let _width, _height;
let _widthHalf, _heightHalf;
const cache = {
objects: new WeakMap()
};
const domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );
domElement.style.overflow = 'hidden';
this.domElement = domElement;
this.getSize = function () {
return {
width: _width,
height: _height
};
};
this.render = function ( scene, camera ) {
if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
_viewMatrix.copy( camera.matrixWorldInverse );
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
renderObject( scene, scene, camera );
zOrder( scene );
};
this.setSize = function ( width, height ) {
_width = width;
_height = height;
_widthHalf = _width / 2;
_heightHalf = _height / 2;
domElement.style.width = width + 'px';
domElement.style.height = height + 'px';
};
function renderObject( object, scene, camera ) {
if ( object.isCSS2DObject ) {
_vector.setFromMatrixPosition( object.matrixWorld );
_vector.applyMatrix4( _viewProjectionMatrix );
const visible = ( object.visible === true ) && ( _vector.z >= - 1 && _vector.z <= 1 ) && ( object.layers.test( camera.layers ) === true );
object.element.style.display = ( visible === true ) ? '' : 'none';
if ( visible === true ) {
object.onBeforeRender( _this, scene, camera );
const element = object.element;
if ( /apple/i.test( navigator.vendor ) ) {
// https://github.com/mrdoob/three.js/issues/21415
element.style.transform = 'translate(-50%,-50%) translate(' + Math.round( _vector.x * _widthHalf + _widthHalf ) + 'px,' + Math.round( - _vector.y * _heightHalf + _heightHalf ) + 'px)';
} else {
element.style.transform = 'translate(-50%,-50%) translate(' + ( _vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - _vector.y * _heightHalf + _heightHalf ) + 'px)';
}
if ( element.parentNode !== domElement ) {
domElement.appendChild( element );
}
object.onAfterRender( _this, scene, camera );
}
const objectData = {
distanceToCameraSquared: getDistanceToSquared( camera, object )
};
cache.objects.set( object, objectData );
}
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
renderObject( object.children[ i ], scene, camera );
}
}
function getDistanceToSquared( object1, object2 ) {
_a.setFromMatrixPosition( object1.matrixWorld );
_b.setFromMatrixPosition( object2.matrixWorld );
return _a.distanceToSquared( _b );
}
function filterAndFlatten( scene ) {
const result = [];
scene.traverse( function ( object ) {
if ( object.isCSS2DObject ) result.push( object );
} );
return result;
}
function zOrder( scene ) {
const sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
if ( a.renderOrder !== b.renderOrder ) {
return b.renderOrder - a.renderOrder;
}
const distanceA = cache.objects.get( a ).distanceToCameraSquared;
const distanceB = cache.objects.get( b ).distanceToCameraSquared;
return distanceA - distanceB;
} );
const zMax = sorted.length;
for ( let i = 0, l = sorted.length; i < l; i ++ ) {
sorted[ i ].element.style.zIndex = zMax - i;
}
}
}
}
export { CSS2DObject, CSS2DRenderer };

View File

@@ -0,0 +1,313 @@
import {
Matrix4,
Object3D,
Quaternion,
Vector3
} from 'three';
/**
* Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
*/
const _position = new Vector3();
const _quaternion = new Quaternion();
const _scale = new Vector3();
class CSS3DObject extends Object3D {
constructor( element = document.createElement( 'div' ) ) {
super();
this.element = element;
this.element.style.position = 'absolute';
this.element.style.pointerEvents = 'auto';
this.element.style.userSelect = 'none';
this.element.setAttribute( 'draggable', false );
this.addEventListener( 'removed', function () {
this.traverse( function ( object ) {
if ( object.element instanceof Element && object.element.parentNode !== null ) {
object.element.parentNode.removeChild( object.element );
}
} );
} );
}
copy( source, recursive ) {
super.copy( source, recursive );
this.element = source.element.cloneNode( true );
return this;
}
}
CSS3DObject.prototype.isCSS3DObject = true;
class CSS3DSprite extends CSS3DObject {
constructor( element ) {
super( element );
this.rotation2D = 0;
}
copy( source, recursive ) {
super.copy( source, recursive );
this.rotation2D = source.rotation2D;
return this;
}
}
CSS3DSprite.prototype.isCSS3DSprite = true;
//
const _matrix = new Matrix4();
const _matrix2 = new Matrix4();
class CSS3DRenderer {
constructor( parameters = {} ) {
const _this = this;
let _width, _height;
let _widthHalf, _heightHalf;
const cache = {
camera: { fov: 0, style: '' },
objects: new WeakMap()
};
const domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );
domElement.style.overflow = 'hidden';
this.domElement = domElement;
const cameraElement = document.createElement( 'div' );
cameraElement.style.transformStyle = 'preserve-3d';
cameraElement.style.pointerEvents = 'none';
domElement.appendChild( cameraElement );
this.getSize = function () {
return {
width: _width,
height: _height
};
};
this.render = function ( scene, camera ) {
const fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;
if ( cache.camera.fov !== fov ) {
domElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : '';
cache.camera.fov = fov;
}
if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
let tx, ty;
if ( camera.isOrthographicCamera ) {
tx = - ( camera.right + camera.left ) / 2;
ty = ( camera.top + camera.bottom ) / 2;
}
const cameraCSSMatrix = camera.isOrthographicCamera ?
'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :
'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );
const style = cameraCSSMatrix +
'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';
if ( cache.camera.style !== style ) {
cameraElement.style.transform = style;
cache.camera.style = style;
}
renderObject( scene, scene, camera, cameraCSSMatrix );
};
this.setSize = function ( width, height ) {
_width = width;
_height = height;
_widthHalf = _width / 2;
_heightHalf = _height / 2;
domElement.style.width = width + 'px';
domElement.style.height = height + 'px';
cameraElement.style.width = width + 'px';
cameraElement.style.height = height + 'px';
};
function epsilon( value ) {
return Math.abs( value ) < 1e-10 ? 0 : value;
}
function getCameraCSSMatrix( matrix ) {
const elements = matrix.elements;
return 'matrix3d(' +
epsilon( elements[ 0 ] ) + ',' +
epsilon( - elements[ 1 ] ) + ',' +
epsilon( elements[ 2 ] ) + ',' +
epsilon( elements[ 3 ] ) + ',' +
epsilon( elements[ 4 ] ) + ',' +
epsilon( - elements[ 5 ] ) + ',' +
epsilon( elements[ 6 ] ) + ',' +
epsilon( elements[ 7 ] ) + ',' +
epsilon( elements[ 8 ] ) + ',' +
epsilon( - elements[ 9 ] ) + ',' +
epsilon( elements[ 10 ] ) + ',' +
epsilon( elements[ 11 ] ) + ',' +
epsilon( elements[ 12 ] ) + ',' +
epsilon( - elements[ 13 ] ) + ',' +
epsilon( elements[ 14 ] ) + ',' +
epsilon( elements[ 15 ] ) +
')';
}
function getObjectCSSMatrix( matrix ) {
const elements = matrix.elements;
const matrix3d = 'matrix3d(' +
epsilon( elements[ 0 ] ) + ',' +
epsilon( elements[ 1 ] ) + ',' +
epsilon( elements[ 2 ] ) + ',' +
epsilon( elements[ 3 ] ) + ',' +
epsilon( - elements[ 4 ] ) + ',' +
epsilon( - elements[ 5 ] ) + ',' +
epsilon( - elements[ 6 ] ) + ',' +
epsilon( - elements[ 7 ] ) + ',' +
epsilon( elements[ 8 ] ) + ',' +
epsilon( elements[ 9 ] ) + ',' +
epsilon( elements[ 10 ] ) + ',' +
epsilon( elements[ 11 ] ) + ',' +
epsilon( elements[ 12 ] ) + ',' +
epsilon( elements[ 13 ] ) + ',' +
epsilon( elements[ 14 ] ) + ',' +
epsilon( elements[ 15 ] ) +
')';
return 'translate(-50%,-50%)' + matrix3d;
}
function renderObject( object, scene, camera, cameraCSSMatrix ) {
if ( object.isCSS3DObject ) {
const visible = ( object.visible === true ) && ( object.layers.test( camera.layers ) === true );
object.element.style.display = ( visible === true ) ? '' : 'none';
if ( visible === true ) {
object.onBeforeRender( _this, scene, camera );
let style;
if ( object.isCSS3DSprite ) {
// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
_matrix.copy( camera.matrixWorldInverse );
_matrix.transpose();
if ( object.rotation2D !== 0 ) _matrix.multiply( _matrix2.makeRotationZ( object.rotation2D ) );
object.matrixWorld.decompose( _position, _quaternion, _scale );
_matrix.setPosition( _position );
_matrix.scale( _scale );
_matrix.elements[ 3 ] = 0;
_matrix.elements[ 7 ] = 0;
_matrix.elements[ 11 ] = 0;
_matrix.elements[ 15 ] = 1;
style = getObjectCSSMatrix( _matrix );
} else {
style = getObjectCSSMatrix( object.matrixWorld );
}
const element = object.element;
const cachedObject = cache.objects.get( object );
if ( cachedObject === undefined || cachedObject.style !== style ) {
element.style.transform = style;
const objectData = { style: style };
cache.objects.set( object, objectData );
}
if ( element.parentNode !== cameraElement ) {
cameraElement.appendChild( element );
}
object.onAfterRender( _this, scene, camera );
}
}
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
renderObject( object.children[ i ], scene, camera, cameraCSSMatrix );
}
}
}
}
export { CSS3DObject, CSS3DSprite, CSS3DRenderer };

View File

@@ -0,0 +1,967 @@
import {
Box3,
Color,
DoubleSide,
Frustum,
Matrix3,
Matrix4,
Vector2,
Vector3,
Vector4
} from 'three';
class RenderableObject {
constructor() {
this.id = 0;
this.object = null;
this.z = 0;
this.renderOrder = 0;
}
}
//
class RenderableFace {
constructor() {
this.id = 0;
this.v1 = new RenderableVertex();
this.v2 = new RenderableVertex();
this.v3 = new RenderableVertex();
this.normalModel = new Vector3();
this.vertexNormalsModel = [ new Vector3(), new Vector3(), new Vector3() ];
this.vertexNormalsLength = 0;
this.color = new Color();
this.material = null;
this.uvs = [ new Vector2(), new Vector2(), new Vector2() ];
this.z = 0;
this.renderOrder = 0;
}
}
//
class RenderableVertex {
constructor() {
this.position = new Vector3();
this.positionWorld = new Vector3();
this.positionScreen = new Vector4();
this.visible = true;
}
copy( vertex ) {
this.positionWorld.copy( vertex.positionWorld );
this.positionScreen.copy( vertex.positionScreen );
}
}
//
class RenderableLine {
constructor() {
this.id = 0;
this.v1 = new RenderableVertex();
this.v2 = new RenderableVertex();
this.vertexColors = [ new Color(), new Color() ];
this.material = null;
this.z = 0;
this.renderOrder = 0;
}
}
//
class RenderableSprite {
constructor() {
this.id = 0;
this.object = null;
this.x = 0;
this.y = 0;
this.z = 0;
this.rotation = 0;
this.scale = new Vector2();
this.material = null;
this.renderOrder = 0;
}
}
//
class Projector {
constructor() {
let _object, _objectCount, _objectPoolLength = 0,
_vertex, _vertexCount, _vertexPoolLength = 0,
_face, _faceCount, _facePoolLength = 0,
_line, _lineCount, _linePoolLength = 0,
_sprite, _spriteCount, _spritePoolLength = 0,
_modelMatrix;
const
_renderData = { objects: [], lights: [], elements: [] },
_vector3 = new Vector3(),
_vector4 = new Vector4(),
_clipBox = new Box3( new Vector3( - 1, - 1, - 1 ), new Vector3( 1, 1, 1 ) ),
_boundingBox = new Box3(),
_points3 = new Array( 3 ),
_viewMatrix = new Matrix4(),
_viewProjectionMatrix = new Matrix4(),
_modelViewProjectionMatrix = new Matrix4(),
_frustum = new Frustum(),
_objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = [];
//
this.projectVector = function ( vector, camera ) {
console.warn( 'THREE.Projector: .projectVector() is now vector.project().' );
vector.project( camera );
};
this.unprojectVector = function ( vector, camera ) {
console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' );
vector.unproject( camera );
};
this.pickingRay = function () {
console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' );
};
//
function RenderList() {
const normals = [];
const colors = [];
const uvs = [];
let object = null;
const normalMatrix = new Matrix3();
function setObject( value ) {
object = value;
normalMatrix.getNormalMatrix( object.matrixWorld );
normals.length = 0;
colors.length = 0;
uvs.length = 0;
}
function projectVertex( vertex ) {
const position = vertex.position;
const positionWorld = vertex.positionWorld;
const positionScreen = vertex.positionScreen;
positionWorld.copy( position ).applyMatrix4( _modelMatrix );
positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix );
const invW = 1 / positionScreen.w;
positionScreen.x *= invW;
positionScreen.y *= invW;
positionScreen.z *= invW;
vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 &&
positionScreen.y >= - 1 && positionScreen.y <= 1 &&
positionScreen.z >= - 1 && positionScreen.z <= 1;
}
function pushVertex( x, y, z ) {
_vertex = getNextVertexInPool();
_vertex.position.set( x, y, z );
projectVertex( _vertex );
}
function pushNormal( x, y, z ) {
normals.push( x, y, z );
}
function pushColor( r, g, b ) {
colors.push( r, g, b );
}
function pushUv( x, y ) {
uvs.push( x, y );
}
function checkTriangleVisibility( v1, v2, v3 ) {
if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true;
_points3[ 0 ] = v1.positionScreen;
_points3[ 1 ] = v2.positionScreen;
_points3[ 2 ] = v3.positionScreen;
return _clipBox.intersectsBox( _boundingBox.setFromPoints( _points3 ) );
}
function checkBackfaceCulling( v1, v2, v3 ) {
return ( ( v3.positionScreen.x - v1.positionScreen.x ) *
( v2.positionScreen.y - v1.positionScreen.y ) -
( v3.positionScreen.y - v1.positionScreen.y ) *
( v2.positionScreen.x - v1.positionScreen.x ) ) < 0;
}
function pushLine( a, b ) {
const v1 = _vertexPool[ a ];
const v2 = _vertexPool[ b ];
// Clip
v1.positionScreen.copy( v1.position ).applyMatrix4( _modelViewProjectionMatrix );
v2.positionScreen.copy( v2.position ).applyMatrix4( _modelViewProjectionMatrix );
if ( clipLine( v1.positionScreen, v2.positionScreen ) === true ) {
// Perform the perspective divide
v1.positionScreen.multiplyScalar( 1 / v1.positionScreen.w );
v2.positionScreen.multiplyScalar( 1 / v2.positionScreen.w );
_line = getNextLineInPool();
_line.id = object.id;
_line.v1.copy( v1 );
_line.v2.copy( v2 );
_line.z = Math.max( v1.positionScreen.z, v2.positionScreen.z );
_line.renderOrder = object.renderOrder;
_line.material = object.material;
if ( object.material.vertexColors ) {
_line.vertexColors[ 0 ].fromArray( colors, a * 3 );
_line.vertexColors[ 1 ].fromArray( colors, b * 3 );
}
_renderData.elements.push( _line );
}
}
function pushTriangle( a, b, c, material ) {
const v1 = _vertexPool[ a ];
const v2 = _vertexPool[ b ];
const v3 = _vertexPool[ c ];
if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return;
if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) {
_face = getNextFaceInPool();
_face.id = object.id;
_face.v1.copy( v1 );
_face.v2.copy( v2 );
_face.v3.copy( v3 );
_face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3;
_face.renderOrder = object.renderOrder;
// face normal
_vector3.subVectors( v3.position, v2.position );
_vector4.subVectors( v1.position, v2.position );
_vector3.cross( _vector4 );
_face.normalModel.copy( _vector3 );
_face.normalModel.applyMatrix3( normalMatrix ).normalize();
for ( let i = 0; i < 3; i ++ ) {
const normal = _face.vertexNormalsModel[ i ];
normal.fromArray( normals, arguments[ i ] * 3 );
normal.applyMatrix3( normalMatrix ).normalize();
const uv = _face.uvs[ i ];
uv.fromArray( uvs, arguments[ i ] * 2 );
}
_face.vertexNormalsLength = 3;
_face.material = material;
if ( material.vertexColors ) {
_face.color.fromArray( colors, a * 3 );
}
_renderData.elements.push( _face );
}
}
return {
setObject: setObject,
projectVertex: projectVertex,
checkTriangleVisibility: checkTriangleVisibility,
checkBackfaceCulling: checkBackfaceCulling,
pushVertex: pushVertex,
pushNormal: pushNormal,
pushColor: pushColor,
pushUv: pushUv,
pushLine: pushLine,
pushTriangle: pushTriangle
};
}
const renderList = new RenderList();
function projectObject( object ) {
if ( object.visible === false ) return;
if ( object.isLight ) {
_renderData.lights.push( object );
} else if ( object.isMesh || object.isLine || object.isPoints ) {
if ( object.material.visible === false ) return;
if ( object.frustumCulled === true && _frustum.intersectsObject( object ) === false ) return;
addObject( object );
} else if ( object.isSprite ) {
if ( object.material.visible === false ) return;
if ( object.frustumCulled === true && _frustum.intersectsSprite( object ) === false ) return;
addObject( object );
}
const children = object.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
projectObject( children[ i ] );
}
}
function addObject( object ) {
_object = getNextObjectInPool();
_object.id = object.id;
_object.object = object;
_vector3.setFromMatrixPosition( object.matrixWorld );
_vector3.applyMatrix4( _viewProjectionMatrix );
_object.z = _vector3.z;
_object.renderOrder = object.renderOrder;
_renderData.objects.push( _object );
}
this.projectScene = function ( scene, camera, sortObjects, sortElements ) {
_faceCount = 0;
_lineCount = 0;
_spriteCount = 0;
_renderData.elements.length = 0;
if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
_viewMatrix.copy( camera.matrixWorldInverse );
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
_frustum.setFromProjectionMatrix( _viewProjectionMatrix );
//
_objectCount = 0;
_renderData.objects.length = 0;
_renderData.lights.length = 0;
projectObject( scene );
if ( sortObjects === true ) {
_renderData.objects.sort( painterSort );
}
//
const objects = _renderData.objects;
for ( let o = 0, ol = objects.length; o < ol; o ++ ) {
const object = objects[ o ].object;
const geometry = object.geometry;
renderList.setObject( object );
_modelMatrix = object.matrixWorld;
_vertexCount = 0;
if ( object.isMesh ) {
if ( geometry.isBufferGeometry ) {
let material = object.material;
const isMultiMaterial = Array.isArray( material );
const attributes = geometry.attributes;
const groups = geometry.groups;
if ( attributes.position === undefined ) continue;
const positions = attributes.position.array;
for ( let i = 0, l = positions.length; i < l; i += 3 ) {
let x = positions[ i ];
let y = positions[ i + 1 ];
let z = positions[ i + 2 ];
const morphTargets = geometry.morphAttributes.position;
if ( morphTargets !== undefined ) {
const morphTargetsRelative = geometry.morphTargetsRelative;
const morphInfluences = object.morphTargetInfluences;
for ( let t = 0, tl = morphTargets.length; t < tl; t ++ ) {
const influence = morphInfluences[ t ];
if ( influence === 0 ) continue;
const target = morphTargets[ t ];
if ( morphTargetsRelative ) {
x += target.getX( i / 3 ) * influence;
y += target.getY( i / 3 ) * influence;
z += target.getZ( i / 3 ) * influence;
} else {
x += ( target.getX( i / 3 ) - positions[ i ] ) * influence;
y += ( target.getY( i / 3 ) - positions[ i + 1 ] ) * influence;
z += ( target.getZ( i / 3 ) - positions[ i + 2 ] ) * influence;
}
}
}
renderList.pushVertex( x, y, z );
}
if ( attributes.normal !== undefined ) {
const normals = attributes.normal.array;
for ( let i = 0, l = normals.length; i < l; i += 3 ) {
renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] );
}
}
if ( attributes.color !== undefined ) {
const colors = attributes.color.array;
for ( let i = 0, l = colors.length; i < l; i += 3 ) {
renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] );
}
}
if ( attributes.uv !== undefined ) {
const uvs = attributes.uv.array;
for ( let i = 0, l = uvs.length; i < l; i += 2 ) {
renderList.pushUv( uvs[ i ], uvs[ i + 1 ] );
}
}
if ( geometry.index !== null ) {
const indices = geometry.index.array;
if ( groups.length > 0 ) {
for ( let g = 0; g < groups.length; g ++ ) {
const group = groups[ g ];
material = isMultiMaterial === true
? object.material[ group.materialIndex ]
: object.material;
if ( material === undefined ) continue;
for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) {
renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material );
}
}
} else {
for ( let i = 0, l = indices.length; i < l; i += 3 ) {
renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material );
}
}
} else {
if ( groups.length > 0 ) {
for ( let g = 0; g < groups.length; g ++ ) {
const group = groups[ g ];
material = isMultiMaterial === true
? object.material[ group.materialIndex ]
: object.material;
if ( material === undefined ) continue;
for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) {
renderList.pushTriangle( i, i + 1, i + 2, material );
}
}
} else {
for ( let i = 0, l = positions.length / 3; i < l; i += 3 ) {
renderList.pushTriangle( i, i + 1, i + 2, material );
}
}
}
} else if ( geometry.isGeometry ) {
console.error( 'THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.' );
return;
}
} else if ( object.isLine ) {
_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
if ( geometry.isBufferGeometry ) {
const attributes = geometry.attributes;
if ( attributes.position !== undefined ) {
const positions = attributes.position.array;
for ( let i = 0, l = positions.length; i < l; i += 3 ) {
renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
}
if ( attributes.color !== undefined ) {
const colors = attributes.color.array;
for ( let i = 0, l = colors.length; i < l; i += 3 ) {
renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] );
}
}
if ( geometry.index !== null ) {
const indices = geometry.index.array;
for ( let i = 0, l = indices.length; i < l; i += 2 ) {
renderList.pushLine( indices[ i ], indices[ i + 1 ] );
}
} else {
const step = object.isLineSegments ? 2 : 1;
for ( let i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) {
renderList.pushLine( i, i + 1 );
}
}
}
} else if ( geometry.isGeometry ) {
console.error( 'THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.' );
return;
}
} else if ( object.isPoints ) {
_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
if ( geometry.isGeometry ) {
console.error( 'THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.' );
return;
} else if ( geometry.isBufferGeometry ) {
const attributes = geometry.attributes;
if ( attributes.position !== undefined ) {
const positions = attributes.position.array;
for ( let i = 0, l = positions.length; i < l; i += 3 ) {
_vector4.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ], 1 );
_vector4.applyMatrix4( _modelViewProjectionMatrix );
pushPoint( _vector4, object, camera );
}
}
}
} else if ( object.isSprite ) {
object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
_vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 );
_vector4.applyMatrix4( _viewProjectionMatrix );
pushPoint( _vector4, object, camera );
}
}
if ( sortElements === true ) {
_renderData.elements.sort( painterSort );
}
return _renderData;
};
function pushPoint( _vector4, object, camera ) {
const invW = 1 / _vector4.w;
_vector4.z *= invW;
if ( _vector4.z >= - 1 && _vector4.z <= 1 ) {
_sprite = getNextSpriteInPool();
_sprite.id = object.id;
_sprite.x = _vector4.x * invW;
_sprite.y = _vector4.y * invW;
_sprite.z = _vector4.z;
_sprite.renderOrder = object.renderOrder;
_sprite.object = object;
_sprite.rotation = object.rotation;
_sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) );
_sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) );
_sprite.material = object.material;
_renderData.elements.push( _sprite );
}
}
// Pools
function getNextObjectInPool() {
if ( _objectCount === _objectPoolLength ) {
const object = new RenderableObject();
_objectPool.push( object );
_objectPoolLength ++;
_objectCount ++;
return object;
}
return _objectPool[ _objectCount ++ ];
}
function getNextVertexInPool() {
if ( _vertexCount === _vertexPoolLength ) {
const vertex = new RenderableVertex();
_vertexPool.push( vertex );
_vertexPoolLength ++;
_vertexCount ++;
return vertex;
}
return _vertexPool[ _vertexCount ++ ];
}
function getNextFaceInPool() {
if ( _faceCount === _facePoolLength ) {
const face = new RenderableFace();
_facePool.push( face );
_facePoolLength ++;
_faceCount ++;
return face;
}
return _facePool[ _faceCount ++ ];
}
function getNextLineInPool() {
if ( _lineCount === _linePoolLength ) {
const line = new RenderableLine();
_linePool.push( line );
_linePoolLength ++;
_lineCount ++;
return line;
}
return _linePool[ _lineCount ++ ];
}
function getNextSpriteInPool() {
if ( _spriteCount === _spritePoolLength ) {
const sprite = new RenderableSprite();
_spritePool.push( sprite );
_spritePoolLength ++;
_spriteCount ++;
return sprite;
}
return _spritePool[ _spriteCount ++ ];
}
//
function painterSort( a, b ) {
if ( a.renderOrder !== b.renderOrder ) {
return a.renderOrder - b.renderOrder;
} else if ( a.z !== b.z ) {
return b.z - a.z;
} else if ( a.id !== b.id ) {
return a.id - b.id;
} else {
return 0;
}
}
function clipLine( s1, s2 ) {
let alpha1 = 0, alpha2 = 1;
// Calculate the boundary coordinate of each vertex for the near and far clip planes,
// Z = -1 and Z = +1, respectively.
const bc1near = s1.z + s1.w,
bc2near = s2.z + s2.w,
bc1far = - s1.z + s1.w,
bc2far = - s2.z + s2.w;
if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) {
// Both vertices lie entirely within all clip planes.
return true;
} else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) {
// Both vertices lie entirely outside one of the clip planes.
return false;
} else {
// The line segment spans at least one clip plane.
if ( bc1near < 0 ) {
// v1 lies outside the near plane, v2 inside
alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) );
} else if ( bc2near < 0 ) {
// v2 lies outside the near plane, v1 inside
alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) );
}
if ( bc1far < 0 ) {
// v1 lies outside the far plane, v2 inside
alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) );
} else if ( bc2far < 0 ) {
// v2 lies outside the far plane, v2 inside
alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) );
}
if ( alpha2 < alpha1 ) {
// The line segment spans two boundaries, but is outside both of them.
// (This can't happen when we're only clipping against just near/far but good
// to leave the check here for future usage if other clip planes are added.)
return false;
} else {
// Update the s1 and s2 vertices to match the clipped line segment.
s1.lerp( s2, alpha1 );
s2.lerp( s1, 1 - alpha2 );
return true;
}
}
}
}
}
export { RenderableObject, RenderableFace, RenderableVertex, RenderableLine, RenderableSprite, Projector };

View File

@@ -0,0 +1,553 @@
import {
Box2,
Camera,
Color,
Matrix3,
Matrix4,
Object3D,
Vector3
} from 'three';
import { Projector } from '../renderers/Projector.js';
import { RenderableFace } from '../renderers/Projector.js';
import { RenderableLine } from '../renderers/Projector.js';
import { RenderableSprite } from '../renderers/Projector.js';
class SVGObject extends Object3D {
constructor( node ) {
super();
this.node = node;
}
}
SVGObject.prototype.isSVGObject = true;
class SVGRenderer {
constructor() {
let _renderData, _elements, _lights,
_svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf,
_v1, _v2, _v3,
_svgNode,
_pathCount = 0,
_precision = null,
_quality = 1,
_currentPath, _currentStyle;
const _this = this,
_clipBox = new Box2(),
_elemBox = new Box2(),
_color = new Color(),
_diffuseColor = new Color(),
_ambientLight = new Color(),
_directionalLights = new Color(),
_pointLights = new Color(),
_clearColor = new Color(),
_vector3 = new Vector3(), // Needed for PointLight
_centroid = new Vector3(),
_normal = new Vector3(),
_normalViewMatrix = new Matrix3(),
_viewMatrix = new Matrix4(),
_viewProjectionMatrix = new Matrix4(),
_svgPathPool = [],
_projector = new Projector(),
_svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
this.domElement = _svg;
this.autoClear = true;
this.sortObjects = true;
this.sortElements = true;
this.overdraw = 0.5;
this.info = {
render: {
vertices: 0,
faces: 0
}
};
this.setQuality = function ( quality ) {
switch ( quality ) {
case 'high': _quality = 1; break;
case 'low': _quality = 0; break;
}
};
this.setClearColor = function ( color ) {
_clearColor.set( color );
};
this.setPixelRatio = function () {};
this.setSize = function ( width, height ) {
_svgWidth = width; _svgHeight = height;
_svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2;
_svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight );
_svg.setAttribute( 'width', _svgWidth );
_svg.setAttribute( 'height', _svgHeight );
_clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf );
_clipBox.max.set( _svgWidthHalf, _svgHeightHalf );
};
this.getSize = function () {
return {
width: _svgWidth,
height: _svgHeight
};
};
this.setPrecision = function ( precision ) {
_precision = precision;
};
function removeChildNodes() {
_pathCount = 0;
while ( _svg.childNodes.length > 0 ) {
_svg.removeChild( _svg.childNodes[ 0 ] );
}
}
function convert( c ) {
return _precision !== null ? c.toFixed( _precision ) : c;
}
this.clear = function () {
removeChildNodes();
_svg.style.backgroundColor = _clearColor.getStyle();
};
this.render = function ( scene, camera ) {
if ( camera instanceof Camera === false ) {
console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' );
return;
}
const background = scene.background;
if ( background && background.isColor ) {
removeChildNodes();
_svg.style.backgroundColor = background.getStyle();
} else if ( this.autoClear === true ) {
this.clear();
}
_this.info.render.vertices = 0;
_this.info.render.faces = 0;
_viewMatrix.copy( camera.matrixWorldInverse );
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
_renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
_elements = _renderData.elements;
_lights = _renderData.lights;
_normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
calculateLights( _lights );
// reset accumulated path
_currentPath = '';
_currentStyle = '';
for ( let e = 0, el = _elements.length; e < el; e ++ ) {
const element = _elements[ e ];
const material = element.material;
if ( material === undefined || material.opacity === 0 ) continue;
_elemBox.makeEmpty();
if ( element instanceof RenderableSprite ) {
_v1 = element;
_v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
renderSprite( _v1, element, material );
} else if ( element instanceof RenderableLine ) {
_v1 = element.v1; _v2 = element.v2;
_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
_elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
if ( _clipBox.intersectsBox( _elemBox ) === true ) {
renderLine( _v1, _v2, material );
}
} else if ( element instanceof RenderableFace ) {
_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
if ( this.overdraw > 0 ) {
expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
}
_elemBox.setFromPoints( [
_v1.positionScreen,
_v2.positionScreen,
_v3.positionScreen
] );
if ( _clipBox.intersectsBox( _elemBox ) === true ) {
renderFace3( _v1, _v2, _v3, element, material );
}
}
}
flushPath(); // just to flush last svg:path
scene.traverseVisible( function ( object ) {
if ( object.isSVGObject ) {
_vector3.setFromMatrixPosition( object.matrixWorld );
_vector3.applyMatrix4( _viewProjectionMatrix );
if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
const x = _vector3.x * _svgWidthHalf;
const y = - _vector3.y * _svgHeightHalf;
const node = object.node;
node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' );
_svg.appendChild( node );
}
} );
};
function calculateLights( lights ) {
_ambientLight.setRGB( 0, 0, 0 );
_directionalLights.setRGB( 0, 0, 0 );
_pointLights.setRGB( 0, 0, 0 );
for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
const light = lights[ l ];
const lightColor = light.color;
if ( light.isAmbientLight ) {
_ambientLight.r += lightColor.r;
_ambientLight.g += lightColor.g;
_ambientLight.b += lightColor.b;
} else if ( light.isDirectionalLight ) {
_directionalLights.r += lightColor.r;
_directionalLights.g += lightColor.g;
_directionalLights.b += lightColor.b;
} else if ( light.isPointLight ) {
_pointLights.r += lightColor.r;
_pointLights.g += lightColor.g;
_pointLights.b += lightColor.b;
}
}
}
function calculateLight( lights, position, normal, color ) {
for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
const light = lights[ l ];
const lightColor = light.color;
if ( light.isDirectionalLight ) {
const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
let amount = normal.dot( lightPosition );
if ( amount <= 0 ) continue;
amount *= light.intensity;
color.r += lightColor.r * amount;
color.g += lightColor.g * amount;
color.b += lightColor.b * amount;
} else if ( light.isPointLight ) {
const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
if ( amount <= 0 ) continue;
amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
if ( amount == 0 ) continue;
amount *= light.intensity;
color.r += lightColor.r * amount;
color.g += lightColor.g * amount;
color.b += lightColor.b * amount;
}
}
}
function renderSprite( v1, element, material ) {
let scaleX = element.scale.x * _svgWidthHalf;
let scaleY = element.scale.y * _svgHeightHalf;
if ( material.isPointsMaterial ) {
scaleX *= material.size;
scaleY *= material.size;
}
const path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z';
let style = '';
if ( material.isSpriteMaterial || material.isPointsMaterial ) {
style = 'fill:' + material.color.getStyle() + ';fill-opacity:' + material.opacity;
}
addPath( style, path );
}
function renderLine( v1, v2, material ) {
const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y );
if ( material.isLineBasicMaterial ) {
let style = 'fill:none;stroke:' + material.color.getStyle() + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap;
if ( material.isLineDashedMaterial ) {
style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize;
}
addPath( style, path );
}
}
function renderFace3( v1, v2, v3, element, material ) {
_this.info.render.vertices += 3;
_this.info.render.faces ++;
const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z';
let style = '';
if ( material.isMeshBasicMaterial ) {
_color.copy( material.color );
if ( material.vertexColors ) {
_color.multiply( element.color );
}
} else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
_diffuseColor.copy( material.color );
if ( material.vertexColors ) {
_diffuseColor.multiply( element.color );
}
_color.copy( _ambientLight );
_centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
calculateLight( _lights, _centroid, element.normalModel, _color );
_color.multiply( _diffuseColor ).add( material.emissive );
} else if ( material.isMeshNormalMaterial ) {
_normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize();
_color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
}
if ( material.wireframe ) {
style = 'fill:none;stroke:' + _color.getStyle() + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin;
} else {
style = 'fill:' + _color.getStyle() + ';fill-opacity:' + material.opacity;
}
addPath( style, path );
}
// Hide anti-alias gaps
function expand( v1, v2, pixels ) {
let x = v2.x - v1.x, y = v2.y - v1.y;
const det = x * x + y * y;
if ( det === 0 ) return;
const idet = pixels / Math.sqrt( det );
x *= idet; y *= idet;
v2.x += x; v2.y += y;
v1.x -= x; v1.y -= y;
}
function addPath( style, path ) {
if ( _currentStyle === style ) {
_currentPath += path;
} else {
flushPath();
_currentStyle = style;
_currentPath = path;
}
}
function flushPath() {
if ( _currentPath ) {
_svgNode = getPathNode( _pathCount ++ );
_svgNode.setAttribute( 'd', _currentPath );
_svgNode.setAttribute( 'style', _currentStyle );
_svg.appendChild( _svgNode );
}
_currentPath = '';
_currentStyle = '';
}
function getPathNode( id ) {
if ( _svgPathPool[ id ] == null ) {
_svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
if ( _quality == 0 ) {
_svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
}
return _svgPathPool[ id ];
}
return _svgPathPool[ id ];
}
}
}
export { SVGObject, SVGRenderer };

View File

@@ -0,0 +1,22 @@
import Node from 'three-nodes/core/Node.js';
class SlotNode extends Node {
constructor( node, name, nodeType ) {
super( nodeType );
this.node = node;
this.name = name;
}
generate( builder ) {
return this.node.build( builder, this.getNodeType( builder ) );
}
}
export default SlotNode;

View File

@@ -0,0 +1,540 @@
import NodeBuilder, { shaderStages } from 'three-nodes/core/NodeBuilder.js';
import SlotNode from './SlotNode.js';
import GLSLNodeParser from 'three-nodes/parsers/GLSLNodeParser.js';
import WebGLPhysicalContextNode from './WebGLPhysicalContextNode.js';
import { ShaderChunk, ShaderLib, UniformsUtils, UniformsLib,
LinearEncoding, RGBAFormat, UnsignedByteType, sRGBEncoding } from 'three';
const nodeShaderLib = {
LineBasicNodeMaterial: ShaderLib.basic,
MeshBasicNodeMaterial: ShaderLib.basic,
PointsNodeMaterial: ShaderLib.points,
MeshStandardNodeMaterial: ShaderLib.standard
};
function getIncludeSnippet( name ) {
return `#include <${name}>`;
}
function getShaderStageProperty( shaderStage ) {
return `${shaderStage}Shader`;
}
class WebGLNodeBuilder extends NodeBuilder {
constructor( object, renderer, shader ) {
super( object, renderer, new GLSLNodeParser() );
this.shader = shader;
this.slots = { vertex: [], fragment: [] };
this._parseObject();
}
addSlot( shaderStage, slotNode ) {
this.slots[ shaderStage ].push( slotNode );
return this.addFlow( shaderStage, slotNode );
}
addFlowCode( code ) {
if ( ! /;\s*$/.test( code ) ) {
code += ';';
}
super.addFlowCode( code + '\n\t' );
}
_parseObject() {
const material = this.material;
const type = material.type;
// shader lib
if ( nodeShaderLib[ type ] !== undefined ) {
const shaderLib = nodeShaderLib[ type ];
const shader = this.shader;
shader.vertexShader = shaderLib.vertexShader;
shader.fragmentShader = shaderLib.fragmentShader;
shader.uniforms = UniformsUtils.merge( [ shaderLib.uniforms, UniformsLib.lights ] );
}
// parse inputs
if ( material.colorNode && material.colorNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.colorNode, 'COLOR', 'vec4' ) );
}
if ( material.opacityNode && material.opacityNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.opacityNode, 'OPACITY', 'float' ) );
}
if ( material.normalNode && material.normalNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.normalNode, 'NORMAL', 'vec3' ) );
}
if ( material.emissiveNode && material.emissiveNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.emissiveNode, 'EMISSIVE', 'vec3' ) );
}
if ( material.metalnessNode && material.metalnessNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.metalnessNode, 'METALNESS', 'float' ) );
}
if ( material.roughnessNode && material.roughnessNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.roughnessNode, 'ROUGHNESS', 'float' ) );
}
if ( material.clearcoatNode && material.clearcoatNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.clearcoatNode, 'CLEARCOAT', 'float' ) );
}
if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) {
this.addSlot( 'fragment', new SlotNode( material.clearcoatRoughnessNode, 'CLEARCOAT_ROUGHNESS', 'float' ) );
}
if ( material.envNode && material.envNode.isNode ) {
const envRadianceNode = new WebGLPhysicalContextNode( WebGLPhysicalContextNode.RADIANCE, material.envNode );
const envIrradianceNode = new WebGLPhysicalContextNode( WebGLPhysicalContextNode.IRRADIANCE, material.envNode );
this.addSlot( 'fragment', new SlotNode( envRadianceNode, 'RADIANCE', 'vec3' ) );
this.addSlot( 'fragment', new SlotNode( envIrradianceNode, 'IRRADIANCE', 'vec3' ) );
}
if ( material.positionNode && material.positionNode.isNode ) {
this.addSlot( 'vertex', new SlotNode( material.positionNode, 'POSITION', 'vec3' ) );
}
if ( material.sizeNode && material.sizeNode.isNode ) {
this.addSlot( 'vertex', new SlotNode( material.sizeNode, 'SIZE', 'float' ) );
}
}
getTexture( textureProperty, uvSnippet, biasSnippet = null ) {
if ( biasSnippet !== null ) {
return `texture2D( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
} else {
return `texture2D( ${textureProperty}, ${uvSnippet} )`;
}
}
getCubeTexture( textureProperty, uvSnippet, biasSnippet = null ) {
const textureCube = 'textureCubeLodEXT'; // textureCubeLodEXT textureLod
if ( biasSnippet !== null ) {
return `${textureCube}( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
} else {
return `${textureCube}( ${textureProperty}, ${uvSnippet} )`;
}
}
getUniforms( shaderStage ) {
const uniforms = this.uniforms[ shaderStage ];
let snippet = '';
for ( const uniform of uniforms ) {
if ( uniform.type === 'texture' ) {
snippet += `uniform sampler2D ${uniform.name}; `;
} else if ( uniform.type === 'cubeTexture' ) {
snippet += `uniform samplerCube ${uniform.name}; `;
} else {
const vectorType = this.getVectorType( uniform.type );
snippet += `uniform ${vectorType} ${uniform.name}; `;
}
}
return snippet;
}
getAttributes( shaderStage ) {
let snippet = '';
if ( shaderStage === 'vertex' ) {
const attributes = this.attributes;
for ( let index = 0; index < attributes.length; index ++ ) {
const attribute = attributes[ index ];
// ignore common attributes to prevent redefinitions
if ( attribute.name === 'uv' || attribute.name === 'position' || attribute.name === 'normal' )
continue;
snippet += `attribute ${attribute.type} ${attribute.name}; `;
}
}
return snippet;
}
getVarys( /* shaderStage */ ) {
let snippet = '';
const varys = this.varys;
for ( let index = 0; index < varys.length; index ++ ) {
const vary = varys[ index ];
snippet += `varying ${vary.type} ${vary.name}; `;
}
return snippet;
}
addCodeAfterSnippet( shaderStage, snippet, code ) {
const shaderProperty = getShaderStageProperty( shaderStage );
let source = this[ shaderProperty ];
const index = source.indexOf( snippet );
if ( index !== - 1 ) {
const start = source.substring( 0, index + snippet.length );
const end = source.substring( index + snippet.length );
source = `${start}\n${code}\n${end}`;
}
this[ shaderProperty ] = source;
}
addCodeAfterInclude( shaderStage, includeName, code ) {
const includeSnippet = getIncludeSnippet( includeName );
this.addCodeAfterSnippet( shaderStage, includeSnippet, code );
}
replaceCode( shaderStage, source, target ) {
const shaderProperty = getShaderStageProperty( shaderStage );
this.shader[ shaderProperty ] = this.shader[ shaderProperty ].replaceAll( source, target );
}
parseInclude( shaderStage, ...includes ) {
for ( const name of includes ) {
const includeSnippet = getIncludeSnippet( name );
const code = ShaderChunk[ name ];
this.replaceCode( shaderStage, includeSnippet, code );
}
}
getTextureEncodingFromMap( map ) {
const isWebGL2 = this.renderer.capabilities.isWebGL2;
if ( isWebGL2 && map && map.isTexture && map.format === RGBAFormat && map.type === UnsignedByteType && map.encoding === sRGBEncoding ) {
return LinearEncoding; // disable inline decode for sRGB textures in WebGL 2
}
return super.getTextureEncodingFromMap( map );
}
buildCode() {
const shaderData = {};
for ( const shaderStage of shaderStages ) {
const uniforms = this.getUniforms( shaderStage );
const attributes = this.getAttributes( shaderStage );
const varys = this.getVarys( shaderStage );
const vars = this.getVars( shaderStage );
const codes = this.getCodes( shaderStage );
shaderData[ shaderStage ] = `${this.getSignature()}
// <node_builder>
// uniforms
${uniforms}
// attributes
${attributes}
// varys
${varys}
// vars
${vars}
// codes
${codes}
// </node_builder>
${this.shader[ getShaderStageProperty( shaderStage ) ]}
`;
}
this.vertexShader = shaderData.vertex;
this.fragmentShader = shaderData.fragment;
}
build() {
super.build();
this._addSnippets();
this._addUniforms();
this.shader.vertexShader = this.vertexShader;
this.shader.fragmentShader = this.fragmentShader;
return this;
}
getSlot( shaderStage, name ) {
const slots = this.slots[ shaderStage ];
for ( const node of slots ) {
if ( node.name === name ) {
return this.getFlowData( shaderStage, node );
}
}
}
_addSnippets() {
this.parseInclude( 'fragment', 'lights_physical_fragment' );
const colorSlot = this.getSlot( 'fragment', 'COLOR' );
const opacityNode = this.getSlot( 'fragment', 'OPACITY' );
const normalSlot = this.getSlot( 'fragment', 'NORMAL' );
const emissiveNode = this.getSlot( 'fragment', 'EMISSIVE' );
const roughnessNode = this.getSlot( 'fragment', 'ROUGHNESS' );
const metalnessNode = this.getSlot( 'fragment', 'METALNESS' );
const clearcoatNode = this.getSlot( 'fragment', 'CLEARCOAT' );
const clearcoatRoughnessNode = this.getSlot( 'fragment', 'CLEARCOAT_ROUGHNESS' );
const positionNode = this.getSlot( 'vertex', 'POSITION' );
const sizeNode = this.getSlot( 'vertex', 'SIZE' );
if ( colorSlot !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'color_fragment',
`${colorSlot.code}\n\tdiffuseColor = ${colorSlot.result};`
);
}
if ( opacityNode !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'alphatest_fragment',
`${opacityNode.code}\n\tdiffuseColor.a = ${opacityNode.result};`
);
}
if ( normalSlot !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'normal_fragment_begin',
`${normalSlot.code}\n\tnormal = ${normalSlot.result};`
);
}
if ( emissiveNode !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'emissivemap_fragment',
`${emissiveNode.code}\n\ttotalEmissiveRadiance = ${emissiveNode.result};`
);
}
if ( roughnessNode !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'roughnessmap_fragment',
`${roughnessNode.code}\n\troughnessFactor = ${roughnessNode.result};`
);
}
if ( metalnessNode !== undefined ) {
this.addCodeAfterInclude(
'fragment',
'metalnessmap_fragment',
`${metalnessNode.code}\n\tmetalnessFactor = ${metalnessNode.result};`
);
}
if ( clearcoatNode !== undefined ) {
this.addCodeAfterSnippet(
'fragment',
'material.clearcoatRoughness = clearcoatRoughness;',
`${clearcoatNode.code}\n\tmaterial.clearcoat = ${clearcoatNode.result};`
);
}
if ( clearcoatRoughnessNode !== undefined ) {
this.addCodeAfterSnippet(
'fragment',
'material.clearcoatRoughness = clearcoatRoughness;',
`${clearcoatRoughnessNode.code}\n\tmaterial.clearcoatRoughness = ${clearcoatRoughnessNode.result};`
);
}
if ( positionNode !== undefined ) {
this.addCodeAfterInclude(
'vertex',
'begin_vertex',
`${positionNode.code}\n\ttransformed = ${positionNode.result};`
);
}
if ( sizeNode !== undefined ) {
this.addCodeAfterSnippet(
'vertex',
'gl_PointSize = size;',
`${sizeNode.code}\n\tgl_PointSize = ${sizeNode.result};`
);
}
for ( const shaderStage of shaderStages ) {
this.addCodeAfterSnippet(
shaderStage,
'main() {',
this.flowCode[ shaderStage ]
);
}
}
_addUniforms() {
for ( const shaderStage of shaderStages ) {
// uniforms
for ( const uniform of this.uniforms[ shaderStage ] ) {
this.shader.uniforms[ uniform.name ] = uniform;
}
}
}
}
export { WebGLNodeBuilder };

View File

@@ -0,0 +1,34 @@
import { WebGLNodeBuilder } from './WebGLNodeBuilder.js';
import NodeFrame from 'three-nodes/core/NodeFrame.js';
import { Material } from 'three';
const builders = new WeakMap();
export const nodeFrame = new NodeFrame();
Material.prototype.onBuild = function ( object, parameters, renderer ) {
builders.set( this, new WebGLNodeBuilder( object, renderer, parameters ).build() );
};
Material.prototype.onBeforeRender = function ( renderer, scene, camera, geometry, object ) {
const nodeBuilder = builders.get( this );
if ( nodeBuilder !== undefined ) {
nodeFrame.material = this;
nodeFrame.camera = camera;
nodeFrame.object = object;
nodeFrame.renderer = renderer;
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
};

View File

@@ -0,0 +1,45 @@
import ContextNode from 'three-nodes/core/ContextNode.js';
import NormalNode from 'three-nodes/accessors/NormalNode.js';
import ExpressionNode from 'three-nodes/core/ExpressionNode.js';
import FloatNode from 'three-nodes/inputs/FloatNode.js';
class WebGLPhysicalContextNode extends ContextNode {
static RADIANCE = 'radiance';
static IRRADIANCE = 'irradiance';
constructor( scope, node ) {
super( node, 'vec3' );
this.scope = scope;
}
generate( builder, output ) {
const scope = this.scope;
let roughness = null;
if ( scope === WebGLPhysicalContextNode.RADIANCE ) {
roughness = new ExpressionNode( 'roughnessFactor', 'float' );
} else if ( scope === WebGLPhysicalContextNode.IRRADIANCE ) {
roughness = new FloatNode( 1.0 ).setConst( true );
this.context.uv = new NormalNode( NormalNode.WORLD );
}
this.context.roughness = roughness;
return super.generate( builder, output );
}
}
export default WebGLPhysicalContextNode;

View File

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

View File

@@ -0,0 +1,106 @@
import { GPULoadOp, GPUStoreOp } from './constants.js';
import { Color } from 'three';
let _clearAlpha;
const _clearColor = new Color();
class WebGPUBackground {
constructor( renderer ) {
this.renderer = renderer;
this.forceClear = false;
}
clear() {
this.forceClear = true;
}
update( scene ) {
const renderer = this.renderer;
const background = ( scene.isScene === true ) ? scene.background : null;
let forceClear = this.forceClear;
if ( background === null ) {
// no background settings, use clear color configuration from the renderer
_clearColor.copy( renderer._clearColor );
_clearAlpha = renderer._clearAlpha;
} else if ( background.isColor === true ) {
// background is an opaque color
_clearColor.copy( background );
_clearAlpha = 1;
forceClear = true;
} else {
console.error( 'THREE.WebGPURenderer: Unsupported background configuration.', background );
}
// configure render pass descriptor
const renderPassDescriptor = renderer._renderPassDescriptor;
const colorAttachment = renderPassDescriptor.colorAttachments[ 0 ];
const depthStencilAttachment = renderPassDescriptor.depthStencilAttachment;
if ( renderer.autoClear === true || forceClear === true ) {
if ( renderer.autoClearColor === true ) {
colorAttachment.clearValue = { r: _clearColor.r, g: _clearColor.g, b: _clearColor.b, a: _clearAlpha };
colorAttachment.loadOp = GPULoadOp.Clear;
colorAttachment.storeOp = GPUStoreOp.Store;
} else {
colorAttachment.loadOp = GPULoadOp.Load;
}
if ( renderer.autoClearDepth === true ) {
depthStencilAttachment.depthClearValue = renderer._clearDepth;
depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
} else {
depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
}
if ( renderer.autoClearStencil === true ) {
depthStencilAttachment.stencilClearValue = renderer._clearStencil;
depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
} else {
depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
}
} else {
colorAttachment.loadOp = GPULoadOp.Load;
depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
}
this.forceClear = false;
}
}
export default WebGPUBackground;

View File

@@ -0,0 +1,22 @@
class WebGPUBinding {
constructor( name = '' ) {
this.name = name;
this.visibility = null;
this.type = null; // read-only
this.isShared = false;
}
setVisibility( visibility ) {
this.visibility = visibility;
}
}
export default WebGPUBinding;

View File

@@ -0,0 +1,254 @@
class WebGPUBindings {
constructor( device, info, properties, textures, renderPipelines, computePipelines, attributes, nodes ) {
this.device = device;
this.info = info;
this.properties = properties;
this.textures = textures;
this.renderPipelines = renderPipelines;
this.computePipelines = computePipelines;
this.attributes = attributes;
this.nodes = nodes;
this.uniformsData = new WeakMap();
this.updateMap = new WeakMap();
}
get( object ) {
let data = this.uniformsData.get( object );
if ( data === undefined ) {
// each object defines an array of bindings (ubos, textures, samplers etc.)
const nodeBuilder = this.nodes.get( object );
const bindings = nodeBuilder.getBindings();
// setup (static) binding layout and (dynamic) binding group
const renderPipeline = this.renderPipelines.get( object );
const bindLayout = renderPipeline.pipeline.getBindGroupLayout( 0 );
const bindGroup = this._createBindGroup( bindings, bindLayout );
data = {
layout: bindLayout,
group: bindGroup,
bindings: bindings
};
this.uniformsData.set( object, data );
}
return data;
}
remove( object ) {
this.uniformsData.delete( object );
}
getForCompute( param ) {
let data = this.uniformsData.get( param );
if ( data === undefined ) {
// bindings are not yet retrieved via node material
const bindings = param.bindings !== undefined ? param.bindings.slice() : [];
const computePipeline = this.computePipelines.get( param );
const bindLayout = computePipeline.getBindGroupLayout( 0 );
const bindGroup = this._createBindGroup( bindings, bindLayout );
data = {
layout: bindLayout,
group: bindGroup,
bindings: bindings
};
this.uniformsData.set( param, data );
}
return data;
}
update( object ) {
const textures = this.textures;
const data = this.get( object );
const bindings = data.bindings;
const updateMap = this.updateMap;
const frame = this.info.render.frame;
let needsBindGroupRefresh = false;
// iterate over all bindings and check if buffer updates or a new binding group is required
for ( const binding of bindings ) {
const isShared = binding.isShared;
const isUpdated = updateMap.get( binding ) === frame;
if ( isShared && isUpdated ) continue;
if ( binding.isUniformBuffer ) {
const buffer = binding.getBuffer();
const bufferGPU = binding.bufferGPU;
const needsBufferWrite = binding.update();
if ( needsBufferWrite === true ) {
this.device.queue.writeBuffer( bufferGPU, 0, buffer, 0 );
}
} else if ( binding.isStorageBuffer ) {
const attribute = binding.attribute;
this.attributes.update( attribute, false, binding.usage );
} else if ( binding.isSampler ) {
const texture = binding.getTexture();
textures.updateSampler( texture );
const samplerGPU = textures.getSampler( texture );
if ( binding.samplerGPU !== samplerGPU ) {
binding.samplerGPU = samplerGPU;
needsBindGroupRefresh = true;
}
} else if ( binding.isSampledTexture ) {
const texture = binding.getTexture();
const needsTextureRefresh = textures.updateTexture( texture );
const textureGPU = textures.getTextureGPU( texture );
if ( textureGPU !== undefined && binding.textureGPU !== textureGPU || needsTextureRefresh === true ) {
binding.textureGPU = textureGPU;
needsBindGroupRefresh = true;
}
}
updateMap.set( binding, frame );
}
if ( needsBindGroupRefresh === true ) {
data.group = this._createBindGroup( bindings, data.layout );
}
}
dispose() {
this.uniformsData = new WeakMap();
this.updateMap = new WeakMap();
}
_createBindGroup( bindings, layout ) {
let bindingPoint = 0;
const entries = [];
for ( const binding of bindings ) {
if ( binding.isUniformBuffer ) {
if ( binding.bufferGPU === null ) {
const byteLength = binding.getByteLength();
binding.bufferGPU = this.device.createBuffer( {
size: byteLength,
usage: binding.usage,
} );
}
entries.push( { binding: bindingPoint, resource: { buffer: binding.bufferGPU } } );
} else if ( binding.isStorageBuffer ) {
if ( binding.bufferGPU === null ) {
const attribute = binding.attribute;
this.attributes.update( attribute, false, binding.usage );
binding.bufferGPU = this.attributes.get( attribute ).buffer;
}
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 ) {
if ( binding.isSampledCubeTexture ) {
binding.textureGPU = this.textures.getDefaultCubeTexture();
} else {
binding.textureGPU = this.textures.getDefaultTexture();
}
}
entries.push( { binding: bindingPoint, resource: binding.textureGPU.createView( { dimension: binding.dimension } ) } );
}
bindingPoint ++;
}
return this.device.createBindGroup( {
layout: layout,
entries: entries
} );
}
}
export default WebGPUBindings;

View File

@@ -0,0 +1,33 @@
import { GPUChunkSize } from './constants.js';
function getFloatLength( floatLength ) {
// ensure chunk size alignment (STD140 layout)
return floatLength + ( ( GPUChunkSize - ( floatLength % GPUChunkSize ) ) % GPUChunkSize );
}
function getVectorLength( count, vectorLength = 4 ) {
const strideLength = getStrideLength( vectorLength );
const floatLength = strideLength * count;
return getFloatLength( floatLength );
}
function getStrideLength( vectorLength ) {
const strideLength = 4;
return vectorLength + ( ( strideLength - ( vectorLength % strideLength ) ) % strideLength );
}
export {
getFloatLength,
getVectorLength,
getStrideLength
};

View File

@@ -0,0 +1,65 @@
import WebGPUProgrammableStage from './WebGPUProgrammableStage.js';
class WebGPUComputePipelines {
constructor( device ) {
this.device = device;
this.pipelines = new WeakMap();
this.stages = {
compute: new WeakMap()
};
}
get( param ) {
let pipeline = this.pipelines.get( param );
// @TODO: Reuse compute pipeline if possible, introduce WebGPUComputePipeline
if ( pipeline === undefined ) {
const device = this.device;
const shader = {
computeShader: param.shader
};
// programmable stage
let stageCompute = this.stages.compute.get( shader );
if ( stageCompute === undefined ) {
stageCompute = new WebGPUProgrammableStage( device, shader.computeShader, 'compute' );
this.stages.compute.set( shader, stageCompute );
}
pipeline = device.createComputePipeline( {
compute: stageCompute.stage
} );
this.pipelines.set( param, pipeline );
}
return pipeline;
}
dispose() {
this.pipelines = new WeakMap();
this.stages = {
compute: new WeakMap()
};
}
}
export default WebGPUComputePipelines;

View File

@@ -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;

View File

@@ -0,0 +1,74 @@
class WebGPUInfo {
constructor() {
this.autoReset = true;
this.render = {
frame: 0,
drawCalls: 0,
triangles: 0,
points: 0,
lines: 0
};
this.memory = {
geometries: 0,
textures: 0
};
}
update( object, count, instanceCount ) {
this.render.drawCalls ++;
if ( object.isMesh ) {
this.render.triangles += instanceCount * ( count / 3 );
} else if ( object.isPoints ) {
this.render.points += instanceCount * count;
} else if ( object.isLineSegments ) {
this.render.lines += instanceCount * ( count / 2 );
} else if ( object.isLine ) {
this.render.lines += instanceCount * ( 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;
this.memory.textures = 0;
}
}
export default WebGPUInfo;

View File

@@ -0,0 +1,42 @@
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 ( geometry.isBufferGeometry !== true ) {
throw new Error( 'THREE.WebGPURenderer: This renderer only supports THREE.BufferGeometry for geometries.' );
}
if ( updateMap.get( geometry ) !== frame ) {
this.geometries.update( geometry );
updateMap.set( geometry, frame );
}
}
dispose() {
this.updateMap = new WeakMap();
}
}
export default WebGPUObjects;

View File

@@ -0,0 +1,22 @@
let _id = 0;
class WebGPUProgrammableStage {
constructor( device, code, type ) {
this.id = _id ++;
this.code = code;
this.type = type;
this.usedTimes = 0;
this.stage = {
module: device.createShaderModule( { code } ),
entryPoint: 'main'
};
}
}
export default WebGPUProgrammableStage;

View File

@@ -0,0 +1,38 @@
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;

View File

@@ -0,0 +1,199 @@
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 cameras = lists.get( scene );
let list;
if ( cameras === undefined ) {
list = new WebGPURenderList();
lists.set( scene, new WeakMap() );
lists.get( scene ).set( camera, list );
} else {
list = cameras.get( camera );
if ( list === undefined ) {
list = new WebGPURenderList();
cameras.set( camera, list );
}
}
return list;
}
dispose() {
this.lists = new WeakMap();
}
}
export default WebGPURenderLists;

View File

@@ -0,0 +1,737 @@
import { GPUPrimitiveTopology, GPUIndexFormat, GPUCompareFunction, GPUFrontFace, GPUCullMode, GPUVertexFormat, GPUBlendFactor, GPUBlendOperation, BlendColorFactor, OneMinusBlendColorFactor, GPUColorWriteFlags, GPUStencilOperation, GPUInputStepMode } from './constants.js';
import {
FrontSide, BackSide, DoubleSide,
NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc,
KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp,
NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending,
AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation,
ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor
} from 'three';
class WebGPURenderPipeline {
constructor( device, renderer, sampleCount ) {
this.cacheKey = null;
this.shaderAttributes = null;
this.stageVertex = null;
this.stageFragment = null;
this.usedTimes = 0;
this._device = device;
this._renderer = renderer;
this._sampleCount = sampleCount;
}
init( cacheKey, stageVertex, stageFragment, object, nodeBuilder ) {
const material = object.material;
const geometry = object.geometry;
// determine shader attributes
const shaderAttributes = this._getShaderAttributes( nodeBuilder, geometry );
// vertex buffers
const vertexBuffers = [];
for ( const attribute of shaderAttributes ) {
const name = attribute.name;
const geometryAttribute = geometry.getAttribute( name );
const stepMode = ( geometryAttribute !== undefined && geometryAttribute.isInstancedBufferAttribute ) ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
vertexBuffers.push( {
arrayStride: attribute.arrayStride,
attributes: [ { shaderLocation: attribute.slot, offset: 0, format: attribute.format } ],
stepMode: stepMode
} );
}
this.cacheKey = cacheKey;
this.shaderAttributes = shaderAttributes;
this.stageVertex = stageVertex;
this.stageFragment = stageFragment;
// blending
let alphaBlend = {};
let colorBlend = {};
if ( material.transparent === true && material.blending !== NoBlending ) {
alphaBlend = this._getAlphaBlend( material );
colorBlend = this._getColorBlend( material );
}
// stencil
let stencilFront = {};
if ( material.stencilWrite === true ) {
stencilFront = {
compare: this._getStencilCompare( material ),
failOp: this._getStencilOperation( material.stencilFail ),
depthFailOp: this._getStencilOperation( material.stencilZFail ),
passOp: this._getStencilOperation( material.stencilZPass )
};
}
//
const primitiveState = this._getPrimitiveState( object, material );
const colorWriteMask = this._getColorWriteMask( material );
const depthCompare = this._getDepthCompare( material );
const colorFormat = this._renderer.getCurrentColorFormat();
const depthStencilFormat = this._renderer.getCurrentDepthStencilFormat();
this.pipeline = this._device.createRenderPipeline( {
vertex: Object.assign( {}, stageVertex.stage, { buffers: vertexBuffers } ),
fragment: Object.assign( {}, stageFragment.stage, { targets: [ {
format: colorFormat,
blend: {
alpha: alphaBlend,
color: colorBlend
},
writeMask: colorWriteMask
} ] } ),
primitive: primitiveState,
depthStencil: {
format: depthStencilFormat,
depthWriteEnabled: material.depthWrite,
depthCompare: depthCompare,
stencilFront: stencilFront,
stencilBack: {}, // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used)
stencilReadMask: material.stencilFuncMask,
stencilWriteMask: material.stencilWriteMask
},
multisample: {
count: this._sampleCount
}
} );
}
_getArrayStride( type, bytesPerElement ) {
// @TODO: This code is GLSL specific. We need to update when we switch to WGSL.
if ( type === 'float' || type === 'int' || type === 'uint' ) return bytesPerElement;
if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return bytesPerElement * 2;
if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return bytesPerElement * 3;
if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return bytesPerElement * 4;
console.error( 'THREE.WebGPURenderer: Shader variable type not supported yet.', type );
}
_getAlphaBlend( material ) {
const blending = material.blending;
const premultipliedAlpha = material.premultipliedAlpha;
let alphaBlend = undefined;
switch ( blending ) {
case NormalBlending:
if ( premultipliedAlpha === false ) {
alphaBlend = {
srcFactor: GPUBlendFactor.One,
dstFactor: GPUBlendFactor.OneMinusSrcAlpha,
operation: GPUBlendOperation.Add
};
}
break;
case AdditiveBlending:
// no alphaBlend settings
break;
case SubtractiveBlending:
if ( premultipliedAlpha === true ) {
alphaBlend = {
srcFactor: GPUBlendFactor.OneMinusSrcColor,
dstFactor: GPUBlendFactor.OneMinusSrcAlpha,
operation: GPUBlendOperation.Add
};
}
break;
case MultiplyBlending:
if ( premultipliedAlpha === true ) {
alphaBlend = {
srcFactor: GPUBlendFactor.Zero,
dstFactor: GPUBlendFactor.SrcAlpha,
operation: GPUBlendOperation.Add
};
}
break;
case CustomBlending:
const blendSrcAlpha = material.blendSrcAlpha;
const blendDstAlpha = material.blendDstAlpha;
const blendEquationAlpha = material.blendEquationAlpha;
if ( blendSrcAlpha !== null && blendDstAlpha !== null && blendEquationAlpha !== null ) {
alphaBlend = {
srcFactor: this._getBlendFactor( blendSrcAlpha ),
dstFactor: this._getBlendFactor( blendDstAlpha ),
operation: this._getBlendOperation( blendEquationAlpha )
};
}
break;
default:
console.error( 'THREE.WebGPURenderer: Blending not supported.', blending );
}
return alphaBlend;
}
_getBlendFactor( blend ) {
let blendFactor;
switch ( blend ) {
case ZeroFactor:
blendFactor = GPUBlendFactor.Zero;
break;
case OneFactor:
blendFactor = GPUBlendFactor.One;
break;
case SrcColorFactor:
blendFactor = GPUBlendFactor.SrcColor;
break;
case OneMinusSrcColorFactor:
blendFactor = GPUBlendFactor.OneMinusSrcColor;
break;
case SrcAlphaFactor:
blendFactor = GPUBlendFactor.SrcAlpha;
break;
case OneMinusSrcAlphaFactor:
blendFactor = GPUBlendFactor.OneMinusSrcAlpha;
break;
case DstColorFactor:
blendFactor = GPUBlendFactor.DstColor;
break;
case OneMinusDstColorFactor:
blendFactor = GPUBlendFactor.OneMinusDstColor;
break;
case DstAlphaFactor:
blendFactor = GPUBlendFactor.DstAlpha;
break;
case OneMinusDstAlphaFactor:
blendFactor = GPUBlendFactor.OneMinusDstAlpha;
break;
case SrcAlphaSaturateFactor:
blendFactor = GPUBlendFactor.SrcAlphaSaturated;
break;
case BlendColorFactor:
blendFactor = GPUBlendFactor.BlendColor;
break;
case OneMinusBlendColorFactor:
blendFactor = GPUBlendFactor.OneMinusBlendColor;
break;
default:
console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend );
}
return blendFactor;
}
_getBlendOperation( blendEquation ) {
let blendOperation;
switch ( blendEquation ) {
case AddEquation:
blendOperation = GPUBlendOperation.Add;
break;
case SubtractEquation:
blendOperation = GPUBlendOperation.Subtract;
break;
case ReverseSubtractEquation:
blendOperation = GPUBlendOperation.ReverseSubtract;
break;
case MinEquation:
blendOperation = GPUBlendOperation.Min;
break;
case MaxEquation:
blendOperation = GPUBlendOperation.Max;
break;
default:
console.error( 'THREE.WebGPURenderer: Blend equation not supported.', blendEquation );
}
return blendOperation;
}
_getColorBlend( material ) {
const blending = material.blending;
const premultipliedAlpha = material.premultipliedAlpha;
const colorBlend = {
srcFactor: null,
dstFactor: null,
operation: null
};
switch ( blending ) {
case NormalBlending:
colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha;
colorBlend.dstFactor = GPUBlendFactor.OneMinusSrcAlpha;
colorBlend.operation = GPUBlendOperation.Add;
break;
case AdditiveBlending:
colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha;
colorBlend.operation = GPUBlendOperation.Add;
break;
case SubtractiveBlending:
colorBlend.srcFactor = GPUBlendFactor.Zero;
colorBlend.dstFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.Zero : GPUBlendFactor.OneMinusSrcColor;
colorBlend.operation = GPUBlendOperation.Add;
break;
case MultiplyBlending:
colorBlend.srcFactor = GPUBlendFactor.Zero;
colorBlend.dstFactor = GPUBlendFactor.SrcColor;
colorBlend.operation = GPUBlendOperation.Add;
break;
case CustomBlending:
colorBlend.srcFactor = this._getBlendFactor( material.blendSrc );
colorBlend.dstFactor = this._getBlendFactor( material.blendDst );
colorBlend.operation = this._getBlendOperation( material.blendEquation );
break;
default:
console.error( 'THREE.WebGPURenderer: Blending not supported.', blending );
}
return colorBlend;
}
_getColorWriteMask( material ) {
return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None;
}
_getDepthCompare( material ) {
let depthCompare;
if ( material.depthTest === false ) {
depthCompare = GPUCompareFunction.Always;
} else {
const depthFunc = material.depthFunc;
switch ( depthFunc ) {
case NeverDepth:
depthCompare = GPUCompareFunction.Never;
break;
case AlwaysDepth:
depthCompare = GPUCompareFunction.Always;
break;
case LessDepth:
depthCompare = GPUCompareFunction.Less;
break;
case LessEqualDepth:
depthCompare = GPUCompareFunction.LessEqual;
break;
case EqualDepth:
depthCompare = GPUCompareFunction.Equal;
break;
case GreaterEqualDepth:
depthCompare = GPUCompareFunction.GreaterEqual;
break;
case GreaterDepth:
depthCompare = GPUCompareFunction.Greater;
break;
case NotEqualDepth:
depthCompare = GPUCompareFunction.NotEqual;
break;
default:
console.error( 'THREE.WebGPURenderer: Invalid depth function.', depthFunc );
}
}
return depthCompare;
}
_getPrimitiveState( object, material ) {
const descriptor = {};
descriptor.topology = this._getPrimitiveTopology( object );
if ( object.isLine === true && object.isLineSegments !== true ) {
const geometry = object.geometry;
const count = ( geometry.index ) ? geometry.index.count : geometry.attributes.position.count;
descriptor.stripIndexFormat = ( count > 65535 ) ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16; // define data type for primitive restart value
}
switch ( material.side ) {
case FrontSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.Back;
break;
case BackSide:
descriptor.frontFace = GPUFrontFace.CW;
descriptor.cullMode = GPUCullMode.Back;
break;
case DoubleSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.None;
break;
default:
console.error( 'THREE.WebGPURenderer: Unknown Material.side value.', material.side );
break;
}
return descriptor;
}
_getPrimitiveTopology( object ) {
if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
else if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
else if ( object.isLineSegments ) return GPUPrimitiveTopology.LineList;
else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
}
_getStencilCompare( material ) {
let stencilCompare;
const stencilFunc = material.stencilFunc;
switch ( stencilFunc ) {
case NeverStencilFunc:
stencilCompare = GPUCompareFunction.Never;
break;
case AlwaysStencilFunc:
stencilCompare = GPUCompareFunction.Always;
break;
case LessStencilFunc:
stencilCompare = GPUCompareFunction.Less;
break;
case LessEqualStencilFunc:
stencilCompare = GPUCompareFunction.LessEqual;
break;
case EqualStencilFunc:
stencilCompare = GPUCompareFunction.Equal;
break;
case GreaterEqualStencilFunc:
stencilCompare = GPUCompareFunction.GreaterEqual;
break;
case GreaterStencilFunc:
stencilCompare = GPUCompareFunction.Greater;
break;
case NotEqualStencilFunc:
stencilCompare = GPUCompareFunction.NotEqual;
break;
default:
console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc );
}
return stencilCompare;
}
_getStencilOperation( op ) {
let stencilOperation;
switch ( op ) {
case KeepStencilOp:
stencilOperation = GPUStencilOperation.Keep;
break;
case ZeroStencilOp:
stencilOperation = GPUStencilOperation.Zero;
break;
case ReplaceStencilOp:
stencilOperation = GPUStencilOperation.Replace;
break;
case InvertStencilOp:
stencilOperation = GPUStencilOperation.Invert;
break;
case IncrementStencilOp:
stencilOperation = GPUStencilOperation.IncrementClamp;
break;
case DecrementStencilOp:
stencilOperation = GPUStencilOperation.DecrementClamp;
break;
case IncrementWrapStencilOp:
stencilOperation = GPUStencilOperation.IncrementWrap;
break;
case DecrementWrapStencilOp:
stencilOperation = GPUStencilOperation.DecrementWrap;
break;
default:
console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation );
}
return stencilOperation;
}
_getVertexFormat( type, bytesPerElement ) {
// float
if ( type === 'float' ) return GPUVertexFormat.Float32;
if ( type === 'vec2' ) {
if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Float16x2;
} else {
return GPUVertexFormat.Float32x2;
}
}
if ( type === 'vec3' ) return GPUVertexFormat.Float32x3;
if ( type === 'vec4' ) {
if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Float16x4;
} else {
return GPUVertexFormat.Float32x4;
}
}
// int
if ( type === 'int' ) return GPUVertexFormat.Sint32;
if ( type === 'ivec2' ) {
if ( bytesPerElement === 1 ) {
return GPUVertexFormat.Sint8x2;
} else if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Sint16x2;
} else {
return GPUVertexFormat.Sint32x2;
}
}
if ( type === 'ivec3' ) return GPUVertexFormat.Sint32x3;
if ( type === 'ivec4' ) {
if ( bytesPerElement === 1 ) {
return GPUVertexFormat.Sint8x4;
} else if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Sint16x4;
} else {
return GPUVertexFormat.Sint32x4;
}
}
// uint
if ( type === 'uint' ) return GPUVertexFormat.Uint32;
if ( type === 'uvec2' ) {
if ( bytesPerElement === 1 ) {
return GPUVertexFormat.Uint8x2;
} else if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Uint16x2;
} else {
return GPUVertexFormat.Uint32x2;
}
}
if ( type === 'uvec3' ) return GPUVertexFormat.Uint32x3;
if ( type === 'uvec4' ) {
if ( bytesPerElement === 1 ) {
return GPUVertexFormat.Uint8x4;
} else if ( bytesPerElement === 2 ) {
return GPUVertexFormat.Uint16x4;
} else {
return GPUVertexFormat.Uint32x4;
}
}
console.error( 'THREE.WebGPURenderer: Shader variable type not supported yet.', type );
}
_getShaderAttributes( nodeBuilder, geometry ) {
const nodeAttributes = nodeBuilder.attributes;
const attributes = [];
for ( let slot = 0; slot < nodeAttributes.length; slot ++ ) {
const nodeAttribute = nodeAttributes[ slot ];
const name = nodeAttribute.name;
const type = nodeAttribute.type;
const geometryAttribute = geometry.getAttribute( name );
const bytesPerElement = ( geometryAttribute !== undefined ) ? geometryAttribute.array.BYTES_PER_ELEMENT : 4;
const arrayStride = this._getArrayStride( type, bytesPerElement );
const format = this._getVertexFormat( type, bytesPerElement );
attributes.push( {
name,
arrayStride,
format,
slot
} );
}
return attributes;
}
}
export default WebGPURenderPipeline;

View File

@@ -0,0 +1,293 @@
import WebGPURenderPipeline from './WebGPURenderPipeline.js';
import WebGPUProgrammableStage from './WebGPUProgrammableStage.js';
class WebGPURenderPipelines {
constructor( renderer, device, sampleCount, nodes, bindings = null ) {
this.renderer = renderer;
this.device = device;
this.sampleCount = sampleCount;
this.nodes = nodes;
this.bindings = bindings;
this.pipelines = [];
this.objectCache = new WeakMap();
this.stages = {
vertex: new Map(),
fragment: new Map()
};
}
get( object ) {
const device = this.device;
const material = object.material;
const cache = this._getCache( object );
let currentPipeline;
if ( this._needsUpdate( object, cache ) ) {
// release previous cache
if ( cache.currentPipeline !== undefined ) {
this._releaseObject( object );
}
// get shader
const nodeBuilder = this.nodes.get( object );
// programmable stages
let stageVertex = this.stages.vertex.get( nodeBuilder.vertexShader );
if ( stageVertex === undefined ) {
stageVertex = new WebGPUProgrammableStage( device, nodeBuilder.vertexShader, 'vertex' );
this.stages.vertex.set( nodeBuilder.vertexShader, stageVertex );
}
let stageFragment = this.stages.fragment.get( nodeBuilder.fragmentShader );
if ( stageFragment === undefined ) {
stageFragment = new WebGPUProgrammableStage( device, nodeBuilder.fragmentShader, 'fragment' );
this.stages.fragment.set( nodeBuilder.fragmentShader, stageFragment );
}
// determine render pipeline
currentPipeline = this._acquirePipeline( stageVertex, stageFragment, object, nodeBuilder );
cache.currentPipeline = currentPipeline;
// keep track of all used times
currentPipeline.usedTimes ++;
stageVertex.usedTimes ++;
stageFragment.usedTimes ++;
// events
material.addEventListener( 'dispose', cache.dispose );
} else {
currentPipeline = cache.currentPipeline;
}
return currentPipeline;
}
dispose() {
this.pipelines = [];
this.objectCache = new WeakMap();
this.shaderModules = {
vertex: new Map(),
fragment: new Map()
};
}
_acquirePipeline( stageVertex, stageFragment, object, nodeBuilder ) {
let pipeline;
const pipelines = this.pipelines;
// check for existing pipeline
const cacheKey = this._computeCacheKey( stageVertex, stageFragment, object );
for ( let i = 0, il = pipelines.length; i < il; i ++ ) {
const preexistingPipeline = pipelines[ i ];
if ( preexistingPipeline.cacheKey === cacheKey ) {
pipeline = preexistingPipeline;
break;
}
}
if ( pipeline === undefined ) {
pipeline = new WebGPURenderPipeline( this.device, this.renderer, this.sampleCount );
pipeline.init( cacheKey, stageVertex, stageFragment, object, nodeBuilder );
pipelines.push( pipeline );
}
return pipeline;
}
_computeCacheKey( stageVertex, stageFragment, object ) {
const material = object.material;
const renderer = this.renderer;
const parameters = [
stageVertex.id, stageFragment.id,
material.transparent, material.blending, material.premultipliedAlpha,
material.blendSrc, material.blendDst, material.blendEquation,
material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha,
material.colorWrite,
material.depthWrite, material.depthTest, material.depthFunc,
material.stencilWrite, material.stencilFunc,
material.stencilFail, material.stencilZFail, material.stencilZPass,
material.stencilFuncMask, material.stencilWriteMask,
material.side,
this.sampleCount,
renderer.getCurrentEncoding(), renderer.getCurrentColorFormat(), renderer.getCurrentDepthStencilFormat()
];
return parameters.join();
}
_getCache( object ) {
let cache = this.objectCache.get( object );
if ( cache === undefined ) {
cache = {
dispose: () => {
this._releaseObject( object );
this.objectCache.delete( object );
object.material.removeEventListener( 'dispose', cache.dispose );
}
};
this.objectCache.set( object, cache );
}
return cache;
}
_releaseObject( object ) {
const cache = this.objectCache.get( object );
this._releasePipeline( cache.currentPipeline );
delete cache.currentPipeline;
this.nodes.remove( object );
this.bindings.remove( object );
}
_releasePipeline( pipeline ) {
if ( -- pipeline.usedTimes === 0 ) {
const pipelines = this.pipelines;
const i = pipelines.indexOf( pipeline );
pipelines[ i ] = pipelines[ pipelines.length - 1 ];
pipelines.pop();
this._releaseStage( pipeline.stageVertex );
this._releaseStage( pipeline.stageFragment );
}
}
_releaseStage( stage ) {
if ( -- stage.usedTimes === 0 ) {
const code = stage.code;
const type = stage.type;
this.stages[ type ].delete( code );
}
}
_needsUpdate( object, cache ) {
const material = object.material;
let needsUpdate = false;
// check material state
if ( cache.material !== material || cache.materialVersion !== material.version ||
cache.transparent !== material.transparent || cache.blending !== material.blending || cache.premultipliedAlpha !== material.premultipliedAlpha ||
cache.blendSrc !== material.blendSrc || cache.blendDst !== material.blendDst || cache.blendEquation !== material.blendEquation ||
cache.blendSrcAlpha !== material.blendSrcAlpha || cache.blendDstAlpha !== material.blendDstAlpha || cache.blendEquationAlpha !== material.blendEquationAlpha ||
cache.colorWrite !== material.colorWrite ||
cache.depthWrite !== material.depthWrite || cache.depthTest !== material.depthTest || cache.depthFunc !== material.depthFunc ||
cache.stencilWrite !== material.stencilWrite || cache.stencilFunc !== material.stencilFunc ||
cache.stencilFail !== material.stencilFail || cache.stencilZFail !== material.stencilZFail || cache.stencilZPass !== material.stencilZPass ||
cache.stencilFuncMask !== material.stencilFuncMask || cache.stencilWriteMask !== material.stencilWriteMask ||
cache.side !== material.side
) {
cache.material = material; cache.materialVersion = material.version;
cache.transparent = material.transparent; cache.blending = material.blending; cache.premultipliedAlpha = material.premultipliedAlpha;
cache.blendSrc = material.blendSrc; cache.blendDst = material.blendDst; cache.blendEquation = material.blendEquation;
cache.blendSrcAlpha = material.blendSrcAlpha; cache.blendDstAlpha = material.blendDstAlpha; cache.blendEquationAlpha = material.blendEquationAlpha;
cache.colorWrite = material.colorWrite;
cache.depthWrite = material.depthWrite; cache.depthTest = material.depthTest; cache.depthFunc = material.depthFunc;
cache.stencilWrite = material.stencilWrite; cache.stencilFunc = material.stencilFunc;
cache.stencilFail = material.stencilFail; cache.stencilZFail = material.stencilZFail; cache.stencilZPass = material.stencilZPass;
cache.stencilFuncMask = material.stencilFuncMask; cache.stencilWriteMask = material.stencilWriteMask;
cache.side = material.side;
needsUpdate = true;
}
// check renderer state
const renderer = this.renderer;
const encoding = renderer.getCurrentEncoding();
const colorFormat = renderer.getCurrentColorFormat();
const depthStencilFormat = renderer.getCurrentDepthStencilFormat();
if ( cache.sampleCount !== this.sampleCount || cache.encoding !== encoding ||
cache.colorFormat !== colorFormat || cache.depthStencilFormat !== depthStencilFormat ) {
cache.sampleCount = this.sampleCount;
cache.encoding = encoding;
cache.colorFormat = colorFormat;
cache.depthStencilFormat = depthStencilFormat;
needsUpdate = true;
}
return needsUpdate;
}
}
export default WebGPURenderPipelines;

View File

@@ -0,0 +1,961 @@
import { GPUIndexFormat, GPUTextureFormat, GPUStoreOp } 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 WebGPUComputePipelines from './WebGPUComputePipelines.js';
import WebGPUBindings from './WebGPUBindings.js';
import WebGPURenderLists from './WebGPURenderLists.js';
import WebGPUTextures from './WebGPUTextures.js';
import WebGPUBackground from './WebGPUBackground.js';
import WebGPUNodes from './nodes/WebGPUNodes.js';
import { Frustum, Matrix4, Vector3, Color, LinearEncoding } from 'three';
console.info( 'THREE.WebGPURenderer: Modified Matrix4.makePerspective() and Matrix4.makeOrtographic() to work with WebGPU, see https://github.com/mrdoob/three.js/issues/20276.' );
Matrix4.prototype.makePerspective = function ( left, right, top, bottom, near, far ) {
const te = this.elements;
const x = 2 * near / ( right - left );
const y = 2 * near / ( top - bottom );
const a = ( right + left ) / ( right - left );
const b = ( top + bottom ) / ( top - bottom );
const c = - far / ( far - near );
const d = - far * near / ( far - near );
te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0;
te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0;
te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d;
te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0;
return this;
};
Matrix4.prototype.makeOrthographic = function ( left, right, top, bottom, near, far ) {
const te = this.elements;
const w = 1.0 / ( right - left );
const h = 1.0 / ( top - bottom );
const p = 1.0 / ( far - near );
const x = ( right + left ) * w;
const y = ( top + bottom ) * h;
const z = near * p;
te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x;
te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y;
te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 1 * p; te[ 14 ] = - z;
te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1;
return this;
};
const _frustum = new Frustum();
const _projScreenMatrix = new Matrix4();
const _vector3 = new Vector3();
class WebGPURenderer {
constructor( parameters = {} ) {
// public
this.domElement = ( parameters.canvas !== undefined ) ? parameters.canvas : this._createCanvasElement();
this.autoClear = true;
this.autoClearColor = true;
this.autoClearDepth = true;
this.autoClearStencil = true;
this.outputEncoding = LinearEncoding;
this.sortObjects = true;
// internals
this._parameters = Object.assign( {}, parameters );
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._colorBuffer = null;
this._depthBuffer = null;
this._info = null;
this._properties = null;
this._attributes = null;
this._geometries = null;
this._nodes = null;
this._bindings = null;
this._objects = null;
this._renderPipelines = null;
this._computePipelines = null;
this._renderLists = null;
this._textures = null;
this._background = null;
this._renderPassDescriptor = null;
this._currentRenderList = null;
this._opaqueSort = null;
this._transparentSort = null;
this._clearAlpha = 1;
this._clearColor = new Color( 0x000000 );
this._clearDepth = 1;
this._clearStencil = 0;
this._renderTarget = null;
// some parameters require default values other than "undefined"
this._parameters.antialias = ( parameters.antialias === true );
if ( this._parameters.antialias === true ) {
this._parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount;
} else {
this._parameters.sampleCount = 1;
}
this._parameters.requiredFeatures = ( parameters.requiredFeatures === undefined ) ? [] : parameters.requiredFeatures;
this._parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
}
async init() {
const parameters = this._parameters;
const adapterOptions = {
powerPreference: parameters.powerPreference
};
const adapter = await navigator.gpu.requestAdapter( adapterOptions );
if ( adapter === null ) {
throw new Error( 'WebGPURenderer: Unable to create WebGPU adapter.' );
}
const deviceDescriptor = {
requiredFeatures: parameters.requiredFeatures,
requiredLimits: parameters.requiredLimits
};
const device = await adapter.requestDevice( deviceDescriptor );
const context = ( parameters.context !== undefined ) ? parameters.context : this.domElement.getContext( 'webgpu' );
context.configure( {
device: device,
format: GPUTextureFormat.BGRA8Unorm // this is the only valid context format right now (r121)
} );
this._adapter = adapter;
this._device = device;
this._context = context;
this._info = new WebGPUInfo();
this._properties = new WebGPUProperties();
this._attributes = new WebGPUAttributes( device );
this._geometries = new WebGPUGeometries( this._attributes, this._info );
this._textures = new WebGPUTextures( device, this._properties, this._info );
this._objects = new WebGPUObjects( this._geometries, this._info );
this._nodes = new WebGPUNodes( this );
this._computePipelines = new WebGPUComputePipelines( device );
this._renderPipelines = new WebGPURenderPipelines( this, device, parameters.sampleCount, this._nodes );
this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes );
this._renderLists = new WebGPURenderLists();
this._background = new WebGPUBackground( this );
//
this._renderPassDescriptor = {
colorAttachments: [ {
view: null
} ],
depthStencilAttachment: {
view: null,
depthStoreOp: GPUStoreOp.Store,
stencilStoreOp: GPUStoreOp.Store
}
};
this._setupColorBuffer();
this._setupDepthBuffer();
}
render( scene, camera ) {
// @TODO: move this to animation loop
this._nodes.updateFrame();
//
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 );
}
// prepare render pass descriptor
const colorAttachment = this._renderPassDescriptor.colorAttachments[ 0 ];
const depthStencilAttachment = this._renderPassDescriptor.depthStencilAttachment;
const renderTarget = this._renderTarget;
if ( renderTarget !== null ) {
// @TODO: Support RenderTarget with antialiasing.
const renderTargetProperties = this._properties.get( renderTarget );
colorAttachment.view = renderTargetProperties.colorTextureGPU.createView();
depthStencilAttachment.view = renderTargetProperties.depthTextureGPU.createView();
} else {
if ( this._parameters.antialias === true ) {
colorAttachment.view = this._colorBuffer.createView();
colorAttachment.resolveTarget = this._context.getCurrentTexture().createView();
} else {
colorAttachment.view = this._context.getCurrentTexture().createView();
colorAttachment.resolveTarget = undefined;
}
depthStencilAttachment.view = this._depthBuffer.createView();
}
//
this._background.update( scene );
// start render pass
const device = this._device;
const cmdEncoder = device.createCommandEncoder( {} );
const passEncoder = cmdEncoder.beginRenderPass( this._renderPassDescriptor );
// global rasterization settings for all renderable objects
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 );
}
// process render lists
const opaqueObjects = this._currentRenderList.opaque;
const transparentObjects = this._currentRenderList.transparent;
if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, passEncoder );
if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, passEncoder );
// finish render pass
passEncoder.end();
device.queue.submit( [ cmdEncoder.finish() ] );
}
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._configureContext();
this._setupColorBuffer();
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._configureContext();
this._setupColorBuffer();
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
};
}
}
getCurrentEncoding() {
const renderTarget = this.getRenderTarget();
return ( renderTarget !== null ) ? renderTarget.texture.encoding : this.outputEncoding;
}
getCurrentColorFormat() {
let format;
const renderTarget = this.getRenderTarget();
if ( renderTarget !== null ) {
const renderTargetProperties = this._properties.get( renderTarget );
format = renderTargetProperties.colorTextureFormat;
} else {
format = GPUTextureFormat.BGRA8Unorm; // default context format
}
return format;
}
getCurrentDepthStencilFormat() {
let format;
const renderTarget = this.getRenderTarget();
if ( renderTarget !== null ) {
const renderTargetProperties = this._properties.get( renderTarget );
format = renderTargetProperties.depthTextureFormat;
} else {
format = GPUTextureFormat.Depth24PlusStencil8;
}
return format;
}
getClearColor( target ) {
return target.copy( this._clearColor );
}
setClearColor( color, alpha = 1 ) {
this._clearColor.set( color );
this._clearAlpha = alpha;
}
getClearAlpha() {
return this._clearAlpha;
}
setClearAlpha( alpha ) {
this._clearAlpha = alpha;
}
getClearDepth() {
return this._clearDepth;
}
setClearDepth( depth ) {
this._clearDepth = depth;
}
getClearStencil() {
return this._clearStencil;
}
setClearStencil( stencil ) {
this._clearStencil = stencil;
}
clear() {
this._background.clear();
}
dispose() {
this._objects.dispose();
this._properties.dispose();
this._renderPipelines.dispose();
this._computePipelines.dispose();
this._nodes.dispose();
this._bindings.dispose();
this._info.dispose();
this._renderLists.dispose();
this._textures.dispose();
}
setRenderTarget( renderTarget ) {
this._renderTarget = renderTarget;
if ( renderTarget !== null ) {
this._textures.initRenderTarget( renderTarget );
}
}
compute( computeParams ) {
const device = this._device;
const cmdEncoder = device.createCommandEncoder( {} );
const passEncoder = cmdEncoder.beginComputePass();
for ( const param of computeParams ) {
// pipeline
const pipeline = this._computePipelines.get( param );
passEncoder.setPipeline( pipeline );
// bind group
const bindGroup = this._bindings.getForCompute( param ).group;
this._bindings.update( param );
passEncoder.setBindGroup( 0, bindGroup );
passEncoder.dispatch( param.num );
}
passEncoder.end();
device.queue.submit( [ cmdEncoder.finish() ] );
}
getRenderTarget() {
return this._renderTarget;
}
_projectObject( object, camera, groupOrder ) {
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.isLineLoop ) {
console.error( 'THREE.WebGPURenderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' );
} else if ( object.isMesh || object.isLine || object.isPoints ) {
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, passEncoder ) {
// process renderable objects
for ( let i = 0, il = renderList.length; i < il; i ++ ) {
const renderItem = renderList[ i ];
// @TODO: Add support for multiple materials per object. This will require to extract
// the material from the renderItem object and pass it with its group data to _renderObject().
const object = renderItem.object;
object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
this._objects.update( object );
if ( camera.isArrayCamera ) {
const cameras = camera.cameras;
for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
const camera2 = cameras[ j ];
if ( object.layers.test( camera2.layers ) ) {
const vp = camera2.viewport;
const minDepth = ( vp.minDepth === undefined ) ? 0 : vp.minDepth;
const maxDepth = ( vp.maxDepth === undefined ) ? 1 : vp.maxDepth;
passEncoder.setViewport( vp.x, vp.y, vp.width, vp.height, minDepth, maxDepth );
this._nodes.update( object, camera2 );
this._bindings.update( object );
this._renderObject( object, passEncoder );
}
}
} else {
this._nodes.update( object, camera );
this._bindings.update( object );
this._renderObject( object, passEncoder );
}
}
}
_renderObject( object, passEncoder ) {
const info = this._info;
// pipeline
const renderPipeline = this._renderPipelines.get( object );
passEncoder.setPipeline( renderPipeline.pipeline );
// 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( index, passEncoder );
}
// vertex buffers
this._setupVertexBuffers( geometry.attributes, passEncoder, renderPipeline );
// draw
const drawRange = geometry.drawRange;
const firstVertex = drawRange.start;
const instanceCount = ( geometry.isInstancedBufferGeometry ) ? geometry.instanceCount : 1;
if ( hasIndex === true ) {
const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count;
passEncoder.drawIndexed( indexCount, instanceCount, firstVertex, 0, 0 );
info.update( object, indexCount, instanceCount );
} else {
const positionAttribute = geometry.attributes.position;
const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count;
passEncoder.draw( vertexCount, instanceCount, firstVertex, 0 );
info.update( object, vertexCount, instanceCount );
}
}
_setupIndexBuffer( index, encoder ) {
const buffer = this._attributes.get( index ).buffer;
const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
encoder.setIndexBuffer( buffer, indexFormat );
}
_setupVertexBuffers( geometryAttributes, encoder, renderPipeline ) {
const shaderAttributes = renderPipeline.shaderAttributes;
for ( const shaderAttribute of shaderAttributes ) {
const name = shaderAttribute.name;
const slot = shaderAttribute.slot;
const attribute = geometryAttributes[ name ];
if ( attribute !== undefined ) {
const buffer = this._attributes.get( attribute ).buffer;
encoder.setVertexBuffer( slot, buffer );
}
}
}
_setupColorBuffer() {
const device = this._device;
if ( device ) {
if ( this._colorBuffer ) this._colorBuffer.destroy();
this._colorBuffer = this._device.createTexture( {
size: {
width: Math.floor( this._width * this._pixelRatio ),
height: Math.floor( this._height * this._pixelRatio ),
depthOrArrayLayers: 1
},
sampleCount: this._parameters.sampleCount,
format: GPUTextureFormat.BGRA8Unorm,
usage: GPUTextureUsage.RENDER_ATTACHMENT
} );
}
}
_setupDepthBuffer() {
const device = this._device;
if ( device ) {
if ( this._depthBuffer ) this._depthBuffer.destroy();
this._depthBuffer = this._device.createTexture( {
size: {
width: Math.floor( this._width * this._pixelRatio ),
height: Math.floor( this._height * this._pixelRatio ),
depthOrArrayLayers: 1
},
sampleCount: this._parameters.sampleCount,
format: GPUTextureFormat.Depth24PlusStencil8,
usage: GPUTextureUsage.RENDER_ATTACHMENT
} );
}
}
_configureContext() {
const device = this._device;
if ( device ) {
this._context.configure( {
device: device,
format: GPUTextureFormat.BGRA8Unorm,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: {
width: Math.floor( this._width * this._pixelRatio ),
height: Math.floor( this._height * this._pixelRatio ),
depthOrArrayLayers: 1
},
} );
}
}
_createCanvasElement() {
const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
canvas.style.display = 'block';
return canvas;
}
}
WebGPURenderer.prototype.isWebGPURenderer = true;
export default WebGPURenderer;

View File

@@ -0,0 +1,73 @@
import WebGPUBinding from './WebGPUBinding.js';
import { GPUBindingType, GPUTextureViewDimension } from './constants.js';
class WebGPUSampledTexture extends WebGPUBinding {
constructor( name, texture ) {
super( name );
this.texture = texture;
this.dimension = GPUTextureViewDimension.TwoD;
this.type = GPUBindingType.SampledTexture;
this.visibility = GPUShaderStage.FRAGMENT;
this.textureGPU = null; // set by the renderer
}
getTexture() {
return this.texture;
}
}
WebGPUSampledTexture.prototype.isSampledTexture = true;
class WebGPUSampledArrayTexture extends WebGPUSampledTexture {
constructor( name ) {
super( name );
this.dimension = GPUTextureViewDimension.TwoDArray;
}
}
WebGPUSampledArrayTexture.prototype.isSampledArrayTexture = true;
class WebGPUSampled3DTexture extends WebGPUSampledTexture {
constructor( name ) {
super( name );
this.dimension = GPUTextureViewDimension.ThreeD;
}
}
WebGPUSampled3DTexture.prototype.isSampled3DTexture = true;
class WebGPUSampledCubeTexture extends WebGPUSampledTexture {
constructor( name ) {
super( name );
this.dimension = GPUTextureViewDimension.Cube;
}
}
WebGPUSampledCubeTexture.prototype.isSampledCubeTexture = true;
export { WebGPUSampledTexture, WebGPUSampledArrayTexture, WebGPUSampled3DTexture, WebGPUSampledCubeTexture };

View File

@@ -0,0 +1,29 @@
import WebGPUBinding from './WebGPUBinding.js';
import { GPUBindingType } from './constants.js';
class WebGPUSampler extends WebGPUBinding {
constructor( name, texture ) {
super( name );
this.texture = texture;
this.type = GPUBindingType.Sampler;
this.visibility = GPUShaderStage.FRAGMENT;
this.samplerGPU = null; // set by the renderer
}
getTexture() {
return this.texture;
}
}
WebGPUSampler.prototype.isSampler = true;
export default WebGPUSampler;

View File

@@ -0,0 +1,23 @@
import WebGPUBinding from './WebGPUBinding.js';
import { GPUBindingType } from './constants.js';
class WebGPUStorageBuffer extends WebGPUBinding {
constructor( name, attribute ) {
super( name );
this.type = GPUBindingType.StorageBuffer;
this.usage = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST;
this.attribute = attribute;
this.bufferGPU = null; // set by the renderer
}
}
WebGPUStorageBuffer.prototype.isStorageBuffer = true;
export default WebGPUStorageBuffer;

View File

@@ -0,0 +1,40 @@
import { WebGLRenderTarget } from 'three';
class WebGPUTextureRenderer {
constructor( renderer, options = {} ) {
this.renderer = renderer;
// @TODO: Consider to introduce WebGPURenderTarget or rename WebGLRenderTarget to just RenderTarget
this.renderTarget = new WebGLRenderTarget( options );
}
getTexture() {
return this.renderTarget.texture;
}
setSize( width, height ) {
this.renderTarget.setSize( width, height );
}
render( scene, camera ) {
const renderer = this.renderer;
const renderTarget = this.renderTarget;
renderer.setRenderTarget( renderTarget );
renderer.render( scene, camera );
renderer.setRenderTarget( null );
}
}
export default WebGPUTextureRenderer;

View File

@@ -0,0 +1,180 @@
// Copyright 2020 Brandon Jones
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import { GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './constants.js';
// ported from https://github.com/toji/web-texture-tool/blob/master/src/webgpu-mipmap-generator.js
class WebGPUTextureUtils {
constructor( device ) {
this.device = device;
const mipmapVertexSource = `
struct VarysStruct {
@builtin( position ) Position: vec4<f32>;
@location( 0 ) vTex : vec2<f32>;
};
@stage( vertex )
fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {
var Varys: VarysStruct;
var pos = array< vec2<f32>, 4 >(
vec2<f32>( -1.0, 1.0 ),
vec2<f32>( 1.0, 1.0 ),
vec2<f32>( -1.0, -1.0 ),
vec2<f32>( 1.0, -1.0 )
);
var tex = array< vec2<f32>, 4 >(
vec2<f32>( 0.0, 0.0 ),
vec2<f32>( 1.0, 0.0 ),
vec2<f32>( 0.0, 1.0 ),
vec2<f32>( 1.0, 1.0 )
);
Varys.vTex = tex[ vertexIndex ];
Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 );
return Varys;
}
`;
const mipmapFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;
@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;
@stage( fragment )
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vTex );
}
`;
this.sampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
// We'll need a new pipeline for every texture format used.
this.pipelines = {};
this.mipmapVertexShaderModule = device.createShaderModule( {
code: mipmapVertexSource
} );
this.mipmapFragmentShaderModule = device.createShaderModule( {
code: mipmapFragmentSource
} );
}
getMipmapPipeline( format ) {
let pipeline = this.pipelines[ format ];
if ( pipeline === undefined ) {
pipeline = this.device.createRenderPipeline( {
vertex: {
module: this.mipmapVertexShaderModule,
entryPoint: 'main',
},
fragment: {
module: this.mipmapFragmentShaderModule,
entryPoint: 'main',
targets: [ { format } ],
},
primitive: {
topology: GPUPrimitiveTopology.TriangleStrip,
stripIndexFormat: GPUIndexFormat.Uint32
}
} );
this.pipelines[ format ] = pipeline;
}
return pipeline;
}
generateMipmaps( textureGPU, textureGPUDescriptor ) {
const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format );
const commandEncoder = this.device.createCommandEncoder( {} );
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
let srcView = textureGPU.createView( {
baseMipLevel: 0,
mipLevelCount: 1
} );
for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
const dstView = textureGPU.createView( {
baseMipLevel: i,
mipLevelCount: 1
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
view: dstView,
loadOp: GPULoadOp.Clear,
storeOp: GPUStoreOp.Store,
clearValue: [ 0, 0, 0, 0 ]
} ]
} );
const bindGroup = this.device.createBindGroup( {
layout: bindGroupLayout,
entries: [ {
binding: 0,
resource: this.sampler
}, {
binding: 1,
resource: srcView
} ]
} );
passEncoder.setPipeline( pipeline );
passEncoder.setBindGroup( 0, bindGroup );
passEncoder.draw( 4, 1, 0, 0 );
passEncoder.end();
srcView = dstView;
}
this.device.queue.submit( [ commandEncoder.finish() ] );
}
}
export default WebGPUTextureUtils;

View File

@@ -0,0 +1,775 @@
import { GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension } from './constants.js';
import { CubeTexture, Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping,
RGBAFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, sRGBEncoding
} from 'three';
import WebGPUTextureUtils from './WebGPUTextureUtils.js';
class WebGPUTextures {
constructor( device, properties, info ) {
this.device = device;
this.properties = properties;
this.info = info;
this.defaultTexture = null;
this.defaultCubeTexture = null;
this.defaultSampler = null;
this.samplerCache = new Map();
this.utils = null;
}
getDefaultSampler() {
if ( this.defaultSampler === null ) {
this.defaultSampler = this.device.createSampler( {} );
}
return this.defaultSampler;
}
getDefaultTexture() {
if ( this.defaultTexture === null ) {
const texture = new Texture();
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
this._uploadTexture( texture );
this.defaultTexture = this.getTextureGPU( texture );
}
return this.defaultTexture;
}
getDefaultCubeTexture() {
if ( this.defaultCubeTexture === null ) {
const texture = new CubeTexture();
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
this._uploadTexture( texture );
this.defaultCubeTexture = this.getTextureGPU( texture );
}
return this.defaultCubeTexture;
}
getTextureGPU( texture ) {
const textureProperties = this.properties.get( texture );
return textureProperties.textureGPU;
}
getSampler( texture ) {
const textureProperties = this.properties.get( texture );
return textureProperties.samplerGPU;
}
updateTexture( texture ) {
let needsUpdate = 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 {
// texture init
if ( textureProperties.initialized === undefined ) {
textureProperties.initialized = true;
const disposeCallback = onTextureDispose.bind( this );
textureProperties.disposeCallback = disposeCallback;
texture.addEventListener( 'dispose', disposeCallback );
this.info.memory.textures ++;
}
//
needsUpdate = this._uploadTexture( texture );
}
}
// if the texture is used for RTT, it's necessary to init it once so the binding
// group's resource definition points to the respective GPUTexture
if ( textureProperties.initializedRTT === false ) {
textureProperties.initializedRTT = true;
needsUpdate = true;
}
return needsUpdate;
}
updateSampler( texture ) {
const array = [];
array.push( texture.wrapS );
array.push( texture.wrapT );
array.push( texture.wrapR );
array.push( texture.magFilter );
array.push( texture.minFilter );
array.push( texture.anisotropy );
const key = array.join();
let samplerGPU = this.samplerCache.get( key );
if ( samplerGPU === undefined ) {
samplerGPU = 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
} );
this.samplerCache.set( key, samplerGPU );
}
const textureProperties = this.properties.get( texture );
textureProperties.samplerGPU = samplerGPU;
}
initRenderTarget( renderTarget ) {
const properties = this.properties;
const renderTargetProperties = properties.get( renderTarget );
if ( renderTargetProperties.initialized === undefined ) {
const device = this.device;
const width = renderTarget.width;
const height = renderTarget.height;
const colorTextureFormat = this._getFormat( renderTarget.texture );
const colorTextureGPU = device.createTexture( {
size: {
width: width,
height: height,
depthOrArrayLayers: 1
},
format: colorTextureFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
} );
this.info.memory.textures ++;
renderTargetProperties.colorTextureGPU = colorTextureGPU;
renderTargetProperties.colorTextureFormat = colorTextureFormat;
// When the ".texture" or ".depthTexture" property of a render target is used as a map,
// the renderer has to find the respective GPUTexture objects to setup the bind groups.
// Since it's not possible to see just from a texture object whether it belongs to a render
// target or not, we need the initializedRTT flag.
const textureProperties = properties.get( renderTarget.texture );
textureProperties.textureGPU = colorTextureGPU;
textureProperties.initializedRTT = false;
if ( renderTarget.depthBuffer === true ) {
const depthTextureFormat = GPUTextureFormat.Depth24PlusStencil8; // @TODO: Make configurable
const depthTextureGPU = device.createTexture( {
size: {
width: width,
height: height,
depthOrArrayLayers: 1
},
format: depthTextureFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT
} );
this.info.memory.textures ++;
renderTargetProperties.depthTextureGPU = depthTextureGPU;
renderTargetProperties.depthTextureFormat = depthTextureFormat;
if ( renderTarget.depthTexture !== null ) {
const depthTextureProperties = properties.get( renderTarget.depthTexture );
depthTextureProperties.textureGPU = depthTextureGPU;
depthTextureProperties.initializedRTT = false;
}
}
//
const disposeCallback = onRenderTargetDispose.bind( this );
renderTargetProperties.disposeCallback = disposeCallback;
renderTarget.addEventListener( 'dispose', disposeCallback );
//
renderTargetProperties.initialized = true;
}
}
dispose() {
this.samplerCache.clear();
}
_convertAddressMode( value ) {
let addressMode = GPUAddressMode.ClampToEdge;
if ( value === RepeatWrapping ) {
addressMode = GPUAddressMode.Repeat;
} else if ( value === MirroredRepeatWrapping ) {
addressMode = GPUAddressMode.MirrorRepeat;
}
return addressMode;
}
_convertFilterMode( value ) {
let filterMode = GPUFilterMode.Linear;
if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {
filterMode = GPUFilterMode.Nearest;
}
return filterMode;
}
_uploadTexture( texture ) {
let needsUpdate = false;
const device = this.device;
const image = texture.image;
const textureProperties = this.properties.get( texture );
const { width, height, depth } = this._getSize( texture );
const needsMipmaps = this._needsMipmaps( texture );
const dimension = this._getDimension( texture );
const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps );
const format = this._getFormat( texture );
let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST;
if ( needsMipmaps === true ) {
// current mipmap generation requires RENDER_ATTACHMENT
usage |= GPUTextureUsage.RENDER_ATTACHMENT;
}
const textureGPUDescriptor = {
size: {
width: width,
height: height,
depthOrArrayLayers: depth,
},
mipLevelCount: mipLevelCount,
sampleCount: 1,
dimension: dimension,
format: format,
usage: usage
};
// texture creation
let textureGPU = textureProperties.textureGPU;
if ( textureGPU === undefined ) {
textureGPU = device.createTexture( textureGPUDescriptor );
textureProperties.textureGPU = textureGPU;
needsUpdate = true;
}
// transfer texture data
if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) {
this._copyBufferToTexture( image, format, textureGPU );
if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor );
} else if ( texture.isCompressedTexture ) {
this._copyCompressedBufferToTexture( texture.mipmaps, format, textureGPU );
} else if ( texture.isCubeTexture ) {
this._copyCubeMapToTexture( image, texture, textureGPU );
} else {
if ( image !== null ) {
// assume HTMLImageElement, HTMLCanvasElement or ImageBitmap
this._getImageBitmap( image, texture ).then( imageBitmap => {
this._copyExternalImageToTexture( imageBitmap, textureGPU );
if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor );
} );
}
}
textureProperties.version = texture.version;
return needsUpdate;
}
_copyBufferToTexture( image, format, textureGPU ) {
// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
// @TODO: Consider to support valid buffer layouts with other formats like RGB
const data = image.data;
const bytesPerTexel = this._getBytesPerTexel( format );
const bytesPerRow = Math.ceil( image.width * bytesPerTexel / 256 ) * 256;
this.device.queue.writeTexture(
{
texture: textureGPU,
mipLevel: 0
},
data,
{
offset: 0,
bytesPerRow
},
{
width: image.width,
height: image.height,
depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1
} );
}
_copyCubeMapToTexture( images, texture, textureGPU ) {
for ( let i = 0; i < images.length; i ++ ) {
const image = images[ i ];
this._getImageBitmap( image, texture ).then( imageBitmap => {
this._copyExternalImageToTexture( imageBitmap, textureGPU, { x: 0, y: 0, z: i } );
} );
}
}
_copyExternalImageToTexture( image, textureGPU, origin = { x: 0, y: 0, z: 0 } ) {
this.device.queue.copyExternalImageToTexture(
{
source: image
}, {
texture: textureGPU,
mipLevel: 0,
origin: origin
}, {
width: image.width,
height: image.height,
depthOrArrayLayers: 1
}
);
}
_copyCompressedBufferToTexture( mipmaps, format, textureGPU ) {
// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
const blockData = this._getBlockData( format );
for ( let i = 0; i < mipmaps.length; i ++ ) {
const mipmap = mipmaps[ i ];
const width = mipmap.width;
const height = mipmap.height;
const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength;
this.device.queue.writeTexture(
{
texture: textureGPU,
mipLevel: i
},
mipmap.data,
{
offset: 0,
bytesPerRow
},
{
width: Math.ceil( width / blockData.width ) * blockData.width,
height: Math.ceil( height / blockData.width ) * blockData.width,
depthOrArrayLayers: 1,
} );
}
}
_generateMipmaps( textureGPU, textureGPUDescriptor ) {
if ( this.utils === null ) {
this.utils = new WebGPUTextureUtils( this.device ); // only create this helper if necessary
}
this.utils.generateMipmaps( textureGPU, textureGPUDescriptor );
}
_getBlockData( format ) {
// this method is only relevant for compressed texture formats
if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1
if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3
if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5
if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSNorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1
if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2
if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float)
if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm)
}
_getBytesPerTexel( format ) {
if ( format === GPUTextureFormat.R8Unorm ) return 1;
if ( format === GPUTextureFormat.R16Float ) return 2;
if ( format === GPUTextureFormat.RG8Unorm ) return 2;
if ( format === GPUTextureFormat.RG16Float ) return 4;
if ( format === GPUTextureFormat.R32Float ) return 4;
if ( format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB ) return 4;
if ( format === GPUTextureFormat.RG32Float ) return 8;
if ( format === GPUTextureFormat.RGBA16Float ) return 8;
if ( format === GPUTextureFormat.RGBA32Float ) return 16;
}
_getDimension( texture ) {
let dimension;
if ( texture.isData3DTexture ) {
dimension = GPUTextureDimension.ThreeD;
} else {
dimension = GPUTextureDimension.TwoD;
}
return dimension;
}
_getFormat( texture ) {
const format = texture.format;
const type = texture.type;
const encoding = texture.encoding;
let formatGPU;
switch ( format ) {
case RGBA_S3TC_DXT1_Format:
formatGPU = ( encoding === sRGBEncoding ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm;
break;
case RGBA_S3TC_DXT3_Format:
formatGPU = ( encoding === sRGBEncoding ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm;
break;
case RGBA_S3TC_DXT5_Format:
formatGPU = ( encoding === sRGBEncoding ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm;
break;
case RGBAFormat:
switch ( type ) {
case UnsignedByteType:
formatGPU = ( encoding === sRGBEncoding ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm;
break;
case HalfFloatType:
formatGPU = GPUTextureFormat.RGBA16Float;
break;
case FloatType:
formatGPU = GPUTextureFormat.RGBA32Float;
break;
default:
console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type );
}
break;
case RedFormat:
switch ( type ) {
case UnsignedByteType:
formatGPU = GPUTextureFormat.R8Unorm;
break;
case HalfFloatType:
formatGPU = GPUTextureFormat.R16Float;
break;
case FloatType:
formatGPU = GPUTextureFormat.R32Float;
break;
default:
console.error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type );
}
break;
case RGFormat:
switch ( type ) {
case UnsignedByteType:
formatGPU = GPUTextureFormat.RG8Unorm;
break;
case HalfFloatType:
formatGPU = GPUTextureFormat.RG16Float;
break;
case FloatType:
formatGPU = GPUTextureFormat.RG32Float;
break;
default:
console.error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type );
}
break;
default:
console.error( 'WebGPURenderer: Unsupported texture format.', format );
}
return formatGPU;
}
_getImageBitmap( image, texture ) {
const width = image.width;
const height = image.height;
if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) {
const options = {};
options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none';
options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default';
return createImageBitmap( image, 0, 0, width, height, options );
} else {
// assume ImageBitmap
return Promise.resolve( image );
}
}
_getMipLevelCount( texture, width, height, needsMipmaps ) {
let mipLevelCount;
if ( texture.isCompressedTexture ) {
mipLevelCount = texture.mipmaps.length;
} else if ( needsMipmaps === true ) {
mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;
} else {
mipLevelCount = 1; // a texture without mipmaps has a base mip (mipLevel 0)
}
return mipLevelCount;
}
_getSize( texture ) {
const image = texture.image;
let width, height, depth;
if ( texture.isCubeTexture ) {
width = ( image.length > 0 ) ? image[ 0 ].width : 1;
height = ( image.length > 0 ) ? image[ 0 ].height : 1;
depth = 6; // one image for each side of the cube map
} else if ( image !== null ) {
width = image.width;
height = image.height;
depth = ( image.depth !== undefined ) ? image.depth : 1;
} else {
width = height = depth = 1;
}
return { width, height, depth };
}
_needsMipmaps( texture ) {
return ( texture.isCompressedTexture !== true ) && ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter );
}
}
function onRenderTargetDispose( event ) {
const renderTarget = event.target;
const properties = this.properties;
const renderTargetProperties = properties.get( renderTarget );
renderTarget.removeEventListener( 'dispose', renderTargetProperties.disposeCallback );
renderTargetProperties.colorTextureGPU.destroy();
properties.remove( renderTarget.texture );
this.info.memory.textures --;
if ( renderTarget.depthBuffer === true ) {
renderTargetProperties.depthTextureGPU.destroy();
this.info.memory.textures --;
if ( renderTarget.depthTexture !== null ) {
properties.remove( renderTarget.depthTexture );
}
}
properties.remove( renderTarget );
}
function onTextureDispose( event ) {
const texture = event.target;
const textureProperties = this.properties.get( texture );
textureProperties.textureGPU.destroy();
texture.removeEventListener( 'dispose', textureProperties.disposeCallback );
this.properties.remove( texture );
this.info.memory.textures --;
}
export default WebGPUTextures;

View File

@@ -0,0 +1,136 @@
import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three';
class WebGPUUniform {
constructor( name, value = null ) {
this.name = name;
this.value = value;
this.boundary = 0; // used to build the uniform buffer according to the STD140 layout
this.itemSize = 0;
this.offset = 0; // this property is set by WebGPUUniformsGroup and marks the start position in the uniform buffer
}
setValue( value ) {
this.value = value;
}
getValue() {
return this.value;
}
}
class FloatUniform extends WebGPUUniform {
constructor( name, value = 0 ) {
super( name, value );
this.boundary = 4;
this.itemSize = 1;
}
}
FloatUniform.prototype.isFloatUniform = true;
class Vector2Uniform extends WebGPUUniform {
constructor( name, value = new Vector2() ) {
super( name, value );
this.boundary = 8;
this.itemSize = 2;
}
}
Vector2Uniform.prototype.isVector2Uniform = true;
class Vector3Uniform extends WebGPUUniform {
constructor( name, value = new Vector3() ) {
super( name, value );
this.boundary = 16;
this.itemSize = 3;
}
}
Vector3Uniform.prototype.isVector3Uniform = true;
class Vector4Uniform extends WebGPUUniform {
constructor( name, value = new Vector4() ) {
super( name, value );
this.boundary = 16;
this.itemSize = 4;
}
}
Vector4Uniform.prototype.isVector4Uniform = true;
class ColorUniform extends WebGPUUniform {
constructor( name, value = new Color() ) {
super( name, value );
this.boundary = 16;
this.itemSize = 3;
}
}
ColorUniform.prototype.isColorUniform = true;
class Matrix3Uniform extends WebGPUUniform {
constructor( name, value = new Matrix3() ) {
super( name, value );
this.boundary = 48;
this.itemSize = 12;
}
}
Matrix3Uniform.prototype.isMatrix3Uniform = true;
class Matrix4Uniform extends WebGPUUniform {
constructor( name, value = new Matrix4() ) {
super( name, value );
this.boundary = 64;
this.itemSize = 16;
}
}
Matrix4Uniform.prototype.isMatrix4Uniform = true;
export { FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform };

View File

@@ -0,0 +1,45 @@
import WebGPUBinding from './WebGPUBinding.js';
import { getFloatLength } from './WebGPUBufferUtils.js';
import { GPUBindingType } from './constants.js';
class WebGPUUniformBuffer extends WebGPUBinding {
constructor( name, buffer = null ) {
super( name );
this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT;
this.type = GPUBindingType.UniformBuffer;
this.visibility = GPUShaderStage.VERTEX;
this.usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
this.buffer = buffer;
this.bufferGPU = null; // set by the renderer
}
getByteLength() {
return getFloatLength( this.buffer.byteLength );
}
getBuffer() {
return this.buffer;
}
update() {
return true;
}
}
WebGPUUniformBuffer.prototype.isUniformBuffer = true;
export default WebGPUUniformBuffer;

View File

@@ -0,0 +1,299 @@
import WebGPUUniformBuffer from './WebGPUUniformBuffer.js';
import { GPUChunkSize } from './constants.js';
class WebGPUUniformsGroup extends WebGPUUniformBuffer {
constructor( name ) {
super( name );
// the order of uniforms in this array must match the order of uniforms in the shader
this.uniforms = [];
}
addUniform( uniform ) {
this.uniforms.push( uniform );
return this;
}
removeUniform( uniform ) {
const index = this.uniforms.indexOf( uniform );
if ( index !== - 1 ) {
this.uniforms.splice( index, 1 );
}
return this;
}
getBuffer() {
let buffer = this.buffer;
if ( buffer === null ) {
const byteLength = this.getByteLength();
buffer = new Float32Array( new ArrayBuffer( byteLength ) );
this.buffer = buffer;
}
return buffer;
}
getByteLength() {
let offset = 0; // global buffer offset in bytes
for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) {
const uniform = this.uniforms[ i ];
// offset within a single chunk in bytes
const chunkOffset = offset % GPUChunkSize;
const remainingSizeInChunk = GPUChunkSize - chunkOffset;
// conformance tests
if ( chunkOffset !== 0 && ( remainingSizeInChunk - uniform.boundary ) < 0 ) {
// check for chunk overflow
offset += ( GPUChunkSize - chunkOffset );
} else if ( chunkOffset % uniform.boundary !== 0 ) {
// check for correct alignment
offset += ( chunkOffset % uniform.boundary );
}
uniform.offset = ( offset / this.bytesPerElement );
offset += ( uniform.itemSize * this.bytesPerElement );
}
return offset;
}
update() {
let updated = false;
for ( const uniform of this.uniforms ) {
if ( this.updateByType( uniform ) === true ) {
updated = true;
}
}
return updated;
}
updateByType( uniform ) {
if ( uniform.isFloatUniform ) return this.updateNumber( uniform );
if ( uniform.isVector2Uniform ) return this.updateVector2( uniform );
if ( uniform.isVector3Uniform ) return this.updateVector3( uniform );
if ( uniform.isVector4Uniform ) return this.updateVector4( uniform );
if ( uniform.isColorUniform ) return this.updateColor( uniform );
if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform );
if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform );
console.error( 'THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform );
}
updateNumber( uniform ) {
let updated = false;
const a = this.buffer;
const v = uniform.getValue();
const offset = uniform.offset;
if ( a[ offset ] !== v ) {
a[ offset ] = v;
updated = true;
}
return updated;
}
updateVector2( uniform ) {
let updated = false;
const a = this.buffer;
const v = uniform.getValue();
const offset = uniform.offset;
if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) {
a[ offset + 0 ] = v.x;
a[ offset + 1 ] = v.y;
updated = true;
}
return updated;
}
updateVector3( uniform ) {
let updated = false;
const a = this.buffer;
const v = uniform.getValue();
const offset = uniform.offset;
if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) {
a[ offset + 0 ] = v.x;
a[ offset + 1 ] = v.y;
a[ offset + 2 ] = v.z;
updated = true;
}
return updated;
}
updateVector4( uniform ) {
let updated = false;
const a = this.buffer;
const v = uniform.getValue();
const offset = uniform.offset;
if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) {
a[ offset + 0 ] = v.x;
a[ offset + 1 ] = v.y;
a[ offset + 2 ] = v.z;
a[ offset + 3 ] = v.w;
updated = true;
}
return updated;
}
updateColor( uniform ) {
let updated = false;
const a = this.buffer;
const c = uniform.getValue();
const offset = uniform.offset;
if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) {
a[ offset + 0 ] = c.r;
a[ offset + 1 ] = c.g;
a[ offset + 2 ] = c.b;
updated = true;
}
return updated;
}
updateMatrix3( uniform ) {
let updated = false;
const a = this.buffer;
const e = uniform.getValue().elements;
const offset = uniform.offset;
if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] ||
a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] ||
a[ offset + 8 ] !== e[ 6 ] || a[ offset + 9 ] !== e[ 7 ] || a[ offset + 10 ] !== e[ 8 ] ) {
a[ offset + 0 ] = e[ 0 ];
a[ offset + 1 ] = e[ 1 ];
a[ offset + 2 ] = e[ 2 ];
a[ offset + 4 ] = e[ 3 ];
a[ offset + 5 ] = e[ 4 ];
a[ offset + 6 ] = e[ 5 ];
a[ offset + 8 ] = e[ 6 ];
a[ offset + 9 ] = e[ 7 ];
a[ offset + 10 ] = e[ 8 ];
updated = true;
}
return updated;
}
updateMatrix4( uniform ) {
let updated = false;
const a = this.buffer;
const e = uniform.getValue().elements;
const offset = uniform.offset;
if ( arraysEqual( a, e, offset ) === false ) {
a.set( e, offset );
updated = true;
}
return updated;
}
}
function arraysEqual( a, b, offset ) {
for ( let i = 0, l = b.length; i < l; i ++ ) {
if ( a[ offset + i ] !== b[ i ] ) return false;
}
return true;
}
WebGPUUniformsGroup.prototype.isUniformsGroup = true;
export default WebGPUUniformsGroup;

View File

@@ -0,0 +1,261 @@
export const GPUPrimitiveTopology = {
PointList: 'point-list',
LineList: 'line-list',
LineStrip: 'line-strip',
TriangleList: 'triangle-list',
TriangleStrip: 'triangle-strip',
};
export const GPUCompareFunction = {
Never: 'never',
Less: 'less',
Equal: 'equal',
LessEqual: 'less-equal',
Greater: 'greater',
NotEqual: 'not-equal',
GreaterEqual: 'greater-equal',
Always: 'always'
};
export const GPUStoreOp = {
Store: 'store',
Discard: 'discard'
};
export const GPULoadOp = {
Load: 'load',
Clear: 'clear'
};
export const GPUFrontFace = {
CCW: 'ccw',
CW: 'cw'
};
export const GPUCullMode = {
None: 'none',
Front: 'front',
Back: 'back'
};
export const GPUIndexFormat = {
Uint16: 'uint16',
Uint32: 'uint32'
};
export const GPUVertexFormat = {
Uint8x2: 'uint8x2',
Uint8x4: 'uint8x4',
Sint8x2: 'sint8x2',
Sint8x4: 'sint8x4',
Unorm8x2: 'unorm8x2',
Unorm8x4: 'unorm8x4',
Snorm8x2: 'snorm8x2',
Snorm8x4: 'snorm8x4',
Uint16x2: 'uint16x2',
Uint16x4: 'uint16x4',
Sint16x2: 'sint16x2',
Sint16x4: 'sint16x4',
Unorm16x2: 'unorm16x2',
Unorm16x4: 'unorm16x4',
Snorm16x2: 'snorm16x2',
Snorm16x4: 'snorm16x4',
Float16x2: 'float16x2',
Float16x4: 'float16x4',
Float32: 'float32',
Float32x2: 'float32x2',
Float32x3: 'float32x3',
Float32x4: 'float32x4',
Uint32: 'uint32',
Uint32x2: 'uint32x2',
Uint32x3: 'uint32x3',
Uint32x4: 'uint32x4',
Sint32: 'sint32',
Sint32x2: 'sint32x2',
Sint32x3: 'sint32x3',
Sint32x4: 'sint32x4'
};
export const GPUTextureFormat = {
// 8-bit formats
R8Unorm: 'r8unorm',
R8Snorm: 'r8snorm',
R8Uint: 'r8uint',
R8Sint: 'r8sint',
// 16-bit formats
R16Uint: 'r16uint',
R16Sint: 'r16sint',
R16Float: 'r16float',
RG8Unorm: 'rg8unorm',
RG8Snorm: 'rg8snorm',
RG8Uint: 'rg8uint',
RG8Sint: 'rg8sint',
// 32-bit formats
R32Uint: 'r32uint',
R32Sint: 'r32sint',
R32Float: 'r32float',
RG16Uint: 'rg16uint',
RG16Sint: 'rg16sint',
RG16Float: 'rg16float',
RGBA8Unorm: 'rgba8unorm',
RGBA8UnormSRGB: 'rgba8unorm-srgb',
RGBA8Snorm: 'rgba8snorm',
RGBA8Uint: 'rgba8uint',
RGBA8Sint: 'rgba8sint',
BGRA8Unorm: 'bgra8unorm',
BGRA8UnormSRGB: 'bgra8unorm-srgb',
// Packed 32-bit formats
RGB9E5UFloat: 'rgb9e5ufloat',
RGB10A2Unorm: 'rgb10a2unorm',
RG11B10uFloat: 'rgb10a2unorm',
// 64-bit formats
RG32Uint: 'rg32uint',
RG32Sint: 'rg32sint',
RG32Float: 'rg32float',
RGBA16Uint: 'rgba16uint',
RGBA16Sint: 'rgba16sint',
RGBA16Float: 'rgba16float',
// 128-bit formats
RGBA32Uint: 'rgba32uint',
RGBA32Sint: 'rgba32sint',
RGBA32Float: 'rgba32float',
// Depth and stencil formats
Stencil8: 'stencil8',
Depth16Unorm: 'depth16unorm',
Depth24Plus: 'depth24plus',
Depth24PlusStencil8: 'depth24plus-stencil8',
Depth32Float: 'depth32float',
// BC compressed formats usable if 'texture-compression-bc' is both
// supported by the device/user agent and enabled in requestDevice.
BC1RGBAUnorm: 'bc1-rgba-unorm',
BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb',
BC2RGBAUnorm: 'bc2-rgba-unorm',
BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb',
BC3RGBAUnorm: 'bc3-rgba-unorm',
BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb',
BC4RUnorm: 'bc4-r-unorm',
BC4RSNorm: 'bc4-r-snorm',
BC5RGUnorm: 'bc5-rg-unorm',
BC5RGSnorm: 'bc5-rg-snorm',
BC6HRGBUFloat: 'bc6h-rgb-ufloat',
BC6HRGBFloat: 'bc6h-rgb-float',
BC7RGBAUnorm: 'bc7-rgba-unorm',
BC7RGBAUnormSRGB: 'bc7-rgba-srgb',
// 'depth24unorm-stencil8' extension
Depth24UnormStencil8: 'depth24unorm-stencil8',
// 'depth32float-stencil8' extension
Depth32FloatStencil8: 'depth32float-stencil8',
};
export const GPUAddressMode = {
ClampToEdge: 'clamp-to-edge',
Repeat: 'repeat',
MirrorRepeat: 'mirror-repeat'
};
export const GPUFilterMode = {
Linear: 'linear',
Nearest: 'nearest'
};
export const GPUBlendFactor = {
Zero: 'zero',
One: 'one',
SrcColor: 'src-color',
OneMinusSrcColor: 'one-minus-src-color',
SrcAlpha: 'src-alpha',
OneMinusSrcAlpha: 'one-minus-src-alpha',
DstColor: 'dst-color',
OneMinusDstColor: 'one-minus-dst-color',
DstAlpha: 'dst-alpha',
OneMinusDstAlpha: 'one-minus-dst-alpha',
SrcAlphaSaturated: 'src-alpha-saturated',
BlendColor: 'blend-color',
OneMinusBlendColor: 'one-minus-blend-color'
};
export const GPUBlendOperation = {
Add: 'add',
Subtract: 'subtract',
ReverseSubtract: 'reverse-subtract',
Min: 'min',
Max: 'max'
};
export const GPUColorWriteFlags = {
None: 0,
Red: 0x1,
Green: 0x2,
Blue: 0x4,
Alpha: 0x8,
All: 0xF
};
export const GPUStencilOperation = {
Keep: 'keep',
Zero: 'zero',
Replace: 'replace',
Invert: 'invert',
IncrementClamp: 'increment-clamp',
DecrementClamp: 'decrement-clamp',
IncrementWrap: 'increment-wrap',
DecrementWrap: 'decrement-wrap'
};
export const GPUBindingType = {
UniformBuffer: 'uniform-buffer',
StorageBuffer: 'storage-buffer',
ReadonlyStorageBuffer: 'readonly-storage-buffer',
Sampler: 'sampler',
ComparisonSampler: 'comparison-sampler',
SampledTexture: 'sampled-texture',
MultisampledTexture: 'multisampled-texture',
ReadonlyStorageTexture: 'readonly-storage-texture',
WriteonlyStorageTexture: 'writeonly-storage-texture'
};
export const GPUTextureDimension = {
OneD: '1d',
TwoD: '2d',
ThreeD: '3d'
};
export const GPUTextureViewDimension = {
OneD: '1d',
TwoD: '2d',
TwoDArray: '2d-array',
Cube: 'cube',
CubeArray: 'cube-array',
ThreeD: '3d'
};
export const GPUInputStepMode = {
Vertex: 'vertex',
Instance: 'instance'
};
export const GPUChunkSize = 16; // size of a chunk in bytes (STD140 layout)
// @TODO: Move to src/constants.js
export const BlendColorFactor = 211;
export const OneMinusBlendColorFactor = 212;

View File

@@ -0,0 +1,822 @@
import { LinearEncoding } from 'three';
import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup.js';
import {
FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
} from './WebGPUNodeUniform.js';
import WebGPUNodeSampler from './WebGPUNodeSampler.js';
import { WebGPUNodeSampledTexture } from './WebGPUNodeSampledTexture.js';
import WebGPUUniformBuffer from '../WebGPUUniformBuffer.js';
import { getVectorLength, getStrideLength } from '../WebGPUBufferUtils.js';
import VarNode from 'three-nodes/core/VarNode.js';
import CodeNode from 'three-nodes/core/CodeNode.js';
import BypassNode from 'three-nodes/core/BypassNode.js';
import ExpressionNode from 'three-nodes/core/ExpressionNode.js';
import NodeBuilder from 'three-nodes/core/NodeBuilder.js';
import MaterialNode from 'three-nodes/accessors/MaterialNode.js';
import PositionNode from 'three-nodes/accessors/PositionNode.js';
import NormalNode from 'three-nodes/accessors/NormalNode.js';
import ModelViewProjectionNode from 'three-nodes/accessors/ModelViewProjectionNode.js';
import SkinningNode from 'three-nodes/accessors/SkinningNode.js';
import ColorSpaceNode from 'three-nodes/display/ColorSpaceNode.js';
import LightContextNode from 'three-nodes/lights/LightContextNode.js';
import OperatorNode from 'three-nodes/math/OperatorNode.js';
import WGSLNodeParser from 'three-nodes/parsers/WGSLNodeParser.js';
import { add, join, nodeObject } from 'three-nodes/ShaderNode.js';
import { getRoughness } from 'three-nodes/functions/PhysicalMaterialFunctions.js';
const wgslTypeLib = {
float: 'f32',
int: 'i32',
vec2: 'vec2<f32>',
vec3: 'vec3<f32>',
vec4: 'vec4<f32>',
uvec4: 'vec4<u32>',
bvec3: 'vec3<bool>',
mat3: 'mat3x3<f32>',
mat4: 'mat4x4<f32>'
};
const wgslMethods = {
dFdx: 'dpdx',
dFdy: 'dpdy'
};
const wgslPolyfill = {
lessThanEqual: new CodeNode( `
fn lessThanEqual( a : vec3<f32>, b : vec3<f32> ) -> vec3<bool> {
return vec3<bool>( a.x <= b.x, a.y <= b.y, a.z <= b.z );
}
` ),
mod: new CodeNode( `
fn mod( x : f32, y : f32 ) -> f32 {
return x - y * floor( x / y );
}
` ),
repeatWrapping: new CodeNode( `
fn repeatWrapping( uv : vec2<f32>, dimension : vec2<i32> ) -> vec2<i32> {
let uvScaled = vec2<i32>( uv * vec2<f32>( dimension ) );
return ( ( uvScaled % dimension ) + dimension ) % dimension;
}
` ),
inversesqrt: new CodeNode( `
fn inversesqrt( x : f32 ) -> f32 {
return 1.0 / sqrt( x );
}
` )
};
class WebGPUNodeBuilder extends NodeBuilder {
constructor( object, renderer, lightNode = null ) {
super( object, renderer, new WGSLNodeParser() );
this.lightNode = lightNode;
this.bindings = { vertex: [], fragment: [] };
this.bindingsOffset = { vertex: 0, fragment: 0 };
this.uniformsGroup = {};
this._parseObject();
}
_parseObject() {
const object = this.object;
const material = this.material;
// parse inputs
if ( material.isMeshStandardMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
let lightNode = material.lightNode;
// VERTEX STAGE
let vertex = new PositionNode( PositionNode.GEOMETRY );
if ( lightNode === null && this.lightNode && this.lightNode.hasLights === true ) {
lightNode = this.lightNode;
}
if ( material.positionNode && material.positionNode.isNode ) {
const assignPositionNode = new OperatorNode( '=', new PositionNode( PositionNode.LOCAL ), material.positionNode );
vertex = new BypassNode( vertex, assignPositionNode );
}
if ( object.isSkinnedMesh === true ) {
vertex = new BypassNode( vertex, new SkinningNode( object ) );
}
this.context.vertex = vertex;
this.addFlow( 'vertex', new VarNode( new ModelViewProjectionNode(), 'MVP', 'vec4' ) );
// COLOR
let colorNode = null;
if ( material.colorNode && material.colorNode.isNode ) {
colorNode = material.colorNode;
} else {
colorNode = new MaterialNode( MaterialNode.COLOR );
}
colorNode = this.addFlow( 'fragment', new VarNode( colorNode, 'Color', 'vec4' ) );
const diffuseColorNode = this.addFlow( 'fragment', new VarNode( colorNode, 'DiffuseColor', 'vec4' ) );
// OPACITY
let opacityNode = null;
if ( material.opacityNode && material.opacityNode.isNode ) {
opacityNode = material.opacityNode;
} else {
opacityNode = new VarNode( new MaterialNode( MaterialNode.OPACITY ) );
}
this.addFlow( 'fragment', new VarNode( opacityNode, 'OPACITY', 'float' ) );
this.addFlow( 'fragment', new ExpressionNode( 'DiffuseColor.a = DiffuseColor.a * OPACITY;' ) );
// ALPHA TEST
let alphaTest = null;
if ( material.alphaTestNode && material.alphaTestNode.isNode ) {
alphaTest = material.alphaTestNode;
} else if ( material.alphaTest > 0 ) {
alphaTest = new MaterialNode( MaterialNode.ALPHA_TEST );
}
if ( alphaTest !== null ) {
this.addFlow( 'fragment', new VarNode( alphaTest, 'AlphaTest', 'float' ) );
this.addFlow( 'fragment', new ExpressionNode( 'if ( DiffuseColor.a <= AlphaTest ) { discard; }' ) );
}
if ( material.isMeshStandardMaterial ) {
// METALNESS
let metalnessNode = null;
if ( material.metalnessNode && material.metalnessNode.isNode ) {
metalnessNode = material.metalnessNode;
} else {
metalnessNode = new MaterialNode( MaterialNode.METALNESS );
}
this.addFlow( 'fragment', new VarNode( metalnessNode, 'Metalness', 'float' ) );
this.addFlow( 'fragment', new ExpressionNode( 'DiffuseColor = vec4<f32>( DiffuseColor.rgb * ( 1.0 - Metalness ), DiffuseColor.a );' ) );
// ROUGHNESS
let roughnessNode = null;
if ( material.roughnessNode && material.roughnessNode.isNode ) {
roughnessNode = material.roughnessNode;
} else {
roughnessNode = new MaterialNode( MaterialNode.ROUGHNESS );
}
roughnessNode = getRoughness( { roughness: roughnessNode } );
this.addFlow( 'fragment', new VarNode( roughnessNode, 'Roughness', 'float' ) );
// SPECULAR_TINT
this.addFlow( 'fragment', new VarNode( new ExpressionNode( 'mix( vec3<f32>( 0.04 ), Color.rgb, Metalness )', 'vec3' ), 'SpecularColor', 'color' ) );
// NORMAL_VIEW
let normalNode = null;
if ( material.normalNode && material.normalNode.isNode ) {
normalNode = material.normalNode;
} else {
normalNode = new NormalNode( NormalNode.VIEW );
}
this.addFlow( 'fragment', new VarNode( normalNode, 'TransformedNormalView', 'vec3' ) );
}
// LIGHT
let outputNode = diffuseColorNode;
if ( lightNode && lightNode.isNode ) {
const lightContextNode = new LightContextNode( lightNode );
outputNode = this.addFlow( 'fragment', new VarNode( lightContextNode, 'Light', 'vec3' ) );
}
// OUTGOING LIGHT
let outgoingLightNode = nodeObject( outputNode ).xyz;
// EMISSIVE
const emissiveNode = material.emissiveNode;
if ( emissiveNode && emissiveNode.isNode ) {
outgoingLightNode = add( emissiveNode, outgoingLightNode );
}
outputNode = join( outgoingLightNode.xyz, nodeObject( diffuseColorNode ).w );
// OUTPUT
const outputEncoding = this.renderer.outputEncoding;
if ( outputEncoding !== LinearEncoding ) {
outputNode = new ColorSpaceNode( ColorSpaceNode.LINEAR_TO_LINEAR, outputNode );
outputNode.fromEncoding( outputEncoding );
}
this.addFlow( 'fragment', new VarNode( outputNode, 'Output', 'vec4' ) );
}
}
addFlowCode( code ) {
if ( ! /;\s*$/.test( code ) ) {
code += ';';
}
super.addFlowCode( code + '\n\t' );
}
getTexture( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
if ( shaderStage === 'fragment' ) {
return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet} )`;
} else {
this._include( 'repeatWrapping' );
const dimension = `textureDimensions( ${textureProperty}, 0 )`;
return `textureLoad( ${textureProperty}, repeatWrapping( ${uvSnippet}, ${dimension} ), 0 )`;
}
}
getPropertyName( node, shaderStage = this.shaderStage ) {
if ( node.isNodeVary === true ) {
if ( shaderStage === 'vertex' ) {
return `NodeVarys.${ node.name }`;
}
} else if ( node.isNodeUniform === true ) {
const name = node.name;
const type = node.type;
if ( type === 'texture' ) {
return name;
} else if ( type === 'buffer' ) {
return `NodeBuffer.${name}`;
} else {
return `NodeUniforms.${name}`;
}
}
return super.getPropertyName( node );
}
getBindings() {
const bindings = this.bindings;
return [ ...bindings.vertex, ...bindings.fragment ];
}
getUniformFromNode( node, shaderStage, type ) {
const uniformNode = super.getUniformFromNode( node, shaderStage, type );
const nodeData = this.getDataFromNode( node, shaderStage );
if ( nodeData.uniformGPU === undefined ) {
let uniformGPU;
const bindings = this.bindings[ shaderStage ];
if ( type === 'texture' ) {
const sampler = new WebGPUNodeSampler( `${uniformNode.name}_sampler`, uniformNode.node );
const texture = new WebGPUNodeSampledTexture( uniformNode.name, uniformNode.node );
// add first textures in sequence and group for last
const lastBinding = bindings[ bindings.length - 1 ];
const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length;
if ( shaderStage === 'fragment' ) {
bindings.splice( index, 0, sampler, texture );
uniformGPU = [ sampler, texture ];
} else {
bindings.splice( index, 0, texture );
uniformGPU = [ texture ];
}
} else if ( type === 'buffer' ) {
const buffer = new WebGPUUniformBuffer( 'NodeBuffer', node.value );
// add first textures in sequence and group for last
const lastBinding = bindings[ bindings.length - 1 ];
const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length;
bindings.splice( index, 0, buffer );
uniformGPU = buffer;
} else {
let uniformsGroup = this.uniformsGroup[ shaderStage ];
if ( uniformsGroup === undefined ) {
uniformsGroup = new WebGPUNodeUniformsGroup( shaderStage );
this.uniformsGroup[ shaderStage ] = uniformsGroup;
bindings.push( uniformsGroup );
}
if ( node.isArrayInputNode === true ) {
uniformGPU = [];
for ( const inputNode of node.nodes ) {
const uniformNodeGPU = this._getNodeUniform( inputNode, type );
// fit bounds to buffer
uniformNodeGPU.boundary = getVectorLength( uniformNodeGPU.itemSize );
uniformNodeGPU.itemSize = getStrideLength( uniformNodeGPU.itemSize );
uniformsGroup.addUniform( uniformNodeGPU );
uniformGPU.push( uniformNodeGPU );
}
} else {
uniformGPU = this._getNodeUniform( uniformNode, type );
uniformsGroup.addUniform( uniformGPU );
}
}
nodeData.uniformGPU = uniformGPU;
if ( shaderStage === 'vertex' ) {
this.bindingsOffset[ 'fragment' ] = bindings.length;
}
}
return uniformNode;
}
getAttributes( shaderStage ) {
let snippet = '';
if ( shaderStage === 'vertex' ) {
const attributes = this.attributes;
const length = attributes.length;
snippet += '\n';
for ( let index = 0; index < length; index ++ ) {
const attribute = attributes[ index ];
const name = attribute.name;
const type = this.getType( attribute.type );
snippet += `\t@location( ${index} ) ${ name } : ${ type }`;
if ( index + 1 < length ) {
snippet += ',\n';
}
}
snippet += '\n';
}
return snippet;
}
getVars( shaderStage ) {
let snippet = '';
const vars = this.vars[ shaderStage ];
for ( let index = 0; index < vars.length; index ++ ) {
const variable = vars[ index ];
const name = variable.name;
const type = this.getType( variable.type );
snippet += `var ${name} : ${type}; `;
}
return snippet;
}
getVarys( shaderStage ) {
let snippet = '';
if ( shaderStage === 'vertex' ) {
snippet += '\t@builtin( position ) Vertex: vec4<f32>;\n';
const varys = this.varys;
for ( let index = 0; index < varys.length; index ++ ) {
const vary = varys[ index ];
snippet += `\t@location( ${index} ) ${ vary.name } : ${ this.getType( vary.type ) };\n`;
}
snippet = this._getWGSLStruct( 'NodeVarysStruct', snippet );
} else if ( shaderStage === 'fragment' ) {
const varys = this.varys;
snippet += '\n';
for ( let index = 0; index < varys.length; index ++ ) {
const vary = varys[ index ];
snippet += `\t@location( ${index} ) ${ vary.name } : ${ this.getType( vary.type ) }`;
if ( index + 1 < varys.length ) {
snippet += ',\n';
}
}
snippet += '\n';
}
return snippet;
}
getUniforms( shaderStage ) {
const uniforms = this.uniforms[ shaderStage ];
let snippet = '';
let groupSnippet = '';
let index = this.bindingsOffset[ shaderStage ];
for ( const uniform of uniforms ) {
if ( uniform.type === 'texture' ) {
if ( shaderStage === 'fragment' ) {
snippet += `@group( 0 ) @binding( ${index ++} ) var ${uniform.name}_sampler : sampler; `;
}
snippet += `@group( 0 ) @binding( ${index ++} ) var ${uniform.name} : texture_2d<f32>; `;
} else if ( uniform.type === 'buffer' ) {
const bufferNode = uniform.node;
const bufferType = this.getType( bufferNode.bufferType );
const bufferCount = bufferNode.bufferCount;
const bufferSnippet = `\t${uniform.name} : array< ${bufferType}, ${bufferCount} >;\n`;
snippet += this._getWGSLUniforms( 'NodeBuffer', bufferSnippet, index ++ ) + '\n\n';
} else {
const vectorType = this.getType( this.getVectorType( uniform.type ) );
if ( Array.isArray( uniform.value ) === true ) {
const length = uniform.value.length;
groupSnippet += `uniform ${vectorType}[ ${length} ] ${uniform.name}; `;
} else {
groupSnippet += `\t${uniform.name} : ${ vectorType};\n`;
}
}
}
if ( groupSnippet ) {
snippet += this._getWGSLUniforms( 'NodeUniforms', groupSnippet, index ++ );
}
return snippet;
}
buildCode() {
const shadersData = { fragment: {}, vertex: {} };
for ( const shaderStage in shadersData ) {
let flow = '// code\n';
flow += `\t${ this.flowCode[ shaderStage ] }`;
flow += '\n';
const flowNodes = this.flowNodes[ shaderStage ];
const mainNode = flowNodes[ flowNodes.length - 1 ];
for ( const node of flowNodes ) {
const flowSlotData = this.getFlowData( shaderStage, node );
const slotName = node.name;
if ( slotName ) {
if ( flow.length > 0 ) flow += '\n';
flow += `\t// FLOW -> ${ slotName }\n\t`;
}
flow += `${ flowSlotData.code }\n\t`;
if ( node === mainNode ) {
flow += '// FLOW RESULT\n\t';
if ( shaderStage === 'vertex' ) {
flow += 'NodeVarys.Vertex = ';
} else if ( shaderStage === 'fragment' ) {
flow += 'return ';
}
flow += `${ flowSlotData.result };`;
}
}
const stageData = shadersData[ shaderStage ];
stageData.uniforms = this.getUniforms( shaderStage );
stageData.attributes = this.getAttributes( shaderStage );
stageData.varys = this.getVarys( shaderStage );
stageData.vars = this.getVars( shaderStage );
stageData.codes = this.getCodes( shaderStage );
stageData.flow = flow;
}
this.vertexShader = this._getWGSLVertexCode( shadersData.vertex );
this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment );
}
getMethod( method ) {
if ( wgslPolyfill[ method ] !== undefined ) {
this._include( method );
}
return wgslMethods[ method ] || method;
}
getType( type ) {
return wgslTypeLib[ type ] || type;
}
_include( name ) {
wgslPolyfill[ name ].build( this );
}
_getNodeUniform( uniformNode, type ) {
if ( type === 'float' ) return new FloatNodeUniform( uniformNode );
if ( type === 'vec2' ) return new Vector2NodeUniform( uniformNode );
if ( type === 'vec3' ) return new Vector3NodeUniform( uniformNode );
if ( type === 'vec4' ) return new Vector4NodeUniform( uniformNode );
if ( type === 'color' ) return new ColorNodeUniform( uniformNode );
if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode );
if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode );
throw new Error( `Uniform "${type}" not declared.` );
}
_getWGSLVertexCode( shaderData ) {
return `${ this.getSignature() }
// uniforms
${shaderData.uniforms}
// varys
${shaderData.varys}
// codes
${shaderData.codes}
@stage( vertex )
fn main( ${shaderData.attributes} ) -> NodeVarysStruct {
// system
var NodeVarys: NodeVarysStruct;
// vars
${shaderData.vars}
// flow
${shaderData.flow}
return NodeVarys;
}
`;
}
_getWGSLFragmentCode( shaderData ) {
return `${ this.getSignature() }
// uniforms
${shaderData.uniforms}
// codes
${shaderData.codes}
@stage( fragment )
fn main( ${shaderData.varys} ) -> @location( 0 ) vec4<f32> {
// vars
${shaderData.vars}
// flow
${shaderData.flow}
}
`;
}
_getWGSLStruct( name, vars ) {
return `
struct ${name} {
\n${vars}
};`;
}
_getWGSLUniforms( name, vars, binding = 0, group = 0 ) {
const structName = name + 'Struct';
const structSnippet = this._getWGSLStruct( structName, vars );
return `${structSnippet}
@binding( ${binding} ) @group( ${group} )
var<uniform> ${name} : ${structName};`;
}
}
export default WebGPUNodeBuilder;

View File

@@ -0,0 +1,21 @@
import { WebGPUSampledTexture } from '../WebGPUSampledTexture.js';
class WebGPUNodeSampledTexture extends WebGPUSampledTexture {
constructor( name, textureNode ) {
super( name, textureNode.value );
this.textureNode = textureNode;
}
getTexture() {
return this.textureNode.value;
}
}
export { WebGPUNodeSampledTexture };

View File

@@ -0,0 +1,21 @@
import WebGPUSampler from '../WebGPUSampler.js';
class WebGPUNodeSampler extends WebGPUSampler {
constructor( name, textureNode ) {
super( name, textureNode.value );
this.textureNode = textureNode;
}
getTexture() {
return this.textureNode.value;
}
}
export default WebGPUNodeSampler;

View File

@@ -0,0 +1,135 @@
import {
FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform,
ColorUniform, Matrix3Uniform, Matrix4Uniform
} from '../WebGPUUniform.js';
class FloatNodeUniform extends FloatUniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class Vector2NodeUniform extends Vector2Uniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class Vector3NodeUniform extends Vector3Uniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class Vector4NodeUniform extends Vector4Uniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class ColorNodeUniform extends ColorUniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class Matrix3NodeUniform extends Matrix3Uniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
class Matrix4NodeUniform extends Matrix4Uniform {
constructor( nodeUniform ) {
super( nodeUniform.name, nodeUniform.value );
this.nodeUniform = nodeUniform;
}
getValue() {
return this.nodeUniform.value;
}
}
export {
FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
};

View File

@@ -0,0 +1,20 @@
import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js';
class WebGPUNodeUniformsGroup extends WebGPUUniformsGroup {
constructor( shaderStage ) {
super( 'nodeUniforms' );
let shaderStageVisibility;
if ( shaderStage === 'vertex' ) shaderStageVisibility = GPUShaderStage.VERTEX;
else if ( shaderStage === 'fragment' ) shaderStageVisibility = GPUShaderStage.FRAGMENT;
this.setVisibility( shaderStageVisibility );
}
}
export default WebGPUNodeUniformsGroup;

View File

@@ -0,0 +1,73 @@
import WebGPUNodeBuilder from './WebGPUNodeBuilder.js';
import NodeFrame from 'three-nodes/core/NodeFrame.js';
class WebGPUNodes {
constructor( renderer ) {
this.renderer = renderer;
this.nodeFrame = new NodeFrame();
this.builders = new WeakMap();
}
get( object, lightNode ) {
let nodeBuilder = this.builders.get( object );
if ( nodeBuilder === undefined ) {
nodeBuilder = new WebGPUNodeBuilder( object, this.renderer, lightNode ).build();
this.builders.set( object, nodeBuilder );
}
return nodeBuilder;
}
remove( object ) {
this.builders.delete( object );
}
updateFrame() {
this.nodeFrame.update();
}
update( object, camera, lightNode ) {
const renderer = this.renderer;
const material = object.material;
const nodeBuilder = this.get( object, lightNode );
const nodeFrame = this.nodeFrame;
nodeFrame.material = material;
nodeFrame.camera = camera;
nodeFrame.object = object;
nodeFrame.renderer = renderer;
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
dispose() {
this.builders = new WeakMap();
}
}
export default WebGPUNodes;