Compare commits
No commits in common. "maverick" and "main" have entirely different histories.
BIN
fallingCand
BIN
fallingCand
Binary file not shown.
52
main.c
52
main.c
@ -8,32 +8,18 @@
|
|||||||
#define GRID_H 2048
|
#define GRID_H 2048
|
||||||
static int g_fbWidth = 800;
|
static int g_fbWidth = 800;
|
||||||
static int g_fbHeight = 800;
|
static int g_fbHeight = 800;
|
||||||
static double g_cursorX = 0.0;
|
|
||||||
static double g_cursorY = 0.0;
|
|
||||||
SandSim sim;
|
SandSim sim;
|
||||||
|
|
||||||
static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
|
|
||||||
(void)window;
|
|
||||||
g_cursorX = xpos;
|
|
||||||
g_cursorY = ypos;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void render_sand(SandSim* sim,
|
static void render_sand(SandSim* sim,
|
||||||
GLuint program,
|
GLuint program,
|
||||||
GLuint vao,
|
GLuint vao,
|
||||||
int fbW,
|
int fbW,
|
||||||
int fbH,
|
int fbH) {
|
||||||
float brushX,
|
|
||||||
float brushY,
|
|
||||||
float brushRadius) {
|
|
||||||
glUseProgram(program);
|
glUseProgram(program);
|
||||||
|
|
||||||
GLint uResLoc = glGetUniformLocation(program, "u_resolution");
|
GLint uResLoc = glGetUniformLocation(program, "u_resolution");
|
||||||
GLint uGridLoc = glGetUniformLocation(program, "u_gridSize");
|
GLint uGridLoc = glGetUniformLocation(program, "u_gridSize");
|
||||||
GLint uStateLoc = glGetUniformLocation(program, "u_state");
|
GLint uStateLoc = glGetUniformLocation(program, "u_state");
|
||||||
GLint uBrushPosLoc = glGetUniformLocation(program, "u_brushPos");
|
|
||||||
GLint uBrushRadLoc = glGetUniformLocation(program, "u_brushRadius");
|
|
||||||
GLint uShowBrushLoc = glGetUniformLocation(program, "u_showBrush");
|
|
||||||
|
|
||||||
if (uResLoc >= 0) {
|
if (uResLoc >= 0) {
|
||||||
glUniform2f(uResLoc, (float)fbW, (float)fbH);
|
glUniform2f(uResLoc, (float)fbW, (float)fbH);
|
||||||
@ -41,22 +27,13 @@ static void render_sand(SandSim* sim,
|
|||||||
if (uGridLoc >= 0) {
|
if (uGridLoc >= 0) {
|
||||||
glUniform2i(uGridLoc, sim->gridW, sim->gridH);
|
glUniform2i(uGridLoc, sim->gridW, sim->gridH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uStateLoc >= 0) {
|
if (uStateLoc >= 0) {
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
|
glBindTexture(GL_TEXTURE_2D, sim->tex_curr);
|
||||||
glUniform1i(uStateLoc, 0);
|
glUniform1i(uStateLoc, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uBrushPosLoc >= 0) {
|
|
||||||
glUniform2f(uBrushPosLoc, brushX, brushY);
|
|
||||||
}
|
|
||||||
if (uBrushRadLoc >= 0) {
|
|
||||||
glUniform1f(uBrushRadLoc, brushRadius);
|
|
||||||
}
|
|
||||||
if (uShowBrushLoc >= 0) {
|
|
||||||
glUniform1i(uShowBrushLoc, 1); // always show for now
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindVertexArray(vao);
|
glBindVertexArray(vao);
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
}
|
}
|
||||||
@ -66,7 +43,6 @@ int main(void) {
|
|||||||
if (!window) {
|
if (!window) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
glfwSetCursorPosCallback(window, cursor_pos_callback);
|
|
||||||
glfwGetFramebufferSize(window, &g_fbWidth, &g_fbHeight);
|
glfwGetFramebufferSize(window, &g_fbWidth, &g_fbHeight);
|
||||||
glViewport(0, 0, g_fbWidth, g_fbHeight);
|
glViewport(0, 0, g_fbWidth, g_fbHeight);
|
||||||
printf("Renderer: %s\n", (const char*)glGetString(GL_RENDERER));
|
printf("Renderer: %s\n", (const char*)glGetString(GL_RENDERER));
|
||||||
@ -115,8 +91,6 @@ int main(void) {
|
|||||||
double sim_dt = 1.0 / 360.0;
|
double sim_dt = 1.0 / 360.0;
|
||||||
double currentTime = glfwGetTime();
|
double currentTime = glfwGetTime();
|
||||||
double accumulator = 0.0;
|
double accumulator = 0.0;
|
||||||
float brushX = 0.0f;
|
|
||||||
float brushY = 0.0f;
|
|
||||||
|
|
||||||
// --- Main loop ---
|
// --- Main loop ---
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
@ -126,18 +100,13 @@ int main(void) {
|
|||||||
if (frameTime > 0.25) frameTime = 0.25;
|
if (frameTime > 0.25) frameTime = 0.25;
|
||||||
accumulator += frameTime;
|
accumulator += frameTime;
|
||||||
|
|
||||||
|
|
||||||
while (accumulator >= sim_dt) {
|
while (accumulator >= sim_dt) {
|
||||||
sand_step_gpu(&sim); // one discrete CA step on the GPU
|
sand_step_gpu(&sim); // one discrete CA step on the GPU (commented bc unimplemeted)
|
||||||
sand_relax_gpu(&sim);
|
|
||||||
accumulator -= sim_dt;
|
accumulator -= sim_dt;
|
||||||
}
|
}
|
||||||
int paintMode = 0;
|
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
|
|
||||||
paintMode = 1;
|
|
||||||
} else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) {
|
|
||||||
paintMode = -1;
|
|
||||||
}
|
|
||||||
// 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);
|
||||||
@ -146,18 +115,9 @@ int main(void) {
|
|||||||
g_fbHeight = fbh;
|
g_fbHeight = fbh;
|
||||||
glViewport(0, 0, g_fbWidth, g_fbHeight);
|
glViewport(0, 0, g_fbWidth, g_fbHeight);
|
||||||
}
|
}
|
||||||
if (g_fbWidth > 0 && g_fbHeight > 0) {
|
|
||||||
float uvx = (float)g_cursorX / (float)g_fbWidth;
|
|
||||||
float uvy = (float)g_cursorY / (float)g_fbHeight;
|
|
||||||
brushX = uvx * (float)sim.gridW;
|
|
||||||
brushY = uvy * (float)sim.gridH;
|
|
||||||
}
|
|
||||||
float brushRadius = 60.0f;
|
|
||||||
sand_paint_gpu(&sim, brushX, brushY, brushRadius, paintMode);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
render_sand(&sim, program, vao, g_fbWidth, g_fbHeight, brushX, brushY, brushRadius);
|
render_sand(&sim, program, vao, g_fbWidth, g_fbHeight);
|
||||||
|
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
sand_hello
Executable file
BIN
sand_hello
Executable file
Binary file not shown.
188
sand_sim.c
188
sand_sim.c
@ -1,45 +1,11 @@
|
|||||||
// sand_sim.c
|
// sand_sim.c
|
||||||
#include "sand_sim.h"
|
#include "sand_sim.h"
|
||||||
|
#include "gl_utils.h" // for create_compute_program_from_file
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "gl_utils.h" // for create_compute_program_from_file
|
|
||||||
void sand_paint_gpu(SandSim* sim,
|
|
||||||
float brushX,
|
|
||||||
float brushY,
|
|
||||||
float brushRadius,
|
|
||||||
int mode) {
|
|
||||||
if (!sim || !sim->prog_paint) return;
|
|
||||||
if (mode == 0) return; // nothing to do
|
|
||||||
|
|
||||||
glUseProgram(sim->prog_paint);
|
|
||||||
|
|
||||||
GLint locGrid = glGetUniformLocation(sim->prog_paint, "u_gridSize");
|
|
||||||
GLint locPos = glGetUniformLocation(sim->prog_paint, "u_brushPos");
|
|
||||||
GLint locRad = glGetUniformLocation(sim->prog_paint, "u_brushRadius");
|
|
||||||
GLint locMode = glGetUniformLocation(sim->prog_paint, "u_mode");
|
|
||||||
|
|
||||||
if (locGrid >= 0) glUniform2i(locGrid, sim->gridW, sim->gridH);
|
|
||||||
if (locPos >= 0) glUniform2f(locPos, brushX, brushY);
|
|
||||||
if (locRad >= 0) glUniform1f(locRad, brushRadius);
|
|
||||||
if (locMode >= 0) glUniform1i(locMode, mode);
|
|
||||||
|
|
||||||
// Bind current state as read-write image
|
|
||||||
glBindImageTexture(0, sim->tex_curr,
|
|
||||||
0, GL_FALSE, 0,
|
|
||||||
GL_READ_WRITE, GL_R8UI);
|
|
||||||
|
|
||||||
// Dispatch over entire grid; 16x16 workgroup like your sim shader
|
|
||||||
GLuint groupsX = (sim->gridW + 16 - 1) / 16;
|
|
||||||
GLuint groupsY = (sim->gridH + 16 - 1) / 16;
|
|
||||||
glDispatchCompute(groupsX, groupsY, 1);
|
|
||||||
|
|
||||||
// Ensure writes visible to subsequent rendering/compute
|
|
||||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void init_textures(SandSim* sim) {
|
static void init_textures(SandSim* sim) {
|
||||||
// tex_curr
|
// tex_curr
|
||||||
glGenTextures(1, &sim->tex_curr);
|
glGenTextures(1, &sim->tex_curr);
|
||||||
@ -59,15 +25,6 @@ static void init_textures(SandSim* sim) {
|
|||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
// NEW: tex_claim for the atomic “ownership” buffer
|
|
||||||
glGenTextures(1, &sim->tex_claim);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, sim->tex_claim);
|
|
||||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 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
|
// Unbind
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
@ -118,26 +75,14 @@ bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath
|
|||||||
sim->gridH = gridH;
|
sim->gridH = gridH;
|
||||||
sim->tex_curr = 0;
|
sim->tex_curr = 0;
|
||||||
sim->tex_next = 0;
|
sim->tex_next = 0;
|
||||||
sim->tex_claim = 0;
|
|
||||||
sim->prog_sim = 0;
|
sim->prog_sim = 0;
|
||||||
sim->prog_paint = 0;
|
|
||||||
|
|
||||||
init_textures(sim);
|
init_textures(sim);
|
||||||
upload_initial_state(sim);
|
upload_initial_state(sim);
|
||||||
|
|
||||||
sim->prog_sim = create_compute_program_from_file(computeShaderPath);
|
sim->prog_sim = create_compute_program_from_file(computeShaderPath);
|
||||||
if (!sim->prog_sim) {
|
if (!sim->prog_sim) {
|
||||||
fprintf(stderr, "[sand_init] Failed to create compute cell tick program\n");
|
fprintf(stderr, "[sand_init] Failed to create compute program\n");
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sim->prog_paint = create_compute_program_from_file("./shaders/sand_paint.comp");
|
|
||||||
if (!sim->prog_paint) {
|
|
||||||
fprintf(stderr, "[sand_init] Failed to create compute paint program\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sim->prog_relax = create_compute_program_from_file("./shaders/sand_relax.comp");
|
|
||||||
if (!sim->prog_relax) {
|
|
||||||
fprintf(stderr, "[sand_init] Failed to create compute relax program\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,110 +90,27 @@ bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sand_step_gpu(SandSim* sim) {
|
void sand_step_gpu(SandSim* sim) {
|
||||||
|
if (!sim || !sim->prog_sim) return;
|
||||||
|
|
||||||
glUseProgram(sim->prog_sim);
|
glUseProgram(sim->prog_sim);
|
||||||
static unsigned int frameCounter = 0;
|
|
||||||
frameCounter++;
|
|
||||||
|
|
||||||
GLint locFrame = glGetUniformLocation(sim->prog_sim, "u_frame");
|
// Set grid size uniform
|
||||||
if (locFrame >= 0) {
|
GLint uGridLoc = glGetUniformLocation(sim->prog_sim, "u_gridSize");
|
||||||
glUniform1ui(locFrame, frameCounter);
|
if (uGridLoc >= 0) {
|
||||||
}
|
glUniform2i(uGridLoc, sim->gridW, sim->gridH);
|
||||||
GLint locGrid = glGetUniformLocation(sim->prog_sim, "u_gridSize");
|
|
||||||
if (locGrid >= 0) {
|
|
||||||
glUniform2i(locGrid, sim->gridW, sim->gridH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear next state to AIR (0)
|
// 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);
|
||||||
|
|
||||||
const GLubyte zeroByte[1] = {0};
|
GLuint groupsX = (sim->gridW + 15) / 16;
|
||||||
glClearTexImage(sim->tex_next,
|
GLuint groupsY = (sim->gridH + 15) / 16;
|
||||||
0,
|
|
||||||
GL_RED_INTEGER,
|
|
||||||
GL_UNSIGNED_BYTE,
|
|
||||||
zeroByte);
|
|
||||||
|
|
||||||
// Clear claim buffer to 0
|
|
||||||
const GLuint zero1[1] = {0};
|
|
||||||
glClearTexImage(sim->tex_claim,
|
|
||||||
0,
|
|
||||||
GL_RED_INTEGER, // format doesn't really matter; data is uint
|
|
||||||
GL_UNSIGNED_INT,
|
|
||||||
zero1);
|
|
||||||
|
|
||||||
// Bind images
|
|
||||||
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);
|
|
||||||
glBindImageTexture(2, sim->tex_claim,
|
|
||||||
0, GL_FALSE, 0,
|
|
||||||
GL_READ_WRITE, GL_R32UI);
|
|
||||||
|
|
||||||
GLuint groupsX = (sim->gridW + 16 - 1) / 16;
|
|
||||||
GLuint groupsY = (sim->gridH + 16 - 1) / 16;
|
|
||||||
glDispatchCompute(groupsX, groupsY, 1);
|
glDispatchCompute(groupsX, groupsY, 1);
|
||||||
|
|
||||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||||
|
|
||||||
// ping-pong: curr <-> next
|
// Ping-pong
|
||||||
GLuint tmp = sim->tex_curr;
|
|
||||||
sim->tex_curr = sim->tex_next;
|
|
||||||
sim->tex_next = tmp;
|
|
||||||
}
|
|
||||||
void sand_relax_gpu(SandSim* sim) {
|
|
||||||
if (!sim || !sim->prog_relax) return;
|
|
||||||
|
|
||||||
glUseProgram(sim->prog_relax);
|
|
||||||
|
|
||||||
static unsigned int relaxFrame = 0;
|
|
||||||
relaxFrame++;
|
|
||||||
|
|
||||||
GLint locFrame = glGetUniformLocation(sim->prog_relax, "u_frame");
|
|
||||||
if (locFrame >= 0) {
|
|
||||||
glUniform1ui(locFrame, relaxFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint locGrid = glGetUniformLocation(sim->prog_relax, "u_gridSize");
|
|
||||||
if (locGrid >= 0) {
|
|
||||||
glUniform2i(locGrid, sim->gridW, sim->gridH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear next state to AIR (0) – tex_next is GL_R8UI
|
|
||||||
const GLubyte zeroByte[1] = {0};
|
|
||||||
glClearTexImage(sim->tex_next,
|
|
||||||
0,
|
|
||||||
GL_RED_INTEGER,
|
|
||||||
GL_UNSIGNED_BYTE,
|
|
||||||
zeroByte);
|
|
||||||
|
|
||||||
// Clear claim buffer (GL_R32UI) to 0
|
|
||||||
const GLuint zero1[1] = {0};
|
|
||||||
glClearTexImage(sim->tex_claim,
|
|
||||||
0,
|
|
||||||
GL_RED_INTEGER,
|
|
||||||
GL_UNSIGNED_INT,
|
|
||||||
zero1);
|
|
||||||
|
|
||||||
// Bind images: curr = read, next = write, claim = read/write
|
|
||||||
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);
|
|
||||||
glBindImageTexture(2, sim->tex_claim,
|
|
||||||
0, GL_FALSE, 0,
|
|
||||||
GL_READ_WRITE, GL_R32UI);
|
|
||||||
|
|
||||||
GLuint groupsX = (sim->gridW + 16 - 1) / 16;
|
|
||||||
GLuint groupsY = (sim->gridH + 16 - 1) / 16;
|
|
||||||
glDispatchCompute(groupsX, groupsY, 1);
|
|
||||||
|
|
||||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
|
||||||
|
|
||||||
// ping-pong: curr <-> next
|
|
||||||
GLuint tmp = sim->tex_curr;
|
GLuint tmp = sim->tex_curr;
|
||||||
sim->tex_curr = sim->tex_next;
|
sim->tex_curr = sim->tex_next;
|
||||||
sim->tex_next = tmp;
|
sim->tex_next = tmp;
|
||||||
@ -257,14 +119,16 @@ void sand_relax_gpu(SandSim* sim) {
|
|||||||
void sand_destroy(SandSim* sim) {
|
void sand_destroy(SandSim* sim) {
|
||||||
if (!sim) return;
|
if (!sim) return;
|
||||||
|
|
||||||
if (sim->tex_curr) glDeleteTextures(1, &sim->tex_curr);
|
if (sim->tex_curr) {
|
||||||
if (sim->tex_next) glDeleteTextures(1, &sim->tex_next);
|
glDeleteTextures(1, &sim->tex_curr);
|
||||||
if (sim->tex_claim) glDeleteTextures(1, &sim->tex_claim);
|
sim->tex_curr = 0;
|
||||||
sim->tex_curr = sim->tex_next = sim->tex_claim = 0;
|
}
|
||||||
|
if (sim->tex_next) {
|
||||||
if (sim->prog_sim) glDeleteProgram(sim->prog_sim);
|
glDeleteTextures(1, &sim->tex_next);
|
||||||
if (sim->prog_paint) glDeleteProgram(sim->prog_paint);
|
sim->tex_next = 0;
|
||||||
if (sim->prog_relax) glDeleteProgram(sim->prog_relax);
|
}
|
||||||
sim->prog_sim = sim->prog_paint = sim->prog_relax = 0;
|
if (sim->prog_sim) {
|
||||||
|
glDeleteProgram(sim->prog_sim);
|
||||||
|
sim->prog_sim = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
sand_sim.h
18
sand_sim.h
@ -7,29 +7,21 @@
|
|||||||
|
|
||||||
// Simple binary sand vs air simulation
|
// Simple binary sand vs air simulation
|
||||||
typedef struct {
|
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 gridW;
|
||||||
int gridH;
|
int gridH;
|
||||||
GLuint tex_curr;
|
|
||||||
GLuint tex_next;
|
|
||||||
GLuint tex_claim; // NEW: claim buffer
|
|
||||||
GLuint prog_sim;
|
|
||||||
GLuint prog_paint;
|
|
||||||
GLuint prog_relax;
|
|
||||||
} SandSim;
|
} SandSim;
|
||||||
|
|
||||||
|
|
||||||
// Initialize sim, allocate textures, upload initial state, load compute shader.
|
// Initialize sim, allocate textures, upload initial state, load compute shader.
|
||||||
// Returns true on success, false on failure.
|
// Returns true on success, false on failure.
|
||||||
bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath);
|
bool sand_init(SandSim* sim, int gridW, int gridH, const char* computeShaderPath);
|
||||||
|
|
||||||
// Advance simulation by 1 discrete tick (one CA step).
|
// Advance simulation by 1 discrete tick (one CA step).
|
||||||
void sand_step_gpu(SandSim* sim);
|
void sand_step_gpu(SandSim* sim);
|
||||||
void sand_relax_gpu(SandSim* sim);
|
|
||||||
// Destroy all GL objects owned by the sim.
|
// Destroy all GL objects owned by the sim.
|
||||||
void sand_destroy(SandSim* sim);
|
void sand_destroy(SandSim* sim);
|
||||||
void sand_paint_gpu(SandSim* sim,
|
|
||||||
float brushX,
|
|
||||||
float brushY,
|
|
||||||
float brushRadius,
|
|
||||||
int mode);
|
|
||||||
#endif // SAND_SIM_H
|
#endif // SAND_SIM_H
|
||||||
|
|||||||
@ -5,9 +5,7 @@ out vec4 FragColor;
|
|||||||
uniform vec2 u_resolution; // framebuffer size in pixels
|
uniform vec2 u_resolution; // framebuffer size in pixels
|
||||||
uniform ivec2 u_gridSize; // sand grid size
|
uniform ivec2 u_gridSize; // sand grid size
|
||||||
uniform usampler2D u_state; // GL_R8UI texture
|
uniform usampler2D u_state; // GL_R8UI texture
|
||||||
uniform vec2 u_brushPos;
|
|
||||||
uniform float u_brushRadius;
|
|
||||||
uniform int u_showBrush;
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 fragCoord = gl_FragCoord.xy;
|
vec2 fragCoord = gl_FragCoord.xy;
|
||||||
|
|
||||||
@ -23,23 +21,6 @@ void main() {
|
|||||||
vec3 color = (v == 1u)
|
vec3 color = (v == 1u)
|
||||||
? vec3(0.9, 0.8, 0.1) // sand
|
? vec3(0.9, 0.8, 0.1) // sand
|
||||||
: vec3(0.05, 0.05, 0.10); // background
|
: vec3(0.05, 0.05, 0.10); // background
|
||||||
if (u_showBrush != 0) {
|
|
||||||
// center of this cell in grid coordinates
|
|
||||||
vec2 cellCenter = vec2(cell) + vec2(0.5);
|
|
||||||
float dist = length(cellCenter - u_brushPos);
|
|
||||||
|
|
||||||
// how thick the ring is, in cells
|
|
||||||
float thickness = 1.0; // 1-cell-thick ring
|
|
||||||
|
|
||||||
// abs(dist - radius) < thickness => inside ring
|
|
||||||
float d = abs(dist - u_brushRadius);
|
|
||||||
|
|
||||||
// Smooth edge: edge = 1 at center of ring, 0 outside
|
|
||||||
float edge = smoothstep(thickness, 0.0, d);
|
|
||||||
|
|
||||||
vec3 ringColor = vec3(1.0); // white ring
|
|
||||||
color = mix(color, ringColor, edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragColor = vec4(color, 1.0);
|
FragColor = vec4(color, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
#version 430 core
|
|
||||||
|
|
||||||
layout(local_size_x = 16, local_size_y = 16) in;
|
|
||||||
|
|
||||||
// Read-write access to the current grid
|
|
||||||
layout(r8ui, binding = 0) coherent uniform uimage2D state;
|
|
||||||
|
|
||||||
uniform ivec2 u_gridSize;
|
|
||||||
uniform vec2 u_brushPos; // in grid-space (cells)
|
|
||||||
uniform float u_brushRadius; // in cells
|
|
||||||
uniform int u_mode; // 0 = none, 1 = add sand, -1 = erase
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
|
||||||
if (pos.x >= u_gridSize.x || pos.y >= u_gridSize.y) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u_mode == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center of this cell in grid coords
|
|
||||||
vec2 cellCenter = vec2(pos) + vec2(0.5);
|
|
||||||
float dist = length(cellCenter - u_brushPos);
|
|
||||||
|
|
||||||
if (dist > u_brushRadius) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint AIR = 0u;
|
|
||||||
const uint SAND = 1u;
|
|
||||||
|
|
||||||
if (u_mode > 0) {
|
|
||||||
// left click: fill with sand
|
|
||||||
imageStore(state, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
} else if (u_mode < 0) {
|
|
||||||
// right click: erase sand
|
|
||||||
imageStore(state, pos, uvec4(AIR, 0u, 0u, 0u));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
#version 430 core
|
|
||||||
layout(local_size_x = 16, local_size_y = 16) in;
|
|
||||||
|
|
||||||
// state_curr: read-only; state_next: write-only
|
|
||||||
layout(r8ui, binding = 0) readonly uniform uimage2D state_curr;
|
|
||||||
layout(r8ui, binding = 1) writeonly uniform uimage2D state_next;
|
|
||||||
|
|
||||||
// claim buffer to resolve conflicts
|
|
||||||
layout(r32ui, binding = 2) coherent uniform uimage2D claim;
|
|
||||||
|
|
||||||
uniform ivec2 u_gridSize;
|
|
||||||
uniform uint u_frame;
|
|
||||||
|
|
||||||
const uint AIR = 0u;
|
|
||||||
const uint SAND = 1u;
|
|
||||||
|
|
||||||
// How far down we scan to approximate column thickness
|
|
||||||
const int MAX_SCAN = 6;
|
|
||||||
|
|
||||||
// How much “taller” this column must be vs neighbor before we slide
|
|
||||||
// Smaller -> shallower, more flowy piles
|
|
||||||
const int SLOPE_THRESHOLD = 2;
|
|
||||||
|
|
||||||
uint hash(uvec2 p, uint seed) {
|
|
||||||
p = p * 1664525u + 1013904223u + seed;
|
|
||||||
p.x ^= p.y >> 16;
|
|
||||||
p.y ^= p.x >> 16;
|
|
||||||
return p.x ^ p.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Approximate thickness of the sand column under (x, y)
|
|
||||||
int columnThickness(int x, int y) {
|
|
||||||
int t = 0;
|
|
||||||
for (int dy = 0; dy < MAX_SCAN; ++dy) {
|
|
||||||
int yy = y + dy;
|
|
||||||
if (yy >= u_gridSize.y) break;
|
|
||||||
uint v = imageLoad(state_curr, ivec2(x, yy)).r;
|
|
||||||
if (v == SAND) {
|
|
||||||
t++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
|
||||||
if (pos.x >= u_gridSize.x || pos.y >= u_gridSize.y) return;
|
|
||||||
|
|
||||||
uint self = imageLoad(state_curr, pos).r;
|
|
||||||
if (self != SAND) return;
|
|
||||||
|
|
||||||
// Lock bottom row in place
|
|
||||||
if (pos.y == u_gridSize.y - 1) {
|
|
||||||
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ivec2 dest = pos;
|
|
||||||
|
|
||||||
// Only relax grains that are resting on something
|
|
||||||
ivec2 down = pos + ivec2(0, 1);
|
|
||||||
uint below = imageLoad(state_curr, down).r;
|
|
||||||
if (below == SAND) {
|
|
||||||
int hSelf = columnThickness(pos.x, pos.y);
|
|
||||||
|
|
||||||
bool canLeft = false;
|
|
||||||
bool canRight = false;
|
|
||||||
|
|
||||||
ivec2 left = pos + ivec2(-1, 0);
|
|
||||||
ivec2 right = pos + ivec2( 1, 0);
|
|
||||||
|
|
||||||
int hLeft = 0;
|
|
||||||
int hRight = 0;
|
|
||||||
|
|
||||||
// Check left side
|
|
||||||
if (left.x >= 0) {
|
|
||||||
uint leftCell = imageLoad(state_curr, left).r;
|
|
||||||
if (leftCell == AIR) {
|
|
||||||
hLeft = columnThickness(left.x, pos.y);
|
|
||||||
int slopeL = hSelf - hLeft;
|
|
||||||
if (slopeL > SLOPE_THRESHOLD) {
|
|
||||||
canLeft = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check right side
|
|
||||||
if (right.x < u_gridSize.x) {
|
|
||||||
uint rightCell = imageLoad(state_curr, right).r;
|
|
||||||
if (rightCell == AIR) {
|
|
||||||
hRight = columnThickness(right.x, pos.y);
|
|
||||||
int slopeR = hSelf - hRight;
|
|
||||||
if (slopeR > SLOPE_THRESHOLD) {
|
|
||||||
canRight = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canLeft || canRight) {
|
|
||||||
if (canLeft && canRight) {
|
|
||||||
// Prefer the *lower* column; randomize if equal
|
|
||||||
if (hLeft < hRight) {
|
|
||||||
dest = left;
|
|
||||||
} else if (hRight < hLeft) {
|
|
||||||
dest = right;
|
|
||||||
} else {
|
|
||||||
uint h = hash(uvec2(pos), u_frame);
|
|
||||||
bool chooseLeft = (h & 1u) != 0u;
|
|
||||||
dest = chooseLeft ? left : right;
|
|
||||||
}
|
|
||||||
} else if (canLeft) {
|
|
||||||
dest = left;
|
|
||||||
} else { // canRight
|
|
||||||
dest = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool staying = (dest.x == pos.x && dest.y == pos.y);
|
|
||||||
|
|
||||||
if (staying) {
|
|
||||||
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
} else {
|
|
||||||
// Moving to new cell; resolve conflicts via claim buffer
|
|
||||||
uint old = imageAtomicCompSwap(claim, dest, 0u, 1u);
|
|
||||||
if (old == 0u) {
|
|
||||||
imageStore(state_next, dest, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
} else {
|
|
||||||
// lost: stay put
|
|
||||||
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,25 +2,10 @@
|
|||||||
|
|
||||||
layout(local_size_x = 16, local_size_y = 16) in;
|
layout(local_size_x = 16, local_size_y = 16) in;
|
||||||
|
|
||||||
// state_curr: read-only; state_next: write-only
|
|
||||||
layout(r8ui, binding = 0) readonly uniform uimage2D state_curr;
|
layout(r8ui, binding = 0) readonly uniform uimage2D state_curr;
|
||||||
layout(r8ui, binding = 1) writeonly uniform uimage2D state_next;
|
layout(r8ui, binding = 1) writeonly uniform uimage2D state_next;
|
||||||
|
|
||||||
// claim buffer to resolve conflicts
|
|
||||||
layout(r32ui, binding = 2) coherent uniform uimage2D claim;
|
|
||||||
|
|
||||||
uniform ivec2 u_gridSize;
|
uniform ivec2 u_gridSize;
|
||||||
uniform uint u_frame;
|
|
||||||
|
|
||||||
const uint AIR = 0u;
|
|
||||||
const uint SAND = 1u;
|
|
||||||
|
|
||||||
uint hash(uvec2 p, uint seed) {
|
|
||||||
p = p * 1664525u + 1013904223u + seed;
|
|
||||||
p.x ^= p.y >> 16;
|
|
||||||
p.y ^= p.x >> 16;
|
|
||||||
return p.x ^ p.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||||
@ -28,90 +13,30 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint AIR = 0u;
|
||||||
|
const uint SAND = 1u;
|
||||||
|
|
||||||
uint self = imageLoad(state_curr, pos).r;
|
uint self = imageLoad(state_curr, pos).r;
|
||||||
if (self != SAND) {
|
|
||||||
// AIR cells: do nothing (next is cleared to AIR on CPU)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ivec2 dest = pos;
|
bool canFallDown =
|
||||||
|
(self == SAND) &&
|
||||||
|
(pos.y + 1 < u_gridSize.y) &&
|
||||||
|
(imageLoad(state_curr, pos + ivec2(0, 1)).r == AIR);
|
||||||
|
|
||||||
// Hard floor: bottom row does not move down
|
bool filledFromAbove =
|
||||||
if (pos.y < u_gridSize.y - 1) {
|
(self == AIR) &&
|
||||||
// 1) Try straight down
|
(pos.y > 0) &&
|
||||||
ivec2 down = pos + ivec2(0, 1);
|
(imageLoad(state_curr, pos + ivec2(0, -1)).r == SAND);
|
||||||
bool canDown = (imageLoad(state_curr, down).r == AIR);
|
|
||||||
|
|
||||||
if (canDown) {
|
uint next;
|
||||||
dest = down;
|
|
||||||
|
if (canFallDown) {
|
||||||
|
next = AIR;
|
||||||
|
} else if (filledFromAbove) {
|
||||||
|
next = SAND;
|
||||||
} else {
|
} else {
|
||||||
// 2) Try diagonals
|
next = self;
|
||||||
bool canLeft = false;
|
|
||||||
bool canRight = false;
|
|
||||||
|
|
||||||
ivec2 downLeft = pos + ivec2(-1, 1);
|
|
||||||
ivec2 downRight = pos + ivec2( 1, 1);
|
|
||||||
|
|
||||||
if (downLeft.x >= 0 && downLeft.y < u_gridSize.y) {
|
|
||||||
if (imageLoad(state_curr, downLeft).r == AIR) {
|
|
||||||
canLeft = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (downRight.x < u_gridSize.x && downRight.y < u_gridSize.y) {
|
|
||||||
if (imageLoad(state_curr, downRight).r == AIR) {
|
|
||||||
canRight = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip which side is "first" based on frame parity
|
imageStore(state_next, pos, uvec4(next, 0u, 0u, 0u));
|
||||||
bool flip = ((u_frame & 1u) != 0u);
|
|
||||||
|
|
||||||
ivec2 firstDest, secondDest;
|
|
||||||
bool canFirst, canSecond;
|
|
||||||
|
|
||||||
if (!flip) {
|
|
||||||
// even frames: left is "first"
|
|
||||||
firstDest = downLeft;
|
|
||||||
secondDest = downRight;
|
|
||||||
canFirst = canLeft;
|
|
||||||
canSecond = canRight;
|
|
||||||
} else {
|
|
||||||
// odd frames: right is "first"
|
|
||||||
firstDest = downRight;
|
|
||||||
secondDest = downLeft;
|
|
||||||
canFirst = canRight;
|
|
||||||
canSecond = canLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canFirst && canSecond) {
|
|
||||||
// both free: pick randomly, but "first"/"second" swap each frame
|
|
||||||
uint h = hash(uvec2(pos), u_frame);
|
|
||||||
bool useFirst = (h & 1u) != 0u;
|
|
||||||
dest = useFirst ? firstDest : secondDest;
|
|
||||||
} else if (canFirst) {
|
|
||||||
dest = firstDest;
|
|
||||||
} else if (canSecond) {
|
|
||||||
dest = secondDest;
|
|
||||||
}
|
|
||||||
// else: no diagonal move, dest stays = pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write into next state with claim resolution
|
|
||||||
if (dest.x == pos.x && dest.y == pos.y) {
|
|
||||||
// Not moving; just keep sand here
|
|
||||||
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
} else {
|
|
||||||
// Moving to a new cell; try to claim it
|
|
||||||
uint old = imageAtomicCompSwap(claim, dest, 0u, 1u);
|
|
||||||
|
|
||||||
if (old == 0u) {
|
|
||||||
// Won the slot: move
|
|
||||||
imageStore(state_next, dest, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
// Original pos stays AIR because state_next was cleared
|
|
||||||
} else {
|
|
||||||
// Lost the slot: stay put
|
|
||||||
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user