import BoundingRectangle from "../Core/BoundingRectangle.js"; import Color from "../Core/Color.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import WindingOrder from "../Core/WindingOrder.js"; import ContextLimits from "./ContextLimits.js"; import freezeRenderState from "./freezeRenderState.js"; function validateBlendEquation(blendEquation) { return ( blendEquation === WebGLConstants.FUNC_ADD || blendEquation === WebGLConstants.FUNC_SUBTRACT || blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT || blendEquation === WebGLConstants.MIN || blendEquation === WebGLConstants.MAX ); } function validateBlendFunction(blendFunction) { return ( blendFunction === WebGLConstants.ZERO || blendFunction === WebGLConstants.ONE || blendFunction === WebGLConstants.SRC_COLOR || blendFunction === WebGLConstants.ONE_MINUS_SRC_COLOR || blendFunction === WebGLConstants.DST_COLOR || blendFunction === WebGLConstants.ONE_MINUS_DST_COLOR || blendFunction === WebGLConstants.SRC_ALPHA || blendFunction === WebGLConstants.ONE_MINUS_SRC_ALPHA || blendFunction === WebGLConstants.DST_ALPHA || blendFunction === WebGLConstants.ONE_MINUS_DST_ALPHA || blendFunction === WebGLConstants.CONSTANT_COLOR || blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_COLOR || blendFunction === WebGLConstants.CONSTANT_ALPHA || blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_ALPHA || blendFunction === WebGLConstants.SRC_ALPHA_SATURATE ); } function validateCullFace(cullFace) { return ( cullFace === WebGLConstants.FRONT || cullFace === WebGLConstants.BACK || cullFace === WebGLConstants.FRONT_AND_BACK ); } function validateDepthFunction(depthFunction) { return ( depthFunction === WebGLConstants.NEVER || depthFunction === WebGLConstants.LESS || depthFunction === WebGLConstants.EQUAL || depthFunction === WebGLConstants.LEQUAL || depthFunction === WebGLConstants.GREATER || depthFunction === WebGLConstants.NOTEQUAL || depthFunction === WebGLConstants.GEQUAL || depthFunction === WebGLConstants.ALWAYS ); } function validateStencilFunction(stencilFunction) { return ( stencilFunction === WebGLConstants.NEVER || stencilFunction === WebGLConstants.LESS || stencilFunction === WebGLConstants.EQUAL || stencilFunction === WebGLConstants.LEQUAL || stencilFunction === WebGLConstants.GREATER || stencilFunction === WebGLConstants.NOTEQUAL || stencilFunction === WebGLConstants.GEQUAL || stencilFunction === WebGLConstants.ALWAYS ); } function validateStencilOperation(stencilOperation) { return ( stencilOperation === WebGLConstants.ZERO || stencilOperation === WebGLConstants.KEEP || stencilOperation === WebGLConstants.REPLACE || stencilOperation === WebGLConstants.INCR || stencilOperation === WebGLConstants.DECR || stencilOperation === WebGLConstants.INVERT || stencilOperation === WebGLConstants.INCR_WRAP || stencilOperation === WebGLConstants.DECR_WRAP ); } /** * @private */ function RenderState(renderState) { const rs = defaultValue(renderState, defaultValue.EMPTY_OBJECT); const cull = defaultValue(rs.cull, defaultValue.EMPTY_OBJECT); const polygonOffset = defaultValue( rs.polygonOffset, defaultValue.EMPTY_OBJECT ); const scissorTest = defaultValue(rs.scissorTest, defaultValue.EMPTY_OBJECT); const scissorTestRectangle = defaultValue( scissorTest.rectangle, defaultValue.EMPTY_OBJECT ); const depthRange = defaultValue(rs.depthRange, defaultValue.EMPTY_OBJECT); const depthTest = defaultValue(rs.depthTest, defaultValue.EMPTY_OBJECT); const colorMask = defaultValue(rs.colorMask, defaultValue.EMPTY_OBJECT); const blending = defaultValue(rs.blending, defaultValue.EMPTY_OBJECT); const blendingColor = defaultValue(blending.color, defaultValue.EMPTY_OBJECT); const stencilTest = defaultValue(rs.stencilTest, defaultValue.EMPTY_OBJECT); const stencilTestFrontOperation = defaultValue( stencilTest.frontOperation, defaultValue.EMPTY_OBJECT ); const stencilTestBackOperation = defaultValue( stencilTest.backOperation, defaultValue.EMPTY_OBJECT ); const sampleCoverage = defaultValue( rs.sampleCoverage, defaultValue.EMPTY_OBJECT ); const viewport = rs.viewport; this.frontFace = defaultValue(rs.frontFace, WindingOrder.COUNTER_CLOCKWISE); this.cull = { enabled: defaultValue(cull.enabled, false), face: defaultValue(cull.face, WebGLConstants.BACK), }; this.lineWidth = defaultValue(rs.lineWidth, 1.0); this.polygonOffset = { enabled: defaultValue(polygonOffset.enabled, false), factor: defaultValue(polygonOffset.factor, 0), units: defaultValue(polygonOffset.units, 0), }; this.scissorTest = { enabled: defaultValue(scissorTest.enabled, false), rectangle: BoundingRectangle.clone(scissorTestRectangle), }; this.depthRange = { near: defaultValue(depthRange.near, 0), far: defaultValue(depthRange.far, 1), }; this.depthTest = { enabled: defaultValue(depthTest.enabled, false), func: defaultValue(depthTest.func, WebGLConstants.LESS), // func, because function is a JavaScript keyword }; this.colorMask = { red: defaultValue(colorMask.red, true), green: defaultValue(colorMask.green, true), blue: defaultValue(colorMask.blue, true), alpha: defaultValue(colorMask.alpha, true), }; this.depthMask = defaultValue(rs.depthMask, true); this.stencilMask = defaultValue(rs.stencilMask, ~0); this.blending = { enabled: defaultValue(blending.enabled, false), color: new Color( defaultValue(blendingColor.red, 0.0), defaultValue(blendingColor.green, 0.0), defaultValue(blendingColor.blue, 0.0), defaultValue(blendingColor.alpha, 0.0) ), equationRgb: defaultValue(blending.equationRgb, WebGLConstants.FUNC_ADD), equationAlpha: defaultValue( blending.equationAlpha, WebGLConstants.FUNC_ADD ), functionSourceRgb: defaultValue( blending.functionSourceRgb, WebGLConstants.ONE ), functionSourceAlpha: defaultValue( blending.functionSourceAlpha, WebGLConstants.ONE ), functionDestinationRgb: defaultValue( blending.functionDestinationRgb, WebGLConstants.ZERO ), functionDestinationAlpha: defaultValue( blending.functionDestinationAlpha, WebGLConstants.ZERO ), }; this.stencilTest = { enabled: defaultValue(stencilTest.enabled, false), frontFunction: defaultValue( stencilTest.frontFunction, WebGLConstants.ALWAYS ), backFunction: defaultValue(stencilTest.backFunction, WebGLConstants.ALWAYS), reference: defaultValue(stencilTest.reference, 0), mask: defaultValue(stencilTest.mask, ~0), frontOperation: { fail: defaultValue(stencilTestFrontOperation.fail, WebGLConstants.KEEP), zFail: defaultValue(stencilTestFrontOperation.zFail, WebGLConstants.KEEP), zPass: defaultValue(stencilTestFrontOperation.zPass, WebGLConstants.KEEP), }, backOperation: { fail: defaultValue(stencilTestBackOperation.fail, WebGLConstants.KEEP), zFail: defaultValue(stencilTestBackOperation.zFail, WebGLConstants.KEEP), zPass: defaultValue(stencilTestBackOperation.zPass, WebGLConstants.KEEP), }, }; this.sampleCoverage = { enabled: defaultValue(sampleCoverage.enabled, false), value: defaultValue(sampleCoverage.value, 1.0), invert: defaultValue(sampleCoverage.invert, false), }; this.viewport = defined(viewport) ? new BoundingRectangle( viewport.x, viewport.y, viewport.width, viewport.height ) : undefined; //>>includeStart('debug', pragmas.debug); if ( this.lineWidth < ContextLimits.minimumAliasedLineWidth || this.lineWidth > ContextLimits.maximumAliasedLineWidth ) { throw new DeveloperError( "renderState.lineWidth is out of range. Check minimumAliasedLineWidth and maximumAliasedLineWidth." ); } if (!WindingOrder.validate(this.frontFace)) { throw new DeveloperError("Invalid renderState.frontFace."); } if (!validateCullFace(this.cull.face)) { throw new DeveloperError("Invalid renderState.cull.face."); } if ( this.scissorTest.rectangle.width < 0 || this.scissorTest.rectangle.height < 0 ) { throw new DeveloperError( "renderState.scissorTest.rectangle.width and renderState.scissorTest.rectangle.height must be greater than or equal to zero." ); } if (this.depthRange.near > this.depthRange.far) { // WebGL specific - not an error in GL ES throw new DeveloperError( "renderState.depthRange.near can not be greater than renderState.depthRange.far." ); } if (this.depthRange.near < 0) { // Would be clamped by GL throw new DeveloperError( "renderState.depthRange.near must be greater than or equal to zero." ); } if (this.depthRange.far > 1) { // Would be clamped by GL throw new DeveloperError( "renderState.depthRange.far must be less than or equal to one." ); } if (!validateDepthFunction(this.depthTest.func)) { throw new DeveloperError("Invalid renderState.depthTest.func."); } if ( this.blending.color.red < 0.0 || this.blending.color.red > 1.0 || this.blending.color.green < 0.0 || this.blending.color.green > 1.0 || this.blending.color.blue < 0.0 || this.blending.color.blue > 1.0 || this.blending.color.alpha < 0.0 || this.blending.color.alpha > 1.0 ) { // Would be clamped by GL throw new DeveloperError( "renderState.blending.color components must be greater than or equal to zero and less than or equal to one." ); } if (!validateBlendEquation(this.blending.equationRgb)) { throw new DeveloperError("Invalid renderState.blending.equationRgb."); } if (!validateBlendEquation(this.blending.equationAlpha)) { throw new DeveloperError("Invalid renderState.blending.equationAlpha."); } if (!validateBlendFunction(this.blending.functionSourceRgb)) { throw new DeveloperError("Invalid renderState.blending.functionSourceRgb."); } if (!validateBlendFunction(this.blending.functionSourceAlpha)) { throw new DeveloperError( "Invalid renderState.blending.functionSourceAlpha." ); } if (!validateBlendFunction(this.blending.functionDestinationRgb)) { throw new DeveloperError( "Invalid renderState.blending.functionDestinationRgb." ); } if (!validateBlendFunction(this.blending.functionDestinationAlpha)) { throw new DeveloperError( "Invalid renderState.blending.functionDestinationAlpha." ); } if (!validateStencilFunction(this.stencilTest.frontFunction)) { throw new DeveloperError("Invalid renderState.stencilTest.frontFunction."); } if (!validateStencilFunction(this.stencilTest.backFunction)) { throw new DeveloperError("Invalid renderState.stencilTest.backFunction."); } if (!validateStencilOperation(this.stencilTest.frontOperation.fail)) { throw new DeveloperError( "Invalid renderState.stencilTest.frontOperation.fail." ); } if (!validateStencilOperation(this.stencilTest.frontOperation.zFail)) { throw new DeveloperError( "Invalid renderState.stencilTest.frontOperation.zFail." ); } if (!validateStencilOperation(this.stencilTest.frontOperation.zPass)) { throw new DeveloperError( "Invalid renderState.stencilTest.frontOperation.zPass." ); } if (!validateStencilOperation(this.stencilTest.backOperation.fail)) { throw new DeveloperError( "Invalid renderState.stencilTest.backOperation.fail." ); } if (!validateStencilOperation(this.stencilTest.backOperation.zFail)) { throw new DeveloperError( "Invalid renderState.stencilTest.backOperation.zFail." ); } if (!validateStencilOperation(this.stencilTest.backOperation.zPass)) { throw new DeveloperError( "Invalid renderState.stencilTest.backOperation.zPass." ); } if (defined(this.viewport)) { if (this.viewport.width < 0) { throw new DeveloperError( "renderState.viewport.width must be greater than or equal to zero." ); } if (this.viewport.height < 0) { throw new DeveloperError( "renderState.viewport.height must be greater than or equal to zero." ); } if (this.viewport.width > ContextLimits.maximumViewportWidth) { throw new DeveloperError( `renderState.viewport.width must be less than or equal to the maximum viewport width (${ContextLimits.maximumViewportWidth.toString()}). Check maximumViewportWidth.` ); } if (this.viewport.height > ContextLimits.maximumViewportHeight) { throw new DeveloperError( `renderState.viewport.height must be less than or equal to the maximum viewport height (${ContextLimits.maximumViewportHeight.toString()}). Check maximumViewportHeight.` ); } } //>>includeEnd('debug'); this.id = 0; this._applyFunctions = []; } let nextRenderStateId = 0; let renderStateCache = {}; /** * Validates and then finds or creates an immutable render state, which defines the pipeline * state for a {@link DrawCommand} or {@link ClearCommand}. All inputs states are optional. Omitted states * use the defaults shown in the example below. * * @param {Object} [renderState] The states defining the render state as shown in the example below. * * @exception {RuntimeError} renderState.lineWidth is out of range. * @exception {DeveloperError} Invalid renderState.frontFace. * @exception {DeveloperError} Invalid renderState.cull.face. * @exception {DeveloperError} scissorTest.rectangle.width and scissorTest.rectangle.height must be greater than or equal to zero. * @exception {DeveloperError} renderState.depthRange.near can't be greater than renderState.depthRange.far. * @exception {DeveloperError} renderState.depthRange.near must be greater than or equal to zero. * @exception {DeveloperError} renderState.depthRange.far must be less than or equal to zero. * @exception {DeveloperError} Invalid renderState.depthTest.func. * @exception {DeveloperError} renderState.blending.color components must be greater than or equal to zero and less than or equal to one * @exception {DeveloperError} Invalid renderState.blending.equationRgb. * @exception {DeveloperError} Invalid renderState.blending.equationAlpha. * @exception {DeveloperError} Invalid renderState.blending.functionSourceRgb. * @exception {DeveloperError} Invalid renderState.blending.functionSourceAlpha. * @exception {DeveloperError} Invalid renderState.blending.functionDestinationRgb. * @exception {DeveloperError} Invalid renderState.blending.functionDestinationAlpha. * @exception {DeveloperError} Invalid renderState.stencilTest.frontFunction. * @exception {DeveloperError} Invalid renderState.stencilTest.backFunction. * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.fail. * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zFail. * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zPass. * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.fail. * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zFail. * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zPass. * @exception {DeveloperError} renderState.viewport.width must be greater than or equal to zero. * @exception {DeveloperError} renderState.viewport.width must be less than or equal to the maximum viewport width. * @exception {DeveloperError} renderState.viewport.height must be greater than or equal to zero. * @exception {DeveloperError} renderState.viewport.height must be less than or equal to the maximum viewport height. * * * @example * const defaults = { * frontFace : WindingOrder.COUNTER_CLOCKWISE, * cull : { * enabled : false, * face : CullFace.BACK * }, * lineWidth : 1, * polygonOffset : { * enabled : false, * factor : 0, * units : 0 * }, * scissorTest : { * enabled : false, * rectangle : { * x : 0, * y : 0, * width : 0, * height : 0 * } * }, * depthRange : { * near : 0, * far : 1 * }, * depthTest : { * enabled : false, * func : DepthFunction.LESS * }, * colorMask : { * red : true, * green : true, * blue : true, * alpha : true * }, * depthMask : true, * stencilMask : ~0, * blending : { * enabled : false, * color : { * red : 0.0, * green : 0.0, * blue : 0.0, * alpha : 0.0 * }, * equationRgb : BlendEquation.ADD, * equationAlpha : BlendEquation.ADD, * functionSourceRgb : BlendFunction.ONE, * functionSourceAlpha : BlendFunction.ONE, * functionDestinationRgb : BlendFunction.ZERO, * functionDestinationAlpha : BlendFunction.ZERO * }, * stencilTest : { * enabled : false, * frontFunction : StencilFunction.ALWAYS, * backFunction : StencilFunction.ALWAYS, * reference : 0, * mask : ~0, * frontOperation : { * fail : StencilOperation.KEEP, * zFail : StencilOperation.KEEP, * zPass : StencilOperation.KEEP * }, * backOperation : { * fail : StencilOperation.KEEP, * zFail : StencilOperation.KEEP, * zPass : StencilOperation.KEEP * } * }, * sampleCoverage : { * enabled : false, * value : 1.0, * invert : false * } * }; * * const rs = RenderState.fromCache(defaults); * * @see DrawCommand * @see ClearCommand * * @private */ RenderState.fromCache = function (renderState) { const partialKey = JSON.stringify(renderState); let cachedState = renderStateCache[partialKey]; if (defined(cachedState)) { ++cachedState.referenceCount; return cachedState.state; } // Cache miss. Fully define render state and try again. let states = new RenderState(renderState); const fullKey = JSON.stringify(states); cachedState = renderStateCache[fullKey]; if (!defined(cachedState)) { states.id = nextRenderStateId++; //>>includeStart('debug', pragmas.debug); states = freezeRenderState(states); //>>includeEnd('debug'); cachedState = { referenceCount: 0, state: states, }; // Cache full render state. Multiple partially defined render states may map to this. renderStateCache[fullKey] = cachedState; } ++cachedState.referenceCount; // Cache partial render state so we can skip validation on a cache hit for a partially defined render state renderStateCache[partialKey] = { referenceCount: 1, state: cachedState.state, }; return cachedState.state; }; /** * @private */ RenderState.removeFromCache = function (renderState) { const states = new RenderState(renderState); const fullKey = JSON.stringify(states); const fullCachedState = renderStateCache[fullKey]; // decrement partial key reference count const partialKey = JSON.stringify(renderState); const cachedState = renderStateCache[partialKey]; if (defined(cachedState)) { --cachedState.referenceCount; if (cachedState.referenceCount === 0) { // remove partial key delete renderStateCache[partialKey]; // decrement full key reference count if (defined(fullCachedState)) { --fullCachedState.referenceCount; } } } // remove full key if reference count is zero if (defined(fullCachedState) && fullCachedState.referenceCount === 0) { delete renderStateCache[fullKey]; } }; /** * This function is for testing purposes only. * @private */ RenderState.getCache = function () { return renderStateCache; }; /** * This function is for testing purposes only. * @private */ RenderState.clearCache = function () { renderStateCache = {}; }; function enableOrDisable(gl, glEnum, enable) { if (enable) { gl.enable(glEnum); } else { gl.disable(glEnum); } } function applyFrontFace(gl, renderState) { gl.frontFace(renderState.frontFace); } function applyCull(gl, renderState) { const cull = renderState.cull; const enabled = cull.enabled; enableOrDisable(gl, gl.CULL_FACE, enabled); if (enabled) { gl.cullFace(cull.face); } } function applyLineWidth(gl, renderState) { gl.lineWidth(renderState.lineWidth); } function applyPolygonOffset(gl, renderState) { const polygonOffset = renderState.polygonOffset; const enabled = polygonOffset.enabled; enableOrDisable(gl, gl.POLYGON_OFFSET_FILL, enabled); if (enabled) { gl.polygonOffset(polygonOffset.factor, polygonOffset.units); } } function applyScissorTest(gl, renderState, passState) { const scissorTest = renderState.scissorTest; const enabled = defined(passState.scissorTest) ? passState.scissorTest.enabled : scissorTest.enabled; enableOrDisable(gl, gl.SCISSOR_TEST, enabled); if (enabled) { const rectangle = defined(passState.scissorTest) ? passState.scissorTest.rectangle : scissorTest.rectangle; gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height); } } function applyDepthRange(gl, renderState) { const depthRange = renderState.depthRange; gl.depthRange(depthRange.near, depthRange.far); } function applyDepthTest(gl, renderState) { const depthTest = renderState.depthTest; const enabled = depthTest.enabled; enableOrDisable(gl, gl.DEPTH_TEST, enabled); if (enabled) { gl.depthFunc(depthTest.func); } } function applyColorMask(gl, renderState) { const colorMask = renderState.colorMask; gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha); } function applyDepthMask(gl, renderState) { gl.depthMask(renderState.depthMask); } function applyStencilMask(gl, renderState) { gl.stencilMask(renderState.stencilMask); } function applyBlendingColor(gl, color) { gl.blendColor(color.red, color.green, color.blue, color.alpha); } function applyBlending(gl, renderState, passState) { const blending = renderState.blending; const enabled = defined(passState.blendingEnabled) ? passState.blendingEnabled : blending.enabled; enableOrDisable(gl, gl.BLEND, enabled); if (enabled) { applyBlendingColor(gl, blending.color); gl.blendEquationSeparate(blending.equationRgb, blending.equationAlpha); gl.blendFuncSeparate( blending.functionSourceRgb, blending.functionDestinationRgb, blending.functionSourceAlpha, blending.functionDestinationAlpha ); } } function applyStencilTest(gl, renderState) { const stencilTest = renderState.stencilTest; const enabled = stencilTest.enabled; enableOrDisable(gl, gl.STENCIL_TEST, enabled); if (enabled) { const frontFunction = stencilTest.frontFunction; const backFunction = stencilTest.backFunction; const reference = stencilTest.reference; const mask = stencilTest.mask; // Section 6.8 of the WebGL spec requires the reference and masks to be the same for // front- and back-face tests. This call prevents invalid operation errors when calling // stencilFuncSeparate on Firefox. Perhaps they should delay validation to avoid requiring this. gl.stencilFunc(frontFunction, reference, mask); gl.stencilFuncSeparate(gl.BACK, backFunction, reference, mask); gl.stencilFuncSeparate(gl.FRONT, frontFunction, reference, mask); const frontOperation = stencilTest.frontOperation; const frontOperationFail = frontOperation.fail; const frontOperationZFail = frontOperation.zFail; const frontOperationZPass = frontOperation.zPass; gl.stencilOpSeparate( gl.FRONT, frontOperationFail, frontOperationZFail, frontOperationZPass ); const backOperation = stencilTest.backOperation; const backOperationFail = backOperation.fail; const backOperationZFail = backOperation.zFail; const backOperationZPass = backOperation.zPass; gl.stencilOpSeparate( gl.BACK, backOperationFail, backOperationZFail, backOperationZPass ); } } function applySampleCoverage(gl, renderState) { const sampleCoverage = renderState.sampleCoverage; const enabled = sampleCoverage.enabled; enableOrDisable(gl, gl.SAMPLE_COVERAGE, enabled); if (enabled) { gl.sampleCoverage(sampleCoverage.value, sampleCoverage.invert); } } const scratchViewport = new BoundingRectangle(); function applyViewport(gl, renderState, passState) { let viewport = defaultValue(renderState.viewport, passState.viewport); if (!defined(viewport)) { viewport = scratchViewport; viewport.width = passState.context.drawingBufferWidth; viewport.height = passState.context.drawingBufferHeight; } passState.context.uniformState.viewport = viewport; gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); } RenderState.apply = function (gl, renderState, passState) { applyFrontFace(gl, renderState); applyCull(gl, renderState); applyLineWidth(gl, renderState); applyPolygonOffset(gl, renderState); applyDepthRange(gl, renderState); applyDepthTest(gl, renderState); applyColorMask(gl, renderState); applyDepthMask(gl, renderState); applyStencilMask(gl, renderState); applyStencilTest(gl, renderState); applySampleCoverage(gl, renderState); applyScissorTest(gl, renderState, passState); applyBlending(gl, renderState, passState); applyViewport(gl, renderState, passState); }; function createFuncs(previousState, nextState) { const funcs = []; if (previousState.frontFace !== nextState.frontFace) { funcs.push(applyFrontFace); } if ( previousState.cull.enabled !== nextState.cull.enabled || previousState.cull.face !== nextState.cull.face ) { funcs.push(applyCull); } if (previousState.lineWidth !== nextState.lineWidth) { funcs.push(applyLineWidth); } if ( previousState.polygonOffset.enabled !== nextState.polygonOffset.enabled || previousState.polygonOffset.factor !== nextState.polygonOffset.factor || previousState.polygonOffset.units !== nextState.polygonOffset.units ) { funcs.push(applyPolygonOffset); } if ( previousState.depthRange.near !== nextState.depthRange.near || previousState.depthRange.far !== nextState.depthRange.far ) { funcs.push(applyDepthRange); } if ( previousState.depthTest.enabled !== nextState.depthTest.enabled || previousState.depthTest.func !== nextState.depthTest.func ) { funcs.push(applyDepthTest); } if ( previousState.colorMask.red !== nextState.colorMask.red || previousState.colorMask.green !== nextState.colorMask.green || previousState.colorMask.blue !== nextState.colorMask.blue || previousState.colorMask.alpha !== nextState.colorMask.alpha ) { funcs.push(applyColorMask); } if (previousState.depthMask !== nextState.depthMask) { funcs.push(applyDepthMask); } if (previousState.stencilMask !== nextState.stencilMask) { funcs.push(applyStencilMask); } if ( previousState.stencilTest.enabled !== nextState.stencilTest.enabled || previousState.stencilTest.frontFunction !== nextState.stencilTest.frontFunction || previousState.stencilTest.backFunction !== nextState.stencilTest.backFunction || previousState.stencilTest.reference !== nextState.stencilTest.reference || previousState.stencilTest.mask !== nextState.stencilTest.mask || previousState.stencilTest.frontOperation.fail !== nextState.stencilTest.frontOperation.fail || previousState.stencilTest.frontOperation.zFail !== nextState.stencilTest.frontOperation.zFail || previousState.stencilTest.backOperation.fail !== nextState.stencilTest.backOperation.fail || previousState.stencilTest.backOperation.zFail !== nextState.stencilTest.backOperation.zFail || previousState.stencilTest.backOperation.zPass !== nextState.stencilTest.backOperation.zPass ) { funcs.push(applyStencilTest); } if ( previousState.sampleCoverage.enabled !== nextState.sampleCoverage.enabled || previousState.sampleCoverage.value !== nextState.sampleCoverage.value || previousState.sampleCoverage.invert !== nextState.sampleCoverage.invert ) { funcs.push(applySampleCoverage); } return funcs; } RenderState.partialApply = function ( gl, previousRenderState, renderState, previousPassState, passState, clear ) { if (previousRenderState !== renderState) { // When a new render state is applied, instead of making WebGL calls for all the states or first // comparing the states one-by-one with the previous state (basically a linear search), we take // advantage of RenderState's immutability, and store a dynamically populated sparse data structure // containing functions that make the minimum number of WebGL calls when transitioning from one state // to the other. In practice, this works well since state-to-state transitions generally only require a // few WebGL calls, especially if commands are stored by state. let funcs = renderState._applyFunctions[previousRenderState.id]; if (!defined(funcs)) { funcs = createFuncs(previousRenderState, renderState); renderState._applyFunctions[previousRenderState.id] = funcs; } const len = funcs.length; for (let i = 0; i < len; ++i) { funcs[i](gl, renderState); } } const previousScissorTest = defined(previousPassState.scissorTest) ? previousPassState.scissorTest : previousRenderState.scissorTest; const scissorTest = defined(passState.scissorTest) ? passState.scissorTest : renderState.scissorTest; // Our scissor rectangle can get out of sync with the GL scissor rectangle on clears. // Seems to be a problem only on ANGLE. See https://github.com/CesiumGS/cesium/issues/2994 if (previousScissorTest !== scissorTest || clear) { applyScissorTest(gl, renderState, passState); } const previousBlendingEnabled = defined(previousPassState.blendingEnabled) ? previousPassState.blendingEnabled : previousRenderState.blending.enabled; const blendingEnabled = defined(passState.blendingEnabled) ? passState.blendingEnabled : renderState.blending.enabled; if ( previousBlendingEnabled !== blendingEnabled || (blendingEnabled && previousRenderState.blending !== renderState.blending) ) { applyBlending(gl, renderState, passState); } if ( previousRenderState !== renderState || previousPassState !== passState || previousPassState.context !== passState.context ) { applyViewport(gl, renderState, passState); } }; RenderState.getState = function (renderState) { //>>includeStart('debug', pragmas.debug); if (!defined(renderState)) { throw new DeveloperError("renderState is required."); } //>>includeEnd('debug'); return { frontFace: renderState.frontFace, cull: { enabled: renderState.cull.enabled, face: renderState.cull.face, }, lineWidth: renderState.lineWidth, polygonOffset: { enabled: renderState.polygonOffset.enabled, factor: renderState.polygonOffset.factor, units: renderState.polygonOffset.units, }, scissorTest: { enabled: renderState.scissorTest.enabled, rectangle: BoundingRectangle.clone(renderState.scissorTest.rectangle), }, depthRange: { near: renderState.depthRange.near, far: renderState.depthRange.far, }, depthTest: { enabled: renderState.depthTest.enabled, func: renderState.depthTest.func, }, colorMask: { red: renderState.colorMask.red, green: renderState.colorMask.green, blue: renderState.colorMask.blue, alpha: renderState.colorMask.alpha, }, depthMask: renderState.depthMask, stencilMask: renderState.stencilMask, blending: { enabled: renderState.blending.enabled, color: Color.clone(renderState.blending.color), equationRgb: renderState.blending.equationRgb, equationAlpha: renderState.blending.equationAlpha, functionSourceRgb: renderState.blending.functionSourceRgb, functionSourceAlpha: renderState.blending.functionSourceAlpha, functionDestinationRgb: renderState.blending.functionDestinationRgb, functionDestinationAlpha: renderState.blending.functionDestinationAlpha, }, stencilTest: { enabled: renderState.stencilTest.enabled, frontFunction: renderState.stencilTest.frontFunction, backFunction: renderState.stencilTest.backFunction, reference: renderState.stencilTest.reference, mask: renderState.stencilTest.mask, frontOperation: { fail: renderState.stencilTest.frontOperation.fail, zFail: renderState.stencilTest.frontOperation.zFail, zPass: renderState.stencilTest.frontOperation.zPass, }, backOperation: { fail: renderState.stencilTest.backOperation.fail, zFail: renderState.stencilTest.backOperation.zFail, zPass: renderState.stencilTest.backOperation.zPass, }, }, sampleCoverage: { enabled: renderState.sampleCoverage.enabled, value: renderState.sampleCoverage.value, invert: renderState.sampleCoverage.invert, }, viewport: defined(renderState.viewport) ? BoundingRectangle.clone(renderState.viewport) : undefined, }; }; export default RenderState;