Skip to content

Instantly share code, notes, and snippets.

@partybusiness
Last active August 7, 2024 23:07
Show Gist options
  • Save partybusiness/17321fb20c5d3b8429ea97acedf64717 to your computer and use it in GitHub Desktop.
Save partybusiness/17321fb20c5d3b8429ea97acedf64717 to your computer and use it in GitHub Desktop.
New version of Conway's Game of Life for Godot
#[compute]
#version 450
// Invocations in the (x, y, z) dimension
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(r32f, set = 0, binding = 0) uniform restrict image2D input_image;
layout(push_constant, std430) uniform Params {
uint width;
} size_data;
void main() {
uint width = size_data.width;
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
uint count = 0;
count += uint(round(imageLoad(input_image, ivec2(uv.x,(uv.y + 1)%width)).r));
count += uint(round(imageLoad(input_image, ivec2(uv.x,(uv.y - 1 + width)%width)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x + 1)%width,uv.y)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x - 1 + width)%width,uv.y)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x + 1)%width,(uv.y + 1)%width)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x - 1 + width)%width,(uv.y + 1)%width)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x - 1 + width)%width,(uv.y - 1 + width)%width)).r));
count += uint(round(imageLoad(input_image, ivec2((uv.x + 1)%width,(uv.y - 1 + width)%width)).r));
float result = 0.0;
if ((count < 2) || (count > 3)) {
} else {
if (count == 3) {
result = 1.0;
} else {
result = round(imageLoad(input_image, uv).r); //stays the same
}
}
imageStore(input_image, uv, vec4(result, 0.0, 0.0, 1.0));
}
#[compute]
#version 450
// Invocations in the (x, y, z) dimension
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(r32f, set = 0, binding = 0) uniform restrict image2D input_image;
layout(push_constant, std430) uniform Params {
uint tx;
uint ty;
} params;
// The code we want to execute in each invocation
void main() {
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
if (params.tx == uv.x && params.ty == uv.y)
imageStore(input_image, uv, vec4(1.0 - imageLoad(input_image, uv).r , 1.0, 1.0, 1.0));
}
extends TextureRect
@export var display_material:ShaderMaterial
var rd:RenderingDevice # = RenderingServer.create_local_rendering_device()
var buffer:RID
var pipeline:RID
var uniform_set:RID
var draw_pipeline:RID
var draw_uniform_set:RID
var counter:float = 0.5
var display_texture:Texture2DRD
var img_width:int = 64
var image_width_constant : PackedInt32Array
var test_mode:int = 0
var running:bool = true
func _ready():
rd = RenderingServer.get_rendering_device()
# Load GLSL shader
var compute_shader_file:RDShaderFile = load("res://life/compute_life.glsl")
var shader_spirv:RDShaderSPIRV = compute_shader_file.get_spirv()
var shader:RID = rd.shader_create_from_spirv(shader_spirv)
#create image width constant for compute shader
image_width_constant = PackedInt32Array()
image_width_constant.push_back(img_width)
image_width_constant.push_back(0) #padding
image_width_constant.push_back(0)
image_width_constant.push_back(0)
#create texture
var tf : RDTextureFormat = RDTextureFormat.new()
tf.format = RenderingDevice.DATA_FORMAT_R32_SFLOAT
tf.texture_type = RenderingDevice.TEXTURE_TYPE_2D
tf.width = img_width
tf.height = img_width
tf.depth = 1
tf.array_layers = 1
tf.mipmaps = 1
tf.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT
display_texture = Texture2DRD.new()
display_texture.texture_rd_rid = rd.texture_create(tf, RDTextureView.new(), [])
#assign texture to display
texture = display_texture
#get uniform with texture ready for shaders
var uniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
uniform.binding = 0
uniform.add_id(display_texture.texture_rd_rid)
uniform_set = rd.uniform_set_create([uniform], shader, 0)
# Create a compute pipeline
pipeline = rd.compute_pipeline_create(shader)
#set up drawing
var draw_shader:RID = rd.shader_create_from_spirv(load("res://life/draw_dot.glsl").get_spirv())
uniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
uniform.binding = 0
uniform.add_id(display_texture.texture_rd_rid)
draw_uniform_set = rd.uniform_set_create([uniform], draw_shader, 0)
draw_pipeline = rd.compute_pipeline_create(draw_shader)
#initial randomization
var random_shader:RID = rd.shader_create_from_spirv(load("res://life/randomize_life.glsl").get_spirv())
var randuniform = RDUniform.new()
randuniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
randuniform.binding = 0
randuniform.add_id(display_texture.texture_rd_rid)
var randuniform_set = rd.uniform_set_create([uniform], random_shader, 0)
var randpipeline = rd.compute_pipeline_create(random_shader)
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, randpipeline)
rd.compute_list_bind_uniform_set(compute_list, randuniform_set, 0)
rd.compute_list_dispatch(compute_list, img_width/8, img_width/8, 1)
rd.compute_list_end()
func _input(event):
if Input.is_action_just_pressed("Pause"):
running = !running
if !running && Input.is_action_just_pressed("Draw"):
var mouse_pos = get_viewport().get_mouse_position()
mouse_pos.x = int(mouse_pos.x * img_width / size.x)
mouse_pos.y = int(mouse_pos.y * img_width / size.y)
draw_dot(mouse_pos.x, mouse_pos.y)
if Input.is_action_just_pressed("Tick"):
run_tick()
func _process(delta):
if !running:
return
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
var mouse_pos = get_viewport().get_mouse_position()
mouse_pos.x = int(mouse_pos.x * img_width / size.x)
mouse_pos.y = int(mouse_pos.y * img_width / size.y)
draw_dot(mouse_pos.x, mouse_pos.y)
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_set_push_constant(compute_list, image_width_constant.to_byte_array(), image_width_constant.size() * 4)
rd.compute_list_dispatch(compute_list, img_width/8, img_width/8, 1)
rd.compute_list_end()
func draw_dot(x:int, y:int):
#flips the given pixel in the shader
var push_constant : PackedInt32Array = PackedInt32Array()
push_constant.push_back(x)
push_constant.push_back(y)
push_constant.push_back(0) #padding to multiple of 16? skipping these will result in error:
push_constant.push_back(0) # "this compute pipeline requires (16) bytes of push constant data"
# but it's clearly using only the first 8 bytes
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, draw_pipeline)
rd.compute_list_bind_uniform_set(compute_list, draw_uniform_set, 0)
rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4)
rd.compute_list_dispatch(compute_list, img_width/8, img_width/8, 1)
rd.compute_list_end()
shader_type canvas_item;
void fragment() {
COLOR.rgb = vec3(step(0.5,texture(TEXTURE,UV + 0.5).r));
}
#[compute]
#version 450
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(r32f, set = 0, binding = 0) uniform restrict image2D output_image;
float random (vec2 uv) {
return fract(sin(dot(uv.xy,
vec2(12.9898,78.233))) * 43758.5453123);
}
void main() {
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
float r = random(uv);
imageStore(output_image, uv, vec4(r, 0.8, 1.0, 1.0));
}
@partybusiness
Copy link
Author

I had done a Conway's before to learn about compute shaders, but then I heard about Texture2DRD which enables multiple shaders to reference the same texture just through RID, which means it can stay on the GPU. So I rewrote it to take advantage of that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment