Compare commits

..

4 Commits

Author SHA1 Message Date
Andrew Cohn
8305dcf10d disabled vsync for moar frames 2025-11-29 14:08:35 -08:00
Andrew Cohn
7045273c39 yeah boy this sand be fallin 2025-11-29 13:59:31 -08:00
Andrew Cohn
61adffe061 query window size every frame so we have something that makes a little bit of sense 2025-11-29 12:39:05 -08:00
Andrew Cohn
8885e6a7c3 babies first gpu code 2025-11-29 12:29:08 -08:00
15 changed files with 581 additions and 113 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
.vscode
*.o

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"makefile.launchConfigurations": [
{
"name": "sand_mangohud",
"cwd": "${workspaceFolder}",
"binaryPath": "/usr/bin/mangohud", // use full path to be safe
"binaryArgs": [
"--dlsym",
"${workspaceFolder}/fallingCand"
]
}
]
}

View File

@ -1,13 +1,12 @@
# Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags glfw3)
LDFLAGS = $(shell pkg-config --libs glfw3) -ldl
SRC = main.c glad.c
SRC = glad.c gl_utils.c main.c sand_sim.c
OBJ = $(SRC:.c=.o)
BIN = fallingCand
all: $(BIN)
all: clean $(BIN)
$(BIN): $(OBJ)
$(CC) $(OBJ) -o $@ $(LDFLAGS)

Binary file not shown.

180
gl_utils.c Normal file
View File

@ -0,0 +1,180 @@
// gl_utils.c
#include "gl_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
void glfw_error_callback(int error, const char* description) {
fprintf(stderr, "GLFW error %d: %s\n", error, description);
}
char* load_text_file(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s': %s\n", path, strerror(errno));
return NULL;
}
if (fseek(f, 0, SEEK_END) != 0) {
fprintf(stderr, "fseek failed on '%s'\n", path);
fclose(f);
return NULL;
}
long size = ftell(f);
if (size < 0) {
fprintf(stderr, "ftell failed on '%s'\n", path);
fclose(f);
return NULL;
}
rewind(f);
char* buffer = malloc((size_t)size + 1);
if (!buffer) {
fprintf(stderr, "Out of memory reading '%s'\n", path);
fclose(f);
return NULL;
}
size_t read = fread(buffer, 1, (size_t)size, f);
fclose(f);
if (read != (size_t)size) {
fprintf(stderr, "Short read on '%s'\n", path);
free(buffer);
return NULL;
}
buffer[size] = '\0';
return buffer;
}
GLuint compile_shader(GLenum type, const char* source, const char* debugName) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char log[1024];
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
fprintf(stderr, "Shader compile failed (%s):\n%s\n", debugName ? debugName : "unknown", log);
glDeleteShader(shader);
exit(EXIT_FAILURE);
}
return shader;
}
GLuint create_program_from_files(const char* vsPath, const char* fsPath) {
char* vsSource = load_text_file(vsPath);
if (!vsSource) {
fprintf(stderr, "Could not load vertex shader from '%s'\n", vsPath);
exit(EXIT_FAILURE);
}
char* fsSource = load_text_file(fsPath);
if (!fsSource) {
fprintf(stderr, "Could not load fragment shader from '%s'\n", fsPath);
free(vsSource);
exit(EXIT_FAILURE);
}
GLuint vs = compile_shader(GL_VERTEX_SHADER, vsSource, vsPath);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fsSource, fsPath);
free(vsSource);
free(fsSource);
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glDeleteShader(vs);
glDeleteShader(fs);
GLint linked = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &linked);
if (!linked) {
char log[1024];
glGetProgramInfoLog(prog, sizeof(log), NULL, log);
fprintf(stderr, "Program link failed:\n%s\n", log);
glDeleteProgram(prog);
exit(EXIT_FAILURE);
}
return prog;
}
GLuint create_compute_program_from_file(const char* path) {
char* source = load_text_file(path);
if (!source) {
fprintf(stderr, "Could not load compute shader from '%s'\n", path);
exit(EXIT_FAILURE);
}
// Reuse compile_shader; type is GL_COMPUTE_SHADER
GLuint cs = compile_shader(GL_COMPUTE_SHADER, source, path);
free(source);
GLuint prog = glCreateProgram();
glAttachShader(prog, cs);
glLinkProgram(prog);
glDeleteShader(cs);
GLint linked = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &linked);
if (!linked) {
char log[1024];
glGetProgramInfoLog(prog, sizeof(log), NULL, log);
fprintf(stderr, "Compute program link failed (%s):\n%s\n", path, log);
glDeleteProgram(prog);
exit(EXIT_FAILURE);
}
return prog;
}
GLFWwindow* init_glfw_glad(const char* title, int width, int height) {
glfwSetErrorCallback(glfw_error_callback);
// Prefer Wayland if this GLFW build supports it
if (glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) {
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
fprintf(stderr, "[info] Requesting Wayland platform via glfwInitHint\n");
} else {
fprintf(stderr, "[warn] Wayland platform not supported by this GLFW build, using default\n");
}
if (!glfwInit()) {
fprintf(stderr, "[fatal] glfwInit failed\n");
return NULL;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL);
if (!window) {
fprintf(stderr, "[fatal] glfwCreateWindow failed\n");
glfwTerminate();
return NULL;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
fprintf(stderr, "[fatal] Failed to initialize GLAD\n");
glfwDestroyWindow(window);
glfwTerminate();
return NULL;
}
glfwSwapInterval(0);
return window;
}

