118 lines
3.6 KiB
Plaintext
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));
|
|
}
|
|
}
|
|
}
|