UNPKG

@luma.gl/engine

Version:

3D Engine Components for luma.gl

176 lines 7.08 kB
// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { Buffer, Sampler, Texture, TextureView, UniformStore } from '@luma.gl/core'; import { DynamicTexture } from "../dynamic-texture/dynamic-texture.js"; import { ShaderInputs } from "../shader-inputs.js"; import { shaderModuleHasUniforms } from "../utils/shader-module-utils.js"; import { uid } from "../utils/uid.js"; import { getModuleNameFromUniformBinding, MATERIAL_BIND_GROUP, MaterialFactory } from "./material-factory.js"; /** * Material owns bind group `3` resources and uniforms for one material instance. * * `setProps()` mutates uniform values in place. Structural resource changes are * expressed through `clone({...})`, which creates a new material identity. */ export class Material { /** Application-provided identifier. */ id; /** Device that owns the material resources. */ device; /** Factory that defines the material schema. */ factory; /** Shader inputs for the material-owned modules. */ shaderInputs; /** Internal binding store including uniform buffers and resource bindings. */ bindings = {}; _uniformStore; _bindGroupCacheToken = {}; constructor(device, props = {}) { this.id = props.id || uid('material'); this.device = device; this.factory = props.factory || new MaterialFactory(device, { modules: props.modules || props.shaderInputs?.getModules() || [] }); const moduleMap = Object.fromEntries((props.shaderInputs?.getModules() || this.factory.modules).map(module => [ module.name, module ])); this.shaderInputs = props.shaderInputs || new ShaderInputs(moduleMap); this._uniformStore = new UniformStore(this.device, this.shaderInputs.modules); for (const [moduleName, module] of Object.entries(this.shaderInputs.modules)) { if (this.ownsModule(moduleName) && shaderModuleHasUniforms(module)) { const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName); this.bindings[`${moduleName}Uniforms`] = uniformBuffer; } } this.updateShaderInputs(); if (props.bindings) { this._replaceOwnedBindings(props.bindings); } } /** Destroys managed uniform-buffer resources owned by this material. */ destroy() { this._uniformStore.destroy(); } /** Creates a new material variant with optional structural and uniform overrides. */ clone(props = {}) { const material = this.factory.createMaterial({ id: props.id, shaderInputs: props.shaderInputs, bindings: { ...this.getResourceBindings(), ...props.bindings } }); if (!props.shaderInputs) { material.setProps(this.shaderInputs.getUniformValues()); } if (props.moduleProps) { material.setProps(props.moduleProps); } return material; } /** Returns `true` if this material owns the supplied binding name. */ ownsBinding(bindingName) { return this.factory.ownsBinding(bindingName); } /** Returns `true` if this material owns the supplied shader module. */ ownsModule(moduleName) { return this.factory.ownsModule(moduleName); } /** Updates material uniform/module props in place without changing material identity. */ setProps(props) { this.shaderInputs.setProps(props); this.updateShaderInputs(); } /** Updates managed uniform buffers and shader-input-owned bindings. */ updateShaderInputs() { this._uniformStore.setUniforms(this.shaderInputs.getUniformValues()); const didChange = this._setOwnedBindings(this.shaderInputs.getBindingValues()); if (didChange) { this._bindGroupCacheToken = {}; } } /** Returns the material-owned resource bindings without internal uniform buffers. */ getResourceBindings() { const resourceBindings = {}; for (const [name, binding] of Object.entries(this.bindings)) { if (!getModuleNameFromUniformBinding(name)) { resourceBindings[name] = binding; } } return resourceBindings; } /** Returns the resolved bindings, including internal uniform buffers and ready textures. */ getBindings() { const validBindings = {}; const validBindingsMap = validBindings; for (const [name, binding] of Object.entries(this.bindings)) { if (binding instanceof DynamicTexture) { if (binding.isReady) { validBindingsMap[name] = binding.texture; } } else { validBindingsMap[name] = binding; } } return validBindings; } /** Packages resolved material bindings into logical bind group `3`. */ getBindingsByGroup() { return this.factory.getBindingsByGroup(this.getBindings()); } /** Returns the stable bind-group cache token for the requested bind group. */ getBindGroupCacheKey(group) { return group === MATERIAL_BIND_GROUP ? this._bindGroupCacheToken : null; } /** Returns the latest update timestamp across material-owned resources. */ getBindingsUpdateTimestamp() { let timestamp = 0; for (const binding of Object.values(this.bindings)) { if (binding instanceof TextureView) { timestamp = Math.max(timestamp, binding.texture.updateTimestamp); } else if (binding instanceof Buffer || binding instanceof Texture) { timestamp = Math.max(timestamp, binding.updateTimestamp); } else if (binding instanceof DynamicTexture) { timestamp = binding.texture ? Math.max(timestamp, binding.texture.updateTimestamp) : Infinity; } else if (!(binding instanceof Sampler)) { timestamp = Math.max(timestamp, binding.buffer.updateTimestamp); } } return timestamp; } /** Replaces owned resource bindings and invalidates the material cache identity when needed. */ _replaceOwnedBindings(bindings) { const didChange = this._setOwnedBindings(bindings); if (didChange) { this._bindGroupCacheToken = {}; } } _setOwnedBindings(bindings) { let didChange = false; for (const [name, binding] of Object.entries(bindings)) { if (binding === undefined) { continue; } if (!this.ownsBinding(name)) { continue; } if (this.bindings[name] !== binding) { this.bindings[name] = binding; didChange = true; } } return didChange; } } //# sourceMappingURL=material.js.map