最新代码

This commit is contained in:
Teo
2025-07-29 11:22:30 +08:00
parent d503e64098
commit b01d143ea6
1490 changed files with 680232 additions and 28 deletions

View File

@ -0,0 +1,215 @@
import {
Matrix4,
Object3D,
Vector2,
Vector3
} from 'three';
class CSS2DObject extends Object3D {
constructor( element = document.createElement( 'div' ) ) {
super();
this.isCSS2DObject = true;
this.element = element;
this.element.style.position = 'absolute';
this.element.style.userSelect = 'none';
this.element.setAttribute( 'draggable', false );
this.center = new Vector2( 0.5, 0.5 ); // ( 0, 0 ) is the lower left; ( 1, 1 ) is the top right
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 );
this.center = source.center;
return this;
}
}
//
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.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) 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;
element.style.transform = 'translate(' + ( - 100 * object.center.x ) + '%,' + ( - 100 * object.center.y ) + '%)' + '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,329 @@
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.isCSS3DObject = true;
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;
}
}
class CSS3DSprite extends CSS3DObject {
constructor( element ) {
super( element );
this.isCSS3DSprite = true;
this.rotation2D = 0;
}
copy( source, recursive ) {
super.copy( source, recursive );
this.rotation2D = source.rotation2D;
return this;
}
}
//
const _matrix = new Matrix4();
const _matrix2 = new Matrix4();
class CSS3DRenderer {
constructor( parameters = {} ) {
const _this = this;
let _width, _height;
let _widthHalf, _heightHalf;
const cache = {
camera: { style: '' },
objects: new WeakMap()
};
const domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );
domElement.style.overflow = 'hidden';
this.domElement = domElement;
const viewElement = document.createElement( 'div' );
viewElement.style.transformOrigin = '0 0';
viewElement.style.pointerEvents = 'none';
domElement.appendChild( viewElement );
const cameraElement = document.createElement( 'div' );
cameraElement.style.transformStyle = 'preserve-3d';
viewElement.appendChild( cameraElement );
this.getSize = function () {
return {
width: _width,
height: _height
};
};
this.render = function ( scene, camera ) {
const fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;
if ( camera.view && camera.view.enabled ) {
// view offset
viewElement.style.transform = `translate( ${ - camera.view.offsetX * ( _width / camera.view.width ) }px, ${ - camera.view.offsetY * ( _height / camera.view.height ) }px )`;
// view fullWidth and fullHeight, view width and height
viewElement.style.transform += `scale( ${ camera.view.fullWidth / camera.view.width }, ${ camera.view.fullHeight / camera.view.height } )`;
} else {
viewElement.style.transform = '';
}
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
let tx, ty;
if ( camera.isOrthographicCamera ) {
tx = - ( camera.right + camera.left ) / 2;
ty = ( camera.top + camera.bottom ) / 2;
}
const scaleByViewOffset = camera.view && camera.view.enabled ? camera.view.height / camera.view.fullHeight : 1;
const cameraCSSMatrix = camera.isOrthographicCamera ?
`scale( ${ scaleByViewOffset } )` + 'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :
`scale( ${ scaleByViewOffset } )` + 'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );
const perspective = camera.isPerspectiveCamera ? 'perspective(' + fov + 'px) ' : '';
const style = perspective + 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';
viewElement.style.width = width + 'px';
viewElement.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,918 @@
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 = [];
//
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.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) 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 ) {
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 ( object.isLine ) {
_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
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 ( object.isPoints ) {
_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
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,556 @@
import {
Box2,
Camera,
Color,
Matrix3,
Matrix4,
Object3D,
SRGBColorSpace,
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.isSVGObject = true;
this.node = node;
}
}
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.outputColorSpace = SRGBColorSpace;
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.outputColorSpace );
};
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( _this.outputColorSpace );
} 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( _this.outputColorSpace ) + ';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( _this.outputColorSpace ) + ';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( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin;
} else {
style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';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,50 @@
class Animation {
constructor( nodes, info ) {
this.nodes = nodes;
this.info = info;
this.animationLoop = null;
this.requestId = null;
this._init();
}
_init() {
const update = ( time, frame ) => {
this.requestId = self.requestAnimationFrame( update );
if ( this.info.autoReset === true ) this.info.reset();
this.nodes.nodeFrame.update();
this.info.frame = this.nodes.nodeFrame.frameId;
if ( this.animationLoop !== null ) this.animationLoop( time, frame );
};
update();
}
dispose() {
self.cancelAnimationFrame( this.requestId );
this.requestId = null;
}
setAnimationLoop( callback ) {
this.animationLoop = callback;
}
}
export default Animation;

View File

@ -0,0 +1,75 @@
import DataMap from './DataMap.js';
import { AttributeType } from './Constants.js';
import { DynamicDrawUsage } from 'three';
class Attributes extends DataMap {
constructor( backend ) {
super();
this.backend = backend;
}
delete( attribute ) {
const attributeData = super.delete( attribute );
if ( attributeData !== undefined ) {
this.backend.destroyAttribute( attribute );
}
}
update( attribute, type ) {
const data = this.get( attribute );
if ( data.version === undefined ) {
if ( type === AttributeType.VERTEX ) {
this.backend.createAttribute( attribute );
} else if ( type === AttributeType.INDEX ) {
this.backend.createIndexAttribute( attribute );
} else if ( type === AttributeType.STORAGE ) {
this.backend.createStorageAttribute( attribute );
}
data.version = this._getBufferAttribute( attribute ).version;
} else {
const bufferAttribute = this._getBufferAttribute( attribute );
if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) {
this.backend.updateAttribute( attribute );
data.version = bufferAttribute.version;
}
}
}
_getBufferAttribute( attribute ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
return attribute;
}
}
export default Attributes;

View File

