import { BlendColorFactor, OneMinusBlendColorFactor, } from '../../common/Constants.js'; import { GPUFrontFace, GPUCullMode, GPUColorWriteFlags, GPUCompareFunction, GPUBlendFactor, GPUBlendOperation, GPUIndexFormat, GPUStencilOperation } from './WebGPUConstants.js'; import { FrontSide, BackSide, DoubleSide, NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth, NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending, ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstColorFactor, OneMinusDstColorFactor, DstAlphaFactor, OneMinusDstAlphaFactor, SrcAlphaSaturateFactor, AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp, NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc } from 'three'; class WebGPUPipelineUtils { constructor( backend ) { this.backend = backend; } createRenderPipeline( renderObject, promises ) { const { object, material, geometry, pipeline } = renderObject; const { vertexProgram, fragmentProgram } = pipeline; const backend = this.backend; const device = backend.device; const utils = backend.utils; const pipelineData = backend.get( pipeline ); const bindingsData = backend.get( renderObject.getBindings() ); // vertex buffers const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject ); // blending let blending; if ( material.transparent === true && material.blending !== NoBlending ) { blending = this._getBlending( material ); } // stencil let stencilFront = {}; if ( material.stencilWrite === true ) { stencilFront = { compare: this._getStencilCompare( material ), failOp: this._getStencilOperation( material.stencilFail ), depthFailOp: this._getStencilOperation( material.stencilZFail ), passOp: this._getStencilOperation( material.stencilZPass ) }; } const colorWriteMask = this._getColorWriteMask( material ); const targets = []; if ( renderObject.context.textures !== null ) { const textures = renderObject.context.textures; for ( let i = 0; i < textures.length; i ++ ) { const colorFormat = utils.getTextureFormatGPU( textures[ i ] ); targets.push( { format: colorFormat, blend: blending, writeMask: colorWriteMask } ); } } else { const colorFormat = utils.getCurrentColorFormat( renderObject.context ); targets.push( { format: colorFormat, blend: blending, writeMask: colorWriteMask } ); } const vertexModule = backend.get( vertexProgram ).module; const fragmentModule = backend.get( fragmentProgram ).module; const primitiveState = this._getPrimitiveState( object, geometry, material ); const depthCompare = this._getDepthCompare( material ); const depthStencilFormat = utils.getCurrentDepthStencilFormat( 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; } } const pipelineDescriptor = { vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ), fragment: Object.assign( {}, fragmentModule, { targets } ), primitive: primitiveState, depthStencil: { format: depthStencilFormat, depthWriteEnabled: material.depthWrite, depthCompare: depthCompare, stencilFront: stencilFront, stencilBack: {}, // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used) stencilReadMask: material.stencilFuncMask, stencilWriteMask: material.stencilWriteMask }, multisample: { count: sampleCount, alphaToCoverageEnabled: material.alphaToCoverage }, layout: device.createPipelineLayout( { bindGroupLayouts: [ bindingsData.layout ] } ) }; if ( promises === null ) { pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor ); } else { const p = new Promise( ( resolve /*, reject*/ ) => { device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => { pipelineData.pipeline = pipeline; resolve(); } ); } ); promises.push( p ); } } createComputePipeline( pipeline, bindings ) { const backend = this.backend; const device = backend.device; const computeProgram = backend.get( pipeline.computeProgram ).module; const pipelineGPU = backend.get( pipeline ); const bindingsData = backend.get( bindings ); pipelineGPU.pipeline = device.createComputePipeline( { compute: computeProgram, layout: device.createPipelineLayout( { bindGroupLayouts: [ bindingsData.layout ] } ) } ); } _getBlending( material ) { let color, alpha; const blending = material.blending; if ( blending === CustomBlending ) { const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : GPUBlendFactor.One; const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : GPUBlendFactor.Zero; const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : GPUBlendFactor.Add; color = { srcFactor: this._getBlendFactor( material.blendSrc ), dstFactor: this._getBlendFactor( material.blendDst ), operation: this._getBlendOperation( material.blendEquation ) }; alpha = { srcFactor: this._getBlendFactor( blendSrcAlpha ), dstFactor: this._getBlendFactor( blendDstAlpha ), operation: this._getBlendOperation( blendEquationAlpha ) }; } else { const premultipliedAlpha = material.premultipliedAlpha; const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => { color = { srcFactor: srcRGB, dstFactor: dstRGB, operation: GPUBlendOperation.Add }; alpha = { srcFactor: srcAlpha, dstFactor: dstAlpha, operation: GPUBlendOperation.Add }; }; if ( premultipliedAlpha ) { switch ( blending ) { case NormalBlending: setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); break; case AdditiveBlending: setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One ); break; case SubtractiveBlending: setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One ); break; case MultiplyBlending: setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.SrcAlpha ); break; } } else { switch ( blending ) { case NormalBlending: setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); break; case AdditiveBlending: setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.SrcAlpha, GPUBlendFactor.One ); break; case SubtractiveBlending: setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One ); break; case MultiplyBlending: setBlend( GPUBlendFactor.Zero, GPUBlendFactor.Src, GPUBlendFactor.Zero, GPUBlendFactor.Src ); break; } } } if ( color !== undefined && alpha !== undefined ) { return { color, alpha }; } else { console.error( 'THREE.WebGPURenderer: Invalid blending: ', blending ); } } _getBlendFactor( blend ) { let blendFactor; switch ( blend ) { case ZeroFactor: blendFactor = GPUBlendFactor.Zero; break; case OneFactor: blendFactor = GPUBlendFactor.One; break; case SrcColorFactor: blendFactor = GPUBlendFactor.Src; break; case OneMinusSrcColorFactor: blendFactor = GPUBlendFactor.OneMinusSrc; break; case SrcAlphaFactor: blendFactor = GPUBlendFactor.SrcAlpha; break; case OneMinusSrcAlphaFactor: blendFactor = GPUBlendFactor.OneMinusSrcAlpha; break; case DstColorFactor: blendFactor = GPUBlendFactor.Dst; break; case OneMinusDstColorFactor: blendFactor = GPUBlendFactor.OneMinusDstColor; break; case DstAlphaFactor: blendFactor = GPUBlendFactor.DstAlpha; break; case OneMinusDstAlphaFactor: blendFactor = GPUBlendFactor.OneMinusDstAlpha; break; case SrcAlphaSaturateFactor: blendFactor = GPUBlendFactor.SrcAlphaSaturated; break; case BlendColorFactor: blendFactor = GPUBlendFactor.Constant; break; case OneMinusBlendColorFactor: blendFactor = GPUBlendFactor.OneMinusConstant; break; default: console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend ); } return blendFactor; } _getStencilCompare( material ) { let stencilCompare; const stencilFunc = material.stencilFunc; switch ( stencilFunc ) { case NeverStencilFunc: stencilCompare = GPUCompareFunction.Never; break; case AlwaysStencilFunc: stencilCompare = GPUCompareFunction.Always; break; case LessStencilFunc: stencilCompare = GPUCompareFunction.Less; break; case LessEqualStencilFunc: stencilCompare = GPUCompareFunction.LessEqual; break; case EqualStencilFunc: stencilCompare = GPUCompareFunction.Equal; break; case GreaterEqualStencilFunc: stencilCompare = GPUCompareFunction.GreaterEqual; break; case GreaterStencilFunc: stencilCompare = GPUCompareFunction.Greater; break; case NotEqualStencilFunc: stencilCompare = GPUCompareFunction.NotEqual; break; default: console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc ); } return stencilCompare; } _getStencilOperation( op ) { let stencilOperation; switch ( op ) { case KeepStencilOp: stencilOperation = GPUStencilOperation.Keep; break; case ZeroStencilOp: stencilOperation = GPUStencilOperation.Zero; break; case ReplaceStencilOp: stencilOperation = GPUStencilOperation.Replace; break; case InvertStencilOp: stencilOperation = GPUStencilOperation.Invert; break; case IncrementStencilOp: stencilOperation = GPUStencilOperation.IncrementClamp; break; case DecrementStencilOp: stencilOperation = GPUStencilOperation.DecrementClamp; break; case IncrementWrapStencilOp: stencilOperation = GPUStencilOperation.IncrementWrap; break; case DecrementWrapStencilOp: stencilOperation = GPUStencilOperation.DecrementWrap; break; default: console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation ); } return stencilOperation; } _getBlendOperation( blendEquation ) { let blendOperation; switch ( blendEquation ) { case AddEquation: blendOperation = GPUBlendOperation.Add; break; case SubtractEquation: blendOperation = GPUBlendOperation.Subtract; break; case ReverseSubtractEquation: blendOperation = GPUBlendOperation.ReverseSubtract; break; case MinEquation: blendOperation = GPUBlendOperation.Min; break; case MaxEquation: blendOperation = GPUBlendOperation.Max; break; default: console.error( 'THREE.WebGPUPipelineUtils: Blend equation not supported.', blendEquation ); } return blendOperation; } _getPrimitiveState( object, geometry, material ) { const descriptor = {}; const utils = this.backend.utils; descriptor.topology = utils.getPrimitiveTopology( object, material ); if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) { descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; } switch ( material.side ) { case FrontSide: descriptor.frontFace = GPUFrontFace.CCW; descriptor.cullMode = GPUCullMode.Back; break; case BackSide: descriptor.frontFace = GPUFrontFace.CCW; descriptor.cullMode = GPUCullMode.Front; break; case DoubleSide: descriptor.frontFace = GPUFrontFace.CCW; descriptor.cullMode = GPUCullMode.None; break; default: console.error( 'THREE.WebGPUPipelineUtils: Unknown material.side value.', material.side ); break; } return descriptor; } _getColorWriteMask( material ) { return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None; } _getDepthCompare( material ) { let depthCompare; if ( material.depthTest === false ) { depthCompare = GPUCompareFunction.Always; } else { const depthFunc = material.depthFunc; switch ( depthFunc ) { case NeverDepth: depthCompare = GPUCompareFunction.Never; break; case AlwaysDepth: depthCompare = GPUCompareFunction.Always; break; case LessDepth: depthCompare = GPUCompareFunction.Less; break; case LessEqualDepth: depthCompare = GPUCompareFunction.LessEqual; break; case EqualDepth: depthCompare = GPUCompareFunction.Equal; break; case GreaterEqualDepth: depthCompare = GPUCompareFunction.GreaterEqual; break; case GreaterDepth: depthCompare = GPUCompareFunction.Greater; break; case NotEqualDepth: depthCompare = GPUCompareFunction.NotEqual; break; default: console.error( 'THREE.WebGPUPipelineUtils: Invalid depth function.', depthFunc ); } } return depthCompare; } } export default WebGPUPipelineUtils;