HLSL Flow Control Quirks

Started by
8 comments, last by DieterVW 15 years, 11 months ago
Hi, I'm writing an HLSL effect file for visualizing wave functions. I create a simple 2D mesh and use the shader to compute the different y-values. In one block of code, I use a renderMode var to keep track of whether I'm rendering the real, imaginary or absolute square values. I've tried several different methods of checking the var. You'd think that it would be a simple if-then-else check convering 3 possible numbers. Here are some examples (the code in question is at the bottom of the section):

float	time = 0.0;
float	timeOffset = 0.0f;

int nRenderMode = 0;
int numWaveFuncs = 1;	//7;
bool bAbsSquare = false;

static const float TWOPI = 6.283185307179586476925286766559;

static const float cExp = 0.25f;

float x0 = -20.0f;
float z0 = 0.0f;

float sigmaX = 0.25f;
float sigmaZ = 0.25f;
float v0 = 4.0f;

float4x4 matWorldViewProj: WORLDVIEWPROJECTION;

///////////////////////////////////////////////

Float WaveAmplitude(VSInput In)
{
	float ampX = pow(TWOPI, cExp);
	ampX *= sqrt(sigmaX);
	ampX = 1.0f / ampX;
	
	float ampZ = pow(TWOPI, cExp);
	ampZ *= sqrt(sigmaZ);
	ampZ = 1.0f / ampZ;
	
	float	a = 1.0f,
			b = 0.0f,
			c = 1.0f;
			
	float	ax	= a * ( (In.Pos.x - x0 - v0*timeOffset) * (In.Pos.x - x0 - v0*timeOffset) ),
			bxz	= b * (In.Pos.x - x0 - v0*timeOffset) * (In.Pos.z - z0),
			cz	= c * (In.Pos.z - z0) * (In.Pos.z - z0);
			
	float	ex = ax + bxz + cz;
	
	ex = exp(-ex);
	
	return ampX * ampZ * ex;
}

Float Phase(VSInput In)
{
	float3 pos = {In.Pos.x, 0.0f, In.Pos.z};
	float3 dir = {1.0f, 0.0f, 0.0f};
	
	float dv = dot(dir, pos);
	
	return dv - timeOffset;
}

Complex WaveFunction(VSInput In)
{
	Complex psi = (Complex) 0;
	
	float amp =		WaveAmplitude(In);
	float theta =	Phase(In);
	
	psi.real = amp * cos(theta);
	psi.imag = amp * sin(theta);
	
	return psi;	
}

///////////////////////////////////////////////////////////////

VSOutput VS_FreeParticle(VSInput In)
{
	VSOutput	Out = (VSOutput) 0;
	
	Complex psi;// = (Complex) 0;
	
	psi.real = 0.0f;
	psi.imag = 0.0f;
	
	float y = 0.0f;
	float yval = 0.0f;
	float ySum = 0.0f;
	
	float hue;
	
	float r = 0.0f,
			i = 0.0f;
	
	float4 tempcolor;
	
	timeOffset = time;
	
	psi = WaveFunction(In);
	
	r = psi.real;
	i = psi.imag;




	
	/***********************************
        ************************************
	*	Here is the logic block in 
	*	question
        ************************************
	************************************/
	if (bAbsSquare == true)
	{
		if (nRenderMode == 0)
			y = r * r + i * i;
	} 
	else
	{
		if (nRenderMode == 1)
			y = r;
		else if (nRenderMode == 2)
			y = i;
	}
	/***********************************
        ************************************
	*	END logic block in 
	*	question
        ************************************
	************************************/



	y = abs(ySum);
	
	tempcolor = float4(y, y, y, 1.0f);
	
	hue = (y * 360.0f + 225) / 360.0f;
	hue = modf(hue, yval);
	hue *= 360.0f;
	
	Out.Color = ConvertFromHSV(hue, 1.0f, y);
	
	//Finally project
	In.Pos.y = ySum;
	Out.Pos = mul(In.Pos, matWorldViewProj);
	
	return Out;
}


No matter what I set bAbsSquare to, it always falls into the first block (true), UNLESS I set manually bAbsSquare to false THE LINE BEFORE. In this form, when I change nRenderMode to something other than zero (and bAbsSquare to the proper value), the graph remains in the first form regardless. It SHOULD fall to the lower block and choose one of those values to graph. It doesn't. But when I comment the check for (nRenderMode == 0) out, the graph goes flat/normal when I change to other modes, meaning that it seems to skip the first block but miss the second block entirely. Even when I use the original method I had:

if (nRenderMode == 0)
	y = r * r + i * i;
else if (nRenderMode == 1)
	y = r;
else if (nRenderMode == 2)
	y = i;

