[SOLVED] Texture assigned to wrong Cg TEXUNIT on first frame

My application heavily relies on the runtime creation of Node geometry and attachment of these Nodes to the scene graph. Each node has three textures assigned to it, as well as a multitexturing Cg shader that is assigned to the common parent Node.

I’m encountering a problem where for the first frame that each Node is attached to the scene graph, the textures seem to be assigned to texture units in a random order. After the first frame, the textures are assigned to the proper texture units and everything looks as expected.

I’m aware of the proper use of TextureStages and setting the correct sort order so that Panda assigns textures to the correct Cg shader inputs in the right order. I’ve also tried using the set_shader_input(“sampler_name”, texturePointer) method of passing textures into my shader, yet the first frame of rendering still seems to assign textures to texture units randomly!

Here’s the gist of my CPU-side code:

NodePath terrainParentNP = /*Terrain parent.*/;
terrainParentNP.set_shader(/*Terrain shader.*/);
terrainParentNP.clear_texture();

NodePath terrainNP = /*Terrain geometry.*/;

PT(Texture) dayTexture = /*Day texture.*/;
PT(Texture) nightTexture = /*Night texture.*/;
PT(Texture) specMaskTexture = /*Specular mask.*/;

terrainNP.set_shader_input("day_texture", dayTexture);
terrainNP.set_shader_input("night_texture", nightTexture);
terrainNP.set_shader_input("mask_texture", specMaskTexture);

terrainNP.reparent_to(terrainParentNP);

Here’s a snippet of my Cg fragment shader:

void fshader(	
	in float4 l_texcoord0 : TEXCOORD0,
	/* ... other shader varyings here ... */
	uniform sampler2D day_texture : TEXUNIT0,
	uniform sampler2D night_texture : TEXUNIT1,
	uniform sampler2D mask_texture : TEXUNIT2,
	/* ... other shader uniforms here ... */
	out float4 o_color : COLOR0)
{
	// Even if I simply output the day_texture color in my shader,
	// I randomly get the night texture or specular mask for the first
	// frame, causing a very noticeable flicker.
	o_color = tex2D(day_texture, l_texcoord0.xy);
	return;
}

Any ideas on what is causing this, or how to work around it?

Thanks,
Matt

Could you put together a small test case that I can run to reproduce the issue, so that I can investigate it further? Thanks!

rdb, I’ll try to put together a small compilable test case if I find time soon. Thanks for the reply.

I discovered some new information but still haven’t fixed the problem.

If I remove the Cg semantics (e.g. TEXUNIT0) from my fragment shader’s sampler2D uniforms, my sample fragment shader as written in my previous post works fine without flicker on the first frame. However, if I modify my fragment shader to actually use all three textures, the bug returns.

Here’s a modified version of my fragment shader that uses all three texture samplers without semantics and still demonstrates the bug.

void fshader(	
	in float4 l_texcoord0 : TEXCOORD0,
	/* ... other shader varyings here ... */
	uniform sampler2D day_texture,
	uniform sampler2D night_texture,
	uniform sampler2D mask_texture,
	/* ... other shader uniforms here ... */
	out float4 o_color : COLOR0)
{
	// This draws the day texture where there is land and the
	// night texture where there is water.  It's nonsensical,
	// but it demonstrates the bug.
	float4 day = tex2D(day_texture, l_texcoord0.xy);
	float4 night = tex2D(night_texture, l_texcoord0.xy);
	float mask = tex2D(mask_texture, l_texcoord0.xy).a;
	o_color = mask * day + (1.0f - mask) * night;
	return;
}

If you don’t specify semantics, Cg will assign semantics by itself. If an input is unused, the compiler always optimises it out and it is treated as if it didn’t exist in the first place, so it could be that the semantics are assigned differently in that case.

I’d have to see it happening for myself before I can make any guesses about what’s going on exactly, though. I’ll have some time to look into it tomorrow.

Just to rule out something; can you make sure that the node in question only has shader inputs applied and no TextureAttrib (so not at a parent node either)?

In response to your question about TextureAttrib, I queried the Node’s net render state, and there seems to be none applied.

terrainNP.reparent_to(terrainParentNP);
CPT(TextureAttrib) texAttrib = static_cast<const TextureAttrib*>
    (terrainNP.get_net_state()->get_attrib(TextureAttrib::get_class_type()));
/*At this point, texAttrib == NULL*/

Matt

For the record, I’ve been able to reproduce it with this Python code:

from panda3d.core import *
from direct.directbase import DirectStart
import time

shader_cg = """//Cg
void vshader(float4 vtx_position : POSITION,
             float2 vtx_texcoord : TEXCOORD0,
             uniform mat4 mat_modelproj,
             out float4 l_position : HPOS,
             out float2 l_texcoord : TEXCOORD0) {
  l_position = mul(mat_modelproj, vtx_position);
  l_texcoord = vtx_texcoord;
}

void fshader(float2 l_texcoord : TEXCOORD0,
             uniform sampler2D tree : TEXUNIT0,
             uniform sampler2D ground : TEXUNIT1,
             out float4 o_color : COLOR) {

  o_color = tex2D(ground, l_texcoord);
  o_color = tex2D(tree, l_texcoord);
  o_color.a = 1.0f;
}
"""

cm = CardMaker("card")
card = render2d.attach_new_node(cm.generate())

tex_tree = loader.loadTexture("maps/envir-treetrunk.jpg")
tex_ground = loader.loadTexture("maps/envir-ground.jpg")

card.set_shader(Shader.make(shader_cg))
card.set_shader_input("tree", tex_tree)
card.set_shader_input("ground", tex_ground)

print "== Frame 0 =="
base.graphicsEngine.renderFrame()
for i in range(2):
    print "== Frame %d ==" % (i + 1)
    base.graphicsEngine.renderFrame()
    time.sleep(2)

I’ll let you know of anything I find out.

OK, I’ve tracked it down.

I found out that prepare_texture, which prepares the texture for being sent to the GPU in the first frame, calls glBindTexture when it’s done. At that point, glActiveTexture hasn’t been called yet by the shader context, so it binds to texunit 0. This means that the last texture to be bound is also bound to texunit 0, overwriting the texture that should actually be bound to texunit 0.
I’ve simply moved glActiveTexture before the preparation code and now it works.

Long story short, I’ve checked in a fix and picked it up for the upcoming 1.8.1 release. Thanks a lot for reporting the issue!

Thank you so much for the quick fix! We’ve mirrored the fix locally, and the bug is now gone.

I’m looking forward to 1.8.1!

Matt