The shader preprocessor reads #if X as #if defined(X), but #if !X is taken for a define named “!X”, which never exists, so the branch is always dropped, without a warning. #if !defined(X) works. In WGSL, where Babylon’s preprocessor is the only one, it is easy to write #if !X and get the other branch every time: we hit it in a WGSL material plugin, whose variant without the define never ran, and two of our measurements compared the same variant twice before we saw it.
Playground: https://playground.babylonjs.com/#T2ZIJX#0. A plane is drawn red when X is not defined and green when it is. On 9.33.0 it is green in both cases, in WGSL and in GLSL alike.
The cause is in ExtractOperation (shaderProcessor.ts): an operand without defined( and without a comparison becomes ShaderDefineIsDefinedOperator(expression), with the ! kept in the name. #19098 reads it as !defined(X), in && and || too, with unit tests; #if X, #if !defined(X) and the comparisons are unchanged.
Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me.
normaly ! must apply to the valid value not logical term try use !(X)
Good catch, silent failures like this are the worst to track down. Until the fix is merged, #if !defined(X) is the safe form. A warning when a define name starts with an exclamation mark would also have saved a lot of time here.
Thanks for the suggestion. We tried it, and in Babylon’s preprocessor #if !(X) is not a way around it: it is read as #if X, so it keeps the opposite branch, again without a warning. ShaderDefineExpression.infixToPostfix drops the operand it has gathered when it meets an opening parenthesis (if (c === "(") { operand = ""; ... }), and the ! in front of (X) is that operand. #if !(defined(X)) is read as #if defined(X) the same way.
The branch the preprocessor keeps on 9.33.1:
| condition |
X not defined |
X defined |
#if !X |
#else |
#else |
#if !(X) |
#else |
#if |
#if !defined(X) |
#if |
#else |
With #19098, #if !X keeps #if and then #else, as #if !defined(X) does. #if !(X) does not change: #19098 reads the ! of an operand in ExtractOperation, and with a parenthesis the ! is already gone by then. So for now #if !defined(X) is the form to use, as metin6176 says.
On the warning: once !X is read as !defined(X), there is no define named !X left to warn about, but !( is the same kind of silent failure. If it is welcome, we can send the ! in front of a parenthesis as a separate PR, rather than widen #19098, which is already approved.
Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me.