Explorar el Código

WebGPUTextures: Add support for mipmap computation.

Mugen87 hace 4 años
padre
commit
9e136bb30e

+ 1 - 1
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -684,7 +684,7 @@ async function initWebGPU( scope ) {
 	scope._properties = new WebGPUProperties();
 	scope._attributes = new WebGPUAttributes( device );
 	scope._geometries = new WebGPUGeometries( scope._attributes, scope._info );
-	scope._textures = new WebGPUTextures( device, scope._properties, scope._info );
+	scope._textures = new WebGPUTextures( device, scope._properties, scope._info, compiler );
 	scope._bindings = new WebGPUBindings( device, scope._info, scope._properties, scope._textures );
 	scope._objects = new WebGPUObjects( scope._geometries, scope._info );
 	scope._renderPipelines = new WebGPURenderPipelines( device, compiler, scope._bindings );

+ 148 - 0
examples/jsm/renderers/webgpu/WebGPUTextureUtils.js

@@ -0,0 +1,148 @@
+// Copyright 2020 Brandon Jones
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import { GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology } from './constants.js';
+
+// ported from https://github.com/toji/web-texture-tool/blob/master/src/webgpu-mipmap-generator.js
+
+class WebGPUTextureUtils {
+
+	constructor( device, glslang ) {
+
+		this.device = device;
+
+		const mipmapVertexSource = `#version 450
+			const vec2 pos[4] = vec2[4](vec2(-1.0f, 1.0f), vec2(1.0f, 1.0f), vec2(-1.0f, -1.0f), vec2(1.0f, -1.0f));
+			const vec2 tex[4] = vec2[4](vec2(0.0f, 0.0f), vec2(1.0f, 0.0f), vec2(0.0f, 1.0f), vec2(1.0f, 1.0f));
+			layout(location = 0) out vec2 vTex;
+			void main() {
+				vTex = tex[gl_VertexIndex];
+				gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
+			}
+		`;
+
+		const mipmapFragmentSource = `#version 450
+			layout(set = 0, binding = 0) uniform sampler imgSampler;
+			layout(set = 0, binding = 1) uniform texture2D img;
+			layout(location = 0) in vec2 vTex;
+			layout(location = 0) out vec4 outColor;
+			void main() {
+				outColor = texture(sampler2D(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( {
+			 code: glslang.compileGLSL( mipmapVertexSource, 'vertex' ),
+		 } );
+		 this.mipmapFragmentShaderModule = device.createShaderModule( {
+			 code: glslang.compileGLSL( mipmapFragmentSource, 'fragment' ),
+		 } );
+
+	}
+
+	getMipmapPipeline( format ) {
+
+		let pipeline = this.pipelines[ format ];
+
+		if ( pipeline === undefined ) {
+
+			pipeline = this.device.createRenderPipeline( {
+				vertexStage: {
+					module: this.mipmapVertexShaderModule,
+					entryPoint: 'main',
+				},
+				fragmentStage: {
+					module: this.mipmapFragmentShaderModule,
+					entryPoint: 'main',
+				},
+				primitiveTopology: GPUPrimitiveTopology.TriangleStrip,
+				vertexState: {
+					indexFormat: GPUIndexFormat.Uint32
+				},
+				colorStates: [ { format } ],
+			} );
+			this.pipelines[ format ] = pipeline;
+
+		}
+
+		return pipeline;
+
+	}
+
+	generateMipmappedTexture( imageBitmap, textureGPU, textureGPUDescriptor ) {
+
+		// mipmaps have to computed manually right now, see https://github.com/gpuweb/gpuweb/issues/386
+
+		const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format );
+
+		const commandEncoder = this.device.createCommandEncoder( {} );
+		// @TODO: Consider making this static.
+		const bindGroupLayout = pipeline.getBindGroupLayout( 0 );
+
+		let srcView = textureGPU.createView( {
+			baseMipLevel: 0,
+			mipLevelCount: 1,
+		} );
+
+		for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {
+
+			const dstView = textureGPU.createView( {
+				baseMipLevel: i,
+				mipLevelCount: 1,
+			} );
+
+			const passEncoder = commandEncoder.beginRenderPass( {
+				colorAttachments: [ {
+					attachment: dstView,
+					loadValue: [ 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.endPass();
+
+			srcView = dstView;
+
+		}
+
+		this.device.defaultQueue.submit( [ commandEncoder.finish() ] );
+
+	}
+
+}
+
+export default WebGPUTextureUtils;

+ 61 - 16
examples/jsm/renderers/webgpu/WebGPUTextures.js

@@ -1,18 +1,21 @@
 import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
-import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';
+import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';
+import WebGPUTextureUtils from './WebGPUTextureUtils.js';
 
 class WebGPUTextures {
 
-	constructor( device, properties, info ) {
+	constructor( device, properties, info, glslang ) {
 
 		this.device = device;
 		this.properties = properties;
 		this.info = info;
+		this.glslang = glslang;
 
 		this.defaultTexture = null;
 		this.defaultSampler = null;
 
 		this.samplerCache = new Map();
+		this.utils = null;
 
 	}
 
@@ -229,6 +232,12 @@ class WebGPUTextures {
 
 	}
 
+	_computeMipLevelCount( width, height ) {
+
+		return Math.floor( Math.log2( Math.max( width, height ) ) ) + 1;
+
+	}
+
 	_convertAddressMode( value ) {
 
 		let addressMode = GPUAddressMode.ClampToEdge;
@@ -288,16 +297,32 @@ class WebGPUTextures {
 		const height = ( image !== undefined ) ? image.height : 1;
 
 		const format = this._convertFormat( texture.type );
+		const needsMipmaps = this._needsMipmaps( texture );
+		const mipLevelCount = ( needsMipmaps === true ) ? this._computeMipLevelCount( width, height ) : undefined;
+
+		let usage = GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST;
+
+		if ( needsMipmaps === true ) {
 
-		const textureGPU = device.createTexture( {
+			usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
+
+		}
+
+		// texture creation
+
+		const textureGPUDescriptor = {
 			size: {
 				width: width,
 				height: height,
 				depth: 1,
 			},
 			format: format,
-			usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST,
-		} );
+			usage: usage,
+			mipLevelCount: mipLevelCount
+		};
+		const textureGPU = device.createTexture( textureGPUDescriptor );
+
+		// transfer texture data
 
 		if ( texture.isDataTexture ) {
 
@@ -317,7 +342,7 @@ class WebGPUTextures {
 
 				createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {
 
-					this._copyImageBitmapToTexture( imageBitmap, textureGPU );
+					this._copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor );
 
 				} );
 
@@ -327,7 +352,7 @@ class WebGPUTextures {
 
 					// assuming ImageBitmap. Directly start copy operation of the contents of ImageBitmap into the destination texture
 
-					this._copyImageBitmapToTexture( image, textureGPU );
+					this._copyImageBitmapToTexture( image, textureGPU, needsMipmaps, textureGPUDescriptor );
 
 				}
 
@@ -342,7 +367,9 @@ class WebGPUTextures {
 	_copyBufferToTexture( image, format, textureGPU ) {
 
 		// this code assumes data textures in RGBA format
+
 		// @TODO: Consider to support valid buffer layouts with other formats like RGB
+		// @TODO: Support mipmaps
 
 		const device = this.device;
 		const data = image.data;
@@ -377,15 +404,7 @@ class WebGPUTextures {
 
 	}
 
-	_getBytesPerTexel( format ) {
-
-		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
-		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
-		if ( format === GPUTextureFormat.RGBA32Float ) return 16;
-
-	}
-
-	_copyImageBitmapToTexture( imageBitmap, textureGPU ) {
+	_copyImageBitmapToTexture( imageBitmap, textureGPU, needsMipmaps, textureGPUDescriptor ) {
 
 		const device = this.device;
 
@@ -401,6 +420,32 @@ class WebGPUTextures {
 			}
 		);
 
+		if ( needsMipmaps === true ) {
+
+			if ( this.utils === null ) {
+
+				this.utils = new WebGPUTextureUtils( this.device, this.glslang ); // only create this helper if necessary
+
+			}
+
+			this.utils.generateMipmappedTexture( imageBitmap, textureGPU, textureGPUDescriptor );
+
+		}
+
+	}
+
+	_getBytesPerTexel( format ) {
+
+		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
+		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
+		if ( format === GPUTextureFormat.RGBA32Float ) return 16;
+
+	}
+
+	_needsMipmaps( texture ) {
+
+		return ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter );
+
 	}
 
 }