Parasol ↔ GLSL
The Parasol language
takes a similar view of the world as a typical GLSL program. That is,
a program consists of exactly one
vertex shader
and exactly one fragment shader.
In GLSL, the vertex shader is responsible for assigning values to
a set of declared outputs, and additionally assigning a clip-space
vertex position to a built-in variable named
gl_Position.
In Parasol, a vertex shader
declares a set of inputs, outputs, and parameters. One of the
outputs is marked as being the vertex
output, to which the programmer is expected to assign a clip-space
vertex position. There are no built-in names. Input, output, and
parameter names in Parasol
programs will appear exactly as written in the resulting GLSL
and are therefore subject to the same naming restrictions (and
this is checked by the compiler).
In early versions
of GLSL, the fragment shader was responsible for calculating a single
RGBA colour vector and assigning it to a built-in variable called
gl_FragColor. In later versions
of OpenGL, it became possible to have multiple colour attachments
to framebuffers, and therefore it became necessary for the fragment
shader to assign multiple outputs. The gl_FragColor
variable was essentially replaced with the
gl_FragData variable, which is
of an array type. In modern OpenGL, the
gl_FragColor and
gl_FragData variables have been
removed, and programmers are required to explicitly declare named
fragment shader outputs.
In Parasol, a fragment shader
declares a set of inputs, outputs, and parameters. All of the
outputs must be named and numbered. This allows the compiler to
map numbered outputs to the gl_FragData
array in early versions of GLSL, and to correctly declare
named outputs in modern GLSL. The compiler checks that output
numbers start at 0 and
increase monotonically.
As stated previously, in GLSL a vertex or fragment shader is
typically represented by a single file and execution begins
at a function called main.
The Parasol language implements
a simple module system to promote re-use of code, and all functions,
types, and shaders must be declared inside of modules. Vertex
and fragment shaders are declared inside of modules and then
aggregated into programs. When it
comes time to produce GLSL, the programmer provides the
fully-qualified name of a
declared program and the compiler
produces a set of GLSL shaders based on the selection.
Hello World GLSL
A simple GLSL vertex shader in GLSL 1.40 that simply transforms
the given object-space coordinates to clip-space by multiplying
by a given modelview and projection matrix:
A simple fragment shader in GLSL 1.40 that declares a colour
output and assigns a constant red colour to it:
The programmer then compiles and loads each shader file from the
OpenGL API, links them, and then uses them.
Hello World Parasol
As stated, all terms, types, and shaders must be declared in
modules. For this tutorial, a HelloWorld
module suffices, declared in package
com.io7m.examples.
The previous GLSL shaders translated to Parasol
become:
The module declares a vertex shader named v,
a fragment shader named p, and combines the two
into a program named p. Any number of
shaders and programs can be declared in a single module (but their names
must obviously differ).
The vertex shader declares an input v_position,
two parameters m_modelview and
m_projection, and an output named
o which is marked as the main
vertex output with the vertex keyword.
The shader performs the same multiplication as the GLSL program, assigning
the result to a local variable named position_clip.
Variables in Parasol are immutable
and the programmer is not required to state the types of values because
the language features local type inference. Finally, the shader writes
the calculated value to the output
o with an
output assignment. The right-hand side of
an output assignment is required to be
a name . The Parasol compiler
requires that all outputs be assigned.
Because the language only provides functions and not operators, the order
of operations is completely unambiguous; the arguments to functions are
evaluated eagerly from left to right and then substituted into the body
of the function. Does the multiplication in the GLSL version mean
(p * m) * v or
p * (m * v)? In this case, it may
not matter, but what about in more complicated expressions with
overloading? Programmers get this wrong constantly. The
Parasol
language keeps things explicit - mistakes become difficult to make.
The fragment shader simply declares a value named red
and assigns it to the single declared output named o.
Note that o is assigned both a name
and a number; this is required by the language.
The program p states that it uses
the vertex shader v and the fragment
shader f. The compiler will check that
the programs are compatible when
p is declared. The rules for
compatibility are the same as they
are for GLSL; the vertex shader must have a corresponding output
with the same name and type as each of the fragment shader's
inputs.
Assuming that the above is in a file named HelloWorld.txt
and that the resulting GLSL shaders should be written to a directory
called /tmp/shaders, the program can be compiled
as follows:
The compiler says nothing unless an error occurs.
By default, the compiler will attempt to produce GLSL shaders for all
known versions of GLSL. It will silently fail to produce a program for
a version that cannot be supported. In the case of the above program,
there are no versions of GLSL that cannot support the program. It is
usually more desirable to specify a range of required versions and have the
compiler raise an error when it cannot produce code for one or more
versions. To compile the same program but with the requirement that
all versions of GLSL must be supported:
The
[,] notation is standard
mathematical range notation. This particular expression represents
an inclusive upper and lower bound that effectively covers all versions.
The notation is
explained fully
in the documentation for the compiler frontend.
An examination of the output directory will show that the compiler
has produced code for all of the GLSL versions:
As can be seen, the resulting GLSL program is almost identical,
with the added bonus that the Parasol
version provides code for all possible GLSL versions.