Home Reference Source

src/components/DeferredRenderer.js

import Component from '../systems/EntitySystem/Component';
import { Command, Pipeline, RenderFullscreenCommand } from '../systems/RenderSystem';
import Camera from './Camera';

export class DeferredPipeline extends Command {

  get gBufferId() {
    return this._gBufferId;
  }

  set gBufferId(value) {
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String!');
    }

    this._gBufferId = value;
    this._dirty = true;
  }

  get lBufferId() {
    return this._lBufferId;
  }

  set lBufferId(value) {
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String!');
    }

    this._lBufferId = value;
    this._dirty = true;
  }

  get gBufferTargets() {
    return this._gBufferTargets;
  }

  set gBufferTargets(value) {
    if (typeof value === 'number') {
      this._gBufferTargets = value | 0;
    } else if (Array.isArray(value)) {
      this._gBufferTargets = value;
    } else {
      throw new Error('`value` is not type of either Number or Array!');
    }

    this._dirty = true;
  }

  get lBufferTargets() {
    return this._lBufferTargets;
  }

  set lBufferTargets(value) {
    if (typeof value === 'number') {
      this._lBufferTargets = value | 0;
    } else if (Array.isArray(value)) {
      this._lBufferTargets = value;
    } else {
      throw new Error('`value` is not type of either Number or Array!');
    }

    this._dirty = true;
  }

  get gBufferLayer() {
    return this._gBufferLayer;
  }

  set gBufferLayer(value) {
    if (!value) {
      this._gBufferLayer = null;
      return;
    }
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String!');
    }

    this._gBufferLayer = value;
  }

  get lBufferLayer() {
    return this._lBufferLayer;
  }

  set lBufferLayer(value) {
    if (!value) {
      this._lBufferLayer = null;
      return;
    }
    if (typeof value !== 'string') {
      throw new Error('`value` is not type of String!');
    }

    this._lBufferLayer = value;
  }

  get fullscreen() {
    return this._fullscreen;
  }

  constructor(owner) {
    super();

    this._owner = owner;
    this._renderer = null;
    this._gBuffer = null;
    this._lBuffer = null;
    this._gBufferId = '#deferred-g-buffer';
    this._lBufferId = '#deferred-l-buffer';
    this._gBufferIdUsed = null;
    this._lBufferIdUsed = null;
    this._gBufferTargets = 2;
    this._lBufferTargets = 2;
    this._gBufferLayer = 'g-buffer';
    this._lBufferLayer = 'l-buffer';
    this._fullscreen = new RenderFullscreenCommand();
    this._dirty = true;
  }

  dispose() {
    super.dispose();

    const { _renderer, _gBufferIdUsed, _lBufferIdUsed, _fullscreen } = this;
    if (!!_renderer) {
      if (!!_gBufferIdUsed) {
        _renderer.unregisterRenderTarget(_gBufferIdUsed);
      }
      if (!!_lBufferIdUsed) {
        _renderer.unregisterRenderTarget(_lBufferIdUsed);
      }
    }
    if (!!_fullscreen) {
      _fullscreen.dispose();
    }
    this._owner = null;
    this._renderer = null;
    this._gBufferId = null;
    this._lBufferId = null;
    this._gBufferIdUsed = null;
    this._lBufferIdUsed = null;
    this._gBufferLayer = null;
    this._lBufferLayer = null;
    this._fullscreen = null;
  }

  onRender(gl, renderer, deltaTime, layer) {
    this._ensureState(renderer);

    const { _owner, _gBufferLayer, _lBufferLayer } = this;
    const { captureEntity, entity } = _owner;
    const target = !!captureEntity
      ? entity.findEntity(captureEntity)
      : entity;
    const rtt = renderer.activeRenderTarget;

    if (!!_gBufferLayer) {
      renderer.enableRenderTarget(this._gBufferIdUsed);
      target.performAction('render-layer', gl, renderer, deltaTime, _gBufferLayer);
      renderer.disableRenderTarget();
    }

    if (!!_lBufferLayer) {
      renderer.enableRenderTarget(this._lBufferIdUsed);
      target.performAction('render-layer', gl, renderer, deltaTime, _lBufferLayer);
      renderer.disableRenderTarget();
    }

    if (!!rtt) {
      renderer.enableRenderTarget(rtt, false);
    }
    this._fullscreen.onRender(gl, renderer, deltaTime, layer);
  }

  onResize(width, height) {
    this._dirty = true;
  }

  _ensureState(renderer) {
    if (!this._dirty) {
      return;
    }
    const { _gBufferId, _lBufferId } = this;
    if (!_gBufferId) {
      throw new Error('`gBufferId` cannot be null!');
    }
    if (!_lBufferId) {
      throw new Error('`lBufferId` cannot be null!');
    }

    if (!!this._gBufferIdUsed) {
      renderer.unregisterRenderTarget(this._gBufferIdUsed);
    }
    this._gBufferIdUsed = this._gBufferId;
    renderer.registerRenderTargetMulti(
      this._gBufferIdUsed,
      renderer.canvas.width,
      renderer.canvas.height,
      this._gBufferTargets
    );

    if (!!this._lBufferIdUsed) {
      renderer.unregisterRenderTarget(this._lBufferIdUsed);
    }
    this._lBufferIdUsed = this._lBufferId;
    renderer.registerRenderTarget(
      this._lBufferIdUsed,
      renderer.canvas.width,
      renderer.canvas.height,
      this._lBufferTargets
    );

    this._renderer = renderer;
    this._dirty = false;
  }

}

