Skip to content

Instantly share code, notes, and snippets.

@TheGag96
Created September 23, 2024 21:48
Show Gist options
  • Save TheGag96/d1a78d19a03b312336a703004c34053a to your computer and use it in GitHub Desktop.
Save TheGag96/d1a78d19a03b312336a703004c34053a to your computer and use it in GitHub Desktop.
GPU-accelerated Conway's Game of Life
Simp :: #import "Simp";
Input :: #import "Input";
#import "Basic";
#import "Random";
#import "Math";
#import "Window_Creation";
#import "GL";
Render_Texture :: struct {
using #as texutre: Simp.Texture;
fbo_handle: GLuint;
}
window: Window_Type;
window_width, window_height: int;
framebuffer_textures: [3] Render_Texture; // Our boards will be triple-buffered. Is double-buffering sufficient?
prev_buffer: int;
shader_game_of_life: Simp.Shader;
main :: () {
window_init();
quit := false;
while !quit {
Input.update_window_events();
for Input.get_window_resizes() {
Simp.update_window(it.window); // Simp will do nothing if it doesn't care about this window.
if it.window == window {
should_reinit := (it.width != window_width) || (it.height != window_height);
window_width = it.width;
window_height = it.height;
if should_reinit randomize_board();
}
}
for Input.events_this_frame {
if it.type == .QUIT then quit = true;
if it.type == {
case .KEYBOARD;
if it.key_pressed {
if it.key_code == .ESCAPE {
quit = true;
}
else if it.key_code == #char "R" {
randomize_board();
}
}
}
}
window_draw_frame();
}
}
GAME_OF_LIFE_SHADER :: #string END
OUT_IN vec2 TexCoords;
OUT_IN vec4 iterated_color;
#ifdef VERTEX_SHADER
in vec4 vert_position;
in vec4 vert_color;
in vec2 vert_uv0;
uniform mat4 projection;
void main() {
TexCoords = vec2(vert_uv0.x, 1.0-vert_uv0.y); // @Cleanup: Figure out what to do to avoid this vertical flip... sigh!
gl_Position = projection * vec4(vert_position.xy, 0.0, 1.0);
iterated_color = vert_color;
}
#endif // VERTEX_SHADER
#ifdef FRAGMENT_SHADER
out vec4 color;
uniform sampler2D diffuse_texture;
void main () {
vec2 tex_size = vec2(textureSize(diffuse_texture, 0));
vec2 one_pixel = vec2(1.0) / tex_size;
float neighbors =
texture(diffuse_texture, TexCoords + vec2( 1.0, 0.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2( 1.0, 1.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2( 0.0, 1.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2(-1.0, 1.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2(-1.0, 0.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2(-1.0, -1.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2( 0.0, -1.0) * one_pixel).r +
texture(diffuse_texture, TexCoords + vec2( 1.0, -1.0) * one_pixel).r;
float this_cell = texture(diffuse_texture, TexCoords).r;
float value;
if (this_cell > 0.0) {
// Alive: Must be 2 or 3 neighbors to stay alive
value = step(2.0, neighbors) * (1.0 - step(4.0, neighbors));
}
else {
// Dead: Must have 3 neighbors to become alive
value = float(neighbors == 3.0);
}
color = vec4(value, value, value, 1.0);
}
#endif // FRAGMENT_SHADER
END
window_init :: () {
#if OS == .WINDOWS {
// Windows.SetProcessDPIAware();
// Windows is very bad at thread-switching by default unless you do this. Sad.
Windows :: #import "Windows";
Windows.timeBeginPeriod(1);
}
window_width, window_height = 1920, 1080;
window = create_window(window_width, window_height, "Game of Life");
window_width, window_height = Simp.get_render_dimensions(window);
Simp.set_render_target(window);
// Reserve space for front/back buffer textures
randomize_board();
for * framebuffer_textures {
// @Hack: Simp currently has no way to let me change texture filtering without doing this directly.
glBindTexture(GL_TEXTURE_2D, it.gl_handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glGenFramebuffers(1, *it.fbo_handle);
glBindFramebuffer(GL_FRAMEBUFFER, it.fbo_handle);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, it.gl_handle, 0);
DRAW_BUFFERS :: GLenum.[GL_COLOR_ATTACHMENT0];
glDrawBuffers(1, DRAW_BUFFERS.data);
}
shader_game_of_life.gl_handle = get_shader_program(GAME_OF_LIFE_SHADER);
shader_game_of_life.alpha_blend = false;
}
randomize_board :: () {
auto_release_temp();
initial_frame: [..] u8;
initial_frame.allocator = temporary_allocator;
array_resize(*initial_frame, window_width * window_height * 4);
for 0..window_width*window_height-1 {
value: u8 = xx (0xFF * (random_get() % 2));
initial_frame[it * 4 + 0] = value;
initial_frame[it * 4 + 1] = value;
initial_frame[it * 4 + 2] = value;
initial_frame[it * 4 + 3] = 0xFF;
}
bitmap: Simp.Bitmap = .{
width = xx window_width,
height = xx window_height,
format = .RGBA8,
};
for * framebuffer_textures {
if it_index == prev_buffer {
bitmap.data = initial_frame;
}
else {
bitmap.data = .[];
}
Simp.texture_load_from_bitmap(it, *bitmap);
}
}
window_draw_frame :: () {
new_buffer := (prev_buffer + 1) % framebuffer_textures.count;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_textures[new_buffer].fbo_handle);
glViewport(0, 0, xx window_width, xx window_height);
set_shader_for_game_of_life(*framebuffer_textures[prev_buffer]);
Simp.immediate_begin();
Simp.immediate_quad(
.{0, cast(float) window_height},
.{cast(float) window_width, cast(float) window_height},
.{cast(float) window_width, 0},
.{0, 0},
);
Simp.immediate_flush();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, xx window_width, xx window_height);
Simp.set_shader_for_images(*framebuffer_textures[new_buffer]);
Simp.immediate_begin();
Simp.immediate_quad(
.{0, cast(float) window_height},
.{cast(float) window_width, cast(float) window_height},
.{cast(float) window_width, 0},
.{0, 0},
);
Simp.immediate_flush();
Simp.swap_buffers(window, vsync = true); // @TODO: Proper frame-limit
prev_buffer = new_buffer;
reset_temporary_storage();
}
// Copied from Simp/shader.jai, since it was #scope_module.
set_shader_for_game_of_life :: (texture: *Simp.Texture) {
if !context.simp context.simp = New(Simp.Immediate_State);
state := context.simp;
#if Simp.render_api == .METAL {
Simp.init_metal();
}
Simp.immediate_flush();
if state.current_shader != *shader_game_of_life {
Simp.immediate_set_shader(*shader_game_of_life);
Simp.set_projection();
}
Simp.immediate_set_texture(texture);
}
// Copied from Simp/backend/gl.jai, since it was #scope_file.
get_shader_program :: (shader_text: string) -> GLuint {
LOG_BUFFER_SIZE :: 512;
make_shader_object :: (shader: string, prefix: string, shader_type : GLenum) -> GLuint {
shader_object := glCreateShader(shader_type);
//shader_str := temp_c_string(shader);
shaders: [2] *u8;
lengths: [2] s32;
shaders[0] = prefix.data;
shaders[1] = shader.data;
lengths[0] = xx prefix.count;
lengths[1] = xx shader.count;
glShaderSource(shader_object, 2, shaders.data, lengths.data);
glCompileShader(shader_object);
success : GLint;
glGetShaderiv(shader_object, GL_COMPILE_STATUS, *success);
if !success then {
log_data: [LOG_BUFFER_SIZE] u8;
glGetShaderInfoLog(shader_object, log_data.count, null, log_data.data);
log("%", to_string(log_data.data), flags=.ERROR);
return 0;
}
return shader_object;
}
// @MODIFIED GLES has a different version directive and needs to specify float precision.
#if OS == .ANDROID {
PREFIX_V :: #string END
#version 300 es
#define VERTEX_SHADER
#define OUT_IN out
precision mediump float;
END
PREFIX_F :: #string END
#version 300 es
#define FRAGMENT_SHADER
#define OUT_IN in
precision mediump float;
END
} else {
PREFIX_V :: #string END
#version 330 core
#define VERTEX_SHADER
#define OUT_IN out
END
PREFIX_F :: #string END
#version 330 core
#define FRAGMENT_SHADER
#define OUT_IN in
END
}
shader_v := make_shader_object(shader_text, PREFIX_V, GL_VERTEX_SHADER);
shader_f := make_shader_object(shader_text, PREFIX_F, GL_FRAGMENT_SHADER);
shader_p := glCreateProgram();
glAttachShader(shader_p, shader_v);
glAttachShader(shader_p, shader_f);
glLinkProgram(shader_p);
success : GLint = 0;
glGetProgramiv(shader_p, GL_LINK_STATUS, *success);
if !success then {
log_data: [LOG_BUFFER_SIZE] u8;
glGetProgramInfoLog(shader_p, log_data.count, null, log_data.data);
log_error("%", to_string(log_data.data));
return 0;
}
glDeleteShader(shader_v);
glDeleteShader(shader_f);
return shader_p;
}
#run {
#if OS == .WINDOWS {
WR :: #import "Windows_Resources";
WR.disable_runtime_console();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment