Explorar o código

WebGPURenderer: Allow manual mipmap creation with StorageTexture (#31983)

* WebGPURenderer: Allow manual mipmap creation with StorageTexture and compute

* CI

* Refactor to mipmapsAutoUpdate
Renaud Rohlinger hai 3 meses
pai
achega
e1809743e6

+ 1 - 0
src/Three.TSL.js

@@ -549,6 +549,7 @@ export const textureBicubicLevel = TSL.textureBicubicLevel;
 export const textureCubeUV = TSL.textureCubeUV;
 export const textureLoad = TSL.textureLoad;
 export const textureSize = TSL.textureSize;
+export const textureLevel = TSL.textureLevel;
 export const textureStore = TSL.textureStore;
 export const thickness = TSL.thickness;
 export const time = TSL.time;

+ 22 - 0
src/nodes/accessors/StorageTextureNode.js

@@ -60,6 +60,14 @@ class StorageTextureNode extends TextureNode {
 		 */
 		this.storeNode = storeNode;
 
+		/**
+		 * The mip level to write to for storage textures.
+		 *
+		 * @type {number}
+		 * @default 0
+		 */
+		this.mipLevel = 0;
+
 		/**
 		 * This flag can be used for type testing.
 		 *
@@ -115,6 +123,19 @@ class StorageTextureNode extends TextureNode {
 
 	}
 
+	/**
+	 * Sets the mip level to write to.
+	 *
+	 * @param {number} level - The mip level.
+	 * @return {StorageTextureNode} A reference to this node.
+	 */
+	setMipLevel( level ) {
+
+		this.mipLevel = level;
+		return this;
+
+	}
+
 	/**
 	 * Generates the code snippet of the storage node. If no `storeNode`
 	 * is defined, the texture node is generated as normal texture.
@@ -200,6 +221,7 @@ class StorageTextureNode extends TextureNode {
 
 		const newNode = super.clone();
 		newNode.storeNode = this.storeNode;
+		newNode.mipLevel = this.mipLevel;
 		return newNode;
 
 	}

+ 1 - 1
src/nodes/accessors/TextureNode.js

@@ -904,7 +904,7 @@ export const uniformTexture = ( value = EmptyTexture ) => texture( value );
  */
 export const textureLoad = ( ...params ) => texture( ...params ).setSampler( false );
 
-//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level );
+export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level );
 
 /**
  * Converts a texture or texture node to a sampler.

+ 1 - 1
src/renderers/common/Bindings.js

@@ -324,7 +324,7 @@ class Bindings extends DataMap {
 
 				}
 
-				if ( texture.isStorageTexture === true ) {
+				if ( texture.isStorageTexture === true && texture.mipmapsAutoUpdate === true ) {
 
 					const textureData = this.get( texture );
 

+ 8 - 0
src/renderers/common/SampledTexture.js

@@ -35,6 +35,14 @@ class SampledTexture extends Sampler {
 		 */
 		this.store = false;
 
+		/**
+		 * The mip level to bind for storage textures.
+		 *
+		 * @type {number}
+		 * @default 0
+		 */
+		this.mipLevel = 0;
+
 		/**
 		 * This flag can be used for type testing.
 		 *

+ 9 - 1
src/renderers/common/StorageTexture.js

@@ -52,8 +52,16 @@ class StorageTexture extends Texture {
 		 */
 		this.isStorageTexture = true;
 
-	}
+		/**
+		 * When `true`, mipmaps will be auto-generated after compute writes.
+		 * When `false`, mipmaps must be written manually via compute shaders.
+		 *
+		 * @type {boolean}
+		 * @default true
+		 */
+		this.mipmapsAutoUpdate = true;
 
+	}
 	/**
 	 * Sets the size of the storage texture.
 	 *

+ 7 - 1
src/renderers/common/Textures.js

@@ -302,7 +302,13 @@ class Textures extends DataMap {
 
 					if ( texture.source.dataReady === true ) backend.updateTexture( texture, options );
 
-					if ( options.needsMipmaps && texture.mipmaps.length === 0 ) backend.generateMipmaps( texture );
+					const skipAutoGeneration = texture.isStorageTexture === true && texture.mipmapsAutoUpdate === false;
+
+					if ( options.needsMipmaps && texture.mipmaps.length === 0 && ! skipAutoGeneration ) {
+
+						backend.generateMipmaps( texture );
+
+					}
 
 					if ( texture.onUpdate ) texture.onUpdate( texture );
 

+ 3 - 1
src/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -951,7 +951,9 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 				}
 
-				texture.store = node.isStorageTextureNode === true;
+				// Only mark as storage binding when actually writing (storeNode is set)
+				texture.store = node.isStorageTextureNode === true && node.storeNode !== null;
+				texture.mipLevel = texture.store ? node.mipLevel : 0;
 				texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 				if ( this.isUnfilterable( node.value ) === false && texture.store === false ) {

+ 3 - 2
src/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -415,6 +415,7 @@ class WebGPUBindingUtils {
 				} else {
 
 					const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount;
+					const baseMipLevel = binding.store ? binding.mipLevel : 0;
 					let propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }`;
 
 					if ( textureData.texture.depthOrArrayLayers > 1 ) {
@@ -423,7 +424,7 @@ class WebGPUBindingUtils {
 
 					}
 
-					propertyName += `-${ mipLevelCount }`;
+					propertyName += `-${ mipLevelCount }-${ baseMipLevel }`;
 
 					resourceGPU = textureData[ propertyName ];
 
@@ -451,7 +452,7 @@ class WebGPUBindingUtils {
 
 						}
 
-						resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount } );
+						resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount, baseMipLevel } );
 
 					}
 

粤ICP备19079148号