Subroutine Bug?

I’m encountering a bizarre behavior with the subroutine feature. Simply changing the order in which subroutines are defined in the shader source file screws up the value returned from one of them.

This pixel shader compiles and links with no errors, and I’ve meticulously combed through the state variables and bindings immediately before the draw call under both versions of the code, and I cannot identify any meaningful (or even meaningless) differences. I also know of nothing in GLSL or OpenGL 4.1 spec that speaks to the relevance of the order in which the subroutines are declared/defined in the source, as long as they are defined before use.

I have not tried this on Nvidia drivers, as I do not have a GL4 compatible nvidia card handy. If this isn’t a driver issue of some kind, I’m totally stumped.

My system/drivers:

Renderer Vendor ATI Technologies Inc.
Renderer Name ATI Radeon HD 5700 Series
Renderer Version 4.1.10362 Compatibility Profile Context
Shading Language Version 4.10
Renderer Type Installable client

Driver Version: 8.801.0.0
Driver Date: 11/25/2010

Here is the source for the pixel shader in question:


#version 410 core                                                                        
                                                                                        
// Pass 0, Deferred Pipeline, Pixel Shader                                               
//                                                                                       
// This shader computes material quantities into the G-buffer 
    
                                                                                        
in vec2 _UVW;		// Texture coordinate                                                  
in vec3 _V;	        // Eye-space surface position                                          
in vec3 _T;		// Eye-space tangent                                                   
in vec3 _B;		// Eye-space bitangent                                                 
in vec3 _N;		// Eye-space surface normal                                            
                                                                                      
                                                                                        
/// subroutine signatures (the function pointer type) (capitalized)                      
                                                                                        
subroutine vec3 _s_ShadingNormal       (vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N);       
subroutine vec3 _s_DiffuseAlbedo       (vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N);       
subroutine vec4 _s_SpecularAlbedoAndExp(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N);       
                                                                                        
/// the subroutine pointers themselves (leading underscore)                              
                                                                                        
subroutine uniform _s_ShadingNormal        _shadingNormal;                               
subroutine uniform _s_DiffuseAlbedo        _diffuseAlbedo;                               
subroutine uniform _s_SpecularAlbedoAndExp _specularAlbedoAndExp;                        
                         

                                          
uniform sampler2D tSpecular;      

/// Mystery!!
/// If I reverse the order of the following two subroutine
/// definitions, the specularMap function returns bogus 
/// values (a 1.0 in the w component)

// Bound to _specularAlbedoAndExp
subroutine(_s_SpecularAlbedoAndExp)
vec4 specularMap(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)
{ return vec4(texture2D(tSpecular, _UVW).xyz, 20.0); }
                                                                                        
// Unbound
subroutine(_s_DiffuseAlbedo)                                                              
vec3 defaultDiffuse(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)                             
{ return vec3(0.8); } 

// Bound to _shadingNormal
subroutine(_s_ShadingNormal)
vec3 phongNormal(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)
{ return N; }

// Unbound
subroutine(_s_ShadingNormal)
vec3 defaultNormal(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)
{ return N; }

// Unbound
subroutine(_s_SpecularAlbedoAndExp)
vec4 defaultSpecular(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)
{ return vec4(1.0, 0.985, 0.96, 30); }

uniform sampler2D tDiffuse;

// Bound to _diffuseAlbedo
subroutine(_s_DiffuseAlbedo)
vec3 diffuseMap(vec2 UVW, vec3 V, vec3 T, vec3 B, vec3 N)
{ return texture2D(tDiffuse, _UVW).xyz; }      
                                                            
void main()                                                                               
{                                                                                         
  gl_FragData[0] = vec4(_V, 1.0);										         // Position       
  gl_FragData[1] = vec4(_shadingNormal(_UVW, _V, _T, _B, _N), 1.0);    // Normal         
  gl_FragData[2] = vec4(_diffuseAlbedo (_UVW, _V, _T, _B, _N), 1.0);   // Diffuse Color  
  gl_FragData[3] = _specularAlbedoAndExp(_UVW, _V, _T, _B, _N);        // Specular Color 
}