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) { var rs = defaultValue(renderState, defaultValue.EMPTY_OBJECT); var cull = defaultValue(rs.cull, defaultValue.EMPTY_OBJECT); var polygonOffset = defaultValue(rs.polygonOffset, defaultValue.EMPTY_OBJECT); var scissorTest = defaultValue(rs.scissorTest, defaultValue.EMPTY_OBJECT); var scissorTestRectangle = defaultValue(scissorTest.rectangle, defaultValue.EMPTY_OBJECT); var depthRange = defaultValue(rs.depthRange, defaultValue.EMPTY_OBJECT); var depthTest = defaultValue(rs.depthTest, defaultValue.EMPTY_OBJECT); var colorMask = defaultValue(rs.colorMask, defaultValue.EMPTY_OBJECT); var blending = defaultValue(rs.blending, defaultValue.EMPTY_OBJECT); var blendingColor = defaultValue(blending.color, defaultValue.EMPTY_OBJECT); var stencilTest = defaultValue(rs.stencilTest, defaultValue.EMPTY_OBJECT); var stencilTestFrontOperation = defaultValue(stencilTest.frontOperation, defaultValue.EMPTY_OBJECT); var stencilTestBackOperation = defaultValue(stencilTest.backOperation, defaultValue.EMPTY_OBJECT); var sampleCoverage = defaultValue(rs.sampleCoverage, defaultValue.EMPTY_OBJECT); var 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 = []; } var nextRenderStateId = 0; var 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 * var 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 * } * }; * * var rs = RenderState.fromCache(defaults); * * @see DrawCommand * @see ClearCommand * * @private */ RenderState.fromCache = function(renderState) { var partialKey = JSON.stringify(renderState); var cachedState = renderStateCache[partialKey]; if (defined(cachedState)) { ++cachedState.referenceCount; return cachedState.state; } // Cache miss. Fully define render state and try again. var states = new RenderState(renderState); var 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) { var states = new RenderState(renderState); var fullKey = JSON.stringify(states); var fullCachedState = renderStateCache[fullKey]; // decrement partial key reference count var partialKey = JSON.stringify(renderState); var 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) { var cull = renderState.cull; var 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) { var polygonOffset = renderState.polygonOffset; var enabled = polygonOffset.enabled; enableOrDisable(gl, gl.POLYGON_OFFSET_FILL, enabled); if (enabled) { gl.polygonOffset(polygonOffset.factor, polygonOffset.units); } } function applyScissorTest(gl, renderState, passState) { var scissorTest = renderState.scissorTest; var enabled = (defined(passState.scissorTest)) ? passState.scissorTest.enabled : scissorTest.enabled; enableOrDisable(gl, gl.SCISSOR_TEST, enabled); if (enabled) { var rectangle = (defined(passState.scissorTest)) ? passState.scissorTest.rectangle : scissorTest.rectangle; gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height); } } function applyDepthRange(gl, renderState) { var depthRange = renderState.depthRange; gl.depthRange(depthRange.near, depthRange.far); } function applyDepthTest(gl, renderState) { var depthTest = renderState.depthTest; var enabled = depthTest.enabled; enableOrDisable(gl, gl.DEPTH_TEST, enabled); if (enabled) { gl.depthFunc(depthTest.func); } } function applyColorMask(gl, renderState) { var 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) { var blending = renderState.blending; var 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) { var stencilTest = renderState.stencilTest; var enabled = stencilTest.enabled; enableOrDisable(gl, gl.STENCIL_TEST, enabled); if (enabled) { var frontFunction = stencilTest.frontFunction; var backFunction = stencilTest.backFunction; var reference = stencilTest.reference; var 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); var frontOperation = stencilTest.frontOperation; var frontOperationFail = frontOperation.fail; var frontOperationZFail = frontOperation.zFail; var frontOperationZPass = frontOperation.zPass; gl.stencilOpSeparate(gl.FRONT, frontOperationFail, frontOperationZFail, frontOperationZPass); var backOperation = stencilTest.backOperation; var backOperationFail = backOperation.fail; var backOperationZFail = backOperation.zFail; var backOperationZPass = backOperation.zPass; gl.stencilOpSeparate(gl.BACK, backOperationFail, backOperationZFail, backOperationZPass); } } function applySampleCoverage(gl, renderState) { var sampleCoverage = renderState.sampleCoverage; var enabled = sampleCoverage.enabled; enableOrDisable(gl, gl.SAMPLE_COVERAGE, enabled); if (enabled) { gl.sampleCoverage(sampleCoverage.value, sampleCoverage.invert); } } var scratchViewport = new BoundingRectangle(); function applyViewport(gl, renderState, passState) { var 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) { var 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. var funcs = renderState._applyFunctions[previousRenderState.id]; if (!defined(funcs)) { funcs = createFuncs(previousRenderState, renderState); renderState._applyFunctions[previousRenderState.id] = funcs; } var len = funcs.length; for (var i = 0; i < len; ++i) { funcs[i](gl, renderState); } } var previousScissorTest = (defined(previousPassState.scissorTest)) ? previousPassState.scissorTest : previousRenderState.scissorTest; var 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/AnalyticalGraphicsInc/cesium/issues/2994 if ((previousScissorTest !== scissorTest) || clear) { applyScissorTest(gl, renderState, passState); } var previousBlendingEnabled = (defined(previousPassState.blendingEnabled)) ? previousPassState.blendingEnabled : previousRenderState.blending.enabled; var 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;