OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

The OpenGL Pipeline Newsletter - Volume 001

Table of Contents
Previous article: Superbuffers Working Group Update
Next article: Improved synchronization across GPUs and CPUs - No more glFinish!

New Texture Functions with Awkward Names to Avoid Ugly Artifacts

One problem with many of the extension specs that we face is that they are too often short on motivating examples.  Even when there are examples, they suffer from dreaded ASCII art.  With this newsletter, I can not only put in a few more examples, I can replace the dreaded ASCII art with the less dreaded programmer art.

Note, these example procedural shaders will alias.  And since they will alias, why not use an aliased source texture as well?

source texture
Figure 1 – Source texture – aliasing yellow and blue stripes!

So let’s start with a trivial shader: apply this texture to a quad.  The quad has texture coordinates

myTC that are passed in from the vertex shader.  myTC

coordinates are 0.0, 0.0 at the lower left corner and 1.0, 1.0 at the upper right corner.

// Fragment Shader 1 – simple texture
varying vec2 myTC;
uniform sampler2D myStripeMap;
void main(void)
{
    gl_FragData[0] = texture2D(myStripeMap, myTC);
}

The technical director asks for a shader that replaces the left side of the texture with lime green.  You write the shader (knowing better than to ask why) and add a new control,

mySlider.  When myTC.s is less than mySlider, the color is green.  When myTC.s is greater than or equal to mySlider

, the color is the source texture.

// Fragment Shader 2 – left green/right textured
varying vec2 myTC;
uniform sampler2D myStripeMap;
uniform float mySlider;

const vec4 green = vec4( 0.0, 1.0, 0.0, 1.0 );
void main(void)
{
    if (myTC.s < mySlider)
        gl_FragData[0] = green;
    else
        gl_FragData[0] = texture2D(myStripeMap, myTC);
}

A quick check with

mySlider

set to 0.5 and everything looks great!

textured quad
Figure 2 – The textured quad when mySlider = 0.5

You are about to ship the shader off to the technical director, but you try a value a bit larger than 0.5.  Where did the vertical gray stripe come from?

vertical stripe
Figure 3 – Sometimes there’s a vertical gray strip

The problem is that the texture fetch is inside varying control flow.  A mipmapped texture fetch or an anisotropic fetch will calculate an implicit derivative for lambda or the line of anisotropy.  Derivatives (explicit or implicit) inside of varying control flow are undefined!  Your graphics card happens to either get an answer that sometimes seems right when the texels are far from the conditional.  But it also seems to get them very wrong near the conditional, and you guess that the derivatives are very very large near the conditional.  The large derivatives near the conditional drive the texture fetches to the bottom of your mipmap pyramid.  That’s why you see the gray vertical stripe.

Note that undefined derivatives mean that different implementations can get very different answers.  In fact, you test your shader on an older system and find out that the older system happens to always give you the “right” answer!

You rewrite the shader to move the texture fetch outside of control flow and wish there was a better way.

// Fragment Shader 3 – with old texture functions
varying vec2 myTC;
uniform sampler2D myStripeMap;
uniform float mySlider;

const vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
void main(void)
{
    vec4 texel = texture2D(myStripeMap, myTC);
    if (myTC.s < mySlider)
        gl_FragData[0] = green;
    else
        gl_FragData[0] = texel;
}

correct picture
Figure 4 – the correct picture

With a new extension under development, you have another choice besides moving the texture fetch outside of control flow.

An extension proposed in the GLSL Working Group, ARB_shader_texture_lod, adds new built-in texture functions that allow the shader writer to explicitly supply the derivatives.  You can calculate the derivatives outside of control flow and fetch the texel inside of control flow.

// Fragment Shader 4 – with new texture functions
// NOT YET APPROVED AT THE TIME OF THIS WRITING!
#extension ARB_shader_texture_lod require
varying vec2 myTC;
uniform sampler2D myStripeMap;
uniform float mySlide;

const vec4 green = vec4(0.0, 1.0, 0.0, 1.0);
void main(void)
{
   vec2 dPdx = dFdx(myTC);
    vec2 dPdy = dFdy(myTC);
    if (myTC.s < mySlide)
        gl_FragData[0] = green;
    else
        gl_FragData[0] =
            texture2DGradARB(myStripeMap, myTC,
                             dPdx, dPdy);
}

Shader 3 and Shader 4 will both get the correct answers on all implementations, but the latter may be more efficient on some implementations.

In summary, existing texture functions may need to calculate implicit derivatives for mipmapped texture fetches or anisotropic texture fetches.  Derivatives inside of varying control flow are undefined.  New texture functions are introduced by ARB_shader_texture_lod with explicit derivative parameters.  This allows a shader writer to move the derivatives outside of varying control flow while keeping the texture fetch inside of control flow.

Bill Licea-Kane, ATI
GLSL Working Group Chair

 


About OpenGL