Procházet zdrojové kódy

WebGPURenderer: MSAA, Postprocessing and Wireframe support in the WebGL Backend (#27473)

* support material.wireframe in webgl backend

* fix reattach framebuffer, support msaa and wireframe demo

* fix depth samples === 0

* cleanup

* more cleanup

* more cleanup to the webgl pipeline

* postprocessing support

* fix msaa

* fix example afterimage

* improved state management and performances

* generate a new webgl slot for fbos

* revert puppeteer

* fix copyFrameBufferToTexture with correct unbinding and cleanup

* fix multipass pipeline in webgl backend and enable new demos

* regenerate screenshot with gaussian blur
Renaud Rohlinger před 1 rokem
rodič
revize
aeb40de385

+ 2 - 1
examples/files.json

@@ -367,7 +367,8 @@
 		"webgpu_tsl_editor",
 		"webgpu_tsl_transpiler",
 		"webgpu_video_panorama",
-		"webgpu_postprocessing_afterimage"
+		"webgpu_postprocessing_afterimage",
+		"webgpu_multisampled_renderbuffers"
 	],
 	"webaudio": [
 		"webaudio_orientation",

+ 3 - 0
examples/jsm/nodes/display/AfterImageNode.js

@@ -21,7 +21,10 @@ class AfterImageNode extends TempNode {
 		this.damp = uniform( damp );
 
 		this._compRT = new RenderTarget();
+		this._compRT.texture.name = 'AfterImageNode.comp';
+
 		this._oldRT = new RenderTarget();
+		this._oldRT.texture.name = 'AfterImageNode.old';
 
 		this.updateBeforeType = NodeUpdateType.RENDER;
 

+ 2 - 0
examples/jsm/nodes/display/GaussianBlurNode.js

@@ -29,7 +29,9 @@ class GaussianBlurNode extends TempNode {
 		this._passDirection = uniform( new Vector2() );
 
 		this._horizontalRT = new RenderTarget();
+		this._horizontalRT.texture.name = 'GaussianBlurNode.horizontal';
 		this._verticalRT = new RenderTarget();
+		this._verticalRT.texture.name = 'GaussianBlurNode.vertical';
 
 		this.updateBeforeType = NodeUpdateType.RENDER;
 

+ 121 - 51
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -71,6 +71,8 @@ class WebGLBackend extends Backend {
 
 		//
 
+		//
+
 		renderContextData.previousContext = this._currentContext;
 		this._currentContext = renderContext;
 
@@ -79,7 +81,6 @@ class WebGLBackend extends Backend {
 		this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext );
 
 		//
-
 		if ( renderContext.viewport ) {
 
 			this.updateViewport( renderContext );
@@ -110,11 +111,49 @@ class WebGLBackend extends Backend {
 
 	finishRender( renderContext ) {
 
+		const { gl } = this;
 		const renderContextData = this.get( renderContext );
 		const previousContext = renderContextData.previousContext;
 
 		this._currentContext = previousContext;
 
+
+		if ( renderContext.textures !== null && renderContext.renderTarget ) {
+
+			const renderTargetContextData = this.get( renderContext.renderTarget );
+
+			const { samples, stencilBuffer } = renderContext.renderTarget;
+			const fb = renderTargetContextData.framebuffer;
+
+			if ( samples > 0 ) {
+
+				const invalidationArray = [];
+				const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
+
+				invalidationArray.push( gl.COLOR_ATTACHMENT0 );
+
+				if ( renderTargetContextData.depthBuffer ) {
+
+					invalidationArray.push( depthStyle );
+
+				}
+
+				// TODO For loop support MRT
+				const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer;
+
+				gl.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer );
+				gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
+
+
+				gl.blitFramebuffer( 0, 0, renderContext.width, renderContext.height, 0, 0, renderContext.width, renderContext.height, gl.COLOR_BUFFER_BIT, gl.NEAREST );
+
+				gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, invalidationArray );
+
+			}
+
+
+		}
+
 		if ( previousContext !== null ) {
 
 			this._setFramebuffer( previousContext );
@@ -133,6 +172,9 @@ class WebGLBackend extends Backend {
 
 		}
 
+
+		this._currentContext = renderContext;
+
 		const occlusionQueryCount = renderContext.occlusionQueryCount;
 
 		if ( occlusionQueryCount > 0 ) {
@@ -151,6 +193,7 @@ class WebGLBackend extends Backend {
 
 		}
 
+
 	}
 
 	resolveOccludedAsync( renderContext ) {
@@ -312,7 +355,7 @@ class WebGLBackend extends Backend {
 
 	draw( renderObject, info ) {
 
-		const { pipeline, material, context } = renderObject;
+		const { pipeline, material, context, isRenderObject } = renderObject;
 		const { programGPU, vaoGPU } = this.get( pipeline );
 
 		const { gl, state } = this;
@@ -321,6 +364,13 @@ class WebGLBackend extends Backend {
 
 		//
 
+		if ( isRenderObject ) {
+
+			// we need to bind the framebuffer per object in multi pass pipeline
+			this._setFramebuffer( context );
+
+		}
+
 		const bindings = renderObject.getBindings();
 
 		for ( const binding of bindings ) {
@@ -334,8 +384,7 @@ class WebGLBackend extends Backend {
 
 			} else if ( binding.isSampledTexture ) {
 
-				gl.activeTexture( gl.TEXTURE0 + index );
-				gl.bindTexture( bindingData.glTextureType, bindingData.textureGPU );
+				state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index );
 
 			}
 
@@ -391,7 +440,20 @@ class WebGLBackend extends Backend {
 		else if ( object.isLineSegments ) mode = gl.LINES;
 		else if ( object.isLine ) mode = gl.LINE_STRIP;
 		else if ( object.isLineLoop ) mode = gl.LINE_LOOP;
-		else mode = gl.TRIANGLES;
+		else {
+
+			if ( material.wireframe === true ) {
+
+				state.setLineWidth( material.wireframeLinewidth * this.renderer.getPixelRatio() );
+				mode = gl.LINES;
+
+			} else {
+
+				mode = gl.TRIANGLES;
+
+			}
+
+		}
 
 		//
 
@@ -482,6 +544,7 @@ class WebGLBackend extends Backend {
 
 	}
 
+
 	destroyTexture( texture ) {
 
 		this.textureUtils.destroyTexture( texture );
@@ -794,100 +857,107 @@ class WebGLBackend extends Backend {
 
 	copyFramebufferToTexture( texture, renderContext ) {
 
-		const { gl } = this;
+		this.textureUtils.copyFramebufferToTexture( texture, renderContext );
 
-		const { textureGPU } = this.get( texture );
+	}
 
-		const width = texture.image.width;
-		const height = texture.image.height;
+	_setFramebuffer( renderContext ) {
 
-		gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null );
+		const { gl, state } = this;
 
-		if ( texture.isDepthTexture ) {
+		let fb = null;
+		let currentFrameBuffer = null;
 
-			const fb = gl.createFramebuffer();
+		if ( renderContext.textures !== null ) {
 
-			gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
+			const renderTargetContextData = this.get( renderContext.renderTarget );
+			const { samples } = renderContext.renderTarget;
 
-			gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, textureGPU, 0 );
+			fb = renderTargetContextData.framebuffer;
+			let msaaFb = renderTargetContextData.msaaFrameBuffer;
+			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
 
-			gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, gl.DEPTH_BUFFER_BIT, gl.NEAREST );
 
-			gl.deleteFramebuffer( fb );
+			if ( fb === undefined ) {
 
+				fb = gl.createFramebuffer();
 
-		} else {
+				state.bindFramebuffer( gl.FRAMEBUFFER, fb );
 
-			gl.bindTexture( gl.TEXTURE_2D, textureGPU );
-			gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height );
+				const textures = renderContext.textures;
 
-			gl.bindTexture( gl.TEXTURE_2D, null );
+				for ( let i = 0; i < textures.length; i ++ ) {
 
-		}
+					const texture = textures[ i ];
+					const textureData = this.get( texture );
+					textureData.renderTarget = renderContext.renderTarget;
 
-		if ( texture.generateMipmaps ) this.generateMipmaps( texture );
+					const attachment = gl.COLOR_ATTACHMENT0 + i;
 
-		this._setFramebuffer( renderContext );
 
-	}
+					gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 );
 
-	_setFramebuffer( renderContext ) {
+				}
 
-		const { gl } = this;
+				if ( renderContext.depthTexture !== null ) {
 
-		if ( renderContext.textures !== null ) {
+					const textureData = this.get( renderContext.depthTexture );
 
-			const renderContextData = this.get( renderContext.renderTarget );
+					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, textureData.textureGPU, 0 );
 
-			let fb = renderContextData.framebuffer;
+				}
 
-			if ( fb === undefined ) {
 
-				fb = gl.createFramebuffer();
+				renderTargetContextData.framebuffer = fb;
 
-				gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
+				state.drawBuffers( renderContext, fb );
 
-				const textures = renderContext.textures;
+			}
 
-				const drawBuffers = [];
+			if ( samples > 0 ) {
 
-				for ( let i = 0; i < textures.length; i ++ ) {
+				if ( msaaFb === undefined ) {
 
-					const texture = textures[ i ];
-					const { textureGPU } = this.get( texture );
+					msaaFb = gl.createFramebuffer();
 
-					const attachment = gl.COLOR_ATTACHMENT0 + i;
+					state.bindFramebuffer( gl.FRAMEBUFFER, msaaFb );
 
-					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, textureGPU, 0 );
+					// TODO For loop support MRT
+					const msaaRenderbuffer = gl.createRenderbuffer();
+					gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffer );
 
-					drawBuffers.push( attachment );
+					const texture = renderContext.textures[ 0 ];
+					const textureData = this.get( texture );
 
-				}
+					gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, renderContext.width, renderContext.height );
+					gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaRenderbuffer );
 
-				gl.drawBuffers( drawBuffers );
+					renderTargetContextData.msaaRenderbuffer = msaaRenderbuffer;
+					renderTargetContextData.msaaFrameBuffer = msaaFb;
 
-				if ( renderContext.depthTexture !== null ) {
+					if ( depthRenderbuffer === undefined ) {
 
-					const { textureGPU } = this.get( renderContext.depthTexture );
+						depthRenderbuffer = gl.createRenderbuffer();
+						this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, renderContext );
 
-					gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, textureGPU, 0 );
+						renderTargetContextData.depthRenderbuffer = depthRenderbuffer;
+
+					}
 
 				}
 
-				renderContextData.framebuffer = fb;
+				currentFrameBuffer = renderTargetContextData.msaaFrameBuffer;
 
 			} else {
 
-				gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
+				currentFrameBuffer = fb;
 
 			}
 
-		} else {
-
-			gl.bindFramebuffer( gl.FRAMEBUFFER, null );
-
 		}
 
+		state.bindFramebuffer( gl.FRAMEBUFFER, currentFrameBuffer );
+
 	}
 
 }

+ 197 - 0
examples/jsm/renderers/webgl/utils/WebGLState.js

@@ -40,6 +40,14 @@ class WebGLState {
 		this.currentStencilZFail = null;
 		this.currentStencilZPass = null;
 		this.currentStencilMask = null;
+		this.currentLineWidth = null;
+
+		this.currentBoundFramebuffers = {};
+		this.currentDrawbuffers = new WeakMap();
+
+		this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS );
+		this.currentTextureSlot = null;
+		this.currentBoundTextures = {};
 
 		if ( initialized === false ) {
 
@@ -161,6 +169,21 @@ class WebGLState {
 
 	}
 
+	setLineWidth( width ) {
+
+		const { currentLineWidth, gl } = this;
+
+		if ( width !== currentLineWidth ) {
+
+			gl.lineWidth( width );
+
+			this.currentLineWidth = width;
+
+		}
+
+	}
+
+
 	setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
 
 		const { gl } = this;
@@ -535,6 +558,180 @@ class WebGLState {
 
 	}
 
+	// framebuffer
+
+
+	bindFramebuffer( target, framebuffer ) {
+
+		const { gl, currentBoundFramebuffers } = this;
+
+		if ( currentBoundFramebuffers[ target ] !== framebuffer ) {
+
+			gl.bindFramebuffer( target, framebuffer );
+
+			currentBoundFramebuffers[ target ] = framebuffer;
+
+			// gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER
+
+			if ( target === gl.DRAW_FRAMEBUFFER ) {
+
+				currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer;
+
+			}
+
+			if ( target === gl.FRAMEBUFFER ) {
+
+				currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer;
+
+			}
+
+			return true;
+
+		}
+
+		return false;
+
+	}
+
+	drawBuffers( renderContext, framebuffer ) {
+
+		const { gl } = this;
+
+		let drawBuffers = [];
+
+		let needsUpdate = false;
+
+		if ( renderContext.textures !== null ) {
+
+			drawBuffers = this.currentDrawbuffers.get( framebuffer );
+
+			if ( drawBuffers === undefined ) {
+
+				drawBuffers = [];
+				this.currentDrawbuffers.set( framebuffer, drawBuffers );
+
+			}
+
+
+			const textures = renderContext.textures;
+
+			if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) {
+
+				for ( let i = 0, il = textures.length; i < il; i ++ ) {
+
+					drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i;
+
+				}
+
+				drawBuffers.length = textures.length;
+
+				needsUpdate = true;
+
+			}
+
+
+		} else {
+
+			if ( drawBuffers[ 0 ] !== gl.BACK ) {
+
+				drawBuffers[ 0 ] = gl.BACK;
+
+				needsUpdate = true;
+
+			}
+
+		}
+
+		if ( needsUpdate ) {
+
+			gl.drawBuffers( drawBuffers );
+
+		}
+
+
+	}
+
+
+	// texture
+
+	activeTexture( webglSlot ) {
+
+		const { gl, currentTextureSlot, maxTextures } = this;
+
+		if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1;
+
+		if ( currentTextureSlot !== webglSlot ) {
+
+			gl.activeTexture( webglSlot );
+			this.currentTextureSlot = webglSlot;
+
+		}
+
+	}
+
+	bindTexture( webglType, webglTexture, webglSlot ) {
+
+		const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this;
+
+		if ( webglSlot === undefined ) {
+
+			if ( currentTextureSlot === null ) {
+
+				webglSlot = gl.TEXTURE0 + maxTextures - 1;
+
+			} else {
+
+				webglSlot = currentTextureSlot;
+
+			}
+
+		}
+
+		let boundTexture = currentBoundTextures[ webglSlot ];
+
+		if ( boundTexture === undefined ) {
+
+			boundTexture = { type: undefined, texture: undefined };
+			currentBoundTextures[ webglSlot ] = boundTexture;
+
+		}
+
+		if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
+
+			if ( currentTextureSlot !== webglSlot ) {
+
+				gl.activeTexture( webglSlot );
+				this.currentTextureSlot = webglSlot;
+
+			}
+
+			gl.bindTexture( webglType, webglTexture );
+
+			boundTexture.type = webglType;
+			boundTexture.texture = webglTexture;
+
+
+		}
+
+
+	}
+
+	unbindTexture() {
+
+		const { gl, currentTextureSlot, currentBoundTextures } = this;
+
+		const boundTexture = currentBoundTextures[ currentTextureSlot ];
+
+		if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
+
+			gl.bindTexture( boundTexture.type, null );
+
+			boundTexture.type = undefined;
+			boundTexture.texture = undefined;
+
+		}
+
+	}
 
 }
 

