1
\$\begingroup\$

I've been trying to develop shaders that would allow me to easily display text on screen using a font texture. I've previously written shaders that simply drew the text on screen and I passed the exact coordinates for the vertices and UV coordinates to the shaders. With my new shaders, I'm trying to pass just a single point for each letter, representing the bottom left corner each letter, as well as a single point for the UV coordinate of the letter, representing its location on the font texture. I then use a geometry shader to generate the triangles and UV coordinates necessary to draw the letters. Here is my code for the shaders:

Vertex Shader:

#version 330
layout(location = 0) in vec2 vertexPosition;
layout(location = 1) in vec2 vertexUV;
layout(location = 2) in float textSize;
uniform unsigned int textureX;
uniform unsigned int textureY;
out VertexData
{
    vec2 uv;
    float textSize;
} outputData;
void main()
{
    // Represents the bottom left corner
    gl_Position = vec4(vertexPosition, 0.0f, 1.0f);
    // Convert the UV coordinates from [0, textureX - 1] to [0, 1]
    outputData.uv = vertexUV / vec2(textureX, textureY);
    outputData.textSize = textSize;
}

Geometry Shader:

#version 330
layout(points) in;
layout(triangle_strip, max_vertices = 6) out;
uniform unsigned int textureX;
uniform unsigned int textureY;
in VertexData
{
    vec2 uv;
    float textSize;
} inputData[];
out FragmentData
{
    vec2 uv;
} outputData;
void main()
{
    float textureWidth = 1.0f / textureX;
    float textureHeight = 1.0f / textureY;
    // Calculate the vertices
    vec2 brCorner = vec2(gl_in[0].gl_Position) + vec2(inputData[0].textSize, 0.0f);
    vec2 tlCorner = vec2(gl_in[0].gl_Position) + vec2(0.0f, inputData[0].textSize);
    vec2 trCorner = vec2(gl_in[0].gl_Position) + vec2(inputData[0].textSize, inputData[0].textSize);
    vec2 uvBR = inputData[0].uv + vec2(textureWidth, 0.0f);
    vec2 uvTL = inputData[0].uv + vec2(0.0f, textureHeight);
    vec2 uvTR = inputData[0].uv + vec2(textureWidth, textureHeight);
    gl_Position = vec4(tlCorner, 0.0f, 1.0f);
    outputData.uv = uvTL;
    EmitVertex();
    gl_Position = gl_in[0].gl_Position;
    outputData.uv = inputData[0].uv;
    EmitVertex();
    gl_Position = vec4(trCorner, 0.0f, 1.0f);
    outputData.uv = uvTR;
    EmitVertex();
    EndPrimitive();
    gl_Position = gl_in[0].gl_Position;
    outputData.uv = inputData[0].uv;
    EmitVertex();
    gl_Position = vec4(brCorner, 0.0f, 1.0f);
    outputData.uv = uvBR;
    EmitVertex();
    gl_Position = vec4(trCorner, 0.0f, 1.0f);
    outputData.uv = uvTR;
    EmitVertex();
    EndPrimitive();
}

Fragment Shader:

#version 330 core
in FragData
{
    vec2 uv;
} inputData;
out vec4 color;
uniform sampler2D textureSampler;
void main()
{
    color = texture2D(textureSampler, inputData.uv);
}

When I call glGetUniformLocation for the textureSampler uniform in my fragment shader, I get a valid uniform location back. However, when I call glGetUniformLocation for textureX and textureY, glGetUniformLocation returns -1. When I searched for what could cause this, the main cause of this issue is usually the compiler optimizing the variable out as it's not used. I don't believe this to be true in my case, but I have no idea what could be causing glGetUniformLocation to return -1 for this.

Edit: After reading Andon M. Coleman's excellent answer on how active uniforms are determined, I discovered the issue. The name of the struct the geometry shader outputs and the name of the struct the fragment shader accepts are different-

Geometry Shader:

out FragmentData
{
    vec2 uv;
} outputData;

Fragment Shader:

in FragData
{
    vec2 uv;
} inputData;

After changing the structs to share the same name, I am able to get a valid uniform location back.

\$\endgroup\$
  • 1
    \$\begingroup\$ Have you double-checked the usual silly things, like: are you attaching the geometry shader to the program? Are there any GL errors/warnings coming back from the compile and link? Are you using KHR_debug to see errors/warnings from the driver? \$\endgroup\$ – Nathan Reed Jan 7 '14 at 5:05
  • \$\begingroup\$ You might consider using uniform buffer objects and glUniformBlockBinding. \$\endgroup\$ – Sean Middleditch Jan 7 '14 at 5:27
  • \$\begingroup\$ @NathanReed I've checked to make sure that I'm attaching the geometry shader and there's no errors/warnings from the compile and link. I've never heard of KHR_debug before, but I'll check it out and learn how to use it. \$\endgroup\$ – Shadow Jan 7 '14 at 5:50
  • \$\begingroup\$ Hopefully this is just an experiment for fun / to learn how geometry shaders work. You are not going to get a performance gain by doing this; generally geometry shaders hurt performance rather than improve it. In any case, having traced the path all instances of your textureX and textureY uniform take, the only scenario in which they are not linked to an active code path would be if you did not attach your geometry shader. The easiest way to see if this is the culprit, would be to add the uniforms to your fragment shader and try sampling your texture using vec2 (textureX, textureY) \$\endgroup\$ – Andon M. Coleman Jan 7 '14 at 7:03
  • 1
    \$\begingroup\$ @AndonM.Coleman Thanks a ton for your suggestion on adding the uniforms to the fragment shader! Having done that, I get a valid uniform location. I'm unclear as to why that worked however; even if my geometry shader was not attached to the program, I have the uniforms in my vertex shader as well. I'll also look into the performance of geometry shaders more as well, as I was unaware that they could have a severe negative effect. \$\endgroup\$ – Shadow Jan 7 '14 at 7:16
