yeah boy this sand be fallin
This commit is contained in:
parent
61adffe061
commit
7045273c39
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
.vscode
|
|
||||||
*.o
|
*.o
|
||||||
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ CC = gcc
|
|||||||
CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags glfw3)
|
CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags glfw3)
|
||||||
LDFLAGS = $(shell pkg-config --libs glfw3) -ldl
|
LDFLAGS = $(shell pkg-config --libs glfw3) -ldl
|
||||||
|
|
||||||
SRC = glad.c gl_utils.c main.c
|
SRC = glad.c gl_utils.c main.c sand_sim.c
|
||||||
OBJ = $(SRC:.c=.o)
|
OBJ = $(SRC:.c=.o)
|
||||||
BIN = fallingCand
|
BIN = fallingCand
|
||||||
|
|
||||||
|
|||||||
BIN
fallingCand
BIN
fallingCand
Binary file not shown.
28
gl_utils.c
28
gl_utils.c
@ -110,6 +110,34 @@ GLuint create_program_from_files(const char* vsPath, const char* fsPath) {
|
|||||||
|
|
||||||
return prog;
|
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) {
|
GLFWwindow* init_glfw_glad(const char* title, int width, int height) {
|
||||||
glfwSetErrorCallback(glfw_error_callback);
|
glfwSetErrorCallback(glfw_error_callback);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ GLuint compile_shader(GLenum type, const char* source, const char* debugName);
|
|||||||
|
|
||||||
// Compile & link a vertex+fragment program from files.
|
// Compile & link a vertex+fragment program from files.
|
||||||
GLuint create_program_from_files(const char* vsPath, const char* fsPath);
|
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,
|
// Minimal GLFW+GLAD init: sets error callback, (optionally) hints Wayland,
|
||||||
// calls glfwInit, creates window, makes context current, loads GLAD.
|
// calls glfwInit, creates window, makes context current, loads GLAD.
|
||||||
// Returns the created window or NULL on fatal error.
|
// Returns the created window or NULL on fatal error.
|
||||||
|
|||||||
67
main.c
67
main.c
@ -3,8 +3,40 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "gl_utils.h"
|
#include "gl_utils.h"
|
||||||
|
#include "sand_sim.h"
|
||||||
|
#define GRID_W 1024
|
||||||
|
#define GRID_H 1024
|
||||||
static int g_fbWidth = 800;
|
static int g_fbWidth = 800;
|
||||||
static int g_fbHeight = 800;
|
static int g_fbHeight = 800;
|
||||||
|
SandSim sim;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uStateLoc >= 0) {
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
|
||||||
|
glUniform1i(uStateLoc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
GLFWwindow* window = init_glfw_glad("Falling Sand - Fullscreen Quad", g_fbWidth, g_fbHeight);
|
GLFWwindow* window = init_glfw_glad("Falling Sand - Fullscreen Quad", g_fbWidth, g_fbHeight);
|
||||||
@ -44,22 +76,37 @@ int main(void) {
|
|||||||
(void*)0);
|
(void*)0);
|
||||||
|
|
||||||
glBindVertexArray(0);
|
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;
|
||||||
|
}
|
||||||
// --- Create shader program ---
|
// --- Create shader program ---
|
||||||
GLuint program = create_program_from_files(
|
GLuint program = create_program_from_files(
|
||||||
"shaders/fullscreen.vert",
|
"shaders/fullscreen.vert",
|
||||||
"shaders/gradient.frag");
|
"shaders/sand_display.frag");
|
||||||
GLint uResLoc = glGetUniformLocation(program, "u_resolution");
|
|
||||||
if (uResLoc == -1) {
|
|
||||||
fprintf(stderr, "[warn] u_resolution uniform not found (maybe optimized out?)\n");
|
|
||||||
}
|
|
||||||
glUseProgram(program);
|
glUseProgram(program);
|
||||||
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
double sim_dt = 1.0 / 120.0;
|
||||||
|
double currentTime = glfwGetTime();
|
||||||
|
double accumulator = 0.0;
|
||||||
|
|
||||||
// --- Main loop ---
|
// --- Main loop ---
|
||||||
while (!glfwWindowShouldClose(window)) {
|
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();
|
glfwPollEvents();
|
||||||
|
|
||||||
// Re-query framebuffer size each frame (cheap and simple)
|
// Re-query framebuffer size each frame (cheap and simple)
|
||||||
int fbw, fbh;
|
int fbw, fbh;
|
||||||
glfwGetFramebufferSize(window, &fbw, &fbh);
|
glfwGetFramebufferSize(window, &fbw, &fbh);
|
||||||
@ -70,13 +117,7 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
glUseProgram(program);
|
render_sand(&sim, program, vao, g_fbWidth, g_fbHeight);
|
||||||
if (uResLoc != -1) {
|
|
||||||
glUniform2f(uResLoc, (float)g_fbWidth, (float)g_fbHeight);
|
|
||||||
}
|
|
||||||
glBindVertexArray(vao);
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
||||||
|
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
134
sand_sim.c
Normal file
134
sand_sim.c
Normal 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
27
sand_sim.h
Normal 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
|
||||||
26
shaders/sand_display.frag
Normal file
26
shaders/sand_display.frag
Normal 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
42
shaders/sand_step.comp
Normal 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));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user