diff --git a/eslint.config.js b/eslint.config.js index 686364408bf7e0..f2893072bd2ba1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -63,6 +63,7 @@ export default [ GPUTexture: 'readonly', GPUMapMode: 'readonly', QUnit: 'readonly', + Ammo: 'readonly', XRRigidTransform: 'readonly', XRMediaBinding: 'readonly', CodeMirror: 'readonly', diff --git a/examples/files.json b/examples/files.json index 08469c9ad5891f..9f99a3c4f776f3 100644 --- a/examples/files.json +++ b/examples/files.json @@ -552,6 +552,12 @@ "games_fps" ], "physics": [ + "physics_ammo_break", + "physics_ammo_cloth", + "physics_ammo_instancing", + "physics_ammo_rope", + "physics_ammo_terrain", + "physics_ammo_volume", "physics_jolt_instancing", "physics_rapier_basic", "physics_rapier_instancing", diff --git a/examples/jsm/Addons.js b/examples/jsm/Addons.js index 4edc716cdca569..67c4f66382e585 100644 --- a/examples/jsm/Addons.js +++ b/examples/jsm/Addons.js @@ -171,6 +171,7 @@ export * from './objects/Sky.js'; export * from './objects/Water.js'; export { Water as Water2 } from './objects/Water2.js'; +export * from './physics/AmmoPhysics.js'; export * from './physics/JoltPhysics.js'; export * from './physics/RapierPhysics.js'; diff --git a/examples/jsm/physics/AmmoPhysics.js b/examples/jsm/physics/AmmoPhysics.js new file mode 100644 index 00000000000000..87664185034ed5 --- /dev/null +++ b/examples/jsm/physics/AmmoPhysics.js @@ -0,0 +1,366 @@ +const AMMO_PATH = 'https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js'; + +/** + * @classdesc Can be used to include Ammo.js as a Physics engine into + * `three.js` apps. The API can be initialized via: + * ```js + * const physics = await AmmoPhysics(); + * ``` + * The component automatically imports Ammo.js from a CDN so make sure + * to use the component with an active Internet connection. + * + * @name AmmoPhysics + * @class + * @hideconstructor + * @three_import import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js'; + */ +async function AmmoPhysics() { + + if ( typeof Ammo === 'undefined' ) { + + await new Promise( ( resolve, reject ) => { + + const script = document.createElement( 'script' ); + script.src = AMMO_PATH; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild( script ); + + } ); + + } + + const AmmoLib = await Ammo(); + + const frameRate = 60; + + const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration(); + const dispatcher = new AmmoLib.btCollisionDispatcher( collisionConfiguration ); + const broadphase = new AmmoLib.btDbvtBroadphase(); + const solver = new AmmoLib.btSequentialImpulseConstraintSolver(); + const world = new AmmoLib.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); + world.setGravity( new AmmoLib.btVector3( 0, - 9.8, 0 ) ); + + const worldTransform = new AmmoLib.btTransform(); + + // + + function getShape( geometry ) { + + const parameters = geometry.parameters; + + // TODO change type to is* + + if ( geometry.type === 'BoxGeometry' ) { + + const sx = parameters.width !== undefined ? parameters.width / 2 : 0.5; + const sy = parameters.height !== undefined ? parameters.height / 2 : 0.5; + const sz = parameters.depth !== undefined ? parameters.depth / 2 : 0.5; + + const shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( sx, sy, sz ) ); + shape.setMargin( 0.05 ); + + return shape; + + } else if ( geometry.type === 'SphereGeometry' || geometry.type === 'IcosahedronGeometry' ) { + + const radius = parameters.radius !== undefined ? parameters.radius : 1; + + const shape = new AmmoLib.btSphereShape( radius ); + shape.setMargin( 0.05 ); + + return shape; + + } + + console.error( 'AmmoPhysics: Unsupported geometry type:', geometry.type ); + + return null; + + } + + const meshes = []; + const meshMap = new WeakMap(); + + function addScene( scene ) { + + scene.traverse( function ( child ) { + + if ( child.isMesh ) { + + const physics = child.userData.physics; + + if ( physics ) { + + addMesh( child, physics.mass, physics.restitution ); + + } + + } + + } ); + + } + + function addMesh( mesh, mass = 0, restitution = 0 ) { + + const shape = getShape( mesh.geometry ); + + if ( shape !== null ) { + + if ( mesh.isInstancedMesh ) { + + handleInstancedMesh( mesh, shape, mass, restitution ); + + } else if ( mesh.isMesh ) { + + handleMesh( mesh, shape, mass, restitution ); + + } + + } + + } + + function handleMesh( mesh, shape, mass, restitution ) { + + const position = mesh.position; + const quaternion = mesh.quaternion; + + const transform = new AmmoLib.btTransform(); + transform.setIdentity(); + transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) ); + transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) ); + + const motionState = new AmmoLib.btDefaultMotionState( transform ); + + const localInertia = new AmmoLib.btVector3( 0, 0, 0 ); + shape.calculateLocalInertia( mass, localInertia ); + + const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia ); + rbInfo.set_m_restitution( restitution ); + + const body = new AmmoLib.btRigidBody( rbInfo ); + // body.setFriction( 4 ); + world.addRigidBody( body ); + + if ( mass > 0 ) { + + meshes.push( mesh ); + meshMap.set( mesh, body ); + + } + + + } + + function handleInstancedMesh( mesh, shape, mass, restitution ) { + + const array = mesh.instanceMatrix.array; + + const bodies = []; + + for ( let i = 0; i < mesh.count; i ++ ) { + + const index = i * 16; + + const transform = new AmmoLib.btTransform(); + transform.setFromOpenGLMatrix( array.slice( index, index + 16 ) ); + + const motionState = new AmmoLib.btDefaultMotionState( transform ); + + const localInertia = new AmmoLib.btVector3( 0, 0, 0 ); + shape.calculateLocalInertia( mass, localInertia ); + + const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia ); + rbInfo.set_m_restitution( restitution ); + + const body = new AmmoLib.btRigidBody( rbInfo ); + world.addRigidBody( body ); + + bodies.push( body ); + + } + + if ( mass > 0 ) { + + meshes.push( mesh ); + + meshMap.set( mesh, bodies ); + + } + + } + + // + + function setMeshPosition( mesh, position, index = 0 ) { + + if ( mesh.isInstancedMesh ) { + + const bodies = meshMap.get( mesh ); + const body = bodies[ index ]; + + body.setAngularVelocity( new AmmoLib.btVector3( 0, 0, 0 ) ); + body.setLinearVelocity( new AmmoLib.btVector3( 0, 0, 0 ) ); + + worldTransform.setIdentity(); + worldTransform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) ); + body.setWorldTransform( worldTransform ); + + } else if ( mesh.isMesh ) { + + const body = meshMap.get( mesh ); + + body.setAngularVelocity( new AmmoLib.btVector3( 0, 0, 0 ) ); + body.setLinearVelocity( new AmmoLib.btVector3( 0, 0, 0 ) ); + + worldTransform.setIdentity(); + worldTransform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) ); + body.setWorldTransform( worldTransform ); + + } + + } + + // + + let lastTime = 0; + + function step() { + + const time = performance.now(); + + if ( lastTime > 0 ) { + + const delta = ( time - lastTime ) / 1000; + + world.stepSimulation( delta, 10 ); + + // + + for ( let i = 0, l = meshes.length; i < l; i ++ ) { + + const mesh = meshes[ i ]; + + if ( mesh.isInstancedMesh ) { + + const array = mesh.instanceMatrix.array; + const bodies = meshMap.get( mesh ); + + for ( let j = 0; j < bodies.length; j ++ ) { + + const body = bodies[ j ]; + + const motionState = body.getMotionState(); + motionState.getWorldTransform( worldTransform ); + + const position = worldTransform.getOrigin(); + const quaternion = worldTransform.getRotation(); + + compose( position, quaternion, array, j * 16 ); + + } + + mesh.instanceMatrix.needsUpdate = true; + mesh.computeBoundingSphere(); + + } else if ( mesh.isMesh ) { + + const body = meshMap.get( mesh ); + + const motionState = body.getMotionState(); + motionState.getWorldTransform( worldTransform ); + + const position = worldTransform.getOrigin(); + const quaternion = worldTransform.getRotation(); + mesh.position.set( position.x(), position.y(), position.z() ); + mesh.quaternion.set( quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w() ); + + } + + } + + } + + lastTime = time; + + } + + // animate + + setInterval( step, 1000 / frameRate ); + + return { + /** + * Adds the given scene to this physics simulation. Only meshes with a + * `physics` object in their {@link Object3D#userData} field will be honored. + * The object can be used to store the mass of the mesh. E.g.: + * ```js + * box.userData.physics = { mass: 1 }; + * ``` + * + * @method + * @name AmmoPhysics#addScene + * @param {Object3D} scene The scene or any type of 3D object to add. + */ + addScene: addScene, + + /** + * Adds the given mesh to this physics simulation. + * + * @method + * @name AmmoPhysics#addMesh + * @param {Mesh} mesh The mesh to add. + * @param {number} [mass=0] The mass in kg of the mesh. + * @param {number} [restitution=0] The restitution of the mesh, usually from 0 to 1. Represents how "bouncy" objects are when they collide with each other. + */ + addMesh: addMesh, + + /** + * Set the position of the given mesh which is part of the physics simulation. Calling this + * method will reset the current simulated velocity of the mesh. + * + * @method + * @name AmmoPhysics#setMeshPosition + * @param {Mesh} mesh The mesh to update the position for. + * @param {Vector3} position - The new position. + * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID. + */ + setMeshPosition: setMeshPosition + // addCompoundMesh + }; + +} + +function compose( position, quaternion, array, index ) { + + const x = quaternion.x(), y = quaternion.y(), z = quaternion.z(), w = quaternion.w(); + const x2 = x + x, y2 = y + y, z2 = z + z; + const xx = x * x2, xy = x * y2, xz = x * z2; + const yy = y * y2, yz = y * z2, zz = z * z2; + const wx = w * x2, wy = w * y2, wz = w * z2; + + array[ index + 0 ] = ( 1 - ( yy + zz ) ); + array[ index + 1 ] = ( xy + wz ); + array[ index + 2 ] = ( xz - wy ); + array[ index + 3 ] = 0; + + array[ index + 4 ] = ( xy - wz ); + array[ index + 5 ] = ( 1 - ( xx + zz ) ); + array[ index + 6 ] = ( yz + wx ); + array[ index + 7 ] = 0; + + array[ index + 8 ] = ( xz + wy ); + array[ index + 9 ] = ( yz - wx ); + array[ index + 10 ] = ( 1 - ( xx + yy ) ); + array[ index + 11 ] = 0; + + array[ index + 12 ] = position.x(); + array[ index + 13 ] = position.y(); + array[ index + 14 ] = position.z(); + array[ index + 15 ] = 1; + +} + +export { AmmoPhysics }; diff --git a/examples/physics_ammo_break.html b/examples/physics_ammo_break.html new file mode 100644 index 00000000000000..f50f4a0283abd1 --- /dev/null +++ b/examples/physics_ammo_break.html @@ -0,0 +1,606 @@ + + + Convex object breaking example + + + + + + + + + + + +
Physics threejs demo with convex objects breaking in real time
Press mouse to throw balls and move the camera.
+
+ + + + + + + + + diff --git a/examples/physics_ammo_cloth.html b/examples/physics_ammo_cloth.html new file mode 100644 index 00000000000000..3125769f9cb725 --- /dev/null +++ b/examples/physics_ammo_cloth.html @@ -0,0 +1,473 @@ + + + Ammo.js softbody cloth demo + + + + + + + + + + +
Ammo.js physics soft body cloth demo
Press Q or A to move the arm.
+
+ + + + + + + + + diff --git a/examples/physics_ammo_instancing.html b/examples/physics_ammo_instancing.html new file mode 100644 index 00000000000000..8fdf5d16adc6a9 --- /dev/null +++ b/examples/physics_ammo_instancing.html @@ -0,0 +1,175 @@ + + + + three.js physics - ammo.js instancing + + + + + + + + + + +
+ three.js physics - ammo.js instancing +
+ + + + + + diff --git a/examples/physics_ammo_rope.html b/examples/physics_ammo_rope.html new file mode 100644 index 00000000000000..ab4f2a21eb4913 --- /dev/null +++ b/examples/physics_ammo_rope.html @@ -0,0 +1,494 @@ + + + Amjs softbody rope demo + + + + + + + + + + +
Ammo.js physics soft body rope demo
Press Q or A to move the arm.
+
+ + + + + + + + + diff --git a/examples/physics_ammo_terrain.html b/examples/physics_ammo_terrain.html new file mode 100644 index 00000000000000..677d2cd9f0c04f --- /dev/null +++ b/examples/physics_ammo_terrain.html @@ -0,0 +1,452 @@ + + + + Ammo.js terrain heightfield demo + + + + + + + + + + +
+
Ammo.js physics terrain heightfield demo
+ + + + + + + + + diff --git a/examples/physics_ammo_volume.html b/examples/physics_ammo_volume.html new file mode 100644 index 00000000000000..e14c6b15d773d9 --- /dev/null +++ b/examples/physics_ammo_volume.html @@ -0,0 +1,515 @@ + + + Ammo.js softbody volume demo + + + + + + + + + + +
+ Ammo.js physics soft body volume demo
+ Click to throw a ball +
+
+ + + + + + + + + diff --git a/examples/screenshots/physics_ammo_break.jpg b/examples/screenshots/physics_ammo_break.jpg new file mode 100644 index 00000000000000..6ac9575f5fbc11 Binary files /dev/null and b/examples/screenshots/physics_ammo_break.jpg differ diff --git a/examples/screenshots/physics_ammo_cloth.jpg b/examples/screenshots/physics_ammo_cloth.jpg new file mode 100644 index 00000000000000..b083eb4ac8116d Binary files /dev/null and b/examples/screenshots/physics_ammo_cloth.jpg differ diff --git a/examples/screenshots/physics_ammo_instancing.jpg b/examples/screenshots/physics_ammo_instancing.jpg new file mode 100644 index 00000000000000..14c92bc3923cf1 Binary files /dev/null and b/examples/screenshots/physics_ammo_instancing.jpg differ diff --git a/examples/screenshots/physics_ammo_rope.jpg b/examples/screenshots/physics_ammo_rope.jpg new file mode 100644 index 00000000000000..806a29a149768e Binary files /dev/null and b/examples/screenshots/physics_ammo_rope.jpg differ diff --git a/examples/screenshots/physics_ammo_terrain.jpg b/examples/screenshots/physics_ammo_terrain.jpg new file mode 100644 index 00000000000000..892922b2399ab1 Binary files /dev/null and b/examples/screenshots/physics_ammo_terrain.jpg differ diff --git a/examples/screenshots/physics_ammo_volume.jpg b/examples/screenshots/physics_ammo_volume.jpg new file mode 100644 index 00000000000000..45a42ed55ec0a2 Binary files /dev/null and b/examples/screenshots/physics_ammo_volume.jpg differ diff --git a/examples/tags.json b/examples/tags.json index 69befd8bba43a3..ce2b7ab331430e 100644 --- a/examples/tags.json +++ b/examples/tags.json @@ -7,6 +7,12 @@ "misc_controls_transform": [ "scale", "rotate", "translate" ], "misc_exporter_gcode": [ "community" ], "misc_raycaster_helper": [ "community" ], + "physics_ammo_break": [ "community" ], + "physics_ammo_cloth": [ "integration", "community" ], + "physics_ammo_instancing": [ "community" ], + "physics_ammo_rope": [ "community" ], + "physics_ammo_terrain": [ "community" ], + "physics_ammo_volume": [ "community" ], "physics_jolt_instancing": [ "community" ], "physics_rapier_instancing": [ "community" ], "physics_rapier_basic": [ "community" ], diff --git a/manual/en/physics.html b/manual/en/physics.html index 49a933efa0a0b2..ec85322ec00932 100644 --- a/manual/en/physics.html +++ b/manual/en/physics.html @@ -71,6 +71,7 @@

1. Using Three.js Physics Addons

@@ -84,6 +85,7 @@

Examples

@@ -132,6 +134,17 @@

3. Importing WASM-based Engines

to handle the WASM memory management and interaction with the physics API directly.

+

+ Examples +

+ +

Projects

diff --git a/manual/zh/physics.html b/manual/zh/physics.html index 885372dc817889..a9a4e53eeb1bff 100644 --- a/manual/zh/physics.html +++ b/manual/zh/physics.html @@ -70,6 +70,7 @@

1. 使用 three.js 物理插件

@@ -82,6 +83,7 @@

示例

@@ -129,6 +131,17 @@

3. 引入基于 WASM 的引擎

需要处理 WASM 内存管理及与物理 API 的直接交互。

+

+ 示例 +

+ +

项目

diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js index 211bce7a63ffe2..78a3056d6c6f08 100644 --- a/src/nodes/lighting/ShadowNode.js +++ b/src/nodes/lighting/ShadowNode.js @@ -48,7 +48,7 @@ export const getShadowRenderObjectFunction = ( renderer, shadow, shadowType, use if ( renderObjectFunction === undefined || ( renderObjectFunction.shadowType !== shadowType || renderObjectFunction.useVelocity !== useVelocity ) ) { - renderObjectFunction = ( object, scene, _camera, geometry, material, group, ...params ) => { + renderObjectFunction = ( object, scene, _camera, geometry, material, group, lightsNode, clippingContext, passId ) => { if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) { @@ -60,7 +60,7 @@ export const getShadowRenderObjectFunction = ( renderer, shadow, shadowType, use object.onBeforeShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); - renderer.renderObject( object, scene, _camera, geometry, material, group, ...params ); + renderer.renderObject( object, scene, _camera, geometry, material, group, lightsNode, clippingContext, passId ); object.onAfterShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); diff --git a/src/renderers/common/nodes/NodeManager.js b/src/renderers/common/nodes/NodeManager.js index 64322c444d7e45..f6d4f0b54e848b 100644 --- a/src/renderers/common/nodes/NodeManager.js +++ b/src/renderers/common/nodes/NodeManager.js @@ -3,7 +3,7 @@ import ChainMap from '../ChainMap.js'; import NodeBuilderState from './NodeBuilderState.js'; import NodeMaterial from '../../../materials/nodes/NodeMaterial.js'; import { cubeMapNode } from '../../../nodes/utils/CubeMapNode.js'; -import { NodeFrame, StackTrace } from '../../../nodes/Nodes.js'; +import { NodeFrame, NodeUpdateType, StackTrace } from '../../../nodes/Nodes.js'; import { renderGroup, cubeTexture, texture, fog, rangeFogFactor, densityFogFactor, reference, pmremTexture, screenUV, uniform } from '../../../nodes/TSL.js'; import { builtin } from '../../../nodes/accessors/BuiltinNode.js'; @@ -115,6 +115,12 @@ class NodeManager extends DataMap { const groupNode = nodeUniformsGroup.groupNode; + // groups that are updated per object always require an update so no further checks are needed + + if ( groupNode.updateType === NodeUpdateType.OBJECT ) return true; + + // check for update + _chainKeys[ 0 ] = groupNode; _chainKeys[ 1 ] = nodeUniformsGroup; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 777288f9102f33..1c14545e4e3ead 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -1578,6 +1578,7 @@ class WebGPUBackend extends Backend { groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( _commandEncoderDescriptor ); groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( _computePassDescriptor ); + groupGPU.currentPipeline = null; _commandEncoderDescriptor.reset(); _computePassDescriptor.reset(); @@ -1599,13 +1600,19 @@ class WebGPUBackend extends Backend { compute( computeGroup, computeNode, bindings, pipeline, dispatchSize = null ) { const computeNodeData = this.get( computeNode ); - const { passEncoderGPU } = this.get( computeGroup ); + const groupGPU = this.get( computeGroup ); + const { passEncoderGPU } = groupGPU; // pipeline const pipelineGPU = this.get( pipeline ).pipeline; - this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); + if ( groupGPU.currentPipeline !== pipelineGPU ) { + + passEncoderGPU.setPipeline( pipelineGPU ); + groupGPU.currentPipeline = pipelineGPU; + + } // bind groups @@ -1727,17 +1734,26 @@ class WebGPUBackend extends Backend { const hasIndex = ( index !== null ); // pipeline - this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); - currentSets.pipeline = pipelineGPU; + + if ( currentSets.pipeline !== pipelineGPU ) { + + passEncoderGPU.setPipeline( pipelineGPU ); + currentSets.pipeline = pipelineGPU; + + } // bind groups + const currentBindingGroups = currentSets.bindingGroups; + for ( let i = 0, l = bindings.length; i < l; i ++ ) { const bindGroup = bindings[ i ]; - const bindingsData = this.get( bindGroup ); + if ( currentBindingGroups[ i ] !== bindGroup.id ) { + const bindingsData = this.get( bindGroup ); + passEncoderGPU.setBindGroup( i, bindingsData.group ); currentBindingGroups[ i ] = bindGroup.id; diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js index 4ed6702b0a176c..851059692556d1 100644 --- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js @@ -48,35 +48,6 @@ class WebGPUPipelineUtils { */ this.backend = backend; - /** - * A Weak Map that tracks the active pipeline for render or compute passes. - * - * @private - * @type {WeakMap<(GPURenderPassEncoder|GPUComputePassEncoder),(GPURenderPipeline|GPUComputePipeline)>} - */ - this._activePipelines = new WeakMap(); - - } - - /** - * Sets the given pipeline for the given pass. The method makes sure to only set the - * pipeline when necessary. - * - * @param {(GPURenderPassEncoder|GPUComputePassEncoder)} pass - The pass encoder. - * @param {(GPURenderPipeline|GPUComputePipeline)} pipeline - The pipeline. - */ - setPipeline( pass, pipeline ) { - - const currentPipeline = this._activePipelines.get( pass ); - - if ( currentPipeline !== pipeline ) { - - pass.setPipeline( pipeline ); - - this._activePipelines.set( pass, pipeline ); - - } - } /** @@ -314,10 +285,11 @@ class WebGPUPipelineUtils { try { let asyncError = null; + let pipelinePromise = null; try { - pipelineData.pipeline = await device.createRenderPipelineAsync( _renderPipelineDescriptor ); + pipelinePromise = device.createRenderPipelineAsync( _renderPipelineDescriptor ); } catch ( err ) { @@ -325,6 +297,22 @@ class WebGPUPipelineUtils { } + _renderPipelineDescriptor.reset(); + + if ( pipelinePromise !== null ) { + + try { + + pipelineData.pipeline = await pipelinePromise; + + } catch ( err ) { + + asyncError = err; + + } + + } + const errorScope = await device.popErrorScope(); if ( errorScope !== null || asyncError !== null ) { @@ -340,8 +328,6 @@ class WebGPUPipelineUtils { } finally { - _renderPipelineDescriptor.reset(); - // Guarantee resolution so `compileAsync`'s Promise.all cannot hang on an // unexpected throw from any await above. resolve(); diff --git a/src/renderers/webgpu/utils/WebGPUUtils.js b/src/renderers/webgpu/utils/WebGPUUtils.js index 6c80c8b96cf033..9fbc2af1ad975d 100644 --- a/src/renderers/webgpu/utils/WebGPUUtils.js +++ b/src/renderers/webgpu/utils/WebGPUUtils.js @@ -24,6 +24,15 @@ class WebGPUUtils { */ this.backend = backend; + /** + * Caches the preferred canvas format. + * + * @private + * @type {?string} + * @default null + */ + this._preferredCanvasFormat = null; + } /** @@ -248,7 +257,13 @@ class WebGPUUtils { if ( bufferType === undefined ) { - return navigator.gpu.getPreferredCanvasFormat(); + if ( this._preferredCanvasFormat === null ) { + + this._preferredCanvasFormat = navigator.gpu.getPreferredCanvasFormat(); + + } + + return this._preferredCanvasFormat; } else if ( bufferType === UnsignedByteType ) {