Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class DiffuseSkyIrradianceLut {
? [import("./ShadersWGSL/fullscreenTriangle.vertex"), import("./ShadersWGSL/diffuseSkyIrradiance.fragment")]
: [import("./Shaders/fullscreenTriangle.vertex"), import("./Shaders/diffuseSkyIrradiance.fragment")]
);
await ShaderStore.LoadPendingIncludesAsync();

// Replace the CUSTOM_IRRADIANCE_FILTERING_INPUT and CUSTOM_IRRADIANCE_FILTERING_FUNCTION placeholders.
// Note, the regex replacements look for lines that *only* contain these placeholder strings.
Expand Down
52 changes: 42 additions & 10 deletions packages/dev/buildTools/src/buildShaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { type DevPackageName } from "./packageMapping.js";

/**
* Template creating hidden ts file containing the shaders.
* When moving to pure es6 we will need to remove the Shader assignment
* For main shaders: includes are registered as lazy resolvers instead of side-effect imports.
* A pending includes loader is self-registered so that materials can eagerly load all includes
* during extraInitializationsAsync via ShaderStore.LoadPendingIncludesAsync().
*/
const TsShaderTemplate = `// Do not edit.
import { ShaderStore } from "##SHADERSTORELOCATION_PLACEHOLDER##";
Expand All @@ -27,6 +29,7 @@ const shader = \`##SHADER_PLACEHOLDER##\`;
if (!ShaderStore.##SHADERSTORE_PLACEHOLDER##[name]) {
ShaderStore.##SHADERSTORE_PLACEHOLDER##[name] = shader;
}
##LOADINCLUDES_PLACEHOLDER##
##EXPORT_PLACEHOLDER##
`;

