mirror of
https://github.com/galacean/engine.git
synced 2026-05-06 22:23:05 +08:00
fix(shader): scan instance uniforms with macro awareness for raw GLSL
_scanInstanceUniforms regex-matches uniform declarations without understanding #ifdef blocks. For raw GLSL paths the source still contains preprocessor directives at scan time, so uniforms inside inactive branches (e.g. renderer_JointMatrix under #ifdef RENDERER_HAS_SKIN) get matched even when they won't compile. This caused "GPU Instancing does not support array uniform" errors for plain MeshRenderer batching whenever a SkinnedMeshRenderer had previously registered renderer_JointMatrix under ShaderDataGroup.Renderer. Add _scanInstanceUniformsWithMacros that walks the source line-by-line with a branch stack for #ifdef/#ifndef/#else/#endif, delegating active lines to the original scanner. compilePlatformSource passes its active macro set; the ShaderLab path keeps using the plain scanner since ShaderMacroProcessor.evaluate already expands directives there. Also change the array-uniform fallback from deletion to keeping the declaration as a regular uniform, so stray matches never directly fail shader compilation.
This commit is contained in:
@@ -164,7 +164,9 @@ export class ShaderFactory {
|
||||
|
||||
let instanceLayout: InstanceBufferLayout | null = null;
|
||||
if (isGPUInstance) {
|
||||
const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag);
|
||||
const activeMacros = new Set<string>();
|
||||
for (let i = 0, len = shaderMacroList.length; i < len; i++) activeMacros.add(shaderMacroList[i].name);
|
||||
const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, activeMacros);
|
||||
noIncludeVertex = injected.vertexSource;
|
||||
noIncludeFrag = injected.fragmentSource;
|
||||
instanceLayout = injected.instanceLayout;
|
||||
@@ -192,12 +194,18 @@ export class ShaderFactory {
|
||||
static injectInstanceUBO(
|
||||
engine: Engine,
|
||||
vertexSource: string,
|
||||
fragmentSource: string
|
||||
fragmentSource: string,
|
||||
activeMacros?: Set<string>
|
||||
): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } {
|
||||
// 1. Scan & strip renderer uniforms from both stages, collect into fieldMap
|
||||
const fieldMap: Record<number, string> = Object.create(null);
|
||||
vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap);
|
||||
fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap);
|
||||
if (activeMacros) {
|
||||
vertexSource = ShaderFactory._scanInstanceUniformsWithMacros(vertexSource, fieldMap, activeMacros);
|
||||
fragmentSource = ShaderFactory._scanInstanceUniformsWithMacros(fragmentSource, fieldMap, activeMacros);
|
||||
} else {
|
||||
vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap);
|
||||
fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap);
|
||||
}
|
||||
|
||||
// Fast empty check without allocating an array
|
||||
let hasField = false;
|
||||
@@ -299,10 +307,10 @@ export class ShaderFactory {
|
||||
if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer)
|
||||
return match;
|
||||
if (isDerived) return "";
|
||||
// Array uniforms not supported in instancing UBO, remove to fail shader compilation
|
||||
// Array uniforms not supported in instancing UBO, keep as regular uniform
|
||||
if (arraySize) {
|
||||
Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`);
|
||||
return "";
|
||||
return match;
|
||||
}
|
||||
// ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance
|
||||
fieldMap[ShaderProperty.getByName(name)._uniqueId] =
|
||||
@@ -311,6 +319,59 @@ export class ShaderFactory {
|
||||
});
|
||||
}
|
||||
|
||||
// Matches preprocessor directives at line start. Only `#ifdef / #ifndef / #else / #endif` are
|
||||
// supported; `#if` with expressions is not recognized (such blocks are treated as always-active).
|
||||
private static readonly _ifdefRegex = /^[ \t]*#ifdef\s+(\w+)/;
|
||||
private static readonly _ifndefRegex = /^[ \t]*#ifndef\s+(\w+)/;
|
||||
private static readonly _elseRegex = /^[ \t]*#else\b/;
|
||||
private static readonly _endifRegex = /^[ \t]*#endif\b/;
|
||||
|
||||
/**
|
||||
* Scan with preprocessor awareness, for raw GLSL paths where `#ifdef` blocks are not yet
|
||||
* expanded. Uniforms inside inactive branches are skipped.
|
||||
*/
|
||||
private static _scanInstanceUniformsWithMacros(
|
||||
source: string,
|
||||
fieldMap: Record<number, string>,
|
||||
activeMacros: Set<string>
|
||||
): string {
|
||||
// Preprocessor branch stack: each frame tracks whether current branch is active
|
||||
const branchStack: boolean[] = [true];
|
||||
const lines = source.split("\n");
|
||||
|
||||
for (let i = 0, n = lines.length; i < n; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
let m = line.match(ShaderFactory._ifdefRegex);
|
||||
if (m) {
|
||||
const parentActive = branchStack[branchStack.length - 1];
|
||||
branchStack.push(parentActive && activeMacros.has(m[1]));
|
||||
continue;
|
||||
}
|
||||
m = line.match(ShaderFactory._ifndefRegex);
|
||||
if (m) {
|
||||
const parentActive = branchStack[branchStack.length - 1];
|
||||
branchStack.push(parentActive && !activeMacros.has(m[1]));
|
||||
continue;
|
||||
}
|
||||
if (ShaderFactory._elseRegex.test(line)) {
|
||||
const parentActive = branchStack.length >= 2 ? branchStack[branchStack.length - 2] : true;
|
||||
const currentActive = branchStack[branchStack.length - 1];
|
||||
branchStack[branchStack.length - 1] = parentActive && !currentActive;
|
||||
continue;
|
||||
}
|
||||
if (ShaderFactory._endifRegex.test(line)) {
|
||||
if (branchStack.length > 1) branchStack.pop();
|
||||
continue;
|
||||
}
|
||||
if (!branchStack[branchStack.length - 1]) continue;
|
||||
|
||||
lines[i] = ShaderFactory._scanInstanceUniforms(line, fieldMap);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
private static _buildLayout(engine: Engine, fieldMap: Record<number, string>): InstanceBufferLayout {
|
||||
const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize;
|
||||
const std140Map = ShaderFactory._std140TypeInfoMap;
|
||||
|
||||
Reference in New Issue
Block a user