3.2 Arithmetic

The standard arithmetic operators “+”, “-”, “*”, and “/” are overloaded to support the natural arithmetic operations on each supported Sh type. For most types these operators operate componentwise, and support scalar promotion by replication on both arguments. However, for quaternions, matrices, and complex numbers, the multiplication and division operators are more specialized, and these types also have special scalar promotion rules.

The modulo operator “%” is also supported, with a generalized meaning suitable for floating-point numbers:

a% b  =  a/b -  |_ a/b _| .
Basically, the modulo operation computes a floating-point remainder. This operation is not defined on quaternions, complex numbers, or matrices. It always operates component wise. Scalar promotion via replication is supported on both arguments. The modulo operation is useful in several contexts in shaders, for instance to compute sawtooth stripe functions or to wrap coordinates.

Any numerical type in Sh may be prefixed with a “-”, indicating a negation. At this point, all types in Sh compute negation the same way: componentwise. It should be noted that componentwise negation is free or very inexpensive on common GPUs.





Operation r <-- a,b,c,...Description






+  N <-- N,N Addition



/ E  +  N <-- 1,N Addition to replicated scalar



/ E  +  N <-- N,1 Addition of replicated scalar



-  N <-- N,N Subtraction



/ E  -  N <-- 1,N Subtraction from replicated scalar



/ E  -  N <-- N,1 Subtraction of replicated scalar



-  N <-- N Negation



*  N <-- N,N Multiplication



*  N <-- 1,N Scalar multiplication



*  N <-- N,1 Scalar multiplication



/  N <-- N,N Division



/  N <-- N,1 Division by a scalar



/ E  /  N <-- 1,N Scaled reciprocal



/ E  %  N <-- N,N Modulo
ri <-- ai/bi-  |_ ai/bi _|



/ E  %  N <-- N,1 Modulo a replicated scalar
ri <-- ai/b-  |_ ai/b _|



/ E  %  N <-- 1,N Replicated scalar modulo
r <--  a/b -  |_ a/b  _| 
 i      i      i



 


Table 3.2: Arithmetic operators

Modifying forms of the “+”, “-”, “*”, “/” and “%” operators are also defined. For example, the expression x *= 5.0 multiplies all components of x by five, storing the result in x. This example also uses scalar promotion, which is supported on the right for modifying operators. For non-commutative operations, such as matrix multiplication, the modifying forms use right multiplication, so a *= b is equivalent to a = a * b.





Operation r <-- a,b,c,...Description






++  N <-- (N) Increment



--  N <-- (N) decrement



+=  N <-- (N),NModifying addition



+=  N <-- (N),1 Modifying addition of scalar



-=  N <-- (N),NModifying subtraction



-=  N <-- (N),1 Modifying subtraction of scalar



*=  N <-- (N),NModifying multiplication



*=  N <-- (N),1 Modifying multiplication by scalar



/=  N <-- (N),NModifying division



/=  N <-- (N),1 Modifying division by scalar



/ E  %=  N <-- (N),NModifying modulo



/ E  %=  N <-- (N),1 Modifying modulo by scalar



 


Table 3.3: Modifying arithmetic operators

Matrices use different definitions of multiplication and division. As described in Section 3.8, / E  the “*” operator on matrices is overloaded to mean matrix multiplication, not componentwise multiplication, and “/” is defined to be multiplication by the matrix inverse of the second argument.

All arithmetic operations return the same type as their arguments, if the arguments are the same type, with the exceptions that subtracting two ShPoints yields an ShVector, and similarily adding an ShVector to an ShPoint returns an ShPoint. An ShAttrib can be combined with any other type while retaining that type, so for instance an ShAttrib can be added to an ShPoint and the result will be a point.

In general, Sh is permissive and its type propagation rules are designed to be easy to remember and trace, rather than always being geometrically meaningful. For instance, addition of ShPoint objects is permitted, although unless the addition is part of an affine blend (with weights that are a partition of unity) addition of points is geometrically meaningless. However, attempting to disallow these kinds of geometrically invalid operations would burden the programmer, make certain common operations (such as affine combination) hard to express, and complicate the language. Hence we have chosen not to enforce these rules strictly.

Even though we don’t strictly enforce geometric rules at the level of C++ types, we are considering adding a capability to Sh that will analyze the internal representation of a program and provide warnings about geometrically improper operations.

If we do not enforce geometric consistency, you may wonder why we bother defining all these semantic types. One major reason is attribute binding. When shaders are written, input and output attributes need to be assigned to concrete backend attribute slots somehow. By providing types such as ShNormal we can allow the backend to match Sh attributes to backend slots semantically.

Secondly, the semantic types also provide implicit documentation. They make the code easier to read than shader code written in languages without semantic types, as the intent of a variable can be inferred from its type. Furthermore, having multiple semantic types permits programs performing introspection (for example, the shrike shader browser program provided with Sh, or a debugger) to present the user with a better user interface. For example, shrike provides the user with a color widget to change uniform color parameters, while the user interface for more general attributes is a slider or input box.

Sh allows scalars to be promoted to tuples in the basic arithmetic operations. Generally this is done by replication of the scalar value, although the rules are more specialized for matrices. Tables 3.4, 3.2 and 3.3 indicate when scalar operands are permitted by giving forms of the operators or functions where one of the inputs has one element only. Automatic promotion of tuples of other sizes is not supported; a cast must be used (see Section 3.11).

In addition to the operators, Sh provides several library functions for performing generic arithmetic. The sum function adds all the elements of a tuple, the prod function multiplies them. The mad function performs a multiply-accumulate operation. The reciprocal rcp function computes 1/x, the reciprocal square root funcion rsqrt function computes 1/ V~ --
  x, the square root sqrt function computes  V~ -
 x, and the cube root function cbrt function computes  V~ --
 3x. Note that mad, rcp, rsqrt, sqrt and cbrt all operate componentwise (unlike their GPU assembly counterparts).

All arithmetic operators available in Sh are listed in Tables 3.2 and 3.3. Functions for general-purpose arithmetic are summarized in Table 3.4.





Operation r <-- a,b,c,... Description






/ E  sum  1 <-- N Sum of components
     sum 
r <--    ai
     i



/ E  prod  1 <-- N Product of components
     prod 
r <--    ai
     i



mad  N <-- N,N,NMultiply and add
r <--  a b + c
 i    i i   i



mad  N <-- 1,N,N Multiply and add
ri <-- abi + ci



mad  N <-- N,1,N Multiply and add
ri <-- aib+  ci



/ E  rcp  N <-- N Reciprocal
ri <-- 1/ai



/ E  rsqrt  N <-- N Reciprocal square root
r <--  1/ V~ a-
 i        i



sqrt  N <-- N Square root
      V~ --
ri <--   ai



/ E  cbrt  N <-- N Cube root
      V~ --
ri <--  3ai



 


Table 3.4: Arithmetic functions


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.