for loop in HLSL good idea?

Started by
5 comments, last by coderchris 15 years, 3 months ago
Hello! I want to texture my DirectX10 terrain with N layers, where N is 1,2,3 or 4. Since the user can edit the layers the number of layers is not known at compile time. Each layer has some textures (diffuse, normal and specular), where the normal and specular textures are optional. So for example a valid configuration with 2 layers could look like this: layer 0: ------- diffuse: foo.jpg normal: lay1_norm.png layer 1: ------- diffuse: bar.png In the pixelshader I want to blend all of these layers to achieve the texturing. My questions are: How could an effective pixel shader that fulfills this task look like? And second: How should I store the textures? I thought about a for loop, texture arrays for the diffuse, normal and specular maps (so diffuse[0] would be the diffuse texture of layer0 and specular[2] the specular map of layer3) and arrays that specfiy if the texture of a layer is set. The pixel shader could look like this:

for(uint i=0; i < layerCount; i++) {
   diffColor = diffuse.Sample( samLinear, float3(input.Tex, i) );
   if( isNormalMapDefined ) {  // test if there is a normal map for layer i
      normal = normal.Sample( samLinear, float3(input.Tex, i) );
      // calc lighting
   }
   // isNormalMapDefined and isSpecularMapDefined are bool arrays
   
   if( isSpecularMapDefined ) {
       specular = specular.Sample( samLinear, float3(input.Tex, i) );
       // modify
   }

   // blend result with previous loop iteration result
}


Is such a code possible in DX10 and SM 4.0? How will be the performance of such code? (Im concerned that a for loop and if statements that have to be executed for every pixel will kill my framerate) Any better solutions? Thanks!
Advertisement
Well you have a few options:

-Use a loop/ifs like you have.
-Put in blank textures to sample for the samplers that are unused, so, you always look them up. Might be cheaper than an if, might not.
-Write multiple shaders/techniques/passes to handle the different combinations.
It depends. If layerCount is a uniform global, the entire loop will be unrolled and optimised out. If not, then the loop will be executed as a loop - and dynamic branching is not exactly cheap. Dynamic branching is available on all SM3 cards and above.
NextWar: The Quest for Earth available now for Windows Phone 7.
You can use multiple pixel shaders for different texture combinations and set the pixel shader at runtime.
Quote:Original post by Hiyar
You can use multiple pixel shaders for different texture combinations and set the pixel shader at runtime.


If this is an option for you, I'd pick this route. That way you won't need to worry about the costs of if statements, loops or redundant texture lookups and may even be able to optimize the shader to the particularities of dealing with a specific number of layers. It may be a bit less elegant, but maybe you can put some shared functions in a common include file to alleviate this.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Thanks for your answers!

Yes, layerCount is a uniform variable. It is defined global in the .fx file and I set it in the host code with layerCountVar->SetInt(count), but the value can change from time to time (for example the user can add another layer).
I don't understand how the for loop can be unrolled if layerCount can change. I load my .fx file with D3DX10CreateEffectFromFile and I thought the whole code is compiled once and never touched again?

About the blank textures: I also thought about this option and ALWAYS sample all textures (diffuse, normal etc.) and if a texture isnt set I basically sample a null-texture. But I think this would not give right results. Imagine a layer that has no normal map, though I sample the null normal map and receive a float4(0,0,0,0) (I guess). If I now calculate my lighting based on this value it just gives crap results. So I think I have to use if statements...

About the different pixel shaders: I haven't thought about this approach cause there are so many possible combinations. I can have 3 layers where the first and second one uses a normal map, I can have 2 layers where one layer uses a specular map and the other doesnt and so on...
Or do you mean I should just make one technique for each layer count value? So one technique for one layer, one technique for 2 layers and so on (but still let the if statements in the code)?
Modern cards are getting very good at dynamic branching.

In terms of performance, the dynamic branching shouldnt be much of a problem because all pixels of the terrain that are rendered are going to end up folloing the EXACT same flow control path, so the hardware will not have any granularity problems, and the branch prediction will work very well.

However, If you ever end up porting this to shader model 3, there is an issue with the dynamic branching option that might make it impractical. The hardware cannot sample textures like that inside dynamic branches. In a sense, the compiler will end up moving all possible texture sampling outside of the dynamic branching.

This happens because sampling textures like this depends on dx/dy of the texture coordinates, which in SM3, can only be computed outside of any flow control. If you are using SM3, you could still partially use dynamic branching by using the ddx()/ddy() instructions once outside of the loop, then use tex2Dgrad to do the actual sampling (though you will have to share texcoords for all layers)

But, if you dont care about SM3 (I wouldnt :P), then I vote use the dynamic branching option that you first suggested.

This topic is closed to new replies.

Advertisement