27
gl_utils.h Normal file
View File

@ -0,0 +1,27 @@
// gl_utils.h
#ifndef GL_UTILS_H
#define GL_UTILS_H
#include "glad/glad.h"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
// Error callback for GLFW
void glfw_error_callback(int error, const char* description);
// Read an entire text file into a null-terminated buffer.
// Caller must free() the returned pointer.
char* load_text_file(const char* path);
// Compile a single shader from source.
GLuint compile_shader(GLenum type, const char* source, const char* debugName);
// Compile & link a vertex+fragment program from files.
GLuint create_program_from_files(const char* vsPath, const char* fsPath);
GLuint create_compute_program_from_file(const char* path);
// Minimal GLFW+GLAD init: sets error callback, (optionally) hints Wayland,
// calls glfwInit, creates window, makes context current, loads GLAD.
// Returns the created window or NULL on fatal error.
GLFWwindow* init_glfw_glad(const char* title, int width, int height);
#endif // GL_UTILS_H

212
main.c
View File

@ -1,142 +1,132 @@
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "glad/glad.h"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "gl_utils.h"
#include "sand_sim.h"
#define GRID_W 2048
#define GRID_H 2048
static int g_fbWidth = 800;
static int g_fbHeight = 800;
SandSim sim;
static void glfw_error_callback(int error, const char* description) {
fprintf(stderr, "GLFW error %d: %s\n", error, description);
static void render_sand(SandSim* sim,
GLuint program,
GLuint vao,
int fbW,
int fbH) {
glUseProgram(program);
GLint uResLoc = glGetUniformLocation(program, "u_resolution");
GLint uGridLoc = glGetUniformLocation(program, "u_gridSize");
GLint uStateLoc = glGetUniformLocation(program, "u_state");
if (uResLoc >= 0) {
glUniform2f(uResLoc, (float)fbW, (float)fbH);
}
if (uGridLoc >= 0) {
glUniform2i(uGridLoc, sim->gridW, sim->gridH);
}
static GLuint create_compute_program(const char* source) {
GLint success = 0;
GLchar log[1024];
GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, sizeof(log), NULL, log);
fprintf(stderr, "Compute shader compilation failed:\n%s\n", log);
exit(EXIT_FAILURE);
if (uStateLoc >= 0) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
glUniform1i(uStateLoc, 0);
}
GLuint program = glCreateProgram();
glAttachShader(program, shader);
glLinkProgram(program);
glDeleteShader(shader);
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, sizeof(log), NULL, log);
fprintf(stderr, "Program link failed:\n%s\n", log);
exit(EXIT_FAILURE);
}
return program;
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
int main(void) {
// --- Init GLFW & create GL context ---
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
fprintf(stderr, "Failed to init GLFW\n");
return EXIT_FAILURE;
}
// Request OpenGL 4.3 core (needed for compute shaders)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// We don't actually *need* to show the window
GLFWwindow* window = glfwCreateWindow(640, 480, "GPU Hello", NULL, NULL);
GLFWwindow* window = init_glfw_glad("Falling Sand - Fullscreen Quad", g_fbWidth, g_fbHeight);
if (!window) {
fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate();
return EXIT_FAILURE;
}
glfwGetFramebufferSize(window, &g_fbWidth, &g_fbHeight);
glViewport(0, 0, g_fbWidth, g_fbHeight);
printf("Renderer: %s\n", (const char*)glGetString(GL_RENDERER));
printf("OpenGL: %s\n", (const char*)glGetString(GL_VERSION));
printf("GLSL: %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
glfwMakeContextCurrent(window);
// --- Create fullscreen triangle geometry (VAO + VBO) ---
float vertices[] = {
// x, y
-1.0f, -1.0f,
3.0f, -1.0f,
-1.0f, 3.0f};
// --- Load GL symbols via GLAD ---
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
fprintf(stderr, "Failed to initialize GLAD\n");
glfwDestroyWindow(window);
glfwTerminate();
return EXIT_FAILURE;
}
GLuint vao = 0, vbo = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
printf("OpenGL version: %s\n", glGetString(GL_VERSION));
printf("GLSL version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
glBindVertexArray(vao);
// --- Prepare data buffer on GPU (SSBO) ---
const GLuint N = 16;
GLuint initial[N];
for (GLuint i = 0; i < N; ++i) {
initial[i] = i;
}
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
GLuint ssbo = 0;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(initial), initial, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo); // binding = 0 in shader
// --- Compute shader source: data[i] *= 2 ---
const char* compute_src =
"#version 430 core\n"
"layout(local_size_x = 16) in;\n"
"layout(std430, binding = 0) buffer Data {\n"
" uint data[];\n"
"};\n"
"void main() {\n"
" uint idx = gl_GlobalInvocationID.x;\n"
" // N == 16 and local_size_x == 16, so we know idx < 16\n"
" data[idx] *= 2u;\n"
"}\n";
GLuint compute_program = create_compute_program(compute_src);
// --- Run the compute shader ---
glUseProgram(compute_program);
// One workgroup of 16 threads (matches local_size_x)
glDispatchCompute(1, 1, 1);
// Make sure writes to the SSBO are visible to the CPU
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
// --- Read back the result ---
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
GLuint* ptr = (GLuint*)glMapBufferRange(
GL_SHADER_STORAGE_BUFFER,
// layout(location = 0) in vec2 aPos;
glEnableVertexAttribArray(0);
glVertexAttribPointer(
0,
sizeof(initial),
GL_MAP_READ_BIT
);
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(float),
(void*)0);
if (!ptr) {
fprintf(stderr, "Failed to map SSBO\n");
} else {
printf("GPU computed values:\n");
for (GLuint i = 0; i < N; ++i) {
printf(" %2u -> %2u\n", i, ptr[i]);
glBindVertexArray(0);
if (!sand_init(&sim, GRID_W, GRID_H, "shaders/sand_step.comp")) {
fprintf(stderr, "Failed to initialize sand sim\n");
return EXIT_FAILURE;
}
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
// --- Create shader program ---
GLuint program = create_program_from_files(
"shaders/fullscreen.vert",
"shaders/sand_display.frag");
glUseProgram(program);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
double sim_dt = 1.0 / 360.0;
double currentTime = glfwGetTime();
double accumulator = 0.0;
// --- Main loop ---
while (!glfwWindowShouldClose(window)) {
double newTime = glfwGetTime();
double frameTime = newTime - currentTime;
currentTime = newTime;
if (frameTime > 0.25) frameTime = 0.25;
accumulator += frameTime;
while (accumulator >= sim_dt) {
sand_step_gpu(&sim); // one discrete CA step on the GPU (commented bc unimplemeted)
accumulator -= sim_dt;
}
glfwPollEvents();
// Re-query framebuffer size each frame (cheap and simple)
int fbw, fbh;
glfwGetFramebufferSize(window, &fbw, &fbh);
if (fbw != g_fbWidth || fbh != g_fbHeight) {
g_fbWidth = fbw;
g_fbHeight = fbh;
glViewport(0, 0, g_fbWidth, g_fbHeight);
}
glClear(GL_COLOR_BUFFER_BIT);
render_sand(&sim, program, vao, g_fbWidth, g_fbHeight);
glfwSwapBuffers(window);
}
// --- Cleanup ---
glDeleteProgram(compute_program);
glDeleteBuffers(1, &ssbo);
glDeleteProgram(program);
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
glfwDestroyWindow(window);
glfwTerminate();
return EXIT_SUCCESS;
}

BIN
sand_hello Executable file

Binary file not shown.

134
sand_sim.c Normal file
View File

@ -0,0 +1,134 @@
// sand_sim.c
#include "sand_sim.h"
#include "gl_utils.h" // for create_compute_program_from_file
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static void init_textures(SandSim* sim) {
// tex_curr
glGenTextures(1, &sim->tex_curr);
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8UI, sim->gridW, sim->gridH);
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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// tex_next
glGenTextures(1, &sim->tex_next);
glBindTexture(GL_TEXTURE_2D, sim->tex_next);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8UI, sim->gridW, sim->gridH);
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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Unbind
glBindTexture(GL_TEXTURE_2D, 0);
}
static void upload_initial_state(SandSim* sim) {
size_t count = (size_t)sim->gridW * (size_t)sim->gridH;
uint8_t* data = calloc(count, 1);
if (!data) {
fprintf(stderr, "[sand_init] Out of memory for initial grid\n");
exit(EXIT_FAILURE);
}
// Seed RNG once (safe enough for now)
static bool seeded = false;
if (!seeded) {
seeded = true;
srand((unsigned int)time(NULL));
}
// Fill top 1/3 of grid with random sand vs air
for (int y = 0; y < sim->gridH / 3; ++y) {
for (int x = 0; x < sim->gridW; ++x) {
data[(size_t)y * sim->gridW + x] = (rand() & 1) ? 1 : 0; // 0=air,1=sand
}
}
// Upload into tex_curr and tex_next
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sim->gridW, sim->gridH,
GL_RED_INTEGER, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, sim->tex_next);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sim->gridW, sim->gridH,
GL_RED_INTEGER, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
free(data);
}
bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath) {
if (!sim || gridW <= 0 || gridH <= 0 || !computeShaderPath) {
fprintf(stderr, "[sand_init] Invalid arguments\n");
return false;
}
sim->gridW = gridW;
sim->gridH = gridH;
sim->tex_curr = 0;
sim->tex_next = 0;
sim->prog_sim = 0;
init_textures(sim);
upload_initial_state(sim);
sim->prog_sim = create_compute_program_from_file(computeShaderPath);
if (!sim->prog_sim) {
fprintf(stderr, "[sand_init] Failed to create compute program\n");
return false;
}
return true;
}
void sand_step_gpu(SandSim* sim) {
if (!sim || !sim->prog_sim) return;
glUseProgram(sim->prog_sim);
// Set grid size uniform
GLint uGridLoc = glGetUniformLocation(sim->prog_sim, "u_gridSize");
if (uGridLoc >= 0) {
glUniform2i(uGridLoc, sim->gridW, sim->gridH);
}
// Bind images (must match bindings in sand_step.comp)
glBindImageTexture(0, sim->tex_curr, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8UI);
glBindImageTexture(1, sim->tex_next, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R8UI);
GLuint groupsX = (sim->gridW + 15) / 16;
GLuint groupsY = (sim->gridH + 15) / 16;
glDispatchCompute(groupsX, groupsY, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
// Ping-pong
GLuint tmp = sim->tex_curr;
sim->tex_curr = sim->tex_next;
sim->tex_next = tmp;
}
void sand_destroy(SandSim* sim) {
if (!sim) return;
if (sim->tex_curr) {
glDeleteTextures(1, &sim->tex_curr);
sim->tex_curr = 0;
}
if (sim->tex_next) {
glDeleteTextures(1, &sim->tex_next);
sim->tex_next = 0;
}
if (sim->prog_sim) {
glDeleteProgram(sim->prog_sim);
sim->prog_sim = 0;
}
}

27
sand_sim.h Normal file
View File

@ -0,0 +1,27 @@
// sand_sim.h
#ifndef SAND_SIM_H
#define SAND_SIM_H
#include <stdbool.h>
#include "glad/glad.h"
// Simple binary sand vs air simulation
typedef struct {
GLuint tex_curr; // R8UI; 0 = air, 1 = sand
GLuint tex_next; // R8UI; ping-pong target
GLuint prog_sim; // compute shader program
int gridW;
int gridH;
} SandSim;
// Initialize sim, allocate textures, upload initial state, load compute shader.
// Returns true on success, false on failure.
bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath);
// Advance simulation by 1 discrete tick (one CA step).
void sand_step_gpu(SandSim* sim);
// Destroy all GL objects owned by the sim.
void sand_destroy(SandSim* sim);
#endif // SAND_SIM_H

