playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
264 lines (263 loc) • 9.85 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 { GSplatManager } from "./gsplat-manager.js";
import { SetUtils } from "../../core/set-utils.js";
import { GSPLAT_FORWARD, GSPLAT_SHADOW } from "../constants.js";
import { GSplatResourceCleanup } from "../gsplat/gsplat-resource-cleanup.js";
const tempLayersToRemove = [];
class GSplatLayerData {
/**
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatDirector} director - The director.
* @param {Layer} layer - The layer.
* @param {Camera} camera - The camera.
*/
constructor(device, director, layer, camera) {
/**
* @type {GSplatManager|null}
*/
__publicField(this, "gsplatManager", null);
/**
* @type {GSplatManager|null}
*/
__publicField(this, "gsplatManagerShadow", null);
this.updateConfiguration(device, director, layer, camera);
}
/**
* Creates a new GSplatManager, sets its render mode, and fires the material:created event.
*
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatDirector} director - The director.
* @param {Layer} layer - The layer.
* @param {GraphNode} cameraNode - The camera node.
* @param {Camera} camera - The camera.
* @param {number} renderMode - The render mode flags.
* @returns {GSplatManager} The created manager.
* @private
*/
createManager(device, director, layer, cameraNode, camera, renderMode) {
const manager = new GSplatManager(device, director, layer, cameraNode);
manager.setRenderMode(renderMode);
if (director.eventHandler) {
director.eventHandler.fire("material:created", manager.material, camera, layer);
}
return manager;
}
/**
* Updates the manager configuration based on current layer placements.
*
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatDirector} director - The director.
* @param {Layer} layer - The layer.
* @param {Camera} camera - The camera.
*/
updateConfiguration(device, director, layer, camera) {
const cameraNode = camera.node;
const hasNormalPlacements = layer.gsplatPlacements.length > 0;
const hasShadowCasters = layer.gsplatShadowCasters.length > 0;
const setsEqual = SetUtils.equals(layer.gsplatPlacementsSet, layer.gsplatShadowCastersSet);
const useSharedManager = setsEqual && hasNormalPlacements;
const desiredMainMode = useSharedManager ? GSPLAT_FORWARD | GSPLAT_SHADOW : hasNormalPlacements ? GSPLAT_FORWARD : 0;
const desiredShadowMode = useSharedManager ? 0 : hasShadowCasters ? GSPLAT_SHADOW : 0;
if (desiredMainMode) {
if (this.gsplatManager) {
this.gsplatManager.setRenderMode(desiredMainMode);
} else {
this.gsplatManager = this.createManager(device, director, layer, cameraNode, camera, desiredMainMode);
}
} else if (this.gsplatManager) {
this.gsplatManager.destroy();
this.gsplatManager = null;
}
if (desiredShadowMode) {
if (this.gsplatManagerShadow) {
this.gsplatManagerShadow.setRenderMode(desiredShadowMode);
} else {
this.gsplatManagerShadow = this.createManager(device, director, layer, cameraNode, camera, desiredShadowMode);
}
} else if (this.gsplatManagerShadow) {
this.gsplatManagerShadow.destroy();
this.gsplatManagerShadow = null;
}
}
destroy() {
this.gsplatManager?.destroy();
this.gsplatManager = null;
this.gsplatManagerShadow?.destroy();
this.gsplatManagerShadow = null;
}
}
class GSplatCameraData {
constructor() {
/**
* @type {Map<Layer, GSplatLayerData>}
*/
__publicField(this, "layersMap", /* @__PURE__ */ new Map());
}
destroy() {
this.layersMap.forEach((layerData) => layerData.destroy());
this.layersMap.clear();
}
removeLayerData(layer) {
const layerData = this.layersMap.get(layer);
if (layerData) {
layerData.destroy();
this.layersMap.delete(layer);
}
}
getLayerData(device, director, layer, camera) {
let layerData = this.layersMap.get(layer);
if (!layerData) {
layerData = new GSplatLayerData(device, director, layer, camera);
this.layersMap.set(layer, layerData);
}
return layerData;
}
}
class GSplatDirector {
/**
* @param {GraphicsDevice} device - The graphics device.
* @param {Renderer} renderer - The renderer.
* @param {Scene} scene - The scene.
* @param {EventHandler} eventHandler - Event handler for firing events.
*/
constructor(device, renderer, scene, eventHandler) {
/**
* @type {GraphicsDevice}
*/
__publicField(this, "device");
/**
* Per camera data.
*
* @type {Map<Camera, GSplatCameraData>}
*/
__publicField(this, "camerasMap", /* @__PURE__ */ new Map());
/**
* @type {Scene}
*/
__publicField(this, "scene");
/**
* @type {EventHandler}
*/
__publicField(this, "eventHandler");
this.device = device;
this.renderer = renderer;
this.scene = scene;
this.eventHandler = eventHandler;
}
destroy() {
this.camerasMap.forEach((cameraData) => cameraData.destroy());
this.camerasMap.clear();
}
getCameraData(camera) {
let cameraData = this.camerasMap.get(camera);
if (!cameraData) {
cameraData = new GSplatCameraData();
this.camerasMap.set(camera, cameraData);
}
return cameraData;
}
/**
* Dispatches pick compute for the given camera and layer, returning a ready-to-render
* pick mesh instance (or null if no gsplat data exists for this camera/layer pair).
*
* @param {Camera} camera - The camera.
* @param {number} width - Pick target width.
* @param {number} height - Pick target height.
* @param {Layer} layer - The layer to pick from.
* @returns {import('../mesh-instance.js').MeshInstance|null} The configured pick mesh instance.
*/
prepareForPicking(camera, width, height, layer) {
const cameraData = this.camerasMap.get(camera);
if (!cameraData) return null;
const layerData = cameraData.layersMap.get(layer);
if (!layerData?.gsplatManager) return null;
return layerData.gsplatManager.prepareForPicking(camera, width, height);
}
/**
* Updates the director for the given layer composition cameras and layers.
*
* @param {LayerComposition} comp - The layer composition.
*/
update(comp) {
GSplatResourceCleanup.process(this.device);
this.camerasMap.forEach((cameraData, camera) => {
if (!comp.camerasSet.has(camera)) {
cameraData.destroy();
this.camerasMap.delete(camera);
} else {
cameraData.layersMap.forEach((layerData, layer) => {
if (!camera.layersSet.has(layer.id) || !layer.enabled) {
tempLayersToRemove.push(layer);
}
});
for (let i = 0; i < tempLayersToRemove.length; i++) {
const layer = tempLayersToRemove[i];
const layerData = cameraData.layersMap.get(layer);
if (layerData) {
layerData.destroy();
cameraData.layersMap.delete(layer);
}
}
tempLayersToRemove.length = 0;
}
});
let gsplatCount = 0;
let bufferCopyUploaded = 0;
let bufferCopyTotal = 0;
const camerasComponents = comp.cameras;
for (let i = 0; i < camerasComponents.length; i++) {
const camera = camerasComponents[i].camera;
let cameraData = this.camerasMap.get(camera);
const layerIds = camera.layers;
for (let j = 0; j < layerIds.length; j++) {
const layer = comp.getLayerById(layerIds[j]);
if (layer?.enabled) {
if (layer.gsplatPlacementsDirty || !cameraData) {
const hasNormalPlacements = layer.gsplatPlacements.length > 0;
const hasShadowCasters = layer.gsplatShadowCasters.length > 0;
if (!hasNormalPlacements && !hasShadowCasters) {
if (cameraData) {
cameraData.removeLayerData(layer);
}
} else {
cameraData ?? (cameraData = this.getCameraData(camera));
const layerData = cameraData.getLayerData(this.device, this, layer, camera);
layerData.updateConfiguration(this.device, this, layer, camera);
if (layerData.gsplatManager) {
layerData.gsplatManager.reconcile(layer.gsplatPlacements);
}
if (layerData.gsplatManagerShadow) {
layerData.gsplatManagerShadow.reconcile(layer.gsplatShadowCasters);
}
}
}
}
}
if (cameraData) {
for (const layerData of cameraData.layersMap.values()) {
if (layerData.gsplatManager) {
gsplatCount += layerData.gsplatManager.update();
bufferCopyUploaded += layerData.gsplatManager.bufferCopyUploaded;
bufferCopyTotal += layerData.gsplatManager.bufferCopyTotal;
}
if (layerData.gsplatManagerShadow) {
gsplatCount += layerData.gsplatManagerShadow.update();
bufferCopyUploaded += layerData.gsplatManagerShadow.bufferCopyUploaded;
bufferCopyTotal += layerData.gsplatManagerShadow.bufferCopyTotal;
}
}
}
}
this.renderer._gsplatCount = gsplatCount;
this.renderer._gsplatBufferCopy = bufferCopyTotal > 0 ? bufferCopyUploaded / bufferCopyTotal * 100 : 0;
this.scene.gsplat.frameEnd();
for (let i = 0; i < comp.layerList.length; i++) {
comp.layerList[i].gsplatPlacementsDirty = false;
}
}
}
export {
GSplatDirector
};