@openhps/core
Version:
Open Hybrid Positioning System - Core component
571 lines (529 loc) • 19.2 kB
JavaScript
import DataMap from '../DataMap.js';
import ChainMap from '../ChainMap.js';
import NodeBuilderState from './NodeBuilderState.js';
import { cubeMapNode } from '../../../nodes/utils/CubeMapNode.js';
import { NodeFrame } from '../../../nodes/Nodes.js';
import { objectGroup, renderGroup, frameGroup, cubeTexture, texture, fog, rangeFogFactor, densityFogFactor, reference, pmremTexture, screenUV } from '../../../nodes/TSL.js';
import { CubeUVReflectionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js';
import { hashArray } from '../../../nodes/core/NodeUtils.js';
const _outputNodeMap = new WeakMap();
const _chainKeys = [];
const _cacheKeyValues = [];
/**
* This renderer module manages node-related objects and is the
* primary interface between the renderer and the node system.
*
* @private
* @augments DataMap
*/
class Nodes extends DataMap {
/**
* Constructs a new nodes management component.
*
* @param {Renderer} renderer - The renderer.
* @param {Backend} backend - The renderer's backend.
*/
constructor(renderer, backend) {
super();
/**
* The renderer.
*
* @type {Renderer}
*/
this.renderer = renderer;
/**
* The renderer's backend.
*
* @type {Backend}
*/
this.backend = backend;
/**
* The node frame.
*
* @type {Renderer}
*/
this.nodeFrame = new NodeFrame();
/**
* A cache for managing node builder states.
*
* @type {Map<number,NodeBuilderState>}
*/
this.nodeBuilderCache = new Map();
/**
* A cache for managing data cache key data.
*
* @type {ChainMap}
*/
this.callHashCache = new ChainMap();
/**
* A cache for managing node uniforms group data.
*
* @type {ChainMap}
*/
this.groupsData = new ChainMap();
/**
* A cache for managing node objects of
* scene properties like fog or environments.
*
* @type {Object<string,WeakMap>}
*/
this.cacheLib = {};
}
/**
* Returns `true` if the given node uniforms group must be updated or not.
*
* @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group.
* @return {boolean} Whether the node uniforms group requires an update or not.
*/
updateGroup(nodeUniformsGroup) {
const groupNode = nodeUniformsGroup.groupNode;
const name = groupNode.name;
// objectGroup is always updated
if (name === objectGroup.name) return true;
// renderGroup is updated once per render/compute call
if (name === renderGroup.name) {
const uniformsGroupData = this.get(nodeUniformsGroup);
const renderId = this.nodeFrame.renderId;
if (uniformsGroupData.renderId !== renderId) {
uniformsGroupData.renderId = renderId;
return true;
}
return false;
}
// frameGroup is updated once per frame
if (name === frameGroup.name) {
const uniformsGroupData = this.get(nodeUniformsGroup);
const frameId = this.nodeFrame.frameId;
if (uniformsGroupData.frameId !== frameId) {
uniformsGroupData.frameId = frameId;
return true;
}
return false;
}
// other groups are updated just when groupNode.needsUpdate is true
_chainKeys[0] = groupNode;
_chainKeys[1] = nodeUniformsGroup;
let groupData = this.groupsData.get(_chainKeys);
if (groupData === undefined) this.groupsData.set(_chainKeys, groupData = {});
_chainKeys.length = 0;
if (groupData.version !== groupNode.version) {
groupData.version = groupNode.version;
return true;
}
return false;
}
/**
* Returns the cache key for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {number} The cache key.
*/
getForRenderCacheKey(renderObject) {
return renderObject.initialCacheKey;
}
/**
* Returns a node builder state for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {NodeBuilderState} The node builder state.
*/
getForRender(renderObject) {
const renderObjectData = this.get(renderObject);
let nodeBuilderState = renderObjectData.nodeBuilderState;
if (nodeBuilderState === undefined) {
const {
nodeBuilderCache
} = this;
const cacheKey = this.getForRenderCacheKey(renderObject);
nodeBuilderState = nodeBuilderCache.get(cacheKey);
if (nodeBuilderState === undefined) {
const nodeBuilder = this.backend.createNodeBuilder(renderObject.object, this.renderer);
nodeBuilder.scene = renderObject.scene;
nodeBuilder.material = renderObject.material;
nodeBuilder.camera = renderObject.camera;
nodeBuilder.context.material = renderObject.material;
nodeBuilder.lightsNode = renderObject.lightsNode;
nodeBuilder.environmentNode = this.getEnvironmentNode(renderObject.scene);
nodeBuilder.fogNode = this.getFogNode(renderObject.scene);
nodeBuilder.clippingContext = renderObject.clippingContext;
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState(nodeBuilder);
nodeBuilderCache.set(cacheKey, nodeBuilderState);
}
nodeBuilderState.usedTimes++;
renderObjectData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
/**
* Deletes the given object from the internal data map
*
* @param {any} object - The object to delete.
* @return {?Object} The deleted dictionary.
*/
delete(object) {
if (object.isRenderObject) {
const nodeBuilderState = this.get(object).nodeBuilderState;
nodeBuilderState.usedTimes--;
if (nodeBuilderState.usedTimes === 0) {
this.nodeBuilderCache.delete(this.getForRenderCacheKey(object));
}
}
return super.delete(object);
}
/**
* Returns a node builder state for the given compute node.
*
* @param {Node} computeNode - The compute node.
* @return {NodeBuilderState} The node builder state.
*/
getForCompute(computeNode) {
const computeData = this.get(computeNode);
let nodeBuilderState = computeData.nodeBuilderState;
if (nodeBuilderState === undefined) {
const nodeBuilder = this.backend.createNodeBuilder(computeNode, this.renderer);
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState(nodeBuilder);
computeData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
/**
* Creates a node builder state for the given node builder.
*
* @private
* @param {NodeBuilder} nodeBuilder - The node builder.
* @return {NodeBuilderState} The node builder state.
*/
_createNodeBuilderState(nodeBuilder) {
return new NodeBuilderState(nodeBuilder.vertexShader, nodeBuilder.fragmentShader, nodeBuilder.computeShader, nodeBuilder.getAttributesArray(), nodeBuilder.getBindings(), nodeBuilder.updateNodes, nodeBuilder.updateBeforeNodes, nodeBuilder.updateAfterNodes, nodeBuilder.observer, nodeBuilder.transforms);
}
/**
* Returns an environment node for the current configured
* scene environment.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene environment.
*/
getEnvironmentNode(scene) {
this.updateEnvironment(scene);
let environmentNode = null;
if (scene.environmentNode && scene.environmentNode.isNode) {
environmentNode = scene.environmentNode;
} else {
const sceneData = this.get(scene);
if (sceneData.environmentNode) {
environmentNode = sceneData.environmentNode;
}
}
return environmentNode;
}
/**
* Returns a background node for the current configured
* scene background.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene background.
*/
getBackgroundNode(scene) {
this.updateBackground(scene);
let backgroundNode = null;
if (scene.backgroundNode && scene.backgroundNode.isNode) {
backgroundNode = scene.backgroundNode;
} else {
const sceneData = this.get(scene);
if (sceneData.backgroundNode) {
backgroundNode = sceneData.backgroundNode;
}
}
return backgroundNode;
}
/**
* Returns a fog node for the current configured scene fog.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene fog.
*/
getFogNode(scene) {
this.updateFog(scene);
return scene.fogNode || this.get(scene).fogNode || null;
}
/**
* Returns a cache key for the given scene and lights node.
* This key is used by `RenderObject` as a part of the dynamic
* cache key (a key that must be checked every time the render
* objects is drawn).
*
* @param {Scene} scene - The scene.
* @param {LightsNode} lightsNode - The lights node.
* @return {number} The cache key.
*/
getCacheKey(scene, lightsNode) {
_chainKeys[0] = scene;
_chainKeys[1] = lightsNode;
const callId = this.renderer.info.calls;
const cacheKeyData = this.callHashCache.get(_chainKeys) || {};
if (cacheKeyData.callId !== callId) {
const environmentNode = this.getEnvironmentNode(scene);
const fogNode = this.getFogNode(scene);
if (lightsNode) _cacheKeyValues.push(lightsNode.getCacheKey(true));
if (environmentNode) _cacheKeyValues.push(environmentNode.getCacheKey());
if (fogNode) _cacheKeyValues.push(fogNode.getCacheKey());
_cacheKeyValues.push(this.renderer.shadowMap.enabled ? 1 : 0);
cacheKeyData.callId = callId;
cacheKeyData.cacheKey = hashArray(_cacheKeyValues);
this.callHashCache.set(_chainKeys, cacheKeyData);
_cacheKeyValues.length = 0;
}
_chainKeys.length = 0;
return cacheKeyData.cacheKey;
}
/**
* A boolean that indicates whether tone mapping should be enabled
* or not.
*
* @type {boolean}
*/
get isToneMappingState() {
return this.renderer.getRenderTarget() ? false : true;
}
/**
* If a scene background is configured, this method makes sure to
* represent the background with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateBackground(scene) {
const sceneData = this.get(scene);
const background = scene.background;
if (background) {
const forceUpdate = scene.backgroundBlurriness === 0 && sceneData.backgroundBlurriness > 0 || scene.backgroundBlurriness > 0 && sceneData.backgroundBlurriness === 0;
if (sceneData.background !== background || forceUpdate) {
const backgroundNode = this.getCacheNode('background', background, () => {
if (background.isCubeTexture === true || background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping || background.mapping === CubeUVReflectionMapping) {
if (scene.backgroundBlurriness > 0 || background.mapping === CubeUVReflectionMapping) {
return pmremTexture(background);
} else {
let envMap;
if (background.isCubeTexture === true) {
envMap = cubeTexture(background);
} else {
envMap = texture(background);
}
return cubeMapNode(envMap);
}
} else if (background.isTexture === true) {
return texture(background, screenUV.flipY()).setUpdateMatrix(true);
} else if (background.isColor !== true) {
console.error('WebGPUNodes: Unsupported background configuration.', background);
}
}, forceUpdate);
sceneData.backgroundNode = backgroundNode;
sceneData.background = background;
sceneData.backgroundBlurriness = scene.backgroundBlurriness;
}
} else if (sceneData.backgroundNode) {
delete sceneData.backgroundNode;
delete sceneData.background;
}
}
/**
* This method is part of the caching of nodes which are used to represents the
* scene's background, fog or environment.
*
* @param {string} type - The type of object to cache.
* @param {Object} object - The object.
* @param {Function} callback - A callback that produces a node representation for the given object.
* @param {boolean} [forceUpdate=false] - Whether an update should be enforced or not.
* @return {Node} The node representation.
*/
getCacheNode(type, object, callback, forceUpdate = false) {
const nodeCache = this.cacheLib[type] || (this.cacheLib[type] = new WeakMap());
let node = nodeCache.get(object);
if (node === undefined || forceUpdate) {
node = callback();
nodeCache.set(object, node);
}
return node;
}
/**
* If a scene fog is configured, this method makes sure to
* represent the fog with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateFog(scene) {
const sceneData = this.get(scene);
const sceneFog = scene.fog;
if (sceneFog) {
if (sceneData.fog !== sceneFog) {
const fogNode = this.getCacheNode('fog', sceneFog, () => {
if (sceneFog.isFogExp2) {
const color = reference('color', 'color', sceneFog).setGroup(renderGroup);
const density = reference('density', 'float', sceneFog).setGroup(renderGroup);
return fog(color, densityFogFactor(density));
} else if (sceneFog.isFog) {
const color = reference('color', 'color', sceneFog).setGroup(renderGroup);
const near = reference('near', 'float', sceneFog).setGroup(renderGroup);
const far = reference('far', 'float', sceneFog).setGroup(renderGroup);
return fog(color, rangeFogFactor(near, far));
} else {
console.error('THREE.Renderer: Unsupported fog configuration.', sceneFog);
}
});
sceneData.fogNode = fogNode;
sceneData.fog = sceneFog;
}
} else {
delete sceneData.fogNode;
delete sceneData.fog;
}
}
/**
* If a scene environment is configured, this method makes sure to
* represent the environment with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateEnvironment(scene) {
const sceneData = this.get(scene);
const environment = scene.environment;
if (environment) {
if (sceneData.environment !== environment) {
const environmentNode = this.getCacheNode('environment', environment, () => {
if (environment.isCubeTexture === true) {
return cubeTexture(environment);
} else if (environment.isTexture === true) {
return texture(environment);
} else {
console.error('Nodes: Unsupported environment configuration.', environment);
}
});
sceneData.environmentNode = environmentNode;
sceneData.environment = environment;
}
} else if (sceneData.environmentNode) {
delete sceneData.environmentNode;
delete sceneData.environment;
}
}
getNodeFrame(renderer = this.renderer, scene = null, object = null, camera = null, material = null) {
const nodeFrame = this.nodeFrame;
nodeFrame.renderer = renderer;
nodeFrame.scene = scene;
nodeFrame.object = object;
nodeFrame.camera = camera;
nodeFrame.material = material;
return nodeFrame;
}
getNodeFrameForRender(renderObject) {
return this.getNodeFrame(renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material);
}
/**
* Returns the current output cache key.
*
* @return {string} The output cache key.
*/
getOutputCacheKey() {
const renderer = this.renderer;
return renderer.toneMapping + ',' + renderer.currentColorSpace;
}
/**
* Checks if the output configuration (tone mapping and color space) for
* the given target has changed.
*
* @param {Texture} outputTarget - The output target.
* @return {boolean} Whether the output configuration has changed or not.
*/
hasOutputChange(outputTarget) {
const cacheKey = _outputNodeMap.get(outputTarget);
return cacheKey !== this.getOutputCacheKey();
}
/**
* Returns a node that represents the output configuration (tone mapping and
* color space) for the current target.
*
* @param {Texture} outputTarget - The output target.
* @return {Node} The output node.
*/
getOutputNode(outputTarget) {
const renderer = this.renderer;
const cacheKey = this.getOutputCacheKey();
const output = texture(outputTarget, screenUV).renderOutput(renderer.toneMapping, renderer.currentColorSpace);
_outputNodeMap.set(outputTarget, cacheKey);
return output;
}
/**
* Triggers the call of `updateBefore()` methods
* for all nodes of the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateBefore(renderObject) {
const nodeBuilder = renderObject.getNodeBuilderState();
for (const node of nodeBuilder.updateBeforeNodes) {
// update frame state for each node
this.getNodeFrameForRender(renderObject).updateBeforeNode(node);
}
}
/**
* Triggers the call of `updateAfter()` methods
* for all nodes of the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateAfter(renderObject) {
const nodeBuilder = renderObject.getNodeBuilderState();
for (const node of nodeBuilder.updateAfterNodes) {
// update frame state for each node
this.getNodeFrameForRender(renderObject).updateAfterNode(node);
}
}
/**
* Triggers the call of `update()` methods
* for all nodes of the given compute node.
*
* @param {Node} computeNode - The compute node.
*/
updateForCompute(computeNode) {
const nodeFrame = this.getNodeFrame();
const nodeBuilder = this.getForCompute(computeNode);
for (const node of nodeBuilder.updateNodes) {
nodeFrame.updateNode(node);
}
}
/**
* Triggers the call of `update()` methods
* for all nodes of the given compute node.
*
* @param {RenderObject} renderObject - The render object.
*/
updateForRender(renderObject) {
const nodeFrame = this.getNodeFrameForRender(renderObject);
const nodeBuilder = renderObject.getNodeBuilderState();
for (const node of nodeBuilder.updateNodes) {
nodeFrame.updateNode(node);
}
}
/**
* Returns `true` if the given render object requires a refresh.
*
* @param {RenderObject} renderObject - The render object.
* @return {boolean} Whether the given render object requires a refresh or not.
*/
needsRefresh(renderObject) {
const nodeFrame = this.getNodeFrameForRender(renderObject);
const monitor = renderObject.getMonitor();
return monitor.needsRefresh(renderObject, nodeFrame);
}
/**
* Frees the internal resources.
*/
dispose() {
super.dispose();
this.nodeFrame = new NodeFrame();
this.nodeBuilderCache = new Map();
this.cacheLib = {};
}
}
export default Nodes;