@ -0,0 +1,195 @@
let vector2 = null;
let vector4 = null;
let color4 = null;
import Color4 from './Color4.js';
import { Vector2, Vector4, REVISION, createCanvasElement } from 'three';
class Backend {
constructor( parameters = {} ) {
this.parameters = Object.assign( {}, parameters );
this.data = new WeakMap();
this.renderer = null;
this.domElement = null;
}
async init( renderer ) {
this.renderer = renderer;
}
// render context
begin( renderContext ) { }
finish( renderContext ) { }
// render object
draw( renderObject, info ) { }
// program
createProgram( program ) { }
destroyProgram( program ) { }
// bindings
createBindings( renderObject ) { }
updateBindings( renderObject ) { }
// pipeline
createRenderPipeline( renderObject ) { }
createComputePipeline( computeNode, pipeline ) { }
destroyPipeline( pipeline ) { }
// cache key
needsRenderUpdate( renderObject ) { } // return Boolean ( fast test )
getRenderCacheKey( renderObject ) { } // return String
// node builder
createNodeBuilder( renderObject ) { } // return NodeBuilder (ADD IT)
// textures
createSampler( texture ) { }
createDefaultTexture( texture ) { }
createTexture( texture ) { }
copyTextureToBuffer( texture, x, y, width, height ) {}
// attributes
createAttribute( attribute ) { }
createIndexAttribute( attribute ) { }
updateAttribute( attribute ) { }
destroyAttribute( attribute ) { }
// canvas
getContext() { }
updateSize() { }
// utils
resolveTimestampAsync( renderContext, type ) { }
hasFeatureAsync( name ) { } // return Boolean
hasFeature( name ) { } // return Boolean
getInstanceCount( renderObject ) {
const { object, geometry } = renderObject;
return geometry.isInstancedBufferGeometry ? geometry.instanceCount : ( object.isInstancedMesh ? object.count : 1 );
}
getDrawingBufferSize() {
vector2 = vector2 || new Vector2();
return this.renderer.getDrawingBufferSize( vector2 );
}
getScissor() {
vector4 = vector4 || new Vector4();
return this.renderer.getScissor( vector4 );
}
setScissorTest( boolean ) { }
getClearColor() {
const renderer = this.renderer;
color4 = color4 || new Color4();
renderer.getClearColor( color4 );
color4.getRGB( color4, this.renderer.currentColorSpace );
return color4;
}
getDomElement() {
let domElement = this.domElement;
if ( domElement === null ) {
domElement = ( this.parameters.canvas !== undefined ) ? this.parameters.canvas : createCanvasElement();
// OffscreenCanvas does not have setAttribute, see #22811
if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${REVISION} webgpu` );
this.domElement = domElement;
}
return domElement;
}
// resource properties
set( object, value ) {
this.data.set( object, value );
}
get( object ) {
let map = this.data.get( object );
if ( map === undefined ) {
map = {};
this.data.set( object, map );
}
return map;
}
has( object ) {
return this.data.has( object );
}
delete( object ) {
this.data.delete( object );
}
}
export default Backend;

View File

@ -0,0 +1,134 @@
import DataMap from './DataMap.js';
import Color4 from './Color4.js';
import { Mesh, SphereGeometry, BackSide, LinearSRGBColorSpace } from 'three';
import { vec4, context, normalWorld, backgroundBlurriness, backgroundIntensity, NodeMaterial, modelViewProjection } from '../../nodes/Nodes.js';
const _clearColor = new Color4();
class Background extends DataMap {
constructor( renderer, nodes ) {
super();
this.renderer = renderer;
this.nodes = nodes;
}
update( scene, renderList, renderContext ) {
const renderer = this.renderer;
const background = this.nodes.getBackgroundNode( scene ) || scene.background;
let forceClear = false;
if ( background === null ) {
// no background settings, use clear color configuration from the renderer
renderer._clearColor.getRGB( _clearColor, LinearSRGBColorSpace );
_clearColor.a = renderer._clearColor.a;
} else if ( background.isColor === true ) {
// background is an opaque color
background.getRGB( _clearColor, LinearSRGBColorSpace );
_clearColor.a = 1;
forceClear = true;
} else if ( background.isNode === true ) {
const sceneData = this.get( scene );
const backgroundNode = background;
_clearColor.copy( renderer._clearColor );
let backgroundMesh = sceneData.backgroundMesh;
if ( backgroundMesh === undefined ) {
const backgroundMeshNode = context( vec4( backgroundNode ).mul( backgroundIntensity ), {
// @TODO: Add Texture2D support using node context
getUV: () => normalWorld,
getTextureLevel: () => backgroundBlurriness
} );
let viewProj = modelViewProjection();
viewProj = viewProj.setZ( viewProj.w );
const nodeMaterial = new NodeMaterial();
nodeMaterial.side = BackSide;
nodeMaterial.depthTest = false;
nodeMaterial.depthWrite = false;
nodeMaterial.fog = false;
nodeMaterial.vertexNode = viewProj;
nodeMaterial.fragmentNode = backgroundMeshNode;
sceneData.backgroundMeshNode = backgroundMeshNode;
sceneData.backgroundMesh = backgroundMesh = new Mesh( new SphereGeometry( 1, 32, 32 ), nodeMaterial );
backgroundMesh.frustumCulled = false;
backgroundMesh.onBeforeRender = function ( renderer, scene, camera ) {
this.matrixWorld.copyPosition( camera.matrixWorld );
};
}
const backgroundCacheKey = backgroundNode.getCacheKey();
if ( sceneData.backgroundCacheKey !== backgroundCacheKey ) {
sceneData.backgroundMeshNode.node = vec4( backgroundNode ).mul( backgroundIntensity );
backgroundMesh.material.needsUpdate = true;
sceneData.backgroundCacheKey = backgroundCacheKey;
}
renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null );
} else {
console.error( 'THREE.Renderer: Unsupported background configuration.', background );
}
//
if ( renderer.autoClear === true || forceClear === true ) {
_clearColor.multiplyScalar( _clearColor.a );
const clearColorValue = renderContext.clearColorValue;
clearColorValue.r = _clearColor.r;
clearColorValue.g = _clearColor.g;
clearColorValue.b = _clearColor.b;
clearColorValue.a = _clearColor.a;
renderContext.depthClearValue = renderer._clearDepth;
renderContext.stencilClearValue = renderer._clearStencil;
renderContext.clearColor = renderer.autoClearColor === true;
renderContext.clearDepth = renderer.autoClearDepth === true;
renderContext.clearStencil = renderer.autoClearStencil === true;
} else {
renderContext.clearColor = false;
renderContext.clearDepth = false;
renderContext.clearStencil = false;
}
}
}
export default Background;

View File

@ -0,0 +1,25 @@
class Binding {
constructor( name = '' ) {
this.name = name;
this.visibility = 0;
}
setVisibility( visibility ) {
this.visibility |= visibility;
}
clone() {
return Object.assign( new this.constructor(), this );
}
}
export default Binding;

View File

@ -0,0 +1,173 @@
import DataMap from './DataMap.js';
import { AttributeType } from './Constants.js';
class Bindings extends DataMap {
constructor( backend, nodes, textures, attributes, pipelines, info ) {
super();
this.backend = backend;
this.textures = textures;
this.pipelines = pipelines;
this.attributes = attributes;
this.nodes = nodes;
this.info = info;
this.pipelines.bindings = this; // assign bindings to pipelines
}
getForRender( renderObject ) {
const bindings = renderObject.getBindings();
const data = this.get( renderObject );
if ( data.bindings !== bindings ) {
// each object defines an array of bindings (ubos, textures, samplers etc.)
data.bindings = bindings;
this._init( bindings );
this.backend.createBindings( bindings );
}
return data.bindings;
}
getForCompute( computeNode ) {
const data = this.get( computeNode );
if ( data.bindings === undefined ) {
const nodeBuilderState = this.nodes.getForCompute( computeNode );
const bindings = nodeBuilderState.bindings;
data.bindings = bindings;
this._init( bindings );
this.backend.createBindings( bindings );
}
return data.bindings;
}
updateForCompute( computeNode ) {
this._update( computeNode, this.getForCompute( computeNode ) );
}
updateForRender( renderObject ) {
this._update( renderObject, this.getForRender( renderObject ) );
}
_init( bindings ) {
for ( const binding of bindings ) {
if ( binding.isSampledTexture ) {
this.textures.updateTexture( binding.texture );
} else if ( binding.isStorageBuffer ) {
const attribute = binding.attribute;
this.attributes.update( attribute, AttributeType.STORAGE );
}
}
}
_update( object, bindings ) {
const { backend } = this;
let needsBindingsUpdate = false;
// iterate over all bindings and check if buffer updates or a new binding group is required
for ( const binding of bindings ) {
if ( binding.isNodeUniformsGroup ) {
const updated = this.nodes.updateGroup( binding );
if ( ! updated ) continue;
}
if ( binding.isUniformBuffer ) {
const updated = binding.update();
if ( updated ) {
backend.updateBinding( binding );
}
} else if ( binding.isSampledTexture ) {
const texture = binding.texture;
if ( binding.needsBindingsUpdate ) needsBindingsUpdate = true;
const updated = binding.update();
if ( updated ) {
this.textures.updateTexture( binding.texture );
}
if ( texture.isStorageTexture === true ) {
const textureData = this.get( texture );
if ( binding.store === true ) {
textureData.needsMipmap = true;
} else if ( texture.generateMipmaps === true && this.textures.needsMipmaps( texture ) && textureData.needsMipmap === true ) {
this.backend.generateMipmaps( texture );
textureData.needsMipmap = false;
}
}
}
}
if ( needsBindingsUpdate === true ) {
const pipeline = this.pipelines.getForRender( object );
this.backend.updateBindings( bindings, pipeline );
}
}
}
export default Bindings;

View File

@ -0,0 +1,38 @@
import Binding from './Binding.js';
import { getFloatLength } from './BufferUtils.js';
class Buffer extends Binding {
constructor( name, buffer = null ) {
super( name );
this.isBuffer = true;
this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT;
this._buffer = buffer;
}
get byteLength() {
return getFloatLength( this._buffer.byteLength );
}
get buffer() {
return this._buffer;
}
update() {
return true;
}
}
export default Buffer;

View File

@ -0,0 +1,33 @@
import { GPU_CHUNK_BYTES } from './Constants.js';
function getFloatLength( floatLength ) {
// ensure chunk size alignment (STD140 layout)
return floatLength + ( ( GPU_CHUNK_BYTES - ( floatLength % GPU_CHUNK_BYTES ) ) % GPU_CHUNK_BYTES );
}
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,89 @@
export default class ChainMap {
constructor() {
this.weakMap = new WeakMap();
}
get( keys ) {
if ( Array.isArray( keys ) ) {
let map = this.weakMap;
for ( let i = 0; i < keys.length; i ++ ) {
map = map.get( keys[ i ] );
if ( map === undefined ) return undefined;
}
return map.get( keys[ keys.length - 1 ] );
} else {
return super.get( keys );
}
}
set( keys, value ) {
if ( Array.isArray( keys ) ) {
let map = this.weakMap;
for ( let i = 0; i < keys.length; i ++ ) {
const key = keys[ i ];
if ( map.has( key ) === false ) map.set( key, new WeakMap() );
map = map.get( key );
}
return map.set( keys[ keys.length - 1 ], value );
} else {
return super.set( keys, value );
}
}
delete( keys ) {
if ( Array.isArray( keys ) ) {
let map = this.weakMap;
for ( let i = 0; i < keys.length; i ++ ) {
map = map.get( keys[ i ] );
if ( map === undefined ) return false;
}
return map.delete( keys[ keys.length - 1 ] );
} else {
return super.delete( keys );
}
}
dispose() {
this.weakMap.clear();
}
}

View File

@ -0,0 +1,165 @@
import { Matrix3, Plane, Vector4 } from 'three';
const _plane = new Plane();
const _viewNormalMatrix = new Matrix3();
let _clippingContextVersion = 0;
class ClippingContext {
constructor() {
this.version = ++ _clippingContextVersion;
this.globalClippingCount = 0;
this.localClippingCount = 0;
this.localClippingEnabled = false;
this.localClipIntersection = false;
this.planes = [];
this.parentVersion = 0;
}
projectPlanes( source, offset ) {
const l = source.length;
const planes = this.planes;
for ( let i = 0; i < l; i ++ ) {
_plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, _viewNormalMatrix );
const v = planes[ offset + i ];
const normal = _plane.normal;
v.x = - normal.x;
v.y = - normal.y;
v.z = - normal.z;
v.w = _plane.constant;
}
}
updateGlobal( renderer, camera ) {
const rendererClippingPlanes = renderer.clippingPlanes;
this.viewMatrix = camera.matrixWorldInverse;
_viewNormalMatrix.getNormalMatrix( this.viewMatrix );
let update = false;
if ( Array.isArray( rendererClippingPlanes ) && rendererClippingPlanes.length !== 0 ) {
const l = rendererClippingPlanes.length;
if ( l !== this.globalClippingCount ) {
const planes = [];
for ( let i = 0; i < l; i ++ ) {
planes.push( new Vector4() );
}
this.globalClippingCount = l;
this.planes = planes;
update = true;
}
this.projectPlanes( rendererClippingPlanes, 0 );
} else if ( this.globalClippingCount !== 0 ) {
this.globalClippingCount = 0;
this.planes = [];
update = true;
}
if ( renderer.localClippingEnabled !== this.localClippingEnabled ) {
this.localClippingEnabled = renderer.localClippingEnabled;
update = true;
}
if ( update ) this.version = _clippingContextVersion ++;
}
update( parent, material ) {
let update = false;
if ( this !== parent && parent.version !== this.parentVersion ) {
this.globalClippingCount = material.isShadowNodeMaterial ? 0 : parent.globalClippingCount;
this.localClippingEnabled = parent.localClippingEnabled;
this.planes = Array.from( parent.planes );
this.parentVersion = parent.version;
this.viewMatrix = parent.viewMatrix;
update = true;
}
if ( this.localClippingEnabled ) {
const localClippingPlanes = material.clippingPlanes;
if ( ( Array.isArray( localClippingPlanes ) && localClippingPlanes.length !== 0 ) ) {
const l = localClippingPlanes.length;
const planes = this.planes;
const offset = this.globalClippingCount;
if ( update || l !== this.localClippingCount ) {
planes.length = offset + l;
for ( let i = 0; i < l; i ++ ) {
planes[ offset + i ] = new Vector4();
}
this.localClippingCount = l;
update = true;
}
this.projectPlanes( localClippingPlanes, offset );
} else if ( this.localClippingCount !== 0 ) {
this.localClippingCount = 0;
update = true;
}
if ( this.localClipIntersection !== material.clipIntersection ) {
this.localClipIntersection = material.clipIntersection;
update = true;
}
}
if ( update ) this.version = _clippingContextVersion ++;
}
}
export default ClippingContext;

View File

@ -0,0 +1,37 @@
import { Color } from 'three';
class Color4 extends Color {
constructor( r, g, b, a = 1 ) {
super( r, g, b );
this.a = a;
}
set( r, g, b, a = 1 ) {
this.a = a;
return super.set( r, g, b );
}
copy( color ) {
if ( color.a !== undefined ) this.a = color.a;
return super.copy( color );
}
clone() {
return new this.constructor( this.r, this.g, this.b, this.a );
}
}
export default Color4;

View File

@ -0,0 +1,17 @@
import Pipeline from './Pipeline.js';
class ComputePipeline extends Pipeline {
constructor( cacheKey, computeProgram ) {
super( cacheKey );
this.computeProgram = computeProgram;
this.isComputePipeline = true;
}
}
export default ComputePipeline;

View File

@ -0,0 +1,14 @@
export const AttributeType = {
VERTEX: 1,
INDEX: 2,
STORAGE: 4
};
// size of a chunk in bytes (STD140 layout)
export const GPU_CHUNK_BYTES = 16;
// @TODO: Move to src/constants.js
export const BlendColorFactor = 211;
export const OneMinusBlendColorFactor = 212;

View File

@ -0,0 +1,65 @@
import { WebGLCubeRenderTarget, Scene, CubeCamera, BoxGeometry, Mesh, BackSide, NoBlending, LinearFilter, LinearMipmapLinearFilter } from 'three';
import { equirectUV } from '../../nodes/utils/EquirectUVNode.js';
import { texture as TSL_Texture } from '../../nodes/accessors/TextureNode.js';
import { positionWorldDirection } from '../../nodes/accessors/PositionNode.js';
import { createNodeMaterialFromType } from '../../nodes/materials/NodeMaterial.js';
// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget
class CubeRenderTarget extends WebGLCubeRenderTarget {
constructor( size = 1, options = {} ) {
super( size, options );
this.isCubeRenderTarget = true;
}
fromEquirectangularTexture( renderer, texture ) {
const currentMinFilter = texture.minFilter;
const currentGenerateMipmaps = texture.generateMipmaps;
texture.generateMipmaps = true;
this.texture.type = texture.type;
this.texture.colorSpace = texture.colorSpace;
this.texture.generateMipmaps = texture.generateMipmaps;
this.texture.minFilter = texture.minFilter;
this.texture.magFilter = texture.magFilter;
const geometry = new BoxGeometry( 5, 5, 5 );
const uvNode = equirectUV( positionWorldDirection );
const material = createNodeMaterialFromType( 'MeshBasicNodeMaterial' );
material.colorNode = TSL_Texture( texture, uvNode, 0 );
material.side = BackSide;
material.blending = NoBlending;
const mesh = new Mesh( geometry, material );
const scene = new Scene();
scene.add( mesh );
// Avoid blurred poles
if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter;
const camera = new CubeCamera( 1, 10, this );
camera.update( renderer, scene );
texture.minFilter = currentMinFilter;
texture.currentGenerateMipmaps = currentGenerateMipmaps;
mesh.geometry.dispose();
mesh.material.dispose();
return this;
}
}
export default CubeRenderTarget;

View File

@ -0,0 +1,54 @@
class DataMap {
constructor() {
this.data = new WeakMap();
}
get( object ) {
let map = this.data.get( object );
if ( map === undefined ) {
map = {};
this.data.set( object, map );
}
return map;
}
delete( object ) {
let map;
if ( this.data.has( object ) ) {
map = this.data.get( object );
this.data.delete( object );
}
return map;
}
has( object ) {
return this.data.has( object );
}
dispose() {
this.data = new WeakMap();
}
}
export default DataMap;

View File

@ -0,0 +1,215 @@
import DataMap from './DataMap.js';
import { AttributeType } from './Constants.js';
import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three';
function arrayNeedsUint32( array ) {
// assumes larger values usually on last
for ( let i = array.length - 1; i >= 0; -- i ) {
if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565
}
return false;
}
function getWireframeVersion( geometry ) {
return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;
}
function getWireframeIndex( geometry ) {
const indices = [];
const geometryIndex = geometry.index;
const geometryPosition = geometry.attributes.position;
if ( geometryIndex !== null ) {
const array = geometryIndex.array;
for ( let i = 0, l = array.length; i < l; i += 3 ) {
const a = array[ i + 0 ];
const b = array[ i + 1 ];
const c = array[ i + 2 ];
indices.push( a, b, b, c, c, a );
}
} else {
const array = geometryPosition.array;
for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
const a = i + 0;
const b = i + 1;
const c = i + 2;
indices.push( a, b, b, c, c, a );
}
}
const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
attribute.version = getWireframeVersion( geometry );
return attribute;
}
class Geometries extends DataMap {
constructor( attributes, info ) {
super();
this.attributes = attributes;
this.info = info;
this.wireframes = new WeakMap();
this.attributeCall = new WeakMap();
}
has( renderObject ) {
const geometry = renderObject.geometry;
return super.has( geometry ) && this.get( geometry ).initialized === true;
}
updateForRender( renderObject ) {
if ( this.has( renderObject ) === false ) this.initGeometry( renderObject );
this.updateAttributes( renderObject );
}
initGeometry( renderObject ) {
const geometry = renderObject.geometry;
const geometryData = this.get( geometry );
geometryData.initialized = true;
this.info.memory.geometries ++;
const onDispose = () => {
this.info.memory.geometries --;
const index = geometry.index;
const geometryAttributes = renderObject.getAttributes();
if ( index !== null ) {
this.attributes.delete( index );
}
for ( const geometryAttribute of geometryAttributes ) {
this.attributes.delete( geometryAttribute );
}
const wireframeAttribute = this.wireframes.get( geometry );
if ( wireframeAttribute !== undefined ) {
this.attributes.delete( wireframeAttribute );
}
geometry.removeEventListener( 'dispose', onDispose );
};
geometry.addEventListener( 'dispose', onDispose );
}
updateAttributes( renderObject ) {
const attributes = renderObject.getAttributes();
for ( const attribute of attributes ) {
this.updateAttribute( attribute, AttributeType.VERTEX );
}
const index = this.getIndex( renderObject );
if ( index !== null ) {
this.updateAttribute( index, AttributeType.INDEX );
}
}
updateAttribute( attribute, type ) {
const callId = this.info.render.calls;
if ( this.attributeCall.get( attribute ) !== callId ) {
this.attributes.update( attribute, type );
this.attributeCall.set( attribute, callId );
}
}
getIndex( renderObject ) {
const { geometry, material } = renderObject;
let index = geometry.index;
if ( material.wireframe === true ) {
const wireframes = this.wireframes;
let wireframeAttribute = wireframes.get( geometry );
if ( wireframeAttribute === undefined ) {
wireframeAttribute = getWireframeIndex( geometry );
wireframes.set( geometry, wireframeAttribute );
} else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) {
this.attributes.delete( wireframeAttribute );
wireframeAttribute = getWireframeIndex( geometry );
wireframes.set( geometry, wireframeAttribute );
}
index = wireframeAttribute;
}
return index;
}
}
export default Geometries;

View File

@ -0,0 +1,99 @@
class Info {
constructor() {
this.autoReset = true;
this.frame = 0;
this.calls = 0;
this.render = {
calls: 0,
drawCalls: 0,
triangles: 0,
points: 0,
lines: 0,
timestamp: 0
};
this.compute = {
calls: 0,
computeCalls: 0,
timestamp: 0
};
this.memory = {
geometries: 0,
textures: 0
};
}
update( object, count, instanceCount ) {
this.render.drawCalls ++;
if ( object.isMesh || object.isSprite ) {
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.' );
}
}
updateTimestamp( type, time ) {
this[ type ].timestamp += time;
}
reset() {
this.render.drawCalls = 0;
this.compute.computeCalls = 0;
this.render.triangles = 0;
this.render.points = 0;
this.render.lines = 0;
this.render.timestamp = 0;
this.compute.timestamp = 0;
}
dispose() {
this.reset();
this.calls = 0;
this.render.calls = 0;
this.compute.calls = 0;
this.render.timestamp = 0;
this.compute.timestamp = 0;
this.memory.geometries = 0;
this.memory.textures = 0;
}
}
export default Info;

View File

@ -0,0 +1,13 @@
class Pipeline {
constructor( cacheKey ) {
this.cacheKey = cacheKey;
this.usedTimes = 0;
}
}
export default Pipeline;

View File

@ -0,0 +1,322 @@
import DataMap from './DataMap.js';
import RenderPipeline from './RenderPipeline.js';
import ComputePipeline from './ComputePipeline.js';
import ProgrammableStage from './ProgrammableStage.js';
class Pipelines extends DataMap {
constructor( backend, nodes ) {
super();
this.backend = backend;
this.nodes = nodes;
this.bindings = null; // set by the bindings
this.caches = new Map();
this.programs = {
vertex: new Map(),
fragment: new Map(),
compute: new Map()
};
}
getForCompute( computeNode, bindings ) {
const { backend } = this;
const data = this.get( computeNode );
if ( this._needsComputeUpdate( computeNode ) ) {
const previousPipeline = data.pipeline;
if ( previousPipeline ) {
previousPipeline.usedTimes --;
previousPipeline.computeProgram.usedTimes --;
}
// get shader
const nodeBuilderState = this.nodes.getForCompute( computeNode );
// programmable stage
let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader );
if ( stageCompute === undefined ) {
if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram );
stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', nodeBuilderState.transforms, nodeBuilderState.nodeAttributes );
this.programs.compute.set( nodeBuilderState.computeShader, stageCompute );
backend.createProgram( stageCompute );
}
// determine compute pipeline
const cacheKey = this._getComputeCacheKey( computeNode, stageCompute );
let pipeline = this.caches.get( cacheKey );
if ( pipeline === undefined ) {
if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( computeNode );
pipeline = this._getComputePipeline( computeNode, stageCompute, cacheKey, bindings );
}
// keep track of all used times
pipeline.usedTimes ++;
stageCompute.usedTimes ++;
//
data.version = computeNode.version;
data.pipeline = pipeline;
}
return data.pipeline;
}
getForRender( renderObject, promises = null ) {
const { backend } = this;
const data = this.get( renderObject );
if ( this._needsRenderUpdate( renderObject ) ) {
const previousPipeline = data.pipeline;
if ( previousPipeline ) {
previousPipeline.usedTimes --;
previousPipeline.vertexProgram.usedTimes --;
previousPipeline.fragmentProgram.usedTimes --;
}
// get shader
const nodeBuilderState = renderObject.getNodeBuilderState();
// programmable stages
let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader );
if ( stageVertex === undefined ) {
if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram );
stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex' );
this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex );
backend.createProgram( stageVertex );
}
let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader );
if ( stageFragment === undefined ) {
if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram );
stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment' );
this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment );
backend.createProgram( stageFragment );
}
// determine render pipeline
const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment );
let pipeline = this.caches.get( cacheKey );
if ( pipeline === undefined ) {
if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline );
pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises );
} else {
renderObject.pipeline = pipeline;
}
// keep track of all used times
pipeline.usedTimes ++;
stageVertex.usedTimes ++;
stageFragment.usedTimes ++;
//
data.pipeline = pipeline;
}
return data.pipeline;
}
delete( object ) {
const pipeline = this.get( object ).pipeline;
if ( pipeline ) {
// pipeline
pipeline.usedTimes --;
if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline );
// programs
if ( pipeline.isComputePipeline ) {
pipeline.computeProgram.usedTimes --;
if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram );
} else {
pipeline.fragmentProgram.usedTimes --;
pipeline.vertexProgram.usedTimes --;
if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram );
if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram );
}
}
super.delete( object );
}
dispose() {
super.dispose();
this.caches = new Map();
this.programs = {
vertex: new Map(),
fragment: new Map(),
compute: new Map()
};
}
updateForRender( renderObject ) {
this.getForRender( renderObject );
}
_getComputePipeline( computeNode, stageCompute, cacheKey, bindings ) {
// check for existing pipeline
cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute );
let pipeline = this.caches.get( cacheKey );
if ( pipeline === undefined ) {
pipeline = new ComputePipeline( cacheKey, stageCompute );
this.caches.set( cacheKey, pipeline );
this.backend.createComputePipeline( pipeline, bindings );
}
return pipeline;
}
_getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) {
// check for existing pipeline
cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment );
let pipeline = this.caches.get( cacheKey );
if ( pipeline === undefined ) {
pipeline = new RenderPipeline( cacheKey, stageVertex, stageFragment );
this.caches.set( cacheKey, pipeline );
renderObject.pipeline = pipeline;
this.backend.createRenderPipeline( renderObject, promises );
}
return pipeline;
}
_getComputeCacheKey( computeNode, stageCompute ) {
return computeNode.id + ',' + stageCompute.id;
}
_getRenderCacheKey( renderObject, stageVertex, stageFragment ) {
return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject );
}
_releasePipeline( pipeline ) {
this.caches.delete( pipeline.cacheKey );
}
_releaseProgram( program ) {
const code = program.code;
const stage = program.stage;
this.programs[ stage ].delete( code );
}
_needsComputeUpdate( computeNode ) {
const data = this.get( computeNode );
return data.pipeline === undefined || data.version !== computeNode.version;
}
_needsRenderUpdate( renderObject ) {
const data = this.get( renderObject );
return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject );
}
}
export default Pipelines;

View File

@ -0,0 +1,33 @@
import { vec4, NodeMaterial } from '../../nodes/Nodes.js';
import QuadMesh from '../../objects/QuadMesh.js';
const quadMesh = new QuadMesh( new NodeMaterial() );
class PostProcessing {
constructor( renderer, outputNode = vec4( 0, 0, 1, 1 ) ) {
this.renderer = renderer;
this.outputNode = outputNode;
}
render() {
quadMesh.material.fragmentNode = this.outputNode;
quadMesh.render( this.renderer );
}
renderAsync() {
quadMesh.material.fragmentNode = this.outputNode;
return quadMesh.renderAsync( this.renderer );
}
}
export default PostProcessing;

View File

@ -0,0 +1,20 @@
let _id = 0;
class ProgrammableStage {
constructor( code, type, transforms = null, attributes = null ) {
this.id = _id ++;
this.code = code;
this.stage = type;
this.transforms = transforms;
this.attributes = attributes;
this.usedTimes = 0;
}
}
export default ProgrammableStage;

View File

@ -0,0 +1,43 @@
import { Vector4 } from 'three';
let id = 0;
class RenderContext {
constructor() {
this.id = id ++;
this.color = true;
this.clearColor = true;
this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 };
this.depth = true;
this.clearDepth = true;
this.clearDepthValue = 1;
this.stencil = false;
this.clearStencil = true;
this.clearStencilValue = 1;
this.viewport = false;
this.viewportValue = new Vector4();
this.scissor = false;
this.scissorValue = new Vector4();
this.textures = null;
this.depthTexture = null;
this.activeCubeFace = 0;
this.sampleCount = 1;
this.width = 0;
this.height = 0;
this.isRenderContext = true;
}
}
export default RenderContext;

View File

@ -0,0 +1,63 @@
import ChainMap from './ChainMap.js';
import RenderContext from './RenderContext.js';
class RenderContexts {
constructor() {
this.chainMaps = {};
}
get( scene, camera, renderTarget = null ) {
const chainKey = [ scene, camera ];
let attachmentState;
if ( renderTarget === null ) {
attachmentState = 'default';
} else {
const format = renderTarget.texture.format;
const count = renderTarget.count;
attachmentState = `${ count }:${ format }:${ renderTarget.samples }:${ renderTarget.depthBuffer }:${ renderTarget.stencilBuffer }`;
}
const chainMap = this.getChainMap( attachmentState );
let renderState = chainMap.get( chainKey );
if ( renderState === undefined ) {
renderState = new RenderContext();
chainMap.set( chainKey, renderState );
}
if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples;
return renderState;
}
getChainMap( attachmentState ) {
return this.chainMaps[ attachmentState ] || ( this.chainMaps[ attachmentState ] = new ChainMap() );
}
dispose() {
this.chainMaps = {};
}
}
export default RenderContexts;

View File

@ -0,0 +1,186 @@
import { LightsNode } from '../../nodes/Nodes.js';
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 RenderList {
constructor() {
this.renderItems = [];
this.renderItemsIndex = 0;
this.opaque = [];
this.transparent = [];
this.lightsNode = new LightsNode( [] );
this.lightsArray = [];
this.occlusionQueryCount = 0;
}
begin() {
this.renderItemsIndex = 0;
this.opaque.length = 0;
this.transparent.length = 0;
this.lightsArray.length = 0;
this.occlusionQueryCount = 0;
return this;
}
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 );
if ( object.occlusionTest === true ) this.occlusionQueryCount ++;
( material.transparent === true || material.transmission > 0 ? 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 );
}
pushLight( light ) {
this.lightsArray.push( light );
}
getLightsNode() {
return this.lightsNode.fromLights( this.lightsArray );
}
sort( customOpaqueSort, customTransparentSort ) {
if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable );
if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable );
}
finish() {
// update lights
this.lightsNode.fromLights( this.lightsArray );
// 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.groupOrder = null;
renderItem.renderOrder = null;
renderItem.z = null;
renderItem.group = null;
}
}
}
export default RenderList;

View File

@ -0,0 +1,38 @@
import ChainMap from './ChainMap.js';
import RenderList from './RenderList.js';
class RenderLists {
constructor() {
this.lists = new ChainMap();
}
get( scene, camera ) {
const lists = this.lists;
const keys = [ scene, camera ];
let list = lists.get( keys );
if ( list === undefined ) {
list = new RenderList();
lists.set( keys, list );
}
return list;
}
dispose() {
this.lists = new ChainMap();
}
}
export default RenderLists;

View File

@ -0,0 +1,263 @@
import ClippingContext from './ClippingContext.js';
let id = 0;
function getKeys( obj ) {
const keys = Object.keys( obj );
let proto = Object.getPrototypeOf( obj );
while ( proto ) {
const descriptors = Object.getOwnPropertyDescriptors( proto );
for ( const key in descriptors ) {
if ( descriptors[ key ] !== undefined ) {
const descriptor = descriptors[ key ];
if ( descriptor && typeof descriptor.get === 'function' ) {
keys.push( key );
}
}
}
proto = Object.getPrototypeOf( proto );
}
return keys;
}
export default class RenderObject {
constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext ) {
this._nodes = nodes;
this._geometries = geometries;
this.id = id ++;
this.renderer = renderer;
this.object = object;
this.material = material;
this.scene = scene;
this.camera = camera;
this.lightsNode = lightsNode;
this.context = renderContext;
this.geometry = object.geometry;
this.version = material.version;
this.drawRange = null;
this.attributes = null;
this.pipeline = null;
this.vertexBuffers = null;
this.updateClipping( renderContext.clippingContext );
this.clippingContextVersion = this.clippingContext.version;
this.initialNodesCacheKey = this.getNodesCacheKey();
this.initialCacheKey = this.getCacheKey();
this._nodeBuilderState = null;
this._bindings = null;
this.onDispose = null;
this.isRenderObject = true;
this.onMaterialDispose = () => {
this.dispose();
};
this.material.addEventListener( 'dispose', this.onMaterialDispose );
}
updateClipping( parent ) {
const material = this.material;
let clippingContext = this.clippingContext;
if ( Array.isArray( material.clippingPlanes ) ) {
if ( clippingContext === parent || ! clippingContext ) {
clippingContext = new ClippingContext();
this.clippingContext = clippingContext;
}
clippingContext.update( parent, material );
} else if ( this.clippingContext !== parent ) {
this.clippingContext = parent;
}
}
get clippingNeedsUpdate() {
if ( this.clippingContext.version === this.clippingContextVersion ) return false;
this.clippingContextVersion = this.clippingContext.version;
return true;
}
getNodeBuilderState() {
return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) );
}
getBindings() {
return this._bindings || ( this._bindings = this.getNodeBuilderState().createBindings() );
}
getIndex() {
return this._geometries.getIndex( this );
}
getChainArray() {
return [ this.object, this.material, this.context, this.lightsNode ];
}
getAttributes() {
if ( this.attributes !== null ) return this.attributes;
const nodeAttributes = this.getNodeBuilderState().nodeAttributes;
const geometry = this.geometry;
const attributes = [];
const vertexBuffers = new Set();
for ( const nodeAttribute of nodeAttributes ) {
const attribute = nodeAttribute.node && nodeAttribute.node.attribute ? nodeAttribute.node.attribute : geometry.getAttribute( nodeAttribute.name );
if ( attribute === undefined ) continue;
attributes.push( attribute );
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
vertexBuffers.add( bufferAttribute );
}
this.attributes = attributes;
this.vertexBuffers = Array.from( vertexBuffers.values() );
return attributes;
}
getVertexBuffers() {
if ( this.vertexBuffers === null ) this.getAttributes();
return this.vertexBuffers;
}
getMaterialCacheKey() {
const { object, material } = this;
let cacheKey = material.customProgramCacheKey();
for ( const property of getKeys( material ) ) {
if ( /^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue;
let value = material[ property ];
if ( value !== null ) {
const type = typeof value;
if ( type === 'number' ) value = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc
else if ( type === 'object' ) value = '{}';
}
cacheKey += /*property + ':' +*/ value + ',';
}
cacheKey += this.clippingContextVersion + ',';
if ( object.skeleton ) {
cacheKey += object.skeleton.bones.length + ',';
}
if ( object.morphTargetInfluences ) {
cacheKey += object.morphTargetInfluences.length + ',';
}
if ( object.isBatchedMesh ) {
cacheKey += object._matricesTexture.uuid + ',';
}
return cacheKey;
}
get needsUpdate() {
return this.initialNodesCacheKey !== this.getNodesCacheKey() || this.clippingNeedsUpdate;
}
getNodesCacheKey() {
// Environment Nodes Cache Key
return this._nodes.getCacheKey( this.scene, this.lightsNode );
}
getCacheKey() {
return this.getMaterialCacheKey() + ',' + this.getNodesCacheKey();
}
dispose() {
this.material.removeEventListener( 'dispose', this.onMaterialDispose );
this.onDispose();
}
}

View File

@ -0,0 +1,93 @@
import ChainMap from './ChainMap.js';
import RenderObject from './RenderObject.js';
class RenderObjects {
constructor( renderer, nodes, geometries, pipelines, bindings, info ) {
this.renderer = renderer;
this.nodes = nodes;
this.geometries = geometries;
this.pipelines = pipelines;
this.bindings = bindings;
this.info = info;
this.chainMaps = {};
}
get( object, material, scene, camera, lightsNode, renderContext, passId ) {
const chainMap = this.getChainMap( passId );
const chainArray = [ object, material, renderContext, lightsNode ];
let renderObject = chainMap.get( chainArray );
if ( renderObject === undefined ) {
renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, passId );
chainMap.set( chainArray, renderObject );
} else {
renderObject.updateClipping( renderContext.clippingContext );
if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
renderObject.dispose();
renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, passId );
} else {
renderObject.version = material.version;
}
}
}
return renderObject;
}
getChainMap( passId = 'default' ) {
return this.chainMaps[ passId ] || ( this.chainMaps[ passId ] = new ChainMap() );
}
dispose() {
this.chainMaps = {};
}
createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, passId ) {
const chainMap = this.getChainMap( passId );
const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext );
renderObject.onDispose = () => {
this.pipelines.delete( renderObject );
this.bindings.delete( renderObject );
this.nodes.delete( renderObject );
chainMap.delete( renderObject.getChainArray() );
};
return renderObject;
}
}
export default RenderObjects;

View File

@ -0,0 +1,16 @@
import Pipeline from './Pipeline.js';
class RenderPipeline extends Pipeline {
constructor( cacheKey, vertexProgram, fragmentProgram ) {
super( cacheKey );
this.vertexProgram = vertexProgram;
this.fragmentProgram = fragmentProgram;
}
}
export default RenderPipeline;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
import Binding from './Binding.js';
let id = 0;
class SampledTexture extends Binding {
constructor( name, texture ) {
super( name );
this.id = id ++;
this.texture = texture;
this.version = texture ? texture.version : 0;
this.store = false;
this.isSampledTexture = true;
}
get needsBindingsUpdate() {
const { texture, version } = this;
return texture.isVideoTexture ? true : version !== texture.version; // @TODO: version === 0 && texture.version > 0 ( add it just to External Textures like PNG,JPG )
}
update() {
const { texture, version } = this;
if ( version !== texture.version ) {
this.version = texture.version;
return true;
}
return false;
}
}
class SampledArrayTexture extends SampledTexture {
constructor( name, texture ) {
super( name, texture );
this.isSampledArrayTexture = true;
}
}
class Sampled3DTexture extends SampledTexture {
constructor( name, texture ) {
super( name, texture );
this.isSampled3DTexture = true;
}
}
class SampledCubeTexture extends SampledTexture {
constructor( name, texture ) {
super( name, texture );
this.isSampledCubeTexture = true;
}
}
export { SampledTexture, SampledArrayTexture, Sampled3DTexture, SampledCubeTexture };

View File

@ -0,0 +1,18 @@
import Binding from './Binding.js';
class Sampler extends Binding {
constructor( name, texture ) {
super( name );
this.texture = texture;
this.version = texture ? texture.version : 0;
this.isSampler = true;
}
}
export default Sampler;

View File

@ -0,0 +1,17 @@
import Buffer from './Buffer.js';
class StorageBuffer extends Buffer {
constructor( name, attribute ) {
super( name, attribute ? attribute.array : null );
this.attribute = attribute;
this.isStorageBuffer = true;
}
}
export default StorageBuffer;

View File

@ -0,0 +1,17 @@
import { BufferAttribute } from 'three';
class StorageBufferAttribute extends BufferAttribute {
constructor( array, itemSize, typeClass = Float32Array ) {
if ( ArrayBuffer.isView( array ) === false ) array = new typeClass( array * itemSize );
super( array, itemSize );
this.isStorageBufferAttribute = true;
}
}
export default StorageBufferAttribute;

View File

@ -0,0 +1,17 @@
import { InstancedBufferAttribute } from 'three';
class StorageInstancedBufferAttribute extends InstancedBufferAttribute {
constructor( array, itemSize, typeClass = Float32Array ) {
if ( ArrayBuffer.isView( array ) === false ) array = new typeClass( array * itemSize );
super( array, itemSize );
this.isStorageInstancedBufferAttribute = true;
}
}
export default StorageInstancedBufferAttribute;

View File

@ -0,0 +1,20 @@
import { Texture, LinearFilter } from 'three';
class StorageTexture extends Texture {
constructor( width = 1, height = 1 ) {
super();
this.image = { width, height };
this.magFilter = LinearFilter;
this.minFilter = LinearFilter;
this.isStorageTexture = true;
}
}
export default StorageTexture;

View File

@ -0,0 +1,344 @@
import DataMap from './DataMap.js';
import { Vector3, DepthTexture, DepthStencilFormat, DepthFormat, UnsignedIntType, UnsignedInt248Type, LinearFilter, NearestFilter, EquirectangularReflectionMapping, EquirectangularRefractionMapping, CubeReflectionMapping, CubeRefractionMapping, UnsignedByteType } from 'three';
const _size = new Vector3();
class Textures extends DataMap {
constructor( renderer, backend, info ) {
super();
this.renderer = renderer;
this.backend = backend;
this.info = info;
}
updateRenderTarget( renderTarget, activeMipmapLevel = 0 ) {
const renderTargetData = this.get( renderTarget );
const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples;
const depthTextureMips = renderTargetData.depthTextureMips || ( renderTargetData.depthTextureMips = {} );
const texture = renderTarget.texture;
const textures = renderTarget.textures;
const size = this.getSize( texture );
const mipWidth = size.width >> activeMipmapLevel;
const mipHeight = size.height >> activeMipmapLevel;
let depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ];
let textureNeedsUpdate = false;
if ( depthTexture === undefined ) {
depthTexture = new DepthTexture();
depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat;
depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType
depthTexture.image.width = mipWidth;
depthTexture.image.height = mipHeight;
depthTextureMips[ activeMipmapLevel ] = depthTexture;
}
if ( renderTargetData.width !== size.width || size.height !== renderTargetData.height ) {
textureNeedsUpdate = true;
depthTexture.needsUpdate = true;
depthTexture.image.width = mipWidth;
depthTexture.image.height = mipHeight;
}
renderTargetData.width = size.width;
renderTargetData.height = size.height;
renderTargetData.textures = textures;
renderTargetData.depthTexture = depthTexture;
renderTargetData.depth = renderTarget.depthBuffer;
renderTargetData.stencil = renderTarget.stencilBuffer;
renderTargetData.renderTarget = renderTarget;
if ( renderTargetData.sampleCount !== sampleCount ) {
textureNeedsUpdate = true;
depthTexture.needsUpdate = true;
renderTargetData.sampleCount = sampleCount;
}
//
const options = { sampleCount };
for ( let i = 0; i < textures.length; i ++ ) {
const texture = textures[ i ];
if ( textureNeedsUpdate ) texture.needsUpdate = true;
this.updateTexture( texture, options );
}
this.updateTexture( depthTexture, options );
// dispose handler
if ( renderTargetData.initialized !== true ) {
renderTargetData.initialized = true;
// dispose
const onDispose = () => {
renderTarget.removeEventListener( 'dispose', onDispose );
if ( textures !== undefined ) {
for ( let i = 0; i < textures.length; i ++ ) {
this._destroyTexture( textures[ i ] );
}
} else {
this._destroyTexture( texture );
}
this._destroyTexture( depthTexture );
};
renderTarget.addEventListener( 'dispose', onDispose );
}
}
updateTexture( texture, options = {} ) {
const textureData = this.get( texture );
if ( textureData.initialized === true && textureData.version === texture.version ) return;
const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture;
const backend = this.backend;
if ( isRenderTarget && textureData.initialized === true ) {
// it's an update
backend.destroySampler( texture );
backend.destroyTexture( texture );
}
//
if ( texture.isFramebufferTexture ) {
const renderer = this.renderer;
const renderTarget = renderer.getRenderTarget();
if ( renderTarget ) {
texture.type = renderTarget.texture.type;
} else {
texture.type = UnsignedByteType;
}
}
//
const { width, height, depth } = this.getSize( texture );
options.width = width;
options.height = height;
options.depth = depth;
options.needsMipmaps = this.needsMipmaps( texture );
options.levels = options.needsMipmaps ? this.getMipLevels( texture, width, height ) : 1;
//
if ( isRenderTarget || texture.isStorageTexture === true ) {
backend.createSampler( texture );
backend.createTexture( texture, options );
} else {
const needsCreate = textureData.initialized !== true;
if ( needsCreate ) backend.createSampler( texture );
if ( texture.version > 0 ) {
const image = texture.image;
if ( image === undefined ) {
console.warn( 'THREE.Renderer: Texture marked for update but image is undefined.' );
} else if ( image.complete === false ) {
console.warn( 'THREE.Renderer: Texture marked for update but image is incomplete.' );
} else {
if ( texture.images ) {
const images = [];
for ( const image of texture.images ) {
images.push( image );
}
options.images = images;
} else {
options.image = image;
}
if ( textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true ) {
backend.createTexture( texture, options );
textureData.isDefaultTexture = false;
}
if ( texture.source.dataReady === true ) backend.updateTexture( texture, options );
if ( options.needsMipmaps && texture.mipmaps.length === 0 ) backend.generateMipmaps( texture );
}
} else {
// async update
backend.createDefaultTexture( texture );
textureData.isDefaultTexture = true;
}
}
// dispose handler
if ( textureData.initialized !== true ) {
textureData.initialized = true;
//
this.info.memory.textures ++;
// dispose
const onDispose = () => {
texture.removeEventListener( 'dispose', onDispose );
this._destroyTexture( texture );
this.info.memory.textures --;
};
texture.addEventListener( 'dispose', onDispose );
}
//
textureData.version = texture.version;
}
getSize( texture, target = _size ) {
let image = texture.images ? texture.images[ 0 ] : texture.image;
if ( image ) {
if ( image.image !== undefined ) image = image.image;
target.width = image.width;
target.height = image.height;
target.depth = texture.isCubeTexture ? 6 : ( image.depth || 1 );
} else {
target.width = target.height = target.depth = 1;
}
return target;
}
getMipLevels( texture, width, height ) {
let mipLevelCount;
if ( texture.isCompressedTexture ) {
mipLevelCount = texture.mipmaps.length;
} else {
mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;
}
return mipLevelCount;
}
needsMipmaps( texture ) {
if ( this.isEnvironmentTexture( texture ) ) return true;
return ( texture.isCompressedTexture === true ) || ( ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter ) );
}
isEnvironmentTexture( texture ) {
const mapping = texture.mapping;
return ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) || ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping );
}
_destroyTexture( texture ) {
this.backend.destroySampler( texture );
this.backend.destroyTexture( texture );
this.delete( texture );
}
}
export default Textures;

View File

@ -0,0 +1,140 @@
import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three';
class Uniform {
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 Uniform {
constructor( name, value = 0 ) {
super( name, value );
this.isFloatUniform = true;
this.boundary = 4;
this.itemSize = 1;
}
}
class Vector2Uniform extends Uniform {
constructor( name, value = new Vector2() ) {
super( name, value );
this.isVector2Uniform = true;
this.boundary = 8;
this.itemSize = 2;
}
}
class Vector3Uniform extends Uniform {
constructor( name, value = new Vector3() ) {
super( name, value );
this.isVector3Uniform = true;
this.boundary = 16;
this.itemSize = 3;
}
}
class Vector4Uniform extends Uniform {
constructor( name, value = new Vector4() ) {
super( name, value );
this.isVector4Uniform = true;
this.boundary = 16;
this.itemSize = 4;
}
}
class ColorUniform extends Uniform {
constructor( name, value = new Color() ) {
super( name, value );
this.isColorUniform = true;
this.boundary = 16;
this.itemSize = 3;
}
}
class Matrix3Uniform extends Uniform {
constructor( name, value = new Matrix3() ) {
super( name, value );
this.isMatrix3Uniform = true;
this.boundary = 48;
this.itemSize = 12;
}
}
class Matrix4Uniform extends Uniform {
constructor( name, value = new Matrix4() ) {
super( name, value );
this.isMatrix4Uniform = true;
this.boundary = 64;
this.itemSize = 16;
}
}
export {
FloatUniform,
Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform,
Matrix3Uniform, Matrix4Uniform
};

View File

@ -0,0 +1,15 @@
import Buffer from './Buffer.js';
class UniformBuffer extends Buffer {
constructor( name, buffer = null ) {
super( name, buffer );
this.isUniformBuffer = true;
}
}
export default UniformBuffer;

View File

@ -0,0 +1,301 @@
import UniformBuffer from './UniformBuffer.js';
import { GPU_CHUNK_BYTES } from './Constants.js';
class UniformsGroup extends UniformBuffer {
constructor( name ) {
super( name );
this.isUniformsGroup = true;
// 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;
}
get buffer() {
let buffer = this._buffer;
if ( buffer === null ) {
const byteLength = this.byteLength;
buffer = new Float32Array( new ArrayBuffer( byteLength ) );
this._buffer = buffer;
}
return buffer;
}
get byteLength() {
let offset = 0; // global buffer offset in bytes
for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) {
const uniform = this.uniforms[ i ];
const { boundary, itemSize } = uniform;
// offset within a single chunk in bytes
const chunkOffset = offset % GPU_CHUNK_BYTES;
const remainingSizeInChunk = GPU_CHUNK_BYTES - chunkOffset;
// conformance tests
if ( chunkOffset !== 0 && ( remainingSizeInChunk - boundary ) < 0 ) {
// check for chunk overflow
offset += ( GPU_CHUNK_BYTES - chunkOffset );
} else if ( chunkOffset % boundary !== 0 ) {
// check for correct alignment
offset += ( chunkOffset % boundary );
}
uniform.offset = ( offset / this.bytesPerElement );
offset += ( itemSize * this.bytesPerElement );
}
return Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES;
}
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;
}
export default UniformsGroup;

View File

@ -0,0 +1,773 @@
import NodeMaterial from '../../../nodes/materials/NodeMaterial.js';
import { getDirection, blur } from '../../../nodes/pmrem/PMREMUtils.js';
import { equirectUV } from '../../../nodes/utils/EquirectUVNode.js';
import { uniform } from '../../../nodes/core/UniformNode.js';
import { uniforms } from '../../../nodes/accessors/UniformsNode.js';
import { texture } from '../../../nodes/accessors/TextureNode.js';
import { cubeTexture } from '../../../nodes/accessors/CubeTextureNode.js';
import { float, vec3 } from '../../../nodes/shadernode/ShaderNode.js';
import { uv } from '../../../nodes/accessors/UVNode.js';
import { attribute } from '../../../nodes/core/AttributeNode.js';
import {
OrthographicCamera,
Color,
Vector3,
BufferGeometry,
BufferAttribute,
RenderTarget,
Mesh,
CubeReflectionMapping,
CubeRefractionMapping,
CubeUVReflectionMapping,
LinearFilter,
NoBlending,
RGBAFormat,
HalfFloatType,
BackSide,
LinearSRGBColorSpace,
PerspectiveCamera,
MeshBasicMaterial,
BoxGeometry
} from 'three';
const LOD_MIN = 4;
// The standard deviations (radians) associated with the extra mips. These are
// chosen to approximate a Trowbridge-Reitz distribution function times the
// geometric shadowing function. These sigma values squared must match the
// variance #defines in cube_uv_reflection_fragment.glsl.js.
const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ];
// The maximum length of the blur for loop. Smaller sigmas will use fewer
// samples and exit early, but not recompile the shader.
const MAX_SAMPLES = 20;
const _flatCamera = /*@__PURE__*/ new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera( 90, 1 );
const _clearColor = /*@__PURE__*/ new Color();
let _oldTarget = null;
let _oldActiveCubeFace = 0;
let _oldActiveMipmapLevel = 0;
// Golden Ratio
const PHI = ( 1 + Math.sqrt( 5 ) ) / 2;
const INV_PHI = 1 / PHI;
// Vertices of a dodecahedron (except the opposites, which represent the
// same axis), used as axis directions evenly spread on a sphere.
const _axisDirections = [
/*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ),
/*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ),
/*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ),
/*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ),
/*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ),
/*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ),
/*@__PURE__*/ new Vector3( - 1, 1, - 1 ),
/*@__PURE__*/ new Vector3( 1, 1, - 1 ),
/*@__PURE__*/ new Vector3( - 1, 1, 1 ),
/*@__PURE__*/ new Vector3( 1, 1, 1 )
];
//
// WebGPU Face indices
const _faceLib = [
3, 1, 5,
0, 4, 2
];
const direction = getDirection( uv(), attribute( 'faceIndex' ) ).normalize();
const outputDirection = vec3( direction.x, direction.y.negate(), direction.z );
/**
* This class generates a Prefiltered, Mipmapped Radiance Environment Map
* (PMREM) from a cubeMap environment texture. This allows different levels of
* blur to be quickly accessed based on material roughness. It is packed into a
* special CubeUV format that allows us to perform custom interpolation so that
* we can support nonlinear formats such as RGBE. Unlike a traditional mipmap
* chain, it only goes down to the LOD_MIN level (above), and then creates extra
* even more filtered 'mips' at the same LOD_MIN resolution, associated with
* higher roughness levels. In this way we maintain resolution to smoothly
* interpolate diffuse lighting while limiting sampling computation.
*
* Paper: Fast, Accurate Image-Based Lighting
* https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view
*/
class PMREMGenerator {
constructor( renderer ) {
this._renderer = renderer;
this._pingPongRenderTarget = null;
this._lodMax = 0;
this._cubeSize = 0;
this._lodPlanes = [];
this._sizeLods = [];
this._sigmas = [];
this._lodMeshes = [];
this._blurMaterial = null;
this._cubemapMaterial = null;
this._equirectMaterial = null;
this._backgroundBox = null;
}
/**
* Generates a PMREM from a supplied Scene, which can be faster than using an
* image if networking bandwidth is low. Optional sigma specifies a blur radius
* in radians to be applied to the scene before PMREM generation. Optional near
* and far planes ensure the scene is rendered in its entirety (the cubeCamera
* is placed at the origin).
*/
fromScene( scene, sigma = 0, near = 0.1, far = 100 ) {
_oldTarget = this._renderer.getRenderTarget();
_oldActiveCubeFace = this._renderer.getActiveCubeFace();
_oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
this._setSize( 256 );
const cubeUVRenderTarget = this._allocateTargets();
cubeUVRenderTarget.depthBuffer = true;
this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget );
if ( sigma > 0 ) {
this._blur( cubeUVRenderTarget, 0, 0, sigma );
}
this._applyPMREM( cubeUVRenderTarget );
this._cleanup( cubeUVRenderTarget );
return cubeUVRenderTarget;
}
/**
* Generates a PMREM from an equirectangular texture, which can be either LDR
* or HDR. The ideal input image size is 1k (1024 x 512),
* as this matches best with the 256 x 256 cubemap output.
*/
fromEquirectangular( equirectangular, renderTarget = null ) {
return this._fromTexture( equirectangular, renderTarget );
}
/**
* Generates a PMREM from an cubemap texture, which can be either LDR
* or HDR. The ideal input cube size is 256 x 256,
* as this matches best with the 256 x 256 cubemap output.
*/
fromCubemap( cubemap, renderTarget = null ) {
return this._fromTexture( cubemap, renderTarget );
}
/**
* Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
* your texture's network fetch for increased concurrency.
*/
compileCubemapShader() {
if ( this._cubemapMaterial === null ) {
this._cubemapMaterial = _getCubemapMaterial();
this._compileMaterial( this._cubemapMaterial );
}
}
/**
* Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
* your texture's network fetch for increased concurrency.
*/
compileEquirectangularShader() {
if ( this._equirectMaterial === null ) {
this._equirectMaterial = _getEquirectMaterial();
this._compileMaterial( this._equirectMaterial );
}
}
/**
* Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class,
* so you should not need more than one PMREMGenerator object. If you do, calling dispose() on
* one of them will cause any others to also become unusable.
*/
dispose() {
this._dispose();
if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose();
if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose();
if ( this._backgroundBox !== null ) {
this._backgroundBox.geometry.dispose();
this._backgroundBox.material.dispose();
}
}
// private interface
_setSize( cubeSize ) {
this._lodMax = Math.floor( Math.log2( cubeSize ) );
this._cubeSize = Math.pow( 2, this._lodMax );
}
_dispose() {
if ( this._blurMaterial !== null ) this._blurMaterial.dispose();
if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose();
for ( let i = 0; i < this._lodPlanes.length; i ++ ) {
this._lodPlanes[ i ].dispose();
}
}
_cleanup( outputTarget ) {
this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel );
outputTarget.scissorTest = false;
_setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height );
}
_fromTexture( texture, renderTarget ) {
if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) {
this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) );
} else { // Equirectangular
this._setSize( texture.image.width / 4 );
}
_oldTarget = this._renderer.getRenderTarget();
_oldActiveCubeFace = this._renderer.getActiveCubeFace();
_oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel();
const cubeUVRenderTarget = renderTarget || this._allocateTargets();
this._textureToCubeUV( texture, cubeUVRenderTarget );
this._applyPMREM( cubeUVRenderTarget );
this._cleanup( cubeUVRenderTarget );
return cubeUVRenderTarget;
}
_allocateTargets() {
const width = 3 * Math.max( this._cubeSize, 16 * 7 );
const height = 4 * this._cubeSize;
const params = {
magFilter: LinearFilter,
minFilter: LinearFilter,
generateMipmaps: false,
type: HalfFloatType,
format: RGBAFormat,
colorSpace: LinearSRGBColorSpace,
//depthBuffer: false
};
const cubeUVRenderTarget = _createRenderTarget( width, height, params );
if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) {
if ( this._pingPongRenderTarget !== null ) {
this._dispose();
}
this._pingPongRenderTarget = _createRenderTarget( width, height, params );
const { _lodMax } = this;
( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas, lodMeshes: this._lodMeshes } = _createPlanes( _lodMax ) );
this._blurMaterial = _getBlurShader( _lodMax, width, height );
}
return cubeUVRenderTarget;
}
_compileMaterial( material ) {
const tmpMesh = this._lodMeshes[ 0 ];
tmpMesh.material = material;
this._renderer.compile( tmpMesh, _flatCamera );
}
_sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) {
const cubeCamera = _cubeCamera;
cubeCamera.near = near;
cubeCamera.far = far;
// px, py, pz, nx, ny, nz
const upSign = [ - 1, 1, - 1, - 1, - 1, - 1 ];
const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ];
const renderer = this._renderer;
const originalAutoClear = renderer.autoClear;
renderer.getClearColor( _clearColor );
renderer.autoClear = false;
let backgroundBox = this._backgroundBox;
if ( backgroundBox === null ) {
const backgroundMaterial = new MeshBasicMaterial( {
name: 'PMREM.Background',
side: BackSide,
depthWrite: false,
depthTest: false
} );
backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial );
}
let useSolidColor = false;
const background = scene.background;
if ( background ) {
if ( background.isColor ) {
backgroundBox.material.color.copy( background );
scene.background = null;
useSolidColor = true;
}
} else {
backgroundBox.material.color.copy( _clearColor );
useSolidColor = true;
}
renderer.setRenderTarget( cubeUVRenderTarget );
renderer.clear();
if ( useSolidColor ) {
renderer.render( backgroundBox, cubeCamera );
}
for ( let i = 0; i < 6; i ++ ) {
const col = i % 3;
if ( col === 0 ) {
cubeCamera.up.set( 0, upSign[ i ], 0 );
cubeCamera.lookAt( forwardSign[ i ], 0, 0 );
} else if ( col === 1 ) {
cubeCamera.up.set( 0, 0, upSign[ i ] );
cubeCamera.lookAt( 0, forwardSign[ i ], 0 );
} else {
cubeCamera.up.set( 0, upSign[ i ], 0 );
cubeCamera.lookAt( 0, 0, forwardSign[ i ] );
}
const size = this._cubeSize;
_setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size );
renderer.render( scene, cubeCamera );
}
renderer.autoClear = originalAutoClear;
scene.background = background;
}
_textureToCubeUV( texture, cubeUVRenderTarget ) {
const renderer = this._renderer;
const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping );
if ( isCubeTexture ) {
if ( this._cubemapMaterial === null ) {
this._cubemapMaterial = _getCubemapMaterial( texture );
}
} else {
if ( this._equirectMaterial === null ) {
this._equirectMaterial = _getEquirectMaterial( texture );
}
}
const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial;
material.fragmentNode.value = texture;
const mesh = this._lodMeshes[ 0 ];
mesh.material = material;
const size = this._cubeSize;
_setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size );
renderer.setRenderTarget( cubeUVRenderTarget );
renderer.render( mesh, _flatCamera );
}
_applyPMREM( cubeUVRenderTarget ) {
const renderer = this._renderer;
const autoClear = renderer.autoClear;
renderer.autoClear = false;
const n = this._lodPlanes.length;
for ( let i = 1; i < n; i ++ ) {
const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] );
const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ];
this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis );
}
renderer.autoClear = autoClear;
}
/**
* This is a two-pass Gaussian blur for a cubemap. Normally this is done
* vertically and horizontally, but this breaks down on a cube. Here we apply
* the blur latitudinally (around the poles), and then longitudinally (towards
* the poles) to approximate the orthogonally-separable blur. It is least
* accurate at the poles, but still does a decent job.
*/
_blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) {
const pingPongRenderTarget = this._pingPongRenderTarget;
this._halfBlur(
cubeUVRenderTarget,
pingPongRenderTarget,
lodIn,
lodOut,
sigma,
'latitudinal',
poleAxis );
this._halfBlur(
pingPongRenderTarget,
cubeUVRenderTarget,
lodOut,
lodOut,
sigma,
'longitudinal',
poleAxis );
}
_halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) {
const renderer = this._renderer;
const blurMaterial = this._blurMaterial;
if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) {
console.error( 'blur direction must be either latitudinal or longitudinal!' );
}
// Number of standard deviations at which to cut off the discrete approximation.
const STANDARD_DEVIATIONS = 3;
const blurMesh = this._lodMeshes[ lodOut ];
blurMesh.material = blurMaterial;
const blurUniforms = blurMaterial.uniforms;
const pixels = this._sizeLods[ lodIn ] - 1;
const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 );
const sigmaPixels = sigmaRadians / radiansPerPixel;
const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES;
if ( samples > MAX_SAMPLES ) {
console.warn( `sigmaRadians, ${
sigmaRadians}, is too large and will clip, as it requested ${
samples} samples when the maximum is set to ${MAX_SAMPLES}` );
}
const weights = [];
let sum = 0;
for ( let i = 0; i < MAX_SAMPLES; ++ i ) {
const x = i / sigmaPixels;
const weight = Math.exp( - x * x / 2 );
weights.push( weight );
if ( i === 0 ) {
sum += weight;
} else if ( i < samples ) {
sum += 2 * weight;
}
}
for ( let i = 0; i < weights.length; i ++ ) {
weights[ i ] = weights[ i ] / sum;
}
targetIn.texture.frame = ( targetIn.texture.frame || 0 ) + 1;
blurUniforms.envMap.value = targetIn.texture;
blurUniforms.samples.value = samples;
blurUniforms.weights.array = weights;
blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0;
if ( poleAxis ) {
blurUniforms.poleAxis.value = poleAxis;
}
const { _lodMax } = this;
blurUniforms.dTheta.value = radiansPerPixel;
blurUniforms.mipInt.value = _lodMax - lodIn;
const outputSize = this._sizeLods[ lodOut ];
const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 );
const y = 4 * ( this._cubeSize - outputSize );
_setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize );
renderer.setRenderTarget( targetOut );
renderer.render( blurMesh, _flatCamera );
}
}
function _createPlanes( lodMax ) {
const lodPlanes = [];
const sizeLods = [];
const sigmas = [];
const lodMeshes = [];
let lod = lodMax;
const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length;
for ( let i = 0; i < totalLods; i ++ ) {
const sizeLod = Math.pow( 2, lod );
sizeLods.push( sizeLod );
let sigma = 1.0 / sizeLod;
if ( i > lodMax - LOD_MIN ) {
sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ];
} else if ( i === 0 ) {
sigma = 0;
}
sigmas.push( sigma );
const texelSize = 1.0 / ( sizeLod - 2 );
const min = - texelSize;
const max = 1 + texelSize;
const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ];
const cubeFaces = 6;
const vertices = 6;
const positionSize = 3;
const uvSize = 2;
const faceIndexSize = 1;
const position = new Float32Array( positionSize * vertices * cubeFaces );
const uv = new Float32Array( uvSize * vertices * cubeFaces );
const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces );
for ( let face = 0; face < cubeFaces; face ++ ) {
const x = ( face % 3 ) * 2 / 3 - 1;
const y = face > 2 ? 0 : - 1;
const coordinates = [
x, y, 0,
x + 2 / 3, y, 0,
x + 2 / 3, y + 1, 0,
x, y, 0,
x + 2 / 3, y + 1, 0,
x, y + 1, 0
];
const faceIdx = _faceLib[ face ];
position.set( coordinates, positionSize * vertices * faceIdx );
uv.set( uv1, uvSize * vertices * faceIdx );
const fill = [ faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx ];
faceIndex.set( fill, faceIndexSize * vertices * faceIdx );
}
const planes = new BufferGeometry();
planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) );
planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) );
planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) );
lodPlanes.push( planes );
lodMeshes.push( new Mesh( planes, null ) );
if ( lod > LOD_MIN ) {
lod --;
}
}
return { lodPlanes, sizeLods, sigmas, lodMeshes };
}
function _createRenderTarget( width, height, params ) {
const cubeUVRenderTarget = new RenderTarget( width, height, params );
cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
cubeUVRenderTarget.texture.name = 'PMREM.cubeUv';
cubeUVRenderTarget.texture.isPMREMTexture = true;
cubeUVRenderTarget.scissorTest = true;
return cubeUVRenderTarget;
}
function _setViewport( target, x, y, width, height ) {
const viewY = target.height - height - y;
target.viewport.set( x, viewY, width, height );
target.scissor.set( x, viewY, width, height );
}
function _getMaterial() {
const material = new NodeMaterial();
material.depthTest = false;
material.depthWrite = false;
material.blending = NoBlending;
return material;
}
function _getBlurShader( lodMax, width, height ) {
const weights = uniforms( new Array( MAX_SAMPLES ).fill( 0 ) );
const poleAxis = uniform( new Vector3( 0, 1, 0 ) );
const dTheta = uniform( 0 );
const n = float( MAX_SAMPLES );
const latitudinal = uniform( 0 ); // false, bool
const samples = uniform( 1 ); // int
const envMap = texture( null );
const mipInt = uniform( 0 ); // int
const CUBEUV_TEXEL_WIDTH = float( 1 / width );
const CUBEUV_TEXEL_HEIGHT = float( 1 / height );
const CUBEUV_MAX_MIP = float( lodMax );
const materialUniforms = {
n,
latitudinal,
weights,
poleAxis,
outputDirection,
dTheta,
samples,
envMap,
mipInt,
CUBEUV_TEXEL_WIDTH,
CUBEUV_TEXEL_HEIGHT,
CUBEUV_MAX_MIP
};
const material = _getMaterial();
material.uniforms = materialUniforms; // TODO: Move to outside of the material
material.fragmentNode = blur( { ...materialUniforms, latitudinal: latitudinal.equal( 1 ) } );
return material;
}
function _getCubemapMaterial( envTexture ) {
const material = _getMaterial();
material.fragmentNode = cubeTexture( envTexture, outputDirection );
return material;
}
function _getEquirectMaterial( envTexture ) {
const material = _getMaterial();
material.fragmentNode = texture( envTexture, equirectUV( outputDirection ), 0 );
return material;
}
export default PMREMGenerator;

View File

@ -0,0 +1,44 @@
class NodeBuilderState {
constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes, transforms = [] ) {
this.vertexShader = vertexShader;
this.fragmentShader = fragmentShader;
this.computeShader = computeShader;
this.transforms = transforms;
this.nodeAttributes = nodeAttributes;
this.bindings = bindings;
this.updateNodes = updateNodes;
this.updateBeforeNodes = updateBeforeNodes;
this.usedTimes = 0;
}
createBindings() {
const bindingsArray = [];
for ( const instanceBinding of this.bindings ) {
let binding = instanceBinding;
if ( instanceBinding.shared !== true ) {
binding = instanceBinding.clone();
}
bindingsArray.push( binding );
}
return bindingsArray;
}
}
export default NodeBuilderState;

View File

@ -0,0 +1,49 @@
import { SampledTexture } from '../SampledTexture.js';
class NodeSampledTexture extends SampledTexture {
constructor( name, textureNode ) {
super( name, textureNode ? textureNode.value : null );
this.textureNode = textureNode;
}
get needsBindingsUpdate() {
return this.textureNode.value !== this.texture || super.needsBindingsUpdate;
}
update() {
const { textureNode } = this;
if ( this.texture !== textureNode.value ) {
this.texture = textureNode.value;
return true;
}
return super.update();
}
}
class NodeSampledCubeTexture extends NodeSampledTexture {
constructor( name, textureNode ) {
super( name, textureNode );
this.isSampledCubeTexture = true;
}
}
export { NodeSampledTexture, NodeSampledCubeTexture };

View File

@ -0,0 +1,15 @@
import Sampler from '../Sampler.js';
class NodeSampler extends Sampler {
constructor( name, textureNode ) {
super( name, textureNode ? textureNode.value : null );
this.textureNode = textureNode;
}
}
export default NodeSampler;

View File

@ -0,0 +1,23 @@
import StorageBuffer from '../StorageBuffer.js';
let _id = 0;
class NodeStorageBuffer extends StorageBuffer {
constructor( nodeUniform ) {
super( 'StorageBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null );
this.nodeUniform = nodeUniform;
}
get buffer() {
return this.nodeUniform.value;
}
}
export default NodeStorageBuffer;

View File

@ -0,0 +1,135 @@
import {
FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform,
ColorUniform, Matrix3Uniform, Matrix4Uniform
} from '../Uniform.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,23 @@
import UniformBuffer from '../UniformBuffer.js';
let _id = 0;
class NodeUniformBuffer extends UniformBuffer {
constructor( nodeUniform ) {
super( 'UniformBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null );
this.nodeUniform = nodeUniform;
}
get buffer() {
return this.nodeUniform.value;
}
}
export default NodeUniformBuffer;

View File

@ -0,0 +1,44 @@
import UniformsGroup from '../UniformsGroup.js';
let id = 0;
class NodeUniformsGroup extends UniformsGroup {
constructor( name, groupNode ) {
super( name );
this.id = id ++;
this.groupNode = groupNode;
this.isNodeUniformsGroup = true;
}
get shared() {
return this.groupNode.shared;
}
getNodes() {
const nodes = [];
for ( const uniform of this.uniforms ) {
const node = uniform.nodeUniform.node;
if ( ! node ) throw new Error( 'NodeUniformsGroup: Uniform has no node.' );
nodes.push( node );
}
return nodes;
}
}
export default NodeUniformsGroup;

View File

@ -0,0 +1,467 @@
import DataMap from '../DataMap.js';
import ChainMap from '../ChainMap.js';
import NodeBuilderState from './NodeBuilderState.js';
import { EquirectangularReflectionMapping, EquirectangularRefractionMapping, NoToneMapping, SRGBColorSpace } from 'three';
import { NodeFrame, vec4, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, viewportBottomLeft, normalWorld, pmremTexture, viewportTopLeft } from '../../../nodes/Nodes.js';
class Nodes extends DataMap {
constructor( renderer, backend ) {
super();
this.renderer = renderer;
this.backend = backend;
this.nodeFrame = new NodeFrame();
this.nodeBuilderCache = new Map();
this.callHashCache = new ChainMap();
this.groupsData = new ChainMap();
}
updateGroup( nodeUniformsGroup ) {
const groupNode = nodeUniformsGroup.groupNode;
const name = groupNode.name;
// objectGroup is every updated
if ( name === objectGroup.name ) return true;
// renderGroup is updated once per render/compute call
if ( name === renderGroup.name ) {
const uniformsGroupData = this.get( nodeUniformsGroup );
const renderId = this.nodeFrame.renderId;
if ( uniformsGroupData.renderId !== renderId ) {
uniformsGroupData.renderId = renderId;
return true;
}
return false;
}
// frameGroup is updated once per frame
if ( name === frameGroup.name ) {
const uniformsGroupData = this.get( nodeUniformsGroup );
const frameId = this.nodeFrame.frameId;
if ( uniformsGroupData.frameId !== frameId ) {
uniformsGroupData.frameId = frameId;
return true;
}
return false;
}
// other groups are updated just when groupNode.needsUpdate is true
const groupChain = [ groupNode, nodeUniformsGroup ];
let groupData = this.groupsData.get( groupChain );
if ( groupData === undefined ) this.groupsData.set( groupChain, groupData = {} );
if ( groupData.version !== groupNode.version ) {
groupData.version = groupNode.version;
return true;
}
return false;
}
getForRenderCacheKey( renderObject ) {
return renderObject.initialCacheKey;
}
getForRender( renderObject ) {
const renderObjectData = this.get( renderObject );
let nodeBuilderState = renderObjectData.nodeBuilderState;
if ( nodeBuilderState === undefined ) {
const { nodeBuilderCache } = this;
const cacheKey = this.getForRenderCacheKey( renderObject );
nodeBuilderState = nodeBuilderCache.get( cacheKey );
if ( nodeBuilderState === undefined ) {
const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer, renderObject.scene );
nodeBuilder.material = renderObject.material;
nodeBuilder.context.material = renderObject.material;
nodeBuilder.lightsNode = renderObject.lightsNode;
nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
nodeBuilder.fogNode = this.getFogNode( renderObject.scene );
nodeBuilder.clippingContext = renderObject.clippingContext;
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
nodeBuilderCache.set( cacheKey, nodeBuilderState );
}
nodeBuilderState.usedTimes ++;
renderObjectData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
delete( object ) {
if ( object.isRenderObject ) {
const nodeBuilderState = this.get( object ).nodeBuilderState;
nodeBuilderState.usedTimes --;
if ( nodeBuilderState.usedTimes === 0 ) {
this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) );
}
}
return super.delete( object );
}
getForCompute( computeNode ) {
const computeData = this.get( computeNode );
let nodeBuilderState = computeData.nodeBuilderState;
if ( nodeBuilderState === undefined ) {
const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer );
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
computeData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
_createNodeBuilderState( nodeBuilder ) {
return new NodeBuilderState(
nodeBuilder.vertexShader,
nodeBuilder.fragmentShader,
nodeBuilder.computeShader,
nodeBuilder.getAttributesArray(),
nodeBuilder.getBindings(),
nodeBuilder.updateNodes,
nodeBuilder.updateBeforeNodes,
nodeBuilder.transforms
);
}
getEnvironmentNode( scene ) {
return scene.environmentNode || this.get( scene ).environmentNode || null;
}
getBackgroundNode( scene ) {
return scene.backgroundNode || this.get( scene ).backgroundNode || null;
}
getFogNode( scene ) {
return scene.fogNode || this.get( scene ).fogNode || null;
}
getCacheKey( scene, lightsNode ) {
const chain = [ scene, lightsNode ];
const callId = this.renderer.info.calls;
let cacheKeyData = this.callHashCache.get( chain );
if ( cacheKeyData === undefined || cacheKeyData.callId !== callId ) {
const environmentNode = this.getEnvironmentNode( scene );
const fogNode = this.getFogNode( scene );
const cacheKey = [];
if ( lightsNode ) cacheKey.push( lightsNode.getCacheKey() );
if ( environmentNode ) cacheKey.push( environmentNode.getCacheKey() );
if ( fogNode ) cacheKey.push( fogNode.getCacheKey() );
cacheKeyData = {
callId,
cacheKey: cacheKey.join( ',' )
};
this.callHashCache.set( chain, cacheKeyData );
}
return cacheKeyData.cacheKey;
}
updateScene( scene ) {
this.updateEnvironment( scene );
this.updateFog( scene );
this.updateBackground( scene );
}
get isToneMappingState() {
return this.renderer.getRenderTarget() ? false : true;
}
updateBackground( scene ) {
const sceneData = this.get( scene );
const background = scene.background;
if ( background ) {
if ( sceneData.background !== background ) {
let backgroundNode = null;
if ( background.isCubeTexture === true || ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping ) ) {
backgroundNode = pmremTexture( background, normalWorld );
} else if ( background.isTexture === true ) {
backgroundNode = texture( background, viewportBottomLeft ).setUpdateMatrix( true );
} else if ( background.isColor !== true ) {
console.error( 'WebGPUNodes: Unsupported background configuration.', background );
}
sceneData.backgroundNode = backgroundNode;
sceneData.background = background;
}
} else if ( sceneData.backgroundNode ) {
delete sceneData.backgroundNode;
delete sceneData.background;
}
}
updateFog( scene ) {
const sceneData = this.get( scene );
const fog = scene.fog;
if ( fog ) {
if ( sceneData.fog !== fog ) {
let fogNode = null;
if ( fog.isFogExp2 ) {
fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
} else if ( fog.isFog ) {
fogNode = rangeFog( reference( 'color', 'color', fog ), reference( 'near', 'float', fog ), reference( 'far', 'float', fog ) );
} else {
console.error( 'WebGPUNodes: Unsupported fog configuration.', fog );
}
sceneData.fogNode = fogNode;
sceneData.fog = fog;
}
} else {
delete sceneData.fogNode;
delete sceneData.fog;
}
}
updateEnvironment( scene ) {
const sceneData = this.get( scene );
const environment = scene.environment;
if ( environment ) {
if ( sceneData.environment !== environment ) {
let environmentNode = null;
if ( environment.isCubeTexture === true ) {
environmentNode = cubeTexture( environment );
} else if ( environment.isTexture === true ) {
environmentNode = texture( environment );
} else {
console.error( 'Nodes: Unsupported environment configuration.', environment );
}
sceneData.environmentNode = environmentNode;
sceneData.environment = environment;
}
} else if ( sceneData.environmentNode ) {
delete sceneData.environmentNode;
delete sceneData.environment;
}
}
getNodeFrame( renderer = this.renderer, scene = null, object = null, camera = null, material = null ) {
const nodeFrame = this.nodeFrame;
nodeFrame.renderer = renderer;
nodeFrame.scene = scene;
nodeFrame.object = object;
nodeFrame.camera = camera;
nodeFrame.material = material;
return nodeFrame;
}
getNodeFrameForRender( renderObject ) {
return this.getNodeFrame( renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material );
}
getOutputNode( outputTexture ) {
let output = texture( outputTexture, viewportTopLeft );
if ( this.isToneMappingState ) {
if ( this.renderer.toneMappingNode ) {
output = vec4( this.renderer.toneMappingNode.context( { color: output.rgb } ), output.a );
} else if ( this.renderer.toneMapping !== NoToneMapping ) {
output = output.toneMapping( this.renderer.toneMapping );
}
}
if ( this.renderer.currentColorSpace === SRGBColorSpace ) {
output = output.linearToColorSpace( this.renderer.currentColorSpace );
}
return output;
}
updateBefore( renderObject ) {
const nodeFrame = this.getNodeFrameForRender( renderObject );
const nodeBuilder = renderObject.getNodeBuilderState();
for ( const node of nodeBuilder.updateBeforeNodes ) {
nodeFrame.updateBeforeNode( node );
}
}
updateForCompute( computeNode ) {
const nodeFrame = this.getNodeFrame();
const nodeBuilder = this.getForCompute( computeNode );
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
updateForRender( renderObject ) {
const nodeFrame = this.getNodeFrameForRender( renderObject );
const nodeBuilder = renderObject.getNodeBuilderState();
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
dispose() {
super.dispose();
this.nodeFrame = new NodeFrame();
this.nodeBuilderCache = new Map();
}
}
export default Nodes;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,145 @@
class WebGLBufferRenderer {
constructor( backend ) {
this.gl = backend.gl;
this.extensions = backend.extensions;
this.info = backend.renderer.info;
this.mode = null;
this.index = 0;
this.type = null;
this.object = null;
}
render( start, count ) {
const { gl, mode, object, type, info, index } = this;
if ( index !== 0 ) {
gl.drawElements( mode, count, type, start );
} else {
gl.drawArrays( mode, start, count );
}
info.update( object, count, mode, 1 );
}
renderInstances( start, count, primcount ) {
const { gl, mode, type, index, object, info } = this;
if ( primcount === 0 ) return;
if ( index !== 0 ) {
gl.drawElementsInstanced( mode, count, type, start, primcount );
} else {
gl.drawArraysInstanced( mode, start, count, primcount );
}
info.update( object, count, mode, primcount );
}
renderMultiDraw( starts, counts, drawCount ) {
const { extensions, mode, object, info } = this;
if ( drawCount === 0 ) return;
const extension = extensions.get( 'WEBGL_multi_draw' );
if ( extension === null ) {
for ( let i = 0; i < drawCount; i ++ ) {
this.render( starts[ i ], counts[ i ] );
}
} else {
if ( this.index !== 0 ) {
extension.multiDrawElementsWEBGL( mode, counts, 0, this.type, starts, 0, drawCount );
} else {
extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount );
}
let elementCount = 0;
for ( let i = 0; i < drawCount; i ++ ) {
elementCount += counts[ i ];
}
info.update( object, elementCount, mode, 1 );
}
}
renderMultiDrawInstances( starts, counts, drawCount, primcount ) {
const { extensions, mode, object, info } = this;
if ( drawCount === 0 ) return;
const extension = extensions.get( 'WEBGL_multi_draw' );
if ( extension === null ) {
for ( let i = 0; i < drawCount; i ++ ) {
this.renderInstances( starts[ i ], counts[ i ], primcount[ i ] );
}
} else {
if ( this.index !== 0 ) {
extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, this.type, starts, 0, primcount, 0, drawCount );
} else {
extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount );
}
let elementCount = 0;
for ( let i = 0; i < drawCount; i ++ ) {
elementCount += counts[ i ];
}
for ( let i = 0; i < primcount.length; i ++ ) {
info.update( object, elementCount, mode, primcount[ i ] );
}
}
}
//
}
export { WebGLBufferRenderer };

View File

@ -0,0 +1,810 @@
import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, vectorComponents } from '../../../nodes/Nodes.js';
import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js';
import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js';
import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
import { RedFormat, RGFormat, IntType, DataTexture, RGBFormat, RGBAFormat, FloatType } from 'three';
const glslMethods = {
[ MathNode.ATAN2 ]: 'atan',
textureDimensions: 'textureSize',
equals: 'equal'
};
const precisionLib = {
low: 'lowp',
medium: 'mediump',
high: 'highp'
};
const supports = {
instance: true,
swizzleAssign: true
};
const defaultPrecisions = `
precision highp float;
precision highp int;
precision mediump sampler2DArray;
precision lowp sampler2DShadow;
`;
class GLSLNodeBuilder extends NodeBuilder {
constructor( object, renderer, scene = null ) {
super( object, renderer, new GLSLNodeParser(), scene );
this.uniformGroups = {};
this.transforms = [];
}
getMethod( method ) {
return glslMethods[ method ] || method;
}
getPropertyName( node, shaderStage ) {
if ( node.isOutputStructVar ) return '';
return super.getPropertyName( node, shaderStage );
}
buildFunctionCode( shaderNode ) {
const layout = shaderNode.layout;
const flowData = this.flowShaderNode( shaderNode );
const parameters = [];
for ( const input of layout.inputs ) {
parameters.push( this.getType( input.type ) + ' ' + input.name );
}
//
const code = `${ this.getType( layout.type ) } ${ layout.name }( ${ parameters.join( ', ' ) } ) {
${ flowData.vars }
${ flowData.code }
return ${ flowData.result };
}`;
//
return code;
}
setupPBO( storageBufferNode ) {
const attribute = storageBufferNode.value;
if ( attribute.pbo === undefined ) {
const originalArray = attribute.array;
const numElements = attribute.count * attribute.itemSize;
const { itemSize } = attribute;
let format = RedFormat;
if ( itemSize === 2 ) {
format = RGFormat;
} else if ( itemSize === 3 ) {
format = RGBFormat;
} else if ( itemSize === 4 ) {
format = RGBAFormat;
}
const width = Math.pow( 2, Math.ceil( Math.log2( Math.sqrt( numElements / itemSize ) ) ) );
let height = Math.ceil( ( numElements / itemSize ) / width );
if ( width * height * itemSize < numElements ) height ++; // Ensure enough space
const newSize = width * height * itemSize;
const newArray = new Float32Array( newSize );
newArray.set( originalArray, 0 );
attribute.array = newArray;
const pboTexture = new DataTexture( attribute.array, width, height, format, FloatType );
pboTexture.needsUpdate = true;
pboTexture.isPBOTexture = true;
const pbo = new UniformNode( pboTexture );
pbo.setPrecision( 'high' );
attribute.pboNode = pbo;
attribute.pbo = pbo.value;
this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label );
}
}
generatePBO( storageArrayElementNode ) {
const { node, indexNode } = storageArrayElementNode;
const attribute = node.value;
if ( this.renderer.backend.has( attribute ) ) {
const attributeData = this.renderer.backend.get( attribute );
attributeData.pbo = attribute.pbo;
}
const nodeUniform = this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label );
const textureName = this.getPropertyName( nodeUniform );
indexNode.increaseUsage( this ); // force cache generate to be used as index in x,y
const indexSnippet = indexNode.build( this, 'uint' );
const elementNodeData = this.getDataFromNode( storageArrayElementNode );
let propertyName = elementNodeData.propertyName;
if ( propertyName === undefined ) {
// property element
const nodeVar = this.getVarFromNode( storageArrayElementNode );
propertyName = this.getPropertyName( nodeVar );
// property size
const bufferNodeData = this.getDataFromNode( node );
let propertySizeName = bufferNodeData.propertySizeName;
if ( propertySizeName === undefined ) {
propertySizeName = propertyName + 'Size';
this.getVarFromNode( node, propertySizeName, 'uint' );
this.addLineFlowCode( `${ propertySizeName } = uint( textureSize( ${ textureName }, 0 ).x )` );
bufferNodeData.propertySizeName = propertySizeName;
}
//
const { itemSize } = attribute;
const channel = '.' + vectorComponents.join( '' ).slice( 0, itemSize );
const uvSnippet = `ivec2(${indexSnippet} % ${ propertySizeName }, ${indexSnippet} / ${ propertySizeName })`;
const snippet = this.generateTextureLoad( null, textureName, uvSnippet, null, '0' );
//
this.addLineFlowCode( `${ propertyName } = ${ snippet + channel }` );
elementNodeData.propertyName = propertyName;
}
return propertyName;
}
generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0' ) {
if ( depthSnippet ) {
return `texelFetch( ${ textureProperty }, ivec3( ${ uvIndexSnippet }, ${ depthSnippet } ), ${ levelSnippet } )`;
} else {
return `texelFetch( ${ textureProperty }, ${ uvIndexSnippet }, ${ levelSnippet } )`;
}
}
generateTexture( texture, textureProperty, uvSnippet, depthSnippet ) {
if ( texture.isDepthTexture ) {
return `texture( ${ textureProperty }, ${ uvSnippet } ).x`;
} else {
if ( depthSnippet ) uvSnippet = `vec3( ${ uvSnippet }, ${ depthSnippet } )`;
return `texture( ${ textureProperty }, ${ uvSnippet } )`;
}
}
generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet ) {
return `textureLod( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet } )`;
}
generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet ) {
return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`;
}
generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) {
if ( shaderStage === 'fragment' ) {
return `texture( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ) )`;
} else {
console.error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` );
}
}
getVars( shaderStage ) {
const snippets = [];
const vars = this.vars[ shaderStage ];
if ( vars !== undefined ) {
for ( const variable of vars ) {
if ( variable.isOutputStructVar ) continue;
snippets.push( `${ this.getVar( variable.type, variable.name ) };` );
}
}
return snippets.join( '\n\t' );
}
getUniforms( shaderStage ) {
const uniforms = this.uniforms[ shaderStage ];
const bindingSnippets = [];
const uniformGroups = {};
for ( const uniform of uniforms ) {
let snippet = null;
let group = false;
if ( uniform.type === 'texture' ) {
const texture = uniform.node.value;
if ( texture.compareFunction ) {
snippet = `sampler2DShadow ${ uniform.name };`;
} else if ( texture.isDataArrayTexture === true ) {
snippet = `sampler2DArray ${ uniform.name };`;
} else {
snippet = `sampler2D ${ uniform.name };`;
}
} else if ( uniform.type === 'cubeTexture' ) {
snippet = `samplerCube ${ uniform.name };`;
} else if ( uniform.type === 'buffer' ) {
const bufferNode = uniform.node;
const bufferType = this.getType( bufferNode.bufferType );
const bufferCount = bufferNode.bufferCount;
const bufferCountSnippet = bufferCount > 0 ? bufferCount : '';
snippet = `${bufferNode.name} {\n\t${ bufferType } ${ uniform.name }[${ bufferCountSnippet }];\n};\n`;
} else {
const vectorType = this.getVectorType( uniform.type );
snippet = `${vectorType} ${uniform.name};`;
group = true;
}
const precision = uniform.node.precision;
if ( precision !== null ) {
snippet = precisionLib[ precision ] + ' ' + snippet;
}
if ( group ) {
snippet = '\t' + snippet;
const groupName = uniform.groupNode.name;
const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] );
groupSnippets.push( snippet );
} else {
snippet = 'uniform ' + snippet;
bindingSnippets.push( snippet );
}
}
let output = '';
for ( const name in uniformGroups ) {
const groupSnippets = uniformGroups[ name ];
output += this._getGLSLUniformStruct( shaderStage + '_' + name, groupSnippets.join( '\n' ) ) + '\n';
}
output += bindingSnippets.join( '\n' );
return output;
}
getTypeFromAttribute( attribute ) {
let nodeType = super.getTypeFromAttribute( attribute );
if ( /^[iu]/.test( nodeType ) && attribute.gpuType !== IntType ) {
let dataAttribute = attribute;
if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data;
const array = dataAttribute.array;
if ( ( array instanceof Uint32Array || array instanceof Int32Array || array instanceof Uint16Array || array instanceof Int16Array ) === false ) {
nodeType = nodeType.slice( 1 );
}
}
return nodeType;
}
getAttributes( shaderStage ) {
let snippet = '';
if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
const attributes = this.getAttributesArray();
let location = 0;
for ( const attribute of attributes ) {
snippet += `layout( location = ${ location ++ } ) in ${ attribute.type } ${ attribute.name };\n`;
}
}
return snippet;
}
getStructMembers( struct ) {
const snippets = [];
const members = struct.getMemberTypes();
for ( let i = 0; i < members.length; i ++ ) {
const member = members[ i ];
snippets.push( `layout( location = ${i} ) out ${ member} m${i};` );
}
return snippets.join( '\n' );
}
getStructs( shaderStage ) {
const snippets = [];
const structs = this.structs[ shaderStage ];
if ( structs.length === 0 ) {
return 'layout( location = 0 ) out vec4 fragColor;\n';
}
for ( let index = 0, length = structs.length; index < length; index ++ ) {
const struct = structs[ index ];
let snippet = '\n';
snippet += this.getStructMembers( struct );
snippet += '\n';
snippets.push( snippet );
}
return snippets.join( '\n\n' );
}
getVaryings( shaderStage ) {
let snippet = '';
const varyings = this.varyings;
if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
for ( const varying of varyings ) {
if ( shaderStage === 'compute' ) varying.needsInterpolation = true;
const type = varying.type;
const flat = type === 'int' || type === 'uint' ? 'flat ' : '';
snippet += `${flat}${varying.needsInterpolation ? 'out' : '/*out*/'} ${type} ${varying.name};\n`;
}
} else if ( shaderStage === 'fragment' ) {
for ( const varying of varyings ) {
if ( varying.needsInterpolation ) {
const type = varying.type;
const flat = type === 'int' || type === 'uint' ? 'flat ' : '';
snippet += `${flat}in ${type} ${varying.name};\n`;
}
}
}
return snippet;
}
getVertexIndex() {
return 'uint( gl_VertexID )';
}
getInstanceIndex() {
return 'uint( gl_InstanceID )';
}
getFrontFacing() {
return 'gl_FrontFacing';
}
getFragCoord() {
return 'gl_FragCoord';
}
getFragDepth() {
return 'gl_FragDepth';
}
isAvailable( name ) {
return supports[ name ] === true;
}
isFlipY() {
return true;
}
registerTransform( varyingName, attributeNode ) {
this.transforms.push( { varyingName, attributeNode } );
}
getTransforms( /* shaderStage */ ) {
const transforms = this.transforms;
let snippet = '';
for ( let i = 0; i < transforms.length; i ++ ) {
const transform = transforms[ i ];
const attributeName = this.getPropertyName( transform.attributeNode );
snippet += `${ transform.varyingName } = ${ attributeName };\n\t`;
}
return snippet;
}
_getGLSLUniformStruct( name, vars ) {
return `
layout( std140 ) uniform ${name} {
${vars}
};`;
}
_getGLSLVertexCode( shaderData ) {
return `#version 300 es
${ this.getSignature() }
// precision
${ defaultPrecisions }
// uniforms
${shaderData.uniforms}
// varyings
${shaderData.varyings}
// attributes
${shaderData.attributes}
// codes
${shaderData.codes}
void main() {
// vars
${shaderData.vars}
// transforms
${shaderData.transforms}
// flow
${shaderData.flow}
gl_PointSize = 1.0;
}
`;
}
_getGLSLFragmentCode( shaderData ) {
return `#version 300 es
${ this.getSignature() }
// precision
${ defaultPrecisions }
// uniforms
${shaderData.uniforms}
// varyings
${shaderData.varyings}
// codes
${shaderData.codes}
${shaderData.structs}
void main() {
// vars
${shaderData.vars}
// flow
${shaderData.flow}
}
`;
}
buildCode() {
const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} };
for ( const shaderStage in shadersData ) {
let flow = '// code\n\n';
flow += this.flowCode[ shaderStage ];
const flowNodes = this.flowNodes[ shaderStage ];
const mainNode = flowNodes[ flowNodes.length - 1 ];
for ( const node of flowNodes ) {
const flowSlotData = this.getFlowData( node/*, shaderStage*/ );
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 && shaderStage !== 'compute' ) {
flow += '// result\n\t';
if ( shaderStage === 'vertex' ) {
flow += 'gl_Position = ';
flow += `${ flowSlotData.result };`;
} else if ( shaderStage === 'fragment' ) {
if ( ! node.outputNode.isOutputStructNode ) {
flow += 'fragColor = ';
flow += `${ flowSlotData.result };`;
}
}
}
}
const stageData = shadersData[ shaderStage ];
stageData.uniforms = this.getUniforms( shaderStage );
stageData.attributes = this.getAttributes( shaderStage );
stageData.varyings = this.getVaryings( shaderStage );
stageData.vars = this.getVars( shaderStage );
stageData.structs = this.getStructs( shaderStage );
stageData.codes = this.getCodes( shaderStage );
stageData.transforms = this.getTransforms( shaderStage );
stageData.flow = flow;
}
if ( this.material !== null ) {
this.vertexShader = this._getGLSLVertexCode( shadersData.vertex );
this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment );
} else {
this.computeShader = this._getGLSLVertexCode( shadersData.compute );
}
}
getUniformFromNode( node, type, shaderStage, name = null ) {
const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache );
let uniformGPU = nodeData.uniformGPU;
if ( uniformGPU === undefined ) {
if ( type === 'texture' ) {
uniformGPU = new NodeSampledTexture( uniformNode.name, uniformNode.node );
this.bindings[ shaderStage ].push( uniformGPU );
} else if ( type === 'cubeTexture' ) {
uniformGPU = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node );
this.bindings[ shaderStage ].push( uniformGPU );
} else if ( type === 'buffer' ) {
node.name = `NodeBuffer_${ node.id }`;
uniformNode.name = `buffer${ node.id }`;
const buffer = new NodeUniformBuffer( node );
buffer.name = node.name;
this.bindings[ shaderStage ].push( buffer );
uniformGPU = buffer;
} else {
const group = node.groupNode;
const groupName = group.name;
const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} );
let uniformsGroup = uniformsStage[ groupName ];
if ( uniformsGroup === undefined ) {
uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group );
//uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
uniformsStage[ groupName ] = uniformsGroup;
this.bindings[ shaderStage ].push( uniformsGroup );
}
uniformGPU = this.getNodeUniform( uniformNode, type );
uniformsGroup.addUniform( uniformGPU );
}
nodeData.uniformGPU = uniformGPU;
}
return uniformNode;
}
}
export default GLSLNodeBuilder;

View File

@ -0,0 +1,258 @@
import { IntType } from 'three';
let _id = 0;
class DualAttributeData {
constructor( attributeData, dualBuffer ) {
this.buffers = [ attributeData.bufferGPU, dualBuffer ];
this.type = attributeData.type;
this.bufferType = attributeData.bufferType;
this.pbo = attributeData.pbo;
this.byteLength = attributeData.byteLength;
this.bytesPerElement = attributeData.BYTES_PER_ELEMENT;
this.version = attributeData.version;
this.isInteger = attributeData.isInteger;
this.activeBufferIndex = 0;
this.baseId = attributeData.id;
}
get id() {
return `${ this.baseId }|${ this.activeBufferIndex }`;
}
get bufferGPU() {
return this.buffers[ this.activeBufferIndex ];
}
get transformBuffer() {
return this.buffers[ this.activeBufferIndex ^ 1 ];
}
switchBuffers() {
this.activeBufferIndex ^= 1;
}
}
class WebGLAttributeUtils {
constructor( backend ) {
this.backend = backend;
}
createAttribute( attribute, bufferType ) {
const backend = this.backend;
const { gl } = backend;
const array = attribute.array;
const usage = attribute.usage || gl.STATIC_DRAW;
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
const bufferData = backend.get( bufferAttribute );
let bufferGPU = bufferData.bufferGPU;
if ( bufferGPU === undefined ) {
bufferGPU = this._createBuffer( gl, bufferType, array, usage );
bufferData.bufferGPU = bufferGPU;
bufferData.bufferType = bufferType;
bufferData.version = bufferAttribute.version;
}
//attribute.onUploadCallback();
let type;
if ( array instanceof Float32Array ) {
type = gl.FLOAT;
} else if ( array instanceof Uint16Array ) {
if ( attribute.isFloat16BufferAttribute ) {
type = gl.HALF_FLOAT;
} else {
type = gl.UNSIGNED_SHORT;
}
} else if ( array instanceof Int16Array ) {
type = gl.SHORT;
} else if ( array instanceof Uint32Array ) {
type = gl.UNSIGNED_INT;
} else if ( array instanceof Int32Array ) {
type = gl.INT;
} else if ( array instanceof Int8Array ) {
type = gl.BYTE;
} else if ( array instanceof Uint8Array ) {
type = gl.UNSIGNED_BYTE;
} else if ( array instanceof Uint8ClampedArray ) {
type = gl.UNSIGNED_BYTE;
} else {
throw new Error( 'THREE.WebGLBackend: Unsupported buffer data format: ' + array );
}
let attributeData = {
bufferGPU,
bufferType,
type,
byteLength: array.byteLength,
bytesPerElement: array.BYTES_PER_ELEMENT,
version: attribute.version,
pbo: attribute.pbo,
isInteger: type === gl.INT || type === gl.UNSIGNED_INT || type === gl.UNSIGNED_SHORT || attribute.gpuType === IntType,
id: _id ++
};
if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
// create buffer for tranform feedback use
const bufferGPUDual = this._createBuffer( gl, bufferType, array, usage );
attributeData = new DualAttributeData( attributeData, bufferGPUDual );
}
backend.set( attribute, attributeData );
}
updateAttribute( attribute ) {
const backend = this.backend;
const { gl } = backend;
const array = attribute.array;
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
const bufferData = backend.get( bufferAttribute );
const bufferType = bufferData.bufferType;
const updateRanges = attribute.isInterleavedBufferAttribute ? attribute.data.updateRanges : attribute.updateRanges;
gl.bindBuffer( bufferType, bufferData.bufferGPU );
if ( updateRanges.length === 0 ) {
// Not using update ranges
gl.bufferSubData( bufferType, 0, array );
} else {
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
const range = updateRanges[ i ];
gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT,
array, range.start, range.count );
}
bufferAttribute.clearUpdateRanges();
}
gl.bindBuffer( bufferType, null );
bufferData.version = bufferAttribute.version;
}
destroyAttribute( attribute ) {
const backend = this.backend;
const { gl } = backend;
if ( attribute.isInterleavedBufferAttribute ) {
backend.delete( attribute.data );
}
const attributeData = backend.get( attribute );
gl.deleteBuffer( attributeData.bufferGPU );
backend.delete( attribute );
}
async getArrayBufferAsync( attribute ) {
const backend = this.backend;
const { gl } = backend;
const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute;
const { bufferGPU } = backend.get( bufferAttribute );
const array = attribute.array;
const byteLength = array.byteLength;
gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU );
const writeBuffer = gl.createBuffer();
gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer );
gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ );
gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength );
await backend.utils._clientWaitAsync();
const dstBuffer = new attribute.array.constructor( array.length );
gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer );
gl.deleteBuffer( writeBuffer );
return dstBuffer.buffer;
}
_createBuffer( gl, bufferType, array, usage ) {
const bufferGPU = gl.createBuffer();
gl.bindBuffer( bufferType, bufferGPU );
gl.bufferData( bufferType, array, usage );
gl.bindBuffer( bufferType, null );
return bufferGPU;
}
}
export default WebGLAttributeUtils;

View File

@ -0,0 +1,36 @@
class WebGLCapabilities {
constructor( backend ) {
this.backend = backend;
this.maxAnisotropy = null;
}
getMaxAnisotropy() {
if ( this.maxAnisotropy !== null ) return this.maxAnisotropy;
const gl = this.backend.gl;
const extensions = this.backend.extensions;
if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
this.maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
} else {
this.maxAnisotropy = 0;
}
return this.maxAnisotropy;
}
}
export default WebGLCapabilities;

View File

@ -0,0 +1,12 @@
export const GLFeatureName = {
'WEBGL_compressed_texture_astc': 'texture-compression-astc',
'WEBGL_compressed_texture_etc': 'texture-compression-etc2',
'WEBGL_compressed_texture_etc1': 'texture-compression-etc1',
'WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc',
'WEBKIT_WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc',
'WEBGL_compressed_texture_s3tc': 'texture-compression-bc',
'EXT_texture_compression_bptc': 'texture-compression-bptc',
'EXT_disjoint_timer_query_webgl2': 'timestamp-query',
};

View File

@ -0,0 +1,36 @@
class WebGLExtensions {
constructor( backend ) {
this.backend = backend;
this.gl = this.backend.gl;
this.availableExtensions = this.gl.getSupportedExtensions();
this.extensions = {};
}
get( name ) {
let extension = this.extensions[ name ];
if ( extension === undefined ) {
extension = this.gl.getExtension( name );
}
return extension;
}
has( name ) {
return this.availableExtensions.includes( name );
}
}
export default WebGLExtensions;

View File

@ -0,0 +1,738 @@
import {
CullFaceNone, CullFaceBack, CullFaceFront, DoubleSide, BackSide,
NormalBlending, NoBlending, CustomBlending, AddEquation,
AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation,
ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor,
OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor,
NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth
} from 'three';
let initialized = false, equationToGL, factorToGL;
class WebGLState {
constructor( backend ) {
this.backend = backend;
this.gl = this.backend.gl;
this.enabled = {};
this.currentFlipSided = null;
this.currentCullFace = null;
this.currentProgram = null;
this.currentBlendingEnabled = false;
this.currentBlending = null;
this.currentBlendSrc = null;
this.currentBlendDst = null;
this.currentBlendSrcAlpha = null;
this.currentBlendDstAlpha = null;
this.currentPremultipledAlpha = null;
this.currentPolygonOffsetFactor = null;
this.currentPolygonOffsetUnits = null;
this.currentColorMask = null;
this.currentDepthFunc = null;
this.currentDepthMask = null;
this.currentStencilFunc = null;
this.currentStencilRef = null;
this.currentStencilFuncMask = null;
this.currentStencilFail = null;
this.currentStencilZFail = null;
this.currentStencilZPass = null;
this.currentStencilMask = null;
this.currentLineWidth = null;
this.currentBoundFramebuffers = {};
this.currentDrawbuffers = new WeakMap();
this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS );
this.currentTextureSlot = null;
this.currentBoundTextures = {};
if ( initialized === false ) {
this._init( this.gl );
initialized = true;
}
}
_init( gl ) {
// Store only WebGL constants here.
equationToGL = {
[ AddEquation ]: gl.FUNC_ADD,
[ SubtractEquation ]: gl.FUNC_SUBTRACT,
[ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT
};
factorToGL = {
[ ZeroFactor ]: gl.ZERO,
[ OneFactor ]: gl.ONE,
[ SrcColorFactor ]: gl.SRC_COLOR,
[ SrcAlphaFactor ]: gl.SRC_ALPHA,
[ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE,
[ DstColorFactor ]: gl.DST_COLOR,
[ DstAlphaFactor ]: gl.DST_ALPHA,
[ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR,
[ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA,
[ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR,
[ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA
};
}
enable( id ) {
const { enabled } = this;
if ( enabled[ id ] !== true ) {
this.gl.enable( id );
enabled[ id ] = true;
}
}
disable( id ) {
const { enabled } = this;
if ( enabled[ id ] !== false ) {
this.gl.disable( id );
enabled[ id ] = false;
}
}
setFlipSided( flipSided ) {
if ( this.currentFlipSided !== flipSided ) {
const { gl } = this;
if ( flipSided ) {
gl.frontFace( gl.CW );
} else {
gl.frontFace( gl.CCW );
}
this.currentFlipSided = flipSided;
}
}
setCullFace( cullFace ) {
const { gl } = this;
if ( cullFace !== CullFaceNone ) {
this.enable( gl.CULL_FACE );
if ( cullFace !== this.currentCullFace ) {
if ( cullFace === CullFaceBack ) {
gl.cullFace( gl.BACK );
} else if ( cullFace === CullFaceFront ) {
gl.cullFace( gl.FRONT );
} else {
gl.cullFace( gl.FRONT_AND_BACK );
}
}
} else {
this.disable( gl.CULL_FACE );
}
this.currentCullFace = cullFace;
}
setLineWidth( width ) {
const { currentLineWidth, gl } = this;
if ( width !== currentLineWidth ) {
gl.lineWidth( width );
this.currentLineWidth = width;
}
}
setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
const { gl } = this;
if ( blending === NoBlending ) {
if ( this.currentBlendingEnabled === true ) {
this.disable( gl.BLEND );
this.currentBlendingEnabled = false;
}
return;
}
if ( this.currentBlendingEnabled === false ) {
this.enable( gl.BLEND );
this.currentBlendingEnabled = true;
}
if ( blending !== CustomBlending ) {
if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) {
if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) {
gl.blendEquation( gl.FUNC_ADD );
this.currentBlendEquation = AddEquation;
this.currentBlendEquationAlpha = AddEquation;
}
if ( premultipliedAlpha ) {
switch ( blending ) {
case NormalBlending:
gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
break;
case AdditiveBlending:
gl.blendFunc( gl.ONE, gl.ONE );
break;
case SubtractiveBlending:
gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
break;
case MultiplyBlending:
gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );
break;
default:
console.error( 'THREE.WebGLState: Invalid blending: ', blending );
break;
}
} else {
switch ( blending ) {
case NormalBlending:
gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
break;
case AdditiveBlending:
gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
break;
case SubtractiveBlending:
gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
break;
case MultiplyBlending:
gl.blendFunc( gl.ZERO, gl.SRC_COLOR );
break;
default:
console.error( 'THREE.WebGLState: Invalid blending: ', blending );
break;
}
}
this.currentBlendSrc = null;
this.currentBlendDst = null;
this.currentBlendSrcAlpha = null;
this.currentBlendDstAlpha = null;
this.currentBlending = blending;
this.currentPremultipledAlpha = premultipliedAlpha;
}
return;
}
// custom blending
blendEquationAlpha = blendEquationAlpha || blendEquation;
blendSrcAlpha = blendSrcAlpha || blendSrc;
blendDstAlpha = blendDstAlpha || blendDst;
if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) {
gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
this.currentBlendEquation = blendEquation;
this.currentBlendEquationAlpha = blendEquationAlpha;
}
if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) {
gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
this.currentBlendSrc = blendSrc;
this.currentBlendDst = blendDst;
this.currentBlendSrcAlpha = blendSrcAlpha;
this.currentBlendDstAlpha = blendDstAlpha;
}
this.currentBlending = blending;
this.currentPremultipledAlpha = false;
}
setColorMask( colorMask ) {
if ( this.currentColorMask !== colorMask ) {
this.gl.colorMask( colorMask, colorMask, colorMask, colorMask );
this.currentColorMask = colorMask;
}
}
setDepthTest( depthTest ) {
const { gl } = this;
if ( depthTest ) {
this.enable( gl.DEPTH_TEST );
} else {
this.disable( gl.DEPTH_TEST );
}
}
setDepthMask( depthMask ) {
if ( this.currentDepthMask !== depthMask ) {
this.gl.depthMask( depthMask );
this.currentDepthMask = depthMask;
}
}
setDepthFunc( depthFunc ) {
if ( this.currentDepthFunc !== depthFunc ) {
const { gl } = this;
switch ( depthFunc ) {
case NeverDepth:
gl.depthFunc( gl.NEVER );
break;
case AlwaysDepth:
gl.depthFunc( gl.ALWAYS );
break;
case LessDepth:
gl.depthFunc( gl.LESS );
break;
case LessEqualDepth:
gl.depthFunc( gl.LEQUAL );
break;
case EqualDepth:
gl.depthFunc( gl.EQUAL );
break;
case GreaterEqualDepth:
gl.depthFunc( gl.GEQUAL );
break;
case GreaterDepth:
gl.depthFunc( gl.GREATER );
break;
case NotEqualDepth:
gl.depthFunc( gl.NOTEQUAL );
break;
default:
gl.depthFunc( gl.LEQUAL );
}
this.currentDepthFunc = depthFunc;
}
}
setStencilTest( stencilTest ) {
const { gl } = this;
if ( stencilTest ) {
this.enable( gl.STENCIL_TEST );
} else {
this.disable( gl.STENCIL_TEST );
}
}
setStencilMask( stencilMask ) {
if ( this.currentStencilMask !== stencilMask ) {
this.gl.stencilMask( stencilMask );
this.currentStencilMask = stencilMask;
}
}
setStencilFunc( stencilFunc, stencilRef, stencilMask ) {
if ( this.currentStencilFunc !== stencilFunc ||
this.currentStencilRef !== stencilRef ||
this.currentStencilFuncMask !== stencilMask ) {
this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
this.currentStencilFunc = stencilFunc;
this.currentStencilRef = stencilRef;
this.currentStencilFuncMask = stencilMask;
}
}
setStencilOp( stencilFail, stencilZFail, stencilZPass ) {
if ( this.currentStencilFail !== stencilFail ||
this.currentStencilZFail !== stencilZFail ||
this.currentStencilZPass !== stencilZPass ) {
this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
this.currentStencilFail = stencilFail;
this.currentStencilZFail = stencilZFail;
this.currentStencilZPass = stencilZPass;
}
}
setMaterial( material, frontFaceCW ) {
const { gl } = this;
material.side === DoubleSide
? this.disable( gl.CULL_FACE )
: this.enable( gl.CULL_FACE );
let flipSided = ( material.side === BackSide );
if ( frontFaceCW ) flipSided = ! flipSided;
this.setFlipSided( flipSided );
( material.blending === NormalBlending && material.transparent === false )
? this.setBlending( NoBlending )
: this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
this.setDepthFunc( material.depthFunc );
this.setDepthTest( material.depthTest );
this.setDepthMask( material.depthWrite );
this.setColorMask( material.colorWrite );
const stencilWrite = material.stencilWrite;
this.setStencilTest( stencilWrite );
if ( stencilWrite ) {
this.setStencilMask( material.stencilWriteMask );
this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
}
this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
material.alphaToCoverage === true
? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
: this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE );
}
setPolygonOffset( polygonOffset, factor, units ) {
const { gl } = this;
if ( polygonOffset ) {
this.enable( gl.POLYGON_OFFSET_FILL );
if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) {
gl.polygonOffset( factor, units );
this.currentPolygonOffsetFactor = factor;
this.currentPolygonOffsetUnits = units;
}
} else {
this.disable( gl.POLYGON_OFFSET_FILL );
}
}
useProgram( program ) {
if ( this.currentProgram !== program ) {
this.gl.useProgram( program );
this.currentProgram = program;
return true;
}
return false;
}
// framebuffer
bindFramebuffer( target, framebuffer ) {
const { gl, currentBoundFramebuffers } = this;
if ( currentBoundFramebuffers[ target ] !== framebuffer ) {
gl.bindFramebuffer( target, framebuffer );
currentBoundFramebuffers[ target ] = framebuffer;
// gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER
if ( target === gl.DRAW_FRAMEBUFFER ) {
currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer;
}
if ( target === gl.FRAMEBUFFER ) {
currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer;
}
return true;
}
return false;
}
drawBuffers( renderContext, framebuffer ) {
const { gl } = this;
let drawBuffers = [];
let needsUpdate = false;
if ( renderContext.textures !== null ) {
drawBuffers = this.currentDrawbuffers.get( framebuffer );
if ( drawBuffers === undefined ) {
drawBuffers = [];
this.currentDrawbuffers.set( framebuffer, drawBuffers );
}
const textures = renderContext.textures;
if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) {
for ( let i = 0, il = textures.length; i < il; i ++ ) {
drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i;
}
drawBuffers.length = textures.length;
needsUpdate = true;
}
} else {
if ( drawBuffers[ 0 ] !== gl.BACK ) {
drawBuffers[ 0 ] = gl.BACK;
needsUpdate = true;
}
}
if ( needsUpdate ) {
gl.drawBuffers( drawBuffers );
}
}
// texture
activeTexture( webglSlot ) {
const { gl, currentTextureSlot, maxTextures } = this;
if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1;
if ( currentTextureSlot !== webglSlot ) {
gl.activeTexture( webglSlot );
this.currentTextureSlot = webglSlot;
}
}
bindTexture( webglType, webglTexture, webglSlot ) {
const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this;
if ( webglSlot === undefined ) {
if ( currentTextureSlot === null ) {
webglSlot = gl.TEXTURE0 + maxTextures - 1;
} else {
webglSlot = currentTextureSlot;
}
}
let boundTexture = currentBoundTextures[ webglSlot ];
if ( boundTexture === undefined ) {
boundTexture = { type: undefined, texture: undefined };
currentBoundTextures[ webglSlot ] = boundTexture;
}
if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
if ( currentTextureSlot !== webglSlot ) {
gl.activeTexture( webglSlot );
this.currentTextureSlot = webglSlot;
}
gl.bindTexture( webglType, webglTexture );
boundTexture.type = webglType;
boundTexture.texture = webglTexture;
}
}
unbindTexture() {
const { gl, currentTextureSlot, currentBoundTextures } = this;
const boundTexture = currentBoundTextures[ currentTextureSlot ];
if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
gl.bindTexture( boundTexture.type, null );
boundTexture.type = undefined;
boundTexture.texture = undefined;
}
}
}
export default WebGLState;

View File

@ -0,0 +1,751 @@
import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, FloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, SRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare } from 'three';
let initialized = false, wrappingToGL, filterToGL, compareToGL;
class WebGLTextureUtils {
constructor( backend ) {
this.backend = backend;
this.gl = backend.gl;
this.extensions = backend.extensions;
this.defaultTextures = {};
if ( initialized === false ) {
this._init( this.gl );
initialized = true;
}
}
_init( gl ) {
// Store only WebGL constants here.
wrappingToGL = {
[ RepeatWrapping ]: gl.REPEAT,
[ ClampToEdgeWrapping ]: gl.CLAMP_TO_EDGE,
[ MirroredRepeatWrapping ]: gl.MIRRORED_REPEAT
};
filterToGL = {
[ NearestFilter ]: gl.NEAREST,
[ NearestMipmapNearestFilter ]: gl.NEAREST_MIPMAP_NEAREST,
[ NearestMipmapLinearFilter ]: gl.NEAREST_MIPMAP_LINEAR,
[ LinearFilter ]: gl.LINEAR,
[ LinearMipmapNearestFilter ]: gl.LINEAR_MIPMAP_NEAREST,
[ LinearMipmapLinearFilter ]: gl.LINEAR_MIPMAP_LINEAR
};
compareToGL = {
[ NeverCompare ]: gl.NEVER,
[ AlwaysCompare ]: gl.ALWAYS,
[ LessCompare ]: gl.LESS,
[ LessEqualCompare ]: gl.LEQUAL,
[ EqualCompare ]: gl.EQUAL,
[ GreaterEqualCompare ]: gl.GEQUAL,
[ GreaterCompare ]: gl.GREATER,
[ NotEqualCompare ]: gl.NOTEQUAL
};
}
filterFallback( f ) {
const { gl } = this;
if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
return gl.NEAREST;
}
return gl.LINEAR;
}
getGLTextureType( texture ) {
const { gl } = this;
let glTextureType;
if ( texture.isCubeTexture === true ) {
glTextureType = gl.TEXTURE_CUBE_MAP;
} else if ( texture.isDataArrayTexture === true ) {
glTextureType = gl.TEXTURE_2D_ARRAY;
} else {
glTextureType = gl.TEXTURE_2D;
}
return glTextureType;
}
getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) {
const { gl, extensions } = this;
if ( internalFormatName !== null ) {
if ( gl[ internalFormatName ] !== undefined ) return gl[ internalFormatName ];
console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
}
let internalFormat = glFormat;
if ( glFormat === gl.RED ) {
if ( glType === gl.FLOAT ) internalFormat = gl.R32F;
if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F;
if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8;
}
if ( glFormat === gl.RED_INTEGER ) {
if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8UI;
if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16UI;
if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI;
if ( glType === gl.BYTE ) internalFormat = gl.R8I;
if ( glType === gl.SHORT ) internalFormat = gl.R16I;
if ( glType === gl.INT ) internalFormat = gl.R32I;
}
if ( glFormat === gl.RG ) {
if ( glType === gl.FLOAT ) internalFormat = gl.RG32F;
if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F;
if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8;
}
if ( glFormat === gl.RGB ) {
if ( glType === gl.FLOAT ) internalFormat = gl.RGB32F;
if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F;
if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8;
if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565;
if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1;
if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4;
if ( glType === gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = gl.RGB9_E5;
}
if ( glFormat === gl.RGBA ) {
if ( glType === gl.FLOAT ) internalFormat = gl.RGBA32F;
if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F;
if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? gl.SRGB8_ALPHA8 : gl.RGBA8;
if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4;
if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1;
}
if ( glFormat === gl.DEPTH_COMPONENT ) {
if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.DEPTH24_STENCIL8;
if ( glType === gl.FLOAT ) internalFormat = gl.DEPTH_COMPONENT32F;
}
if ( glFormat === gl.DEPTH_STENCIL ) {
if ( glType === gl.UNSIGNED_INT_24_8 ) internalFormat = gl.DEPTH24_STENCIL8;
}
if ( internalFormat === gl.R16F || internalFormat === gl.R32F ||
internalFormat === gl.RG16F || internalFormat === gl.RG32F ||
internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F ) {
extensions.get( 'EXT_color_buffer_float' );
}
return internalFormat;
}
setTextureParameters( textureType, texture ) {
const { gl, extensions, backend } = this;
const { currentAnisotropy } = backend.get( texture );
gl.texParameteri( textureType, gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] );
gl.texParameteri( textureType, gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] );
if ( textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY ) {
gl.texParameteri( textureType, gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] );
}
gl.texParameteri( textureType, gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] );
// follow WebGPU backend mapping for texture filtering
const minFilter = ! texture.isVideoTexture && texture.minFilter === LinearFilter ? LinearMipmapLinearFilter : texture.minFilter;
gl.texParameteri( textureType, gl.TEXTURE_MIN_FILTER, filterToGL[ minFilter ] );
if ( texture.compareFunction ) {
gl.texParameteri( textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE );
gl.texParameteri( textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] );
}
if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
if ( texture.magFilter === NearestFilter ) return;
if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return;
if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2
if ( texture.anisotropy > 1 || currentAnisotropy !== texture.anisotropy ) {
const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, backend.getMaxAnisotropy() ) );
backend.get( texture ).currentAnisotropy = texture.anisotropy;
}
}
}
createDefaultTexture( texture ) {
const { gl, backend, defaultTextures } = this;
const glTextureType = this.getGLTextureType( texture );
let textureGPU = defaultTextures[ glTextureType ];
if ( textureGPU === undefined ) {
textureGPU = gl.createTexture();
backend.state.bindTexture( glTextureType, textureGPU );
gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
// gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
defaultTextures[ glTextureType ] = textureGPU;
}
backend.set( texture, {
textureGPU,
glTextureType,
isDefault: true
} );
}
createTexture( texture, options ) {
const { gl, backend } = this;
const { levels, width, height, depth } = options;
const glFormat = backend.utils.convert( texture.format, texture.colorSpace );
const glType = backend.utils.convert( texture.type );
const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture );
const textureGPU = gl.createTexture();
const glTextureType = this.getGLTextureType( texture );
backend.state.bindTexture( glTextureType, textureGPU );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
gl.pixelStorei( gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE );
this.setTextureParameters( glTextureType, texture );
if ( texture.isDataArrayTexture ) {
gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );
} else if ( ! texture.isVideoTexture ) {
gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
}
backend.set( texture, {
textureGPU,
glTextureType,
glFormat,
glType,
glInternalFormat
} );
}
copyBufferToTexture( buffer, texture ) {
const { gl, backend } = this;
const { textureGPU, glTextureType, glFormat, glType } = backend.get( texture );
const { width, height } = texture.source.data;
gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, buffer );
backend.state.bindTexture( glTextureType, textureGPU );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false );
gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, 0 );
gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, null );
backend.state.unbindTexture();
// debug
// const framebuffer = gl.createFramebuffer();
// gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer );
// gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 );
// const readout = new Float32Array( width * height * 4 );
// const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT );
// const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE );
// gl.readPixels( 0, 0, width, height, altFormat, altType, readout );
// gl.bindFramebuffer( gl.FRAMEBUFFER, null );
// console.log( readout );
}
updateTexture( texture, options ) {
const { gl } = this;
const { width, height } = options;
const { textureGPU, glTextureType, glFormat, glType, glInternalFormat } = this.backend.get( texture );
if ( texture.isRenderTargetTexture || ( textureGPU === undefined /* unsupported texture format */ ) )
return;
const getImage = ( source ) => {
if ( source.isDataTexture ) {
return source.image.data;
} else if ( source instanceof ImageBitmap || source instanceof OffscreenCanvas || source instanceof HTMLImageElement || source instanceof HTMLCanvasElement ) {
return source;
}
return source.data;
};
this.backend.state.bindTexture( glTextureType, textureGPU );
if ( texture.isCompressedTexture ) {
const mipmaps = texture.mipmaps;
for ( let i = 0; i < mipmaps.length; i ++ ) {
const mipmap = mipmaps[ i ];
if ( texture.isCompressedArrayTexture ) {
const image = options.image;
if ( texture.format !== gl.RGBA ) {
if ( glFormat !== null ) {
gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 );
} else {
console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
}
} else {
gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data );
}
} else {
if ( glFormat !== null ) {
gl.compressedTexSubImage2D( gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data );
} else {
console.warn( 'Unsupported compressed texture format' );
}
}
}
} else if ( texture.isCubeTexture ) {
const images = options.images;
for ( let i = 0; i < 6; i ++ ) {
const image = getImage( images[ i ] );
gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image );
}
} else if ( texture.isDataArrayTexture ) {
const image = options.image;
gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );
} else if ( texture.isVideoTexture ) {
texture.update();
gl.texImage2D( glTextureType, 0, glInternalFormat, glFormat, glType, options.image );
} else {
const image = getImage( options.image );
gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, image );
}
}
generateMipmaps( texture ) {
const { gl, backend } = this;
const { textureGPU, glTextureType } = backend.get( texture );
backend.state.bindTexture( glTextureType, textureGPU );
gl.generateMipmap( glTextureType );
}
deallocateRenderBuffers( renderTarget ) {
const { gl, backend } = this;
// remove framebuffer reference
if ( renderTarget ) {
const renderContextData = backend.get( renderTarget );
renderContextData.renderBufferStorageSetup = undefined;
if ( renderContextData.framebuffer ) {
gl.deleteFramebuffer( renderContextData.framebuffer );
renderContextData.framebuffer = undefined;
}
if ( renderContextData.depthRenderbuffer ) {
gl.deleteRenderbuffer( renderContextData.depthRenderbuffer );
renderContextData.depthRenderbuffer = undefined;
}
if ( renderContextData.stencilRenderbuffer ) {
gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer );
renderContextData.stencilRenderbuffer = undefined;
}
if ( renderContextData.msaaFrameBuffer ) {
gl.deleteFramebuffer( renderContextData.msaaFrameBuffer );
renderContextData.msaaFrameBuffer = undefined;
}
if ( renderContextData.msaaRenderbuffers ) {
for ( let i = 0; i < renderContextData.msaaRenderbuffers.length; i ++ ) {
gl.deleteRenderbuffer( renderContextData.msaaRenderbuffers[ i ] );
}
renderContextData.msaaRenderbuffers = undefined;
}
}
}
destroyTexture( texture ) {
const { gl, backend } = this;
const { textureGPU, renderTarget } = backend.get( texture );
this.deallocateRenderBuffers( renderTarget );
gl.deleteTexture( textureGPU );
backend.delete( texture );
}
copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
const { gl, backend } = this;
const { state } = this.backend;
const width = srcTexture.image.width;
const height = srcTexture.image.height;
const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture );
state.bindTexture( glTextureType, dstTextureGPU );
// As another texture upload may have changed pixelStorei
// parameters, make sure they are correct for the dstTexture
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY );
gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha );
gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment );
if ( srcTexture.isDataTexture ) {
gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
} else {
if ( srcTexture.isCompressedTexture ) {
gl.compressedTexSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
} else {
gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image );
}
}
// Generate mipmaps only when copying level 0
if ( level === 0 && dstTexture.generateMipmaps ) gl.generateMipmap( gl.TEXTURE_2D );
state.unbindTexture();
}
copyFramebufferToTexture( texture, renderContext ) {
const { gl } = this;
const { state } = this.backend;
const { textureGPU } = this.backend.get( texture );
const width = texture.image.width;
const height = texture.image.height;
const requireDrawFrameBuffer = texture.isDepthTexture === true || ( renderContext.renderTarget && renderContext.renderTarget.samples > 0 );
if ( requireDrawFrameBuffer ) {
let mask;
let attachment;
if ( texture.isDepthTexture === true ) {
mask = gl.DEPTH_BUFFER_BIT;
attachment = gl.DEPTH_ATTACHMENT;
if ( renderContext.stencil ) {
mask |= gl.STENCIL_BUFFER_BIT;
}
} else {
mask = gl.COLOR_BUFFER_BIT;
attachment = gl.COLOR_ATTACHMENT0;
}
const fb = gl.createFramebuffer();
state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureGPU, 0 );
gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST );
gl.deleteFramebuffer( fb );
} else {
state.bindTexture( gl.TEXTURE_2D, textureGPU );
gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height );
state.unbindTexture();
}
if ( texture.generateMipmaps ) this.generateMipmaps( texture );
this.backend._setFramebuffer( renderContext );
}
// Setup storage for internal depth/stencil buffers and bind to correct framebuffer
setupRenderBufferStorage( renderbuffer, renderContext ) {
const { gl } = this;
const renderTarget = renderContext.renderTarget;
const { samples, depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget;
gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
if ( depthBuffer && ! stencilBuffer ) {
let glInternalFormat = gl.DEPTH_COMPONENT24;
if ( samples > 0 ) {
if ( depthTexture && depthTexture.isDepthTexture ) {
if ( depthTexture.type === gl.FLOAT ) {
glInternalFormat = gl.DEPTH_COMPONENT32F;
}
}
gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height );
} else {
gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height );
}
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
} else if ( depthBuffer && stencilBuffer ) {
if ( samples > 0 ) {
gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height );
} else {
gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height );
}
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
}
}
async copyTextureToBuffer( texture, x, y, width, height ) {
const { backend, gl } = this;
const { textureGPU, glFormat, glType } = this.backend.get( texture );
const fb = gl.createFramebuffer();
gl.bindFramebuffer( gl.READ_FRAMEBUFFER, fb );
gl.framebufferTexture2D( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureGPU, 0 );
const typedArrayType = this._getTypedArrayType( glType );
const bytesPerTexel = this._getBytesPerTexel( glFormat );
const elementCount = width * height;
const byteLength = elementCount * bytesPerTexel;
const buffer = gl.createBuffer();
gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer );
gl.bufferData( gl.PIXEL_PACK_BUFFER, byteLength, gl.STREAM_READ );
gl.readPixels( x, y, width, height, glFormat, glType, 0 );
gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null );
await backend.utils._clientWaitAsync();
const dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT );
gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer );
gl.getBufferSubData( gl.PIXEL_PACK_BUFFER, 0, dstBuffer );
gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null );
gl.deleteFramebuffer( fb );
return dstBuffer;
}
_getTypedArrayType( glType ) {
const { gl } = this;
if ( glType === gl.UNSIGNED_BYTE ) return Uint8Array;
if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) return Uint16Array;
if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) return Uint16Array;
if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) return Uint16Array;
if ( glType === gl.UNSIGNED_SHORT ) return Uint16Array;
if ( glType === gl.UNSIGNED_INT ) return Uint32Array;
if ( glType === gl.FLOAT ) return Float32Array;
throw new Error( `Unsupported WebGL type: ${glType}` );
}
_getBytesPerTexel( glFormat ) {
const { gl } = this;
if ( glFormat === gl.RGBA ) return 4;
if ( glFormat === gl.RGB ) return 3;
if ( glFormat === gl.ALPHA ) return 1;
}
}
export default WebGLTextureUtils;

View File

@ -0,0 +1,268 @@
import { RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT5_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT1_Format, RGB_S3TC_DXT1_Format, DepthFormat, DepthStencilFormat, LuminanceAlphaFormat, LuminanceFormat, RedFormat, RGBFormat, RGBAFormat, AlphaFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBAIntegerFormat, HalfFloatType, FloatType, UnsignedIntType, IntType, UnsignedShortType, ShortType, ByteType, UnsignedInt248Type, UnsignedInt5999Type, UnsignedShort5551Type, UnsignedShort4444Type, UnsignedByteType, RGBA_BPTC_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, SRGBColorSpace, NoColorSpace } from 'three';
class WebGLUtils {
constructor( backend ) {
this.backend = backend;
this.gl = this.backend.gl;
this.extensions = backend.extensions;
}
convert( p, colorSpace = NoColorSpace ) {
const { gl, extensions } = this;
let extension;
if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE;
if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4;
if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1;
if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV;
if ( p === ByteType ) return gl.BYTE;
if ( p === ShortType ) return gl.SHORT;
if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT;
if ( p === IntType ) return gl.INT;
if ( p === UnsignedIntType ) return gl.UNSIGNED_INT;
if ( p === FloatType ) return gl.FLOAT;
if ( p === HalfFloatType ) {
return gl.HALF_FLOAT;
}
if ( p === AlphaFormat ) return gl.ALPHA;
if ( p === RGBFormat ) return gl.RGB;
if ( p === RGBAFormat ) return gl.RGBA;
if ( p === LuminanceFormat ) return gl.LUMINANCE;
if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA;
if ( p === DepthFormat ) return gl.DEPTH_COMPONENT;
if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL;
// WebGL2 formats.
if ( p === RedFormat ) return gl.RED;
if ( p === RedIntegerFormat ) return gl.RED_INTEGER;
if ( p === RGFormat ) return gl.RG;
if ( p === RGIntegerFormat ) return gl.RG_INTEGER;
if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER;
// S3TC
if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
if ( colorSpace === SRGBColorSpace ) {
extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' );
if ( extension !== null ) {
if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT;
if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
} else {
return null;
}
} else {
extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
if ( extension !== null ) {
if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
} else {
return null;
}
}
}
// PVRTC
if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
if ( extension !== null ) {
if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
} else {
return null;
}
}
// ETC
if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
extension = extensions.get( 'WEBGL_compressed_texture_etc' );
if ( extension !== null ) {
if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2;
if ( p === RGBA_ETC2_EAC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC;
} else {
return null;
}
}
// ASTC
if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) {
extension = extensions.get( 'WEBGL_compressed_texture_astc' );
if ( extension !== null ) {
if ( p === RGBA_ASTC_4x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR;
if ( p === RGBA_ASTC_5x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR;
if ( p === RGBA_ASTC_5x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR;
if ( p === RGBA_ASTC_6x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR;
if ( p === RGBA_ASTC_6x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR;
if ( p === RGBA_ASTC_8x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR;
if ( p === RGBA_ASTC_8x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR;
if ( p === RGBA_ASTC_8x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR;
if ( p === RGBA_ASTC_10x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR;
if ( p === RGBA_ASTC_10x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR;
if ( p === RGBA_ASTC_10x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR;
if ( p === RGBA_ASTC_10x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR;
if ( p === RGBA_ASTC_12x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR;
if ( p === RGBA_ASTC_12x12_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR;
} else {
return null;
}
}
// BPTC
if ( p === RGBA_BPTC_Format ) {
extension = extensions.get( 'EXT_texture_compression_bptc' );
if ( extension !== null ) {
if ( p === RGBA_BPTC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT;
} else {
return null;
}
}
// RGTC
if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) {
extension = extensions.get( 'EXT_texture_compression_rgtc' );
if ( extension !== null ) {
if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT;
if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT;
if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT;
if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT;
} else {
return null;
}
}
//
if ( p === UnsignedInt248Type ) {
return gl.UNSIGNED_INT_24_8;
}
// if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats)
return ( gl[ p ] !== undefined ) ? gl[ p ] : null;
}
_clientWaitAsync() {
const { gl } = this;
const sync = gl.fenceSync( gl.SYNC_GPU_COMMANDS_COMPLETE, 0 );
gl.flush();
return new Promise( ( resolve, reject ) => {
function test() {
const res = gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 );
if ( res === gl.WAIT_FAILED ) {
gl.deleteSync( sync );
reject();
return;
}
if ( res === gl.TIMEOUT_EXPIRED ) {
requestAnimationFrame( test );
return;
}
gl.deleteSync( sync );
resolve();
}
test();
} );
}
}
export default WebGLUtils;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
import WebGPU from '../../capabilities/WebGPU.js';
import Renderer from '../common/Renderer.js';
import WebGLBackend from '../webgl/WebGLBackend.js';
import WebGPUBackend from './WebGPUBackend.js';
/*
const debugHandler = {
get: function ( target, name ) {
// Add |update
if ( /^(create|destroy)/.test( name ) ) console.log( 'WebGPUBackend.' + name );
return target[ name ];
}
};
*/
class WebGPURenderer extends Renderer {
constructor( parameters = {} ) {
let BackendClass;
if ( parameters.forceWebGL ) {
BackendClass = WebGLBackend;
} else if ( WebGPU.isAvailable() ) {
BackendClass = WebGPUBackend;
} else {
BackendClass = WebGLBackend;
console.warn( 'THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' );
}
const backend = new BackendClass( parameters );
//super( new Proxy( backend, debugHandler ) );
super( backend, parameters );
this.isWebGPURenderer = true;
}
}
export default WebGPURenderer;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
import NodeFunction from '../../../nodes/core/NodeFunction.js';
import NodeFunctionInput from '../../../nodes/core/NodeFunctionInput.js';
const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+)?/i;
const propertiesRegexp = /[a-z_0-9]+|<(.*?)>+/ig;
const wgslTypeLib = {
f32: 'float'
};
const parse = ( source ) => {
source = source.trim();
const declaration = source.match( declarationRegexp );
if ( declaration !== null && declaration.length === 4 ) {
// tokenizer
const inputsCode = declaration[ 2 ];
const propsMatches = [];
let nameMatch = null;
while ( ( nameMatch = propertiesRegexp.exec( inputsCode ) ) !== null ) {
propsMatches.push( nameMatch );
}
// parser
const inputs = [];
let i = 0;
while ( i < propsMatches.length ) {
// default
const name = propsMatches[ i ++ ][ 0 ];
let type = propsMatches[ i ++ ][ 0 ];
type = wgslTypeLib[ type ] || type;
// precision
if ( i < propsMatches.length && propsMatches[ i ][ 0 ].startsWith( '<' ) === true )
i ++;
// add input
inputs.push( new NodeFunctionInput( type, name ) );
}
//
const blockCode = source.substring( declaration[ 0 ].length );
const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
const type = declaration[ 3 ] || 'void';
return {
type,
inputs,
name,
inputsCode,
blockCode
};
} else {
throw new Error( 'FunctionNode: Function is not a WGSL code.' );
}
};
class WGSLNodeFunction extends NodeFunction {
constructor( source ) {
const { type, inputs, name, inputsCode, blockCode } = parse( source );
super( type, inputs, name );
this.inputsCode = inputsCode;
this.blockCode = blockCode;
}
getCode( name = this.name ) {
const type = this.type !== 'void' ? '-> ' + this.type : '';
return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ type }` + this.blockCode;
}
}
export default WGSLNodeFunction;

