diff --git a/examples/webgpu_compute_birds.html b/examples/webgpu_compute_birds.html
index a5f64a6b1d434a..075f910ac8957c 100644
--- a/examples/webgpu_compute_birds.html
+++ b/examples/webgpu_compute_birds.html
@@ -158,7 +158,7 @@
//
- renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: false } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: false, requiredLimits: { maxStorageBuffersInVertexStage: 3 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( render );
diff --git a/examples/webgpu_compute_cloth.html b/examples/webgpu_compute_cloth.html
index 908a631e472951..47d4f51a58eba0 100644
--- a/examples/webgpu_compute_cloth.html
+++ b/examples/webgpu_compute_cloth.html
@@ -90,7 +90,7 @@
async function init() {
- renderer = new THREE.WebGPURenderer( { antialias: true } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, requiredLimits: { maxStorageBuffersInVertexStage: 1 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.NeutralToneMapping;
diff --git a/examples/webgpu_compute_particles_fluid.html b/examples/webgpu_compute_particles_fluid.html
index 7d26c79d5347d5..ee02b41800c7be 100644
--- a/examples/webgpu_compute_particles_fluid.html
+++ b/examples/webgpu_compute_particles_fluid.html
@@ -77,7 +77,7 @@
async function init() {
- renderer = new THREE.WebGPURenderer( { antialias: true } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, requiredLimits: { maxStorageBuffersInVertexStage: 1 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
diff --git a/examples/webgpu_compute_points.html b/examples/webgpu_compute_points.html
index ed0de9049165e1..ab87413135bd17 100644
--- a/examples/webgpu_compute_points.html
+++ b/examples/webgpu_compute_points.html
@@ -124,7 +124,7 @@
mesh.count = particlesCount;
scene.add( mesh );
- renderer = new THREE.WebGPURenderer( { antialias: true } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, requiredLimits: { maxStorageBuffersInVertexStage: 1 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
diff --git a/examples/webgpu_compute_water.html b/examples/webgpu_compute_water.html
index 60848b49fe4e76..49a863564aefb4 100644
--- a/examples/webgpu_compute_water.html
+++ b/examples/webgpu_compute_water.html
@@ -457,7 +457,7 @@
const duckMesh = new THREE.InstancedMesh( duckModel.geometry, duckModel.material, NUM_DUCKS );
scene.add( duckMesh );
- renderer = new THREE.WebGPURenderer( { antialias: true } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, requiredLimits: { maxStorageBuffersInVertexStage: 2 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
diff --git a/examples/webgpu_mrt.html b/examples/webgpu_mrt.html
index 3dfdc7cd40a61c..9df0536b7a0c29 100644
--- a/examples/webgpu_mrt.html
+++ b/examples/webgpu_mrt.html
@@ -82,7 +82,7 @@
// renderer
- renderer = new THREE.WebGPURenderer( { antialias: true } );
+ renderer = new THREE.WebGPURenderer( { antialias: true, requiredLimits: { maxColorAttachments: 5 } } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( render );
diff --git a/package.json b/package.json
index a5e769d34152eb..3335e9658eed5f 100644
--- a/package.json
+++ b/package.json
@@ -54,12 +54,11 @@
"lint-addons": "eslint examples/jsm",
"lint-examples": "eslint examples",
"lint-editor": "eslint editor",
- "lint-playground": "eslint playground",
"lint-manual": "eslint manual",
"lint-test": "eslint test",
"lint-utils": "eslint utils",
"lint": "npm run lint-core",
- "lint-fix": "npm run lint-core -- --fix && npm run lint-addons -- --fix && npm run lint-examples -- --fix && npm run lint-docs -- --fix && npm run lint-editor -- --fix && npm run lint-playground -- --fix && npm run lint-manual -- --fix && npm run lint-test -- --fix && npm run lint-utils -- --fix",
+ "lint-fix": "npm run lint-core -- --fix && npm run lint-addons -- --fix && npm run lint-examples -- --fix && npm run lint-editor -- --fix && npm run lint-manual -- --fix && npm run lint-test -- --fix && npm run lint-utils -- --fix",
"test-unit": "qunit test/unit/three.source.unit.js",
"test-unit-addons": "qunit test/unit/three.addons.unit.js",
"test-e2e": "node test/e2e/puppeteer.js",
diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js
index ee2c64bbb7b276..48425d7863316b 100644
--- a/src/nodes/core/NodeBuilder.js
+++ b/src/nodes/core/NodeBuilder.js
@@ -584,7 +584,7 @@ class NodeBuilder {
bindingsArray.push( binding );
- sharedGroup = sharedGroup && binding.groupNode.shared !== true;
+ sharedGroup = sharedGroup && binding.groupNode.shared;
}
@@ -598,7 +598,7 @@ class NodeBuilder {
if ( bindGroup === undefined ) {
- bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );
+ bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group );
bindGroupsCache.set( bindingsArray, bindGroup );
@@ -606,7 +606,7 @@ class NodeBuilder {
} else {
- bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray );
+ bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group );
}
diff --git a/src/renderers/common/BindGroup.js b/src/renderers/common/BindGroup.js
index ea44aaf4962fae..43afc93a7855a7 100644
--- a/src/renderers/common/BindGroup.js
+++ b/src/renderers/common/BindGroup.js
@@ -15,9 +15,8 @@ class BindGroup {
* @param {string} name - The bind group's name.
* @param {Array} bindings - An array of bindings.
* @param {number} index - The group index.
- * @param {Array} bindingsReference - An array of reference bindings.
*/
- constructor( name = '', bindings = [], index = 0, bindingsReference = [] ) {
+ constructor( name = '', bindings = [], index = 0 ) {
/**
* The bind group's name.
@@ -40,13 +39,6 @@ class BindGroup {
*/
this.index = index;
- /**
- * An array of reference bindings.
- *
- * @type {Array}
- */
- this.bindingsReference = bindingsReference;
-
/**
* The group's ID.
*
diff --git a/src/renderers/common/nodes/NodeBuilderState.js b/src/renderers/common/nodes/NodeBuilderState.js
index 5e9b47832e43e7..4b0dfd3b800412 100644
--- a/src/renderers/common/nodes/NodeBuilderState.js
+++ b/src/renderers/common/nodes/NodeBuilderState.js
@@ -126,7 +126,7 @@ class NodeBuilderState {
if ( shared !== true ) {
- const bindingsGroup = new BindGroup( instanceGroup.name, [], instanceGroup.index, instanceGroup.bindingsReference );
+ const bindingsGroup = new BindGroup( instanceGroup.name, [], instanceGroup.index );
bindings.push( bindingsGroup );
for ( const instanceBinding of instanceGroup.bindings ) {
diff --git a/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js b/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js
index 0390649f2fa075..ebcf069b803a52 100644
--- a/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js
@@ -1,5 +1,5 @@
import DataMap from '../../common/DataMap.js';
-import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
+import { GPUFilterMode, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
/**
* A WebGPU backend utility module used by {@link WebGPUTextureUtils}.
@@ -24,65 +24,88 @@ class WebGPUTexturePassUtils extends DataMap {
*/
this.device = device;
- const mipmapVertexSource = `
+ const mipmapSource = `
struct VarysStruct {
- @builtin( position ) Position: vec4,
- @location( 0 ) vTex : vec2
+ @builtin( position ) Position: vec4f,
+ @location( 0 ) vTex : vec2f,
+ @location( 1 ) @interpolate(flat, either) vBaseArrayLayer: u32,
};
+@group( 0 ) @binding ( 2 )
+var flipY: u32;
+
@vertex
-fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {
+fn mainVS(
+ @builtin( vertex_index ) vertexIndex : u32,
+ @builtin( instance_index ) instanceIndex : u32 ) -> VarysStruct {
var Varys : VarysStruct;
- var pos = array< vec2, 4 >(
- vec2( -1.0, 1.0 ),
- vec2( 1.0, 1.0 ),
- vec2( -1.0, -1.0 ),
- vec2( 1.0, -1.0 )
- );
-
- var tex = array< vec2, 4 >(
- vec2( 0.0, 0.0 ),
- vec2( 1.0, 0.0 ),
- vec2( 0.0, 1.0 ),
- vec2( 1.0, 1.0 )
+ var pos = array(
+ vec2f( -1, -1 ),
+ vec2f( -1, 3 ),
+ vec2f( 3, -1 ),
);
- Varys.vTex = tex[ vertexIndex ];
- Varys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 );
+ let p = pos[ vertexIndex ];
+ let mult = select( vec2f( 0.5, -0.5 ), vec2f( 0.5, 0.5 ), flipY != 0 );
+ Varys.vTex = p * vec2f( 0.5, -0.5 ) + vec2f( 0.5 );
+ Varys.Position = vec4f( p, 0, 1 );
+ Varys.vBaseArrayLayer = instanceIndex;
return Varys;
}
-`;
- const mipmapFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;
@group( 0 ) @binding( 1 )
-var img : texture_2d;
+var img2d : texture_2d;
@fragment
-fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
+fn main_2d( Varys: VarysStruct ) -> @location( 0 ) vec4 {
- return textureSample( img, imgSampler, vTex );
+ return textureSample( img2d, imgSampler, Varys.vTex );
}
-`;
- const flipYFragmentSource = `
-@group( 0 ) @binding( 0 )
-var imgSampler : sampler;
+@group( 0 ) @binding( 1 )
+var img2dArray : texture_2d_array;
+
+@fragment
+fn main_2d_array( Varys: VarysStruct ) -> @location( 0 ) vec4 {
+
+ return textureSample( img2dArray, imgSampler, Varys.vTex, Varys.vBaseArrayLayer );
+
+}
+
+const faceMat = array(
+ mat3x3f( 0, 0, -2, 0, -2, 0, 1, 1, 1 ), // pos-x
+ mat3x3f( 0, 0, 2, 0, -2, 0, -1, 1, -1 ), // neg-x
+ mat3x3f( 2, 0, 0, 0, 0, 2, -1, 1, -1 ), // pos-y
+ mat3x3f( 2, 0, 0, 0, 0, -2, -1, -1, 1 ), // neg-y
+ mat3x3f( 2, 0, 0, 0, -2, 0, -1, 1, 1 ), // pos-z
+ mat3x3f( -2, 0, 0, 0, -2, 0, 1, 1, -1 ), // neg-z
+);
@group( 0 ) @binding( 1 )
-var img : texture_2d;
+var imgCube : texture_cube;
@fragment
-fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
+fn main_cube( Varys: VarysStruct ) -> @location( 0 ) vec4 {
- return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
+ return textureSample( imgCube, imgSampler, faceMat[ Varys.vBaseArrayLayer ] * vec3f( fract( Varys.vTex ), 1 ) );
+
+}
+
+@group( 0 ) @binding( 1 )
+var imgCubeArray : texture_cube_array;
+
+@fragment
+fn main_cube_array( Varys: VarysStruct ) -> @location( 0 ) vec4 {
+
+ return textureSample( imgCubeArray, imgSampler, faceMat[ Varys.vBaseArrayLayer % 6 ] * vec3f( fract( Varys.vTex ), 1 ), Varys.vBaseArrayLayer );
}
`;
@@ -102,49 +125,40 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
/**
- * A cache for GPU render pipelines used for copy/transfer passes.
- * Every texture format requires a unique pipeline.
- *
- * @type {Object}
- */
- this.transferPipelines = {};
-
- /**
- * A cache for GPU render pipelines used for flipY passes.
- * Every texture format requires a unique pipeline.
- *
- * @type {Object}
+ * flip uniform buffer
+ * @type {GPUBuffer}
*/
- this.flipYPipelines = {};
+ this.flipUniformBuffer = device.createBuffer( {
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
+ } );
+ device.queue.writeBuffer( this.flipUniformBuffer, 0, new Uint32Array( [ 1 ] ) );
/**
- * The mipmap vertex shader module.
- *
- * @type {GPUShaderModule}
+ * no flip uniform buffer
+ * @type {GPUBuffer}
*/
- this.mipmapVertexShaderModule = device.createShaderModule( {
- label: 'mipmapVertex',
- code: mipmapVertexSource
+ this.noFlipUniformBuffer = device.createBuffer( {
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM
} );
/**
- * The mipmap fragment shader module.
+ * A cache for GPU render pipelines used for copy/transfer passes.
+ * Every texture format and textureBindingViewDimension combo requires a unique pipeline.
*
- * @type {GPUShaderModule}
+ * @type {Object}
*/
- this.mipmapFragmentShaderModule = device.createShaderModule( {
- label: 'mipmapFragment',
- code: mipmapFragmentSource
- } );
+ this.transferPipelines = {};
/**
- * The flipY fragment shader module.
+ * The mipmap shader module.
*
* @type {GPUShaderModule}
*/
- this.flipYFragmentShaderModule = device.createShaderModule( {
- label: 'flipYFragment',
- code: flipYFragmentSource
+ this.mipmapShaderModule = device.createShaderModule( {
+ label: 'mipmap',
+ code: mipmapSource
} );
}
@@ -154,29 +168,27 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
* requires a unique render pipeline for each texture format.
*
* @param {string} format - The GPU texture format
+ * @param {string?} textureBindingViewDimension - The GPU texture binding view dimension
* @return {GPURenderPipeline} The GPU render pipeline.
*/
- getTransferPipeline( format ) {
+ getTransferPipeline( format, textureBindingViewDimension ) {
- let pipeline = this.transferPipelines[ format ];
+ textureBindingViewDimension = textureBindingViewDimension || '2d-array';
+ const key = `${ format }-${ textureBindingViewDimension }`;
+ let pipeline = this.transferPipelines[ key ];
if ( pipeline === undefined ) {
pipeline = this.device.createRenderPipeline( {
- label: `mipmap-${ format }`,
+ label: `mipmap-${ format }-${ textureBindingViewDimension }`,
vertex: {
- module: this.mipmapVertexShaderModule,
- entryPoint: 'main'
+ module: this.mipmapShaderModule,
},
fragment: {
- module: this.mipmapFragmentShaderModule,
- entryPoint: 'main',
+ module: this.mipmapShaderModule,
+ entryPoint: `main_${ textureBindingViewDimension.replace( '-', '_' ) }`,
targets: [ { format } ]
},
- primitive: {
- topology: GPUPrimitiveTopology.TriangleStrip,
- stripIndexFormat: GPUIndexFormat.Uint32
- },
layout: 'auto'
} );
@@ -188,45 +200,6 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
}
- /**
- * Returns a render pipeline for the flipY render pass. The pass
- * requires a unique render pipeline for each texture format.
- *
- * @param {string} format - The GPU texture format
- * @return {GPURenderPipeline} The GPU render pipeline.
- */
- getFlipYPipeline( format ) {
-
- let pipeline = this.flipYPipelines[ format ];
-
- if ( pipeline === undefined ) {
-
- pipeline = this.device.createRenderPipeline( {
- label: `flipY-${ format }`,
- 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;
-
- }
-
/**
* Flip the contents of the given GPU texture along its vertical axis.
*
@@ -239,32 +212,18 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
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 },
+ size: { width, height },
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 copyTransferPipeline = this.getTransferPipeline( format, textureGPU.textureBindingViewDimension );
+ const flipTransferPipeline = this.getTransferPipeline( format, tempTexture.textureBindingViewDimension );
const commandEncoder = this.device.createCommandEncoder( {} );
- const pass = ( pipeline, sourceView, destinationView ) => {
+ const pass = ( pipeline, sourceTexture, sourceArrayLayer, destinationTexture, destinationArrayLayer, flipY ) => {
const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
@@ -275,28 +234,40 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
resource: this.flipYSampler
}, {
binding: 1,
- resource: sourceView
+ resource: sourceTexture.createView( {
+ dimension: sourceTexture.textureBindingViewDimension || '2d-array',
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ } ),
+ }, {
+ binding: 2,
+ resource: { buffer: flipY ? this.flipUniformBuffer : this.noFlipUniformBuffer }
} ]
} );
const passEncoder = commandEncoder.beginRenderPass( {
colorAttachments: [ {
- view: destinationView,
+ view: destinationTexture.createView( {
+ dimension: '2d',
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ baseArrayLayer: destinationArrayLayer,
+ arrayLayerCount: 1,
+ } ),
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.draw( 3, 1, 0, sourceArrayLayer );
passEncoder.end();
};
- pass( transferPipeline, srcView, dstView );
- pass( flipYPipeline, dstView, srcView );
+ pass( copyTransferPipeline, textureGPU, baseArrayLayer, tempTexture, 0, false );
+ pass( flipTransferPipeline, tempTexture, 0, textureGPU, baseArrayLayer, true );
this.device.queue.submit( [ commandEncoder.finish() ] );
@@ -308,21 +279,13 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
* Generates mipmaps for the given GPU texture.
*
* @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureGPUDescriptor - The texture descriptor.
- * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
* @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps.
*/
- generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0, encoder = null ) {
+ generateMipmaps( textureGPU, encoder = null ) {
const textureData = this.get( textureGPU );
- if ( textureData.layers === undefined ) {
-
- textureData.layers = [];
-
- }
-
- const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer );
+ const passes = textureData.layers || this._mipmapCreateBundles( textureGPU );
const commandEncoder = encoder || this.device.createCommandEncoder( { label: 'mipmapEncoder' } );
@@ -330,7 +293,7 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
if ( encoder === null ) this.device.queue.submit( [ commandEncoder.finish() ] );
- textureData.layers[ baseArrayLayer ] = passes;
+ textureData.layers = passes;
}
@@ -339,68 +302,67 @@ fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {
* are managed as render bundles to improve performance.
*
* @param {GPUTexture} textureGPU - The GPU texture object.
- * @param {Object} textureGPUDescriptor - The texture descriptor.
- * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view.
* @return {Array