TexGen issues with recent Panda build

I’ve been playing around with the latest CVS of Panda, and I’ve noticed that the texture coordinate generator is causing a crash under Windows when using the auto-shader.

In particular,

self.nodePath.setTexGen(self.TexStage, TexGenAttrib.MWorldPosition)

results in an exception. I’m still working on setting up a development environment so I can debug live Panda code, but I figured I would toss this out there in case anyone knows what might be up.

From what I have heard, texture transforms are now supported under the auto-shader, but I think there might be an issue. (The crash doesn’t occur when auto-shader is off.) Sorry I can’t provide more info than that at this time.

This code works fine for me:

from direct.directbase import DirectStart
from pandac.PandaModules import *
b=loader.loadModel("panda")
b.reparentTo(render)
b.setShaderAuto()
b.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
run()

Can you give me a minimal piece of code that crashes for you?

Sure, here you go.

from pandac.PandaModules import *
import direct.directbase.DirectStart

render.setShaderAuto()

smiley = loader.loadModel ('models/smiley.egg')
smiley.reparentTo (render)

tex = loader.loadTexture ('maps/envir-ground.jpg')
ts = TextureStage ('ts')

smiley.setTexture (ts, tex)
smiley.setTexGen(ts, TexGenAttrib.MWorldPosition)

run()

It seems to have something to do with the texture stage assignment.

Using TextureStage.getDefault() works, but I can’t use the texture stage I just assigned to the node. Maybe some priority conflict with multiple stages?

Ooh. This is going to require some thought.

The problem is that in ShaderGenerator::synthesize_shader, it enumerates all incoming texture coordinates to the shader. But it doesn’t handle out-of-order texture stages with TexGenAttribs. It works fine if the first texture stage has a TexGen, but if it doesn’t, there is a gap in the texcoord_vreg/texcoord_freg list, which causes a crash here:

  for (int i=0; i<_num_textures; i++) {
    if (!tex_gen->has_stage(texture->get_on_stage(i))) {
      texcoord_vreg.push_back(alloc_vreg());
      texcoord_freg.push_back(alloc_freg());
      text << "\t in float4 vtx_texcoord" << i << " : " << texcoord_vreg[i] << ",\n";   // <-- Oops, crashes because second stage has TexGen, first does not, so #2 referenced in a singular-entry list.
      text << "\t out float4 l_texcoord" << i << " : " << texcoord_freg[i] << ",\n";
    }
  }

All the TexGen code assumes there’s a 1-to-1 match between the stage number and the texcoords defined in the shader, but this isn’t always the case. (Such as my example code, where the first stage is a standard stage and the second uses TexGen coords.)

Whoops, so that’s the issue. I quickly debugged it before but I couldn’t find what was wrong, but you pointed it out. Thanks! :slight_smile:

I guess we should add code to detect gaps - if there’s a gap, it will just insert the texcoord input anyways (even if it won’t be used). This will be the safest solution, as I’m not sure if Cg allows gaps in TEXCOORD# semantics.

I’m sure the compiler will optimize it out for us.

Post removed.

Just noticed a bug in my patch to fix this. I’m so tired!

I think I have everything backwards, so I want to run a few more tests.

BTW, how do you get access to the text of the generated shader code from Python now? Since the shader is no longer a separate object that can be queried directly, I couldn’t figure out how to get access. I’d also love to see what this compiles down into.

Is there a way to query what texture stages are assigned to a node, too?

Don’t worry, I can fix it too, but I can’t until I finished making some other lowlevel changes that prevent me from testing it correctly.

I’ll also add a GSG.getShaderGenerator() method - thanks for pointing out that omission.

Still, you can set the “dump-generated-shaders” variable to print them out.

You can use the NodePath interfaces to get the texture stages - if not, you can always get the lowlevel TextureAttrib to fetch them, just like the ShaderGenerator does.

Thanks for looking into this problem!

Thanks, dump-generated-shaders works perfectly!

I think I know why I got confused about my code. I think my projected texture is being added first, due to being higher up the scenegraph. I assume that when the render state is created, Panda walks the hierarchy and accumulates the stages it finds and then sorts based on priority?

