286 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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;
 |