12
shaders/double.comp Normal file
View File

@ -0,0 +1,12 @@
#version 460 core
layout(local_size_x = 16) in;
layout(std430, binding = 0) buffer Data {
uint data[];
};
void main() {
uint idx = gl_GlobalInvocationID.x;
data[idx] *= 2u;
}

8
shaders/fullscreen.vert Normal file
View File

@ -0,0 +1,8 @@
#version 430 core
layout(location = 0) in vec2 aPos;
void main()
{
gl_Position = vec4(aPos, 0.0, 1.0);
}

11
shaders/gradient.frag Normal file
View File

@ -0,0 +1,11 @@
#version 430 core
out vec4 FragColor;
uniform vec2 u_resolution;
void main()
{
// Normalized coordinates in [0,1]
vec2 uv = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(uv.x, uv.y, 0.2);
FragColor = vec4(color, 1.0);
}

26
shaders/sand_display.frag Normal file
View File

@ -0,0 +1,26 @@
#version 430 core
out vec4 FragColor;
uniform vec2 u_resolution; // framebuffer size in pixels
uniform ivec2 u_gridSize; // sand grid size
uniform usampler2D u_state; // GL_R8UI texture
void main() {
vec2 fragCoord = gl_FragCoord.xy;
// Normalized coordinates
vec2 uv = fragCoord / u_resolution;
uv.y = 1.0 - uv.y;
// Map to grid coordinates
ivec2 cell = ivec2(uv * vec2(u_gridSize));
cell = clamp(cell, ivec2(0), u_gridSize - ivec2(1));
uint v = texelFetch(u_state, cell, 0).r;
vec3 color = (v == 1u)
? vec3(0.9, 0.8, 0.1) // sand
: vec3(0.05, 0.05, 0.10); // background
FragColor = vec4(color, 1.0);
}

42
shaders/sand_step.comp Normal file
View File

@ -0,0 +1,42 @@
#version 430 core
layout(local_size_x = 16, local_size_y = 16) in;
layout(r8ui, binding = 0) readonly uniform uimage2D state_curr;
layout(r8ui, binding = 1) writeonly uniform uimage2D state_next;
uniform ivec2 u_gridSize;
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
if (pos.x >= u_gridSize.x || pos.y >= u_gridSize.y) {
return;
}
const uint AIR = 0u;
const uint SAND = 1u;
uint self = imageLoad(state_curr, pos).r;
bool canFallDown =
(self == SAND) &&
(pos.y + 1 < u_gridSize.y) &&
(imageLoad(state_curr, pos + ivec2(0, 1)).r == AIR);
bool filledFromAbove =
(self == AIR) &&
(pos.y > 0) &&
(imageLoad(state_curr, pos + ivec2(0, -1)).r == SAND);
uint next;
if (canFallDown) {
next = AIR;
} else if (filledFromAbove) {
next = SAND;
} else {
next = self;
}
imageStore(state_next, pos, uvec4(next, 0u, 0u, 0u));
}