Technical Overview
How does it work?
FShade makes use of F#'s quotations (see Code Quotations) for translating the source-code to target languages like GLSL.
These quotations can be obtained by either applying the ReflectedDefinition
attribute to functions/modules or by
defining computation expression builders having a member Quote : unit -> unit
.
Since we wanted shaders to be first-class we opted for the latter and defined computation expression builders for the
various available shader-stages.
Let's look at a simple vertex shader example.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The above shader simply scales the per-vertex-value p by a factor 3 and returns it.
The record field p here is annotated with Semantic("pos")
which will be used as input-name in the
resulting low-level code and guides compositions (more on that below).
To obtain a compilable Effect
from the shader-function we use a somewhat magical combinator.
1:
|
|
Under the hood ofFunction
actually invokes the given function using Unchecked.defaultof<_>
as
argument value which ensures that per-vertex values are not accessed by shaders at compile-time.
The transformation will raise an exception stating shader functions may not access their vertex-input statically
when trying to do so.
The resulting Effect
represents an assignment of shaders to the available stages.
This encoding is similar to DirectX's Effects
where each Effect may contain any subset of shader-stages.
The effect from above can then be compiled to a Module
which in turn can be compiled to GLSL like this:
1: 2: 3: 4: 5: 6: 7: 8: |
|
The EffectConfig
states that we're interested only in the vertex shader (lastStage)
and that we'd like the result to include the output annotated with Semantic("pos")
as 4 dimensional vector (V4d) which
should be bound to output-location 2.
Here we use FShade's standard GLSL410 compiler to generate the final code:
|
Compositions
FShade currently provides 5 shader stages that can be used in effects.
1: 2: 3: 4: 5: 6: |
|
Just like in standard shader languages a shader can be composed with another shader for a later stage.
Therefore effects can obviously be composed whenever maxStage(left) < minStage(right)
.
However FShade also provides composition for other scenarios. Consider the following effects:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: |
|
These two (admittedly strage) effects are concerned with different aspects of a vertex but both make use of the vertex's Normal.
FShade's composition allows us to compose both effects sequentially using:
1: 2: 3: 4: 5: |
|
which gives us the following GLSL code:
|
Note that Coloring
here uses the normal from Transformation
When composed the other way round:
1: 2: 3: 4: 5: |
|
the resulting code looks like:
|
From a high-level point of view compose [a; b]
is similar to F#'s a >> b
since it
pipes the outputs of a
into b
.
However compose
works on n-ary functions taking an arbitrary number of inputs and producing an arbitrary number of outputs.
Matching values are identified using the Semantic
annotations defined in the types.
Currently only compositions for a subset of shader combinations are implemented.
compose [Vertex; Vertex] -> Vertex
compose [Geometry; Geometry] -> Geometry
compose [Geometry; Vertex] -> Geometry
compose [Fragment; Fragment] -> Fragment
compose [a; b] when a < b -> Effect [a;b]
However these seem sufficient in all cases we encountered so far ;)
Linking
As you may have noticed in the above composed effects tend to have a lot of outputs which may (partly) not be needed for rendering.
Therefore FShade provides functions for removing unused outputs and adding unspecified outputs by passing them along.
A little example:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
|
Here our fragment shader needs an input Normal and returns Colors and Normal.
In our config we declare only Colors to be used and bind it to location 0.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
The compiler now automatically removes the output Normal from the effect and creates a vertex shader passing the remaining inputs along. Since fragment shaders always implicitly require a position the vertex shader will finally pass Positions and Normal.
We refer to this process as linking since it links all shaders together creating a tight set of in-/outputs.
|
Note that passing along inputs is simple for vertex/fragment shaders but can be rather complicated for other stages.
When passing values through a GeometryShader FShade looks for an output annotated with [<SourceVertexIndex>]
and uses its value
to determine the corresponding input vertex for the output vertex in question.
For Point inputs the SourceVertexIndex is always 0.
FShade can also pass values through tessellation shaders as long as they can be interpolated linearly. (e.g. are floating point values)
Uniforms
FShade uses F#'s (?)
operator for accessing uniforms on the global value uniform
.
1: 2: 3: 4: 5: |
|
this shader accesses the uniform named MVPMatrix
which will be part of a uniform buffer MyUniformBuffer
having type M44d
.
Since the return type for (?)
is generic the value mvp needs a type annotation.
To avoid these annotations one may simply define extension properties for uniforms like:
1: 2: 3: 4: 5: 6: 7: |
|
Note that FShade does not contain any typed uniform-accessors like that but rendering frameworks may define their available uniform values like that.
{p: V4d;}
type SemanticAttribute =
inherit Attribute
new : s:string -> SemanticAttribute
member Semantic : string
--------------------
new : s:string -> SemanticAttribute
type V4d =
struct
new : v:int -> V4d + 25 overloads
val X : float
val Y : float
val Z : float
val W : float
member Abs : V4d
member AllDifferent : v:V4d -> bool + 1 overload
member AllEqual : v:V4d -> bool + 1 overload
member AllGreater : v:V4d -> bool + 1 overload
member AllGreaterOrEqual : v:V4d -> bool + 1 overload
...
end
--------------------
V4d ()
(+0 other overloads)
V4d(v: int) : V4d
(+0 other overloads)
V4d(a: int []) : V4d
(+0 other overloads)
V4d(v: int64) : V4d
(+0 other overloads)
V4d(a: int64 []) : V4d
(+0 other overloads)
V4d(v: float32) : V4d
(+0 other overloads)
V4d(a: float32 []) : V4d
(+0 other overloads)
V4d(v: float) : V4d
(+0 other overloads)
V4d(a: float []) : V4d
(+0 other overloads)
V4d(index_fun: System.Func<int,float>) : V4d
(+0 other overloads)
module Effect
from Utilities
--------------------
module Effect
from FShade
--------------------
type Effect =
new : m:Lazy<Map<ShaderStage,Shader>> -> Effect
new : m:Lazy<Map<ShaderStage,Shader>> * o:Effect list -> Effect
private new : id:string * shaders:Lazy<Map<ShaderStage,Shader>> * composedOf:Effect list -> Effect
member ComposedOf : Effect list
member FirstShader : Shader option
member FragmentShader : Shader option
member GeometryShader : Shader option
member Id : string
member InputToplogy : InputTopology option
member Inputs : Map<string,Type>
...
--------------------
new : m:System.Lazy<Map<ShaderStage,Shader>> -> Effect
new : m:System.Lazy<Map<ShaderStage,Shader>> * o:Effect list -> Effect
module EffectConfig
from FShade
--------------------
type EffectConfig =
{depthRange: Range1d;
flipHandedness: bool;
lastStage: ShaderStage;
outputs: Map<string,(Type * int)>;}
module ShaderStage
from Aardvark.Base
--------------------
type ShaderStage =
| Vertex = 0
| TessControl = 1
| TessEval = 2
| Geometry = 3
| Fragment = 4
| Compute = -1
ShaderStage.Vertex: ShaderStage = 0
--------------------
ShaderStage.Vertex: ShaderStage = 1
module Map
from FShade.BasicQuotationPatterns
--------------------
module Map
from FShade.StateExtensions
--------------------
module Map
from Aardvark.Base.Prelude
--------------------
module Map
from Aardvark.Base.Predefined Lenses
--------------------
module Map
from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> =
interface IEnumerable
interface IComparable
interface IEnumerable<KeyValuePair<'Key,'Value>>
interface ICollection<KeyValuePair<'Key,'Value>>
interface IDictionary<'Key,'Value>
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
member Add : key:'Key * value:'Value -> Map<'Key,'Value>
member ContainsKey : key:'Key -> bool
override Equals : obj -> bool
member Remove : key:'Key -> Map<'Key,'Value>
...
--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
module ModuleCompiler
from Utilities
--------------------
module ModuleCompiler
from FShade.Imperative
--------------------
module ModuleCompiler
from FShade.SpirV Extensions
--------------------
module ModuleCompiler
from FShade.Backends
val compileGLSL410 : module_:Module -> string
--------------------
val compileGLSL410 : module_:Module -> GLSL.GLSLShader
| Vertex = 0
| TessControl = 1
| TessEval = 2
| Geometry = 3
| Fragment = 4
module ShaderStage
from Aardvark.Base
--------------------
type ShaderStage = ShaderStage
| Vertex = 0
| TessControl = 1
| TessEval = 2
| Geometry = 3
| Fragment = 4
| Compute = -1
{pos: V4d;
normal: V3d;}
type PositionAttribute =
inherit SemanticAttribute
new : unit -> PositionAttribute
--------------------
new : unit -> PositionAttribute
type V3d =
struct
new : v:int -> V3d + 25 overloads
val X : float
val Y : float
val Z : float
member Abs : V3d
member AllDifferent : v:V3d -> bool + 1 overload
member AllEqual : v:V3d -> bool + 1 overload
member AllGreater : v:V3d -> bool + 1 overload
member AllGreaterOrEqual : v:V3d -> bool + 1 overload
member AllInfinity : bool
...
end
--------------------
V3d ()
(+0 other overloads)
V3d(v: int) : V3d
(+0 other overloads)
V3d(a: int []) : V3d
(+0 other overloads)
V3d(v: int64) : V3d
(+0 other overloads)
V3d(a: int64 []) : V3d
(+0 other overloads)
V3d(v: float32) : V3d
(+0 other overloads)
V3d(a: float32 []) : V3d
(+0 other overloads)
V3d(v: float) : V3d
(+0 other overloads)
V3d(a: float []) : V3d
(+0 other overloads)
V3d(index_fun: System.Func<int,float>) : V3d
(+0 other overloads)
{n: V3d;
color: V4d;}
val compose : effects:#seq<Effect> -> Effect
--------------------
val compose : effects:#seq<Effect> -> Effect
from Overview
from Overview
{n: V3d;}
{c: V4d;
n: V3d;}
union case TextureAspect.Color: TextureAspect
--------------------
type ColorAttribute =
inherit SemanticAttribute
new : unit -> ColorAttribute
--------------------
new : unit -> ColorAttribute
from Overview
ShaderStage.Fragment: ShaderStage = 4
--------------------
ShaderStage.Fragment: ShaderStage = 5
type M44d =
struct
new : a:float[] -> M44d + 2 overloads
val M00 : float
val M01 : float
val M02 : float
val M03 : float
val M10 : float
val M11 : float
val M12 : float
val M13 : float
val M20 : float
...
end
--------------------
M44d ()
M44d(a: float []) : M44d
M44d(a: float [], start: int) : M44d
M44d(m00: float, m01: float, m02: float, m03: float, m10: float, m11: float, m12: float, m13: float, m20: float, m21: float, m22: float, m23: float, m30: float, m31: float, m32: float, m33: float) : M44d
interface IComparable
private new : parent:Option<UniformScope> * name:string -> UniformScope
override Equals : o:obj -> bool
member GetChildScope : n:string -> UniformScope
override GetHashCode : unit -> int
member FullName : string
member private Id : int
member Name : string
member Parent : Option<UniformScope>
static member CreatePickler : r:IPicklerResolver -> Pickler<UniformScope>
...