Table of Contents
GPU FFI
The new 'GPU FFI' lets you write vertex and fragment shaders to draw things.
Here is a very basic shader that you can just paste as a virtual program (or print). It will color a triangle underneath itself green-blue.
Wish the GPU compiles pipeline "$this's triangle" { {vec2 p0 vec2 p1 vec2 p2} { vec2 vertices[4] = vec2[4](p0, p1, p2, p0); return vertices[gl_VertexIndex]; } { return vec4(0, 0.1, 0.1, 1); } } Wish $this is outlined blue When $this has region /r/ { set r' [region move $r down 100%] Wish the GPU draws pipeline "$this's triangle" with arguments [list {*}[lrange [region vertices ${r'}] 0 2]] }
The pipeline source object that you pass after Wish the GPU compiles pipeline NAME
has the form {ALL-ARGS VERT-SHADER [FRAG-ARGS] FRAG-SHADER}
.
The first element of the pipeline source object, {vec2 p0 vec2 p1 vec2 p2}
, is a list of overall arguments for both shaders. These are accessible from both the vertex and fragment shader. You pass these in at draw time (they're push constants – sort of like uniforms in GL, but limited in total size to 128 bytes).
(The exception is fn
-type arguments, which aren't actually passed in; they're meant to be names of GPU functions you've wished to be compiled with Wish the GPU compiles function NAME {...}
. They just fall out and don't map to arguments in Gpu::draw)
The second element in the pipeline is the source code of the main function of a vertex shader. It should return a vec2 vertex of a quad in Vulkan triangle-strip vertex order (should be topleft, topright, bottomleft, bottomright, like a Z, not counterclockwise) based on gl_VertexIndex
(it will be called with vertex index 0, 1, 2, 3). The returned vertex should be in screen coordinates, not in [0, 1]. You can access the builtin vec2 _resolution
to get resolution of the screen. (In practice, so far we mostly use the vertex shader for clipping so we don't have to touch the whole display on every draw.)
The third (or fourth, if you make fragment shader fn arguments before it) element in the pipeline is the source code of the main function of a fragment shader. You can access vec2 gl_FragCoord
to get current pixel coordinates. It should return a vec4 color. You can access any of the overall arguments (including _resolution
) from here as well.
You can make sampler2D
-type arguments if you want to pass an image in; you can make fn
arguments to a fragment shader as third argument. See the implementations of drawing primitives in virtual-programs/display/*.folk for more examples.
Adding functions
When you want to use functions, maybe to start using fun new color palettes in your shaders, you can add them like this:
# source: https://iquilezles.org/articles/palettes/ # cosine based palette, 4 vec3 params Wish the GPU compiles function "palette" {{float t vec3 a vec3 b vec3 c vec3 d} vec3 { return a + b*cos( 6.28318*(c*t+d) ); }} Wish the GPU compiles pipeline "$this's triangle" { {vec2 p0 vec2 p1 vec2 p2 float u_time} { vec2 vertices[4] = vec2[4](p0, p1, p2, p0); return vertices[gl_VertexIndex]; } {fn palette} { vec3 gradientColor = palette(gl_FragCoord.x, vec3(0.5,0.5,0.5),vec3(sin(u_time),0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,sin(u_time),0.20)); return vec4(gradientColor, 1.0); } } Wish $this is outlined blue When $this has region /r/ & the clock time is /t/ { set r' [region move $r down 100%] Wish the GPU draws pipeline "$this's triangle" with arguments [list {*}[lrange [region vertices ${r'}] 0 2] [fmod $t 16384]] }
TODO: Document internal Gpu:: API
These must be called on the display thread: Gpu::pipeline
, Gpu::draw
, Gpu::fn
(originally in https://github.com/FolkComputer/folk/pull/93 but pulling here to keep updated)