Fork me on GitHub

Creating Interactive Futhark GUIs

Posted on April 25, 2016 by Niels G. W. Serup

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!

A Simple Start: Game of Life

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:

import life
import quadlife
import quadlife_alt

These Python modules are generated from the Futhark programs life.fut, quadlife.fut, and quadlife_alt.fut by means of the futhark-pyopencl code generator. life uses the usual Game of Life rules, while quadlife and quadlife_alt use rules invented by Torben Mogensen.

It then sets up PyGame:

screen = pygame.display.set_mode(size)

where 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)

Here, the l variable is the imported simulation backend, i.e. either life, quadlife, or 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 two-dimensional i32 array.

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:

def render():
    frame = l.render_frame(history)
    pygame.surfarray.blit_array(surface, frame.get())
    screen.blit(surface, (0, 0))
    pygame.display.flip()

Here, l.render_frame is a Futhark function that takes a two-dimensional 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:

while True:
    world, history = l.steps(world, history, steps)
    render()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

The 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.

The Three Other Ones

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

More on Arrays

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.

Tips

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.

Try them for yourself!

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 make, 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 futhark-pyopencl code 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.

Write your own!

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!