3.6. Flow ControlFlow control is very much like that in C++. The entry point into a shader is the function main. A program containing both vertex and fragment shaders has two functions named main, one for entering a vertex shader to process each vertex and one to enter a fragment shader to process each fragment. Before main is entered, any initializers for global variable declarations are executed. Looping can be done with for, while, and do-while, just as in C++. Variables can be declared in for and while statements, and their scope lasts until the end of their substatements. The keywords break and continue also exist and behave as in C. Selection can be done with if and if-else, just as in C++, with the exception that a variable cannot be declared in the if statement. Selection by means of the selection operator (?:) is also available, with the extra constraint that the second and third operands must have exactly the same type. The type of the expression provided to an if statement or a while statement, or to terminate a for statement, must be a scalar Boolean. As in C, the right-hand operand to logical and (&&) is not evaluated (or at least appears not to be evaluated) if the left-hand operand evaluates to false, and the right-hand operand to logical or (||) is not evaluated if the left-hand operand evaluates to true. Similarly, only one of the second or third operands in the selection operator (:?) will be evaluated. A logical exclusive or (^^) is also provided, for which both sides are always evaluated. A special branch, discard, can prevent a fragment from updating the frame buffer. When a fragment shader executes the discard keyword, the fragment being processed is marked to be discarded. An implementation might or might not continue executing the shader, but it is guaranteed that there is no effect on the frame buffer. A goto keyword or equivalent is not available, nor are labels. Switching with switch is also not provided. 3.6.1. FunctionsFunction calls operate much as in C++. Function names can be overloaded by parameter type but not solely by return type. Either a function definition (body) or declaration must be in scope before a function is called. Parameter types are always checked. This means an empty parameter list () in a function declaration is not ambiguous, as in C, but rather explicitly means that the function accepts no arguments. Also, parameters must have exact matches since no automatic promotions are done, so selection of overloaded functions is quite straightforward. Exiting from a function with return operates the same as in C++. Functions returning nonvoid types must return values whose type must exactly match the return type of the function. Functions may not be called recursively, either directly or indirectly. 3.6.2. Calling ConventionsThe OpenGL Shading Language uses call by value-return as its calling convention. The call by value part is familiar from C: Parameters qualified as input parameters are copied into the function and not passed as a reference. Because there are no pointers, a function need not worry about its parameters being aliases of some other memory. The return part of call by value-return means parameters qualified as output parameters are returned to the caller by being copied back from the called function to the caller when the function returns. To specify which parameters are copied when, prefix them with the qualifier keywords in, out, or inout. For something that is just copied into the function but not returned, use in. The in qualifier is also implied when no qualifiers are specified. To say a parameter is not to be copied in but is to be set and copied back on return, use the qualifier out. To say a parameter is copied both in and out, use the qualifier inout.
The const qualifier can also be applied to function parameters. Here, it does not mean the variable is a compile-time constant, but rather that the function is not allowed to write it. Note that an ordinary, nonqualified input-only parameter can be written to; it just won't be copied back to the caller. Hence, there is a distinction between a parameter qualified as const in and one qualified only as in (or with no qualifier). Of course, out and inout parameters cannot be declared as const. void ComputeCoord(in vec3 normal, // Parameter 'normal' is copied in, // can be written to, but will not be // copied back out. vec3 tangent, // Same behavior as if "in" was used. inout vec3 coord)// Copied in and copied back out. Or, vec3 ComputeCoord(const vec3 normal,// normal cannot be written to vec3 tangent, in vec3 coord) //the function will return the result The following are not legal: void ComputeCoord(const out vec3 normal, //not legal; can't write normal const inout vec3 tang, //not legal; can't write tang in out vec3 coord) //not legal; use inout Structures and arrays can also be passed as arguments to a function. Keep in mind, though, that these data types are passed by value and there are no references, so it is possible to cause some large copies to occur at function call time. Array parameters must be declared with their size, and only arrays of matching type and size can be passed to an array parameter. The return type of a function is not allowed to be an array. Functions can either return a value or return nothing. If a function returns nothing, it must be declared as type void. If a function returns a value, the type can be any type except an array. However, structures can be returned, and structures can contain arrays. 3.6.3. Built-in FunctionsA large set of built-in functions is available. Built-in functions are documented in full in Chapter 5. A shader can override these functions, providing its own definition. To override a function, provide a prototype or definition that is in scope at call time. The compiler or linker then looks for a user-defined version of the function to resolve that call. For example, one of the built-in sine functions is declared as float sin(float x); If you want to experiment with performance or accuracy trade-offs in a sine function or specialize it for a particular domain, you can override the built-in function with your own function. float sin(float x) { return <.. some function of x..> } void main() { // call the sin function above, not the built-in sin function float s = sin(x); } This is similar to the standard language linking techniques of using libraries of functions and to having more locally scoped function definitions satisfy references before the library is checked. If the definition is in a different shader, just make sure a prototype is visible before calling the function. Otherwise, the built-in version is used. |