Browse Source

WebGPURenderer: GPU FlipY (#26818)

* GPU FlipY

* use tempTexture.destroy()

* fix name

* Rename to `WebGPUTexturePassUtils` and `_getPassUtils()`
sunag 1 year ago
parent
commit
e2f5911729

+ 0 - 163
examples/jsm/renderers/webgpu/utils/WebGPUTextureMipmapUtils.js

@@ -1,163 +0,0 @@
-import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
-
-class WebGPUTextureMipmapUtils {
-
-	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 );
-
-}
-`;
-
-		this.sampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );
-
-		// We'll need a new pipeline for every texture format used.
-		this.pipelines = {};
-
-		this.mipmapVertexShaderModule = device.createShaderModule( {
-			label: 'mipmapVertex',
-			code: mipmapVertexSource
-		} );
-
-		this.mipmapFragmentShaderModule = device.createShaderModule( {
-			label: 'mipmapFragment',
-			code: mipmapFragmentSource
-		} );
-
-	}
-
-	getMipmapPipeline( format ) {
-
-		let pipeline = this.pipelines[ format ];
-
-		if ( pipeline === undefined ) {
-
-			pipeline = this.device.createRenderPipeline( {
-				vertex: {
-					module: this.mipmapVertexShaderModule,
-					entryPoint: 'main'
-				},
-				fragment: {
-					module: this.mipmapFragmentShaderModule,
-					entryPoint: 'main',
-					targets: [ { format } ]
-				},
-				primitive: {
-					topology: GPUPrimitiveTopology.TriangleStrip,
-					stripIndexFormat: GPUIndexFormat.Uint32
-				},
-				layout: 'auto'
-			} );
-
-			this.pipelines[ format ] = pipeline;
-
-		}
-
-		return pipeline;
-
-	}
-
-	generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {
-
-		const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format );
-
-		const commandEncoder = this.device.createCommandEncoder( {} );
-		const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.
-
-		let srcView = textureGPU.createView( {
-			baseMipLevel: 0,
-			mipLevelCount: 1,
-			dimension: GPUTextureViewDimension.TwoD,
-			baseArrayLayer
-		} );
-
-		for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
-
-			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 ]
-				} ]
-			} );
-
-			const bindGroup = this.device.createBindGroup( {
-				layout: bindGroupLayout,
-				entries: [ {
-					binding: 0,
-					resource: this.sampler
-				}, {
-					binding: 1,
-					resource: srcView
-				} ]
-			} );
-
-			passEncoder.setPipeline( pipeline );
-			passEncoder.setBindGroup( 0, bindGroup );
-			passEncoder.draw( 4, 1, 0, 0 );
-			passEncoder.end();
-
-			srcView = dstView;
-
-		}
-
-		this.device.queue.submit( [ commandEncoder.finish() ] );
-
-	}
-
-}
-
-export default WebGPUTextureMipmapUtils;

+ 285 - 0
examples/jsm/renderers/webgpu/utils/WebGPUTexturePassUtils.js

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

+ 44 - 14
examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -15,7 +15,7 @@ import {
 
 import { CubeReflectionMapping, CubeRefractionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
 
-import WebGPUTextureMipmapUtils from './WebGPUTextureMipmapUtils.js';
+import WebGPUTexturePassUtils from './WebGPUTexturePassUtils.js';
 
 const _compareToWebGPU = {
 	[ NeverCompare ]: 'never',
@@ -28,13 +28,15 @@ const _compareToWebGPU = {
 	[ NotEqualCompare ]: 'not-equal'
 };
 
+const _flipMap = [ 0, 1, 3, 2, 4, 5 ];
+
 class WebGPUTextureUtils {
 
 	constructor( backend ) {
 
 		this.backend = backend;
 
-		this.mipmapUtils = null;
+		this._passUtils = null;
 
 		this.defaultTexture = null;
 		this.defaultCubeTexture = null;
@@ -237,7 +239,7 @@ class WebGPUTextureUtils {
 
 		if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) {
 
-			this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU );
+			this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY );
 
 		} else if ( texture.isCompressedTexture ) {
 
@@ -245,7 +247,7 @@ class WebGPUTextureUtils {
 
 		} else if ( texture.isCubeTexture ) {
 
-			this._copyCubeMapToTexture( options.images, texture, textureData.texture, textureDescriptorGPU );
+			this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY );
 
 		} else if ( texture.isVideoTexture ) {
 
@@ -255,7 +257,7 @@ class WebGPUTextureUtils {
 
 		} else {
 
-			this._copyImageToTexture( options.image, textureData.texture );
+			this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY );
 
 		}
 
@@ -361,19 +363,21 @@ class WebGPUTextureUtils {
 
 	}
 
-	_copyCubeMapToTexture( images, texture, textureGPU, textureDescriptorGPU ) {
+	_copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY ) {
 
 		for ( let i = 0; i < 6; i ++ ) {
 
 			const image = images[ i ];
 
+			const flipIndex = flipY === true ? _flipMap[ i ] : i;
+
 			if ( image.isDataTexture ) {
 
-				this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, i );
+				this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY );
 
 			} else {
 
-				this._copyImageToTexture( image, textureGPU, i );
+				this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY );
 
 			}
 
@@ -381,7 +385,7 @@ class WebGPUTextureUtils {
 
 	}
 
-	_copyImageToTexture( image, textureGPU, originDepth = 0 ) {
+	_copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) {
 
 		const device = this.backend.device;
 
@@ -399,21 +403,41 @@ class WebGPUTextureUtils {
 			}
 		);
 
+		if ( flipY === true ) {
+
+			this._flipY( textureGPU, textureDescriptorGPU, originDepth );
+
+		}
+
 	}
 
-	_generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) {
+	_getPassUtils() {
+
+		let passUtils = this._passUtils;
 
-		if ( this.mipmapUtils === null ) {
+		if ( passUtils === null ) {
 
-			this.mipmapUtils = new WebGPUTextureMipmapUtils( this.backend.device );
+			this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device );
 
 		}
 
-		this.mipmapUtils.generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer );
+		return passUtils;
 
 	}
 
-	_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth = 0 ) {
+	_generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) {
+
+		this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer );
+
+	}
+
+	_flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) {
+
+		this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth );
+
+	}
+
+	_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) {
 
 		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
 		// @TODO: Consider to support valid buffer layouts with other formats like RGB
@@ -442,6 +466,12 @@ class WebGPUTextureUtils {
 				depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1
 			} );
 
+		if ( flipY === true ) {
+
+			this._flipY( textureGPU, textureDescriptorGPU, originDepth );
+
+		}
+
 	}
 
 	_copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) {