739 lines
14 KiB
JavaScript
739 lines
14 KiB
JavaScript
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;
|