6
\$\begingroup\$

Since there was a little bit of confusion in the comments regarding what is required for a uniform to be active, I will try to explain it here.

The first thing you need to understand is that in OpenGL, GLSL programs are built in two phases:

  1. Compile
  2. Link

These are analogous to compiling and linking in a language like C, except some implementations of GLSL (e.g. NVIDIA) actually "compile" your shader in both phases. The most important thing that happens in the Compile phase is that general parse / syntax errors are caught, and inputs and outputs are determined.

After the compile phase determines the set of inputs and outputs for each stage of the GLSL program, the Link phase then assigns data locations to things like generic vertex attribute locations, fragment bindings, uniform locations, etc. But there is a catch, it only assigns locations to uniforms that actively contribute to pipeline output.


Below are two overly-simplified shaders that exemplify what happens when a Vertex Shader outputs something that is not used by any other stage to produce an output.

Hypothetical Vertex Shader (Active + Inactive Uniforms)

#version 330

uniform float UsedButNotActive;
uniform float Active;

in      vec4  ndc_vtx;

out     float UsedButNotOutput;
out     float Used;

void main (void)
{
  gl_Position = ndc_vtx;

  UsedButNotOutput = UsedButNotActive;
  Used             = Active;
}

Hypothetical Fragment Shader

#version 330

in  float UsedButNotOutput;
in  float Used;

out vec4  color;

void main (void)
{
  // Inactive, because this does not produce any output
  float Pointless = sqrt (UsedButNotOutput);

  // Used, and any variable that was used to compute it, is part of an active code path
  color = vec4 (Used);
}

Because the variable Used is the only variable in the fragment shader stage that contributes to an output, it is the only active code path. This means the uniform Active is part of an active code path and will be assigned a uniform location.

UsedButNotOuput, on the other hand, is used to compute something in the fragment shader, but that something is never output. This variable effectively dies inside the fragment shader. If you trace its path back to the vertex shader, you will see that UsedButNotActive was used to set this value, but does not belong to an active code path. Thus, that uniform is not active and is not assigned a uniform location.


Remember how I mentioned that some GLSL implementations compile your shader twice? Well, after all this active vs. inactive codepath determination, some lines and variables will effectively be stripped from the shader the second time it is compiled.

Hypothetical Link-Phase Vertex Shader (Active Uniforms)

#version 330

//uniform float UsedButNotActive; -- STRIPPED
uniform float Active;

in      vec4  ndc_vtx;

//out     float UsedButNotOutput; -- STRIPPED
out     float Used;

void main (void)
{
  gl_Position = ndc_vtx;

  //UsedButNotOutput = UsedButNotActive; -- STRIPPED
  Used   = Active;
}

Hypothetical Link-Phase Fragment Shader

#version 330

//in  float UsedButNotOutput; -- STRIPPED
in  float Used;

out vec4  color;

void main (void)
{
  //float Pointless = sqrt (UsedButNotOutput); -- STRIPPED

  // Used, and any variable that was used to compute it, is part of an active code path
  color = vec4 (Used);
}

It is worth mentioning that some of this behavior is altered by new OpenGL features such as Separate Shader Objects, which makes the concept of active vs. inactive uniforms almost meaningless / impossible to determine. Also, when transform feedback is used, it means that data does not have to be output from the fragment shader stage in order for a variable to be involved in an active codepath. However, this explanation should make it obvious why if you are missing the geometry shader stage of your GLSL program in your code, then the uniforms textureX and textureY do not belong to any active code paths.

Also note that any uniform used to calculate gl_Position is active simply because gl_Position is an important interpolated/used input for every shader stage following vertex. It is responsible for producing gl_FragCoord, which determines where the fragment is output in the fragment shader stage, for instance.

\$\endgroup\$
  • \$\begingroup\$ Thank you VERY much for that explanation! Although I partially understood how active uniforms were determined before, your post really clarified it. Also, thanks to your post, I took a better look at my shaders inputs & outputs and found why it wasn't working! Take a closer look at the names of the output from the geometry shader: out FragmentData and the input from the fragment shader: in FragData After renaming those two structs to the same name, I get a valid uniform location back. \$\endgroup\$ – Shadow Jan 8 '14 at 6:34
  • \$\begingroup\$ @Shadow: I had not even noticed the naming issues with your interface blocks. I feel silly now, as that is usually the first place I look when there is a GS issue. Regarding my earlier comment about GS performance, it is not usually a huge hit but generally if you are using a GS because you think that emitting 4-6 vertices from 1 point is going to improve vertex performance you will wind up with a small performance penalty in the end. Often people discover this when they implement omni-shadows with a GS, thinking that 1 draw call will magically be quicker than 6 to draw into a cube map. \$\endgroup\$ – Andon M. Coleman Jan 8 '14 at 17:34
  • \$\begingroup\$ Pretty much the only reason I want to use a geometry shader is because I've been experimenting with using voxels, and RAM is usually an issue for me, as I try to load as many voxels as I can. I originally used just a vertex shader and fragment shader for rendering, and stored each vertex, but that used up tons of RAM. I began using geometry shaders because of that, so that I could simply store a single point and a number corresponding to which side should be rendered from the point. \$\endgroup\$ – Shadow Jan 8 '14 at 18:52
  • 1
    \$\begingroup\$ Although the geometry shaders may cause a performance hit, the RAM I saved was well worth the hit. I believe my test project showed little to no fps drop from switching, but the RAM usage dropped from ~1GB to ~600MB. (Just as a side note, switching to using a geometry shader in my shader program was not the only optimization I made) \$\endgroup\$ – Shadow Jan 8 '14 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.