src/components/PostprocessRack.js
import System from '../systems/System';
import Component from '../systems/EntitySystem/Component';
import Camera, { PostprocessPass } from './Camera';
export class PostprocessRackPass extends PostprocessPass {
get connections() {
return this._connections;
}
set connections(value) {
this._connections = value;
}
constructor() {
super();
this._connections = null;
this._passes = new Map();
}
dispose() {
this.unregisterAllPasses();
super.dispose();
this._connections = null;
this._passes = null;
}
getTargetId(id) {
if (typeof id !== 'string') {
throw new Error('`id` is not type of String!');
}
const found = id.indexOf(':');
if (found < 0) {
return super.getTargetId(id);
} else {
const pid = id.substr(0, found);
const tid = id.substr(found + 1);
const pass = this._passes.get(pid);
if (!pass) {
throw new Error(`Unknown postprocess rack pass: ${pid}`);
}
return pass.getTargetId(tid);
}
}
registerPass(id, pass) {
if (typeof id !== 'string') {
throw new Error('`id` is not type of String!');
}
if (!(pass instanceof PostprocessPass)) {
throw new Error('`pass` is not type of PostprocessPass!');
}
this._passes.set(id, pass);
}
unregisterPass(id) {
if (typeof id !== 'string') {
throw new Error('`id` is not type of String!');
}
this._passes.delete(id);
}
unregisterAllPasses() {
this._passes.clear();
}
getPass(id) {
if (typeof id !== 'string') {
throw new Error('`id` is not type of String!');
}
return this._passes.get(id) || null;
}
onApply(gl, renderer, textureSource, renderTarget) {
super.onApply(gl, renderer, textureSource, renderTarget);
const { _connections, _passes } = this;
if (!_connections || _connections.length <= 0) {
console.warn('Cannot postprocess empty list of connections!');
return;
}
for (const c of _connections) {
const [ source, effect, target ] = c;
let ts = textureSource;
let rt = renderTarget;
if (source === target && !!source) {
console.warn(
`Cannot postprocess image when source and target are the same: ${source}`
);
}
if (!!source) {
ts = this.getTargetId(source);
if (!ts) {
console.warn(`Trying to get unknown postprocess rack source: ${source}`);
continue;
}
}
if (!!target) {
rt = this.getTargetId(target);
if (!rt) {
console.warn(`Trying to get unknown postprocess rack target: ${target}`);
continue;
}
}
const pass = _passes.get(effect);
if (!pass) {
console.warn(
`Trying to apply unknown postprocess rack effect: ${effect}`
);
continue;
}
pass.onApply(gl, renderer, ts, rt);
}
}
}
export class PostprocessRackRawEffectPass extends PostprocessPass {
get shader() {
return this._shader;
}
set shader(value) {
this._shader = value;
}
get overrideUniforms() {
return this._overrideUniforms;
}
set overrideUniforms(value) {
this._overrideUniforms = value;
}
get overrideSamplers() {
return this._overrideSamplers;
}
set overrideSamplers(value) {
this._overrideSamplers = value;
}
constructor() {
super();
this._shader = null;
this._overrideUniforms = null;
this._overrideSamplers = null;
}
dispose() {
super.dipose();
this._shader = null;
this._overrideUniforms = null;
this._overrideSamplers = null;
}
onApply(gl, renderer, textureSource, renderTarget) {
super.onApply(gl, renderer, textureSource, renderTarget);
this.apply(
gl,
renderer,
textureSource,
renderTarget,
this._shader,
this._overrideUniforms,
this._overrideSamplers
);
}
}
/**
* Camera postprocess rack.
*/
export default class PostprocessRack extends Component {
static get propsTypes() {
return {
connections: 'array(array(any))',
targets: 'array(any)',
sourceFloatPointData: 'boolean',
effects: 'map(any)',
composites: 'map(any)'
};
}
/**
* Component factory.
*
* @return {PostprocessRack} Component instance.
*/
static factory() {
return new PostprocessRack();
}
get connections() {
return this._mainPass.connections;
}
set connections(value) {
if (!value) {
this._mainPass.connections = null;
return;
}
if (!Array.isArray(value)) {
throw new Error('`value` is not type of Array!');
}
for (const item of value) {
if (!Array.isArray(item)) {
throw new Error('One of `value` items is not type of Array!');
}
}
this._mainPass.connections = value;
}
get targets() {
return this._targets;
}
set targets(value) {
if (!value) {
this._targets = null;
return;
}
const { _targets, _mainPass } = this;
if (!!_targets) {
for (const id in _targets) {
_mainPass.destroyTarget(id);
}
}
this._targets = value;
if (!!value) {
for (const id in value) {
const { level, floatPointData, potMode } = value[id];
_mainPass.createTarget(id, level, floatPointData, potMode);
}
}
}
get sourceFloatPointData() {
return this._sourceFloatPointData;
}
set sourceFloatPointData(value) {
if (typeof value !== 'boolean') {
throw new Error('`value` is not type of Boolean!');
}
this._sourceFloatPointData = value;
}
get effects() {
return this._effects;
}
set effects(value) {
this._unregisterEffects();
this._effects = value;
this._registerEffects();
}
get composites() {
return this._composites;
}
set composites(value) {
this._unregisterComposites();
this._composites = value;
this._registerComposites();
}
/**
* Constructor.
*/
constructor() {
super();
this._sourceFloatPointData = false;
this._mainPass = new PostprocessRackPass();
this._targets = null;
this._effects = null;
this._effectsPasses = null;
this._composites = null;
this._compositesPasses = null;
this._compositesEffectsPasses = null;
}
/**
* Destructor (disposes internal resources).
*
* @example
* rack.dispose();
* rack = null;
*/
dispose() {
super.dispose();
this._mainPass.dispose();
this._mainPass = null;
this._targets = null;
this._effects = null;
this._effectsPasses = null;
this._composites = null;
this._compositesPasses = null;
this._compositesEffectsPasses = null;
}
getTargetId(id) {
if (typeof id !== 'string') {
throw new Error('`id` is not type of String!');
}
const found = id.indexOf(':');
if (found < 0) {
return super.getTargetId(id);
} else {
const pid = id.substr(0, found);
const tid = id.substr(found + 1);
const pass = this._passes.get(pid);
if (!pass) {
throw new Error(`Unknown postprocess rack pass: ${pid}`);
}
return pass.getTargetId(tid);
}
}
registerPass(id, pass) {
this._mainPass.registerPass(id, pass);
}
unregisterPass(id) {
this._mainPass.unregisterPass(id);
}
unregisterAllPasses() {
this._mainPass.unregisterAllPasses();
}
getPass(id) {
return this._mainPass.getPass(id);
}
onAttach() {
this._register();
}
onDetach() {
this._unregister();
}
_register() {
const { entity } = this;
if (!entity) {
return;
}
const camera = entity.getComponent(Camera);
if (!camera) {
throw new Error('There is no Camera component in entity!');
}
camera.registerPostprocess(this._mainPass, this._sourceFloatPointData);
}
_unregister() {
const { entity } = this;
if (!entity) {
return;
}
const camera = entity.getComponent(Camera);
if (!camera) {
throw new Error('There is no Camera component in entity!');
}
camera.unregisterPostprocess();
}
_registerEffects() {
const { _effects } = this;
if (!_effects) {
return;
}
this._effectsPasses = new Map();
for (const id in _effects) {
const { shader, overrideUniforms, overrideSamplers } = _effects[id];
const pass = new PostprocessRackRawEffectPass();
pass.shader = shader;
pass.overrideUniforms = overrideUniforms;
pass.overrideSamplers = overrideSamplers;
this._effectsPasses.set(id, pass);
this.registerPass(id, pass);
}
}
_unregisterEffects() {
const { _effectsPasses } = this;
if (!_effectsPasses) {
return;
}
for (const [id, pass] of _effectsPasses.entries()) {
pass.dispose();
this.unregisterPass(id);
}
this._effectsPasses = null;
}
_registerComposites() {
const { _composites } = this;
if (!_composites) {
return;
}
const { AssetSystem } = System.systems;
if (!AssetSystem) {
throw new Error('There is no registered AssetSystem!');
}
this._compositesPasses = new Map();
this._compositesEffectsPasses = new Map();
for (const id in _composites) {
const assetPath = _composites[id];
if (!assetPath) {
throw new Error('There is no postprocess rack effect asset provided!');
}
const asset = AssetSystem.get(`postprocess://${assetPath}`);
if (!asset) {
throw new Error(`There is no loaded postprocess rack effect asset: ${assetPath}`);
}
this._compositesEffectsPasses[id] = new Map();
const { targets, connections, effects } = asset;
const mainPass = new PostprocessRackPass();
for (const effect in effects) {
const { shader, overrideUniforms, overrideSamplers } = effects[effect];
const pass = new PostprocessRackRawEffectPass();
pass.shader = shader;
pass.overrideUniforms = overrideUniforms;
pass.overrideSamplers = overrideSamplers;
this._compositesEffectsPasses[id][effect] = pass;
mainPass.registerPass(effect, pass);
}
for (const target in targets) {
const { level, floatPointData, potMode } = targets[target];
mainPass.createTarget(target, level, floatPointData, potMode);
}
mainPass.connections = connections;
this.registerPass(id, mainPass);
}
}
_unregisterComposites() {
const { _compositesPasses, _compositesEffectsPasses } = this;
if (!_compositesPasses) {
return;
}
for (const [id, pass] of _compositesPasses.entries()) {
pass.dispose();
this.unregisterPass(id);
}
for (const [, pass] of _compositesEffectsPasses.entries()) {
pass.dispose();
}
this._compositesPasses = null;
this._compositesEffectsPasses = null;
}
}