a laundry list of recurring HLSL nightmares

Started by
15 comments, last by Mick is Stumped 13 years, 6 months ago
I've had a lot of trouble working with HLSL... mainly because the documentation is holier than the holiest of swiss cheese.

Probably my sensibilities were most offended when I managed to put two and two together and realize that the integer registers had to be treated as a four component for loop initialization vector (ie, x is the stop condition, y is the initial value, z is the increment, and w if anything I forget) and the whole for loop syntax was really a crock of bullshit (which the docs of course fail to mention)

But I digress. I've accrued such a laundry list of nagging quandaries, I decided it was time to seek serious help. The equally dysfunctional XNA forums are down atm/again so rather than laying my beef on Microsoft's doorstep, I remembered this forum.

Some quick background. Basically I'm simulating the fixed function pipeline. Or rather taking some software which was written for FF and providing a mechanism for clients to override the functionality with custom shaders. So the FF states are basically tracked and written to select shader constant registers as they change.

All shaders assumed to be written in HLSL declare what states they are interested in via the :register(c0) syntax.

So generally speaking this seems to work well. However a few of the constant values occasionally become mysteriously bad. This is in no way caused by SetPixelShaderConstant calls. Pretty much these few constants must be constantly fed their values before every drawing call to make them stick.

My theory is they are getting written over by const like temporary variables declared in individual shader/function blocks. I tried mapping them to constant registers with the :register syntax, but even though the docs say:

[Storage_Class] [Type_Modifier] Type Name[Index]
[: Semantic]
[Annotations]
[= Initial_Value]
[: Packoffset]
[: Register];

It seems to be impossible to both have an initial value and use the :register syntax. So if you declare a constant of any kind like so:

float2 x = {0,0} : register(c0);

It won't compile. The only thing that compiles is:

float2 x = {0,0}; : register(c0);

But this doesn't seem to do any register mapping (at least in the cases I tried.)

Secondly I've had a ton of trouble associating textures with SetTexture and SetSamplerState. I'm not even sure it's possible...

Using the :TEXTURE0 style semantic may or may not implicate the individual texture variables. I'm not sure, the order of declaration may be all that matters.

It's seemingly impossible to assign semantics/registers to samplers if you need to initialize them with sampler_state, for the same reason above. So it may be assigning them to a texture variable via the sampler_state syntax is not good enough.

It's just not practical to ensure that every shaders required textures are congruent, 1, 2, 3, etc. For instance texture0 is the model texture. Texture1 is reserved for brightness correction in some cases, so texture2 is a dither texture. Shaders never need texture1 as of yet, because they can do the brightness adjustment based on a single constant. But tex1 gets that spot because the SetTextureStageState etc requires each stage be back to back.

So ideally what is required is a shader to be able to have one :TEXTURE0 texture, and another :TEXTURE2 texture, and no :TEXTURE1 texture... which correspond to SetTexture(0) and SetTexture(2).

I've yet to be able to achieve that with HLSL. I tried just now creating a black placeholder texture so there is always something set, ie. SetTexture(0,1,2) to no avail.

PS: As a bonus I'm curious how long a SetTexture is good for. The docs don't say. I think I've done some tests that show you can't just set it and forget it. I know SetRenderTarget invalidates SetViewport, but I'd like to know what other states are unreliable if any.

Thanks.
Advertisement

However a few of the constant values occasionally become mysteriously bad. This is in no way caused by SetPixelShaderConstant calls.
[/qquote]

Like most device state, there's only 3 ways to change it:

1. Resetting the device
2. Applying a state block
3. Calling the function that sets it.

If the state is changing, it has to be one of those things. Even D3DX helpers like Effects and constant tables that set state must do so through one of those methods. If you're having trouble tracking down the point at which at state change occurs, I would recommend using PIX to capture a frame. You can then go through your frame call by call, and inspect device state before and after.

Quote:
My theory is they are getting written over by const like temporary variables declared in individual shader/function blocks. I tried mapping them to constant registers with the :register syntax, but even though the docs say:

[Storage_Class] [Type_Modifier] Type Name[Index]
[: Semantic]
[Annotations]
[= Initial_Value]
[: Packoffset]
[: Register];

It seems to be impossible to both have an initial value and use the :register syntax. So if you declare a constant of any kind like so:

float2 x = {0,0} : register(c0);

It won't compile. The only thing that compiles is:

float2 x = {0,0}; : register(c0);

But this doesn't seem to do any register mapping (at least in the cases I tried.)


I think the syntax you need is this:
float2 x : register(c0) = {0, 0};


