diff --git a/docs/examples/gpgpu/game-of-life/index.md b/docs/examples/gpgpu/game-of-life/index.md new file mode 100644 index 0000000..d4c4f36 --- /dev/null +++ b/docs/examples/gpgpu/game-of-life/index.md @@ -0,0 +1,12 @@ +--- +title: Game of Life +position: 3 +--- + +::: example-editor + +<<< ./index.ts +<<< @/snippets/canvas-square/styles.css +<<< @/snippets/default/index.html + +::: diff --git a/docs/examples/gpgpu/game-of-life/index.ts b/docs/examples/gpgpu/game-of-life/index.ts new file mode 100644 index 0000000..e8f3da6 --- /dev/null +++ b/docs/examples/gpgpu/game-of-life/index.ts @@ -0,0 +1,79 @@ +import { useWebGLContext, useLoop, usePingPongFBO, useWebGLCanvas } from "usegl"; +import "./styles.css"; + +const { gl, canvas } = useWebGLContext("#glCanvas"); + +const gridSize = 100; + +const lifeUpdateFragment = /* glsl */ ` + uniform sampler2D tCurrentState; + varying vec2 vUv; + + int getCellState(vec2 coord) { + vec4 color = texture2D(tCurrentState, coord); + return color.r > 0.5 ? 1 : 0; + } + + void main() { + int neighbors = 0; + float dx = 1.0 / float(${gridSize}); + float dy = 1.0 / float(${gridSize}); + + neighbors += getCellState(vUv + vec2(-dx, -dy)); + neighbors += getCellState(vUv + vec2(-dx, 0.0)); + neighbors += getCellState(vUv + vec2(-dx, dy)); + neighbors += getCellState(vUv + vec2(0.0, -dy)); + neighbors += getCellState(vUv + vec2(0.0, dy)); + neighbors += getCellState(vUv + vec2(dx, -dy)); + neighbors += getCellState(vUv + vec2(dx, 0.0)); + neighbors += getCellState(vUv + vec2(dx, dy)); + + int currentState = getCellState(vUv); + float newState = 0.0; + + if (currentState == 0 && neighbors == 3) { + newState = 1.0; // Birth + } else if (currentState == 1 && (neighbors == 2 || neighbors == 3)) { + newState = 1.0; // Survival + } + + gl_FragColor = vec4(newState, newState, newState, 1.0); + } +`; + +const initialData = new Float32Array(gridSize * gridSize * 4); +for (let i = 0; i < gridSize * gridSize; i++) { + const alive = Math.random() < 0.5 ? 1 : 0; + initialData.set([alive, alive, alive, 1], i * 4); +} + +const gameState = usePingPongFBO(gl, { + fragment: lifeUpdateFragment, + dataTexture: { + name: "tCurrentState", + initialData, + }, +}); + +const renderPass = useWebGLCanvas({ + canvas, + fragment: /* glsl */ ` + uniform sampler2D tCurrentState; + attribute vec2 vUv; + + void main() { + gl_FragColor = texture2D(tCurrentState, vUv); + } + `, + uniforms: { + tCurrentState: () => gameState.texture, + }, +}); + +let lastTime = 0; +useLoop(({ elapsedTime }) => { + if (elapsedTime - lastTime < 50) return; + gameState.render(); + renderPass.render(); + lastTime = elapsedTime; +});