5.4 Algebra

As shaders and stream program objects become larger and more complex the ability to reuse and encapsulate code and data becomes more important. The Sh toolkit, due to its close integration with C++, can support many standard forms of modularity such as functions, overloaded operators, classes, namespaces, and separate compilation.

However, additional forms of modularity are possible. A dataflow model, for instance, is very natural for both shaders and stream programs. In a dataflow language, modules are connected by pipes and data flows from one end of the network to another. We use the term dataflow loosely to mean any form of modularity that supports this network structure; both data-driven and event-driven, synchronous and asynchronous dataflow models have been developed. Visual dataflow languages are often used for end-user programming in modelling, image processing, signal processing, numerical, and animation applications, such as Houdini, Maya, LabView, and Khoros. Visual dataflow languages, in turn, are related to functional languages, where higher-order functions provide useful forms of modularity not supported directly by C++, such as currying.

An algebra consists of a set of operators and a set of objects which is closed under application of those operators. To support dataflow and functional forms of modularity, Sh supports an algebra over program objects [?]. Using the operators in this algebra, we can manipulate and specialize program objects without having to modify (or even have access to) the source code of the original shaders.

It should be emphasized that both operators do not simply build a multipass network. Shader algebra operations are compiled, not interpreted. They actually operate on the internal representation of program objects to build a completely new program object, which is ultimately run through the full suite of optimizations and virtualizations supported by the Sh backend. The resulting implementations are as efficient as if the programs had been built from scratch or with the use of library functions. Even if a multipass implementation is eventually executed after virtualization, the components of the multipass implementation need not have any particular relationship to the components used in a shader algebra expression.

Two operators are defined: connection, which is defined as functional composition or application, and combination, which is equivalent in the case of Sh to concatenation of source code. These operators are defined over shader objects, in which case they create new shader objects. These operators in effect create a functional language, in which program objects are treated as functions that take an ordered sequence of n inputs and map them to an ordered sequence of m outputs.

5.4.1 Connection

Suppose we have a shader object q1 with n inputs and k outputs and another shader object p1 with k inputs and m outputs. The connection operator creates a new shader object with n inputs and m outputs by taking the outputs of q1 and feeding them in the same order to the inputs of p1. In other words, it performs functional composition.

We denote this operator in Sh using the “<<” operator, with inputs on the right and outputs on the left. For instance, the k outputs of q1 can connected to the k inputs of p1 using “p1 << q1”.

The outputs of q1 must match the inputs of p1 in number, size, and type (both storage and semantic). These are checked dynamically, at C++ runtime.

This operator can also be used to apply a program to tuple or stream data, and convert an attribute input to a parameter input. An inverse “>>” is also supported to convert parameters to inputs. This is discussed in more detail in Section 5.8.

5.4.2 Combination

Suppose we are given two shader objects p2 and q2. Let p2 have n inputs and m outputs, and let q2 have k inputs and l outputs. We define the combination of p2 and q2 to have n + k inputs and m + l outputs, with the inputs and outputs of p2 appearing first, followed by the inputs and outputs of q2. The computations of p2 and q2 are both performed, with the local variables of each in different scopes.

We denote this operator in Sh using “&”, and so the combination of p2 and q2 can be denoted with “p2 & q2”. Note that “&” binds more loosely than “<<”.

Because of the way Sh is defined, the combination operator is in fact equivalent to the concatenation of the source code of the input shaders, using two separate scopes. Such a concatenation would ensure that the inputs and outputs of p2 are declared before q2, and so would give the same result as defined above.

For vertex and fragment shaders, a special ShPosition semantic type is defined which is semantically equivalent to an ShPoint but binds to the special position input and output of these shader units on GPUs. / E  The last definition always dominates, so if a position is computed in two shaders that are combined, only the position in the second shader will be used, and the first position will be converted to a point. This is important when combining two shaders each of which have an ShPosition declaration.

The combination operator can also be used to combine channels of data into streams for use in the implementation of Sh’s stream processing model. This is discussed in more detail in Section 5.8.


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.