A statement block contains one or more expressions.
{ expression 1; expression 2; ... expression n; }
Expressions are sequences of variables And literals, punctuated by operators. A literal is an explicit data value, such as 1 for an integer or 2.1 for a floating-point number. Literals are often used to assign a value to a variable.
An expression followed by a semicolon (;) is called a statement. Statements range in complexity from simple expressions to blocks of statements that accomplish a sequence of actions. Flow-control statements determine the order statements are executed.
A statement block also indicates subscope. Variables declared within a statement block are only recognized within the block. HLSL statements determine the order in which expressions are evaluated. Each expression can be one of the following:
Operators determine how the variables and literals are combined, compared, selected, and so on. The operators include:
Operator Name | Operators |
---|---|
+, -, *, /, % | |
[i] | |
=, +=, -=, *=, /= | |
&&, ||, ?: | |
(type) | |
, | |
<, >, ==, !=, <=, >= | |
++, -- | |
. | |
!, -, + |
Many of the operators are "per component." This means that the operation is performed independently for each component of each variable. For example, a single component variable has one operation performed. On the other hand, a four-component variable has four operations performed, one for each component.
The additive and multiplicative operators are: +, -, *, /, %
int i1 = 1; int i2 = 2; int i3 = i1 + i2; // i3 = 3 i3 = i1 * i2; // i3 = 1 * 2 = 2
i3 = i1/i2; // i3 = 1/3 = 0.333. Truncated to 0 because i3 is an integer. i3 = i2/i1; // i3 = 2/1 = 2
float f1 = 1.0; float f2 = 2.0f; float f3 = f1 - f2; // f3 = 1.0 - 2.0 = -1.0 f3 = f1 * f2; // f3 = 1.0 * 2.0 = 2.0
f3 = f1/f2; // f3 = 1.0/2.0 = 0.5 f3 = f2/f1; // f3 = 2.0/1.0 = 2.0
The modulus operator returns the remainder of a division. This produces different results when using integers and floating-point numbers. Integer remainders that are fractional will be truncated.
int i1 = 1; int i2 = 2; i3 = i1 % i2; // i3 = remainder of 1/2, which is 1 i3 = i2 % i1; // i3 = remainder of 2/1, which is 0 i3 = 5 % 2; // i3 = remainder of 5/2, which is 1 i3 = 9 % 2; // i3 = remainder of 9/2, which is 1
The modulus operator truncates a fractional remainder when using integers.
f3 = f1 % f2; // f3 = remainder or 1.0/2.0, which is 0.5 f3 = f2 % f1; // f3 = remainder of 2.0/1.0, which is 0.0
The % operator is defined only in cases where either both sides are positive or both sides are negative. Unlike C, it also operates on floating-point data types, as well as integers.
The array member selection operator "[i]" selects one or more components in an array. It is a set of square brackets that contain a zero-based index.
int arrayOfInts[4] = { 0,1,2,3 }; arrayOfInts[0] = 2; arrayOfInts[1] = arrayOfInts[0];
The array operator can also be used to access a vector.
float4 4D_Vector = { 0.0f, 1.0f, 2.0f, 3.0f }; float 1DFloat = 4D_Vector[1]; // 1.0f
By adding an additional index, the array operator can also access a matrix.
float4x4 mat4x4 = {{0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} }; mat4x4[0][1] = 1.1f; float 1DFloat = mat4x4[0][1]; // 0.0f
The first index is the zero-based row index. The second index is the zero-based column index.
The assignment operators are: =, +=, -=, *=, /=
Variables can be assigned literal values:
int i = 1; half h = 3.0; float f2 = 3.1f; bool b = false; string str = "string";
Variables can also be assigned the result of a mathematical operation:
int i1 = 1; i1 += 2; // i1 = 1 + 2 = 3
A variable can be used on either side of the equals sign:
float f3 = 0.5f; f3 *= f3; // f3 = 0.5 * 0.5 = 0.25
Division for floating-point variables is as expected because decimal remainders are not a problem.
float f1 = 1.0; f1 /= 3.0f; // f1 = 1.0/3.0 = 0.333
Be careful if you are using integers that may get divided, especially when truncation affects the result. This example is identical to the previous example, except for the data type. The truncation causes a very different result.
int i1 = 1; i1 /= 3; // i1 = 1/3 = 0.333, which gets truncated to 0
The Boolean math operators are: &&, ||, ?:
bool b1 = true; bool b2 = false; bool b3 = b1 && b2 // b3 = true AND false = false b3 = b1 || b2 // b3 = true OR false = true
Unlike short-circuit evaluation of &&, ||, and ?: in C, HLSL expressions never short-circuit an evaluation because they are vector operations. All sides of the expression are always evaluated.
Boolean operators function on a per-component basis. This means that if you compare two vectors, the result is a vector containing the Boolean result of the comparison for each component.
For expressions that use Boolean operators, the size and component type of each variable are promoted to be the same before the operation occurs. The promoted type determines the resolution at which the operation takes place, as well as the result type of the expression. For example an int3 + float expression would be promoted to float3 + float3 for evaluation, and its result would be of type float3.
An expression preceded by a type name in parenthesis is an explicit type cast. A type cast converts the original expression to the data type of the cast. In general, the simple data types can be cast to the more complex data types (with a promotion cast), but only some complex data types can be cast into simple data types (with a demotion cast).
The comma operator (,) separates one or more expressions that are to be evaluated in order. The value of the last expression in the sequence is used as the value of the sequence.
Here is one case worth calling attention to. If the constructor type is accidentally left off the right side of the equals sign, the right side now contains four expressions, separated by three commas.
// Instead of using a constructor float4 x = float4(0,0,0,1); // The type on the right side is accidentally left off float4 x = (0,0,0,1);
The comma operator evaluates an expression from left to right. This reduces the right hand side to:
float4 x = 1;
HLSL uses scalar promotion in this case, so the result is as if this were written as follows:
float4 x = float4(1,1,1,1);
In this instance, leaving off the float4 type from the right side is probably a mistake that the compiler is unable to detect because this is a valid statement.
The comparison operators are: <, >, ==, !=, <=, >=.
Compare values that are greater than (or less than) any scalar value:
if( dot(lightDirection, normalVector) > 0 ) // Do something; the face is lit
if( dot(lightDirection, normalVector) &< 0 ) // Do nothing; the face is backwards
Or, compare values equal to (or not equal to) any scalar value:
if(color.a == 0) // Skip processing because the face is invisible if(color.a != 0) // Blend two colors together using the alpha value
Or combine both and compare values that are greater than or equal to (or less than or equal to) any scalar value:
if( position.z >= oldPosition.z ) // Skip the new face because it is behind the existing face
if( currentValue &<= someInitialCondition ) // Reset the current value to its initial condition
Each of these comparisons can be done with any scalar data type. Comparison operators do not support the complex data types such as vector, matrix, or the object types.
The prefix and postfix operators are: ++, --. Prefix operators change the contents of the variable before the expression is evaluated. Postfix operators change the contents of the variable after the expression is evaluated.
In this case, a loop uses the contents of i to keep track of the loop count.
float4 arrayOfFloats[4] = { 1.0f, 2.0f, 3.0f, 4.4f }; for (int i = 0; i<4; ) { arrayOfFloats[i++] *= 2; }
Because the postfix increment operator "++" is used, "arrayOfFloats[i]" is multiplied by 2 before i is incremented. This could be slightly rearranged to use the prefix increment operator. This one is harder to read, although both examples are equivalent.
float4 arrayOfFloats[4] = { 1.0f, 2.0f, 3.0f, 4.4f }; for (int i = 0; i<4; ) { arrayOfFloats[++i - 1] *= 2; }
Because the prefix operator "++" is used, "arrayOfFloats[i+1 - 1]" is multiplied by 2 after i is incremented.
The prefix decrement and postfix decrement operator "--" are applied in the same sequence as the increment operator. The difference is that decrement subtracts 1 instead of adding 1.
The structure member selection operator (.) is a period. Given this structure:
struct position { float4 x; float4 y; float4 z; };
It can be read like this:
struct position pos = { 1,2,3 }; float 1D_Float = pos.x 1D_Float = pos.y
Each member can be read or written with the structure operator:
struct position pos = { 1,2,3 }; pos.x = 2.0f; pos.z = 1.0f; // z = 1.0f pos.z = pos.x // z = 2.0f
The unary operators are: !, -, +
Unary operators operate on a single operand.
bool b = false; bool b2 = !b; // b2 = true int i = 2; int i2 = -i; // i2 = -2 int j = +i2; // j = +2
When an expression contains more than one operator, operator precedence determines the order of evaluation.
Curly braces ({,}) start and end a statement block. When a statement block uses a single statement, the curly braces are optional.