/**
 * Deferred pipeline renderer.
 */
export default class DeferredRenderer extends Component {

  static get propsTypes() {
    return {
      shader: 'string_null',
      overrideUniforms: 'map(any)',
      overrideSamplers: 'map(any)',
      gBufferId: 'string',
      lBufferid: 'string',
      gBufferTargets: 'number|array(any)',
      lBufferTargets: 'number|array(any)',
      gBufferLayer: 'string_null',
      lBufferLayer: 'string_null'
    };
  }

  static factory() {
    return new DeferredRenderer();
  }

  /** @type {String|null} */
  get shader() {
    return this._pipeline.fullscreen.shader;
  }

  /** @type {String|null} */
  set shader(value) {
    this._pipeline.fullscreen.shader = value;
  }

  /** @type {*} */
  get overrideUniforms() {
    const { fullscreen } = this._pipeline;
    const result = {};
    for (const [key, value] of fullscreen.overrideUniforms) {
      result[key] = value;
    }

    return result;
  }

  /** @type {*} */
  set overrideUniforms(value) {
    const { overrideUniforms } = this._pipeline.fullscreen;

    overrideUniforms.clear();
    if (!value) {
      return;
    }

    for (const name in value) {
      overrideUniforms.set(name, value[name]);
    }
  }

  /** @type {*} */
  get overrideSamplers() {
    const { fullscreen } = this._pipeline;
    const result = {};
    for (const [key, value] of fullscreen.overrideSamplers) {
      result[key] = value;
    }

    return result;
  }

  /** @type {*} */
  set overrideSamplers(value) {
    const { overrideSamplers } = this._pipeline.fullscreen;

    overrideSamplers.clear();
    if (!value) {
      return;
    }

    for (const name in value) {
      overrideSamplers.set(name, value[name]);
    }
  }

  /** @type {String} */
  get gBufferId() {
    return this._pipeline.gBufferId;
  }

  /** @type {String} */
  set gBufferId(value) {
    this._pipeline.gBufferId = value;
  }

  /** @type {String} */
  get lBufferId() {
    return this._pipeline.lBufferId;
  }

  /** @type {String} */
  set lBufferId(value) {
    this._pipeline.lBufferId = value;
  }

  /** @type {Number} */
  get gBufferTargets() {
    return this._pipeline.gBufferTargets;
  }

  /** @type {Number} */
  set gBufferTargets(value) {
    this._pipeline.gBufferTargets = value;
  }

  /** @type {Number} */
  get lBufferTargets() {
    return this._pipeline.lBufferTargets;
  }

  /** @type {Number} */
  set lBufferTargets(value) {
    this._pipeline.lBufferTargets = value;
  }

  /** @type {String|null} */
  get gBufferLayer() {
    return this._pipeline.gBufferLayer;
  }

  /** @type {String|null} */
  set gBufferLayer(value) {
    this._pipeline.gBufferLayer = value;
  }

  /** @type {String|null} */
  get lBufferLayer() {
    return this._pipeline.lBufferLayer;
  }

  /** @type {String|null} */
  set lBufferLayer(value) {
    this._pipeline.lBufferLayer = value;
  }

  get gBuffer() {
    return this._pipeline.gBuffer;
  }

  get lBuffer() {
    return this._pipeline.lBuffer;
  }

  /**
   * Constructor.
   */
  constructor() {
    super();

    this._camera = null;
    this._pipeline = new DeferredPipeline(this);
  }

  /**
   * @override
   */
  dispose() {
    if (!!this._pipeline) {
      this._pipeline.dispose();
      this._pipeline = null;
    }
    this._camera = null;

    super.dispose();
  }

  /**
   * @override
   */
  onAttach() {
    const camera = this._camera = this.entity.getComponent(Camera);
    if (!camera) {
      throw new Error('There is no component of Camera type!');
    }

    camera.command = this._pipeline;
  }

  /**
   * @override
   */
  onDetach() {
    if (!!this._camera) {
      this._camera.command = null;
      this._camera = null;
    }
  }

}