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.
*