Composition
Semantics
Since effect composition is rather complicated we'd like to elaborate its workings using pseudo code here.
From a high level perspective the GLSL/HLSL shader stages can be seen as functions
1:
2:
3:
4:
|
type VertexShader = Vertex -> Vertex
type TessellationShader = Primitive<Vertex> -> seq<Primitive<Vertex>>
type GeometryShader = Primitive<Vertex> -> seq<Primitive<Vertex>>
type FragmentShader = Fragment -> Fragment
|
As we see the first three stages (Vertex, Geometry and Tessellation) work on spatial vertices and the fragment
shader operates on per-fragment data.
lets define some compose operators for these pseudo-shaders defining the composition semantics.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
|
let composeVertexVertex (l : VertexShader) (r : VertexShader) : VertexShader =
// simply apply the right shader to the outputs of the left one
l >> r
let composeTessellationVertex (l : TessellationShader) (r : VertexShader) : TessellationShader =
// apply the VertexShader to all vertice of all outputs of the TessellationShader
l >> Seq.map (Primitive.map r)
let composeGeometryVertex (l : GeometryShader) (r : VertexShader) : GeometryShader =
// apply the VertexShader to all vertice of all outputs of the GeometryShader
l >> Seq.map (Primitive.map r)
let composeGeometryGeometry (l : GeometryShader) (r : GeometryShader) : GeometryShader =
// apply the right shader to all primitives produced by the left one
l >> Seq.collect r
let composeFragmentFragment (l : FragmentShader) (r : FragmentShader) : FragmentShader =
// simply apply the right shader to the outputs of the left one
l >> r
|
Based on these compositions we can now define compositions for tagged Shaders which will be helpful
when implementing effect compositions below.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
|
type Shader =
| Vertex of VertexShader
| Tessellation of TessellationShader
| Geometry of GeometryShader
| Fragment of FragmentShader
let composeShader (l : Shader) (r : Shader) =
match l, r with
| Vertex l, Vertex r -> composeVertexVertex l r |> Vertex
| Tessellation l, Vertex r -> composeTessellationVertex l r |> Tessellation
| Geometry l, Vertex r -> composeGeometryVertex l r |> Geometry
| Geometry l, Geometry r -> composeGeometryGeometry l r |> Geometry
| Fragment l, Fragment r -> composeFragmentFragment l r |> Fragment
| _ -> failwith "composition not possible"
|
Since Vertex, Tessellation and Geometry work on primitives (or vertices) we refer to them as PrimitiveShader
.
With these tools at hand we can now define the effect composition operator.
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:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
|
type PrimitiveShader =
{
vertex : Option<VertexShader>
tessellation : Option<TessellationShader>
geometry : Option<GeometryShader>
}
type Effect =
{
primitive : PrimitiveShader
fragment : Option<FragmentShader>
}
let composePrimitive (l : PrimitiveShader) (r : PrimitiveShader) : PrimitiveShader =
// apply all shaders in the order
// [l.vertex; l.tessellation; l.geometry; r.vertex; r.tessellation; r.geometry]
// using the compose functions defined above and fail if an impossible constellation
// arises (e.g. composeTessellationTessellation would be needed)
let shaders (p : PrimitiveShader) =
[
match p.vertex with | Some s -> yield Vertex s | None -> ()
match p.tessellation with | Some s -> yield Tessellation s | None -> ()
match p.geometry with | Some s -> yield Geometry s | None -> ()
]
let withShader (s : Shader) (p : PrimitiveShader) =
match s with
| Geometry gs -> { p with geometry = Some gs }
| Tessellation ts -> { p with tessellation = Some ts }
| Vertex vs -> { p with vertex = Some vs }
| _ -> p
match l with
| { geometry = Some lg } ->
l |> withShader (r |> shaders |> List.fold composeShader (Geometry lg))
| { tessellation = Some lt } ->
l |> withShader (r |> shaders |> List.fold composeShader (Tessellation lt))
| { vertex = Some lv } ->
l |> withShader (r |> shaders |> List.fold composeShader (Vertex lv))
| _ ->
r
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Effect =
let empty =
{
primitive = { vertex = None; tessellation = None; geometry = None }
fragment = None
}
let compose2 (l : Effect) (r : Effect) =
{
primitive =
composePrimitive l.primitive r.primitive
fragment =
match l.fragment, r.fragment with
| o, None | None, o -> o
| Some l, Some r -> Some (composeFragmentFragment l r)
}
|
As we see here Effect.compose2
treats primitive- and fragment-shaders separately and ensures ordering only
inside the primitive-stage. Arguably the composition
could simply fail if called like Effect.compose2 { fragment = Some f } { primitive = <non-empty> }
but we
decided to keep it as flexible as possible.
Neutral Element
Effect.empty
poses as the neutral element for Effect.compose2
which can easily be seen by looking at the code.
Therefore Effect.compose2 Effect.empty a = a
and Effect.compose2 a Effect.empty = a
hold.
Associativity
Since Effect.compose2
resembles function composition (>>)
it obviously inherits its associativity.
Therefore Effect.compose2 a (Effect.compose2 b c) = Effect.compose2 (Effect.compose2 a b) c
holds.
Note that it is not commutative which means Effect.compose2 a b <> Effect.compose2 b a
and also not idempotent
meaning Effect.compose a a <> a
.
These properties motivate the combinator
1:
2:
|
let compose (effects : seq<Effect>) : Effect =
effects |> Seq.fold compose2 empty
|
Realworld
Real FShade Effects do not provide simple functions but instead contain Shaders for some of the stages.
Since these Shaders are not required to have matching inputs/outputs implementing the composition gets a bit more tedious.
From a high-level point of view FShade's Shaders can be seen as modules with an arbitrary number of inputs and outputs which
are semantically tagged using a name.
Consider composing the following shaders
FShade composes these in the following way
As you see in the illustration we need to be able to pass values through shaders which is rather simple
for the vertex/fragment stages but hard for geometry and tessellation.
In order to pass values through tessellation shaders we simply interpolate them using the given
domain when possible. (more on that in Tessellation)
For geometry shaders the passing cannot be implemented unless the input-primitives are Point<'a>
or the user provides
hints for the passing. This is done via the [<SourceVertexIndex>]
attribute which is discussed in detail in Geometry.
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Quotations
namespace Aardvark
namespace Aardvark.Base
namespace FShade
namespace FShade.Imperative
Multiple items
union case Vertex.Vertex: Vertex
--------------------
type Vertex = | Vertex
Multiple items
union case Fragment.Fragment: Fragment
--------------------
type Fragment = | Fragment
type Primitive<'a> = 'a list
type 'T list = List<'T>
val map : mapping:('a -> 'b) -> p:Primitive<'a> -> Primitive<'b>
val mapping : ('a -> 'b)
val p : Primitive<'a>
Multiple items
module List
from FShade.StateExtensions
--------------------
module List
from Aardvark.Base.Prelude
--------------------
module List
from Aardvark.Base.Predefined Lenses
--------------------
module List
from Aardvark.Base.Arrays
--------------------
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
val map : mapping:('T -> 'U) -> list:'T list -> 'U list
type VertexShader = Vertex -> Vertex
type TessellationShader = Primitive<Vertex> -> seq<Primitive<Vertex>>
Multiple items
module Primitive
from Composition
--------------------
type Primitive<'a> = 'a list
Multiple items
val seq : sequence:seq<'T> -> seq<'T>
--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
type GeometryShader = Primitive<Vertex> -> seq<Primitive<Vertex>>
type FragmentShader = Fragment -> Fragment
val composeVertexVertex : l:VertexShader -> r:VertexShader -> VertexShader
val l : VertexShader
val r : VertexShader
val composeTessellationVertex : l:TessellationShader -> r:VertexShader -> TessellationShader
val l : TessellationShader
Multiple items
active recognizer Seq: Expr -> Expr list option
--------------------
module Seq
from Aardvark.Base.Prelude
--------------------
module Seq
from Aardvark.Base.Arrays
--------------------
module Seq
from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>
val composeGeometryVertex : l:GeometryShader -> r:VertexShader -> GeometryShader
val l : GeometryShader
val composeGeometryGeometry : l:GeometryShader -> r:GeometryShader -> GeometryShader
val r : GeometryShader
val collect : mapping:('T -> #seq<'U>) -> source:seq<'T> -> seq<'U>
val composeFragmentFragment : l:FragmentShader -> r:FragmentShader -> FragmentShader
val l : FragmentShader
val r : FragmentShader
Multiple items
module Shader
from FShade
--------------------
type Shader =
| Vertex of VertexShader
| Tessellation of TessellationShader
| Geometry of GeometryShader
| Fragment of FragmentShader
Multiple items
union case Shader.Vertex: VertexShader -> Shader
--------------------
type Vertex = | Vertex
union case Shader.Tessellation: TessellationShader -> Shader
Multiple items
union case Shader.Geometry: GeometryShader -> Shader
--------------------
module Geometry
from Aardvark.Base
--------------------
type Geometry =
{vertexAttributes: Map<Symbol,IMod<IBuffer>>;
indices: Option<BufferView>;
uniforms: Map<string,IMod>;
call: IMod<DrawCallInfo list>;}
Multiple items
union case Shader.Fragment: FragmentShader -> Shader
--------------------
type Fragment = | Fragment
val composeShader : l:Shader -> r:Shader -> Shader
val l : Shader
val r : Shader
val failwith : message:string -> 'T
type PrimitiveShader =
{vertex: Option<VertexShader>;
tessellation: Option<TessellationShader>;
geometry: Option<GeometryShader>;}
PrimitiveShader.vertex: Option<VertexShader>
Multiple items
module Option
from FShade.Optimizer.Helpers
--------------------
module Option
from Aardvark.Base.Prelude
--------------------
module Option
from Microsoft.FSharp.Core
PrimitiveShader.tessellation: Option<TessellationShader>
PrimitiveShader.geometry: Option<GeometryShader>
Multiple items
module Effect
from FShade
--------------------
type Effect =
{primitive: PrimitiveShader;
fragment: Option<FragmentShader>;}
Effect.primitive: PrimitiveShader
Effect.fragment: Option<FragmentShader>
val composePrimitive : l:PrimitiveShader -> r:PrimitiveShader -> PrimitiveShader
val l : PrimitiveShader
val r : PrimitiveShader
val shaders : (PrimitiveShader -> Shader list)
val p : PrimitiveShader
union case Option.Some: Value: 'T -> Option<'T>
val s : VertexShader
union case Option.None: Option<'T>
val s : TessellationShader
val s : GeometryShader
val withShader : (Shader -> PrimitiveShader -> PrimitiveShader)
val s : Shader
val gs : GeometryShader
val ts : TessellationShader
val tessellation : TessBuilder
val vs : VertexShader
val vertex : VertexBuilder
val lg : GeometryShader
val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'State
val lt : TessellationShader
val lv : VertexShader
Multiple items
type CompilationRepresentationAttribute =
inherit Attribute
new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute
member Flags : CompilationRepresentationFlags
--------------------
new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute
type CompilationRepresentationFlags =
| None = 0
| Static = 1
| Instance = 2
| ModuleSuffix = 4
| UseNullAsTrueValue = 8
| Event = 16
CompilationRepresentationFlags.ModuleSuffix: CompilationRepresentationFlags = 4
Multiple items
module Effect
from Composition
--------------------
module Effect
from FShade
--------------------
type Effect =
{primitive: PrimitiveShader;
fragment: Option<FragmentShader>;}
val empty : Effect
val fragment : FragmentBuilder
val compose2 : l:Effect -> r:Effect -> Effect
val l : Effect
val r : Effect
val o : Option<FragmentShader>
val compose : effects:seq<Effect> -> Effect
val effects : seq<Effect>
val fold : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> 'State