fallingCand/shaders/sand_step.comp
2025-11-29 16:30:47 -08:00

118 lines
3.6 KiB
Plaintext

#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;
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() {
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) {
// AIR cells: do nothing (next is cleared to AIR on CPU)
return;
}
ivec2 dest = pos;
// Hard floor: bottom row does not move down
if (pos.y < u_gridSize.y - 1) {
// 1) Try straight down
ivec2 down = pos + ivec2(0, 1);
bool canDown = (imageLoad(state_curr, down).r == AIR);
if (canDown) {
dest = down;
} else {
// 2) Try diagonals
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
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));
}
}
}