playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
126 lines (125 loc) • 4.15 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { DebugHelper } from "../../core/debug.js";
import { WorldClusters } from "../lighting/world-clusters.js";
import { FramePassMultiView } from "./frame-pass-multi-view.js";
const tempClusterArray = [];
class WorldClustersAllocator {
/**
* Create a new instance.
*
* @param {GraphicsDevice} graphicsDevice - The graphics device.
*/
constructor(graphicsDevice) {
/**
* Empty cluster with no lights.
*
* @type {WorldClusters|null}
*/
__publicField(this, "_empty", null);
/**
* All allocated clusters
*
* @type {WorldClusters[]}
*/
__publicField(this, "_allocated", []);
/**
* Render actions with all unique light clusters. The key is the hash of lights on a layer, the
* value is a render action with unique light clusters.
*
* @type {Map<number, RenderAction>}
*/
__publicField(this, "_clusters", /* @__PURE__ */ new Map());
this.device = graphicsDevice;
}
destroy() {
if (this._empty) {
this._empty.destroy();
this._empty = null;
}
this._allocated.forEach((cluster) => {
cluster.destroy();
});
this._allocated.length = 0;
}
get count() {
return this._allocated.length;
}
// returns an empty light cluster object to be used when no lights are used
get empty() {
if (!this._empty) {
const empty = new WorldClusters(this.device);
empty.name = "ClusterEmpty";
empty.update([]);
this._empty = empty;
}
return this._empty;
}
/**
* Assign clusters for one frame pass that owns {@link RenderPass#renderActions}.
* No-op when the pass has no render actions.
*
* @param {import('../../platform/graphics/frame-pass.js').FramePass} renderPass - Render pass
* (not a {@link FramePassMultiView} wrapper; those are unwrapped in {@link WorldClustersAllocator#assign}).
* @private
*/
_assignClustersForPass(renderPass) {
const renderActions = renderPass.renderActions;
if (!renderActions) {
return;
}
const count = renderActions.length;
for (let i = 0; i < count; i++) {
const ra = renderActions[i];
ra.lightClusters = null;
const layer = ra.layer;
if (layer.hasClusteredLights && layer.meshInstances.length) {
const hash = layer.getLightIdHash();
const existingRenderAction = this._clusters.get(hash);
let clusters = existingRenderAction?.lightClusters;
if (!clusters) {
clusters = tempClusterArray.pop() ?? new WorldClusters(this.device);
DebugHelper.setName(clusters, `Cluster-${this._allocated.length}`);
this._allocated.push(clusters);
this._clusters.set(hash, ra);
}
ra.lightClusters = clusters;
}
if (!ra.lightClusters) {
ra.lightClusters = this.empty;
}
}
}
// assign light clusters to render actions that need it
assign(renderPasses) {
tempClusterArray.push(...this._allocated);
this._allocated.length = 0;
this._clusters.clear();
const passCount = renderPasses.length;
for (let p = 0; p < passCount; p++) {
const pass = renderPasses[p];
if (pass instanceof FramePassMultiView) {
const children = pass.children;
for (let c = 0; c < children.length; c++) {
this._assignClustersForPass(children[c]);
}
} else {
this._assignClustersForPass(pass);
}
}
tempClusterArray.forEach((item) => item.destroy());
tempClusterArray.length = 0;
}
update(renderPasses, lighting) {
this.assign(renderPasses);
this._clusters.forEach((renderAction) => {
const layer = renderAction.layer;
const cluster = renderAction.lightClusters;
cluster.update(layer.clusteredLightsSet, lighting);
});
}
}
export {
WorldClustersAllocator
};