import { GPUIndexFormat, GPUCompareFunction, GPUFrontFace, GPUCullMode, GPUVertexFormat, GPUBlendFactor, GPUBlendOperation, BlendColorFactor, OneMinusBlendColorFactor, GPUColorWriteFlags, GPUStencilOperation, GPUInputStepMode } from './constants.js'; import { FrontSide, BackSide, DoubleSide, NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth, NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc, KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp, NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending, AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor } from 'three'; class WebGPURenderPipeline { constructor( device, utils ) { this.cacheKey = null; this.shaderAttributes = null; this.stageVertex = null; this.stageFragment = null; this.usedTimes = 0; this._device = device; this._utils = utils; } init( cacheKey, stageVertex, stageFragment, object, nodeBuilder ) { const material = object.material; const geometry = object.geometry; // determine shader attributes const shaderAttributes = this._getShaderAttributes( nodeBuilder, geometry ); // vertex buffers const vertexBuffers = []; for ( const attribute of shaderAttributes ) { const name = attribute.name; const geometryAttribute = geometry.getAttribute( name ); const stepMode = ( geometryAttribute !== undefined && geometryAttribute.isInstancedBufferAttribute ) ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; vertexBuffers.push( { arrayStride: attribute.arrayStride, attributes: [ { shaderLocation: attribute.slot, offset: attribute.offset, format: attribute.format } ], stepMode: stepMode } ); } this.cacheKey = cacheKey; this.shaderAttributes = shaderAttributes; this.stageVertex = stageVertex; this.stageFragment = stageFragment; // blending let alphaBlend = {}; let colorBlend = {}; if ( material.transparent === true && material.blending !== NoBlending ) { alphaBlend = this._getAlphaBlend( material ); colorBlend = 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 primitiveState = this._getPrimitiveState( object, material ); const colorWriteMask = this._getColorWriteMask( material ); const depthCompare = this._getDepthCompare( material ); const colorFormat = this._utils.getCurrentColorFormat(); const depthStencilFormat = this._utils.getCurrentDepthStencilFormat(); const sampleCount = this._utils.getSampleCount(); this.pipeline = this._device.createRenderPipeline( { vertex: Object.assign( {}, stageVertex.stage, { buffers: vertexBuffers } ), fragment: Object.assign( {}, stageFragment.stage, { targets: [ { format: colorFormat, blend: { alpha: alphaBlend, color: colorBlend }, 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' } ); } _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; } _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.WebGPURenderer: Blend equation not supported.', blendEquation ); } return blendOperation; } _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; } _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.WebGPURenderer: Invalid depth function.', depthFunc ); } } return depthCompare; } _getPrimitiveState( object, material ) { const descriptor = {}; descriptor.topology = this._utils.getPrimitiveTopology( object ); if ( object.isLine === true && object.isLineSegments !== true ) { const geometry = object.geometry; 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.WebGPURenderer: Unknown Material.side value.', material.side ); break; } return descriptor; } _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; } _getVertexFormat( type, bytesPerElement ) { // float if ( type === 'float' ) return GPUVertexFormat.Float32; if ( type === 'vec2' ) { if ( bytesPerElement === 2 ) { return GPUVertexFormat.Float16x2; } else { return GPUVertexFormat.Float32x2; } } if ( type === 'vec3' ) return GPUVertexFormat.Float32x3; if ( type === 'vec4' ) { if ( bytesPerElement === 2 ) { return GPUVertexFormat.Float16x4; } else { return GPUVertexFormat.Float32x4; } } // int if ( type === 'int' ) return GPUVertexFormat.Sint32; if ( type === 'ivec2' ) { if ( bytesPerElement === 1 ) { return GPUVertexFormat.Sint8x2; } else if ( bytesPerElement === 2 ) { return GPUVertexFormat.Sint16x2; } else { return GPUVertexFormat.Sint32x2; } } if ( type === 'ivec3' ) return GPUVertexFormat.Sint32x3; if ( type === 'ivec4' ) { if ( bytesPerElement === 1 ) { return GPUVertexFormat.Sint8x4; } else if ( bytesPerElement === 2 ) { return GPUVertexFormat.Sint16x4; } else { return GPUVertexFormat.Sint32x4; } } // uint if ( type === 'uint' ) return GPUVertexFormat.Uint32; if ( type === 'uvec2' ) { if ( bytesPerElement === 1 ) { return GPUVertexFormat.Uint8x2; } else if ( bytesPerElement === 2 ) { return GPUVertexFormat.Uint16x2; } else { return GPUVertexFormat.Uint32x2; } } if ( type === 'uvec3' ) return GPUVertexFormat.Uint32x3; if ( type === 'uvec4' ) { if ( bytesPerElement === 1 ) { return GPUVertexFormat.Uint8x4; } else if ( bytesPerElement === 2 ) { return GPUVertexFormat.Uint16x4; } else { return GPUVertexFormat.Uint32x4; } } console.error( 'THREE.WebGPURenderer: Shader variable type not supported yet.', type ); } _getShaderAttributes( nodeBuilder, geometry ) { const nodeAttributes = nodeBuilder.attributes; const attributes = []; for ( let slot = 0; slot < nodeAttributes.length; slot ++ ) { const nodeAttribute = nodeAttributes[ slot ]; const name = nodeAttribute.name; const type = nodeAttribute.type; const geometryAttribute = geometry.getAttribute( name ); const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT; const format = this._getVertexFormat( type, bytesPerElement ); let arrayStride = geometryAttribute.itemSize * bytesPerElement; let offset = 0; if ( geometryAttribute.isInterleavedBufferAttribute === true ) { // @TODO: It can be optimized for "vertexBuffers" on RenderPipeline arrayStride = geometryAttribute.data.stride * bytesPerElement; offset = geometryAttribute.offset * bytesPerElement; } attributes.push( { name, arrayStride, offset, format, slot } ); } return attributes; } } export default WebGPURenderPipeline;