I was assuming my TexGen-fed stage was lower in the hierarchy but I think it’s actually stage #1 instead of stage #2, as I originally assumed. If so, the rest of my post is correct. If not, then my assumptions are wrong and so is the patch. :slight_smile:

Found this post by David, sounds pretty concise: TextureStages/TextureAttrib stacking on node chain.

Update: After some testing, I find that yes, this is correct. The two stage orders I list below are indeed correct, as the default stage is actually rendered last because it walks the hierarchy in a reverse order. I went and modified the sort order of the stages so that the TexGenAttrib’d stage was first and last, and the shader changed the texcoord used by the world position matrix according. The only issue that I find is that it continuously generates the shader per frame? I don’t know why it’s doing this.

Anyway, here is my original post:

I’ve tried implementing a register mapping solution. A new vector list is created of texcoords that are assigned during the pass-through phase. This list is consulted when generating the in/out texcoords that are passed from vertex to fragment shader. Basically, a register mapping is added to this list if it’s merely going to be passed from the vertex shader to the fragment shader unchanged. (Which is what happens with anything that isn’t a TexGenAttrib-assigned stage.) The mapping allows for gaps in texcoord assignments.

For example, here’s how my texture stages look on my Panda node:

stage 1 -> MWorldPosition assigned with a TexMatrixAttrib (RGB colormap, ts.MModulate)
stage 2 -> Plain-Jane UV-mapped texture (RGB colormap, ts.MModulate)

It is being lit by a directional light and one ambient light. It uses no other feature.

And here is the shader which that renderstate generates (in/out order rearranged to clearly reflect what is incoming and outgoing for each stage):

//Cg
void vshader(
         in float4 vtx_texcoord0 : TEXCOORD0,
         in float4 vtx_normal : TEXCOORD1,
         uniform float4x4 trans_model_to_world,
         uniform float4x4 trans_model_to_view,
         uniform float4x4 tpose_view_to_model,
         uniform float4x4 mat_modelproj
         float4 vtx_position : POSITION,
         out float4 l_position : POSITION,
         out float4 l_texcoord0 : TEXCOORD0,
         out float4 l_world_position : TEXCOORD1,
         out float4 l_eye_position : TEXCOORD2,
         out float4 l_eye_normal : TEXCOORD3,
) {
         l_position = mul(mat_modelproj, vtx_position);
         l_world_position = mul(trans_model_to_world, vtx_position);
         l_eye_position = mul(trans_model_to_view, vtx_position);
         l_eye_normal.xyz = mul((float3x3)tpose_view_to_model, vtx_normal.xyz);
         l_eye_normal.w = 0;
         l_texcoord0 = vtx_texcoord0;
}

void fshader(
         in float4 l_texcoord1 : TEXCOORD0,
         in float4 l_world_position : TEXCOORD1,
         in float4 l_eye_position : TEXCOORD2,
         in float4 l_eye_normal : TEXCOORD3,
         uniform sampler2D tex_0,
         uniform float4x4 texmat_0,
         uniform sampler2D tex_1,
         uniform float4 alight_alight0,
         uniform float4x4 dlight_dlight0_rel_view,
         uniform float4x4 attr_material,
         uniform float4 attr_color,
         uniform float4 attr_colorscale
         out float4 o_color : COLOR0,
) {
         float4 result;
         float4 l_texcoord0 = l_world_position;
         l_texcoord0 = mul(texmat_0, l_texcoord0);
         l_texcoord0.xyz /= l_texcoord0.w;
         // Fetch all textures.
         float4 tex0 = tex2D(tex_0, l_texcoord0.xy);
         float4 tex1 = tex2D(tex_1, l_texcoord1.xy);
         // Correct the surface normal for interpolation effects
         l_eye_normal.xyz = normalize(l_eye_normal.xyz);
         // Begin view-space light calculations
         float ldist,lattenv,langle;
         float4 lcolor,lspec,lvec,lpoint,latten,ldir,leye,lhalf;
         float4 tot_ambient = float4(0,0,0,0);
         float4 tot_diffuse = float4(0,0,0,0);
         // Ambient Light 0
         lcolor = alight_alight0;
         tot_ambient += lcolor;
         // Directional Light 0
         lcolor = dlight_dlight0_rel_view[0];
         lspec  = dlight_dlight0_rel_view[1];
         lvec   = dlight_dlight0_rel_view[2];
         lcolor *= saturate(dot(l_eye_normal.xyz, lvec.xyz));
         tot_diffuse += lcolor;
         // Begin view-space light summation
         result = float4(0,0,0,0);
         result += tot_ambient * attr_material[0];
         result += tot_diffuse * attr_material[1];
         result = saturate(result);
         // End view-space light calculations
         result *= tex0;
         result *= tex1;
         result *= attr_colorscale;
         o_color = result * 1.000001;
}

