5.5 Nibbles

Connecting and combining existing programs using the algebra operators can result in redundant computation. The optimizer can only do so much, especially if redundant inputs or outputs are present or the same computation is expressed in different ways.

Fortunately, the “<<” operator, in conjunction with the optimizer in the Sh compiler (particularly dead code removal) and the definition of some simple “glue” programs, can be used to specialize program objects and eliminate redundant computation.

For instance, suppose we combine two program objects and the resulting program computes the same value twice (in two different ways, so we cannot discover this fact automatically). We can define a simple program that copies its inputs to its outputs except for one of the redundant results. This “discard” program can be connected to the output of the combined shader and the Sh dead code eliminator will remove the redundant computation.

Nibbles are functions that build and return small ShPrograms that support primitive but useful operations. These primitives can be used to glue together other program objects, or even to generate new programs from scratch. You can combine nibbles using the shader algebra operators to generate many useful programs. The shader algebra operators provide a functional language and the nibbles are the basic functions from which other functions can be built.

5.5.1 Interface Adaptation and Specialization

The supported interface manipulation nibbles include the following:

keep<T>(int n = 1):

Generates a program that copies n channels of type T from its input to its output. The names of the channels are retained if they are set.
keep<T>(const std::string & name):

Generates a program that passes through one input of type T with the given name on both input and output.
lose<T>(int n = 1):

Generates a program that reads n channels of type T from its input and discards them (no outputs).
lose<T>(const std::string & name):

Generates a program that reads one channel of type T from its input with the given name (this is really just a sanity check) and discards it.
dup<T>(int n=2, const std::string & name = ""):

Generates a program that reads one input channel of the given type T and creates n duplicates on its output. An optional name can be given that is checked against the input. However, the outputs are unnamed (since they would be ambiguous if they all had the same name).

Combinations of keep, lose, and dup with “&” can be used to describe mappings that retain or make copies of a subset of outputs.

Sh also provides manipulator versions of this functionality, described in Section 5.6. To distinguish the two, the nibbles use a lower-case naming convention like that of the standard library rather than the sh prefix convention used by other nibbles. You should use the manipulator versions if you want to avoid specifying type information. Also, manipulators are available for specifying general swizzles, extractions, and insertions.

5.5.2 Passthrough

The shOutputPass nibble function generates a passthrough program that copies the outputs of a given ShProgram and keeps all the names. This is like the keep nibble, but it uses a program as an argument and can generate a glue program with different type on different channels. The shInputPass function does the same thing for the inputs of a given ShProgram. This is useful in cases where vertex shader outputs need to be duplicated before being passed to the fragment shader.


ShProgram shOutputPass (
  const ShProgram & p
);
ShProgram shInputPass (
  const ShProgram & p
);

5.5.3 Texture Access

The shAccess nibble generates an ShProgram that takes a texture coordinate as input and performs a texture lookup. The return type of the lookup is the storage type of the texture. The shAccess nibble is overloaded on all built-in texture types, as shown below in Listing ??.

[
        caption=Access nibbles,
        label=listing:ref:access
]
template<typename T>
ShProgram shAccess (
    const ShBaseTexture1D<T> &tex,
    const std::string & output_name = "result"
    const std::string & input_name = "u"    // ShTexCoord1f
);
template<typename T>
ShProgram shAccess (
    const ShBaseTexture2D<T> &tex,
    const std::string & output_name = "result",
    const std::string & input_name = "u"    // ShTexCoord2f
);
template<typename T>
ShProgram shAccess (
    const ShBaseTextureRect<T> &tex,
    const std::string & output_name = "result",
    const std::string & input_name = "u"    // ShTexCoord2f
);
template<typename T>
ShProgram shAccess (
    const ShBaseTexture3D<T> &tex,
    const std::string & output_name = "result",
    const std::string & input_name = "u"    // ShTexCoord3f
);
template<typename T>
ShProgram shAccess (
    const ShBaseTextureCube<T> &tex,
    const std::string & output_name = "result",
    const std::string & input_name = "u"    // ShVector3f
);