However giving an initial value for a uniform (which is a shader constant that's visible to the app and can have its value set) doesn't really make sense in most cases. This is because the app always specifies the value for the constant, which means the app would have to know about this initial value and use it to initialized whatever app-side data structures are used for setting the shader constants. The effects framework is capable of doing that so it can make sense in that case, but it doesn't sound like you're using effects (are you?). And even if you are using effects, mapping a constant to a register implies that your app is taking direct control of shader constants rather than having the effect do it.

Quote:
Secondly I've had a ton of trouble associating textures with SetTexture and SetSamplerState. I'm not even sure it's possible...

Using the :TEXTURE0 style semantic may or may not implicate the individual texture variables. I'm not sure, the order of declaration may be all that matters.

It's seemingly impossible to assign semantics/registers to samplers if you need to initialize them with sampler_state, for the same reason above. So it may be assigning them to a texture variable via the sampler_state syntax is not good enough.

It's just not practical to ensure that every shaders required textures are congruent, 1, 2, 3, etc. For instance texture0 is the model texture. Texture1 is reserved for brightness correction in some cases, so texture2 is a dither texture. Shaders never need texture1 as of yet, because they can do the brightness adjustment based on a single constant. But tex1 gets that spot because the SetTextureStageState etc requires each stage be back to back.

So ideally what is required is a shader to be able to have one :TEXTURE0 texture, and another :TEXTURE2 texture, and no :TEXTURE1 texture... which correspond to SetTexture(0) and SetTexture(2).

I've yet to be able to achieve that with HLSL. I tried just now creating a black placeholder texture so there is always something set, ie. SetTexture(0,1,2) to no avail.


Assigning a semantic doesn't do anything on its own. All it does is provide some metadata that the host app can retrieve. It's up to the host app to actually do anything with that data. So that's why assigning that semantic isn't doing anything.

To bind a sampler to an index you can use with SetTexture or SetSamplerState, you bind it to a sampler register (the same way you bind a uniform to a constant register). The sampler registers for ps_2_0 and ps_3_0 are s0-s15.

Also I should mention that if you're not using effects, assigning a sampler state won't do anything.

Quote:
As a bonus I'm curious how long a SetTexture is good for. The docs don't say. I think I've done some tests that show you can't just set it and forget it. I know SetRenderTarget invalidates SetViewport, but I'd like to know what other states are unreliable if any.


Same as shader constant registers. It's good until you reset, you apply a state block, or you call SetTexture.

[Edited by - MJP on October 11, 2010 12:03:11 PM]
The docs do say that initial values for constants are meaningless and must be set by the application.

Docs:
Initial Value
"Global variables—that are not marked static or extern—are not compiled into the shader and cannot be optimized. To initialize this type of global variable, use reflection to get their value and then set the value from the API using a constant buffer."


To make it more clear, it should probably say something like:

"Global variables—that are not marked static or extern—are not compiled into the shader, are not set automatically, and cannot be optimized. To initialize this type of global variable, use reflection to get their value and then set the value from the API using a constant buffer. This process of reflecting the initial value and setting it can be taken care of automatically by using Effects."
There's a lot here I'll have to think about / read more carefully, but it all looks pretty positive.

Off the top of my head, I'm pretty sure I tried putting the :register syntax before the assignment and it did not compile, but I will need to check again. Anyway if that's correct the docs are wrong there, unless the the lines below are not meant to be read as coming after the top line (which is pretty damn confusing)

There's a slim chance ApplyStateBlock might be the problem, I will have to test that to rule it out. I did not realize shader constants were considered states as such since they bypass the fixed function API more or less. I hope so.

I'm a little confused about semantics... if they are strictly superficial, then they would not be useful for declaring per vertex inputs. The annotation I understood as superficial (not effecting execution) but the semantics not so.

No the D3DX Effects API is not being used / probably would not make sense in this context. Also to be clear, not using the Effects API is not incompatible with the sampler_state syntax. In other words, the values supplied to sampler_state take precedence over the current state of SetSamplerState (edited: or I'm pretty damn sure that's my experience anyway... apologies for assertive wording)

[Edited by - Mick is Stumped on October 11, 2010 7:42:29 PM]
The Sampler state blocks for member assignment in HLSL code are Effects specific. The normal HLSL does not support that syntax. You'll have to set sampler state from the API too. Same goes with blending and rasterizing.
Quote:Original post by Mick is Stumped
Off the top of my head, I'm pretty sure I tried putting the :register syntax before the assignment and it did not compile, but I will need to check again. Anyway if that's correct the docs are wrong there, unless the the lines below are not meant to be read as coming after the top line (which is pretty damn confusing)


I tried it very quickly using one of the samples in the SDK, and it compiled. And yeah it would appear the docs are wrong, unless we're both missing something.

Quote:Original post by Mick is Stumped
I'm a little confused about semantics... if they are strictly superficial, then they would not be useful for declaring per vertex inputs. The annotation I understood as superficial (not effecting execution) but the semantics not so.


Sorry, I meant semantics for your uniforms. For inputs/outputs to your shaders, semantics obviously have meaning to the pipeline.

@DieterVW, I won't swear my life on it, but I'm nearly positive at some point I could force a sampler in the shader to linear or point filtering for example by specifying the desired mode in sampler_state form (no Effects interface involved)

@MJP, there were definitely some places in the code where ApplyStateBlock could cause trouble, but I fixed everything as near as I can tell without any gain in terms of the unreliable constant states. I'm sure the fixes have hardened the code some...

But my theory at this point is basically, because we are not explicitly setting the constants before setting the shader... in other words the constants just change as the interface states change, and the shaders are basically set before each drawing call (all shaders are expected to use the same constants in the same way)

[theory continued]
The HLSL compiled code is basically taking over some constants in some shaders because it thinks the current shader does not care about them... loading it with some arbitrary constant value from the shader's code, subsequently screwing the values that had been supplied by SetShaderConstant.

DieterVW says that register mapped values cannot be initialized by the shader (as I understand him) so I'm thinking the only thing to try is to just explicitly map any true constants to constant registers and initialize them all one time when the device is created...

This would of course make setting up shaders a lot more cumbersome for users. Some other approach, like forcing the compiler to treat automatic variables like float(1,1,1) as such / not try to map them to whatever constant registers it likes (if that ever happens)

FYI: For the record, I think I've already tried disabling all optimizations.


Thanks for all of the sound advice,

PS: I'd really appreciate it if someone could ensure me / explain what conditions are required for there to be a one to one mapping between the SetTexture index and a desired texture/sampler inside a shader... AND even if the shader only declares say index 0 and index 2 with no mention of index 1.


EDITED: I'd really like to get this sorted out because I'm a little skeptical about this approach in general, even though broadly speaking it seems to work quite well. For instance however I worry that code compiled in different user environments will for example bungle some other set of constant registers. I'm pretty near convinced it all comes down to how the compiler is treating the code.

[Edited by - Mick is Stumped on October 11, 2010 7:35:21 PM]
1. So something you said in your last post reminded me of something I forgot about, which is that shaders can temporarily set the values of constant registers. The compiler does this for literals and other constants in the shader code, so that they can be expressed in the shader assembly. You can see these values if you look at the disassembly for a shader, they look like this:
    vs_2_0    def c5, 0.159154937, 0.5, 6.28318548, -3.14159274    def c6, 2.38732409, 0.5, 0.100000001, 0    def c7, 1, 0, 0, 0    def c8, -1.55009923e-006, -2.17013894e-005, 0.00260416674, 0.00026041668    def c9, -0.020833334, -0.125, 1, 0.5

These values don't persist however (and the compiler only uses constant registers not used by that program), so they shouldn't mess with any values you've set onto the registers through SetVertexShaderConstantF/SetPixelShaderConstantF.

2. If you have a global constant in your shader code that you don't want to be visible to the app (which means it won't be mapped to a constant register), you use the "static" modifier.

