UNPKG

@cesium/engine

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

968 lines (882 loc) 33.1 kB
import BoundingRectangle from "../Core/BoundingRectangle.js"; import Color from "../Core/Color.js"; import Frozen from "../Core/Frozen.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 = renderState ?? Frozen.EMPTY_OBJECT; const cull = rs.cull ?? Frozen.EMPTY_OBJECT; const polygonOffset = rs.polygonOffset ?? Frozen.EMPTY_OBJECT; const scissorTest = rs.scissorTest ?? Frozen.EMPTY_OBJECT; const scissorTestRectangle = scissorTest.rectangle ?? Frozen.EMPTY_OBJECT; const depthRange = rs.depthRange ?? Frozen.EMPTY_OBJECT; const depthTest = rs.depthTest ?? Frozen.EMPTY_OBJECT; const colorMask = rs.colorMask ?? Frozen.EMPTY_OBJECT; const blending = rs.blending ?? Frozen.EMPTY_OBJECT; const blendingColor = blending.color ?? Frozen.EMPTY_OBJECT; const stencilTest = rs.stencilTest ?? Frozen.EMPTY_OBJECT; const stencilTestFrontOperation = stencilTest.frontOperation ?? Frozen.EMPTY_OBJECT; const stencilTestBackOperation = stencilTest.backOperation ?? Frozen.EMPTY_OBJECT; const sampleCoverage = rs.sampleCoverage ?? Frozen.EMPTY_OBJECT; const viewport = rs.viewport; this.frontFace = rs.frontFace ?? WindingOrder.COUNTER_CLOCKWISE; this.cull = { enabled: cull.enabled ?? false, face: cull.face ?? WebGLConstants.BACK, }; this.lineWidth = rs.lineWidth ?? 1.0; this.polygonOffset = { enabled: polygonOffset.enabled ?? false, factor: polygonOffset.factor ?? 0, units: polygonOffset.units ?? 0, }; this.scissorTest = { enabled: scissorTest.enabled ?? false, rectangle: BoundingRectangle.clone(scissorTestRectangle), }; this.depthRange = { near: depthRange.near ?? 0, far: depthRange.far ?? 1, }; this.depthTest = { enabled: depthTest.enabled ?? false, func: depthTest.func ?? WebGLConstants.LESS, // func, because function is a JavaScript keyword }; this.colorMask = { red: colorMask.red ?? true, green: colorMask.green ?? true, blue: colorMask.blue ?? true, alpha: colorMask.alpha ?? true, }; this.depthMask = rs.depthMask ?? true; this.stencilMask = rs.stencilMask ?? ~0; this.blending = { enabled: blending.enabled ?? false, color: new Color( blendingColor.red ?? 0.0, blendingColor.green ?? 0.0, blendingColor.blue ?? 0.0, blendingColor.alpha ?? 0.0, ), equationRgb: blending.equationRgb ?? WebGLConstants.FUNC_ADD, equationAlpha: blending.equationAlpha ?? WebGLConstants.FUNC_ADD, functionSourceRgb: blending.functionSourceRgb ?? WebGLConstants.ONE, functionSourceAlpha: blending.functionSourceAlpha ?? WebGLConstants.ONE, functionDestinationRgb: blending.functionDestinationRgb ?? WebGLConstants.ZERO, functionDestinationAlpha: blending.functionDestinationAlpha ?? WebGLConstants.ZERO, }; this.stencilTest = { enabled: stencilTest.enabled ?? false, frontFunction: stencilTest.frontFunction ?? WebGLConstants.ALWAYS, backFunction: stencilTest.backFunction ?? WebGLConstants.ALWAYS, reference: stencilTest.reference ?? 0, mask: stencilTest.mask ?? ~0, frontOperation: { fail: stencilTestFrontOperation.fail ?? WebGLConstants.KEEP, zFail: stencilTestFrontOperation.zFail ?? WebGLConstants.KEEP, zPass: stencilTestFrontOperation.zPass ?? WebGLConstants.KEEP, }, backOperation: { fail: stencilTestBackOperation.fail ?? WebGLConstants.KEEP, zFail: stencilTestBackOperation.zFail ?? WebGLConstants.KEEP, zPass: stencilTestBackOperation.zPass ?? WebGLConstants.KEEP, }, }; this.sampleCoverage = { enabled: sampleCoverage.enabled ?? false, value: sampleCoverage.value ?? 1.0, invert: 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 = 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;