it always seems to hit more than one of the blocks. When I leave the check for the absolute square (nRenderMode == 0) commented out (in this second form), the other values work just fine. When I put it back in and comment the imaginary part out (nRenderMode == 2), both the AbsSqr and Real parts graph correctly. If I comment the real part out (nRenderMode == 1), both the AbsSqr and Imaginary blocks seem to hit, as the graph for AbsSqr begins to fluctuate like the real or imaginary parts do (and it shouldn't). So when all 3 if's are in, the imaginary and AbsSqr values seem to affect one another, but the real doesn't. It seems in both forms of nRenderMode checking, leaving the check for the imaginary part seems to interfere only with the AbsSqr. The only place I change the bAbsSquare and nRenderMode values are on the C++ side of things and I have verified that each is correct. I know this is long and convoluted but any help would be appreciated.
Advertisement
I am not an HLSL whiz.

I know that some GPUs will not support nested logic.

Given that, a suggestion:

Try, with your "original" code, leaving out the else's.

Such as
if (nRenderMode == 0)	y = r * r + i * i;if (nRenderMode == 1)	y = r;if (nRenderMode == 2)	y = i;

It's probably just as efficient as your original.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

I tried that unfortunetly.

When I try
if (nRenderMode == 0)	ySum = r * r + i * i;	if (nRenderMode == 1)	ySum = r;	if (nRenderMode == 2)	ySum = i;


The first statement when true causes a graph that fluctuates up and down when it shouldn't. Should I try changing the first value check to 5 (arbitrary) instead of 0, technically niether of the if's should fire when nRenderMode == 0. However, one of them SEEMS to be getting hit because ySum is always reset to 0 before processing and when none are getting hit, ySum shouldn't change. Yet it looks/behaves just like one of the other graphs (except phase is different).

I just tried this:

if (nRenderMode == 10)	ySum = r * r + i * i;else{	if (nRenderMode == 1)		ySum = r;		else if (nRenderMode == 2)		ySum = i;}


And the first and third graphs are exactly the same (both looking like graph 1 should) but graph 2 (renderMode 1) is normal. However, when I change the if (nRenderMode == 10) to if (nRenderMode == 5), the first graph disappears and the second graph looks just like the first is supposed to while the 3rd graph is normal...

When I remove the nested else, the first graph then acts like the 3rd one should, the second one behaves like the first is supposed to and the 3rd graph is normal.

My head hurts...

*edit*
Also, nRenderMode is only supposed to change when I push Q, W, or E. I've searched for it just in case and those are the only places where the var is referenced.
Are you using an effect for which you need to "CommitChanges?"

That's required if you change effect parameters between effect->Begin() and effect->End() ( or whatever your coding language requires ).

EDIT: Maybe I'm missing something but it looks like you set y=abs(ySum) in spite of any logic (in your first post).

if (bAbsSquare == true){  if (nRenderMode == 0)    y = r * r + i * i;} else{  if (nRenderMode == 1)    y = r;  else if (nRenderMode == 2)    y = i;}// set y value no matter whaty = abs(ySum);


Have you revised that to ySum (as in later post)?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Sorry, y in your example was supposed to read ySum, I just got lazy. ySum was the sum of multiple waves (only one right now for debug, hence the = instead of += ) and y was the value used for coloring.

I'm not changing any parameters inside the Begin()/End() block.

Thanks for the help though so far.

*continues racking brain*
Still just hacking around here.

Assuming that you set bAbsSqr externally to the effect (seems obvious), are you sure the "true" value you set externally is the same "true" value that the GPU tests for? (you said it works if you set bAbsSqr internal to the GPU)

1. Instead of "bAbsSqr==true", how 'bout just "if( bAbsSqr )"?

2. You might try changing bAbsSqr to an int and testing for "bAbsSqr > 0" or something similar.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Well, I've figured it's best to revert to the normal nRenderMode checks, i.e. if (nRenderMode == 1) else if (nRenderMode == 2) else if (nRenderMode == 3) format and try and solve that problem.

It's extremely confusing that changing the value to compare nRenderMode against changes the flow. And I mean values that should never be attained. See above with the changing of the nRenderMode check from 50 to 100 changes the flow. It shouldn't considering nRenderMode can only be 1, 2, or 3 and nRenderMode == 50 or 100 should never evaluate to true, yet the presence of that check and the value it checks alters program flow.

(The code window seems to hide the plus signs...)

if (nRenderMode == 100)	ySum = r * r + i * i;else{	if (nRenderMode == 1)		ySum = r;	else if (nRenderMode == 2)		ySum = i;}


behaves differently than

if (nRenderMode == 50)	ySum = r * r + i * i;else{	if (nRenderMode == 1)		ySum = r;	else if (nRenderMode == 2)		ySum = i;}


Grrrrrr....

Thanks again though Buckeye.
Depending on your graphics card, the driver may try to do some trickery with control statements like these. To give the driver a better hint as to how you want the code generated try using the D3DXSHADER_PREFER_FLOW_CONTROL flag when you create the shader. You could also try using the [branch] directive in the HLSL code itself. It would look something like this.
[branch]if (nRenderMode == 0)  y = r * r + i * i;else if (nRenderMode == 1)  y = r;else if (nRenderMode == 2)  y = i;

This would give the driver stronger hints about what you really want to do.

neneboricua
Quote:Original post by neneboricua19
Depending on your graphics card, the driver may try to do some trickery with control statements like these. To give the driver a better hint as to how you want the code generated try using the D3DXSHADER_PREFER_FLOW_CONTROL flag when you create the shader. You could also try using the [branch] directive in the HLSL code itself. It would look something like this.
[branch]if (nRenderMode == 0)  y = r * r + i * i;else if (nRenderMode == 1)  y = r;else if (nRenderMode == 2)  y = i;

This would give the driver stronger hints about what you really want to do.

neneboricua



Actually, this flag only affects the way fxc compiles the shader. The driver will never see these flags and is free to do whatever it sees fit, but that doesn't mean it should break your intended behavior.

Take a look at the asm produced when compiling this shader and see if the branching is maintained. You should be able to see the affects of [branch] and PREFER_FLOW_CONTROL at work when comparing asm code with and without them.
It looks like you are not using fx files for your shader. In which case, any global constant must be set by the runtime. The initializers you specified in your shader for variables such as numWavFuncs, etc. are ignored by the compiler. Only static variable initializers will be used by the compiler.

The reason for this is that static variables cannot be changed by the runtime, where as the other uniform constant variables can be changed. So, uniform constant initializers are ignored and will have to be set manually through shader reflection. The static variables will be optimized into the shader at compile time.

If you use fx files, the initializers will be respected and set correctly. fx keeps track of this for you and can really help reduce this kind of work.

This topic is closed to new replies.

Advertisement