I’m presenting a patch that includes my changes just so you can see what is going on. Note that this isn’t really what I consider commit-worthy code. (Not that I have write access to CVS, anyway!) It generates compiler warnings and is a pretty ugly hack IMHO. I think parts of the shader generator should be re-factored to keep track of what is used and where. I don’t think that just building a list of the registers in use is not enough – This is going to obfuscate the entire shader generator system, but I did it this way so I had to make the minimal changes to the current logic.

Obviously, this code is not very well tested, just with my (very narrow) test case. I haven’t tried exercising all of the render state functions and see how the output of the shader behaves.

I am presenting to help give you an idea of how I think this should be solved, by first showing the quick-fix hacky method and then maybe segueing into better methods. :slight_smile: The concept of the shader generator isn’t complicated but there’s a lot interleaved within it.

Here’s the patch:

Index: shaderGenerator.cxx
===================================================================
RCS file: /cvsroot/panda3d/panda/src/pgraphnodes/shaderGenerator.cxx,v
retrieving revision 1.20
diff -u -r1.20 shaderGenerator.cxx
--- shaderGenerator.cxx	15 Jul 2009 15:05:52 -0000	1.20
+++ shaderGenerator.cxx	6 Aug 2009 16:54:35 -0000
@@ -571,6 +571,7 @@
   pvector<char *> texcoord_vreg;
   pvector<char *> texcoord_freg;
   pvector<char *> tslightvec_freg;
+  pvector<int> texcoord_fmap;
   char *world_position_freg = 0;
   char *world_normal_freg = 0;
   char *eye_position_freg = 0;
@@ -590,12 +591,17 @@
   text << "void vshader(\n";
   const TextureAttrib *texture = DCAST(TextureAttrib, rs->get_attrib_def(TextureAttrib::get_class_slot()));
   const TexGenAttrib *tex_gen = DCAST(TexGenAttrib, rs->get_attrib_def(TexGenAttrib::get_class_slot()));
