You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
13 KiB
JavaScript

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