3. Like I said before, you map a sampler to an index by binding it to a register.
sampler2D diffuseMap : register(s2);

That sampler is bound to the texture set with SetTexture(2, ...), as well as any sampler states set with SetSamplerState(2, ...).
Quote:Original post by MJP
1. So something you said in your last post reminded me of something I forgot about, which is that shaders can temporarily set the values of constant registers. The compiler does this for literals and other constants in the shader code, so that they can be expressed in the shader assembly. You can see these values if you look at the disassembly for a shader, they look like this:
    vs_2_0    def c5, 0.159154937, 0.5, 6.28318548, -3.14159274    def c6, 2.38732409, 0.5, 0.100000001, 0    def c7, 1, 0, 0, 0    def c8, -1.55009923e-006, -2.17013894e-005, 0.00260416674, 0.00026041668    def c9, -0.020833334, -0.125, 1, 0.5

These values don't persist however (and the compiler only uses constant registers not used by that program), so they shouldn't mess with any values you've set onto the registers through SetVertexShaderConstantF/SetPixelShaderConstantF.


This is basically what I've been suspecting, except the problem in "my" case is there are many different shaders, some depend on some constants, others don't, but the constants need to remain pristine, because if one shader writes over a constant (something that should never happen) the next shader up to bat probably needs that constant, but... as you can see it was clobbered with bogus data by the last shader (again, the app only updates the constants when their expected values change -- versus when the shaders change, which it does not do)

Quote:
2. If you have a global constant in your shader code that you don't want to be visible to the app (which means it won't be mapped to a constant register), you use the "static" modifier.


My understanding of the static keyword is that it's only used to make a locale variable remain persistent. In which case it would not be a constant I'd suspect. I've not seen any docs about other uses of 'static' though in C++ there are admittedly at least a few.

We do need however some way to make the compiler not use those registers. The constants are declared in every program at the moment, but only some "entrypoints" use some constants, so the compiler probably considers the unreferenced ones candidates for "taking over".

Quote:
3. Like I said before, you map a sampler to an index by binding it to a register.
sampler2D diffuseMap : register(s2);

That sampler is bound to the texture set with SetTexture(2, ...), as well as any sampler states set with SetSamplerState(2, ...).


Yes, this is my understanding up to this point. I will need to try some stuff. There are also texture registers I think, t0~n. I think the Direct3D interfaces always use SetSamplerState(1) for SetTexture(1).

I think what I was reaching for was a definitive statement that yes registers(s2) is the same as SamplerState(2) ... ie. HLSL will not take it upon itself to rewire things because there are now instructions that reference s1 for example.
Is it possible the "shared" keyword is what I'm looking for? I'm not sure what I was thinking it meant before, but if it means, don't clobber this constant ever, then that's what I need... will try that now!!

UPDATE: shared did not help, but I'm still curious what it is useful for.

This topic is closed to new replies.

Advertisement