+ 134 - 7
examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js

@@ -225,11 +225,11 @@ class WebGLTextureUtils {
 
 			textureGPU = gl.createTexture();
 
-			gl.bindTexture( glTextureType, textureGPU );
+			backend.state.bindTexture( glTextureType, textureGPU );
 			gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
 			gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
 
-			//gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
+			// gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
 
 			defaultTextures[ glTextureType ] = textureGPU;
 
@@ -255,7 +255,7 @@ class WebGLTextureUtils {
 		const textureGPU = gl.createTexture();
 		const glTextureType = this.getGLTextureType( texture );
 
-		gl.bindTexture( glTextureType, textureGPU );
+		backend.state.bindTexture( glTextureType, textureGPU );
 
 		gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
 		gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
@@ -264,8 +264,6 @@ class WebGLTextureUtils {
 
 		this.setTextureParameters( glTextureType, texture );
 
-		gl.bindTexture( glTextureType, textureGPU );
-
 		if ( texture.isDataArrayTexture ) {
 
 			gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );
@@ -311,7 +309,7 @@ class WebGLTextureUtils {
 
 		};
 
-		gl.bindTexture( glTextureType, textureGPU );
+		this.backend.state.bindTexture( glTextureType, textureGPU );
 
 		if ( texture.isCompressedTexture ) {
 
@@ -408,7 +406,17 @@ class WebGLTextureUtils {
 	destroyTexture( texture ) {
 
 		const { gl, backend } = this;
-		const { textureGPU } = backend.get( texture );
+		const { textureGPU, renderTarget } = backend.get( texture );
+
+		// remove framebuffer reference
+		if ( renderTarget ) {
+
+			const renderContextData = backend.get( renderTarget );
+			renderContextData.framebuffer = undefined;
+			renderContextData.msaaFrameBuffer = undefined;
+			renderContextData.depthRenderbuffer = undefined;
+
+		}
 
 		gl.deleteTexture( textureGPU );
 
@@ -416,6 +424,125 @@ class WebGLTextureUtils {
 
 	}
 
+	copyFramebufferToTexture( texture, renderContext ) {
+
+		const { gl } = this;
+		const { state } = this.backend;
+
+		const { textureGPU } = this.backend.get( texture );
+
+		const width = texture.image.width;
+		const height = texture.image.height;
+
+		state.bindFramebuffer( gl.READ_FRAMEBUFFER, null );
+
+		if ( texture.isDepthTexture ) {
+
+			const fb = gl.createFramebuffer();
+
+			gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb );
+
+			gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, textureGPU, 0 );
+
+			gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, gl.DEPTH_BUFFER_BIT, gl.NEAREST );
+
+			gl.deleteFramebuffer( fb );
+
+
+		} else {
+
+			state.bindTexture( gl.TEXTURE_2D, textureGPU );
+			gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height );
+
+			state.unbindTexture();
+
+		}
+
+		if ( texture.generateMipmaps ) this.generateMipmaps( texture );
+
+		this.backend._setFramebuffer( renderContext );
+
+	}
+
+	// Setup storage for internal depth/stencil buffers and bind to correct framebuffer
+	setupRenderBufferStorage( renderbuffer, renderContext ) {
+
+		const { gl } = this;
+		const renderTarget = renderContext.renderTarget;
+
+		const { samples, depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget;
+
+		gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
+
+		if ( depthBuffer && ! stencilBuffer ) {
+
+			let glInternalFormat = gl.DEPTH_COMPONENT24;
+
+			if ( samples > 0 ) {
+
+
+				if ( depthTexture && depthTexture.isDepthTexture ) {
+
+					if ( depthTexture.type === gl.FLOAT ) {
+
+						glInternalFormat = gl.DEPTH_COMPONENT32F;
+
+					}
+
+				}
+
+				gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height );
+
+			} else {
+
+				gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height );
+
+			}
+
+			gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
+
+		} else if ( depthBuffer && stencilBuffer ) {
+
+			if ( samples > 0 ) {
+
+				gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height );
+
+			} else {
+
+				gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height );
+
+			}
+
+
+			gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
+
+		} else {
+
+			const textures = renderContext.textures;
+
+			for ( let i = 0; i < textures.length; i ++ ) {
+
+				const texture = textures[ i ];
+				const { glInternalFormat } = this.get( texture );
+
+				if ( samples > 0 ) {
+
+					gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height );
+
+				} else {
+
+					gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height );
+
+				}
+
+			}
+
+		}
+
+		gl.bindRenderbuffer( gl.RENDERBUFFER, null );
+
+	}
+
 	async copyTextureToBuffer( texture, x, y, width, height ) {
 
 		const { backend, gl } = this;

+ 6 - 3
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -15,10 +15,13 @@ import { getFormat } from '../utils/WebGPUTextureUtils.js';
 
 import WGSLNodeParser from './WGSLNodeParser.js';
 
+// GPUShaderStage is not defined in browsers not supporting WebGPU
+const GPUShaderStage = window.GPUShaderStage;
+
 const gpuShaderStageLib = {
-	'vertex': GPUShaderStage.VERTEX,
-	'fragment': GPUShaderStage.FRAGMENT,
-	'compute': GPUShaderStage.COMPUTE
+	'vertex': GPUShaderStage ? GPUShaderStage.VERTEX : 1,
+	'fragment': GPUShaderStage ? GPUShaderStage.FRAGMENT : 2,
+	'compute': GPUShaderStage ? GPUShaderStage.COMPUTE : 4
 };
 
 const supports = {

+ 14 - 1
examples/jsm/renderers/webgpu/utils/WebGPUPipelineUtils.js

@@ -102,7 +102,20 @@ class WebGPUPipelineUtils {
 		const primitiveState = this._getPrimitiveState( object, geometry, material );
 		const depthCompare = this._getDepthCompare( material );
 		const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
-		const sampleCount = utils.getSampleCount( renderObject.context );
+		let sampleCount = utils.getSampleCount( renderObject.context );
+
+		if ( sampleCount > 1 ) {
+
+			// WebGPU only supports power-of-two sample counts and 2 is not a valid value
+			sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
+
+			if ( sampleCount === 2 ) {
+
+				sampleCount = 4;
+
+			}
+
+		}
 
 		pipelineData.pipeline = device.createRenderPipeline( {
 			vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),

+ 15 - 1
examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -113,7 +113,21 @@ class WebGPUTextureUtils {
 		const dimension = this._getDimension( texture );
 		const format = texture.internalFormat || getFormat( texture, backend.device );
 
-		const sampleCount = options.sampleCount !== undefined ? options.sampleCount : 1;
+		let sampleCount = options.sampleCount !== undefined ? options.sampleCount : 1;
+
+		if ( sampleCount > 1 ) {
+
+			// WebGPU only supports power-of-two sample counts and 2 is not a valid value
+			sampleCount = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );
+
+			if ( sampleCount === 2 ) {
+
+				sampleCount = 4;
+
+			}
+
+		}
+
 		const primarySampleCount = texture.isRenderTargetTexture ? 1 : sampleCount;
 
 		let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;

binární
examples/screenshots/webgpu_multisampled_renderbuffers.jpg


binární
examples/screenshots/webgpu_portal.jpg


binární
examples/screenshots/webgpu_postprocessing_afterimage.jpg


binární
examples/screenshots/webgpu_skinning_instancing.jpg


+ 189 - 0
examples/webgpu_multisampled_renderbuffers.html

@@ -0,0 +1,189 @@
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - Multisampled Renderbuffers</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Multisampled Renderbuffers
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { texture, MeshBasicNodeMaterial, MeshPhongNodeMaterial } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import QuadMesh from 'three/addons/objects/QuadMesh.js';
+
+			let camera, scene, renderer;
+			const mouse = new THREE.Vector2();
+
+			let quadMesh, renderTarget;
+
+			let box, box2;
+
+			const dpr = 1;
+
+			const params = {
+				animated: true,
+				samples: 4
+			};
+
+			const mat4 = new THREE.Matrix4();
+
+			const count = 50;
+			const fullRadius = 20; // Radius of the sphere
+			const halfRadius = 10; // Radius of the sphere
+			const positions = new Array( count ).fill().map( ( _, i ) => {
+
+				const radius = ( i % 2 === 0 ) ? fullRadius : halfRadius;
+
+				const phi = Math.acos( 2 * Math.random() - 1 ) - Math.PI / 2; // phi: latitude, range -π/2 to π/2
+				const theta = 2 * Math.PI * Math.random(); // theta: longitude, range 0 to 2π
+
+				return new THREE.Vector3(
+					radius * Math.cos( phi ) * Math.cos( theta ), // x
+					radius * Math.sin( phi ), // y
+					radius * Math.cos( phi ) * Math.sin( theta ) // z
+				);
+
+			} );
+
+
+			initGUI();
+			init();
+
+			function initGUI() {
+
+				const gui = new GUI();
+				gui.add( params, 'samples', 0, 4 ).step( 1 );
+				gui.add( params, 'animated', true );
+
+			}
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 50 );
+				camera.position.z = 3;
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x111111 );
+
+				// textured mesh
+
+				const geometryBox = new THREE.BoxGeometry( 7, 7, 7, 12, 12, 12 );
+				const materialBox = new MeshPhongNodeMaterial();
+				const materialBoxInner = new MeshPhongNodeMaterial( { color: 0xff0000 } );
+
+				materialBox.wireframe = true;
+
+				//
+
+				box = new THREE.InstancedMesh( geometryBox, materialBox, count );
+				box2 = new THREE.InstancedMesh( geometryBox, materialBoxInner, count );
+
+				for ( let i = 0; i < count; i ++ ) {
+
+					box.setMatrixAt( i, mat4.identity().setPosition( positions[ i ] ) );
+					box2.setMatrixAt( i, mat4.multiplyScalar( 0.996 ).setPosition( positions[ i ] ) );
+
+				}
+
+				scene.add( box, box2 );
+
+				//
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( dpr );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				renderTarget = new THREE.RenderTarget( window.innerWidth * dpr, window.innerHeight * dpr, {
+					samples: params.samples,
+					depthBuffer: true,
+				} );
+
+				window.addEventListener( 'mousemove', onWindowMouseMove );
+				window.addEventListener( 'resize', onWindowResize );
+
+				// FX
+
+				// modulate the final color based on the mouse position
+
+				const materialFX = new MeshBasicNodeMaterial();
+				materialFX.colorNode = texture( renderTarget.texture ).rgb;
+
+				quadMesh = new QuadMesh( materialFX );
+
+			}
+
+			function onWindowMouseMove( e ) {
+
+				mouse.x = e.offsetX / window.innerWidth;
+				mouse.y = e.offsetY / window.innerHeight;
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderTarget.setSize( window.innerWidth * dpr, window.innerHeight * dpr );
+
+			}
+
+			function animate() {
+
+				if ( params.animated ) {
+
+					box.rotation.x += 0.001;
+					box.rotation.y += 0.002;
+					box2.rotation.x += 0.001;
+					box2.rotation.y += 0.002;
+
+				}
+
+				renderTarget.samples = params.samples;
+
+				renderer.setRenderTarget( renderTarget );
+				renderer.render( scene, camera );
+
+				renderer.setRenderTarget( null );
+				quadMesh.render( renderer );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 0 - 10
examples/webgpu_portal.html

@@ -29,8 +29,6 @@
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
-			import WebGPU from 'three/addons/capabilities/WebGPU.js';
-
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -44,14 +42,6 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
-
-					document.body.appendChild( WebGPU.getErrorMessage() );
-
-					throw new Error( 'No WebGPU support' );
-
-				}
-
 				//
 
 				sceneMain = new THREE.Scene();

+ 1 - 17
examples/webgpu_postprocessing_afterimage.html

@@ -23,7 +23,6 @@
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
-			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
 			import PostProcessing from 'three/addons/renderers/common/PostProcessing.js';
 			import { pass } from 'three/nodes';
@@ -43,14 +42,6 @@
 
 			function init() {
 
-				if ( WebGPU.isAvailable() === false ) {
-
-					document.body.appendChild( WebGPU.getErrorMessage() );
-
-					throw new Error( 'No WebGPU support' );
-
-				}
-
 				renderer = new WebGPURenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
@@ -104,15 +95,8 @@
 				mesh.rotation.x += 0.0075;
 				mesh.rotation.y += 0.015;
 
-				if ( renderer.backend.isWebGPUBackend ) {
 
-					postProcessing.render();
-
-				} else {
-
-					renderer.render( scene, camera );
-
-				}
+				postProcessing.render();
 
 			}
 

+ 8 - 16
examples/webgpu_skinning_instancing.html

@@ -142,19 +142,17 @@
 
 				// post processing ( just for WebGPUBackend for now )
 
-				if ( renderer.backend.isWebGPUBackend ) {
 
-					const scenePass = pass( scene, camera );
-					const scenePassColor = scenePass.getTextureNode();
-					const scenePassDepth = scenePass.getDepthNode().remapClamp( .15, .3 );
+				const scenePass = pass( scene, camera );
+				const scenePassColor = scenePass.getTextureNode();
+				const scenePassDepth = scenePass.getDepthNode().remapClamp( .15, .3 );
 
-					const scenePassColorBlurred = scenePassColor.gaussianBlur();
-					scenePassColorBlurred.directionNode = scenePassDepth;
+				const scenePassColorBlurred = scenePassColor.gaussianBlur();
+				scenePassColorBlurred.directionNode = scenePassDepth;
 
-					postProcessing = new PostProcessing( renderer );
-					postProcessing.outputNode = scenePassColorBlurred;
+				postProcessing = new PostProcessing( renderer );
+				postProcessing.outputNode = scenePassColorBlurred;
 
-				}
 
 				// events
 
@@ -177,15 +175,9 @@
 
 				if ( mixer ) mixer.update( delta );
 
-				if ( renderer.backend.isWebGPUBackend ) {
-
-					postProcessing.render();
 
-				} else {
+				postProcessing.render();
 
-					renderer.render( scene, camera );
-
-				}
 
 			}
 

+ 1 - 0
test/e2e/puppeteer.js

@@ -128,6 +128,7 @@ const exceptionList = [
 	'webgpu_sprites',
 	'webgpu_video_panorama',
 	'webgpu_postprocessing_afterimage',
+	'webgpu_multisampled_renderbuffers',
 
 	// WebGPURenderer: Unknown problem
 	'webgpu_backdrop_water',