+  // Generate texcoord mapping based on what kind of inputs we need
   for (int i=0; i<_num_textures; i++) {
     if (!tex_gen->has_stage(texture->get_on_stage(i))) {
+      // Standard texture coords, just pass through to fshader
       texcoord_vreg.push_back(alloc_vreg());
       texcoord_freg.push_back(alloc_freg());
-      text << "\t in float4 vtx_texcoord" << i << " : " << texcoord_vreg[i] << ",\n";
-      text << "\t out float4 l_texcoord" << i << " : " << texcoord_freg[i] << ",\n";
+      // Map the assigned regs, so we can reference it later for this stage
+		  texcoord_fmap.push_back(i) ;
+      int currtex = texcoord_fmap.size() - 1 ;
+      text << "\t in float4 vtx_texcoord" << currtex << " : " << texcoord_vreg[currtex] << ",\n";
+      text << "\t out float4 l_texcoord" << currtex << " : " << texcoord_freg[currtex] << ",\n";
     }
   }
   if (_vertex_colors) {
@@ -689,7 +695,15 @@
   }
   for (int i=0; i<_num_textures; i++) {
     if (!tex_gen->has_stage(texture->get_on_stage(i))) {
-      text << "\t l_texcoord" << i << " = vtx_texcoord" << i << ";\n";
+      // look for whatever texcoord we've been mapped to for this stage
+      for (int map=0; map<texcoord_fmap.size(); map++)
+      {
+        if (texcoord_fmap[map] == i)
+        {
+          text << "\t l_texcoord" << map << " = vtx_texcoord" << map << ";\n";
+          break ;
+        }
+      }
     }
   }
   if (_vertex_colors) {
@@ -743,7 +757,12 @@
     nassertr(tex != NULL, NULL);
     text << "\t uniform sampler" << texture_type_as_string(tex->get_texture_type()) << " tex_" << i << ",\n";
     if (!tex_gen->has_stage(stage)) {
-      text << "\t in float4 l_texcoord" << i << " : " << texcoord_freg[i] << ",\n";
+      // look for whatever texcoord we've been mapped to for this stage
+      for (int map=0; map<texcoord_fmap.size(); map++)
+      {
+        if (texcoord_fmap[map] == i)
+          text << "\t in float4 l_texcoord" << i << " : " << texcoord_freg[map] << ",\n";
+      }
     }
     if (tex_matrix->has_stage(stage)) {
       text << "\t uniform float4x4 texmat_" << i << ",\n";
@@ -872,7 +891,8 @@
   }
   for (int i=0; i<_num_textures; i++) {
     if (i != _map_index_height) {
-      Texture *tex = texture->get_on_texture(texture->get_on_stage(i));
+      TextureStage *stage = texture->get_on_stage(i);
+      Texture *tex = texture->get_on_texture(stage);
       nassertr(tex != NULL, NULL);
       // Parallax mapping pushes the texture coordinates of the other textures away from the camera.
       // The normal map coordinates aren't pushed (since that would give inconsistent behaviour when
@@ -880,8 +900,20 @@
       if (_map_index_height >= 0 && i != _map_index_normal) {
         text << "\t l_texcoord" << i << ".xyz += parallax_offset;\n";
       }
+      int texcoordindex = i ; // by default, map to current texture number
+      // if we are a texgen stage then look for whatever texcoord we've been mapped to for this stage
+      if (tex_gen != NULL && tex_gen->has_stage(stage)) {
+        for (int map=0; map<texcoord_fmap.size(); map++)
+        {
+          if (texcoord_fmap[map] == i)
+          {
+            texcoordindex = map ;
+            break ;
+          }
+        }
+      }
       text << "\t float4 tex" << i << " = tex" << texture_type_as_string(tex->get_texture_type());
-      text << "(tex_" << i << ", l_texcoord" << i << ".";
+      text << "(tex_" << i << ", l_texcoord" << texcoordindex << ".";
       switch(tex->get_texture_type()) {
         case Texture::TT_cube_map:
         case Texture::TT_3d_texture: text << "xyz"; break;
@@ -1270,6 +1302,8 @@
   }
   text << "}\n";
 
+  //pgraph_cat.error() << text.str() << "\n" ;
+
   // Insert the shader into the shader attrib.
   CPT(RenderAttrib) shattr = create_shader_attrib(text.str());
   if (_subsume_alpha_test) {

Thanks for the patch. I’m not so sure though since the output texcoord names in the vshader do not match the input texcoord names in the fshader. Although the semantics match, I’m not sure if every Cg compiler chews this correctly.

I do agree that the shader generator should be rewritten so that this mess is not necessary.

I’ve hunted around but cannot find this out for conclusive. The only mention I can find is that the samplers are not allowed to access texcoords outside of their associated stage in the basic profile. But I just started writing shaders when I got into Panda, so my experience is limited.

Like I said, this is just a patch to get it working as-is, but it definitely needs a redesign. I did find a slight bug in my patch, where it isn’t properly copying over all of the texcoord associations. (I couldn’t get normal maps to show up properly in testing.)

I’ve updated my post with the patch to include that fix.

I don’t think it’s worth it to make the current code too complex - we can just remove the ‘if’ that tests for ‘!tex_gen->has_stage(texture->get_on_stage(i))’. I bet it won’t impact much on performance, and a smart compiler would probably get it out for us.

Filed as bug: https://bugs.launchpad.net/panda3d/+bug/424637

Just in case for future searches!

Oh, I entirely forgot about this issue. Just checked in a fix to CVS.