View File

@ -0,0 +1,14 @@
import NodeParser from '../../../nodes/core/NodeParser.js';
import WGSLNodeFunction from './WGSLNodeFunction.js';
class WGSLNodeParser extends NodeParser {
parseFunction( source ) {
return new WGSLNodeFunction( source );
}
}
export default WGSLNodeParser;

View File

@ -0,0 +1,321 @@
import { Float16BufferAttribute } from 'three';
import { GPUInputStepMode } from './WebGPUConstants.js';
const typedArraysToVertexFormatPrefix = new Map( [
[ Int8Array, [ 'sint8', 'snorm8' ]],
[ Uint8Array, [ 'uint8', 'unorm8' ]],
[ Int16Array, [ 'sint16', 'snorm16' ]],
[ Uint16Array, [ 'uint16', 'unorm16' ]],
[ Int32Array, [ 'sint32', 'snorm32' ]],
[ Uint32Array, [ 'uint32', 'unorm32' ]],
[ Float32Array, [ 'float32', ]],
] );
const typedAttributeToVertexFormatPrefix = new Map( [
[ Float16BufferAttribute, [ 'float16', ]],
] );
const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [
[ Int32Array, 'sint32' ],
[ Int16Array, 'sint32' ], // patch for INT16
[ Uint32Array, 'uint32' ],
[ Uint16Array, 'uint32' ], // patch for UINT16
[ Float32Array, 'float32' ]
] );
class WebGPUAttributeUtils {
constructor( backend ) {
this.backend = backend;
}
createAttribute( attribute, usage ) {
const bufferAttribute = this._getBufferAttribute( attribute );
const backend = this.backend;
const bufferData = backend.get( bufferAttribute );
let buffer = bufferData.buffer;
if ( buffer === undefined ) {
const device = backend.device;
let array = bufferAttribute.array;
// patch for INT16 and UINT16
if ( attribute.normalized === false && ( array.constructor === Int16Array || array.constructor === Uint16Array ) ) {
const tempArray = new Uint32Array( array.length );
for ( let i = 0; i < array.length; i ++ ) {
tempArray[ i ] = array[ i ];
}
array = tempArray;
}
bufferAttribute.array = array;
if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) {
array = new array.constructor( bufferAttribute.count * 4 );
for ( let i = 0; i < bufferAttribute.count; i ++ ) {
array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 );
}
// Update BufferAttribute
bufferAttribute.itemSize = 4;
bufferAttribute.array = array;
}
const size = array.byteLength + ( ( 4 - ( array.byteLength % 4 ) ) % 4 ); // ensure 4 byte alignment, see #20441
buffer = device.createBuffer( {
label: bufferAttribute.name,
size: size,
usage: usage,
mappedAtCreation: true
} );
new array.constructor( buffer.getMappedRange() ).set( array );
buffer.unmap();
bufferData.buffer = buffer;
}
}
updateAttribute( attribute ) {
const bufferAttribute = this._getBufferAttribute( attribute );
const backend = this.backend;
const device = backend.device;
const buffer = backend.get( bufferAttribute ).buffer;
const array = bufferAttribute.array;
const updateRanges = bufferAttribute.updateRanges;
if ( updateRanges.length === 0 ) {
// Not using update ranges
device.queue.writeBuffer(
buffer,
0,
array,
0
);
} else {
for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
const range = updateRanges[ i ];
device.queue.writeBuffer(
buffer,
0,
array,
range.start * array.BYTES_PER_ELEMENT,
range.count * array.BYTES_PER_ELEMENT
);
}
bufferAttribute.clearUpdateRanges();
}
}
createShaderVertexBuffers( renderObject ) {
const attributes = renderObject.getAttributes();
const vertexBuffers = new Map();
for ( let slot = 0; slot < attributes.length; slot ++ ) {
const geometryAttribute = attributes[ slot ];
const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
const bufferAttribute = this._getBufferAttribute( geometryAttribute );
let vertexBufferLayout = vertexBuffers.get( bufferAttribute );
if ( vertexBufferLayout === undefined ) {
let arrayStride, stepMode;
if ( geometryAttribute.isInterleavedBufferAttribute === true ) {
arrayStride = geometryAttribute.data.stride * bytesPerElement;
stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
} else {
arrayStride = geometryAttribute.itemSize * bytesPerElement;
stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
}
// patch for INT16 and UINT16
if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) {
arrayStride = 4;
}
vertexBufferLayout = {
arrayStride,
attributes: [],
stepMode
};
vertexBuffers.set( bufferAttribute, vertexBufferLayout );
}
const format = this._getVertexFormat( geometryAttribute );
const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0;
vertexBufferLayout.attributes.push( {
shaderLocation: slot,
offset,
format
} );
}
return Array.from( vertexBuffers.values() );
}
destroyAttribute( attribute ) {
const backend = this.backend;
const data = backend.get( this._getBufferAttribute( attribute ) );
data.buffer.destroy();
backend.delete( attribute );
}
async getArrayBufferAsync( attribute ) {
const backend = this.backend;
const device = backend.device;
const data = backend.get( this._getBufferAttribute( attribute ) );
const bufferGPU = data.buffer;
const size = bufferGPU.size;
let readBufferGPU = data.readBuffer;
let needsUnmap = true;
if ( readBufferGPU === undefined ) {
readBufferGPU = device.createBuffer( {
label: attribute.name,
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
} );
needsUnmap = false;
data.readBuffer = readBufferGPU;
}
const cmdEncoder = device.createCommandEncoder( {} );
cmdEncoder.copyBufferToBuffer(
bufferGPU,
0,
readBufferGPU,
0,
size
);
if ( needsUnmap ) readBufferGPU.unmap();
const gpuCommands = cmdEncoder.finish();
device.queue.submit( [ gpuCommands ] );
await readBufferGPU.mapAsync( GPUMapMode.READ );
const arrayBuffer = readBufferGPU.getMappedRange();
return arrayBuffer;
}
_getVertexFormat( geometryAttribute ) {
const { itemSize, normalized } = geometryAttribute;
const ArrayType = geometryAttribute.array.constructor;
const AttributeType = geometryAttribute.constructor;
let format;
if ( itemSize == 1 ) {
format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
} else {
const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
const prefix = prefixOptions[ normalized ? 1 : 0 ];
if ( prefix ) {
const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
if ( paddedItemSize % 1 ) {
throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' );
}
format = `${prefix}x${paddedItemSize}`;
}
}
if ( ! format ) {
console.error( 'THREE.WebGPUAttributeUtils: Vertex format not supported yet.' );
}
return format;
}
_getBufferAttribute( attribute ) {
if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
return attribute;
}
}
export default WebGPUAttributeUtils;

