UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

126 lines (125 loc) 4.17 kB
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", []); /** * Layer render steps with all unique light clusters. The key is the hash of lights on a layer, * the value is a layer render step with unique light clusters. * * @type {Map<number, LayerRenderStep>} */ __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 RenderPassForward#layerRenderSteps}. * No-op when the pass has no layer render steps. * * @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 layerRenderSteps = renderPass.layerRenderSteps; if (!layerRenderSteps) { return; } const count = layerRenderSteps.length; for (let i = 0; i < count; i++) { const step = layerRenderSteps[i]; step.lightClusters = null; const layer = step.layer; if (layer.hasClusteredLights && layer.meshInstances.length) { const hash = layer.getLightIdHash(); const existingStep = this._clusters.get(hash); let clusters = existingStep?.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, step); } step.lightClusters = clusters; } if (!step.lightClusters) { step.lightClusters = this.empty; } } } // assign light clusters to layer render steps 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((step) => { const layer = step.layer; const cluster = step.lightClusters; cluster.update(layer.clusteredLightsSet, lighting); }); } } export { WorldClustersAllocator };