@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
416 lines • 19.4 kB
JavaScript
import { Material } from "./material.js";
import { EngineStore } from "../Engines/engineStore.js";
import { _ProcessIncludes } from "../Engines/Processors/shaderProcessor.js";
import { ShaderStore } from "../Engines/shaderStore.js";
const rxOption = new RegExp("^([gimus]+)!");
/**
* Class that manages the plugins of a material
* @since 5.0
*/
export class MaterialPluginManager {
/**
* Creates a new instance of the plugin manager
* @param material material that this manager will manage the plugins for
*/
constructor(material) {
/** @internal */
this._plugins = [];
this._activePlugins = [];
this._activePluginsForExtraEvents = [];
this._material = material;
this._scene = material.getScene();
this._engine = this._scene.getEngine();
}
/**
* @internal
*/
_addPlugin(plugin) {
for (let i = 0; i < this._plugins.length; ++i) {
if (this._plugins[i].name === plugin.name) {
return false;
}
}
if (this._material._uniformBufferLayoutBuilt) {
this._material.resetDrawCache();
this._material._createUniformBuffer();
}
if (!plugin.isCompatible(this._material.shaderLanguage)) {
// eslint-disable-next-line no-throw-literal
throw `The plugin "${plugin.name}" can't be added to the material "${this._material.name}" because the plugin is not compatible with the shader language of the material.`;
}
const pluginClassName = plugin.getClassName();
if (!MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName]) {
MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName] = "MATERIALPLUGIN_" + ++MaterialPluginManager._MaterialPluginCounter;
}
this._material._callbackPluginEventGeneric = (id, info) => this._handlePluginEvent(id, info);
this._plugins.push(plugin);
this._plugins.sort((a, b) => a.priority - b.priority);
this._codeInjectionPoints = {};
const defineNamesFromPlugins = {};
defineNamesFromPlugins[MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName]] = {
type: "boolean",
default: true,
};
for (const plugin of this._plugins) {
plugin.collectDefines(defineNamesFromPlugins);
this._collectPointNames("vertex", plugin.getCustomCode("vertex", this._material.shaderLanguage));
this._collectPointNames("fragment", plugin.getCustomCode("fragment", this._material.shaderLanguage));
}
this._defineNamesFromPlugins = defineNamesFromPlugins;
return true;
}
/**
* @internal
*/
_activatePlugin(plugin) {
if (this._activePlugins.indexOf(plugin) === -1) {
this._activePlugins.push(plugin);
this._activePlugins.sort((a, b) => a.priority - b.priority);
this._material._callbackPluginEventIsReadyForSubMesh = this._handlePluginEventIsReadyForSubMesh.bind(this);
this._material._callbackPluginEventPrepareDefinesBeforeAttributes = this._handlePluginEventPrepareDefinesBeforeAttributes.bind(this);
this._material._callbackPluginEventPrepareDefines = this._handlePluginEventPrepareDefines.bind(this);
this._material._callbackPluginEventBindForSubMesh = this._handlePluginEventBindForSubMesh.bind(this);
if (plugin.registerForExtraEvents) {
this._activePluginsForExtraEvents.push(plugin);
this._activePluginsForExtraEvents.sort((a, b) => a.priority - b.priority);
this._material._callbackPluginEventHasRenderTargetTextures = this._handlePluginEventHasRenderTargetTextures.bind(this);
this._material._callbackPluginEventFillRenderTargetTextures = this._handlePluginEventFillRenderTargetTextures.bind(this);
this._material._callbackPluginEventHardBindForSubMesh = this._handlePluginEventHardBindForSubMesh.bind(this);
}
}
}
/**
* Gets a plugin from the list of plugins managed by this manager
* @param name name of the plugin
* @returns the plugin if found, else null
*/
getPlugin(name) {
for (let i = 0; i < this._plugins.length; ++i) {
if (this._plugins[i].name === name) {
return this._plugins[i];
}
}
return null;
}
_handlePluginEventIsReadyForSubMesh(eventData) {
let isReady = true;
for (const plugin of this._activePlugins) {
isReady = isReady && plugin.isReadyForSubMesh(eventData.defines, this._scene, this._engine, eventData.subMesh);
}
eventData.isReadyForSubMesh = isReady;
}
_handlePluginEventPrepareDefinesBeforeAttributes(eventData) {
for (const plugin of this._activePlugins) {
plugin.prepareDefinesBeforeAttributes(eventData.defines, this._scene, eventData.mesh);
}
}
_handlePluginEventPrepareDefines(eventData) {
for (const plugin of this._activePlugins) {
plugin.prepareDefines(eventData.defines, this._scene, eventData.mesh);
}
}
_handlePluginEventHardBindForSubMesh(eventData) {
for (const plugin of this._activePluginsForExtraEvents) {
plugin.hardBindForSubMesh(this._material._uniformBuffer, this._scene, this._engine, eventData.subMesh);
}
}
_handlePluginEventBindForSubMesh(eventData) {
for (const plugin of this._activePlugins) {
plugin.bindForSubMesh(this._material._uniformBuffer, this._scene, this._engine, eventData.subMesh);
}
}
_handlePluginEventHasRenderTargetTextures(eventData) {
let hasRenderTargetTextures = false;
for (const plugin of this._activePluginsForExtraEvents) {
hasRenderTargetTextures = plugin.hasRenderTargetTextures();
if (hasRenderTargetTextures) {
break;
}
}
eventData.hasRenderTargetTextures = hasRenderTargetTextures;
}
_handlePluginEventFillRenderTargetTextures(eventData) {
for (const plugin of this._activePluginsForExtraEvents) {
plugin.fillRenderTargetTextures(eventData.renderTargets);
}
}
_handlePluginEvent(id, info) {
switch (id) {
case 512 /* MaterialPluginEvent.GetActiveTextures */: {
const eventData = info;
for (const plugin of this._activePlugins) {
plugin.getActiveTextures(eventData.activeTextures);
}
break;
}
case 256 /* MaterialPluginEvent.GetAnimatables */: {
const eventData = info;
for (const plugin of this._activePlugins) {
plugin.getAnimatables(eventData.animatables);
}
break;
}
case 1024 /* MaterialPluginEvent.HasTexture */: {
const eventData = info;
let hasTexture = false;
for (const plugin of this._activePlugins) {
hasTexture = plugin.hasTexture(eventData.texture);
if (hasTexture) {
break;
}
}
eventData.hasTexture = hasTexture;
break;
}
case 2 /* MaterialPluginEvent.Disposed */: {
const eventData = info;
for (const plugin of this._plugins) {
plugin.dispose(eventData.forceDisposeTextures);
}
break;
}
case 4 /* MaterialPluginEvent.GetDefineNames */: {
const eventData = info;
eventData.defineNames = this._defineNamesFromPlugins;
break;
}
case 128 /* MaterialPluginEvent.PrepareEffect */: {
const eventData = info;
for (const plugin of this._activePlugins) {
eventData.fallbackRank = plugin.addFallbacks(eventData.defines, eventData.fallbacks, eventData.fallbackRank);
plugin.getAttributes(eventData.attributes, this._scene, eventData.mesh);
}
if (this._uniformList.length > 0) {
eventData.uniforms.push(...this._uniformList);
}
if (this._samplerList.length > 0) {
eventData.samplers.push(...this._samplerList);
}
if (this._uboList.length > 0) {
eventData.uniformBuffersNames.push(...this._uboList);
}
eventData.customCode = this._injectCustomCode(eventData, eventData.customCode);
break;
}
case 8 /* MaterialPluginEvent.PrepareUniformBuffer */: {
const eventData = info;
this._uboDeclaration = "";
this._vertexDeclaration = "";
this._fragmentDeclaration = "";
this._uniformList = [];
this._samplerList = [];
this._uboList = [];
const isWebGPU = this._material.shaderLanguage === 1 /* ShaderLanguage.WGSL */;
for (const plugin of this._plugins) {
const uniforms = plugin.getUniforms(this._material.shaderLanguage);
if (uniforms) {
if (uniforms.ubo) {
for (const uniform of uniforms.ubo) {
if (uniform.size && uniform.type) {
const arraySize = uniform.arraySize ?? 0;
eventData.ubo.addUniform(uniform.name, uniform.size, arraySize);
if (isWebGPU) {
let type;
switch (uniform.type) {
case "mat4":
type = "mat4x4f";
break;
case "float":
type = "f32";
break;
default:
type = `${uniform.type}f`;
break;
}
this._uboDeclaration += `uniform ${uniform.name}: ${type}${arraySize > 0 ? `[${arraySize}]` : ""};\n`;
}
else {
this._uboDeclaration += `${uniform.type} ${uniform.name}${arraySize > 0 ? `[${arraySize}]` : ""};\n`;
}
}
this._uniformList.push(uniform.name);
}
}
if (uniforms.vertex) {
this._vertexDeclaration += uniforms.vertex + "\n";
}
if (uniforms.fragment) {
this._fragmentDeclaration += uniforms.fragment + "\n";
}
}
plugin.getSamplers(this._samplerList);
plugin.getUniformBuffersNames(this._uboList);
}
break;
}
}
}
_collectPointNames(shaderType, customCode) {
if (!customCode) {
return;
}
for (const pointName in customCode) {
if (!this._codeInjectionPoints[shaderType]) {
this._codeInjectionPoints[shaderType] = {};
}
this._codeInjectionPoints[shaderType][pointName] = true;
}
}
_injectCustomCode(eventData, existingCallback) {
return (shaderType, code) => {
if (existingCallback) {
code = existingCallback(shaderType, code);
}
if (this._uboDeclaration) {
code = code.replace("#define ADDITIONAL_UBO_DECLARATION", this._uboDeclaration);
}
if (this._vertexDeclaration) {
code = code.replace("#define ADDITIONAL_VERTEX_DECLARATION", this._vertexDeclaration);
}
if (this._fragmentDeclaration) {
code = code.replace("#define ADDITIONAL_FRAGMENT_DECLARATION", this._fragmentDeclaration);
}
const points = this._codeInjectionPoints?.[shaderType];
if (!points) {
return code;
}
let processorOptions = null;
for (let pointName in points) {
let injectedCode = "";
for (const plugin of this._activePlugins) {
let customCode = plugin.getCustomCode(shaderType, this._material.shaderLanguage)?.[pointName];
if (!customCode) {
continue;
}
if (plugin.resolveIncludes) {
if (processorOptions === null) {
const shaderLanguage = 0 /* ShaderLanguage.GLSL */;
processorOptions = {
defines: [], // not used by _ProcessIncludes
indexParameters: eventData.indexParameters,
isFragment: false,
shouldUseHighPrecisionShader: this._engine._shouldUseHighPrecisionShader,
processor: undefined, // not used by _ProcessIncludes
supportsUniformBuffers: this._engine.supportsUniformBuffers,
shadersRepository: ShaderStore.GetShadersRepository(shaderLanguage),
includesShadersStore: ShaderStore.GetIncludesShadersStore(shaderLanguage),
version: undefined, // not used by _ProcessIncludes
platformName: this._engine.shaderPlatformName,
processingContext: undefined, // not used by _ProcessIncludes
isNDCHalfZRange: this._engine.isNDCHalfZRange,
useReverseDepthBuffer: this._engine.useReverseDepthBuffer,
processCodeAfterIncludes: undefined, // not used by _ProcessIncludes
};
}
processorOptions.isFragment = shaderType === "fragment";
_ProcessIncludes(customCode, processorOptions, (code) => (customCode = code));
}
injectedCode += customCode + "\n";
}
if (injectedCode.length > 0) {
if (pointName.charAt(0) === "!") {
// pointName is a regular expression
pointName = pointName.substring(1);
let regexFlags = "g";
if (pointName.charAt(0) === "!") {
// no flags
regexFlags = "";
pointName = pointName.substring(1);
}
else {
// get the flag(s)
const matchOption = rxOption.exec(pointName);
if (matchOption && matchOption.length >= 2) {
regexFlags = matchOption[1];
pointName = pointName.substring(regexFlags.length + 1);
}
}
if (regexFlags.indexOf("g") < 0) {
// we force the "g" flag so that the regexp object is stateful!
regexFlags += "g";
}
const sourceCode = code;
const rx = new RegExp(pointName, regexFlags);
let match = rx.exec(sourceCode);
while (match !== null) {
let newCode = injectedCode;
for (let i = 0; i < match.length; ++i) {
newCode = newCode.replace("$" + i, match[i]);
}
code = code.replace(match[0], newCode);
match = rx.exec(sourceCode);
}
}
else {
const fullPointName = "#define " + pointName;
code = code.replace(fullPointName, "\n" + injectedCode + "\n" + fullPointName);
}
}
}
return code;
};
}
}
/** Map a plugin class name to a #define name (used in the vertex/fragment shaders as a marker of the plugin usage) */
MaterialPluginManager._MaterialPluginClassToMainDefine = {};
MaterialPluginManager._MaterialPluginCounter = 0;
(() => {
EngineStore.OnEnginesDisposedObservable.add(() => {
UnregisterAllMaterialPlugins();
});
})();
const plugins = [];
let inited = false;
let observer = null;
/**
* Registers a new material plugin through a factory, or updates it. This makes the plugin available to all materials instantiated after its registration.
* @param pluginName The plugin name
* @param factory The factory function which allows to create the plugin
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function RegisterMaterialPlugin(pluginName, factory) {
if (!inited) {
observer = Material.OnEventObservable.add((material) => {
for (const [, factory] of plugins) {
factory(material);
}
}, 1 /* MaterialPluginEvent.Created */);
inited = true;
}
const existing = plugins.filter(([name, _factory]) => name === pluginName);
if (existing.length > 0) {
existing[0][1] = factory;
}
else {
plugins.push([pluginName, factory]);
}
}
/**
* Removes a material plugin from the list of global plugins.
* @param pluginName The plugin name
* @returns true if the plugin has been removed, else false
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function UnregisterMaterialPlugin(pluginName) {
for (let i = 0; i < plugins.length; ++i) {
if (plugins[i][0] === pluginName) {
plugins.splice(i, 1);
if (plugins.length === 0) {
UnregisterAllMaterialPlugins();
}
return true;
}
}
return false;
}
/**
* Clear the list of global material plugins
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function UnregisterAllMaterialPlugins() {
plugins.length = 0;
inited = false;
Material.OnEventObservable.remove(observer);
observer = null;
}
//# sourceMappingURL=materialPluginManager.js.map