5.6 Manipulators

Unfortunately, to satisfy the type rules for connecting program objects using the shader algebra operators, we need to define in advance compatible interfaces. This is annoying if all we want to do is rearrange the inputs and outputs of a shader. We would like to be able to specify simple rearrangements inline, within an expression, and have the system figure out the details. For example, if we just want to eliminate an output to specialize a shader, it would be nice if we didn’t have to fully specify the type of the output we are trying to eliminate.

Sh provides some shortcuts, similar to manipulators in the C++ iostream library, for manipulating the input and output channels of shaders. These manipulators are really functions that return instances of instances of special manipulator classes. Manipulator classes store information about the particular manipulation required. When connected to a program object in an expression, the appropriate glue program is automatically generated, using introspection over the programs it is connected with, to perform the desired manipulation. This mechanism automatically resolves type issues. / E  Manipulators can also be combined with each other using shader algebra operators to create more complex manipulations. However, manipulators are limited to interface adaptation. For more complex operations, ordinary program objects are necessary.

There are two kinds of manipulators: those that operate on a fixed number of channels (which we call atomic), and expandable manipulators that “grow” to consume all channels available. / E  Atomic (fixed) manipulators can be combined with each other and with program objects using both the “&” and “<<” operators to create more complex manipulations. / E  Expandable manipulators can only be combined with programs and other manipulators using the “<<” operator.

5.6.1 Fixed Manipulators

Fixed manipulators include the following:

shKeep(int n = 1):
Represents an operation that copies n channels of any type. The names are retained if they are set.
shKeep(const std::string & name):
Represents an operation that copies one channel (which must have the given name).
shLose(int n = 1):
Represents an operation that reads n channels and discards them (no outputs).
shLose(const std::string & name):
Represents an operation that reads one channel and discards it (no outputs). The input is checked against the name if it is provided as a sanity check.
shDup(int n = 2, const std::string & name = ""):
Represents an operation that duplicates one channel andn times. An optional name can be given that is checked against the channel. However, the outputs are unnamed.

Combinations of the manipulator objects returned by shKeep, shLose, and shDup with “&” can be used to describe mappings that retain and optionally create duplicates of an arbitrary subset of outputs.

Sh also provides nibble versions of this functionality, described in Section 5.5. To distinguish the two, the nibbles use a lower-case naming convention like that of the standard library: keep, lose, and dup. You should use the nibble versions if you want type checking.

5.6.2 Expandable Manipulators

Expandable manipulators include the following:

shExtract(T i):
Moves the referenced channel to the beginning of the attribute list, rearranging the other channels to close the gap.
shInsert(T i):
/  E  Moves the first channel to the referenced channel, rearranging the other channels as necessary.
shDrop(T i):
Discards the referenced channel, and rearranges the other channels to close the gap.
shSwizzle(T i0, T i1, ...):
Performs a swizzle of the given indices. The channels are rearranged into the order given by the arguments. Note that duplication and deletion of channels is also possible. This manipulator accepts between 1 and 10 indices.
shRange(T i0)(T i1, T i2):
Takes an arbitrary sequence of (T i) (to identify a single channel) or (T i1, T i2) (to identify a channel range) postfixes. This manipulator is an alternative form for specifying a swizzle.

These manipulators all need to identify the channels they act on.

To identify channels by position an integer is used for T. Negative numbers may be used to specify the position of a channel counted from the end of the attribute list, with -1 identifying the last channel. Channels can also be identified by name, using const char * for T.

Note that names are not automatically assigned to Sh variables, since C++ has no standard way to find out the names of its own variables. A name method is therefore provided on most Sh types to provide string names for such identification purposes (other string metadata can also be attached for introspection purposes). See Section 2.7.

The shInsert manipulator is useful when combined with currying, described later, to replace a named attribute with a parameter, or when you just want to refer to inputs by name rather than position. The shExtract manipulator is handy if you want to reference the outputs of a program by name rather than position. The shSwizzle (not to be confused with the swizzle operator on tuples) and shRange manipulators are generally useful when “adapting” the interface of some program objects to others, or to the order in which data is presented.

These manipulators cannot handle all possible cases. In particular, types are retained, and sometimes extra computation (such as normalization of vectors) is required. Manipulators are just a convenience; more complex adaptation of the input and output of shaders, including type casts and any additional computation required, can be accomplished by using either nibbles or defining suitable “glue” programs.

All manipulators can be attached (using the << operator) to either the outputs (on the left-hand side) or the inputs (on the right-hand side) of a program. Note that standard C++ precedence for << is left-to-right, and hence parentheses may be needed to achieve the desired effect.


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.