View File

@ -0,0 +1,258 @@
import {
GPUTextureAspect, GPUTextureViewDimension, GPUBufferBindingType, GPUTextureSampleType
} from './WebGPUConstants.js';
import { FloatType, IntType, UnsignedIntType } from 'three';
class WebGPUBindingUtils {
constructor( backend ) {
this.backend = backend;
}
createBindingsLayout( bindings ) {
const backend = this.backend;
const device = backend.device;
const entries = [];
let index = 0;
for ( const binding of bindings ) {
const bindingGPU = {
binding: index ++,
visibility: binding.visibility
};
if ( binding.isUniformBuffer || binding.isStorageBuffer ) {
const buffer = {}; // GPUBufferBindingLayout
if ( binding.isStorageBuffer ) {
buffer.type = GPUBufferBindingType.Storage;
}
bindingGPU.buffer = buffer;
} else if ( binding.isSampler ) {
const sampler = {}; // GPUSamplerBindingLayout
if ( binding.texture.isDepthTexture ) {
if ( binding.texture.compareFunction !== null ) {
sampler.type = 'comparison';
}
}
bindingGPU.sampler = sampler;
} else if ( binding.isSampledTexture && binding.texture.isVideoTexture ) {
bindingGPU.externalTexture = {}; // GPUExternalTextureBindingLayout
} else if ( binding.isSampledTexture && binding.store ) {
const format = this.backend.get( binding.texture ).texture.format;
bindingGPU.storageTexture = { format }; // GPUStorageTextureBindingLayout
} else if ( binding.isSampledTexture ) {
const texture = {}; // GPUTextureBindingLayout
if ( binding.texture.isDepthTexture ) {
texture.sampleType = GPUTextureSampleType.Depth;
} else if ( binding.texture.isDataTexture ) {
const type = binding.texture.type;
if ( type === IntType ) {
texture.sampleType = GPUTextureSampleType.SInt;
} else if ( type === UnsignedIntType ) {
texture.sampleType = GPUTextureSampleType.UInt;
} else if ( type === FloatType ) {
// @TODO: Add support for this soon: backend.hasFeature( 'float32-filterable' )
texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
}
}
if ( binding.isSampledCubeTexture ) {
texture.viewDimension = GPUTextureViewDimension.Cube;
} else if ( binding.texture.isDataArrayTexture ) {
texture.viewDimension = GPUTextureViewDimension.TwoDArray;
}
bindingGPU.texture = texture;
} else {
console.error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` );
}
entries.push( bindingGPU );
}
return device.createBindGroupLayout( { entries } );
}
createBindings( bindings ) {
const backend = this.backend;
const bindingsData = backend.get( bindings );
// setup (static) binding layout and (dynamic) binding group
const bindLayoutGPU = this.createBindingsLayout( bindings );
const bindGroupGPU = this.createBindGroup( bindings, bindLayoutGPU );
bindingsData.layout = bindLayoutGPU;
bindingsData.group = bindGroupGPU;
bindingsData.bindings = bindings;
}
updateBinding( binding ) {
const backend = this.backend;
const device = backend.device;
const buffer = binding.buffer;
const bufferGPU = backend.get( binding ).buffer;
device.queue.writeBuffer( bufferGPU, 0, buffer, 0 );
}
createBindGroup( bindings, layoutGPU ) {
const backend = this.backend;
const device = backend.device;
let bindingPoint = 0;
const entriesGPU = [];
for ( const binding of bindings ) {
if ( binding.isUniformBuffer ) {
const bindingData = backend.get( binding );
if ( bindingData.buffer === undefined ) {
const byteLength = binding.byteLength;
const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
const bufferGPU = device.createBuffer( {
label: 'bindingBuffer_' + binding.name,
size: byteLength,
usage: usage
} );
bindingData.buffer = bufferGPU;
}
entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
} else if ( binding.isStorageBuffer ) {
const bindingData = backend.get( binding );
if ( bindingData.buffer === undefined ) {
const attribute = binding.attribute;
//const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | /*GPUBufferUsage.COPY_SRC |*/ GPUBufferUsage.COPY_DST;
//backend.attributeUtils.createAttribute( attribute, usage ); // @TODO: Move it to universal renderer
bindingData.buffer = backend.get( attribute ).buffer;
}
entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
} else if ( binding.isSampler ) {
const textureGPU = backend.get( binding.texture );
entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } );
} else if ( binding.isSampledTexture ) {
const textureData = backend.get( binding.texture );
let dimensionViewGPU;
if ( binding.isSampledCubeTexture ) {
dimensionViewGPU = GPUTextureViewDimension.Cube;
} else if ( binding.texture.isDataArrayTexture ) {
dimensionViewGPU = GPUTextureViewDimension.TwoDArray;
} else {
dimensionViewGPU = GPUTextureViewDimension.TwoD;
}
let resourceGPU;
if ( textureData.externalTexture !== undefined ) {
resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } );
} else {
const aspectGPU = GPUTextureAspect.All;
resourceGPU = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount: binding.store ? 1 : textureData.mipLevelCount } );
}
entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } );
}
bindingPoint ++;
}
return device.createBindGroup( {
layout: layoutGPU,
entries: entriesGPU
} );
}
}
export default WebGPUBindingUtils;

View File

@ -0,0 +1,324 @@
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',
// 'depth32float-stencil8' extension
Depth32FloatStencil8: 'depth32float-stencil8',
// 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',
// ETC2 compressed formats usable if 'texture-compression-etc2' is both
// supported by the device/user agent and enabled in requestDevice.
ETC2RGB8Unorm: 'etc2-rgb8unorm',
ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb',
ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm',
ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb',
ETC2RGBA8Unorm: 'etc2-rgba8unorm',
ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb',
EACR11Unorm: 'eac-r11unorm',
EACR11Snorm: 'eac-r11snorm',
EACRG11Unorm: 'eac-rg11unorm',
EACRG11Snorm: 'eac-rg11snorm',
// ASTC compressed formats usable if 'texture-compression-astc' is both
// supported by the device/user agent and enabled in requestDevice.
ASTC4x4Unorm: 'astc-4x4-unorm',
ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb',
ASTC5x4Unorm: 'astc-5x4-unorm',
ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb',
ASTC5x5Unorm: 'astc-5x5-unorm',
ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb',
ASTC6x5Unorm: 'astc-6x5-unorm',
ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb',
ASTC6x6Unorm: 'astc-6x6-unorm',
ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb',
ASTC8x5Unorm: 'astc-8x5-unorm',
ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb',
ASTC8x6Unorm: 'astc-8x6-unorm',
ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb',
ASTC8x8Unorm: 'astc-8x8-unorm',
ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb',
ASTC10x5Unorm: 'astc-10x5-unorm',
ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb',
ASTC10x6Unorm: 'astc-10x6-unorm',
ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb',
ASTC10x8Unorm: 'astc-10x8-unorm',
ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb',
ASTC10x10Unorm: 'astc-10x10-unorm',
ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb',
ASTC12x10Unorm: 'astc-12x10-unorm',
ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb',
ASTC12x12Unorm: 'astc-12x12-unorm',
ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb',
};
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',
Src: 'src',
OneMinusSrc: 'one-minus-src',
SrcAlpha: 'src-alpha',
OneMinusSrcAlpha: 'one-minus-src-alpha',
Dst: 'dst',
OneMinusDstColor: 'one-minus-dst',
DstAlpha: 'dst-alpha',
OneMinusDstAlpha: 'one-minus-dst-alpha',
SrcAlphaSaturated: 'src-alpha-saturated',
Constant: 'constant',
OneMinusConstant: 'one-minus-constant'
};
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 GPUBufferBindingType = {
Uniform: 'uniform',
Storage: 'storage',
ReadOnlyStorage: 'read-only-storage'
};
export const GPUSamplerBindingType = {
Filtering: 'filtering',
NonFiltering: 'non-filtering',
Comparison: 'comparison'
};
export const GPUTextureSampleType = {
Float: 'float',
UnfilterableFloat: 'unfilterable-float',
Depth: 'depth',
SInt: 'sint',
UInt: 'uint'
};
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 GPUTextureAspect = {
All: 'all',
StencilOnly: 'stencil-only',
DepthOnly: 'depth-only'
};
export const GPUInputStepMode = {
Vertex: 'vertex',
Instance: 'instance'
};
export const GPUFeatureName = {
DepthClipControl: 'depth-clip-control',
Depth32FloatStencil8: 'depth32float-stencil8',
TextureCompressionBC: 'texture-compression-bc',
TextureCompressionETC2: 'texture-compression-etc2',
TextureCompressionASTC: 'texture-compression-astc',
TimestampQuery: 'timestamp-query',
IndirectFirstInstance: 'indirect-first-instance',
ShaderF16: 'shader-f16',
RG11B10UFloat: 'rg11b10ufloat-renderable',
BGRA8UNormStorage: 'bgra8unorm-storage',
Float32Filterable: 'float32-filterable'
};

View File

@ -0,0 +1,591 @@
import { BlendColorFactor, OneMinusBlendColorFactor, } from '../../common/Constants.js';
import {
GPUFrontFace, GPUCullMode, GPUColorWriteFlags, GPUCompareFunction, GPUBlendFactor, GPUBlendOperation, GPUIndexFormat, GPUStencilOperation
} from './WebGPUConstants.js';
import {
FrontSide, BackSide, DoubleSide,
NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending,
ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstColorFactor,
OneMinusDstColorFactor, DstAlphaFactor, OneMinusDstAlphaFactor, SrcAlphaSaturateFactor,
AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation,
KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp,
NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc
} from 'three';
class WebGPUPipelineUtils {
constructor( backend ) {
this.backend = backend;
}
createRenderPipeline( renderObject, promises ) {
const { object, material, geometry, pipeline } = renderObject;
const { vertexProgram, fragmentProgram } = pipeline;
const backend = this.backend;
const device = backend.device;
const utils = backend.utils;
const pipelineData = backend.get( pipeline );
const bindingsData = backend.get( renderObject.getBindings() );
// vertex buffers
const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject );
// blending
let blending;
if ( material.transparent === true && material.blending !== NoBlending ) {
blending = this._getBlending( 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 colorWriteMask = this._getColorWriteMask( material );
const targets = [];
if ( renderObject.context.textures !== null ) {
const textures = renderObject.context.textures;
for ( let i = 0; i < textures.length; i ++ ) {
const colorFormat = utils.getTextureFormatGPU( textures[ i ] );
targets.push( {
format: colorFormat,
blend: blending,
writeMask: colorWriteMask
} );
}
} else {
const colorFormat = utils.getCurrentColorFormat( renderObject.context );
targets.push( {
format: colorFormat,
blend: blending,
writeMask: colorWriteMask
} );
}
const vertexModule = backend.get( vertexProgram ).module;
const fragmentModule = backend.get( fragmentProgram ).module;
const primitiveState = this._getPrimitiveState( object, geometry, material );
const depthCompare = this._getDepthCompare( material );
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
let sampleCount = utils.getSampleCount( renderObject.context );
if ( sampleCount > 1 ) {
// WebGPU only supports power-of-two sample counts and 2 is not a valid value
sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
if ( sampleCount === 2 ) {
sampleCount = 4;
}
}
const pipelineDescriptor = {
vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),
fragment: Object.assign( {}, fragmentModule, { targets } ),
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: sampleCount,
alphaToCoverageEnabled: material.alphaToCoverage
},
layout: device.createPipelineLayout( {
bindGroupLayouts: [ bindingsData.layout ]
} )
};
if ( promises === null ) {
pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor );
} else {
const p = new Promise( ( resolve /*, reject*/ ) => {
device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => {
pipelineData.pipeline = pipeline;
resolve();
} );
} );
promises.push( p );
}
}
createComputePipeline( pipeline, bindings ) {
const backend = this.backend;
const device = backend.device;
const computeProgram = backend.get( pipeline.computeProgram ).module;
const pipelineGPU = backend.get( pipeline );
const bindingsData = backend.get( bindings );
pipelineGPU.pipeline = device.createComputePipeline( {
compute: computeProgram,
layout: device.createPipelineLayout( {
bindGroupLayouts: [ bindingsData.layout ]
} )
} );
}
_getBlending( material ) {
let color, alpha;
const blending = material.blending;
if ( blending === CustomBlending ) {
const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : GPUBlendFactor.One;
const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : GPUBlendFactor.Zero;
const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : GPUBlendFactor.Add;
color = {
srcFactor: this._getBlendFactor( material.blendSrc ),
dstFactor: this._getBlendFactor( material.blendDst ),
operation: this._getBlendOperation( material.blendEquation )
};
alpha = {
srcFactor: this._getBlendFactor( blendSrcAlpha ),
dstFactor: this._getBlendFactor( blendDstAlpha ),
operation: this._getBlendOperation( blendEquationAlpha )
};
} else {
const premultipliedAlpha = material.premultipliedAlpha;
const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => {
color = {
srcFactor: srcRGB,
dstFactor: dstRGB,
operation: GPUBlendOperation.Add
};
alpha = {
srcFactor: srcAlpha,
dstFactor: dstAlpha,
operation: GPUBlendOperation.Add
};
};
if ( premultipliedAlpha ) {
switch ( blending ) {
case NormalBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
break;
case AdditiveBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One );
break;
case SubtractiveBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One );
break;
case MultiplyBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.SrcAlpha );
break;
}
} else {
switch ( blending ) {
case NormalBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha );
break;
case AdditiveBlending:
setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.SrcAlpha, GPUBlendFactor.One );
break;
case SubtractiveBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One );
break;
case MultiplyBlending:
setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.Src );
break;
}
}
}
if ( color !== undefined && alpha !== undefined ) {
return { color, alpha };
} else {
console.error( 'THREE.WebGPURenderer: Invalid blending: ', blending );
}
}
_getBlendFactor( blend ) {
let blendFactor;
switch ( blend ) {
case ZeroFactor:
blendFactor = GPUBlendFactor.Zero;
break;
case OneFactor:
blendFactor = GPUBlendFactor.One;
break;
case SrcColorFactor:
blendFactor = GPUBlendFactor.Src;
break;
case OneMinusSrcColorFactor:
blendFactor = GPUBlendFactor.OneMinusSrc;
break;
case SrcAlphaFactor:
blendFactor = GPUBlendFactor.SrcAlpha;
break;
case OneMinusSrcAlphaFactor:
blendFactor = GPUBlendFactor.OneMinusSrcAlpha;
break;
case DstColorFactor:
blendFactor = GPUBlendFactor.Dst;
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.Constant;
break;
case OneMinusBlendColorFactor:
blendFactor = GPUBlendFactor.OneMinusConstant;
break;
default:
console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend );
}
return blendFactor;
}
_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;
}
_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.WebGPUPipelineUtils: Blend equation not supported.', blendEquation );
}
return blendOperation;
}
_getPrimitiveState( object, geometry, material ) {
const descriptor = {};
const utils = this.backend.utils;
descriptor.topology = utils.getPrimitiveTopology( object, material );
if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) {
descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
}
switch ( material.side ) {
case FrontSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.Back;
break;
case BackSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.Front;
break;
case DoubleSide:
descriptor.frontFace = GPUFrontFace.CCW;
descriptor.cullMode = GPUCullMode.None;
break;
default:
console.error( 'THREE.WebGPUPipelineUtils: Unknown material.side value.', material.side );
break;
}
return descriptor;
}
_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.WebGPUPipelineUtils: Invalid depth function.', depthFunc );
}
}
return depthCompare;
}
}
export default WebGPUPipelineUtils;

View File

@ -0,0 +1,285 @@
import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
class WebGPUTexturePassUtils {
constructor( device ) {
this.device = device;
const mipmapVertexSource = `
struct VarysStruct {
@builtin( position ) Position: vec4<f32>,
@location( 0 ) vTex : vec2<f32>
};
@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>;
@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vTex );
}
`;
const flipYFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;
@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;
@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
}
`;
this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
// We'll need a new pipeline for every texture format used.
this.transferPipelines = {};
this.flipYPipelines = {};
this.mipmapVertexShaderModule = device.createShaderModule( {
label: 'mipmapVertex',
code: mipmapVertexSource
} );
this.mipmapFragmentShaderModule = device.createShaderModule( {
label: 'mipmapFragment',
code: mipmapFragmentSource
} );
this.flipYFragmentShaderModule = device.createShaderModule( {
label: 'flipYFragment',
code: flipYFragmentSource
} );
}
getTransferPipeline( format ) {
let pipeline = this.transferPipelines[ 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
},
layout: 'auto'
} );
this.transferPipelines[ format ] = pipeline;
}
return pipeline;
}
getFlipYPipeline( format ) {
let pipeline = this.flipYPipelines[ format ];
if ( pipeline === undefined ) {
pipeline = this.device.createRenderPipeline( {
vertex: {
module: this.mipmapVertexShaderModule,
entryPoint: 'main'
},
fragment: {
module: this.flipYFragmentShaderModule,
entryPoint: 'main',
targets: [ { format } ]
},
primitive: {
topology: GPUPrimitiveTopology.TriangleStrip,
stripIndexFormat: GPUIndexFormat.Uint32
},
layout: 'auto'
} );
this.flipYPipelines[ format ] = pipeline;
}
return pipeline;
}
flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
const format = textureGPUDescriptor.format;
const { width, height } = textureGPUDescriptor.size;
const transferPipeline = this.getTransferPipeline( format );
const flipYPipeline = this.getFlipYPipeline( format );
const tempTexture = this.device.createTexture( {
size: { width, height, depthOrArrayLayers: 1 },
format,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
} );
const srcView = textureGPU.createView( {
baseMipLevel: 0,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
const dstView = tempTexture.createView( {
baseMipLevel: 0,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer: 0
} );
const commandEncoder = this.device.createCommandEncoder( {} );
const pass = ( pipeline, sourceView, destinationView ) => {
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
const bindGroup = this.device.createBindGroup( {
layout: bindGroupLayout,
entries: [ {
binding: 0,
resource: this.flipYSampler
}, {
binding: 1,
resource: sourceView
} ]
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
view: destinationView,
loadOp: GPULoadOp.Clear,
storeOp: GPUStoreOp.Store,
clearValue: [ 0, 0, 0, 0 ]
} ]
} );
passEncoder.setPipeline( pipeline );
passEncoder.setBindGroup( 0, bindGroup );
passEncoder.draw( 4, 1, 0, 0 );
passEncoder.end();
};
pass( transferPipeline, srcView, dstView );
pass( flipYPipeline, dstView, srcView );
this.device.queue.submit( [ commandEncoder.finish() ] );
tempTexture.destroy();
}
generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
const pipeline = this.getTransferPipeline( 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,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
const bindGroup = this.device.createBindGroup( {
layout: bindGroupLayout,
entries: [ {
binding: 0,
resource: this.mipmapSampler
}, {
binding: 1,
resource: srcView
} ]
} );
const dstView = textureGPU.createView( {
baseMipLevel: i,
mipLevelCount: 1,
dimension: GPUTextureViewDimension.TwoD,
baseArrayLayer
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
view: dstView,
loadOp: GPULoadOp.Clear,
storeOp: GPUStoreOp.Store,
clearValue: [ 0, 0, 0, 0 ]
} ]
} );
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 WebGPUTexturePassUtils;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
import { GPUPrimitiveTopology, GPUTextureFormat } from './WebGPUConstants.js';
class WebGPUUtils {
constructor( backend ) {
this.backend = backend;
}
getCurrentDepthStencilFormat( renderContext ) {
let format;
if ( renderContext.depthTexture !== null ) {
format = this.getTextureFormatGPU( renderContext.depthTexture );
} else if ( renderContext.depth && renderContext.stencil ) {
format = GPUTextureFormat.Depth24PlusStencil8;
} else if ( renderContext.depth ) {
format = GPUTextureFormat.Depth24Plus;
}
return format;
}
getTextureFormatGPU( texture ) {
return this.backend.get( texture ).texture.format;
}
getCurrentColorFormat( renderContext ) {
let format;
if ( renderContext.textures !== null ) {
format = this.getTextureFormatGPU( renderContext.textures[ 0 ] );
} else {
format = GPUTextureFormat.BGRA8Unorm; // default context format
}
return format;
}
getCurrentColorSpace( renderContext ) {
if ( renderContext.textures !== null ) {
return renderContext.textures[ 0 ].colorSpace;
}
return this.backend.renderer.outputColorSpace;
}
getPrimitiveTopology( object, material ) {
if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList;
else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
}
getSampleCount( renderContext ) {
if ( renderContext.textures !== null ) {
return renderContext.sampleCount;
}
return this.backend.parameters.sampleCount;
}
}
export default WebGPUUtils;