import Color from '../Core/Color.js'; import defined from '../Core/defined.js'; import destroyObject from '../Core/destroyObject.js'; import CesiumMath from '../Core/Math.js'; import ClearCommand from '../Renderer/ClearCommand.js'; import Framebuffer from '../Renderer/Framebuffer.js'; import Texture from '../Renderer/Texture.js'; /** * Creates a minimal amount of textures and framebuffers. * * @alias PostProcessStageTextureCache * @constructor * * @param {PostProcessStageCollection} postProcessStageCollection The post process collection. * * @private */ function PostProcessStageTextureCache(postProcessStageCollection) { this._collection = postProcessStageCollection; this._framebuffers = []; this._stageNameToFramebuffer = {}; this._width = undefined; this._height = undefined; this._updateDependencies = false; } function getLastStageName(stage) { while (defined(stage.length)) { stage = stage.get(stage.length - 1); } return stage.name; } function getStageDependencies(collection, context, dependencies, stage, previousName) { if (!stage.enabled || !stage._isSupported(context)) { return previousName; } var stageDependencies = dependencies[stage.name] = {}; if (defined(previousName)) { var previous = collection.getStageByName(previousName); stageDependencies[getLastStageName(previous)] = true; } var uniforms = stage.uniforms; if (defined(uniforms)) { var uniformNames = Object.getOwnPropertyNames(uniforms); var uniformNamesLength = uniformNames.length; for (var i = 0; i < uniformNamesLength; ++i) { var value = uniforms[uniformNames[i]]; if (typeof value === 'string') { var dependent = collection.getStageByName(value); if (defined(dependent)) { stageDependencies[getLastStageName(dependent)] = true; } } } } return stage.name; } function getCompositeDependencies(collection, context, dependencies, composite, previousName) { if ((defined(composite.enabled) && !composite.enabled) || (defined(composite._isSupported) && !composite._isSupported(context))) { return previousName; } var originalDependency = previousName; var inSeries = !defined(composite.inputPreviousStageTexture) || composite.inputPreviousStageTexture; var currentName = previousName; var length = composite.length; for (var i = 0; i < length; ++i) { var stage = composite.get(i); if (defined(stage.length)) { currentName = getCompositeDependencies(collection, context, dependencies, stage, previousName); } else { currentName = getStageDependencies(collection, context, dependencies, stage, previousName); } // Stages in a series only depend on the previous stage if (inSeries) { previousName = currentName; } } // Stages not in a series depend on every stage executed before it since it could reference it as a uniform. // This prevents looking at the dependencies of each stage in the composite, but might create more framebuffers than necessary. // In practice, there are only 2-3 stages in these composites. var j; var name; if (!inSeries) { for (j = 1; j < length; ++j) { name = getLastStageName(composite.get(j)); var currentDependencies = dependencies[name]; for (var k = 0; k < j; ++k) { currentDependencies[getLastStageName(composite.get(k))] = true; } } } else { for (j = 1; j < length; ++j) { name = getLastStageName(composite.get(j)); if (!defined(dependencies[name])) { dependencies[name] = {}; } dependencies[name][originalDependency] = true; } } return currentName; } function getDependencies(collection, context) { var dependencies = {}; if (defined(collection.ambientOcclusion)) { var ao = collection.ambientOcclusion; var bloom = collection.bloom; var tonemapping = collection._tonemapping; var fxaa = collection.fxaa; var previousName = getCompositeDependencies(collection, context, dependencies, ao, undefined); previousName = getCompositeDependencies(collection, context, dependencies, bloom, previousName); previousName = getStageDependencies(collection, context, dependencies, tonemapping, previousName); previousName = getCompositeDependencies(collection, context, dependencies, collection, previousName); getStageDependencies(collection, context, dependencies, fxaa, previousName); } else { getCompositeDependencies(collection, context, dependencies, collection, undefined); } return dependencies; } function getFramebuffer(cache, stageName, dependencies) { var collection = cache._collection; var stage = collection.getStageByName(stageName); var textureScale = stage._textureScale; var forcePowerOfTwo = stage._forcePowerOfTwo; var pixelFormat = stage._pixelFormat; var pixelDatatype = stage._pixelDatatype; var clearColor = stage._clearColor; var i; var framebuffer; var framebuffers = cache._framebuffers; var length = framebuffers.length; for (i = 0; i < length; ++i) { framebuffer = framebuffers[i]; if (textureScale !== framebuffer.textureScale || forcePowerOfTwo !== framebuffer.forcePowerOfTwo || pixelFormat !== framebuffer.pixelFormat || pixelDatatype !== framebuffer.pixelDatatype || !Color.equals(clearColor, framebuffer.clearColor)) { continue; } var stageNames = framebuffer.stages; var stagesLength = stageNames.length; var foundConflict = false; for (var j = 0; j < stagesLength; ++j) { if (dependencies[stageNames[j]]) { foundConflict = true; break; } } if (!foundConflict) { break; } } if (defined(framebuffer) && i < length) { framebuffer.stages.push(stageName); return framebuffer; } framebuffer = { textureScale : textureScale, forcePowerOfTwo : forcePowerOfTwo, pixelFormat : pixelFormat, pixelDatatype : pixelDatatype, clearColor : clearColor, stages : [stageName], buffer : undefined, clear : undefined }; framebuffers.push(framebuffer); return framebuffer; } function createFramebuffers(cache, context) { var dependencies = getDependencies(cache._collection, context); for (var stageName in dependencies) { if (dependencies.hasOwnProperty(stageName)) { cache._stageNameToFramebuffer[stageName] = getFramebuffer(cache, stageName, dependencies[stageName]); } } } function releaseResources(cache) { var framebuffers = cache._framebuffers; var length = framebuffers.length; for (var i = 0; i < length; ++i) { var framebuffer = framebuffers[i]; framebuffer.buffer = framebuffer.buffer && framebuffer.buffer.destroy(); framebuffer.buffer = undefined; } } function updateFramebuffers(cache, context) { var width = cache._width; var height = cache._height; var framebuffers = cache._framebuffers; var length = framebuffers.length; for (var i = 0; i < length; ++i) { var framebuffer = framebuffers[i]; var scale = framebuffer.textureScale; var textureWidth = Math.ceil(width * scale); var textureHeight = Math.ceil(height * scale); var size = Math.min(textureWidth, textureHeight); if (framebuffer.forcePowerOfTwo) { if (!CesiumMath.isPowerOfTwo(size)) { size = CesiumMath.nextPowerOfTwo(size); } textureWidth = size; textureHeight = size; } framebuffer.buffer = new Framebuffer({ context : context, colorTextures : [new Texture({ context : context, width : textureWidth, height : textureHeight, pixelFormat : framebuffer.pixelFormat, pixelDatatype : framebuffer.pixelDatatype })] }); framebuffer.clear = new ClearCommand({ color : framebuffer.clearColor, framebuffer : framebuffer.buffer }); } } PostProcessStageTextureCache.prototype.updateDependencies = function() { this._updateDependencies = true; }; /** * Called before the stages in the collection are executed. Creates the minimum amount of framebuffers for a post-process collection. * * @param {Context} context The context. */ PostProcessStageTextureCache.prototype.update = function(context) { var collection = this._collection; var updateDependencies = this._updateDependencies; var aoEnabled = defined(collection.ambientOcclusion) && collection.ambientOcclusion.enabled && collection.ambientOcclusion._isSupported(context); var bloomEnabled = defined(collection.bloom) && collection.bloom.enabled && collection.bloom._isSupported(context); var tonemappingEnabled = defined(collection._tonemapping) && collection._tonemapping.enabled && collection._tonemapping._isSupported(context); var fxaaEnabled = defined(collection.fxaa) && collection.fxaa.enabled && collection.fxaa._isSupported(context); var needsCheckDimensionsUpdate = !defined(collection._activeStages) || collection._activeStages.length > 0 || aoEnabled || bloomEnabled || tonemappingEnabled || fxaaEnabled; if (updateDependencies || (!needsCheckDimensionsUpdate && this._framebuffers.length > 0)) { releaseResources(this); this._framebuffers.length = 0; this._stageNameToFramebuffer = {}; this._width = undefined; this._height = undefined; } if (!updateDependencies && !needsCheckDimensionsUpdate) { return; } if (this._framebuffers.length === 0) { createFramebuffers(this, context); } var width = context.drawingBufferWidth; var height = context.drawingBufferHeight; var dimensionsChanged = this._width !== width || this._height !== height; if (!updateDependencies && !dimensionsChanged) { return; } this._width = width; this._height = height; this._updateDependencies = false; releaseResources(this); updateFramebuffers(this, context); }; /** * Clears all of the framebuffers. * * @param {Context} context The context. */ PostProcessStageTextureCache.prototype.clear = function(context) { var framebuffers = this._framebuffers; for (var i = 0; i < framebuffers.length; ++i) { framebuffers[i].clear.execute(context); } }; /** * Gets the stage with the given name. * @param {String} name The name of the stage. * @return {PostProcessStage|PostProcessStageComposite} */ PostProcessStageTextureCache.prototype.getStageByName = function(name) { return this._collection.getStageByName(name); }; /** * Gets the output texture for a stage with the given name. * @param {String} name The name of the stage. * @return {Texture|undefined} The output texture of the stage with the given name. */ PostProcessStageTextureCache.prototype.getOutputTexture = function(name) { return this._collection.getOutputTexture(name); }; /** * Gets the framebuffer for a stage with the given name. * * @param {String} name The name of the stage. * @return {Framebuffer|undefined} The framebuffer for the stage with the given name. */ PostProcessStageTextureCache.prototype.getFramebuffer = function(name) { var framebuffer = this._stageNameToFramebuffer[name]; if (!defined(framebuffer)) { return undefined; } return framebuffer.buffer; }; /** * Returns true if this object was destroyed; otherwise, false. *
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
true
if this object was destroyed; otherwise, false
.
*
* @see PostProcessStageTextureCache#destroy
*/
PostProcessStageTextureCache.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*