import { CullFaceNone, CullFaceBack, CullFaceFront, DoubleSide, BackSide, NormalBlending, NoBlending, CustomBlending, AddEquation, AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation, ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor, OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor, NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth } from 'three'; let initialized = false, equationToGL, factorToGL; class WebGLState { constructor( backend ) { this.backend = backend; this.gl = this.backend.gl; this.enabled = {}; this.currentFlipSided = null; this.currentCullFace = null; this.currentProgram = null; this.currentBlendingEnabled = false; this.currentBlending = null; this.currentBlendSrc = null; this.currentBlendDst = null; this.currentBlendSrcAlpha = null; this.currentBlendDstAlpha = null; this.currentPremultipledAlpha = null; this.currentPolygonOffsetFactor = null; this.currentPolygonOffsetUnits = null; this.currentColorMask = null; this.currentDepthFunc = null; this.currentDepthMask = null; this.currentStencilFunc = null; this.currentStencilRef = null; this.currentStencilFuncMask = null; this.currentStencilFail = null; this.currentStencilZFail = null; this.currentStencilZPass = null; this.currentStencilMask = null; this.currentLineWidth = null; this.currentBoundFramebuffers = {}; this.currentDrawbuffers = new WeakMap(); this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS ); this.currentTextureSlot = null; this.currentBoundTextures = {}; if ( initialized === false ) { this._init( this.gl ); initialized = true; } } _init( gl ) { // Store only WebGL constants here. equationToGL = { [ AddEquation ]: gl.FUNC_ADD, [ SubtractEquation ]: gl.FUNC_SUBTRACT, [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT }; factorToGL = { [ ZeroFactor ]: gl.ZERO, [ OneFactor ]: gl.ONE, [ SrcColorFactor ]: gl.SRC_COLOR, [ SrcAlphaFactor ]: gl.SRC_ALPHA, [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, [ DstColorFactor ]: gl.DST_COLOR, [ DstAlphaFactor ]: gl.DST_ALPHA, [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA }; } enable( id ) { const { enabled } = this; if ( enabled[ id ] !== true ) { this.gl.enable( id ); enabled[ id ] = true; } } disable( id ) { const { enabled } = this; if ( enabled[ id ] !== false ) { this.gl.disable( id ); enabled[ id ] = false; } } setFlipSided( flipSided ) { if ( this.currentFlipSided !== flipSided ) { const { gl } = this; if ( flipSided ) { gl.frontFace( gl.CW ); } else { gl.frontFace( gl.CCW ); } this.currentFlipSided = flipSided; } } setCullFace( cullFace ) { const { gl } = this; if ( cullFace !== CullFaceNone ) { this.enable( gl.CULL_FACE ); if ( cullFace !== this.currentCullFace ) { if ( cullFace === CullFaceBack ) { gl.cullFace( gl.BACK ); } else if ( cullFace === CullFaceFront ) { gl.cullFace( gl.FRONT ); } else { gl.cullFace( gl.FRONT_AND_BACK ); } } } else { this.disable( gl.CULL_FACE ); } this.currentCullFace = cullFace; } setLineWidth( width ) { const { currentLineWidth, gl } = this; if ( width !== currentLineWidth ) { gl.lineWidth( width ); this.currentLineWidth = width; } } setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { const { gl } = this; if ( blending === NoBlending ) { if ( this.currentBlendingEnabled === true ) { this.disable( gl.BLEND ); this.currentBlendingEnabled = false; } return; } if ( this.currentBlendingEnabled === false ) { this.enable( gl.BLEND ); this.currentBlendingEnabled = true; } if ( blending !== CustomBlending ) { if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) { if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) { gl.blendEquation( gl.FUNC_ADD ); this.currentBlendEquation = AddEquation; this.currentBlendEquationAlpha = AddEquation; } if ( premultipliedAlpha ) { switch ( blending ) { case NormalBlending: gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); break; case AdditiveBlending: gl.blendFunc( gl.ONE, gl.ONE ); break; case SubtractiveBlending: gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); break; case MultiplyBlending: gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); break; default: console.error( 'THREE.WebGLState: Invalid blending: ', blending ); break; } } else { switch ( blending ) { case NormalBlending: gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); break; case AdditiveBlending: gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); break; case SubtractiveBlending: gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); break; case MultiplyBlending: gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); break; default: console.error( 'THREE.WebGLState: Invalid blending: ', blending ); break; } } this.currentBlendSrc = null; this.currentBlendDst = null; this.currentBlendSrcAlpha = null; this.currentBlendDstAlpha = null; this.currentBlending = blending; this.currentPremultipledAlpha = premultipliedAlpha; } return; } // custom blending blendEquationAlpha = blendEquationAlpha || blendEquation; blendSrcAlpha = blendSrcAlpha || blendSrc; blendDstAlpha = blendDstAlpha || blendDst; if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) { gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); this.currentBlendEquation = blendEquation; this.currentBlendEquationAlpha = blendEquationAlpha; } if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) { gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); this.currentBlendSrc = blendSrc; this.currentBlendDst = blendDst; this.currentBlendSrcAlpha = blendSrcAlpha; this.currentBlendDstAlpha = blendDstAlpha; } this.currentBlending = blending; this.currentPremultipledAlpha = false; } setColorMask( colorMask ) { if ( this.currentColorMask !== colorMask ) { this.gl.colorMask( colorMask, colorMask, colorMask, colorMask ); this.currentColorMask = colorMask; } } setDepthTest( depthTest ) { const { gl } = this; if ( depthTest ) { this.enable( gl.DEPTH_TEST ); } else { this.disable( gl.DEPTH_TEST ); } } setDepthMask( depthMask ) { if ( this.currentDepthMask !== depthMask ) { this.gl.depthMask( depthMask ); this.currentDepthMask = depthMask; } } setDepthFunc( depthFunc ) { if ( this.currentDepthFunc !== depthFunc ) { const { gl } = this; switch ( depthFunc ) { case NeverDepth: gl.depthFunc( gl.NEVER ); break; case AlwaysDepth: gl.depthFunc( gl.ALWAYS ); break; case LessDepth: gl.depthFunc( gl.LESS ); break; case LessEqualDepth: gl.depthFunc( gl.LEQUAL ); break; case EqualDepth: gl.depthFunc( gl.EQUAL ); break; case GreaterEqualDepth: gl.depthFunc( gl.GEQUAL ); break; case GreaterDepth: gl.depthFunc( gl.GREATER ); break; case NotEqualDepth: gl.depthFunc( gl.NOTEQUAL ); break; default: gl.depthFunc( gl.LEQUAL ); } this.currentDepthFunc = depthFunc; } } setStencilTest( stencilTest ) { const { gl } = this; if ( stencilTest ) { this.enable( gl.STENCIL_TEST ); } else { this.disable( gl.STENCIL_TEST ); } } setStencilMask( stencilMask ) { if ( this.currentStencilMask !== stencilMask ) { this.gl.stencilMask( stencilMask ); this.currentStencilMask = stencilMask; } } setStencilFunc( stencilFunc, stencilRef, stencilMask ) { if ( this.currentStencilFunc !== stencilFunc || this.currentStencilRef !== stencilRef || this.currentStencilFuncMask !== stencilMask ) { this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); this.currentStencilFunc = stencilFunc; this.currentStencilRef = stencilRef; this.currentStencilFuncMask = stencilMask; } } setStencilOp( stencilFail, stencilZFail, stencilZPass ) { if ( this.currentStencilFail !== stencilFail || this.currentStencilZFail !== stencilZFail || this.currentStencilZPass !== stencilZPass ) { this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); this.currentStencilFail = stencilFail; this.currentStencilZFail = stencilZFail; this.currentStencilZPass = stencilZPass; } } setMaterial( material, frontFaceCW ) { const { gl } = this; material.side === DoubleSide ? this.disable( gl.CULL_FACE ) : this.enable( gl.CULL_FACE ); let flipSided = ( material.side === BackSide ); if ( frontFaceCW ) flipSided = ! flipSided; this.setFlipSided( flipSided ); ( material.blending === NormalBlending && material.transparent === false ) ? this.setBlending( NoBlending ) : this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); this.setDepthFunc( material.depthFunc ); this.setDepthTest( material.depthTest ); this.setDepthMask( material.depthWrite ); this.setColorMask( material.colorWrite ); const stencilWrite = material.stencilWrite; this.setStencilTest( stencilWrite ); if ( stencilWrite ) { this.setStencilMask( material.stencilWriteMask ); this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); } this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); material.alphaToCoverage === true ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); } setPolygonOffset( polygonOffset, factor, units ) { const { gl } = this; if ( polygonOffset ) { this.enable( gl.POLYGON_OFFSET_FILL ); if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) { gl.polygonOffset( factor, units ); this.currentPolygonOffsetFactor = factor; this.currentPolygonOffsetUnits = units; } } else { this.disable( gl.POLYGON_OFFSET_FILL ); } } useProgram( program ) { if ( this.currentProgram !== program ) { this.gl.useProgram( program ); this.currentProgram = program; return true; } return false; } // framebuffer bindFramebuffer( target, framebuffer ) { const { gl, currentBoundFramebuffers } = this; if ( currentBoundFramebuffers[ target ] !== framebuffer ) { gl.bindFramebuffer( target, framebuffer ); currentBoundFramebuffers[ target ] = framebuffer; // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER if ( target === gl.DRAW_FRAMEBUFFER ) { currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; } if ( target === gl.FRAMEBUFFER ) { currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; } return true; } return false; } drawBuffers( renderContext, framebuffer ) { const { gl } = this; let drawBuffers = []; let needsUpdate = false; if ( renderContext.textures !== null ) { drawBuffers = this.currentDrawbuffers.get( framebuffer ); if ( drawBuffers === undefined ) { drawBuffers = []; this.currentDrawbuffers.set( framebuffer, drawBuffers ); } const textures = renderContext.textures; if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { for ( let i = 0, il = textures.length; i < il; i ++ ) { drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; } drawBuffers.length = textures.length; needsUpdate = true; } } else { if ( drawBuffers[ 0 ] !== gl.BACK ) { drawBuffers[ 0 ] = gl.BACK; needsUpdate = true; } } if ( needsUpdate ) { gl.drawBuffers( drawBuffers ); } } // texture activeTexture( webglSlot ) { const { gl, currentTextureSlot, maxTextures } = this; if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; if ( currentTextureSlot !== webglSlot ) { gl.activeTexture( webglSlot ); this.currentTextureSlot = webglSlot; } } bindTexture( webglType, webglTexture, webglSlot ) { const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this; if ( webglSlot === undefined ) { if ( currentTextureSlot === null ) { webglSlot = gl.TEXTURE0 + maxTextures - 1; } else { webglSlot = currentTextureSlot; } } let boundTexture = currentBoundTextures[ webglSlot ]; if ( boundTexture === undefined ) { boundTexture = { type: undefined, texture: undefined }; currentBoundTextures[ webglSlot ] = boundTexture; } if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { if ( currentTextureSlot !== webglSlot ) { gl.activeTexture( webglSlot ); this.currentTextureSlot = webglSlot; } gl.bindTexture( webglType, webglTexture ); boundTexture.type = webglType; boundTexture.texture = webglTexture; } } unbindTexture() { const { gl, currentTextureSlot, currentBoundTextures } = this; const boundTexture = currentBoundTextures[ currentTextureSlot ]; if ( boundTexture !== undefined && boundTexture.type !== undefined ) { gl.bindTexture( boundTexture.type, null ); boundTexture.type = undefined; boundTexture.texture = undefined; } } } export default WebGLState;