5.5.4 Type and Size Conversion

The casting nibble shCast generates a program that casts an input of tuple type T to type T2. If the number of elements of T is less than the number of elements of T2, this nibble pads the result tuple with 0 components at the end. If the opposite is true, it truncates the result tuple. In other words, it has the same behaviour as the cast library function.


template<typename T, typename T2>
ShProgram shCast(
  const std::string & output_name = "result",
  const std::string & input_name = "x"
);

The fill-casting nibble shFillcast casts from tuple type T to type T2, with the same semantics as the fillcast library function. If the number of elements in T is less than the number of elements in T2, the generated program pads with repeated last component at end.


template<typename T, typename T2>
ShProgram shFillcast (
  const std::string & output_name = "result",
  const std::string & input_name = "x"
);

5.5.5 Transformations

The transformation nibble shTransform creates an ShProgram that transforms a variable of type T2 by a matrix of type ShMatrix<Rows, Cols, Binding, T>.


template<typename T2, int Rows, int Cols,
         ShBindingType Binding, typename T>
ShProgram shTransform (
  const ShMatrix<Rows, Cols, Binding, T> &M,
  const std::string & output_name = "result",
  const std::string & input_name = "x"
);

5.5.6 Basis Conversion

The basis conversion nibble function shChangeBasis generates a program that takes 3 vectors defining an orthonormal basis and projects the fourth vector onto them, returning the coordinates of the vector in the new basis.


ShProgram shChangeBasis (
    const std::string & output_name="result",
    const std::string & input_name0="v0",
    const std::string & input_name1="v1",
    const std::string & input_name2="v2"
);

5.5.7 Primitive Computations

There are also nibbles corresponding to / E  all unary library functions and operators. These nibbles all have one input and one output. By default the input is named "x" and the output is named "result" (although both can be configured). For example, nibbles for shAbs, shCos, shAcos, shSin, shAsin, shFrac, shSqrt, shNeg (negate), shNormalize, and shPos all exist. All the unary nibbles have similar interfaces. We only give one:


template<typename T> ShProgram shAbs (
  const std::string & output_name = "result",
  const std::string & input_name = "x"
);

Nibbles are also defined for / E  all of the binary library functions and operators. These nibbles all have two inputs and one output. By default the first input is named "x", the second input is named "y", and the output is named "result" (although all three can be configured). For example, shAdd, shSub, shMul, shDiv, shDot, shPow, shSlt, shSle, shSgt, shSge, shSeq, shSne, shFmod, shMin, and shMax are all defined. Note in particular the names of the boolean functions (set less-than, set less-than-or-equal, etc.). All the binary nibbles have similar interfaces. We only give one:


template<typename T> ShProgram shAdd (
  const std::string & output_name = "result",
  const std::string & input_name0 = "x",
  const std::string & input_name1 = "y"
);

Lastly, trinary functions also have corresponding nibbles, in particular shLerp and shCond . The shLerp has two type parameters, the type of the interpolation parameter and the type of the value to be interpolated. If only one type is given, these are assumed to be the same.


template<typename T1, typename T2 = T1> ShProgram shLerp (
  const std::string & output_name = "result",
  const std::string & input_name0 = "t",
  const std::string & input_name1 = "x",
  const std::string & input_name2 = "y"
);

shCond takes two type parameters, the type of the conditional and the type of the two values to choose from. Again, these are assumed to be the same by default.


template<typename T1, typename T2 = T1> ShProgram shCond (
  const std::string & output_name = "result",
  const std::string & input_name0 = "t",
  const std::string & input_name1 = "x",
  const std::string & input_name2 = "y"
);


Note: This manual is available as a bound book from AK Peters, including better formatting, in-depth examples, and about 200 pages not available on-line.