Expand Down Expand Up @@ -140,11 +143,16 @@ export function BuildShader(filePath: string, basePackageName: string | undefine
.replace(/\{\n([^#])/g, "{$1")
.replace(/\n\}/g, "}")
.replace(/^(?:[\t ]*(?:\r?\n|\r))+/gm, "")
.replace(/;\n([^#])/g, ";$1");
// Join consecutive lines (minification), but preserve newlines before:
// - preprocessor directives (#ifdef, #include, etc.)
// - shader declaration keywords that the processor handles per-line
.replace(/;\n(?!#|varying |uniform |attribute |var |var<|const |fn |struct |@|flat |linear |perspective )/g, ";");

// Generate imports for includes.
// Collect include dependencies for eager loading via _PendingIncludesLoaders.
let includeText = "";
const includeImportPaths: string[] = [];
const includes = GetIncludes(fxData);
const useEagerIncludes = checkArgs("--eager-includes", true);
includes.forEach((entry) => {
// Entry may have been something like #include<core/helperFunctions> where "core" is intended to override the basePackageName.
const isCoreInclude = (entry as string).startsWith("core/");
Expand All @@ -160,17 +168,21 @@ export function BuildShader(filePath: string, basePackageName: string | undefine
throw new Error("Unnecessary core include");
}

includeText =
includeText +
`import "./ShadersInclude/${entry}";
if (useEagerIncludes) {
includeText += `import "./ShadersInclude/${entry}";
`;
} else {
includeImportPaths.push(`"./ShadersInclude/${entry}"`);
}
} else {
const basePackageNameForImport = isCoreInclude ? "core" : basePackageName === undefined ? DetermineBasePackageNameForShaderInclude(filePath) : basePackageName;
const actualEntry = (entry as string).replace(/^core\//, "");
includeText =
includeText +
`import "${basePackageNameForImport}/Shaders${appendDirName}/ShadersInclude/${actualEntry}";
if (useEagerIncludes) {
includeText += `import "${basePackageNameForImport}/Shaders${appendDirName}/ShadersInclude/${actualEntry}";
`;
} else {
includeImportPaths.push(`"${basePackageNameForImport}/Shaders${appendDirName}/ShadersInclude/${actualEntry}"`);
}
// The shader code itself also needs to be updated by replacing `#include<core/helperFunctions>` with `#include<helperFunctions>`
if (isCoreInclude) {
fxData = fxData.replace(new RegExp(`#include<${entry}>`, "g"), `#include<${actualEntry}>`);
Expand All @@ -185,21 +197,41 @@ export function BuildShader(filePath: string, basePackageName: string | undefine
if (isCore) {
if (isInclude) {
shaderStoreLocation = "../../Engines/shaderStore";
includeText = includeText.replace(/ShadersInclude\//g, "");
if (useEagerIncludes) {
includeText = includeText.replace(/ShadersInclude\//g, "");
}
} else {
shaderStoreLocation = "../Engines/shaderStore";
}
} else {
shaderStoreLocation = "core/Engines/shaderStore";
}

// Generate loadIncludesAsync for shader files (including includes with nested #include directives).
// This self-registers a pending loader that eagerly loads all includes in parallel so
// the store is fully populated before ProcessIncludes runs.
// Include files also push loaders for their nested includes so that
// LoadPendingIncludesAsync can recursively load the full dependency tree.
let loadIncludesText = "";
if (!useEagerIncludes && includeImportPaths.length > 0) {
const imports = includeImportPaths.map((p) => ` import(${p})`).join(",\n");
loadIncludesText = `ShaderStore._PendingIncludesLoaders.push(() => Promise.all([
${imports}
]));`;
// For include files in core, paths are relative to ShadersInclude/ not Shaders/
if (isInclude && isCore) {
loadIncludesText = loadIncludesText.replace(/\.\/(ShadersInclude\/)/g, "./");
}
}

// Fill template in.
let tsContent = TsShaderTemplate.replace("##SHADERSTORELOCATION_PLACEHOLDER##", shaderStoreLocation);
tsContent = tsContent
.replace("##INCLUDES_PLACEHOLDER##", includeText)
.replace("##NAME_PLACEHOLDER##", shaderName)
.replace("##SHADER_PLACEHOLDER##", fxData)
.replace(new RegExp("##SHADERSTORE_PLACEHOLDER##", "g"), shaderStore)
.replace("##LOADINCLUDES_PLACEHOLDER##", loadIncludesText)
.replace(
"##EXPORT_PLACEHOLDER##",
`/** @internal */
Expand Down
2 changes: 2 additions & 0 deletions packages/dev/core/src/Collisions/gpuPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Logger } from "core/Misc/logger";
import { type Scene } from "core/scene";
import { type Nullable } from "core/types";
import { type Observer } from "core/Misc/observable";
import { ShaderStore } from "../Engines/shaderStore";

/**
* Class used to store the result of a GPU picking operation
Expand Down Expand Up @@ -176,6 +177,7 @@ export class GPUPicker {
} else {
await Promise.all([import("../Shaders/picking.fragment"), import("../Shaders/picking.vertex")]);
}
await ShaderStore.LoadPendingIncludesAsync();
},
};

Expand Down
20 changes: 20 additions & 0 deletions packages/dev/core/src/Engines/Processors/shaderProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ export function Initialize(options: _IProcessingOptions): void {
}
}

/**
* Pre-strips conditional blocks (#ifdef/#ifndef/#if/#else/#elif/#endif) from shader source
* based on the provided defines, BEFORE include resolution. This ensures that #include directives
* inside disabled conditional blocks are never resolved, enabling tree-shaking of unused shader includes.
* @param sourceCode the shader source code
* @param options the processing options (uses defines and processor)
* @param engine the engine (optional, used for global defines)
* @returns the source code with disabled conditional blocks removed
* @internal
*/
export function PreStripConditionalIncludes(sourceCode: string, options: _IProcessingOptions, engine?: AbstractEngine): string {
const preprocessors = PreparePreProcessors(options, engine);
const preProcessorsFromCode: { [key: string]: string } = {};
// Pass options without the processor so that EvaluatePreProcessors only evaluates
// #ifdef/#endif conditionals and does NOT run line-level processors (texture, uniform,
// varying, etc.) — those must only run once, during ProcessShaderConversion.
const strippedOptions: _IProcessingOptions = { ...options, processor: null };
return EvaluatePreProcessors(sourceCode, preprocessors, strippedOptions, preProcessorsFromCode);
}

/** @internal */
export function Process(sourceCode: string, options: _IProcessingOptions, callback: (migratedCode: string, codeBeforeMigration: string) => void, engine?: AbstractEngine) {
if (options.processor?.preProcessShaderCode) {
Expand Down
Loading
Loading