Initial commit
diff --git a/test/compute_boids.wgsl b/test/compute_boids.wgsl
new file mode 100644
index 0000000..6df8a30
--- /dev/null
+++ b/test/compute_boids.wgsl
@@ -0,0 +1,152 @@
+# Copyright 2020 The Tint Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import "GLSL.std.450" as std;
+
+# vertex shader
+
+[[location 0]] var<in> a_particlePos : vec2<f32>;
+[[location 1]] var<in> a_particleVel : vec2<f32>;
+[[location 2]] var<in> a_pos : vec2<f32>;
+[[builtin position]] var gl_Position : vec4<f32>;
+
+fn vtx_main() -> void {
+  var angle : f32 = -std::atan2(a_particleVel.x, a_particleVel.y);
+  var pos : vec2<f32> = vec2<f32>(
+      (a_pos.x * std::cos(angle)) - (a_pos.y * std::sin(angle)),
+      (a_pos.x * std::sin(angle)) + (a_pos.y * std::cos(angle)));
+  gl_Position = vec4<f32>(pos + a_particlePos, 0, 1);
+  return;
+}
+entry_point vertex as "main" = vtx_main;
+
+# fragment shader
+[[location 0]] var<out> fragColor : vec4<f32>;
+
+fn frag_main() -> void {
+  fragColor = vec4<f32>(1.0, 1.0, 1.0, 1.0);
+  return;
+}
+entry_point fragment as "main" = frag_main;
+
+# compute shader
+type Particle = struct {
+  [[offset 0]] pos : vec2<f32>;
+  [[offset 8]] vel : vec2<f32>;
+};
+
+type SimParams = struct {
+  [[offset 0]] deltaT : f32;
+  [[offset 4]] rule1Distance : f32;
+  [[offset 8]] rule2Distance : f32;
+  [[offset 12]] rule3Distance : f32;
+  [[offset 16]] rule1Scale : f32;
+  [[offset 20]] rule2Scale : f32;
+  [[offset 24]] rule3Scale : f32;
+};
+
+type Particles = struct {
+  [[offset 0]] particles : array<Particle, 5>;
+};
+
+[[binding 0, set 0]] var<uniform> params : SimParams;
+[[binding 1, set 0]] var<storage_buffer> particlesA : Particles;
+[[binding 2, set 0]] var<storage_buffer> particlesB : Particles;
+
+[[builtin global_invocation_id]] var gl_GlobalInvocationID : vec3<f32>;
+
+# https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
+fn compute_main() -> void {
+  var index : u32 = gl_GlobalInvocationID.x;
+  if (index >= 5) {
+    return;
+  }
+
+  var vPos : vec2<f32> = particlesA.particles[index].pos;
+  var vVel : vec2<f32> = particlesA.particles[index].vel;
+
+  var cMass : vec2<f32> = vec2<f32>(0, 0);
+  var cVel : vec2<f32> = vec2<f32>(0, 0);
+  var colVel : vec2<f32> = vec2<f32>(0, 0);
+  var cMassCount : i32 = 0;
+  var cVelCount : i32 = 0;
+
+  var pos : vec2<f32>;
+  var vel : vec2<f32>;
+  var i : i32 = 0;
+  loop {
+    if (i >= 5) {
+      break;
+    }
+    if (i == index) {
+      continue;
+    }
+
+    pos = particlesA.particles[i].pos.xy;
+    vel = particlesA.particles[i].vel.xy;
+
+    if (std::distance(pos, vPos) < params.rule1Distance) {
+      cMass = cMass + pos;
+      cMassCount = cMassCount + 1;
+    }
+    if (std::distance(pos, vPos) < params.rule2Distance) {
+      colVel = colVel - (pos - vPos);
+    }
+    if (std::distance(pos, vPos) < params.rule3Distance) {
+      cVel = cVel + vel;
+      cVelCount = cVelCount + 1;
+    }
+
+    continuing {
+      i = i + 1;
+    }
+  }
+  if (cMassCount > 0) {
+    cMass = (cMass / vec2<f32>(cMassCount, cMassCount)) + vPos;
+  }
+  if (cVelCount > 0) {
+    cVel = cVel / vec2<f32>(cVelCount, cVelCount);
+  }
+
+  vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) +
+      (cVel * params.rule3Scale);
+
+  # clamp velocity for a more pleasing simulation
+  vVel = std::normalize(vVel) * std::fclamp(std::length(vVel), 0.0, 0.1);
+
+  # kinematic update
+  vPos = vPos + (vVel * params.deltaT);
+
+  # Wrap around boundary
+  if (vPos.x < -1.0) {
+    vPos.x = 1.0;
+  }
+  if (vPos.x > 1.0) {
+    vPos.x = -1.0;
+  }
+  if (vPos.y < -1.0) {
+    vPos.y = 1.0;
+  }
+  if (vPos.y > 1.0) {
+    vPos.y = -1.0;
+  }
+
+  # Write back
+  particlesB.particles[index].pos = vPos;
+  particlesB.particles[index].vel = vVel;
+
+  return;
+}
+entry_point compute as "main" = compute_main;
+