FShade: first-class shaders for F#
FShade is a library that extends F# with a domain specific language for shaders. It provides high-level utilities for working with first-class shaders inlcuding composition-, modification- and preprocessing-tools. It focuses on graphics shaders but also provides reusable utilities for compiling F# functions to other c-like languages.
FShade uses primitive vector-/matrix-types from Aardvark.Base for representing their shader-language counterparts but could (on demand) be extended to use others too.
Example
This example implements a very basic vertex shader that can be compiled using FShade.
1: 2: 3: 4: 5: 6: 7: |
|
Effects
FShade represents shader code using Effect
which assigns shader code to the available pipeline stages.
The available stages are the ones currently available in modern graphics APIs:
- Vertex
- TessControl
- TessEval
- Geometry
- Fragment
Each Effect
can provide code for a subset of these stages and can be composed with others using Effect.compose
as described in Composition.
Effect
also exposes some other functions for creating/transforming/inspecting effects.
The shader from above can be translated to an effect by using
1:
|
|
Compilation
FShade's compilation process consists of three stages which successively lower the abstraction level.
Module
Module serves as the common interface for FShade's compiler and provides a list of entry-point definitions (using F#'s Expr)
Effect.toModule
translates an Effect to one of these.
CModule
CModules represent the content of c-files which can consist or various value-/type-definitions. In order to compile a
Module
one can use ModuleCompiler
. Note that in this step needs information about intrinsic functions.
Therefore ModuleCompiler.compile
requires a Backend
as argument which knows about the intrinsic (predefined) functions
and types for the current target language.
Assembler
FShade defines a GLSL 'Assembler' that simply transforms a CModule to GLSL code. This is done by simply visiting the
entire CModule while emitting appropriate c-code. This way assemblers don't need to stick to any specific interface and other
assemblers may produce other output-types. For example SpirV would need to produce byte[]
instead of strings.
Here's an example compiling an effect to GLSL illustrating the compiler-stages.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
|
|
namespace Microsoft.FSharp.Data
--------------------
type Data =
{pos: V4d;
tc: V2d;}
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)
type V2d =
struct
new : v:int -> V2d + 20 overloads
val X : float
val Y : float
member Abs : V2d
member AllDifferent : v:V2d -> bool + 1 overload
member AllEqual : v:V2d -> bool + 1 overload
member AllGreater : v:V2d -> bool + 1 overload
member AllGreaterOrEqual : v:V2d -> bool + 1 overload
member AllInfinity : bool
member AllNaN : bool
...
end
--------------------
V2d ()
(+0 other overloads)
V2d(v: int) : V2d
(+0 other overloads)
V2d(a: int []) : V2d
(+0 other overloads)
V2d(v: int64) : V2d
(+0 other overloads)
V2d(a: int64 []) : V2d
(+0 other overloads)
V2d(v: float32) : V2d
(+0 other overloads)
V2d(a: float32 []) : V2d
(+0 other overloads)
V2d(v: float) : V2d
(+0 other overloads)
V2d(a: float []) : V2d
(+0 other overloads)
V2d(index_fun: System.Func<int,float>) : V2d
(+0 other overloads)
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
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
module GLSL
from Utilities
--------------------
namespace FShade.GLSL
module Assembler
from Utilities.GLSL
--------------------
module Assembler
from FShade.GLSL
val assemble : cfg:GLSL.Backend -> s:CModule -> string
--------------------
val assemble : backend:GLSL.Backend -> m:CModule -> GLSL.GLSLShader