@luma.gl/engine
Version:
3D Engine Components for luma.gl
203 lines • 8.19 kB
JavaScript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { log } from '@luma.gl/core';
// import type {VariableShaderType, UniformValue, UniformFormat, UniformInfoDevice, Texture, Sampler} from '@luma.gl/core';
import { getShaderModuleDependencies } from '@luma.gl/shadertools';
import { splitUniformsAndBindings } from "./model/split-uniforms-and-bindings.js";
/**
* ShaderInputs holds uniform and binding values for one or more shader modules,
* - It can generate binary data for any uniform buffer
* - It can manage a uniform buffer for each block
* - It can update managed uniform buffers with a single call
* - It performs some book keeping on what has changed to minimize unnecessary writes to uniform buffers.
*/
export class ShaderInputs {
options = {
disableWarnings: false
};
/**
* The map of modules
* @todo should should this include the resolved dependencies?
*/
// @ts-ignore Fix typings
modules;
/** Stores the uniform values for each module */
moduleUniforms;
/** Stores the uniform bindings for each module */
moduleBindings;
/** Tracks if uniforms have changed */
// moduleUniformsChanged: Record<keyof ShaderPropsT, false | string>;
/**
* Create a new UniformStore instance
* @param modules
*/
constructor(
// @ts-ignore Fix typings
modules, options) {
Object.assign(this.options, options);
// Extract modules with dependencies
const resolvedModules = getShaderModuleDependencies(Object.values(modules).filter(isShaderInputsModuleWithDependencies));
for (const resolvedModule of resolvedModules) {
// @ts-ignore
modules[resolvedModule.name] = resolvedModule;
}
log.log(1, 'Creating ShaderInputs with modules', Object.keys(modules))();
// Store the module definitions and create storage for uniform values and binding values, per module
// @ts-ignore Fix typings
this.modules = modules;
this.moduleUniforms = {};
this.moduleBindings = {};
// Initialize the modules
for (const [name, module] of Object.entries(modules)) {
if (module) {
this._addModule(module);
if (module.name && name !== module.name && !this.options.disableWarnings) {
log.warn(`Module name: ${name} vs ${module.name}`)();
}
}
}
}
/** Destroy */
destroy() { }
/**
* Set module props
*/
setProps(props) {
for (const name of Object.keys(props)) {
const moduleName = name;
const moduleProps = props[moduleName] || {};
const module = this.modules[moduleName];
if (!module) {
// Ignore props for unregistered modules
if (!this.options.disableWarnings) {
log.warn(`Module ${name} not found`)();
}
}
else {
const oldUniforms = this.moduleUniforms[moduleName];
const oldBindings = this.moduleBindings[moduleName];
const uniformsAndBindings = module.getUniforms?.(moduleProps, oldUniforms) || moduleProps;
const { uniforms, bindings } = splitUniformsAndBindings(uniformsAndBindings, module.uniformTypes);
this.moduleUniforms[moduleName] = mergeModuleUniforms(oldUniforms, uniforms, module.uniformTypes);
this.moduleBindings[moduleName] = { ...oldBindings, ...bindings };
// this.moduleUniformsChanged ||= moduleName;
}
// console.log(`setProps(${String(moduleName)}`, moduleName, this.moduleUniforms[moduleName])
}
}
/**
* Return the map of modules
* @todo should should this include the resolved dependencies?
*/
getModules() {
return Object.values(this.modules);
}
/** Get all uniform values for all modules */
getUniformValues() {
return this.moduleUniforms;
}
/** Merges all bindings for the shader (from the various modules) */
getBindingValues() {
const bindings = {};
for (const moduleBindings of Object.values(this.moduleBindings)) {
Object.assign(bindings, moduleBindings);
}
return bindings;
}
// INTERNAL
/** Return a debug table that can be used for console.table() or log.table() */
getDebugTable() {
const table = {};
for (const [moduleName, module] of Object.entries(this.moduleUniforms)) {
for (const [key, value] of Object.entries(module)) {
table[`${moduleName}.${key}`] = {
type: this.modules[moduleName].uniformTypes?.[key],
value: String(value)
};
}
}
return table;
}
_addModule(module) {
const moduleName = module.name;
// Get default uniforms from module
this.moduleUniforms[moduleName] = mergeModuleUniforms({}, (module.defaultUniforms || {}), module.uniformTypes);
this.moduleBindings[moduleName] = {};
}
}
function mergeModuleUniforms(currentUniforms = {}, nextUniforms = {}, uniformTypes = {}) {
const mergedUniforms = { ...currentUniforms };
for (const [key, value] of Object.entries(nextUniforms)) {
if (value !== undefined) {
mergedUniforms[key] = mergeModuleUniformValue(currentUniforms[key], value, uniformTypes[key]);
}
}
return mergedUniforms;
}
function mergeModuleUniformValue(currentValue, nextValue, uniformType) {
if (!uniformType || typeof uniformType === 'string') {
return cloneModuleUniformValue(nextValue);
}
if (Array.isArray(uniformType)) {
if (isPackedUniformArrayValue(nextValue) || !Array.isArray(nextValue)) {
return cloneModuleUniformValue(nextValue);
}
const currentArray = Array.isArray(currentValue) && !isPackedUniformArrayValue(currentValue)
? [...currentValue]
: [];
const mergedArray = currentArray.slice();
for (let index = 0; index < nextValue.length; index++) {
const elementValue = nextValue[index];
if (elementValue !== undefined) {
mergedArray[index] = mergeModuleUniformValue(currentArray[index], elementValue, uniformType[0]);
}
}
return mergedArray;
}
if (!isPlainUniformObject(nextValue)) {
return cloneModuleUniformValue(nextValue);
}
const uniformStruct = uniformType;
const currentObject = isPlainUniformObject(currentValue) ? currentValue : {};
const mergedObject = { ...currentObject };
for (const [key, value] of Object.entries(nextValue)) {
if (value !== undefined) {
mergedObject[key] = mergeModuleUniformValue(currentObject[key], value, uniformStruct[key]);
}
}
return mergedObject;
}
function cloneModuleUniformValue(value) {
if (ArrayBuffer.isView(value)) {
return Array.prototype.slice.call(value);
}
if (Array.isArray(value)) {
if (isPackedUniformArrayValue(value)) {
return value.slice();
}
const compositeArray = value;
return compositeArray.map(element => element === undefined ? undefined : cloneModuleUniformValue(element));
}
if (isPlainUniformObject(value)) {
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
key,
nestedValue === undefined ? undefined : cloneModuleUniformValue(nestedValue)
]));
}
return value;
}
function isPackedUniformArrayValue(value) {
return (ArrayBuffer.isView(value) ||
(Array.isArray(value) && (value.length === 0 || typeof value[0] === 'number')));
}
function isPlainUniformObject(value) {
return (Boolean(value) &&
typeof value === 'object' &&
!Array.isArray(value) &&
!ArrayBuffer.isView(value));
}
function isShaderInputsModuleWithDependencies(module) {
return Boolean(module?.dependencies);
}
//# sourceMappingURL=shader-inputs.js.map