HxSL - haXe Shader Language
Today hardware accelerated 3D rendering is using "shaders" in order to perform many tasks :
- vertex shaders are used for transforming and projecting each geometry point into 2D space, and setup "variables" that will be interpolated on a per-pixel basis and can be used by the pixel shader
- fragment shaders are used for blending different textures and colors into a single pixel color that will be written to screen
Thanks to Macros, we were able to develop a high level shader language called HxSL that uses haXe syntax and can directly be embedded into haXe programs source.
Shader expression
The syntax of the shader follow the syntax of haXe language, however only a subset of the language is supported. Here's a HxSL shader expression example :
var input : { pos : Float3, uv : Float2 }; var tuv : Float2; function vertex( mpos : Matrix, mproj : Matrix ) { out = pos.xyzw * mpos * mproj; tuv = uv; } function fragment( t : Texture ) { out = t.get(tuv,wrap); }
A shader expression consists in the following :
- an
inputobject, which declare the data that is stored into the Vertex buffer - zero or many
variablesthat will be written by vertex shader, then interpolated by the GPU and read by the fragment shader - one vertex shader function, with its
parameters(constants for all the vertex processed) - one fragment shader function, with its
parameters(constants for all the pixels processed)
The following variable types are defined :
Float: a single scalar valueFloat2,Float3,Float4: a set of 2,3,4 floatsMatrixorM44: a 4x4 floats matrixM33,M43andM34: the corresponding matrixTexture: a 2D textureCubeTexture: a cubic texture (6 faces)Int(orColor) : a 32 bit integer which is converted to four floats in [0,1.0] range
Each shader will write to the out variable, which has the type Float4
Local variables
It is possible to declare local variables in a HxSL shader. They can be either declared with an initial value :
function vertex(...) { var tpos = pos.xyzw * mpos; out = tpos * mproj; }
Or they can be declared and initialized later, in which case it is necessary to declare their type as well :
function vertex(...) { var tpos : Float4; tpos = pos.xyzw * mpos; out = tpos * mproj; }
Constants
Constants can be a single scalar value or a group of constant values declared as an haXe array :
0.5 // Float [0.5,1.5] // Float2 [1,2,3] // Float3 [1,2,3,4.5] // Float4
Operations
Here's the list of operations allowed in HxSL. FloatX means that this can apply to Float, Float2, Float3 and Float4 values, as soon as they are all of the same type.
All operations can be used in two ways :
// standard C-like way : add(x,y) // Object-Oriented way : x.add(y)
add(or+) : add two values componentsfunction add( a : FloatX, b : FloatX ) : FloatX;
sub(or-) : subtract two values componentsfunction sub( a : FloatX, b : FloatX ) : FloatX;
mul(or*) : multiply two values components, or project a vector, or multiply two matrix :function mul( a : FloatX, b : FloatX ) : FloatX; function mul( a : Float4, m : Matrix ) : Float4; function mul( a : Matrix, b : Matrix ) : Matrix;
div(or/) : divide two values componentsfunction div( a : FloatX, b : FloatX ) : FloatX;
mod(or%) : calculate the remainder of the divisionfunction mode( a : FloatX, b : FloatX ) : FloatX;
pow: calculate a^b for two values componentsfunction pow( a : FloatX, b : FloatX ) : FloatX;
min: calculate the minimum for two values componentsfunction min( a : FloatX, b : FloatX ) : FloatX;
max: calculate the maximum for two values componentsfunction max( a : FloatX, b : FloatX ) : FloatX;
dot(ordp,dp3,dp4) : calculate the dot product of two vectorsfunction dot( a : Float3, b : Float3 ) : Float; function dot( a : Float4, b : Float4 ) : Float;
cross(orcrs) : calculate the cross product of two vectorsfunction cross( a : Float3, b : Float3 ) : Float3;
neg(or unary-) : calculate the negative of one value componentsfunction neg( v : FloatX ) : FloatX;
inv(orrcp, or1 / x) : calculate the inverse of one value componentsfunction inv( v : FloatX ) : FloatX;
sqrt(orsqt) : calculate the square root of one value componentsfunction sqrt( v : FloatX ) : FloatX;
rsqrt(orrsq, or1 / sqrt(x)) : calculate the inverse square root of one value componentsfunction rsqrt( v : FloatX ) : FloatX;
log: calculate the logarithm of one value componentsfunction log( v : FloatX ) : FloatX;
exp: calculate the exponent of one value componentsfunction exp( v : FloatX ) : FloatX;
length(orlen) : calculate the length of one value componentsfunction length( v : FloatX ) : Float;
normalize(ornorm, ornrm') : return the normalized vector of one value componentsfunction normalize( v : FloatX ) : Float3;
sin: calculate the sinus of one value componentsfunction sin( v : FloatX ) : FloatX;
cos: calculate the cosine of one value componentsfunction cos( v : FloatX ) : FloatX;
abs: calculate the absolute of one value componentsfunction abs( v : FloatX ) : FloatX;
saturate(orsat) : calculatemin(1,max(v,0))of one value componentsfunction saturate( v : FloatX ) : FloatX;
fract(orfrc) : calculate the fractional portion of one value componentsfunction fract( v : FloatX ) : FloatX;
int: calculate the integer portion of one value componentsfunction int( v : FloatX ) : FloatX;
lt(or<) : return (a < b) ? 1 : 0 for each value componentsfunction lt( v : FloatX ) : FloatX;
lte(or<=) : return (a <= b) ? 1 : 0 for each value componentsfunction lte( v : FloatX ) : FloatX;
gt(or>) : return (a > b) ? 1 : 0 for each value componentsfunction gt( v : FloatX ) : FloatX;
gte(or>=) : return (a >= b) ? 1 : 0 for each value componentsfunction gte( v : FloatX ) : FloatX;
- "eq" (or "==") : return (a == b) ? 1 : 0 for each value components
function eq( v : FloatX ) : FloatX;
- "neq" (or "!=") : return (a != b) ? 1 : 0 for each value components
function neq( v : FloatX ) : FloatX;
kill: only available in fragment shader. Skip a pixel write if the value is < 0function kill( v : Float ) : Void;
get: access a texture color value using a given texture coordinates
The following texture flags are allowed :wrap: wrap around the texture bordersclamp: stick to the texture bordersnearest: round-to-nearest pixel readlinear: bilinear pixel readmm_no: disable mipmappingmm_nearest: round-to-nearest mipmappingmm_linear: linear mipmappinglod(v): texture lod (for mipmapping)function get( t : Texture, v : Float2, ....flags ) : Float4; function get( t : CubeTexture, v : Float3, ....flags ) : Float4;
transpose: transpose a matrix componentsfunction transpose( m : MXY ) : MYX;
Swizzling / Masking
It is possible to read only a part of a value components, or to rearrange the elements of a value by using swizzling. For example :
var tmp : Float4 = [1,2,3,4]; out.x = tmp.x; // copy 1 into x out.xz = tmp.yz; // copy 2 into x and 3 into z out = tmp.xxww; // copy 1 into x and y and 4 into z and w
The fact of only writing some parts of a value is called masking.
You can only swizzle on components which are accessible, for instance you cannot read the z value of a Float2 variable. The only exception is for input and variables (not temporary ones) which all 4 components can be read since they are always filled with 0 if not written.
Auto-Scalar Swizzling
When some operations take two FloatX values, if you use a single Float as one of two values, HxSL will automatically swizzle on all the components. For instance :
var tmp : Float4 = [1,2,3,4]; out = tmp * 0.5; // will multiply all components by 0.5
This also works for any single Float value :
var half : Float = 0.5; out = half * tmp; // same as writing half.xxxx
If / Else
If/Else can be inside a value-expression this way :
color = if( red < 0 ) 1 else 0.2;
This will actually compile to the code corresponding to the following :
color = (red < 0) * 1 + (red >= 0) * 0.2;
You can only use comparison operators inside a if/else value expression.
If/Else statements are not supported so far, but might be added in next HxSL version.
For Loops
It is also possible to use for loops in HxSL, as long as they operate on constant values :
// make the sum of 3x3 textures samples var color = [0,0,0]; for( x in -1...2 ) for( y in -1...2 ) color += tex.get(uv + [x,y]);
For-loops will be unrolled so the shader will be the same as if you would have written the corresponding 9 lines for each of the (x,y) possible values.
Helper Functions
You can define helper functions before vertex and fragment shaders. They can take several arguments and will return a result value. In practice, helper functions are inlined in the compiled shader.
// helper function example : function lerp( x : Float, y : Float, v : Float ) { return x * (1 - v) + y * v; }
Include
It is possible to have extern files containing any HxSL declarations. Such extern files can be included in any shader with the include statement :
include('baseShader.hxsl'); // only define the fragment shader function fragment( t : Texture ) { out = t.get(tuv,wrap); }
Matrix Transposition
In order to perform operations such as vector projection or matrix multiplication, it is necessary to have the right matrix transposed (that is to be able to read its columns instead of its lines). This is because a Matrix consists in actually 4 Float4 values, which can represent either the matrix lines (by default) or its columns (transposed mode).
HxSL performs what is called matrix transposition inference. It infers from the operation you are using in which mode (either transposed or not) the matrix should be.
For instance when projecting a vector :
function vertext( mpos : Matrix ) { var tpos = pos * mpos; ... }
The will force the matrix mpos to be transposed. Any further operation using mpos in a not-transposed way will cause an error.
Row Access
You can access a matrix row vector with m[3], this will for example return the 4th row of the matrix.
Indirect Access
You can also use indirect access in HxSL by declaring some Array-variables, such as the following :
var input : { indexes : Float2 } function vertex( pos : Float4<10> ) { out = pos[indexes.x] + pos[indexes.y] }
Array variables must be of constant size.
Please note that if you are indexing a Matrix Array, your indexes needs to be multiples of 4 : 0 will reference the first matrix and 4 the second one.
Using HxSL with Flash11
In order to use shaders with Flash11 API, you can simply declare a new shader such as the following example is showing :
class Shader extends format.hxsl.Shader { static var SRC = { var input : { pos : Float3, uv : Float2 }; var tuv : Float2; function vertex( mpos : Matrix, mproj : Matrix ) { out = pos.xyzw * mpos * mproj; tuv = uv; } function fragment( t : Texture ) { out = t.get(tuv,wrap); } } }
This will generate a bind and init methods for your shader that will perform all the necessary setup of shader constants, and select the shader program for your 3D context.
You can find all details and examples here : Using Flash 3D API