Futhark is a functional language which can be compiled into efficient GPU code. See our performance page for details. Futhark is a pretty specialised language: You can express complex programs using, among other things, Futhark’s second-order array combinators, but the programs cannot themselves be interactive in any way; in Futhark you can only describe computations which take an input, return an output, and do not have side effects. However, since you might want to add an interactive touch to your Futhark program (we do!), we have developed a code generator for generating Python modules. A Futhark-generated Python module can be imported into any Python program, provided you have NumPy and PyOpenCL installed, and can then be used for the computing-intensive part of a GUI, while the main Python file is used for everything else.
In the previous blog post, Troels showed you how to use Futhark’s PyOpenCL backend to generate PNG files with Python and NumPy. There was also a short mention of interactive interfaces to Futhark programs. In this blog post I will demonstrate how to use PyGame and Python to create those interactive visual interfaces. The concept is largely the same as in the previous post, but the visualisations are prettier.
PyGame is a Python library for the SDL 1.2 graphics library. Although a Python 3 port does exist, for compatibility reasons we use the original Python 2 library. Library-savvy readers might point out that a more modern library like pyglet or PySDL2 might be more… modern, but we really only require few features, so good old PyGame is sufficient for these basic examples.
We currently have four GUI examples:
It is pretty easy to create an interactive visualisation with Futhark, so expect this number to go up. However, note that Futhark development is not primarily oriented towards graphics programming – we just want to show off what we have!
Let us start with Futhark’s own Game of Life visualisation. This is the simplest of the four visualisations, as it is actually not interactive. However, it shows how few lines of code are needed to integrate Futhark, NumPy, and PyGame.
First, here is a video of me running the visualisation!
Direct link: life-2016.04.20.webm
Secondly, here is how it works: life-gui.py first imports our three Game of Life variants:
These Python modules are generated from the Futhark programs life.fut,
by means of the
futhark-pyopencl code generator.
life uses the
usual Game of Life rules, while
rules invented by Torben Mogensen.
It then sets up PyGame:
screen = pygame.display.set_mode(size)
size is a tuple of
(width, height). We then generate a
NumPy array of random initial states:
initworld = numpy.random.choice([True,False], size=size)
A NumPy array can be used as an argument to an entry point function in a Futhark-generated Python module. In this case, we use the NumPy array to get the initial simulation values by calling:
world, history = l.init(initworld)
l variable is the imported simulation backend,
quadlife_alt. You can
specify the backend with the
--variant command-line flag. The
l.init function is a Futhark function that takes a two-dimensional
bool array and returns a two-dimensional
bool array and a
We also need a temporary PyGame surface for transferring pixel data from NumPy to PyGame, so we run this:
surface = pygame.Surface(size)
To render a frame, we use the temporary
surface surface to
transform the returned NumPy array into something PyGame can
understand, after which we blit it to the screen:
frame = l.render_frame(history)
screen.blit(surface, (0, 0))
l.render_frame is a Futhark function that takes a
i32 array and returns a three-dimensional pixel
array in the colour format expected by PyGame.
As the blit array we need to use
frame.get() and not just
frame. This is because the Futhark-compiled Python module gives
us a PyOpenCL array, which is mostly compatible with NumPy, but not
fully – and Pygame really wants a proper NumPy array, so we use the
get() method to obtain that.
Finally, since Game of Life is a state-based simulation, we need a way to step through the simulation, using previous output as new input. This is pretty simple in Python:
world, history = l.steps(world, history, steps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
steps argument is the number of simulation steps to perform
per frame, and defaults to 3. You can set this to any positive 32-bit
i32. To increase the work done per frame, we have set the default to
3 and not e.g. 1. This choice reflects possible real-world use, where
we might not care about having a real-time visualisation of a
simulation, but just use the visualisation to track progress, and thus
ask the Futhark program to perform large chunks of work at a time, and
update the display fairly rarely.
We have also added a simple PyGame event check, so that you can close the simulation window as expected.
In the fluid simulator you can add both particles and forces. See for yourself:
Direct link: fluid-2016.04.20.webm
My laptop’s GPU (a nVidia GT 650 M) is not the newest one around, so I am running this in a fairly small window to avoid too much lag.
The Mandelbrot Explorer is also pretty nifty. Note that this implementation re-renders the entire visible region from scratch for every frame. This would likely be too slow if it was not GPU-accelerated.
Direct link: mandelbrot-2016.04.20.webm
In the end of the video, I switch to a Mandelbrot implementation written in pure NumPy (also included in the benchmarks repository). You can also check out the Mandelbrot performance numbers.
Finally, there is the HotSpot 2D Heat Equation GUI. You can see its performance and a description of what it is computing here. This visualisation is pretty silly, since every marked pixel gets the same power output level. The initial heat levels are random and take a while to dissipate, which is why the simulation spends quite some time before the generated graphics resemble the originally drawn graphics.
Direct link: hotspot-2016.04.20.webm
Every array in a Futhark-compiled Python module is of type pyopencl.array.Array, which means that all data is stored on the compute device, in our case the GPU. This enables GPU-stored arrays to be passed to and from a Python program without copying between CPU and GPU.
If you pass an array which is not a PyOpenCL array, but is NumPy-compatible, the generated Python code will automatically convert it into a PyOpenCL array by transferring its data to the compute device. This means that you can use NumPy to construct e.g. the initial arrays of a simulation, and not worry about the finer details.
Futhark is an optimising compiler which takes an entire program as input. As such, its optimisations are not directed at separate functions, but rather the program as a whole. This is in stark contrast to how computing libraries, e.g. NumPy, usually work. They consist of many primitive functions, and expect the programmer to structure them together using the host language, in this case Python.
Also, for every call to a function in a Futhark Python module, Python causes some overhead, which is another reason to have few calls to Futhark and much code in Futhark.
If you install the Futhark compiler (and PyOpenCL, NumPy, and PyGame), you should be able to compile and run all of the four GUI examples. First run:
git clone https://github.com/diku-dk/futhark-benchmarks.git
This will download all of Futhark’s benchmarks. Then for each of the
four interactive examples,
cd into its directory, run
and then follow the local README to run the GUI.
However, if you do not have the patience required to install Futhark
(and GHC), we have manually pre-compiled the current versions of the
four programs into Python for you with the
generator. Download futhark-guis-v0.1.tar.gz. This has only been tested on a
Debian, so run at your own risk. You still need to have PyOpenCL,
NumPy, and PyGame installed.
Do you have an idea for a computing-intensive program well suited for interactive use? If you can think of something, or even want to try your hand at implementing it, please contribute!