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 ) { 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 ); // vertex buffers const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject ); // blending let blending; if ( material.transparent === true && material.blending !== NoBlending ) { blending = { alpha: this._getAlphaBlend( material ), color: this._getColorBlend( 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 vertexModule = backend.get( vertexProgram ).module; const fragmentModule = backend.get( fragmentProgram ).module; const primitiveState = this._getPrimitiveState( object, geometry, material ); const colorWriteMask = this._getColorWriteMask( material ); const depthCompare = this._getDepthCompare( material ); const colorFormat = utils.getCurrentColorFormat( renderObject.context ); const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); const sampleCount = utils.getSampleCount( renderObject.context ); pipelineData.pipeline = device.createRenderPipeline( { vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ), fragment: Object.assign( {}, fragmentModule, { targets: [ { format: colorFormat, blend: blending, writeMask: colorWriteMask } ] } ), 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 }, layout: 'auto' } ); } createComputePipeline( pipeline ) { const backend = this.backend; const device = backend.device; const computeProgram = backend.get( pipeline.computeProgram ).module; const pipelineGPU = backend.get( pipeline ); pipelineGPU.pipeline = device.createComputePipeline( { compute: computeProgram, layout: 'auto' } ); } _getAlphaBlend( material ) { const blending = material.blending; const premultipliedAlpha = material.premultipliedAlpha; let alphaBlend = undefined; switch ( blending ) { case NormalBlending: if ( premultipliedAlpha === false ) { alphaBlend = { srcFactor: GPUBlendFactor.One, dstFactor: GPUBlendFactor.OneMinusSrcAlpha, operation: GPUBlendOperation.Add }; } break; case AdditiveBlending: alphaBlend = { srcFactor: GPUBlendFactor.Zero, dstFactor: GPUBlendFactor.One, operation: GPUBlendOperation.Add }; break; case SubtractiveBlending: if ( premultipliedAlpha === true ) { alphaBlend = { srcFactor: GPUBlendFactor.OneMinusSrcColor, dstFactor: GPUBlendFactor.OneMinusSrcAlpha, operation: GPUBlendOperation.Add }; } break; case MultiplyBlending: if ( premultipliedAlpha === true ) { alphaBlend = { srcFactor: GPUBlendFactor.Zero, dstFactor: GPUBlendFactor.SrcAlpha, operation: GPUBlendOperation.Add }; } break; case CustomBlending: const blendSrcAlpha = material.blendSrcAlpha; const blendDstAlpha = material.blendDstAlpha; const blendEquationAlpha = material.blendEquationAlpha; if ( blendSrcAlpha !== null && blendDstAlpha !== null && blendEquationAlpha !== null ) { alphaBlend = { srcFactor: this._getBlendFactor( blendSrcAlpha ), dstFactor: this._getBlendFactor( blendDstAlpha ), operation: this._getBlendOperation( blendEquationAlpha ) }; } break; default: console.error( 'THREE.WebGPURenderer: Blending not supported.', blending ); } return alphaBlend; } _getBlendFactor( blend ) { let blendFactor; switch ( blend ) { case ZeroFactor: blendFactor = GPUBlendFactor.Zero; break; case OneFactor: blendFactor = GPUBlendFactor.One; break; case SrcColorFactor: blendFactor = GPUBlendFactor.SrcColor; break; case OneMinusSrcColorFactor: blendFactor = GPUBlendFactor.OneMinusSrcColor; break; case SrcAlphaFactor: blendFactor = GPUBlendFactor.SrcAlpha; break; case OneMinusSrcAlphaFactor: blendFactor = GPUBlendFactor.OneMinusSrcAlpha; break; case DstColorFactor: blendFactor = GPUBlendFactor.DstColor; 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.BlendColor; break; case OneMinusBlendColorFactor: blendFactor = GPUBlendFactor.OneMinusBlendColor; break; default: console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend ); } return blendFactor; } _getColorBlend( material ) { const blending = material.blending; const premultipliedAlpha = material.premultipliedAlpha; const colorBlend = { srcFactor: null, dstFactor: null, operation: null }; switch ( blending ) { case NormalBlending: colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha; colorBlend.dstFactor = GPUBlendFactor.OneMinusSrcAlpha; colorBlend.operation = GPUBlendOperation.Add; break; case AdditiveBlending: colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha; colorBlend.dstFactor = GPUBlendFactor.One; colorBlend.operation = GPUBlendOperation.Add; break; case SubtractiveBlending: colorBlend.srcFactor = GPUBlendFactor.Zero; colorBlend.dstFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.Zero : GPUBlendFactor.OneMinusSrcColor; colorBlend.operation = GPUBlendOperation.Add; break; case MultiplyBlending: colorBlend.srcFactor = GPUBlendFactor.Zero; colorBlend.dstFactor = GPUBlendFactor.SrcColor; colorBlend.operation = GPUBlendOperation.Add; break; case CustomBlending: colorBlend.srcFactor = this._getBlendFactor( material.blendSrc ); colorBlend.dstFactor = this._getBlendFactor( material.blendDst ); colorBlend.operation = this._getBlendOperation( material.blendEquation ); break; default: console.error( 'THREE.WebGPURenderer: Blending not supported.', blending ); } return colorBlend; } _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 ( object.isLine === true && object.isLineSegments !== true ) { const count = ( geometry.index ) ? geometry.index.count : geometry.attributes.position.count; descriptor.stripIndexFormat = ( count > 65535 ) ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16; // define data type for primitive restart value } switch ( material.side ) { case FrontSide: descriptor.frontFace = GPUFrontFace.CW; descriptor.cullMode = GPUCullMode.Front; break; case BackSide: descriptor.frontFace = GPUFrontFace.CW; descriptor.cullMode = GPUCullMode.Back; break; case DoubleSide: descriptor.frontFace = GPUFrontFace.CW; 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;