Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: Support MSAA with Postprocessing #28784

Merged
merged 12 commits into from
Jul 4, 2024
20 changes: 16 additions & 4 deletions src/nodes/display/PassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ class PassTextureNode extends TextureNode {

class PassNode extends TempNode {

constructor( scope, scene, camera ) {
constructor( scope, scene, camera, options = {} ) {

super( 'vec4' );

this.scope = scope;
this.scene = scene;
this.camera = camera;
this.options = options;

this._pixelRatio = 1;
this._width = 1;
Expand All @@ -60,7 +61,7 @@ class PassNode extends TempNode {
//depthTexture.type = FloatType;
depthTexture.name = 'PostProcessingDepth';

const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } );
const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options } );
renderTarget.texture.name = 'PostProcessing';
renderTarget.depthTexture = depthTexture;

Expand Down Expand Up @@ -130,7 +131,18 @@ class PassNode extends TempNode {

}

setup() {
setup( { renderer } ) {

this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples;

// Disable MSAA for WebGL backend for now
if ( renderer.backend.isWebGLBackend === true ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this exception still required? My understand is you have added this bit since rendering multisampled to a color renderable texture does not yet work in the WebGL backend. But can at least the "normal" MSAA be used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately yes it's still needed. I noticed that using PassNode with samples > 0 doesn't seem to render anything in WebGL. I believe this might be due to the need for an additional frameBuffer somewhere in the render pipeline, which I couldn't easily resolve. That's why I added this comment.

Copy link
Collaborator

@sunag sunag Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add this path in WebGLBackend.beginRender() for now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't recommend so because basic THREE.RenderTarget setups works just fine with samples > 0. For example webgpu_multisampled_renderbuffers.html.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's merge this PR then! It's possible do it in another PR, currently already a step forward in that sense. So the future goal is for us to move this and isMultisampleRenderTargetTexture into the renderer as @Mugen87 said, when this is ready examples like webgpu_backdrop_water should work too.


this.renderTarget.samples = 0;

}

this.renderTarget.depthTexture.isMultisampleRenderTargetTexture = this.renderTarget.samples > 1;

return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();

Expand Down Expand Up @@ -194,7 +206,7 @@ PassNode.DEPTH = 'depth';

export default PassNode;

export const pass = ( scene, camera ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera ) );
export const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) );
export const texturePass = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) );
export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) );

Expand Down
9 changes: 6 additions & 3 deletions src/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,18 @@ class Renderer {

const {
logarithmicDepthBuffer = false,
alpha = true
alpha = true,
antialias = false,
samples = 0
} = parameters;

// public

this.domElement = backend.getDomElement();

this.backend = backend;

this.samples = samples || ( antialias === true ) ? 4 : 0;

this.autoClear = true;
this.autoClearColor = true;
this.autoClearDepth = true;
Expand Down Expand Up @@ -456,7 +459,7 @@ class Renderer {
generateMipmaps: false,
minFilter: LinearFilter,
magFilter: LinearFilter,
samples: this.backend.parameters.antialias ? 4 : 0
samples: this.samples
} );

