Shader instructions execute even if not connected?


I was playing with Custom node inside mat. editor and found this weird behaviour when made some loops.

Just for a test, I’ve made 100 000 000 for(){} loops to see, how this is going to effect performance. And the strange thing that fps dropped significantly even if I do not connect Custom node to material input.

With connected node:

With dissconnected node:

Without the node at all:

I know, that Custom node does behave differently, but I think that it should implement inside a shader as a function and not calling if not used. To be honest I’m not sure, why this implements inside shader even if user do not connect it at all.

So… Is this a bug, or something? Because, for example, I need to use it with if(){} statement… And is condition is false, than loop should not be executed. Loop does not execute, but I still get some performance issues.

Check out the generated HLSL. I guess this only happens in preview window. Also compilers are smart that they remove loop where you don’t do anything. If you actually make loop that big that will not be optimized your GPU will hang. So try to avoid loops larger than 100-1000.

I do avoid. I made this number just for a test to see, how will “if” material node work. And this behavior has appeared.

IF node does actually use ternary operation in UE4 which will execute both sides. result = bool ? a : b;

But it does not execute both sides… It executes the one, that is true.

But the question is about executing when not connected anyway.

You can’t test shaders like that. Shader compiler will throw away anything that can be pre computed at compile time. Also UE4 material compiler will precalculate many of the things that are not know at compile time but know at draw call time. Eg. Dynamic Material constants.

Also ternary does execute both.

But anyway it’s quite odd find that custom node that does not even get connected affect editor performance. Do you have live updates on?

I don’t understand… It should not execute, because if “if” statement fails, then the code will not be executed at all…

I did some tests in WebGL with this kind of code:

vec4 color = vec4(1,1,1,1);
if (true) {
	for (int i = 0; i<10000000;++i)
		color = vec4(0,0,0,0);
gl_FragColor = color;

So when I set to false, code wont be executed this number of times and I get good performanse. But if true, then I get like 5 fps.

But why mat nodes cannot behave like that??

I think the reason you are getting 5fps is due to the loop you have there, that if true is running 10 million times per frame, per vertex. That’s pretty extreme.

I am not positive on how the Custom node affects compilation, but I can tell you a StaticSwitch node will cause the shader to be compiled twice, once for true, once for false. Anything that can be modified by a material instance needs to be fully compiled in all configurations. Even though it’s not possible to modify the custom node from an instance, there may be an engine limitation placed on that node that causes it to act the same way, and most certainly will if using an instance variable as an input to set the condition.

I know, that this is very expensive (Per pixel, not per vertex, btw… It is fragment shader), a i’m NOT gonna use it. It is just for an example to see performance difference.

And… It doesn’t matter, how it should compile. It does matter, how it runs. As I said, in GLSL (Probably in HLSL too), “if(){}” is not going to call the logic inside, if condition is false.

I just stumbled on this as I was looking for the answer to the part about the “if” statement and the instruction cost. I did a test myself, using a distance based-POM material I needed this answer for. And it turns out that the unused condition is left unexecuted until the “if” is fulfilled in its respective favor.

In the “if” statement, I used constant values for this test. So I started with A=1000 and B=500. So therefore the condition “A>B” was fulfilled. Which is where the POM function is executed (Blue).

When I switched the A value from 1000 to 200, the fulfilled condition was then “A<B” and the other sections of the material was called (Red).

I also set “A==B” to the Red as well. And it held up when both variables were changed to 500.

And as you can see from the included images, the instruction count jumped 27 instructions when the value of A was greater than the value of B.

If statements will only skip code if the condition can be computed at compile time. The material editor will perform as much computation as possible outside of the GPU shader code, and just pass the result to the GPU as a constant, and no HLSL is generated for that computation. This can only be done if all the nodes connected to the input depend only on constant values. As soon as you plug in things like vertex data or texture data, everything has to be computed for both sides of the if node, and there will be no branches in the generated HLSL. You can verify this for yourself by looking at the HLSL for your material.