gpWFC
October 29, 2018 ยท View on GitHub
Implementation of the Wave Function Collapse procedural content generation algorithm, using (py)OpenCL for GPU acceleration.
![]()
Getting Started
make sure you have the python packages pyopencl, numpy and pyglet installed.
You can then run a basic example using
python main.py
in the preview window the following keybindings are set:
escape: closespace: do one oberservation/propagation cycle and renderr: cycle until stable, then render againd: debug view (overlay decimal display of bitmask for each tile)
There is also a more interesting sprite-based example that you can run using
python circuit.py [render]
but as you can see I didn't set up the model constraints properly. Maybe you want to fix that?
main.py can take a few options that are just passed as strings on the command line, in any order.
They might not all be compatible with each other, in any case main.py is only a
starting point to write your own set up code with a more serious model.
cpu
propagate using a simplistic CPU algorithm.
3d
work in a 3d space (4x4x2 by default), with a very rudimentary preview. more of a proof of concept, but totally workable.
In the 3d preview, the up and down keys can be used to cycle through slices of the Z axis.
silent
don't open a preview or render, just measure the execution time.
render
automatically step execution forward and take save a screenshot to shots/0001.png etc.
You can use e.g. ffmpeg to turn the png frames into an animation.
Programatic Usage
gpWFC is set up to follow a 'mix and match' modular architecture as best as possible.
It is therefore divided into a couple of components that need to be used to run a simulation:
- the Tiles (
TileandSpriteTilefrommodels.py):tile.weight(float): the relative probability of occurencetile.compatible(other, direction_id)(bool): constraint information- additional information for the Preview, e.g.
tile.imageandtile.rotationforSpriteTile
- the Model (
Model2dandModel3dfrommodels.py):- information about the world:
model.world_shape(tuple): dimensions of the world (any nr of axes)model.get_neighbours(pos)(generator): tile adjacency information
- information about the tiles:
model.tiles(list): the tiles to be usedmodel.get_allowed_tiles(bitmask)(list): a way to resolve the opaque bitmask
- information about the world:
- the Runner (
RunnerandBacktrackingRunnerfromrunners.py):runner.step()(string): execute a single observartion/propagation cyclerunner.finish()(string): run the simulation until it either fails or stabilizesrunner.run()(generator): iterate overrunner.step()- all of these return/yield status strings, which are one of:
'done'- fully collapsed'error'- overconstrained / stuck'continue'- step successful but uncollapsed tiles remain
- the Preview (
PreviewWindow*frompreviews.py):preview.draw_tiles(pos, bits): draw the tiles atpos(tuple)preview.launch(): enter interactive preview modepreview.render(): enter non-interactive render loop
- the Observer and Propagator (
observers.pyandpropagators.py):- you probably don't need to touch these
You can find a straightforward example of the basic setup steps in circuit.py, it should follow this flow:
- instantiate a Model
- instantiate Tiles and register them with the Model
- instantiate a Runner and pass it the Model
- instantiate a Preview and pass it the Runner
- launch the Preview
GPU-only rendering
There is a terribly broken glsl-render branch that tries to not ever get the buffer back to CPU memory during propagation,
while still rendering the world in a GLSL shader.
Unfortunately I could never get it to work properly with pyOpenCL to date, and due to some other constraints
I also cannot test or bring the current version back to the best state it was in,
so it will remain in a messy test state for now.
If anyone is brave enough to touch it though, when working, it should give some incredible performance gains as the rendering and memory transfer / gpu blocking are by far the biggest slow-downs at the moment. There is also some hope since a new version of pyOpenCL is apparently on the way.