Skip to content

Commit

Permalink
Refactoring to LayerComposition internal implementation (#5738)
Browse files Browse the repository at this point in the history
* Refactoring to LayerComposition internal implementation

* also change mesh culling to not use render actions to be compatible with custom passes with customer render actions

* Update src/scene/composition/render-action.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
Co-authored-by: Will Eastcott <will@playcanvas.com>
  • Loading branch information
3 people authored Oct 11, 2023
1 parent fae6a1a commit ec72104
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 133 deletions.
2 changes: 1 addition & 1 deletion src/deprecated/deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ Object.defineProperty(Layer.prototype, 'renderTarget', {
set: function (rt) {
Debug.deprecated(`pc.Layer#renderTarget is deprecated. Set the render target on the camera instead.`);
this._renderTarget = rt;
this._dirtyCameras = true;
this._dirtyComposition = true;
},
get: function () {
return this._renderTarget;
Expand Down
2 changes: 1 addition & 1 deletion src/framework/components/camera/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ class CameraComponent extends Component {
dirtyLayerCompositionCameras() {
// layer composition needs to update order
const layerComp = this.system.app.scene.layers;
layerComp._dirtyCameras = true;
layerComp._dirty = true;
}

/**
Expand Down
122 changes: 70 additions & 52 deletions src/scene/composition/layer-composition.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ class LayerComposition extends EventHandler {
*/
layerNameMap = new Map();

/**
* A mapping of {@link Layer} to its opaque index in {@link LayerComposition#layerList}.
*
* @type {Map<import('../layer.js').Layer, number>}
* @ignore
*/
layerOpaqueIndexMap = new Map();

/**
* A mapping of {@link Layer} to its transparent index in {@link LayerComposition#layerList}.
*
* @type {Map<import('../layer.js').Layer, number>}
* @ignore
*/
layerTransparentIndexMap = new Map();

/**
* A read-only array of boolean values, matching {@link LayerComposition#layerList}. True means only
* semi-transparent objects are rendered, and false means opaque.
Expand Down Expand Up @@ -81,6 +97,13 @@ class LayerComposition extends EventHandler {
*/
_renderActions = [];

/**
* True if the composition needs to be updated before rendering.
*
* @ignore
*/
_dirty = false;

/**
* Create a new layer composition.
*
Expand All @@ -94,8 +117,6 @@ class LayerComposition extends EventHandler {

this._opaqueOrder = {};
this._transparentOrder = {};

this._dirtyCameras = false;
}

destroy() {
Expand All @@ -108,24 +129,24 @@ class LayerComposition extends EventHandler {
const len = this.layerList.length;

// if composition dirty flag is not set, test if layers are marked dirty
if (!this._dirtyCameras) {
if (!this._dirty) {
for (let i = 0; i < len; i++) {
if (this.layerList[i]._dirtyCameras) {
this._dirtyCameras = true;
if (this.layerList[i]._dirtyComposition) {
this._dirty = true;
break;
}
}
}

if (this._dirtyCameras) {
if (this._dirty) {

this._dirtyCameras = false;
this._dirty = false;

// walk the layers and build an array of unique cameras from all layers
this.cameras.length = 0;
for (let i = 0; i < len; i++) {
const layer = this.layerList[i];
layer._dirtyCameras = false;
layer._dirtyComposition = false;

// for all cameras in the layer
for (let j = 0; j < layer.cameras.length; j++) {
Expand Down Expand Up @@ -172,8 +193,8 @@ class LayerComposition extends EventHandler {
for (let j = 0; j < len; j++) {

const layer = this.layerList[j];
const isLayerEnabled = this.subLayerEnabled[j];
if (layer && isLayerEnabled) {
const isLayerEnabled = layer.enabled && this.subLayerEnabled[j];
if (isLayerEnabled) {

// if layer needs to be rendered
if (layer.cameras.length > 0) {
Expand All @@ -196,7 +217,8 @@ class LayerComposition extends EventHandler {
}

// add render action to describe rendering step
lastRenderAction = this.addRenderAction(this._renderActions, renderActionCount, layer, j, camera,
const isTransparent = this.subLayerList[j];
lastRenderAction = this.addRenderAction(this._renderActions, renderActionCount, layer, isTransparent, camera,
cameraFirstRenderAction, postProcessMarked);
renderActionCount++;
cameraFirstRenderAction = false;
Expand Down Expand Up @@ -235,7 +257,7 @@ class LayerComposition extends EventHandler {
}

// function adds new render action to a list, while trying to limit allocation and reuse already allocated objects
addRenderAction(renderActions, renderActionIndex, layer, layerIndex, camera, cameraFirstRenderAction, postProcessMarked) {
addRenderAction(renderActions, renderActionIndex, layer, isTransparent, camera, cameraFirstRenderAction, postProcessMarked) {

// try and reuse object, otherwise allocate new
/** @type {RenderAction} */
Expand Down Expand Up @@ -281,8 +303,8 @@ class LayerComposition extends EventHandler {

// store the properties - write all as we reuse previously allocated class instances
renderAction.triggerPostprocess = false;
renderAction.layerIndex = layerIndex;
renderAction.layer = layer;
renderAction.transparent = isTransparent;
renderAction.camera = camera;
renderAction.renderTarget = rt;
renderAction.clearColor = clearColor;
Expand All @@ -301,7 +323,7 @@ class LayerComposition extends EventHandler {
for (let a = startIndex; a >= 0; a--) {

const ra = this._renderActions[a];
const layer = this.layerList[ra.layerIndex];
const layer = ra.layer;

// if we hit render action with a render target (other than depth layer), that marks the end of camera stack
// TODO: refactor this as part of depth layer refactoring
Expand Down Expand Up @@ -335,17 +357,15 @@ class LayerComposition extends EventHandler {
Debug.trace(TRACEID_RENDER_ACTION, 'Render Actions for composition: ' + this.name);
for (let i = 0; i < this._renderActions.length; i++) {
const ra = this._renderActions[i];
const layerIndex = ra.layerIndex;
const layer = this.layerList[layerIndex];
const enabled = layer.enabled && this.subLayerEnabled[layerIndex];
const transparent = this.subLayerList[layerIndex];
const layer = ra.layer;
const enabled = layer.enabled && this.isEnabled(layer, ra.transparent);
const camera = ra.camera;
const clear = (ra.clearColor ? 'Color ' : '..... ') + (ra.clearDepth ? 'Depth ' : '..... ') + (ra.clearStencil ? 'Stencil' : '.......');

Debug.trace(TRACEID_RENDER_ACTION, i +
(' Cam: ' + (camera ? camera.entity.name : '-')).padEnd(22, ' ') +
(' Lay: ' + layer.name).padEnd(22, ' ') +
(transparent ? ' TRANSP' : ' OPAQUE') +
(ra.transparent ? ' TRANSP' : ' OPAQUE') +
(enabled ? ' ENABLED ' : ' DISABLED') +
(' RT: ' + (ra.renderTarget ? ra.renderTarget.name : '-')).padEnd(30, ' ') +
' Clear: ' + clear +
Expand All @@ -365,11 +385,10 @@ class LayerComposition extends EventHandler {
}

_isSublayerAdded(layer, transparent) {
for (let i = 0; i < this.layerList.length; i++) {
if (this.layerList[i] === layer && this.subLayerList[i] === transparent) {
Debug.error(`Sublayer ${layer.name}, transparent: ${transparent} is already added.`);
return true;
}
const map = transparent ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;
if (map.get(layer) !== undefined) {
Debug.error(`Sublayer ${layer.name}, transparent: ${transparent} is already added.`);
return true;
}
return false;
}
Expand All @@ -392,7 +411,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.push(true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -415,7 +434,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.splice(index, 0, true, true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -436,7 +455,7 @@ class LayerComposition extends EventHandler {
this.subLayerList.splice(id, 1);
this.subLayerEnabled.splice(id, 1);
id = this.layerList.indexOf(layer);
this._dirtyCameras = true;
this._dirty = true;
this.fire('remove', layer);
}

Expand All @@ -463,7 +482,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.push(true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -486,7 +505,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.splice(index, 0, true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -507,7 +526,7 @@ class LayerComposition extends EventHandler {
this._updateOpaqueOrder(i, len - 1);

this.subLayerEnabled.splice(i, 1);
this._dirtyCameras = true;
this._dirty = true;
if (this.layerList.indexOf(layer) < 0) {
this.fire('remove', layer); // no sublayers left
}
Expand All @@ -530,7 +549,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.push(true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -552,7 +571,7 @@ class LayerComposition extends EventHandler {
this.subLayerEnabled.splice(index, 0, true);

this._updateLayerMaps();
this._dirtyCameras = true;
this._dirty = true;
this.fire('add', layer);
}

Expand All @@ -572,7 +591,7 @@ class LayerComposition extends EventHandler {
this._updateTransparentOrder(i, len - 1);

this.subLayerEnabled.splice(i, 1);
this._dirtyCameras = true;
this._dirty = true;
if (this.layerList.indexOf(layer) < 0) {
this.fire('remove', layer); // no sublayers left
}
Expand All @@ -582,39 +601,32 @@ class LayerComposition extends EventHandler {
this._updateLayerMaps();
}

_getSublayerIndex(layer, transparent) {
// find sublayer index in the composition array
let id = this.layerList.indexOf(layer);
if (id < 0) return -1;

if (this.subLayerList[id] !== transparent) {
id = this.layerList.indexOf(layer, id + 1);
if (id < 0) return -1;
if (this.subLayerList[id] !== transparent) {
return -1;
}
}
return id;
}

/**
* Gets index of the opaque part of the supplied layer in the {@link LayerComposition#layerList}.
*
* @param {import('../layer.js').Layer} layer - A {@link Layer} to find index of.
* @returns {number} The index of the opaque part of the specified layer.
* @returns {number} The index of the opaque part of the specified layer, or -1 if it is not
* part of the composition.
*/
getOpaqueIndex(layer) {
return this._getSublayerIndex(layer, false);
return this.layerOpaqueIndexMap.get(layer) ?? -1;
}

/**
* Gets index of the semi-transparent part of the supplied layer in the {@link LayerComposition#layerList}.
*
* @param {import('../layer.js').Layer} layer - A {@link Layer} to find index of.
* @returns {number} The index of the semi-transparent part of the specified layer.
* @returns {number} The index of the semi-transparent part of the specified layer, or -1 if it
* is not part of the composition.
*/
getTransparentIndex(layer) {
return this._getSublayerIndex(layer, true);
return this.layerTransparentIndexMap.get(layer) ?? -1;
}

isEnabled(layer, transparent) {
const index = transparent ? this.getTransparentIndex(layer) : this.getOpaqueIndex(layer);
Debug.assert(index >= 0, `${transparent ? 'Transparent' : 'Opaque'} layer ${layer.name} is not part of the composition.`);
return this.subLayerEnabled[index];
}

/**
Expand All @@ -625,10 +637,16 @@ class LayerComposition extends EventHandler {
_updateLayerMaps() {
this.layerIdMap.clear();
this.layerNameMap.clear();
this.layerOpaqueIndexMap.clear();
this.layerTransparentIndexMap.clear();

for (let i = 0; i < this.layerList.length; i++) {
const layer = this.layerList[i];
this.layerIdMap.set(layer.id, layer);
this.layerNameMap.set(layer.name, layer);

const subLayerIndexMap = this.subLayerList[i] ? this.layerTransparentIndexMap : this.layerOpaqueIndexMap;
subLayerIndexMap.set(layer, i);
}
}

Expand Down
17 changes: 4 additions & 13 deletions src/scene/composition/render-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
class RenderAction {
constructor() {

// index into a layer stored in LayerComposition.layerList
this.layerIndex = 0;

// the layer
/** @type {import('../layer.js').Layer|null} */
this.layer = null;

// true if this uses transparent sublayer, opaque otherwise
this.transparent = false;

// camera of type CameraComponent
this.camera = null;

Expand Down Expand Up @@ -61,16 +62,6 @@ class RenderAction {
get hasDirectionalShadowLights() {
return this.directionalLights.length > 0;
}

/**
* @param {import('./layer-composition.js').LayerComposition} layerComposition - The layer
* composition.
* @returns {boolean} - True if the layer / sublayer referenced by the render action is enabled
*/
isLayerEnabled(layerComposition) {
const layer = layerComposition.layerList[this.layerIndex];
return layer.enabled && layerComposition.subLayerEnabled[this.layerIndex];
}
}

export { RenderAction };
Loading

0 comments on commit ec72104

Please sign in to comment.