-
Notifications
You must be signed in to change notification settings - Fork 10
FShade type cheatsheet
FShade maps .NET types to GLSL types using a translation scheme which is outlined here.
All basic types are mapped to their corresponding types according to the following table.
GLSL | F# |
---|---|
bool |
bool |
int |
int8 , int16 , int32
|
uint |
uint8 , uint16 , uint32
|
float |
float32 , float , double
|
double |
|
void |
unit , System.Void
|
Note that FShade treats all double precision types as single precision types for historical reasons and that there is currently no way to specify a type that causes the generated code to contain double precision types. This was one of the very early choices made in FShade and changing it would sadly break most existing shaders.
FShade uses the vector-, matrix-types from Aardvark.Base to represent their GPU counterparts which are mapped to GLSL using the following scheme.
GLSL | F# |
---|---|
vec[N] |
V[N]d , V[N]f
|
dvec[N] |
|
ivec[N] |
V[N]i |
uvec[N] |
C[N]ui |
mat[N]x[M] |
M[N][M]d , M[N][M]f
|
dmat[N]x[M] |
Note that Aardvark.Base defines matrices to be row-major whereas GLSL defines them to be column-major. This problem can be circumvented using a special FShade option reverseMatrixLogic
in the GLSL assembler that adapts all affected operations involving a matrix to expect row-major matrices. That way the shader inputs can be filled with row-major matrices resulting in the desired behaviour.
FShade defines sampler and image types according to their GLSL counterparts in PascalCase. (e.g. Sampler2d <-> sampler2D).
The mapping between types is therefore rather simple
GLSL | F# |
---|---|
sampler((1|2|3)D|Cube) |
Sampler2((1|2|3)d|Cube) |
isampler((1|2|3)D|Cube) |
IntSampler((1|2|3)d|Cube) |
sampler((1|2|3)D|Cube)Array |
Sampler((1|2|3)d|Cube)Array |
sampler((1|2)D|Cube)MS |
Sampler((1|2)d|Cube)MS |
image((1|2|3)D) |
Image((1|2|3)d) |
... |
Samplers can be created using the computation-expression builders provided by FShade like:
let mySampler : Sampler2d =
sampler2d {
texture uniform?TextureName
filter FilterMode.MinMagMipLinear
addressU WrapMode.Wrap
addressV WrapMode.Wrap
// other existing properties:
// addressW, maxAnisotropy, borderColor,
// maxLod, minLod, mipLodBias, comparison
}
let shader (d : Data) =
fragment {
return mySampler.Sample(d.tc)
}
Note that samplers need to be defined as static values when used by shaders.
When assembling GLSL code the information supplied above cannot be expressed in the code directly. Therefore the Effect
contains a list of all used uniform-values for a set of shaders providing this sampler information.
GLSL and similar languages do not support dynamically sized arrays at all.
The .NET does only have types for dynamically sized arrays.
Therefore FShade needs a special type for representing fixed-size arrays in shader code. We chose to use Arr<'d, 'a>
from Aardvark.Base which can be instantiated using Aardvark.Base's type provider for number-types (e.g. N<10>
). FShade fully supports those arrays and Arr<16 N, float>
will be translated to float[16]
appropriately.
F# code may use user-defined types, which need to be translated to GLSL when used by a shader. FShade automatically does that using the following translation scheme. Some major restrictions apply here since GLSL does not provide pointers or dynamic allocation.
-
Types must not be recursive in any way
e.g.
type Tree = { l : Tree; r : Tree }
and similar things cannot be translated to GLSL. - Inheritance/Subtyping is not supported
- Function types are not supported
Records and Tuples are translated to GLSL struct types by FShade simply defining appropriate fields sequentially. FShade also defines functions for constructing these types that expect values for all fields.
For example the F# definition type Record = { a : int * int; b : bool }
gets translated to:
struct Tup_int_int {
int Item1;
int Item2;
}
struct Record {
Tup_int_int a;
bool b;
}
Tup_int_int new_Tup_int_int(int item1, int item2) {
Tup_int_int r;
r.Item1 = item1;
r.Item2 = item2;
return r;
}
Record new_Record(Tup_int_int a, bool b) {
Record r;
r.a = a;
r.b = b
return r;
}
Note that using records or tuples as shader inputs (uniforms, attributes, etc.) is perfectly valid from a shader point-of-view but a rendering-system needs to respect the layouting rules implied. Aardvark.Rendering currently supports custom structs for uniforms but not for vertex-attributes.
The translation process for union types is a little more involved than for records or tuples, since the target language cannot express these types directly.
Consider the following definition
type MyUnion =
| CaseA of value : int
| CaseB of a : float * b : float
FShade translates this type to GLSL
struct MyUnion {
int tag;
int CaseA_value;
float CaseB_a;
float CaseB_b;
}
MyUnion new_CaseA(int value) {
MyUnion r;
r.tag = 0;
r.CaseA_value = value;
return r;
}
MyUnion new_CaseB(float a, float b) {
MyUnion r;
r.tag = 1;
r.CaseB_a = a;
r.CaseB_b = b;
return r;
}
As you see in the above code the struct-layout wastes a lot of space, since all fields exist for any union-case but we couldn't figure out a way of avoiding that in GLSL. When these types are only used internally (not as input/output) the GLSL compiler is likely inlining them anyway so we decided not to investigate optimization possibilities further for the moment.
FShade allows pattern matching on these types just like F# itself does.
FShade has minimal support for other (C#, etc.) types but when using members on them they need to have a ReflectedDefinition
associated with them.
This essentially means that you can use class types in FShade but only when defining them in F# (you can't get ReflectedDefinitions otherwise).
It's strongly recommended not to use these types when avoidable, since it makes the entire compilation-process fragile and intransparent (mainly caused by inheritance, etc.)