frameBufferTarget.isPostProcessingRenderTarget = true;
Expand Down
24 changes: 5 additions & 19 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,6 @@ class WebGPUBackend extends Backend {
// some parameters require default values other than "undefined"
this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha;

this.parameters.antialias = ( parameters.antialias === true );

if ( this.parameters.antialias === true ) {

this.parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount;

} else {

this.parameters.sampleCount = 1;

}

this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;

this.trackTimestamp = ( parameters.trackTimestamp === true );
Expand Down Expand Up @@ -153,8 +141,6 @@ class WebGPUBackend extends Backend {

let descriptor = this.defaultRenderPassdescriptor;

const antialias = this.parameters.antialias;

if ( descriptor === null ) {

const renderer = this.renderer;
Expand All @@ -170,7 +156,7 @@ class WebGPUBackend extends Backend {

const colorAttachment = descriptor.colorAttachments[ 0 ];

if ( antialias === true ) {
if ( this.renderer.samples > 0 ) {

colorAttachment.view = this.colorBuffer.createView();

Expand All @@ -186,7 +172,7 @@ class WebGPUBackend extends Backend {

const colorAttachment = descriptor.colorAttachments[ 0 ];

if ( antialias === true ) {
if ( this.renderer.samples > 0 ) {

colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();

Expand Down Expand Up @@ -269,7 +255,7 @@ class WebGPUBackend extends Backend {
const depthTextureData = this.get( renderContext.depthTexture );

const depthStencilAttachment = {
view: depthTextureData.texture.createView(),
view: depthTextureData.texture.createView()
};

descriptor = {
Expand Down Expand Up @@ -976,7 +962,7 @@ class WebGPUBackend extends Backend {

const utils = this.utils;

const sampleCount = utils.getSampleCount( renderObject.context );
const sampleCount = utils.getSampleCountRenderContext( renderObject.context );
const colorSpace = utils.getCurrentColorSpace( renderObject.context );
const colorFormat = utils.getCurrentColorFormat( renderObject.context );
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
Expand Down Expand Up @@ -1041,7 +1027,7 @@ class WebGPUBackend extends Backend {
material.stencilFail, material.stencilZFail, material.stencilZPass,
material.stencilFuncMask, material.stencilWriteMask,
material.side,
utils.getSampleCount( renderContext ),
utils.getSampleCountRenderContext( renderContext ),
utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
utils.getPrimitiveTopology( object, material ),
renderObject.clippingContextVersion
Expand Down
16 changes: 12 additions & 4 deletions src/renderers/webgpu/nodes/WGSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class WGSLNodeBuilder extends NodeBuilder {

this._include( 'repeatWrapping' );

const dimension = `textureDimensions( ${ textureProperty }, 0 )`;
const dimension = texture.isMultisampleRenderTargetTexture === true ? `textureDimensions( ${ textureProperty } )` : `textureDimensions( ${ textureProperty }, 0 )`;

return `textureLoad( ${ textureProperty }, threejs_repeatWrapping( ${ uvSnippet }, ${ dimension } ), i32( ${ levelSnippet } ) )`;

Expand Down Expand Up @@ -269,7 +269,7 @@ class WGSLNodeBuilder extends NodeBuilder {

isUnfilterable( texture ) {

return this.getComponentTypeFromTexture( texture ) !== 'float' || ( texture.isDataTexture === true && texture.type === FloatType );
return this.getComponentTypeFromTexture( texture ) !== 'float' || ( texture.isDataTexture === true && texture.type === FloatType ) || texture.isMultisampleRenderTargetTexture === true;

}

Expand Down Expand Up @@ -864,6 +864,14 @@ ${ flowData.code }

let textureType;

let multisampled = '';

if ( texture.isMultisampleRenderTargetTexture === true ) {

multisampled = '_multisampled';

}

if ( texture.isCubeTexture === true ) {

textureType = 'texture_cube<f32>';
Expand All @@ -874,7 +882,7 @@ ${ flowData.code }

} else if ( texture.isDepthTexture === true ) {

textureType = 'texture_depth_2d';
textureType = `texture_depth${multisampled}_2d`;

} else if ( texture.isVideoTexture === true ) {

Expand All @@ -895,7 +903,7 @@ ${ flowData.code }

const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 );

textureType = `texture_2d<${ componentPrefix }32>`;
textureType = `texture${multisampled}_2d<${ componentPrefix }32>`;

}

Expand Down
6 changes: 6 additions & 0 deletions src/renderers/webgpu/utils/WebGPUBindingUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ class WebGPUBindingUtils {

const texture = {}; // GPUTextureBindingLayout

if ( binding.texture.isMultisampleRenderTargetTexture === true ) {

texture.multisampled = true;

}

if ( binding.texture.isDepthTexture ) {

texture.sampleType = GPUTextureSampleType.Depth;
Expand Down
17 changes: 1 addition & 16 deletions src/renderers/webgpu/utils/WebGPUPipelineUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,7 @@ class WebGPUPipelineUtils {

_getSampleCount( renderObjectContext ) {

let sampleCount = this.backend.utils.getSampleCount( renderObjectContext );

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;

}

}

return sampleCount;
return this.backend.utils.getSampleCountRenderContext( renderObjectContext );

}

Expand Down
21 changes: 5 additions & 16 deletions src/renderers/webgpu/utils/WebGPUTextureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,9 @@ class WebGPUTextureUtils {

let sampleCount = options.sampleCount !== undefined ? options.sampleCount : 1;

if ( sampleCount > 1 ) {
sampleCount = backend.utils.getSampleCount( sampleCount );

// 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;
const primarySampleCount = texture.isRenderTargetTexture && ! texture.isMultisampleRenderTargetTexture ? 1 : sampleCount;

let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;

Expand Down Expand Up @@ -190,7 +179,7 @@ class WebGPUTextureUtils {

}

if ( texture.isRenderTargetTexture && sampleCount > 1 ) {
if ( texture.isRenderTargetTexture && sampleCount > 1 && ! texture.isMultisampleRenderTargetTexture ) {

const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU );

Expand Down Expand Up @@ -263,7 +252,7 @@ class WebGPUTextureUtils {
height: height,
depthOrArrayLayers: 1
},
sampleCount: backend.parameters.sampleCount,
sampleCount: backend.utils.getSampleCount( backend.renderer.samples ),
format: GPUTextureFormat.BGRA8Unorm,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
} );
Expand Down Expand Up @@ -312,7 +301,7 @@ class WebGPUTextureUtils {
depthTexture.image.width = width;
depthTexture.image.height = height;

this.createTexture( depthTexture, { sampleCount: backend.parameters.sampleCount, width, height } );
this.createTexture( depthTexture, { sampleCount: backend.utils.getSampleCount( backend.renderer.samples ), width, height } );

return backend.get( depthTexture ).texture;

Expand Down
27 changes: 24 additions & 3 deletions src/renderers/webgpu/utils/WebGPUUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,36 @@ class WebGPUUtils {

}

getSampleCount( renderContext ) {
getSampleCount( sampleCount ) {

let count = 1;

if ( sampleCount > 1 ) {

// WebGPU only supports power-of-two sample counts and 2 is not a valid value
count = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) );

if ( count === 2 ) {

count = 4;

}

}

return count;

}

getSampleCountRenderContext( renderContext ) {

if ( renderContext.textures !== null ) {

return renderContext.sampleCount;
return this.getSampleCount( renderContext.sampleCount );

}

return this.backend.parameters.sampleCount;
return this.getSampleCount( this.backend.renderer.samples );

}

Expand Down