Skip to content

Commit

Permalink
WebGPURenderer: hardware clipping support. (#28578)
Browse files Browse the repository at this point in the history
* hardware clipping

* misc fixes

lint

---------

Co-authored-by: aardgoose <[email protected]>
  • Loading branch information
aardgoose and aardgoose authored Nov 6, 2024
1 parent d82b592 commit 8bc792b
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 26 deletions.
27 changes: 26 additions & 1 deletion src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { lightingContext } from '../../nodes/lighting/LightingContextNode.js';
import IrradianceNode from '../../nodes/lighting/IrradianceNode.js';
import { depth, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js';
import { cameraFar, cameraNear } from '../../nodes/accessors/Camera.js';
import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js';
import { clipping, clippingAlpha, hardwareClipping } from '../../nodes/accessors/ClippingNode.js';
import NodeMaterialObserver from './manager/NodeMaterialObserver.js';
import getAlphaHashThreshold from '../../nodes/functions/material/getAlphaHashThreshold.js';

Expand Down Expand Up @@ -50,6 +50,7 @@ class NodeMaterial extends Material {

this.fog = true;
this.lights = false;
this.hardwareClipping = false;

this.lightsNode = null;
this.envNode = null;
Expand Down Expand Up @@ -241,6 +242,28 @@ class NodeMaterial extends Material {

}

setupHardwareClipping( builder ) {

this.hardwareClipping = false;

if ( builder.clippingContext === null ) return;

const candidateCount = builder.clippingContext.unionPlanes.length;

// 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances

if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {

builder.stack.add( hardwareClipping() );

this.hardwareClipping = true;

}

return;

}

setupDepth( builder ) {

const { renderer, camera } = builder;
Expand Down Expand Up @@ -330,6 +353,8 @@ class NodeMaterial extends Material {

}

this.setupHardwareClipping( builder );

const mvp = modelViewProjection();

builder.context.vertex = builder.removeStack();
Expand Down
26 changes: 26 additions & 0 deletions src/nodes/accessors/BuiltinNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Node from '../core/Node.js';
import { nodeProxy } from '../tsl/TSLBase.js';

class BuiltinNode extends Node {

constructor( name ) {

super( 'float' );

this.name = name;

this.isBuiltinNode = true;

}

generate( /* builder */ ) {

return this.name;

}

}

export default BuiltinNode;

export const builtin = nodeProxy( BuiltinNode );
53 changes: 38 additions & 15 deletions src/nodes/accessors/ClippingNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { diffuseColor } from '../core/PropertyNode.js';
import { Loop } from '../utils/LoopNode.js';
import { smoothstep } from '../math/MathNode.js';
import { uniformArray } from './UniformArrayNode.js';
import { builtin } from './BuiltinNode.js';

class ClippingNode extends Node {

Expand All @@ -30,11 +31,16 @@ class ClippingNode extends Node {
const clippingContext = builder.clippingContext;
const { intersectionPlanes, unionPlanes } = clippingContext;

this.hardwareClipping = builder.material.hardwareClipping;

if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {

return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );

} else if ( this.scope === ClippingNode.HARDWARE ) {

return this.setupHardwareClipping( unionPlanes, builder );

} else {

return this.setupDefault( intersectionPlanes, unionPlanes );
Expand All @@ -54,15 +60,13 @@ class ClippingNode extends Node {

const numUnionPlanes = unionPlanes.length;

if ( numUnionPlanes > 0 ) {
if ( ! this.hardwareClipping && numUnionPlanes > 0 ) {

const clippingPlanes = uniformArray( unionPlanes );

let plane;

Loop( numUnionPlanes, ( { i } ) => {

plane = clippingPlanes.element( i );
const plane = clippingPlanes.element( i );

distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
Expand All @@ -80,11 +84,9 @@ class ClippingNode extends Node {
const clippingPlanes = uniformArray( intersectionPlanes );
const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );

let plane;

Loop( numIntersectionPlanes, ( { i } ) => {

plane = clippingPlanes.element( i );
const plane = clippingPlanes.element( i );

distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
Expand All @@ -111,15 +113,13 @@ class ClippingNode extends Node {

const numUnionPlanes = unionPlanes.length;

if ( numUnionPlanes > 0 ) {
if ( ! this.hardwareClipping && numUnionPlanes > 0 ) {

const clippingPlanes = uniformArray( unionPlanes );

let plane;

Loop( numUnionPlanes, ( { i } ) => {

plane = clippingPlanes.element( i );
const plane = clippingPlanes.element( i );
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();

} );
Expand All @@ -133,11 +133,9 @@ class ClippingNode extends Node {
const clippingPlanes = uniformArray( intersectionPlanes );
const clipped = bool( true ).toVar( 'clipped' );

let plane;

Loop( numIntersectionPlanes, ( { i } ) => {

plane = clippingPlanes.element( i );
const plane = clippingPlanes.element( i );
clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );

} );
Expand All @@ -150,13 +148,38 @@ class ClippingNode extends Node {

}

setupHardwareClipping( unionPlanes, builder ) {

const numUnionPlanes = unionPlanes.length;

builder.enableHardwareClipping( numUnionPlanes );

return Fn( () => {

const clippingPlanes = uniformArray( unionPlanes );
const hw_clip_distances = builtin( builder.getClipDistance() );

Loop( numUnionPlanes, ( { i } ) => {

const plane = clippingPlanes.element( i );

const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate();
hw_clip_distances.element( i ).assign( distance );

} );

} )();

}

}

ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
ClippingNode.DEFAULT = 'default';
ClippingNode.HARDWARE = 'hardware';

export default ClippingNode;

export const clipping = () => nodeObject( new ClippingNode() );

export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) );
export const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) );
6 changes: 6 additions & 0 deletions src/renderers/common/ClippingContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ class ClippingContext {

}

get unionClippingCount() {

return this.unionPlanes.length;

}

}

export default ClippingContext;
6 changes: 6 additions & 0 deletions src/renderers/common/RenderObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export default class RenderObject {

}

get hardwareClippingPlanes() {

return this.material.hardwareClipping === true ? this.clippingContext.unionClippingCount : 0;

}

getNodeBuilderState() {

return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) );
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/webgl-fallback/WebGLBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ class WebGLBackend extends Backend {

draw( renderObject/*, info*/ ) {

const { object, pipeline, material, context } = renderObject;
const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject;
const { programGPU } = this.get( pipeline );

const { gl, state } = this;
Expand All @@ -658,7 +658,7 @@ class WebGLBackend extends Backend {

const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );

state.setMaterial( material, frontFaceCW );
state.setMaterial( material, frontFaceCW, hardwareClippingPlanes );

state.useProgram( programGPU );

Expand Down
47 changes: 40 additions & 7 deletions src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class GLSLNodeBuilder extends NodeBuilder {
this.uniformGroups = {};
this.transforms = [];
this.extensions = {};
this.builtins = { vertex: [], fragment: [], compute: [] };

this.useComparisonMethod = true;

Expand Down Expand Up @@ -595,6 +596,12 @@ ${ flowData.code }

}

for ( const builtin of this.builtins[ shaderStage ] ) {

snippet += `${builtin};\n`;

}

return snippet;

}
Expand Down Expand Up @@ -701,24 +708,42 @@ ${ flowData.code }

}

getClipDistance() {

return 'gl_ClipDistance';

}

isAvailable( name ) {

let result = supports[ name ];

if ( result === undefined ) {

if ( name === 'float32Filterable' ) {
let extensionName;

const extensions = this.renderer.backend.extensions;
result = false;

if ( extensions.has( 'OES_texture_float_linear' ) ) {
switch ( name ) {

extensions.get( 'OES_texture_float_linear' );
result = true;
case 'float32Filterable':
extensionName = 'OES_texture_float_linear';
break;

} else {
case 'clipDistance':
extensionName = 'WEBGL_clip_cull_distance';
break;

result = false;
}

if ( extensionName !== undefined ) {

const extensions = this.renderer.backend.extensions;

if ( extensions.has( extensionName ) ) {

extensions.get( extensionName );
result = true;

}

Expand All @@ -738,6 +763,14 @@ ${ flowData.code }

}

enableHardwareClipping( planeCount ) {

this.enableExtension( 'GL_ANGLE_clip_cull_distance', 'require' );

this.builtins[ 'vertex' ].push( `out float gl_ClipDistance[ ${ planeCount } ]` );

}

registerTransform( varyingName, attributeNode ) {

this.transforms.push( { varyingName, attributeNode } );
Expand Down
27 changes: 26 additions & 1 deletion src/renderers/webgl-fallback/utils/WebGLState.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class WebGLState {
this.currentStencilZPass = null;
this.currentStencilMask = null;
this.currentLineWidth = null;
this.currentClippingPlanes = 0;

this.currentBoundFramebuffers = {};
this.currentDrawbuffers = new WeakMap();
Expand Down Expand Up @@ -478,7 +479,7 @@ class WebGLState {

}

setMaterial( material, frontFaceCW ) {
setMaterial( material, frontFaceCW, hardwareClippingPlanes ) {

const { gl } = this;

Expand Down Expand Up @@ -516,6 +517,30 @@ class WebGLState {
? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
: this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE );

if ( hardwareClippingPlanes > 0 ) {

if ( this.currentClippingPlanes !== hardwareClippingPlanes ) {

const CLIP_DISTANCE0_WEBGL = 0x3000;

for ( let i = 0; i < 8; i ++ ) {

if ( i < hardwareClippingPlanes ) {

this.enable( CLIP_DISTANCE0_WEBGL + i );

} else {

this.disable( CLIP_DISTANCE0_WEBGL + i );

}

}

}

}

}

setPolygonOffset( polygonOffset, factor, units ) {
Expand Down
Loading

0 comments on commit 8bc792b

Please sign in to comment.