// TODO Shader library
import Pass from './Pass';
import CompositorNode from './CompositorNode';
// TODO curlnoise demo wrong
// PENDING
// Use topological sort ?
/**
* Filter node
*
* @constructor clay.compositor.FilterNode
* @extends clay.compositor.CompositorNode
*
* @example
var node = new clay.compositor.FilterNode({
name: 'fxaa',
shader: clay.Shader.source('clay.compositor.fxaa'),
inputs: {
texture: {
node: 'scene',
pin: 'color'
}
},
// Multiple outputs is preserved for MRT support in WebGL2.0
outputs: {
color: {
attachment: clay.FrameBuffer.COLOR_ATTACHMENT0
parameters: {
format: clay.Texture.RGBA,
width: 512,
height: 512
},
// Node will keep the RTT rendered in last frame
keepLastFrame: true,
// Force the node output the RTT rendered in last frame
outputLastFrame: true
}
}
});
*
*/
var FilterNode = CompositorNode.extend(function () {
return /** @lends clay.compositor.FilterNode# */ {
/**
* @type {string}
*/
name: '',
/**
* @type {Object}
*/
inputs: {},
/**
* @type {Object}
*/
outputs: null,
/**
* @type {string}
*/
shader: '',
/**
* Input links, will be updated by the graph
* @example:
* inputName: {
* node: someNode,
* pin: 'xxxx'
* }
* @type {Object}
*/
inputLinks: {},
/**
* Output links, will be updated by the graph
* @example:
* outputName: {
* node: someNode,
* pin: 'xxxx'
* }
* @type {Object}
*/
outputLinks: {},
/**
* @type {clay.compositor.Pass}
*/
pass: null,
// Save the output texture of previous frame
// Will be used when there exist a circular reference
_prevOutputTextures: {},
_outputTextures: {},
// Example: { name: 2 }
_outputReferences: {},
_rendering: false,
// If rendered in this frame
_rendered: false,
_compositor: null
};
}, function () {
var pass = new Pass({
fragment: this.shader
});
this.pass = pass;
},
/** @lends clay.compositor.FilterNode.prototype */
{
/**
* @param {clay.Renderer} renderer
*/
render: function (renderer, frameBuffer) {
this.trigger('beforerender', renderer);
this._rendering = true;
var _gl = renderer.gl;
for (var inputName in this.inputLinks) {
var link = this.inputLinks[inputName];
var inputTexture = link.node.getOutput(renderer, link.pin);
this.pass.setUniform(inputName, inputTexture);
}
// Output
if (!this.outputs) {
this.pass.outputs = null;
this._compositor.getFrameBuffer().unbind(renderer);
this.pass.render(renderer, frameBuffer);
}
else {
this.pass.outputs = {};
var attachedTextures = {};
for (var name in this.outputs) {
var parameters = this.updateParameter(name, renderer);
if (isNaN(parameters.width)) {
this.updateParameter(name, renderer);
}
var outputInfo = this.outputs[name];
var texture = this._compositor.allocateTexture(parameters);
this._outputTextures[name] = texture;
var attachment = outputInfo.attachment || _gl.COLOR_ATTACHMENT0;
if (typeof(attachment) === 'string') {
attachment = _gl[attachment];
}
attachedTextures[attachment] = texture;
}
this._compositor.getFrameBuffer().bind(renderer);
for (var attachment in attachedTextures) {
// FIXME attachment changes in different nodes
this._compositor.getFrameBuffer().attach(
attachedTextures[attachment], attachment
);
}
this.pass.render(renderer);
// Because the data of texture is changed over time,
// Here update the mipmaps of texture each time after rendered;
this._compositor.getFrameBuffer().updateMipmap(renderer);
}
for (var inputName in this.inputLinks) {
var link = this.inputLinks[inputName];
link.node.removeReference(link.pin);
}
this._rendering = false;
this._rendered = true;
this.trigger('afterrender', renderer);
},
// TODO Remove parameter function callback
updateParameter: function (outputName, renderer) {
var outputInfo = this.outputs[outputName];
var parameters = outputInfo.parameters;
var parametersCopy = outputInfo._parametersCopy;
if (!parametersCopy) {
parametersCopy = outputInfo._parametersCopy = {};
}
if (parameters) {
for (var key in parameters) {
if (key !== 'width' && key !== 'height') {
parametersCopy[key] = parameters[key];
}
}
}
var width, height;
if (typeof parameters.width === 'function') {
width = parameters.width.call(this, renderer);
}
else {
width = parameters.width;
}
if (typeof parameters.height === 'function') {
height = parameters.height.call(this, renderer);
}
else {
height = parameters.height;
}
width = Math.ceil(width);
height = Math.ceil(height);
if (
parametersCopy.width !== width
|| parametersCopy.height !== height
) {
if (this._outputTextures[outputName]) {
this._outputTextures[outputName].dispose(renderer);
}
}
parametersCopy.width = width;
parametersCopy.height = height;
return parametersCopy;
},
/**
* Set parameter
* @param {string} name
* @param {} value
*/
setParameter: function (name, value) {
this.pass.setUniform(name, value);
},
/**
* Get parameter value
* @param {string} name
* @return {}
*/
getParameter: function (name) {
return this.pass.getUniform(name);
},
/**
* Set parameters
* @param {Object} obj
*/
setParameters: function (obj) {
for (var name in obj) {
this.setParameter(name, obj[name]);
}
},
// /**
// * Set shader code
// * @param {string} shaderStr
// */
// setShader: function (shaderStr) {
// var material = this.pass.material;
// material.shader.setFragment(shaderStr);
// material.attachShader(material.shader, true);
// },
/**
* Proxy of pass.material.define('fragment', xxx);
* @param {string} symbol
* @param {number} [val]
*/
define: function (symbol, val) {
this.pass.material.define('fragment', symbol, val);
},
/**
* Proxy of pass.material.undefine('fragment', xxx)
* @param {string} symbol
*/
undefine: function (symbol) {
this.pass.material.undefine('fragment', symbol);
},
removeReference: function (outputName) {
this._outputReferences[outputName]--;
if (this._outputReferences[outputName] === 0) {
var outputInfo = this.outputs[outputName];
if (outputInfo.keepLastFrame) {
if (this._prevOutputTextures[outputName]) {
this._compositor.releaseTexture(this._prevOutputTextures[outputName]);
}
this._prevOutputTextures[outputName] = this._outputTextures[outputName];
}
else {
// Output of this node have alreay been used by all other nodes
// Put the texture back to the pool.
this._compositor.releaseTexture(this._outputTextures[outputName]);
}
}
},
clear: function () {
CompositorNode.prototype.clear.call(this);
// Default disable all texture
this.pass.material.disableTexturesAll();
}
});
export default FilterNode;