Last active
May 17, 2024 19:46
-
-
Save partybusiness/13255ad293f8050394acfcb74ca75087 to your computer and use it in GitHub Desktop.
Conway's Game of Life implemented as a compute shader in Godot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[compute] | |
#version 450 | |
// Invocations in the (x, y, z) dimension | |
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; | |
// Current state of the Conway's game | |
layout(set = 0, binding = 0, std430) restrict buffer GameDataBuffer { | |
uint data[]; //32bits each | |
} | |
game_data; | |
// Texture size info | |
//layout(set = 0, binding = 1, std430) restrict buffer SizeDataBuffer { | |
// uint width; | |
// uint height; | |
//} | |
//size_data; | |
// The code we want to execute in each invocation | |
void main() { | |
//TODO instead of hard coding this, get it from size_data | |
uint width = 64; | |
uint height = 64; | |
uint rC = width / 32; //row count of how many uints are in each row | |
uint index = gl_GlobalInvocationID.x; // uniquely identifies this invocation across all work groups | |
uint source = game_data.data[index]; //source data from current uint | |
uint result = 0; | |
uint fs = height*rC; //full size of data | |
//all adjacent uints that we might need | |
uint below = game_data.data[(index+fs-rC)%(fs)]; | |
uint above = game_data.data[(index+rC)%(fs)]; | |
uint left = game_data.data[(index+1)%(fs)]; | |
uint right = game_data.data[(index-1)%(fs)]; | |
uint botleft = game_data.data[(index+fs-rC+1)%(fs)]; | |
uint botright = game_data.data[(index+fs-rC-1)%(fs)]; | |
uint topleft = game_data.data[(index+rC+1)%(fs)]; | |
uint topright = game_data.data[(index+fs+rC-1)%(fs)]; | |
//TODO maybe pull first and last bits out of the loop, for performance | |
for (int x =0;x<32;x++) { //loop through bits | |
uint count = 0; | |
if (x== 0) { //handle first bit as exception | |
count += ((above >> x) & 1) + ((above >> (x+1)) & 1) + ((topright >> 31 ) & 1); | |
count += ((below >> x) & 1) + ((below >> (x+1)) & 1) + ((botright >> 31 ) & 1); | |
count += ((source >> (x+1)) & 1) + ((right >> 31 ) & 1); | |
} else if (x==31) { //handle last bit as exception | |
count += ((above >> x) & 1) + ((above >> (x-1)) & 1)+ ((topleft >> 0 ) & 1); | |
count += ((below >> x) & 1) + ((below >> (x-1)) & 1)+ ((botleft >> 0 ) & 1); | |
count += ((source >> (x-1)) & 1) + ((left >> 0 ) & 1); | |
} else { //handle all bits in the middle | |
count += ((above >> x) & 1) + ((above >> (x+1)) & 1) + ((above >> (x-1)) & 1); | |
count += ((below >> x) & 1) + ((below >> (x+1)) & 1) + ((below >> (x-1)) & 1); | |
count += ((source >> (x+1)) & 1) + ((source >> (x-1)) & 1); | |
} | |
if ((count < 2) || (count > 3)) { | |
//leave it at 0 | |
result = result & ~(1 << x); | |
} else { | |
if (count == 3) { | |
result |= 1 << x; | |
} else { | |
result = result | (source & (1 << x)); | |
} | |
} | |
} | |
game_data.data[index] = result; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
@export var display_material:Material | |
@export var game_state:PackedByteArray | |
const width:int = 64 | |
const height:int = 64 | |
var rd := RenderingServer.create_local_rendering_device() | |
var buffer:RID | |
var pipeline:RID | |
var uniform_set:RID | |
var counter:float = 0.5 | |
var processing:bool = false; | |
func _ready(): | |
game_state.resize(floori(width*height)) | |
game_state.fill(0) | |
#randomizes initial state | |
for i in range(0, game_state.size()): | |
game_state[i] = randi()%256 & randi()%256 #approx one quarter will be filled | |
#sets up display material | |
display_material.set_shader_parameter("width", width) | |
display_material.set_shader_parameter("height", height) | |
display_material.set_shader_parameter("game_data", game_state) | |
#sets up compute shader | |
buffer = rd.storage_buffer_create(game_state.size(), game_state) | |
# Load GLSL shader | |
var shader_file:RDShaderFile = load("res://life/compute_life.glsl") | |
var shader_spirv:RDShaderSPIRV = shader_file.get_spirv() | |
var shader:RID = rd.shader_create_from_spirv(shader_spirv) | |
var uniform:RDUniform = RDUniform.new() | |
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER | |
uniform.binding = 0 # this needs to match the "binding" in our shader file | |
uniform.add_id(buffer) | |
uniform_set = rd.uniform_set_create([uniform], shader, 0) | |
# the last parameter (the 0) needs to match the "set" in our shader file | |
# Create a compute pipeline | |
pipeline = rd.compute_pipeline_create(shader) | |
run_tick() | |
func _process(delta): | |
if processing: # submitted last frame | |
rd.sync() | |
processing = false | |
game_state = rd.buffer_get_data(buffer) | |
#TODO should I convert game_state to PackedInt32Array to use less space? | |
display_material.set_shader_parameter("game_data", game_state) | |
counter-=delta | |
if counter<0: | |
counter += 0.1 | |
run_tick() | |
func run_tick(): | |
#run one tick of the game | |
var compute_list := rd.compute_list_begin() | |
rd.compute_list_bind_compute_pipeline(compute_list, pipeline) | |
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0) | |
rd.compute_list_dispatch(compute_list, width*height/256, 1, 1) | |
rd.compute_list_end() | |
rd.submit() | |
processing = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type canvas_item; | |
uniform uint game_data[512]; // 64x64 divided by 8 bits each | |
uniform uint screenWidth = 64; | |
uniform uint screenHeight = 64; | |
void fragment() { | |
float sw = float(screenWidth / 8u); //width in bytes | |
//TODO if we pass PackedInt32Array switch 8u to 32u | |
float sh = float(screenHeight); | |
uint px = uint(floor(UV.x*sw)); | |
uint py = uint(floor(UV.y*sh)); | |
uint index = px + py * screenWidth / 8u; | |
//get which bit within this uint we need | |
uint shift = uint(UV.x * float(screenWidth)) - px * 8u; | |
//get colours | |
uint sample = game_data[index]; | |
uint pixel = (sample >> shift) & 1u; | |
COLOR.rgb = vec3((float(pixel))); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment