UNPKG

@cesium/engine

Version:

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

455 lines (410 loc) 12.8 kB
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 FramebufferManager from "../Renderer/FramebufferManager.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; } const stageDependencies = (dependencies[stage.name] = {}); if (defined(previousName)) { const previous = collection.getStageByName(previousName); stageDependencies[getLastStageName(previous)] = true; } const uniforms = stage.uniforms; if (defined(uniforms)) { const uniformNames = Object.getOwnPropertyNames(uniforms); const uniformNamesLength = uniformNames.length; for (let i = 0; i < uniformNamesLength; ++i) { const value = uniforms[uniformNames[i]]; if (typeof value === "string") { const 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; } const originalDependency = previousName; const inSeries = !defined(composite.inputPreviousStageTexture) || composite.inputPreviousStageTexture; let currentName = previousName; const length = composite.length; for (let i = 0; i < length; ++i) { const 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. let j; let name; if (!inSeries) { for (j = 1; j < length; ++j) { name = getLastStageName(composite.get(j)); const currentDependencies = dependencies[name]; for (let 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) { const dependencies = {}; if (defined(collection.ambientOcclusion)) { const ao = collection.ambientOcclusion; const bloom = collection.bloom; const tonemapping = collection._tonemapping; const fxaa = collection.fxaa; let 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) { const collection = cache._collection; const stage = collection.getStageByName(stageName); const textureScale = stage._textureScale; const forcePowerOfTwo = stage._forcePowerOfTwo; const pixelFormat = stage._pixelFormat; const pixelDatatype = stage._pixelDatatype; const clearColor = stage._clearColor; let i; let framebuffer; const framebuffers = cache._framebuffers; const 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; } const stageNames = framebuffer.stages; const stagesLength = stageNames.length; let foundConflict = false; for (let 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: new FramebufferManager({ pixelFormat: pixelFormat, pixelDatatype: pixelDatatype, }), clear: undefined, }; framebuffers.push(framebuffer); return framebuffer; } function createFramebuffers(cache, context) { const dependencies = getDependencies(cache._collection, context); for (const stageName in dependencies) { if (dependencies.hasOwnProperty(stageName)) { cache._stageNameToFramebuffer[stageName] = getFramebuffer( cache, stageName, dependencies[stageName], ); } } } function releaseResources(cache) { const framebuffers = cache._framebuffers; const length = framebuffers.length; for (let i = 0; i < length; ++i) { const framebuffer = framebuffers[i]; framebuffer.buffer.destroy(); } } function updateFramebuffers(cache, context) { const width = cache._width; const height = cache._height; const framebuffers = cache._framebuffers; const length = framebuffers.length; for (let i = 0; i < length; ++i) { const framebuffer = framebuffers[i]; const scale = framebuffer.textureScale; let textureWidth = Math.ceil(width * scale); let textureHeight = Math.ceil(height * scale); let size = Math.min(textureWidth, textureHeight); if (framebuffer.forcePowerOfTwo) { if (!CesiumMath.isPowerOfTwo(size)) { size = CesiumMath.nextPowerOfTwo(size); } textureWidth = size; textureHeight = size; } framebuffer.buffer.update(context, textureWidth, textureHeight); framebuffer.clear = new ClearCommand({ color: framebuffer.clearColor, framebuffer: framebuffer.buffer.framebuffer, }); } } 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) { const collection = this._collection; const updateDependencies = this._updateDependencies; const aoEnabled = defined(collection.ambientOcclusion) && collection.ambientOcclusion.enabled && collection.ambientOcclusion._isSupported(context); const bloomEnabled = defined(collection.bloom) && collection.bloom.enabled && collection.bloom._isSupported(context); const tonemappingEnabled = defined(collection._tonemapping) && collection._tonemapping.enabled && collection._tonemapping._isSupported(context); const fxaaEnabled = defined(collection.fxaa) && collection.fxaa.enabled && collection.fxaa._isSupported(context); const 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); } const width = context.drawingBufferWidth; const height = context.drawingBufferHeight; const 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) { const framebuffers = this._framebuffers; for (let 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) { const framebuffer = this._stageNameToFramebuffer[name]; if (!defined(framebuffer)) { return undefined; } return framebuffer.buffer.framebuffer; }; /** * Returns true if this object was destroyed; otherwise, false. * <p> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * </p> * * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>. * * @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. * <p> * Once an object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * </p> * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @see PostProcessStageTextureCache#isDestroyed */ PostProcessStageTextureCache.prototype.destroy = function () { releaseResources(this); return destroyObject(this); }; export default PostProcessStageTextureCache;