test/benchmark: Add more shaders to the corpus

Kindly donated by Brandon Jones and Austin Eng.
Tint has been used to convert all the deprecated attributes to the new style. In doing so, comments have been stripped. These are not massively important for the benchmarking.

Bindings have also been adjusted to be sequential and unique so that the MSL backend doesn't have to deal with binding remapping.

Existing benchmark files that used an underscore '_' have been renamed to use a dash '-' instead, to match the new files.

Change-Id: If5fb507b981f107ed570f6eedb55b232448f67aa
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/77443
Reviewed-by: Brandon Jones <bajones@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/benchmark/benchmark.h b/src/benchmark/benchmark.h
index 40de31d..0d3c764 100644
--- a/src/benchmark/benchmark.h
+++ b/src/benchmark/benchmark.h
@@ -57,12 +57,19 @@
 
 /// Declares a set of benchmarks for the given function using a list of WGSL
 /// files in `<tint>/test/benchmark`.
-#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC)                   \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "empty.wgsl");           \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl");       \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple_fragment.wgsl"); \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple_vertex.wgsl");   \
-  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple_compute.wgsl");
+#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC)                                 \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "animometer.wgsl");                    \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "bloom-vertical-blur.wgsl");           \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "cluster-lights.wgsl");                \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "empty.wgsl");                         \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "metaball-isosurface.wgsl");           \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl");                     \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "shadow-fragment.wgsl");               \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-compute.wgsl");                \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-fragment.wgsl");               \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-vertex.wgsl");                 \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-fragment.wgsl"); \
+  TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-vertex.wgsl");
 
 }  // namespace tint::benchmark
 
diff --git a/test/benchmark/animometer.wgsl b/test/benchmark/animometer.wgsl
new file mode 100644
index 0000000..b850d14
--- /dev/null
+++ b/test/benchmark/animometer.wgsl
@@ -0,0 +1,48 @@
+struct Time {
+  value : f32;
+}
+
+struct Uniforms {
+  scale : f32;
+  offsetX : f32;
+  offsetY : f32;
+  scalar : f32;
+  scalarOffset : f32;
+}
+
+@binding(0) @group(0) var<uniform> time : Time;
+
+@binding(1) @group(0) var<uniform> uniforms : Uniforms;
+
+struct VertexOutput {
+  @builtin(position)
+  Position : vec4<f32>;
+  @location(0)
+  v_color : vec4<f32>;
+}
+
+@stage(vertex)
+fn vert_main(@location(0) position : vec4<f32>, @location(1) color : vec4<f32>) -> VertexOutput {
+  var fade : f32 = ((uniforms.scalarOffset + ((time.value * uniforms.scalar) / 10.0)) % 1.0);
+  if ((fade < 0.5)) {
+    fade = (fade * 2.0);
+  } else {
+    fade = ((1.0 - fade) * 2.0);
+  }
+  var xpos : f32 = (position.x * uniforms.scale);
+  var ypos : f32 = (position.y * uniforms.scale);
+  var angle : f32 = ((3.141590118 * 2.0) * fade);
+  var xrot : f32 = ((xpos * cos(angle)) - (ypos * sin(angle)));
+  var yrot : f32 = ((xpos * sin(angle)) + (ypos * cos(angle)));
+  xpos = (xrot + uniforms.offsetX);
+  ypos = (yrot + uniforms.offsetY);
+  var output : VertexOutput;
+  output.v_color = (vec4<f32>(fade, (1.0 - fade), 0.0, 1.0) + color);
+  output.Position = vec4<f32>(xpos, ypos, 0.0, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main(@location(0) v_color : vec4<f32>) -> @location(0) vec4<f32> {
+  return v_color;
+}
diff --git a/test/benchmark/animometer.wgsl.expected.glsl b/test/benchmark/animometer.wgsl.expected.glsl
new file mode 100644
index 0000000..533937f
--- /dev/null
+++ b/test/benchmark/animometer.wgsl.expected.glsl
@@ -0,0 +1,149 @@
+SKIP: FAILED
+
+#version 310 es
+precision mediump float;
+
+struct Time {
+  float value;
+};
+struct Uniforms {
+  float scale;
+  float offsetX;
+  float offsetY;
+  float scalar;
+  float scalarOffset;
+};
+
+layout (binding = 0) uniform Time_1 {
+  float value;
+} time;
+layout (binding = 1) uniform Uniforms_1 {
+  float scale;
+  float offsetX;
+  float offsetY;
+  float scalar;
+  float scalarOffset;
+} uniforms;
+
+struct VertexOutput {
+  vec4 Position;
+  vec4 v_color;
+};
+struct tint_symbol_2 {
+  vec4 position;
+  vec4 color;
+};
+struct tint_symbol_3 {
+  vec4 v_color;
+  vec4 Position;
+};
+
+VertexOutput vert_main_inner(vec4 position, vec4 color) {
+  float fade = ((uniforms.scalarOffset + ((time.value * uniforms.scalar) / 10.0f)) % 1.0f);
+  if ((fade < 0.5f)) {
+    fade = (fade * 2.0f);
+  } else {
+    fade = ((1.0f - fade) * 2.0f);
+  }
+  float xpos = (position.x * uniforms.scale);
+  float ypos = (position.y * uniforms.scale);
+  float angle = ((3.141590118f * 2.0f) * fade);
+  float xrot = ((xpos * cos(angle)) - (ypos * sin(angle)));
+  float yrot = ((xpos * sin(angle)) + (ypos * cos(angle)));
+  xpos = (xrot + uniforms.offsetX);
+  ypos = (yrot + uniforms.offsetY);
+  VertexOutput tint_symbol = VertexOutput(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  tint_symbol.v_color = (vec4(fade, (1.0f - fade), 0.0f, 1.0f) + color);
+  tint_symbol.Position = vec4(xpos, ypos, 0.0f, 1.0f);
+  return tint_symbol;
+}
+
+struct tint_symbol_5 {
+  vec4 v_color;
+};
+struct tint_symbol_6 {
+  vec4 value;
+};
+
+tint_symbol_3 vert_main(tint_symbol_2 tint_symbol_1) {
+  VertexOutput inner_result = vert_main_inner(tint_symbol_1.position, tint_symbol_1.color);
+  tint_symbol_3 wrapper_result = tint_symbol_3(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.Position = inner_result.Position;
+  wrapper_result.v_color = inner_result.v_color;
+  return wrapper_result;
+}
+in vec4 position;
+in vec4 color;
+out vec4 v_color;
+void main() {
+  tint_symbol_2 inputs;
+  inputs.position = position;
+  inputs.color = color;
+  tint_symbol_3 outputs;
+  outputs = vert_main(inputs);
+  v_color = outputs.v_color;
+  gl_Position = outputs.Position;
+  gl_Position.y = -gl_Position.y;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:40: '%' :  wrong operand types: no operation '%' exists that takes a left-hand operand of type ' temp mediump float' and a right operand of type ' const float' (or there is no acceptable conversion)
+ERROR: 0:40: '' : compilation terminated 
+ERROR: 2 compilation errors.  No code generated.
+
+
+
+#version 310 es
+precision mediump float;
+
+struct Time {
+  float value;
+};
+struct Uniforms {
+  float scale;
+  float offsetX;
+  float offsetY;
+  float scalar;
+  float scalarOffset;
+};
+struct VertexOutput {
+  vec4 Position;
+  vec4 v_color;
+};
+struct tint_symbol_2 {
+  vec4 position;
+  vec4 color;
+};
+struct tint_symbol_3 {
+  vec4 v_color;
+  vec4 Position;
+};
+struct tint_symbol_5 {
+  vec4 v_color;
+};
+struct tint_symbol_6 {
+  vec4 value;
+};
+
+vec4 frag_main_inner(vec4 v_color) {
+  return v_color;
+}
+
+tint_symbol_6 frag_main(tint_symbol_5 tint_symbol_4) {
+  vec4 inner_result_1 = frag_main_inner(tint_symbol_4.v_color);
+  tint_symbol_6 wrapper_result_1 = tint_symbol_6(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+in vec4 v_color;
+out vec4 value;
+void main() {
+  tint_symbol_5 inputs;
+  inputs.v_color = v_color;
+  tint_symbol_6 outputs;
+  outputs = frag_main(inputs);
+  value = outputs.value;
+}
+
+
diff --git a/test/benchmark/animometer.wgsl.expected.hlsl b/test/benchmark/animometer.wgsl.expected.hlsl
new file mode 100644
index 0000000..1e37c1e
--- /dev/null
+++ b/test/benchmark/animometer.wgsl.expected.hlsl
@@ -0,0 +1,65 @@
+cbuffer cbuffer_time : register(b0, space0) {
+  uint4 time[1];
+};
+cbuffer cbuffer_uniforms : register(b1, space0) {
+  uint4 uniforms[2];
+};
+
+struct VertexOutput {
+  float4 Position;
+  float4 v_color;
+};
+struct tint_symbol_1 {
+  float4 position : TEXCOORD0;
+  float4 color : TEXCOORD1;
+};
+struct tint_symbol_2 {
+  float4 v_color : TEXCOORD0;
+  float4 Position : SV_Position;
+};
+
+VertexOutput vert_main_inner(float4 position, float4 color) {
+  float fade = ((asfloat(uniforms[1].x) + ((asfloat(time[0].x) * asfloat(uniforms[0].w)) / 10.0f)) % 1.0f);
+  if ((fade < 0.5f)) {
+    fade = (fade * 2.0f);
+  } else {
+    fade = ((1.0f - fade) * 2.0f);
+  }
+  float xpos = (position.x * asfloat(uniforms[0].x));
+  float ypos = (position.y * asfloat(uniforms[0].x));
+  float angle = ((3.141590118f * 2.0f) * fade);
+  float xrot = ((xpos * cos(angle)) - (ypos * sin(angle)));
+  float yrot = ((xpos * sin(angle)) + (ypos * cos(angle)));
+  xpos = (xrot + asfloat(uniforms[0].y));
+  ypos = (yrot + asfloat(uniforms[0].z));
+  VertexOutput output = (VertexOutput)0;
+  output.v_color = (float4(fade, (1.0f - fade), 0.0f, 1.0f) + color);
+  output.Position = float4(xpos, ypos, 0.0f, 1.0f);
+  return output;
+}
+
+tint_symbol_2 vert_main(tint_symbol_1 tint_symbol) {
+  const VertexOutput inner_result = vert_main_inner(tint_symbol.position, tint_symbol.color);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.Position = inner_result.Position;
+  wrapper_result.v_color = inner_result.v_color;
+  return wrapper_result;
+}
+
+struct tint_symbol_4 {
+  float4 v_color : TEXCOORD0;
+};
+struct tint_symbol_5 {
+  float4 value : SV_Target0;
+};
+
+float4 frag_main_inner(float4 v_color) {
+  return v_color;
+}
+
+tint_symbol_5 frag_main(tint_symbol_4 tint_symbol_3) {
+  const float4 inner_result_1 = frag_main_inner(tint_symbol_3.v_color);
+  tint_symbol_5 wrapper_result_1 = (tint_symbol_5)0;
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
diff --git a/test/benchmark/animometer.wgsl.expected.msl b/test/benchmark/animometer.wgsl.expected.msl
new file mode 100644
index 0000000..f970428
--- /dev/null
+++ b/test/benchmark/animometer.wgsl.expected.msl
@@ -0,0 +1,71 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct Time {
+  /* 0x0000 */ float value;
+};
+struct Uniforms {
+  /* 0x0000 */ float scale;
+  /* 0x0004 */ float offsetX;
+  /* 0x0008 */ float offsetY;
+  /* 0x000c */ float scalar;
+  /* 0x0010 */ float scalarOffset;
+};
+struct VertexOutput {
+  float4 Position;
+  float4 v_color;
+};
+struct tint_symbol_1 {
+  float4 position [[attribute(0)]];
+  float4 color [[attribute(1)]];
+};
+struct tint_symbol_2 {
+  float4 v_color [[user(locn0)]];
+  float4 Position [[position]];
+};
+struct tint_symbol_4 {
+  float4 v_color [[user(locn0)]];
+};
+struct tint_symbol_5 {
+  float4 value [[color(0)]];
+};
+
+VertexOutput vert_main_inner(float4 position, float4 color, const constant Uniforms* const tint_symbol_6, const constant Time* const tint_symbol_7) {
+  float fade = fmod(((*(tint_symbol_6)).scalarOffset + (((*(tint_symbol_7)).value * (*(tint_symbol_6)).scalar) / 10.0f)), 1.0f);
+  if ((fade < 0.5f)) {
+    fade = (fade * 2.0f);
+  } else {
+    fade = ((1.0f - fade) * 2.0f);
+  }
+  float xpos = (position[0] * (*(tint_symbol_6)).scale);
+  float ypos = (position[1] * (*(tint_symbol_6)).scale);
+  float angle = ((3.141590118f * 2.0f) * fade);
+  float xrot = ((xpos * cos(angle)) - (ypos * sin(angle)));
+  float yrot = ((xpos * sin(angle)) + (ypos * cos(angle)));
+  xpos = (xrot + (*(tint_symbol_6)).offsetX);
+  ypos = (yrot + (*(tint_symbol_6)).offsetY);
+  VertexOutput output = {};
+  output.v_color = (float4(fade, (1.0f - fade), 0.0f, 1.0f) + color);
+  output.Position = float4(xpos, ypos, 0.0f, 1.0f);
+  return output;
+}
+
+vertex tint_symbol_2 vert_main(const constant Uniforms* tint_symbol_8 [[buffer(0)]], const constant Time* tint_symbol_9 [[buffer(1)]], tint_symbol_1 tint_symbol [[stage_in]]) {
+  VertexOutput const inner_result = vert_main_inner(tint_symbol.position, tint_symbol.color, tint_symbol_8, tint_symbol_9);
+  tint_symbol_2 wrapper_result = {};
+  wrapper_result.Position = inner_result.Position;
+  wrapper_result.v_color = inner_result.v_color;
+  return wrapper_result;
+}
+
+float4 frag_main_inner(float4 v_color) {
+  return v_color;
+}
+
+fragment tint_symbol_5 frag_main(tint_symbol_4 tint_symbol_3 [[stage_in]]) {
+  float4 const inner_result_1 = frag_main_inner(tint_symbol_3.v_color);
+  tint_symbol_5 wrapper_result_1 = {};
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+
diff --git a/test/benchmark/animometer.wgsl.expected.spvasm b/test/benchmark/animometer.wgsl.expected.spvasm
new file mode 100644
index 0000000..305ea64
--- /dev/null
+++ b/test/benchmark/animometer.wgsl.expected.spvasm
@@ -0,0 +1,232 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 138
+; Schema: 0
+               OpCapability Shader
+         %76 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %vert_main "vert_main" %position_1 %color_1 %Position_1 %v_color_1 %vertex_point_size
+               OpEntryPoint Fragment %frag_main "frag_main" %v_color_2 %value_1
+               OpExecutionMode %frag_main OriginUpperLeft
+               OpName %position_1 "position_1"
+               OpName %color_1 "color_1"
+               OpName %Position_1 "Position_1"
+               OpName %v_color_1 "v_color_1"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %v_color_2 "v_color_2"
+               OpName %value_1 "value_1"
+               OpName %Time "Time"
+               OpMemberName %Time 0 "value"
+               OpName %time "time"
+               OpName %Uniforms "Uniforms"
+               OpMemberName %Uniforms 0 "scale"
+               OpMemberName %Uniforms 1 "offsetX"
+               OpMemberName %Uniforms 2 "offsetY"
+               OpMemberName %Uniforms 3 "scalar"
+               OpMemberName %Uniforms 4 "scalarOffset"
+               OpName %uniforms "uniforms"
+               OpName %VertexOutput "VertexOutput"
+               OpMemberName %VertexOutput 0 "Position"
+               OpMemberName %VertexOutput 1 "v_color"
+               OpName %vert_main_inner "vert_main_inner"
+               OpName %position "position"
+               OpName %color "color"
+               OpName %fade "fade"
+               OpName %xpos "xpos"
+               OpName %ypos "ypos"
+               OpName %angle "angle"
+               OpName %xrot "xrot"
+               OpName %yrot "yrot"
+               OpName %output "output"
+               OpName %vert_main "vert_main"
+               OpName %frag_main_inner "frag_main_inner"
+               OpName %v_color "v_color"
+               OpName %frag_main "frag_main"
+               OpDecorate %position_1 Location 0
+               OpDecorate %color_1 Location 1
+               OpDecorate %Position_1 BuiltIn Position
+               OpDecorate %v_color_1 Location 0
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpDecorate %v_color_2 Location 0
+               OpDecorate %value_1 Location 0
+               OpDecorate %Time Block
+               OpMemberDecorate %Time 0 Offset 0
+               OpDecorate %time NonWritable
+               OpDecorate %time Binding 0
+               OpDecorate %time DescriptorSet 0
+               OpDecorate %Uniforms Block
+               OpMemberDecorate %Uniforms 0 Offset 0
+               OpMemberDecorate %Uniforms 1 Offset 4
+               OpMemberDecorate %Uniforms 2 Offset 8
+               OpMemberDecorate %Uniforms 3 Offset 12
+               OpMemberDecorate %Uniforms 4 Offset 16
+               OpDecorate %uniforms NonWritable
+               OpDecorate %uniforms Binding 1
+               OpDecorate %uniforms DescriptorSet 0
+               OpMemberDecorate %VertexOutput 0 Offset 0
+               OpMemberDecorate %VertexOutput 1 Offset 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %position_1 = OpVariable %_ptr_Input_v4float Input
+    %color_1 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %8 = OpConstantNull %v4float
+ %Position_1 = OpVariable %_ptr_Output_v4float Output %8
+  %v_color_1 = OpVariable %_ptr_Output_v4float Output %8
+%_ptr_Output_float = OpTypePointer Output %float
+         %12 = OpConstantNull %float
+%vertex_point_size = OpVariable %_ptr_Output_float Output %12
+  %v_color_2 = OpVariable %_ptr_Input_v4float Input
+    %value_1 = OpVariable %_ptr_Output_v4float Output %8
+       %Time = OpTypeStruct %float
+%_ptr_Uniform_Time = OpTypePointer Uniform %Time
+       %time = OpVariable %_ptr_Uniform_Time Uniform
+   %Uniforms = OpTypeStruct %float %float %float %float %float
+%_ptr_Uniform_Uniforms = OpTypePointer Uniform %Uniforms
+   %uniforms = OpVariable %_ptr_Uniform_Uniforms Uniform
+%VertexOutput = OpTypeStruct %v4float %v4float
+         %21 = OpTypeFunction %VertexOutput %v4float %v4float
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+     %uint_0 = OpConstant %uint 0
+     %uint_3 = OpConstant %uint 3
+   %float_10 = OpConstant %float 10
+    %float_1 = OpConstant %float 1
+%_ptr_Function_float = OpTypePointer Function %float
+  %float_0_5 = OpConstant %float 0.5
+       %bool = OpTypeBool
+    %float_2 = OpConstant %float 2
+%float_3_14159012 = OpConstant %float 3.14159012
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+%_ptr_Function_VertexOutput = OpTypePointer Function %VertexOutput
+        %107 = OpConstantNull %VertexOutput
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+    %float_0 = OpConstant %float 0
+       %void = OpTypeVoid
+        %121 = OpTypeFunction %void
+        %130 = OpTypeFunction %v4float %v4float
+%vert_main_inner = OpFunction %VertexOutput None %21
+   %position = OpFunctionParameter %v4float
+      %color = OpFunctionParameter %v4float
+         %26 = OpLabel
+       %fade = OpVariable %_ptr_Function_float Function %12
+       %xpos = OpVariable %_ptr_Function_float Function %12
+       %ypos = OpVariable %_ptr_Function_float Function %12
+      %angle = OpVariable %_ptr_Function_float Function %12
+       %xrot = OpVariable %_ptr_Function_float Function %12
+       %yrot = OpVariable %_ptr_Function_float Function %12
+     %output = OpVariable %_ptr_Function_VertexOutput Function %107
+         %30 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_4
+         %31 = OpLoad %float %30
+         %33 = OpAccessChain %_ptr_Uniform_float %time %uint_0
+         %34 = OpLoad %float %33
+         %36 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_3
+         %37 = OpLoad %float %36
+         %38 = OpFMul %float %34 %37
+         %40 = OpFDiv %float %38 %float_10
+         %41 = OpFAdd %float %31 %40
+         %43 = OpFRem %float %41 %float_1
+               OpStore %fade %43
+         %46 = OpLoad %float %fade
+         %48 = OpFOrdLessThan %bool %46 %float_0_5
+               OpSelectionMerge %50 None
+               OpBranchConditional %48 %51 %52
+         %51 = OpLabel
+         %53 = OpLoad %float %fade
+         %55 = OpFMul %float %53 %float_2
+               OpStore %fade %55
+               OpBranch %50
+         %52 = OpLabel
+         %56 = OpLoad %float %fade
+         %57 = OpFSub %float %float_1 %56
+         %58 = OpFMul %float %57 %float_2
+               OpStore %fade %58
+               OpBranch %50
+         %50 = OpLabel
+         %59 = OpCompositeExtract %float %position 0
+         %60 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_0
+         %61 = OpLoad %float %60
+         %62 = OpFMul %float %59 %61
+               OpStore %xpos %62
+         %64 = OpCompositeExtract %float %position 1
+         %65 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_0
+         %66 = OpLoad %float %65
+         %67 = OpFMul %float %64 %66
+               OpStore %ypos %67
+         %70 = OpFMul %float %float_3_14159012 %float_2
+         %71 = OpLoad %float %fade
+         %72 = OpFMul %float %70 %71
+               OpStore %angle %72
+         %74 = OpLoad %float %xpos
+         %77 = OpLoad %float %angle
+         %75 = OpExtInst %float %76 Cos %77
+         %78 = OpFMul %float %74 %75
+         %79 = OpLoad %float %ypos
+         %81 = OpLoad %float %angle
+         %80 = OpExtInst %float %76 Sin %81
+         %82 = OpFMul %float %79 %80
+         %83 = OpFSub %float %78 %82
+               OpStore %xrot %83
+         %85 = OpLoad %float %xpos
+         %87 = OpLoad %float %angle
+         %86 = OpExtInst %float %76 Sin %87
+         %88 = OpFMul %float %85 %86
+         %89 = OpLoad %float %ypos
+         %91 = OpLoad %float %angle
+         %90 = OpExtInst %float %76 Cos %91
+         %92 = OpFMul %float %89 %90
+         %93 = OpFAdd %float %88 %92
+               OpStore %yrot %93
+         %95 = OpLoad %float %xrot
+         %97 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_1
+         %98 = OpLoad %float %97
+         %99 = OpFAdd %float %95 %98
+               OpStore %xpos %99
+        %100 = OpLoad %float %yrot
+        %102 = OpAccessChain %_ptr_Uniform_float %uniforms %uint_2
+        %103 = OpLoad %float %102
+        %104 = OpFAdd %float %100 %103
+               OpStore %ypos %104
+        %109 = OpAccessChain %_ptr_Function_v4float %output %uint_1
+        %110 = OpLoad %float %fade
+        %111 = OpLoad %float %fade
+        %112 = OpFSub %float %float_1 %111
+        %114 = OpCompositeConstruct %v4float %110 %112 %float_0 %float_1
+        %115 = OpFAdd %v4float %114 %color
+               OpStore %109 %115
+        %116 = OpAccessChain %_ptr_Function_v4float %output %uint_0
+        %117 = OpLoad %float %xpos
+        %118 = OpLoad %float %ypos
+        %119 = OpCompositeConstruct %v4float %117 %118 %float_0 %float_1
+               OpStore %116 %119
+        %120 = OpLoad %VertexOutput %output
+               OpReturnValue %120
+               OpFunctionEnd
+  %vert_main = OpFunction %void None %121
+        %124 = OpLabel
+        %126 = OpLoad %v4float %position_1
+        %127 = OpLoad %v4float %color_1
+        %125 = OpFunctionCall %VertexOutput %vert_main_inner %126 %127
+        %128 = OpCompositeExtract %v4float %125 0
+               OpStore %Position_1 %128
+        %129 = OpCompositeExtract %v4float %125 1
+               OpStore %v_color_1 %129
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
+%frag_main_inner = OpFunction %v4float None %130
+    %v_color = OpFunctionParameter %v4float
+        %133 = OpLabel
+               OpReturnValue %v_color
+               OpFunctionEnd
+  %frag_main = OpFunction %void None %121
+        %135 = OpLabel
+        %137 = OpLoad %v4float %v_color_2
+        %136 = OpFunctionCall %v4float %frag_main_inner %137
+               OpStore %value_1 %136
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/animometer.wgsl.expected.wgsl b/test/benchmark/animometer.wgsl.expected.wgsl
new file mode 100644
index 0000000..b850d14
--- /dev/null
+++ b/test/benchmark/animometer.wgsl.expected.wgsl
@@ -0,0 +1,48 @@
+struct Time {
+  value : f32;
+}
+
+struct Uniforms {
+  scale : f32;
+  offsetX : f32;
+  offsetY : f32;
+  scalar : f32;
+  scalarOffset : f32;
+}
+
+@binding(0) @group(0) var<uniform> time : Time;
+
+@binding(1) @group(0) var<uniform> uniforms : Uniforms;
+
+struct VertexOutput {
+  @builtin(position)
+  Position : vec4<f32>;
+  @location(0)
+  v_color : vec4<f32>;
+}
+
+@stage(vertex)
+fn vert_main(@location(0) position : vec4<f32>, @location(1) color : vec4<f32>) -> VertexOutput {
+  var fade : f32 = ((uniforms.scalarOffset + ((time.value * uniforms.scalar) / 10.0)) % 1.0);
+  if ((fade < 0.5)) {
+    fade = (fade * 2.0);
+  } else {
+    fade = ((1.0 - fade) * 2.0);
+  }
+  var xpos : f32 = (position.x * uniforms.scale);
+  var ypos : f32 = (position.y * uniforms.scale);
+  var angle : f32 = ((3.141590118 * 2.0) * fade);
+  var xrot : f32 = ((xpos * cos(angle)) - (ypos * sin(angle)));
+  var yrot : f32 = ((xpos * sin(angle)) + (ypos * cos(angle)));
+  xpos = (xrot + uniforms.offsetX);
+  ypos = (yrot + uniforms.offsetY);
+  var output : VertexOutput;
+  output.v_color = (vec4<f32>(fade, (1.0 - fade), 0.0, 1.0) + color);
+  output.Position = vec4<f32>(xpos, ypos, 0.0, 1.0);
+  return output;
+}
+
+@stage(fragment)
+fn frag_main(@location(0) v_color : vec4<f32>) -> @location(0) vec4<f32> {
+  return v_color;
+}
diff --git a/test/benchmark/bloom-vertical-blur.wgsl b/test/benchmark/bloom-vertical-blur.wgsl
new file mode 100644
index 0000000..6425a83
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl
@@ -0,0 +1,42 @@
+let bloomDir = vec2(0.0, 1.0);
+
+var<private> offsets : array<f32, 3> = array<f32, 3>(0.0, 1.384615421, 3.230769157);
+
+var<private> weights : array<f32, 3> = array<f32, 3>(0.227027029, 0.31621623, 0.07027027);
+
+struct BloomUniforms {
+  radius : f32;
+  dim : f32;
+}
+
+@group(0) @binding(0) var<uniform> bloom : BloomUniforms;
+
+@group(0) @binding(1) var bloomTexture : texture_2d<f32>;
+
+@group(0) @binding(2) var bloomSampler : sampler;
+
+struct FragmentInput {
+  @location(0)
+  texCoord : vec2<f32>;
+}
+
+fn getGaussianBlur(texCoord : vec2<f32>) -> vec4<f32> {
+  let texelRadius = (vec2(bloom.radius) / vec2<f32>(textureDimensions(bloomTexture)));
+  let step = (bloomDir * texelRadius);
+  var sum = vec4(0.0);
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, texCoord) * weights[0]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord + (step * 1.0))) * weights[1]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord - (step * 1.0))) * weights[1]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord + (step * 2.0))) * weights[2]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord - (step * 2.0))) * weights[2]));
+  return vec4(sum.rgb, 1.0);
+}
+
+@group(0) @binding(3) var prevTexture : texture_2d<f32>;
+
+@stage(fragment)
+fn fragmentMain(input : FragmentInput) -> @location(0) vec4<f32> {
+  let blurColor = getGaussianBlur(input.texCoord);
+  let dimColor = (textureSample(prevTexture, bloomSampler, input.texCoord) * bloom.dim);
+  return (blurColor + dimColor);
+}
diff --git a/test/benchmark/bloom-vertical-blur.wgsl.expected.glsl b/test/benchmark/bloom-vertical-blur.wgsl.expected.glsl
new file mode 100644
index 0000000..0ed2c1b
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl.expected.glsl
@@ -0,0 +1,67 @@
+#version 310 es
+precision mediump float;
+
+const vec2 bloomDir = vec2(0.0f, 1.0f);
+float weights[3] = float[3](0.227027029f, 0.31621623f, 0.07027027f);
+
+struct BloomUniforms {
+  float radius;
+  float dim;
+};
+
+layout (binding = 0) uniform BloomUniforms_1 {
+  float radius;
+  float dim;
+} bloom;
+uniform highp sampler2D bloomTexture;
+
+
+struct FragmentInput {
+  vec2 texCoord;
+};
+
+vec4 getGaussianBlur(vec2 texCoord) {
+  vec2 texelRadius = (vec2(bloom.radius) / vec2(textureSize(bloomTexture, 0)));
+  vec2 tint_symbol = (bloomDir * texelRadius);
+  vec4 sum = vec4(0.0f);
+  sum = (sum + (texture(bloomTexture, texCoord) * weights[0]));
+  sum = (sum + (texture(bloomTexture, (texCoord + (tint_symbol * 1.0f))) * weights[1]));
+  sum = (sum + (texture(bloomTexture, (texCoord - (tint_symbol * 1.0f))) * weights[1]));
+  sum = (sum + (texture(bloomTexture, (texCoord + (tint_symbol * 2.0f))) * weights[2]));
+  sum = (sum + (texture(bloomTexture, (texCoord - (tint_symbol * 2.0f))) * weights[2]));
+  return vec4(sum.rgb, 1.0f);
+}
+
+uniform highp sampler2D prevTexture;
+
+struct tint_symbol_3 {
+  vec2 texCoord;
+};
+struct tint_symbol_4 {
+  vec4 value;
+};
+
+vec4 fragmentMain_inner(FragmentInput tint_symbol_1) {
+  vec4 blurColor = getGaussianBlur(tint_symbol_1.texCoord);
+  vec4 dimColor = (texture(prevTexture, tint_symbol_1.texCoord) * bloom.dim);
+  return (blurColor + dimColor);
+}
+
+tint_symbol_4 fragmentMain(tint_symbol_3 tint_symbol_2) {
+  FragmentInput tint_symbol_5 = FragmentInput(tint_symbol_2.texCoord);
+  vec4 inner_result = fragmentMain_inner(tint_symbol_5);
+  tint_symbol_4 wrapper_result = tint_symbol_4(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+in vec2 texCoord;
+out vec4 value;
+void main() {
+  tint_symbol_3 inputs;
+  inputs.texCoord = texCoord;
+  tint_symbol_4 outputs;
+  outputs = fragmentMain(inputs);
+  value = outputs.value;
+}
+
+
diff --git a/test/benchmark/bloom-vertical-blur.wgsl.expected.hlsl b/test/benchmark/bloom-vertical-blur.wgsl.expected.hlsl
new file mode 100644
index 0000000..cde8072
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl.expected.hlsl
@@ -0,0 +1,50 @@
+static const float2 bloomDir = float2(0.0f, 1.0f);
+static float offsets[3] = {0.0f, 1.384615421f, 3.230769157f};
+static float weights[3] = {0.227027029f, 0.31621623f, 0.07027027f};
+
+cbuffer cbuffer_bloom : register(b0, space0) {
+  uint4 bloom[1];
+};
+Texture2D<float4> bloomTexture : register(t1, space0);
+SamplerState bloomSampler : register(s2, space0);
+
+struct FragmentInput {
+  float2 texCoord;
+};
+
+float4 getGaussianBlur(float2 texCoord) {
+  int2 tint_tmp;
+  bloomTexture.GetDimensions(tint_tmp.x, tint_tmp.y);
+  const float2 texelRadius = (float2((asfloat(bloom[0].x)).xx) / float2(tint_tmp));
+  const float2 step = (bloomDir * texelRadius);
+  float4 sum = float4((0.0f).xxxx);
+  sum = (sum + (bloomTexture.Sample(bloomSampler, texCoord) * weights[0]));
+  sum = (sum + (bloomTexture.Sample(bloomSampler, (texCoord + (step * 1.0f))) * weights[1]));
+  sum = (sum + (bloomTexture.Sample(bloomSampler, (texCoord - (step * 1.0f))) * weights[1]));
+  sum = (sum + (bloomTexture.Sample(bloomSampler, (texCoord + (step * 2.0f))) * weights[2]));
+  sum = (sum + (bloomTexture.Sample(bloomSampler, (texCoord - (step * 2.0f))) * weights[2]));
+  return float4(sum.rgb, 1.0f);
+}
+
+Texture2D<float4> prevTexture : register(t3, space0);
+
+struct tint_symbol_1 {
+  float2 texCoord : TEXCOORD0;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Target0;
+};
+
+float4 fragmentMain_inner(FragmentInput input) {
+  const float4 blurColor = getGaussianBlur(input.texCoord);
+  const float4 dimColor = (prevTexture.Sample(bloomSampler, input.texCoord) * asfloat(bloom[0].y));
+  return (blurColor + dimColor);
+}
+
+tint_symbol_2 fragmentMain(tint_symbol_1 tint_symbol) {
+  const FragmentInput tint_symbol_4 = {tint_symbol.texCoord};
+  const float4 inner_result = fragmentMain_inner(tint_symbol_4);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
diff --git a/test/benchmark/bloom-vertical-blur.wgsl.expected.msl b/test/benchmark/bloom-vertical-blur.wgsl.expected.msl
new file mode 100644
index 0000000..74f258c
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl.expected.msl
@@ -0,0 +1,48 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper {
+  float arr[3];
+};
+struct BloomUniforms {
+  /* 0x0000 */ float radius;
+  /* 0x0004 */ float dim;
+};
+struct FragmentInput {
+  float2 texCoord;
+};
+struct tint_symbol_1 {
+  float2 texCoord [[user(locn0)]];
+};
+struct tint_symbol_2 {
+  float4 value [[color(0)]];
+};
+
+constant float2 bloomDir = float2(0.0f, 1.0f);
+float4 getGaussianBlur(float2 texCoord, const constant BloomUniforms* const tint_symbol_4, texture2d<float, access::sample> tint_symbol_5, sampler tint_symbol_6, thread tint_array_wrapper* const tint_symbol_7) {
+  float2 const texelRadius = (float2((*(tint_symbol_4)).radius) / float2(int2(tint_symbol_5.get_width(), tint_symbol_5.get_height())));
+  float2 const step = (bloomDir * texelRadius);
+  float4 sum = float4(0.0f);
+  sum = (sum + (tint_symbol_5.sample(tint_symbol_6, texCoord) * (*(tint_symbol_7)).arr[0]));
+  sum = (sum + (tint_symbol_5.sample(tint_symbol_6, (texCoord + (step * 1.0f))) * (*(tint_symbol_7)).arr[1]));
+  sum = (sum + (tint_symbol_5.sample(tint_symbol_6, (texCoord - (step * 1.0f))) * (*(tint_symbol_7)).arr[1]));
+  sum = (sum + (tint_symbol_5.sample(tint_symbol_6, (texCoord + (step * 2.0f))) * (*(tint_symbol_7)).arr[2]));
+  sum = (sum + (tint_symbol_5.sample(tint_symbol_6, (texCoord - (step * 2.0f))) * (*(tint_symbol_7)).arr[2]));
+  return float4(float4(sum).rgb, 1.0f);
+}
+
+float4 fragmentMain_inner(FragmentInput input, const constant BloomUniforms* const tint_symbol_8, texture2d<float, access::sample> tint_symbol_9, sampler tint_symbol_10, thread tint_array_wrapper* const tint_symbol_11, texture2d<float, access::sample> tint_symbol_12) {
+  float4 const blurColor = getGaussianBlur(input.texCoord, tint_symbol_8, tint_symbol_9, tint_symbol_10, tint_symbol_11);
+  float4 const dimColor = (tint_symbol_12.sample(tint_symbol_10, input.texCoord) * (*(tint_symbol_8)).dim);
+  return (blurColor + dimColor);
+}
+
+fragment tint_symbol_2 fragmentMain(const constant BloomUniforms* tint_symbol_13 [[buffer(0)]], texture2d<float, access::sample> tint_symbol_14 [[texture(0)]], sampler tint_symbol_15 [[sampler(0)]], texture2d<float, access::sample> tint_symbol_17 [[texture(1)]], tint_symbol_1 tint_symbol [[stage_in]]) {
+  thread tint_array_wrapper tint_symbol_16 = {.arr={0.227027029f, 0.31621623f, 0.07027027f}};
+  FragmentInput const tint_symbol_3 = {.texCoord=tint_symbol.texCoord};
+  float4 const inner_result = fragmentMain_inner(tint_symbol_3, tint_symbol_13, tint_symbol_14, tint_symbol_15, &(tint_symbol_16), tint_symbol_17);
+  tint_symbol_2 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
diff --git a/test/benchmark/bloom-vertical-blur.wgsl.expected.spvasm b/test/benchmark/bloom-vertical-blur.wgsl.expected.spvasm
new file mode 100644
index 0000000..41995a4
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl.expected.spvasm
@@ -0,0 +1,202 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 144
+; Schema: 0
+               OpCapability Shader
+               OpCapability ImageQuery
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %fragmentMain "fragmentMain" %texCoord_1 %value
+               OpExecutionMode %fragmentMain OriginUpperLeft
+               OpName %texCoord_1 "texCoord_1"
+               OpName %value "value"
+               OpName %bloomDir "bloomDir"
+               OpName %offsets "offsets"
+               OpName %weights "weights"
+               OpName %BloomUniforms "BloomUniforms"
+               OpMemberName %BloomUniforms 0 "radius"
+               OpMemberName %BloomUniforms 1 "dim"
+               OpName %bloom "bloom"
+               OpName %bloomTexture "bloomTexture"
+               OpName %bloomSampler "bloomSampler"
+               OpName %prevTexture "prevTexture"
+               OpName %getGaussianBlur "getGaussianBlur"
+               OpName %texCoord "texCoord"
+               OpName %sum "sum"
+               OpName %FragmentInput "FragmentInput"
+               OpMemberName %FragmentInput 0 "texCoord"
+               OpName %fragmentMain_inner "fragmentMain_inner"
+               OpName %input "input"
+               OpName %fragmentMain "fragmentMain"
+               OpDecorate %texCoord_1 Location 0
+               OpDecorate %value Location 0
+               OpDecorate %_arr_float_uint_3 ArrayStride 4
+               OpDecorate %BloomUniforms Block
+               OpMemberDecorate %BloomUniforms 0 Offset 0
+               OpMemberDecorate %BloomUniforms 1 Offset 4
+               OpDecorate %bloom NonWritable
+               OpDecorate %bloom DescriptorSet 0
+               OpDecorate %bloom Binding 0
+               OpDecorate %bloomTexture DescriptorSet 0
+               OpDecorate %bloomTexture Binding 1
+               OpDecorate %bloomSampler DescriptorSet 0
+               OpDecorate %bloomSampler Binding 2
+               OpDecorate %prevTexture DescriptorSet 0
+               OpDecorate %prevTexture Binding 3
+               OpMemberDecorate %FragmentInput 0 Offset 0
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+ %texCoord_1 = OpVariable %_ptr_Input_v2float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %8 = OpConstantNull %v4float
+      %value = OpVariable %_ptr_Output_v4float Output %8
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+   %bloomDir = OpConstantComposite %v2float %float_0 %float_1
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+%_arr_float_uint_3 = OpTypeArray %float %uint_3
+%float_1_38461542 = OpConstant %float 1.38461542
+%float_3_23076916 = OpConstant %float 3.23076916
+         %17 = OpConstantComposite %_arr_float_uint_3 %float_0 %float_1_38461542 %float_3_23076916
+%_ptr_Private__arr_float_uint_3 = OpTypePointer Private %_arr_float_uint_3
+    %offsets = OpVariable %_ptr_Private__arr_float_uint_3 Private %17
+%float_0_227027029 = OpConstant %float 0.227027029
+%float_0_31621623 = OpConstant %float 0.31621623
+%float_0_0702702701 = OpConstant %float 0.0702702701
+         %23 = OpConstantComposite %_arr_float_uint_3 %float_0_227027029 %float_0_31621623 %float_0_0702702701
+    %weights = OpVariable %_ptr_Private__arr_float_uint_3 Private %23
+%BloomUniforms = OpTypeStruct %float %float
+%_ptr_Uniform_BloomUniforms = OpTypePointer Uniform %BloomUniforms
+      %bloom = OpVariable %_ptr_Uniform_BloomUniforms Uniform
+         %30 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_30 = OpTypePointer UniformConstant %30
+%bloomTexture = OpVariable %_ptr_UniformConstant_30 UniformConstant
+         %33 = OpTypeSampler
+%_ptr_UniformConstant_33 = OpTypePointer UniformConstant %33
+%bloomSampler = OpVariable %_ptr_UniformConstant_33 UniformConstant
+%prevTexture = OpVariable %_ptr_UniformConstant_30 UniformConstant
+         %35 = OpTypeFunction %v4float %v2float
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+      %int_0 = OpConstant %int 0
+         %52 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %59 = OpTypeSampledImage %30
+%_ptr_Private_float = OpTypePointer Private %float
+      %int_1 = OpConstant %int 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+    %v3float = OpTypeVector %float 3
+%FragmentInput = OpTypeStruct %v2float
+        %120 = OpTypeFunction %v4float %FragmentInput
+     %uint_1 = OpConstant %uint 1
+       %void = OpTypeVoid
+        %137 = OpTypeFunction %void
+%getGaussianBlur = OpFunction %v4float None %35
+   %texCoord = OpFunctionParameter %v2float
+         %38 = OpLabel
+        %sum = OpVariable %_ptr_Function_v4float Function %8
+         %41 = OpAccessChain %_ptr_Uniform_float %bloom %uint_0
+         %42 = OpLoad %float %41
+         %43 = OpCompositeConstruct %v2float %42 %42
+         %48 = OpLoad %30 %bloomTexture
+         %45 = OpImageQuerySizeLod %v2int %48 %int_0
+         %44 = OpConvertSToF %v2float %45
+         %50 = OpFDiv %v2float %43 %44
+         %51 = OpFMul %v2float %bloomDir %50
+               OpStore %sum %52
+         %55 = OpLoad %v4float %sum
+         %57 = OpLoad %33 %bloomSampler
+         %58 = OpLoad %30 %bloomTexture
+         %60 = OpSampledImage %59 %58 %57
+         %56 = OpImageSampleImplicitLod %v4float %60 %texCoord
+         %62 = OpAccessChain %_ptr_Private_float %weights %int_0
+         %63 = OpLoad %float %62
+         %64 = OpVectorTimesScalar %v4float %56 %63
+         %65 = OpFAdd %v4float %55 %64
+               OpStore %sum %65
+         %66 = OpLoad %v4float %sum
+         %68 = OpLoad %33 %bloomSampler
+         %69 = OpLoad %30 %bloomTexture
+         %70 = OpSampledImage %59 %69 %68
+         %71 = OpVectorTimesScalar %v2float %51 %float_1
+         %72 = OpFAdd %v2float %texCoord %71
+         %67 = OpImageSampleImplicitLod %v4float %70 %72
+         %74 = OpAccessChain %_ptr_Private_float %weights %int_1
+         %75 = OpLoad %float %74
+         %76 = OpVectorTimesScalar %v4float %67 %75
+         %77 = OpFAdd %v4float %66 %76
+               OpStore %sum %77
+         %78 = OpLoad %v4float %sum
+         %80 = OpLoad %33 %bloomSampler
+         %81 = OpLoad %30 %bloomTexture
+         %82 = OpSampledImage %59 %81 %80
+         %83 = OpVectorTimesScalar %v2float %51 %float_1
+         %84 = OpFSub %v2float %texCoord %83
+         %79 = OpImageSampleImplicitLod %v4float %82 %84
+         %85 = OpAccessChain %_ptr_Private_float %weights %int_1
+         %86 = OpLoad %float %85
+         %87 = OpVectorTimesScalar %v4float %79 %86
+         %88 = OpFAdd %v4float %78 %87
+               OpStore %sum %88
+         %89 = OpLoad %v4float %sum
+         %91 = OpLoad %33 %bloomSampler
+         %92 = OpLoad %30 %bloomTexture
+         %93 = OpSampledImage %59 %92 %91
+         %95 = OpVectorTimesScalar %v2float %51 %float_2
+         %96 = OpFAdd %v2float %texCoord %95
+         %90 = OpImageSampleImplicitLod %v4float %93 %96
+         %98 = OpAccessChain %_ptr_Private_float %weights %int_2
+         %99 = OpLoad %float %98
+        %100 = OpVectorTimesScalar %v4float %90 %99
+        %101 = OpFAdd %v4float %89 %100
+               OpStore %sum %101
+        %102 = OpLoad %v4float %sum
+        %104 = OpLoad %33 %bloomSampler
+        %105 = OpLoad %30 %bloomTexture
+        %106 = OpSampledImage %59 %105 %104
+        %107 = OpVectorTimesScalar %v2float %51 %float_2
+        %108 = OpFSub %v2float %texCoord %107
+        %103 = OpImageSampleImplicitLod %v4float %106 %108
+        %109 = OpAccessChain %_ptr_Private_float %weights %int_2
+        %110 = OpLoad %float %109
+        %111 = OpVectorTimesScalar %v4float %103 %110
+        %112 = OpFAdd %v4float %102 %111
+               OpStore %sum %112
+        %114 = OpLoad %v4float %sum
+        %115 = OpVectorShuffle %v3float %114 %114 0 1 2
+        %116 = OpCompositeExtract %float %115 0
+        %117 = OpCompositeExtract %float %115 1
+        %118 = OpCompositeExtract %float %115 2
+        %119 = OpCompositeConstruct %v4float %116 %117 %118 %float_1
+               OpReturnValue %119
+               OpFunctionEnd
+%fragmentMain_inner = OpFunction %v4float None %120
+      %input = OpFunctionParameter %FragmentInput
+        %124 = OpLabel
+        %126 = OpCompositeExtract %v2float %input 0
+        %125 = OpFunctionCall %v4float %getGaussianBlur %126
+        %128 = OpLoad %33 %bloomSampler
+        %129 = OpLoad %30 %prevTexture
+        %130 = OpSampledImage %59 %129 %128
+        %131 = OpCompositeExtract %v2float %input 0
+        %127 = OpImageSampleImplicitLod %v4float %130 %131
+        %133 = OpAccessChain %_ptr_Uniform_float %bloom %uint_1
+        %134 = OpLoad %float %133
+        %135 = OpVectorTimesScalar %v4float %127 %134
+        %136 = OpFAdd %v4float %125 %135
+               OpReturnValue %136
+               OpFunctionEnd
+%fragmentMain = OpFunction %void None %137
+        %140 = OpLabel
+        %142 = OpLoad %v2float %texCoord_1
+        %143 = OpCompositeConstruct %FragmentInput %142
+        %141 = OpFunctionCall %v4float %fragmentMain_inner %143
+               OpStore %value %141
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/bloom-vertical-blur.wgsl.expected.wgsl b/test/benchmark/bloom-vertical-blur.wgsl.expected.wgsl
new file mode 100644
index 0000000..6425a83
--- /dev/null
+++ b/test/benchmark/bloom-vertical-blur.wgsl.expected.wgsl
@@ -0,0 +1,42 @@
+let bloomDir = vec2(0.0, 1.0);
+
+var<private> offsets : array<f32, 3> = array<f32, 3>(0.0, 1.384615421, 3.230769157);
+
+var<private> weights : array<f32, 3> = array<f32, 3>(0.227027029, 0.31621623, 0.07027027);
+
+struct BloomUniforms {
+  radius : f32;
+  dim : f32;
+}
+
+@group(0) @binding(0) var<uniform> bloom : BloomUniforms;
+
+@group(0) @binding(1) var bloomTexture : texture_2d<f32>;
+
+@group(0) @binding(2) var bloomSampler : sampler;
+
+struct FragmentInput {
+  @location(0)
+  texCoord : vec2<f32>;
+}
+
+fn getGaussianBlur(texCoord : vec2<f32>) -> vec4<f32> {
+  let texelRadius = (vec2(bloom.radius) / vec2<f32>(textureDimensions(bloomTexture)));
+  let step = (bloomDir * texelRadius);
+  var sum = vec4(0.0);
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, texCoord) * weights[0]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord + (step * 1.0))) * weights[1]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord - (step * 1.0))) * weights[1]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord + (step * 2.0))) * weights[2]));
+  sum = (sum + (textureSample(bloomTexture, bloomSampler, (texCoord - (step * 2.0))) * weights[2]));
+  return vec4(sum.rgb, 1.0);
+}
+
+@group(0) @binding(3) var prevTexture : texture_2d<f32>;
+
+@stage(fragment)
+fn fragmentMain(input : FragmentInput) -> @location(0) vec4<f32> {
+  let blurColor = getGaussianBlur(input.texCoord);
+  let dimColor = (textureSample(prevTexture, bloomSampler, input.texCoord) * bloom.dim);
+  return (blurColor + dimColor);
+}
diff --git a/test/benchmark/cluster-lights.wgsl b/test/benchmark/cluster-lights.wgsl
new file mode 100644
index 0000000..00d4ee6
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl
@@ -0,0 +1,119 @@
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@group(0) @binding(0) var<uniform> camera : Camera;
+
+struct ClusterBounds {
+  minAABB : vec3<f32>;
+  maxAABB : vec3<f32>;
+}
+
+struct Clusters {
+  bounds : array<ClusterBounds, 27648>;
+}
+
+@group(0) @binding(1) var<storage, read> clusters : Clusters;
+
+struct ClusterLights {
+  offset : u32;
+  count : u32;
+}
+
+struct ClusterLightGroup {
+  offset : atomic<u32>;
+  lights : array<ClusterLights, 27648>;
+  indices : array<u32, 1769472>;
+}
+
+@group(0) @binding(2) var<storage, read_write> clusterLights : ClusterLightGroup;
+
+struct Light {
+  position : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+struct GlobalLights {
+  ambient : vec3<f32>;
+  dirColor : vec3<f32>;
+  dirIntensity : f32;
+  dirDirection : vec3<f32>;
+  lightCount : u32;
+  lights : array<Light>;
+}
+
+@group(0) @binding(3) var<storage, read> globalLights : GlobalLights;
+
+let tileCount = vec3(32u, 18u, 48u);
+
+fn linearDepth(depthSample : f32) -> f32 {
+  return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar));
+}
+
+fn getTile(fragCoord : vec4<f32>) -> vec3<u32> {
+  let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear)));
+  let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear))));
+  let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0));
+  return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile);
+}
+
+fn getClusterIndex(fragCoord : vec4<f32>) -> u32 {
+  let tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+fn sqDistPointAABB(point : vec3<f32>, minAABB : vec3<f32>, maxAABB : vec3<f32>) -> f32 {
+  var sqDist = 0.0;
+  for(var i : i32 = 0; (i < 3); i = (i + 1)) {
+    let v = point[i];
+    if ((v < minAABB[i])) {
+      sqDist = (sqDist + ((minAABB[i] - v) * (minAABB[i] - v)));
+    }
+    if ((v > maxAABB[i])) {
+      sqDist = (sqDist + ((v - maxAABB[i]) * (v - maxAABB[i])));
+    }
+  }
+  return sqDist;
+}
+
+@stage(compute) @workgroup_size(4, 2, 4)
+fn computeMain(@builtin(global_invocation_id) global_id : vec3<u32>) {
+  let tileIndex = ((global_id.x + (global_id.y * tileCount.x)) + ((global_id.z * tileCount.x) * tileCount.y));
+  var clusterLightCount = 0u;
+  var cluserLightIndices : array<u32, 256>;
+  for(var i = 0u; (i < globalLights.lightCount); i = (i + 1u)) {
+    let range = globalLights.lights[i].range;
+    var lightInCluster : bool = (range <= 0.0);
+    if (!(lightInCluster)) {
+      let lightViewPos = (camera.view * vec4(globalLights.lights[i].position, 1.0));
+      let sqDist = sqDistPointAABB(lightViewPos.xyz, clusters.bounds[tileIndex].minAABB, clusters.bounds[tileIndex].maxAABB);
+      lightInCluster = (sqDist <= (range * range));
+    }
+    if (lightInCluster) {
+      cluserLightIndices[clusterLightCount] = i;
+      clusterLightCount = (clusterLightCount + 1u);
+    }
+    if ((clusterLightCount == 256u)) {
+      break;
+    }
+  }
+  let lightCount = clusterLightCount;
+  var offset = atomicAdd(&(clusterLights.offset), lightCount);
+  if ((offset >= 1769472u)) {
+    return;
+  }
+  for(var i = 0u; (i < clusterLightCount); i = (i + 1u)) {
+    clusterLights.indices[(offset + i)] = cluserLightIndices[i];
+  }
+  clusterLights.lights[tileIndex].offset = offset;
+  clusterLights.lights[tileIndex].count = clusterLightCount;
+}
diff --git a/test/benchmark/cluster-lights.wgsl.expected.glsl b/test/benchmark/cluster-lights.wgsl.expected.glsl
new file mode 100644
index 0000000..4669df3
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl.expected.glsl
@@ -0,0 +1,137 @@
+#version 310 es
+precision mediump float;
+
+struct Camera {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+};
+
+layout (binding = 0) uniform Camera_1 {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+} camera;
+
+struct ClusterBounds {
+  vec3 minAABB;
+  vec3 maxAABB;
+};
+struct Clusters {
+  ClusterBounds bounds[27648];
+};
+
+layout (binding = 1) buffer Clusters_1 {
+  ClusterBounds bounds[27648];
+} clusters;
+
+struct ClusterLights {
+  uint offset;
+  uint count;
+};
+struct ClusterLightGroup {
+  uint offset;
+  ClusterLights lights[27648];
+  uint indices[1769472];
+};
+
+layout (binding = 2) buffer ClusterLightGroup_1 {
+  uint offset;
+  ClusterLights lights[27648];
+  uint indices[1769472];
+} clusterLights;
+
+struct Light {
+  vec3 position;
+  float range;
+  vec3 color;
+  float intensity;
+};
+
+layout (binding = 3) buffer GlobalLights_1 {
+  vec3 ambient;
+  vec3 dirColor;
+  float dirIntensity;
+  vec3 dirDirection;
+  uint lightCount;
+  Light lights[];
+} globalLights;
+const uvec3 tileCount = uvec3(32u, 18u, 48u);
+
+float sqDistPointAABB(vec3 point, vec3 minAABB, vec3 maxAABB) {
+  float sqDist = 0.0f;
+  {
+    for(int i = 0; (i < 3); i = (i + 1)) {
+      float v = point[i];
+      if ((v < minAABB[i])) {
+        sqDist = (sqDist + ((minAABB[i] - v) * (minAABB[i] - v)));
+      }
+      if ((v > maxAABB[i])) {
+        sqDist = (sqDist + ((v - maxAABB[i]) * (v - maxAABB[i])));
+      }
+    }
+  }
+  return sqDist;
+}
+
+struct tint_symbol_1 {
+  uvec3 global_id;
+};
+
+void computeMain_inner(uvec3 global_id) {
+  uint tileIndex = ((global_id.x + (global_id.y * tileCount.x)) + ((global_id.z * tileCount.x) * tileCount.y));
+  uint clusterLightCount = 0u;
+  uint cluserLightIndices[256] = uint[256](0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u);
+  {
+    for(uint i = 0u; (i < globalLights.lightCount); i = (i + 1u)) {
+      float range = globalLights.lights[i].range;
+      bool lightInCluster = (range <= 0.0f);
+      if (!(lightInCluster)) {
+        vec4 lightViewPos = (camera.view * vec4(globalLights.lights[i].position, 1.0f));
+        float sqDist = sqDistPointAABB(lightViewPos.xyz, clusters.bounds[tileIndex].minAABB, clusters.bounds[tileIndex].maxAABB);
+        lightInCluster = (sqDist <= (range * range));
+      }
+      if (lightInCluster) {
+        cluserLightIndices[clusterLightCount] = i;
+        clusterLightCount = (clusterLightCount + 1u);
+      }
+      if ((clusterLightCount == 256u)) {
+        break;
+      }
+    }
+  }
+  uint offset = atomicAdd(clusterLights.offset, clusterLightCount);
+  if ((offset >= 1769472u)) {
+    return;
+  }
+  {
+    for(uint i = 0u; (i < clusterLightCount); i = (i + 1u)) {
+      clusterLights.indices[(offset + i)] = cluserLightIndices[i];
+    }
+  }
+  clusterLights.lights[tileIndex].offset = offset;
+  clusterLights.lights[tileIndex].count = clusterLightCount;
+}
+
+layout(local_size_x = 4, local_size_y = 2, local_size_z = 4) in;
+void computeMain(tint_symbol_1 tint_symbol) {
+  computeMain_inner(tint_symbol.global_id);
+  return;
+}
+void main() {
+  tint_symbol_1 inputs;
+  inputs.global_id = gl_GlobalInvocationID;
+  computeMain(inputs);
+}
+
+
diff --git a/test/benchmark/cluster-lights.wgsl.expected.hlsl b/test/benchmark/cluster-lights.wgsl.expected.hlsl
new file mode 100644
index 0000000..d8b6e01
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl.expected.hlsl
@@ -0,0 +1,101 @@
+uint atomicAdd_1(RWByteAddressBuffer buffer, uint offset, uint value) {
+  uint original_value = 0;
+  buffer.InterlockedAdd(offset, value, original_value);
+  return original_value;
+}
+
+cbuffer cbuffer_camera : register(b0, space0) {
+  uint4 camera[14];
+};
+
+ByteAddressBuffer clusters : register(t1, space0);
+
+RWByteAddressBuffer clusterLights : register(u2, space0);
+
+ByteAddressBuffer globalLights : register(t3, space0);
+static const uint3 tileCount = uint3(32u, 18u, 48u);
+
+float linearDepth(float depthSample) {
+  return ((asfloat(camera[13].w) * asfloat(camera[13].z)) / mad(depthSample, (asfloat(camera[13].z) - asfloat(camera[13].w)), asfloat(camera[13].w)));
+}
+
+uint3 getTile(float4 fragCoord) {
+  const float sliceScale = (float(tileCount.z) / log2((asfloat(camera[13].w) / asfloat(camera[13].z))));
+  const float sliceBias = -(((float(tileCount.z) * log2(asfloat(camera[13].z))) / log2((asfloat(camera[13].w) / asfloat(camera[13].z)))));
+  const uint zTile = uint(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0f));
+  return uint3(uint((fragCoord.x / (asfloat(camera[13].x) / float(tileCount.x)))), uint((fragCoord.y / (asfloat(camera[13].y) / float(tileCount.y)))), zTile);
+}
+
+uint getClusterIndex(float4 fragCoord) {
+  const uint3 tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+float sqDistPointAABB(float3 tint_symbol, float3 minAABB, float3 maxAABB) {
+  float sqDist = 0.0f;
+  {
+    [loop] for(int i = 0; (i < 3); i = (i + 1)) {
+      const float v = tint_symbol[i];
+      if ((v < minAABB[i])) {
+        sqDist = (sqDist + ((minAABB[i] - v) * (minAABB[i] - v)));
+      }
+      if ((v > maxAABB[i])) {
+        sqDist = (sqDist + ((v - maxAABB[i]) * (v - maxAABB[i])));
+      }
+    }
+  }
+  return sqDist;
+}
+
+struct tint_symbol_2 {
+  uint3 global_id : SV_DispatchThreadID;
+};
+
+float4x4 tint_symbol_6(uint4 buffer[14], uint offset) {
+  const uint scalar_offset = ((offset + 0u)) / 4;
+  const uint scalar_offset_1 = ((offset + 16u)) / 4;
+  const uint scalar_offset_2 = ((offset + 32u)) / 4;
+  const uint scalar_offset_3 = ((offset + 48u)) / 4;
+  return float4x4(asfloat(buffer[scalar_offset / 4]), asfloat(buffer[scalar_offset_1 / 4]), asfloat(buffer[scalar_offset_2 / 4]), asfloat(buffer[scalar_offset_3 / 4]));
+}
+
+void computeMain_inner(uint3 global_id) {
+  const uint tileIndex = ((global_id.x + (global_id.y * tileCount.x)) + ((global_id.z * tileCount.x) * tileCount.y));
+  uint clusterLightCount = 0u;
+  uint cluserLightIndices[256] = (uint[256])0;
+  {
+    [loop] for(uint i = 0u; (i < globalLights.Load(44u)); i = (i + 1u)) {
+      const float range = asfloat(globalLights.Load(((48u + (32u * i)) + 12u)));
+      bool lightInCluster = (range <= 0.0f);
+      if (!(lightInCluster)) {
+        const float4 lightViewPos = mul(float4(asfloat(globalLights.Load3((48u + (32u * i)))), 1.0f), tint_symbol_6(camera, 128u));
+        const float sqDist = sqDistPointAABB(lightViewPos.xyz, asfloat(clusters.Load3((32u * tileIndex))), asfloat(clusters.Load3(((32u * tileIndex) + 16u))));
+        lightInCluster = (sqDist <= (range * range));
+      }
+      if (lightInCluster) {
+        cluserLightIndices[clusterLightCount] = i;
+        clusterLightCount = (clusterLightCount + 1u);
+      }
+      if ((clusterLightCount == 256u)) {
+        break;
+      }
+    }
+  }
+  uint offset = atomicAdd_1(clusterLights, 0u, clusterLightCount);
+  if ((offset >= 1769472u)) {
+    return;
+  }
+  {
+    [loop] for(uint i = 0u; (i < clusterLightCount); i = (i + 1u)) {
+      clusterLights.Store((221188u + (4u * (offset + i))), asuint(cluserLightIndices[i]));
+    }
+  }
+  clusterLights.Store((4u + (8u * tileIndex)), asuint(offset));
+  clusterLights.Store(((4u + (8u * tileIndex)) + 4u), asuint(clusterLightCount));
+}
+
+[numthreads(4, 2, 4)]
+void computeMain(tint_symbol_2 tint_symbol_1) {
+  computeMain_inner(tint_symbol_1.global_id);
+  return;
+}
diff --git a/test/benchmark/cluster-lights.wgsl.expected.msl b/test/benchmark/cluster-lights.wgsl.expected.msl
new file mode 100644
index 0000000..93a76ea
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl.expected.msl
@@ -0,0 +1,138 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+
+struct Camera {
+  /* 0x0000 */ float4x4 projection;
+  /* 0x0040 */ float4x4 inverseProjection;
+  /* 0x0080 */ float4x4 view;
+  /* 0x00c0 */ packed_float3 position;
+  /* 0x00cc */ float time;
+  /* 0x00d0 */ float2 outputSize;
+  /* 0x00d8 */ float zNear;
+  /* 0x00dc */ float zFar;
+};
+struct ClusterBounds {
+  /* 0x0000 */ packed_float3 minAABB;
+  /* 0x000c */ int8_t tint_pad[4];
+  /* 0x0010 */ packed_float3 maxAABB;
+  /* 0x001c */ int8_t tint_pad_1[4];
+};
+struct tint_array_wrapper {
+  /* 0x0000 */ ClusterBounds arr[27648];
+};
+struct Clusters {
+  /* 0x0000 */ tint_array_wrapper bounds;
+};
+struct ClusterLights {
+  /* 0x0000 */ uint offset;
+  /* 0x0004 */ uint count;
+};
+struct tint_array_wrapper_1 {
+  /* 0x0000 */ ClusterLights arr[27648];
+};
+struct tint_array_wrapper_2 {
+  /* 0x0000 */ uint arr[1769472];
+};
+struct ClusterLightGroup {
+  /* 0x0000 */ atomic_uint offset;
+  /* 0x0004 */ tint_array_wrapper_1 lights;
+  /* 0x36004 */ tint_array_wrapper_2 indices;
+};
+struct Light {
+  /* 0x0000 */ packed_float3 position;
+  /* 0x000c */ float range;
+  /* 0x0010 */ packed_float3 color;
+  /* 0x001c */ float intensity;
+};
+struct GlobalLights {
+  /* 0x0000 */ packed_float3 ambient;
+  /* 0x000c */ int8_t tint_pad_2[4];
+  /* 0x0010 */ packed_float3 dirColor;
+  /* 0x001c */ float dirIntensity;
+  /* 0x0020 */ packed_float3 dirDirection;
+  /* 0x002c */ uint lightCount;
+  /* 0x0030 */ Light lights[1];
+};
+struct tint_array_wrapper_3 {
+  uint arr[256];
+};
+
+constant uint3 tileCount = uint3(32u, 18u, 48u);
+float linearDepth(float depthSample, const constant Camera* const tint_symbol) {
+  return (((*(tint_symbol)).zFar * (*(tint_symbol)).zNear) / fma(depthSample, ((*(tint_symbol)).zNear - (*(tint_symbol)).zFar), (*(tint_symbol)).zFar));
+}
+
+uint3 getTile(float4 fragCoord, const constant Camera* const tint_symbol_1) {
+  float const sliceScale = (float(tileCount[2]) / log2(((*(tint_symbol_1)).zFar / (*(tint_symbol_1)).zNear)));
+  float const sliceBias = -(((float(tileCount[2]) * log2((*(tint_symbol_1)).zNear)) / log2(((*(tint_symbol_1)).zFar / (*(tint_symbol_1)).zNear))));
+  uint const zTile = uint(fmax(((log2(linearDepth(fragCoord[2], tint_symbol_1)) * sliceScale) + sliceBias), 0.0f));
+  return uint3(uint((fragCoord[0] / ((*(tint_symbol_1)).outputSize[0] / float(tileCount[0])))), uint((fragCoord[1] / ((*(tint_symbol_1)).outputSize[1] / float(tileCount[1])))), zTile);
+}
+
+uint getClusterIndex(float4 fragCoord, const constant Camera* const tint_symbol_2) {
+  uint3 const tile = getTile(fragCoord, tint_symbol_2);
+  return ((tile[0] + (tile[1] * tileCount[0])) + ((tile[2] * tileCount[0]) * tileCount[1]));
+}
+
+float sqDistPointAABB(float3 point, float3 minAABB, float3 maxAABB) {
+  float sqDist = 0.0f;
+  for(int i = 0; (i < 3); i = as_type<int>((as_type<uint>(i) + as_type<uint>(1)))) {
+    float const v = point[i];
+    if ((v < minAABB[i])) {
+      sqDist = (sqDist + ((minAABB[i] - v) * (minAABB[i] - v)));
+    }
+    if ((v > maxAABB[i])) {
+      sqDist = (sqDist + ((v - maxAABB[i]) * (v - maxAABB[i])));
+    }
+  }
+  return sqDist;
+}
+
+void computeMain_inner(uint3 global_id, const device GlobalLights* const tint_symbol_3, const constant Camera* const tint_symbol_4, const device Clusters* const tint_symbol_5, device ClusterLightGroup* const tint_symbol_6) {
+  uint const tileIndex = ((global_id[0] + (global_id[1] * tileCount[0])) + ((global_id[2] * tileCount[0]) * tileCount[1]));
+  uint clusterLightCount = 0u;
+  tint_array_wrapper_3 cluserLightIndices = {};
+  for(uint i = 0u; (i < (*(tint_symbol_3)).lightCount); i = (i + 1u)) {
+    float const range = (*(tint_symbol_3)).lights[i].range;
+    bool lightInCluster = (range <= 0.0f);
+    if (!(lightInCluster)) {
+      float4 const lightViewPos = ((*(tint_symbol_4)).view * float4((*(tint_symbol_3)).lights[i].position, 1.0f));
+      float const sqDist = sqDistPointAABB(float4(lightViewPos).xyz, (*(tint_symbol_5)).bounds.arr[tileIndex].minAABB, (*(tint_symbol_5)).bounds.arr[tileIndex].maxAABB);
+      lightInCluster = (sqDist <= (range * range));
+    }
+    if (lightInCluster) {
+      cluserLightIndices.arr[clusterLightCount] = i;
+      clusterLightCount = (clusterLightCount + 1u);
+    }
+    if ((clusterLightCount == 256u)) {
+      break;
+    }
+  }
+  uint const lightCount = clusterLightCount;
+  uint offset = atomic_fetch_add_explicit(&((*(tint_symbol_6)).offset), lightCount, memory_order_relaxed);
+  if ((offset >= 1769472u)) {
+    return;
+  }
+  for(uint i = 0u; (i < clusterLightCount); i = (i + 1u)) {
+    (*(tint_symbol_6)).indices.arr[(offset + i)] = cluserLightIndices.arr[i];
+  }
+  (*(tint_symbol_6)).lights.arr[tileIndex].offset = offset;
+  (*(tint_symbol_6)).lights.arr[tileIndex].count = clusterLightCount;
+}
+
+kernel void computeMain(const device GlobalLights* tint_symbol_7 [[buffer(2)]], const constant Camera* tint_symbol_8 [[buffer(0)]], const device Clusters* tint_symbol_9 [[buffer(3)]], device ClusterLightGroup* tint_symbol_10 [[buffer(1)]], uint3 global_id [[thread_position_in_grid]]) {
+  computeMain_inner(global_id, tint_symbol_7, tint_symbol_8, tint_symbol_9, tint_symbol_10);
+  return;
+}
+
diff --git a/test/benchmark/cluster-lights.wgsl.expected.spvasm b/test/benchmark/cluster-lights.wgsl.expected.spvasm
new file mode 100644
index 0000000..f2374b2
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl.expected.spvasm
@@ -0,0 +1,504 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 312
+; Schema: 0
+               OpCapability Shader
+         %48 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %computeMain "computeMain" %global_id_1
+               OpExecutionMode %computeMain LocalSize 4 2 4
+               OpName %global_id_1 "global_id_1"
+               OpName %Camera "Camera"
+               OpMemberName %Camera 0 "projection"
+               OpMemberName %Camera 1 "inverseProjection"
+               OpMemberName %Camera 2 "view"
+               OpMemberName %Camera 3 "position"
+               OpMemberName %Camera 4 "time"
+               OpMemberName %Camera 5 "outputSize"
+               OpMemberName %Camera 6 "zNear"
+               OpMemberName %Camera 7 "zFar"
+               OpName %camera "camera"
+               OpName %Clusters "Clusters"
+               OpMemberName %Clusters 0 "bounds"
+               OpName %ClusterBounds "ClusterBounds"
+               OpMemberName %ClusterBounds 0 "minAABB"
+               OpMemberName %ClusterBounds 1 "maxAABB"
+               OpName %clusters "clusters"
+               OpName %ClusterLightGroup "ClusterLightGroup"
+               OpMemberName %ClusterLightGroup 0 "offset"
+               OpMemberName %ClusterLightGroup 1 "lights"
+               OpName %ClusterLights "ClusterLights"
+               OpMemberName %ClusterLights 0 "offset"
+               OpMemberName %ClusterLights 1 "count"
+               OpMemberName %ClusterLightGroup 2 "indices"
+               OpName %clusterLights "clusterLights"
+               OpName %GlobalLights "GlobalLights"
+               OpMemberName %GlobalLights 0 "ambient"
+               OpMemberName %GlobalLights 1 "dirColor"
+               OpMemberName %GlobalLights 2 "dirIntensity"
+               OpMemberName %GlobalLights 3 "dirDirection"
+               OpMemberName %GlobalLights 4 "lightCount"
+               OpMemberName %GlobalLights 5 "lights"
+               OpName %Light "Light"
+               OpMemberName %Light 0 "position"
+               OpMemberName %Light 1 "range"
+               OpMemberName %Light 2 "color"
+               OpMemberName %Light 3 "intensity"
+               OpName %globalLights "globalLights"
+               OpName %tileCount "tileCount"
+               OpName %linearDepth "linearDepth"
+               OpName %depthSample "depthSample"
+               OpName %getTile "getTile"
+               OpName %fragCoord "fragCoord"
+               OpName %getClusterIndex "getClusterIndex"
+               OpName %fragCoord_0 "fragCoord"
+               OpName %sqDistPointAABB "sqDistPointAABB"
+               OpName %point "point"
+               OpName %minAABB "minAABB"
+               OpName %maxAABB "maxAABB"
+               OpName %sqDist "sqDist"
+               OpName %i "i"
+               OpName %computeMain_inner "computeMain_inner"
+               OpName %global_id "global_id"
+               OpName %clusterLightCount "clusterLightCount"
+               OpName %cluserLightIndices "cluserLightIndices"
+               OpName %i_0 "i"
+               OpName %lightInCluster "lightInCluster"
+               OpName %offset "offset"
+               OpName %i_1 "i"
+               OpName %computeMain "computeMain"
+               OpDecorate %global_id_1 BuiltIn GlobalInvocationId
+               OpDecorate %Camera Block
+               OpMemberDecorate %Camera 0 Offset 0
+               OpMemberDecorate %Camera 0 ColMajor
+               OpMemberDecorate %Camera 0 MatrixStride 16
+               OpMemberDecorate %Camera 1 Offset 64
+               OpMemberDecorate %Camera 1 ColMajor
+               OpMemberDecorate %Camera 1 MatrixStride 16
+               OpMemberDecorate %Camera 2 Offset 128
+               OpMemberDecorate %Camera 2 ColMajor
+               OpMemberDecorate %Camera 2 MatrixStride 16
+               OpMemberDecorate %Camera 3 Offset 192
+               OpMemberDecorate %Camera 4 Offset 204
+               OpMemberDecorate %Camera 5 Offset 208
+               OpMemberDecorate %Camera 6 Offset 216
+               OpMemberDecorate %Camera 7 Offset 220
+               OpDecorate %camera NonWritable
+               OpDecorate %camera DescriptorSet 0
+               OpDecorate %camera Binding 0
+               OpDecorate %Clusters Block
+               OpMemberDecorate %Clusters 0 Offset 0
+               OpMemberDecorate %ClusterBounds 0 Offset 0
+               OpMemberDecorate %ClusterBounds 1 Offset 16
+               OpDecorate %_arr_ClusterBounds_uint_27648 ArrayStride 32
+               OpDecorate %clusters NonWritable
+               OpDecorate %clusters DescriptorSet 0
+               OpDecorate %clusters Binding 1
+               OpDecorate %ClusterLightGroup Block
+               OpMemberDecorate %ClusterLightGroup 0 Offset 0
+               OpMemberDecorate %ClusterLightGroup 1 Offset 4
+               OpMemberDecorate %ClusterLights 0 Offset 0
+               OpMemberDecorate %ClusterLights 1 Offset 4
+               OpDecorate %_arr_ClusterLights_uint_27648 ArrayStride 8
+               OpMemberDecorate %ClusterLightGroup 2 Offset 221188
+               OpDecorate %_arr_uint_uint_1769472 ArrayStride 4
+               OpDecorate %clusterLights DescriptorSet 0
+               OpDecorate %clusterLights Binding 2
+               OpDecorate %GlobalLights Block
+               OpMemberDecorate %GlobalLights 0 Offset 0
+               OpMemberDecorate %GlobalLights 1 Offset 16
+               OpMemberDecorate %GlobalLights 2 Offset 28
+               OpMemberDecorate %GlobalLights 3 Offset 32
+               OpMemberDecorate %GlobalLights 4 Offset 44
+               OpMemberDecorate %GlobalLights 5 Offset 48
+               OpMemberDecorate %Light 0 Offset 0
+               OpMemberDecorate %Light 1 Offset 12
+               OpMemberDecorate %Light 2 Offset 16
+               OpMemberDecorate %Light 3 Offset 28
+               OpDecorate %_runtimearr_Light ArrayStride 32
+               OpDecorate %globalLights NonWritable
+               OpDecorate %globalLights DescriptorSet 0
+               OpDecorate %globalLights Binding 3
+               OpDecorate %_arr_uint_uint_256 ArrayStride 4
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%global_id_1 = OpVariable %_ptr_Input_v3uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat4v4float = OpTypeMatrix %v4float 4
+    %v3float = OpTypeVector %float 3
+    %v2float = OpTypeVector %float 2
+     %Camera = OpTypeStruct %mat4v4float %mat4v4float %mat4v4float %v3float %float %v2float %float %float
+%_ptr_Uniform_Camera = OpTypePointer Uniform %Camera
+     %camera = OpVariable %_ptr_Uniform_Camera Uniform
+%ClusterBounds = OpTypeStruct %v3float %v3float
+ %uint_27648 = OpConstant %uint 27648
+%_arr_ClusterBounds_uint_27648 = OpTypeArray %ClusterBounds %uint_27648
+   %Clusters = OpTypeStruct %_arr_ClusterBounds_uint_27648
+%_ptr_StorageBuffer_Clusters = OpTypePointer StorageBuffer %Clusters
+   %clusters = OpVariable %_ptr_StorageBuffer_Clusters StorageBuffer
+%ClusterLights = OpTypeStruct %uint %uint
+%_arr_ClusterLights_uint_27648 = OpTypeArray %ClusterLights %uint_27648
+%uint_1769472 = OpConstant %uint 1769472
+%_arr_uint_uint_1769472 = OpTypeArray %uint %uint_1769472
+%ClusterLightGroup = OpTypeStruct %uint %_arr_ClusterLights_uint_27648 %_arr_uint_uint_1769472
+%_ptr_StorageBuffer_ClusterLightGroup = OpTypePointer StorageBuffer %ClusterLightGroup
+%clusterLights = OpVariable %_ptr_StorageBuffer_ClusterLightGroup StorageBuffer
+      %Light = OpTypeStruct %v3float %float %v3float %float
+%_runtimearr_Light = OpTypeRuntimeArray %Light
+%GlobalLights = OpTypeStruct %v3float %v3float %float %v3float %uint %_runtimearr_Light
+%_ptr_StorageBuffer_GlobalLights = OpTypePointer StorageBuffer %GlobalLights
+%globalLights = OpVariable %_ptr_StorageBuffer_GlobalLights StorageBuffer
+    %uint_32 = OpConstant %uint 32
+    %uint_18 = OpConstant %uint 18
+    %uint_48 = OpConstant %uint 48
+  %tileCount = OpConstantComposite %v3uint %uint_32 %uint_18 %uint_48
+         %35 = OpTypeFunction %float %float
+     %uint_7 = OpConstant %uint 7
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+     %uint_6 = OpConstant %uint 6
+         %57 = OpTypeFunction %v3uint %v4float
+    %float_0 = OpConstant %float 0
+     %uint_5 = OpConstant %uint 5
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+        %112 = OpTypeFunction %uint %v4float
+        %128 = OpTypeFunction %float %v3float %v3float %v3float
+%_ptr_Function_float = OpTypePointer Function %float
+        %136 = OpConstantNull %float
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%_ptr_Function_int = OpTypePointer Function %int
+        %141 = OpConstantNull %int
+      %int_3 = OpConstant %int 3
+       %bool = OpTypeBool
+      %int_1 = OpConstant %int 1
+       %void = OpTypeVoid
+        %187 = OpTypeFunction %void %v3uint
+%_ptr_Function_uint = OpTypePointer Function %uint
+        %205 = OpConstantNull %uint
+   %uint_256 = OpConstant %uint 256
+%_arr_uint_uint_256 = OpTypeArray %uint %uint_256
+%_ptr_Function__arr_uint_uint_256 = OpTypePointer Function %_arr_uint_uint_256
+        %210 = OpConstantNull %_arr_uint_uint_256
+     %uint_4 = OpConstant %uint 4
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+%_ptr_Function_bool = OpTypePointer Function %bool
+        %232 = OpConstantNull %bool
+     %uint_2 = OpConstant %uint 2
+%_ptr_Uniform_mat4v4float = OpTypePointer Uniform %mat4v4float
+%_ptr_StorageBuffer_v3float = OpTypePointer StorageBuffer %v3float
+    %float_1 = OpConstant %float 1
+%_ptr_StorageBuffer_uint_0 = OpTypePointer StorageBuffer %uint
+        %307 = OpTypeFunction %void
+%linearDepth = OpFunction %float None %35
+%depthSample = OpFunctionParameter %float
+         %38 = OpLabel
+         %41 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+         %42 = OpLoad %float %41
+         %44 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+         %45 = OpLoad %float %44
+         %46 = OpFMul %float %42 %45
+         %49 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+         %50 = OpLoad %float %49
+         %51 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+         %52 = OpLoad %float %51
+         %53 = OpFSub %float %50 %52
+         %54 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+         %55 = OpLoad %float %54
+         %47 = OpExtInst %float %48 Fma %depthSample %53 %55
+         %56 = OpFDiv %float %46 %47
+               OpReturnValue %56
+               OpFunctionEnd
+    %getTile = OpFunction %v3uint None %57
+  %fragCoord = OpFunctionParameter %v4float
+         %60 = OpLabel
+         %62 = OpCompositeExtract %uint %tileCount 2
+         %61 = OpConvertUToF %float %62
+         %64 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+         %65 = OpLoad %float %64
+         %66 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+         %67 = OpLoad %float %66
+         %68 = OpFDiv %float %65 %67
+         %63 = OpExtInst %float %48 Log2 %68
+         %69 = OpFDiv %float %61 %63
+         %72 = OpCompositeExtract %uint %tileCount 2
+         %71 = OpConvertUToF %float %72
+         %74 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+         %75 = OpLoad %float %74
+         %73 = OpExtInst %float %48 Log2 %75
+         %76 = OpFMul %float %71 %73
+         %78 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+         %79 = OpLoad %float %78
+         %80 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+         %81 = OpLoad %float %80
+         %82 = OpFDiv %float %79 %81
+         %77 = OpExtInst %float %48 Log2 %82
+         %83 = OpFDiv %float %76 %77
+         %70 = OpFNegate %float %83
+         %88 = OpCompositeExtract %float %fragCoord 2
+         %87 = OpFunctionCall %float %linearDepth %88
+         %86 = OpExtInst %float %48 Log2 %87
+         %89 = OpFMul %float %86 %69
+         %90 = OpFAdd %float %89 %70
+         %85 = OpExtInst %float %48 NMax %90 %float_0
+         %84 = OpConvertFToU %uint %85
+         %93 = OpCompositeExtract %float %fragCoord 0
+         %96 = OpAccessChain %_ptr_Uniform_float %camera %uint_5 %uint_0
+         %97 = OpLoad %float %96
+         %99 = OpCompositeExtract %uint %tileCount 0
+         %98 = OpConvertUToF %float %99
+        %100 = OpFDiv %float %97 %98
+        %101 = OpFDiv %float %93 %100
+         %92 = OpConvertFToU %uint %101
+        %103 = OpCompositeExtract %float %fragCoord 1
+        %105 = OpAccessChain %_ptr_Uniform_float %camera %uint_5 %uint_1
+        %106 = OpLoad %float %105
+        %108 = OpCompositeExtract %uint %tileCount 1
+        %107 = OpConvertUToF %float %108
+        %109 = OpFDiv %float %106 %107
+        %110 = OpFDiv %float %103 %109
+        %102 = OpConvertFToU %uint %110
+        %111 = OpCompositeConstruct %v3uint %92 %102 %84
+               OpReturnValue %111
+               OpFunctionEnd
+%getClusterIndex = OpFunction %uint None %112
+%fragCoord_0 = OpFunctionParameter %v4float
+        %115 = OpLabel
+        %116 = OpFunctionCall %v3uint %getTile %fragCoord_0
+        %117 = OpCompositeExtract %uint %116 0
+        %118 = OpCompositeExtract %uint %116 1
+        %119 = OpCompositeExtract %uint %tileCount 0
+        %120 = OpIMul %uint %118 %119
+        %121 = OpIAdd %uint %117 %120
+        %122 = OpCompositeExtract %uint %116 2
+        %123 = OpCompositeExtract %uint %tileCount 0
+        %124 = OpIMul %uint %122 %123
+        %125 = OpCompositeExtract %uint %tileCount 1
+        %126 = OpIMul %uint %124 %125
+        %127 = OpIAdd %uint %121 %126
+               OpReturnValue %127
+               OpFunctionEnd
+%sqDistPointAABB = OpFunction %float None %128
+      %point = OpFunctionParameter %v3float
+    %minAABB = OpFunctionParameter %v3float
+    %maxAABB = OpFunctionParameter %v3float
+        %133 = OpLabel
+     %sqDist = OpVariable %_ptr_Function_float Function %136
+          %i = OpVariable %_ptr_Function_int Function %141
+               OpStore %sqDist %float_0
+               OpStore %i %int_0
+               OpBranch %142
+        %142 = OpLabel
+               OpLoopMerge %143 %144 None
+               OpBranch %145
+        %145 = OpLabel
+        %147 = OpLoad %int %i
+        %149 = OpSLessThan %bool %147 %int_3
+        %146 = OpLogicalNot %bool %149
+               OpSelectionMerge %151 None
+               OpBranchConditional %146 %152 %151
+        %152 = OpLabel
+               OpBranch %143
+        %151 = OpLabel
+        %153 = OpLoad %int %i
+        %154 = OpVectorExtractDynamic %float %point %153
+        %155 = OpLoad %int %i
+        %156 = OpVectorExtractDynamic %float %minAABB %155
+        %157 = OpFOrdLessThan %bool %154 %156
+               OpSelectionMerge %158 None
+               OpBranchConditional %157 %159 %158
+        %159 = OpLabel
+        %160 = OpLoad %float %sqDist
+        %161 = OpLoad %int %i
+        %162 = OpVectorExtractDynamic %float %minAABB %161
+        %163 = OpFSub %float %162 %154
+        %164 = OpLoad %int %i
+        %165 = OpVectorExtractDynamic %float %minAABB %164
+        %166 = OpFSub %float %165 %154
+        %167 = OpFMul %float %163 %166
+        %168 = OpFAdd %float %160 %167
+               OpStore %sqDist %168
+               OpBranch %158
+        %158 = OpLabel
+        %169 = OpLoad %int %i
+        %170 = OpVectorExtractDynamic %float %maxAABB %169
+        %171 = OpFOrdGreaterThan %bool %154 %170
+               OpSelectionMerge %172 None
+               OpBranchConditional %171 %173 %172
+        %173 = OpLabel
+        %174 = OpLoad %float %sqDist
+        %175 = OpLoad %int %i
+        %176 = OpVectorExtractDynamic %float %maxAABB %175
+        %177 = OpFSub %float %154 %176
+        %178 = OpLoad %int %i
+        %179 = OpVectorExtractDynamic %float %maxAABB %178
+        %180 = OpFSub %float %154 %179
+        %181 = OpFMul %float %177 %180
+        %182 = OpFAdd %float %174 %181
+               OpStore %sqDist %182
+               OpBranch %172
+        %172 = OpLabel
+               OpBranch %144
+        %144 = OpLabel
+        %183 = OpLoad %int %i
+        %185 = OpIAdd %int %183 %int_1
+               OpStore %i %185
+               OpBranch %142
+        %143 = OpLabel
+        %186 = OpLoad %float %sqDist
+               OpReturnValue %186
+               OpFunctionEnd
+%computeMain_inner = OpFunction %void None %187
+  %global_id = OpFunctionParameter %v3uint
+        %191 = OpLabel
+%clusterLightCount = OpVariable %_ptr_Function_uint Function %205
+%cluserLightIndices = OpVariable %_ptr_Function__arr_uint_uint_256 Function %210
+        %i_0 = OpVariable %_ptr_Function_uint Function %205
+%lightInCluster = OpVariable %_ptr_Function_bool Function %232
+     %offset = OpVariable %_ptr_Function_uint Function %205
+        %i_1 = OpVariable %_ptr_Function_uint Function %205
+        %192 = OpCompositeExtract %uint %global_id 0
+        %193 = OpCompositeExtract %uint %global_id 1
+        %194 = OpCompositeExtract %uint %tileCount 0
+        %195 = OpIMul %uint %193 %194
+        %196 = OpIAdd %uint %192 %195
+        %197 = OpCompositeExtract %uint %global_id 2
+        %198 = OpCompositeExtract %uint %tileCount 0
+        %199 = OpIMul %uint %197 %198
+        %200 = OpCompositeExtract %uint %tileCount 1
+        %201 = OpIMul %uint %199 %200
+        %202 = OpIAdd %uint %196 %201
+               OpStore %clusterLightCount %uint_0
+               OpStore %i_0 %uint_0
+               OpBranch %212
+        %212 = OpLabel
+               OpLoopMerge %213 %214 None
+               OpBranch %215
+        %215 = OpLabel
+        %217 = OpLoad %uint %i_0
+        %220 = OpAccessChain %_ptr_StorageBuffer_uint %globalLights %uint_4
+        %221 = OpLoad %uint %220
+        %222 = OpULessThan %bool %217 %221
+        %216 = OpLogicalNot %bool %222
+               OpSelectionMerge %223 None
+               OpBranchConditional %216 %224 %223
+        %224 = OpLabel
+               OpBranch %213
+        %223 = OpLabel
+        %225 = OpLoad %uint %i_0
+        %227 = OpAccessChain %_ptr_StorageBuffer_float %globalLights %uint_5 %225 %uint_1
+        %228 = OpLoad %float %227
+        %229 = OpFOrdLessThanEqual %bool %228 %float_0
+               OpStore %lightInCluster %229
+        %234 = OpLoad %bool %lightInCluster
+        %233 = OpLogicalNot %bool %234
+               OpSelectionMerge %235 None
+               OpBranchConditional %233 %236 %235
+        %236 = OpLabel
+        %239 = OpAccessChain %_ptr_Uniform_mat4v4float %camera %uint_2
+        %240 = OpLoad %mat4v4float %239
+        %241 = OpLoad %uint %i_0
+        %243 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %uint_5 %241 %uint_0
+        %244 = OpLoad %v3float %243
+        %245 = OpCompositeExtract %float %244 0
+        %246 = OpCompositeExtract %float %244 1
+        %247 = OpCompositeExtract %float %244 2
+        %249 = OpCompositeConstruct %v4float %245 %246 %247 %float_1
+        %250 = OpMatrixTimesVector %v4float %240 %249
+        %252 = OpVectorShuffle %v3float %250 %250 0 1 2
+        %253 = OpAccessChain %_ptr_StorageBuffer_v3float %clusters %uint_0 %202 %uint_0
+        %254 = OpLoad %v3float %253
+        %255 = OpAccessChain %_ptr_StorageBuffer_v3float %clusters %uint_0 %202 %uint_1
+        %256 = OpLoad %v3float %255
+        %251 = OpFunctionCall %float %sqDistPointAABB %252 %254 %256
+        %257 = OpFMul %float %228 %228
+        %258 = OpFOrdLessThanEqual %bool %251 %257
+               OpStore %lightInCluster %258
+               OpBranch %235
+        %235 = OpLabel
+        %259 = OpLoad %bool %lightInCluster
+               OpSelectionMerge %260 None
+               OpBranchConditional %259 %261 %260
+        %261 = OpLabel
+        %262 = OpLoad %uint %clusterLightCount
+        %263 = OpAccessChain %_ptr_Function_uint %cluserLightIndices %262
+        %264 = OpLoad %uint %i_0
+               OpStore %263 %264
+        %265 = OpLoad %uint %clusterLightCount
+        %266 = OpIAdd %uint %265 %uint_1
+               OpStore %clusterLightCount %266
+               OpBranch %260
+        %260 = OpLabel
+        %267 = OpLoad %uint %clusterLightCount
+        %268 = OpIEqual %bool %267 %uint_256
+               OpSelectionMerge %269 None
+               OpBranchConditional %268 %270 %269
+        %270 = OpLabel
+               OpBranch %213
+        %269 = OpLabel
+               OpBranch %214
+        %214 = OpLabel
+        %271 = OpLoad %uint %i_0
+        %272 = OpIAdd %uint %271 %uint_1
+               OpStore %i_0 %272
+               OpBranch %212
+        %213 = OpLabel
+        %273 = OpLoad %uint %clusterLightCount
+        %277 = OpAccessChain %_ptr_StorageBuffer_uint_0 %clusterLights %uint_0
+        %274 = OpAtomicIAdd %uint %277 %uint_1 %uint_0 %273
+               OpStore %offset %274
+        %279 = OpLoad %uint %offset
+        %280 = OpUGreaterThanEqual %bool %279 %uint_1769472
+               OpSelectionMerge %281 None
+               OpBranchConditional %280 %282 %281
+        %282 = OpLabel
+               OpReturn
+        %281 = OpLabel
+               OpStore %i_1 %uint_0
+               OpBranch %284
+        %284 = OpLabel
+               OpLoopMerge %285 %286 None
+               OpBranch %287
+        %287 = OpLabel
+        %289 = OpLoad %uint %i_1
+        %290 = OpLoad %uint %clusterLightCount
+        %291 = OpULessThan %bool %289 %290
+        %288 = OpLogicalNot %bool %291
+               OpSelectionMerge %292 None
+               OpBranchConditional %288 %293 %292
+        %293 = OpLabel
+               OpBranch %285
+        %292 = OpLabel
+        %294 = OpLoad %uint %offset
+        %295 = OpLoad %uint %i_1
+        %296 = OpIAdd %uint %294 %295
+        %297 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %uint_2 %296
+        %298 = OpLoad %uint %i_1
+        %299 = OpAccessChain %_ptr_Function_uint %cluserLightIndices %298
+        %300 = OpLoad %uint %299
+               OpStore %297 %300
+               OpBranch %286
+        %286 = OpLabel
+        %301 = OpLoad %uint %i_1
+        %302 = OpIAdd %uint %301 %uint_1
+               OpStore %i_1 %302
+               OpBranch %284
+        %285 = OpLabel
+        %303 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %uint_1 %202 %uint_0
+        %304 = OpLoad %uint %offset
+               OpStore %303 %304
+        %305 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %uint_1 %202 %uint_1
+        %306 = OpLoad %uint %clusterLightCount
+               OpStore %305 %306
+               OpReturn
+               OpFunctionEnd
+%computeMain = OpFunction %void None %307
+        %309 = OpLabel
+        %311 = OpLoad %v3uint %global_id_1
+        %310 = OpFunctionCall %void %computeMain_inner %311
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/cluster-lights.wgsl.expected.wgsl b/test/benchmark/cluster-lights.wgsl.expected.wgsl
new file mode 100644
index 0000000..00d4ee6
--- /dev/null
+++ b/test/benchmark/cluster-lights.wgsl.expected.wgsl
@@ -0,0 +1,119 @@
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@group(0) @binding(0) var<uniform> camera : Camera;
+
+struct ClusterBounds {
+  minAABB : vec3<f32>;
+  maxAABB : vec3<f32>;
+}
+
+struct Clusters {
+  bounds : array<ClusterBounds, 27648>;
+}
+
+@group(0) @binding(1) var<storage, read> clusters : Clusters;
+
+struct ClusterLights {
+  offset : u32;
+  count : u32;
+}
+
+struct ClusterLightGroup {
+  offset : atomic<u32>;
+  lights : array<ClusterLights, 27648>;
+  indices : array<u32, 1769472>;
+}
+
+@group(0) @binding(2) var<storage, read_write> clusterLights : ClusterLightGroup;
+
+struct Light {
+  position : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+struct GlobalLights {
+  ambient : vec3<f32>;
+  dirColor : vec3<f32>;
+  dirIntensity : f32;
+  dirDirection : vec3<f32>;
+  lightCount : u32;
+  lights : array<Light>;
+}
+
+@group(0) @binding(3) var<storage, read> globalLights : GlobalLights;
+
+let tileCount = vec3(32u, 18u, 48u);
+
+fn linearDepth(depthSample : f32) -> f32 {
+  return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar));
+}
+
+fn getTile(fragCoord : vec4<f32>) -> vec3<u32> {
+  let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear)));
+  let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear))));
+  let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0));
+  return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile);
+}
+
+fn getClusterIndex(fragCoord : vec4<f32>) -> u32 {
+  let tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+fn sqDistPointAABB(point : vec3<f32>, minAABB : vec3<f32>, maxAABB : vec3<f32>) -> f32 {
+  var sqDist = 0.0;
+  for(var i : i32 = 0; (i < 3); i = (i + 1)) {
+    let v = point[i];
+    if ((v < minAABB[i])) {
+      sqDist = (sqDist + ((minAABB[i] - v) * (minAABB[i] - v)));
+    }
+    if ((v > maxAABB[i])) {
+      sqDist = (sqDist + ((v - maxAABB[i]) * (v - maxAABB[i])));
+    }
+  }
+  return sqDist;
+}
+
+@stage(compute) @workgroup_size(4, 2, 4)
+fn computeMain(@builtin(global_invocation_id) global_id : vec3<u32>) {
+  let tileIndex = ((global_id.x + (global_id.y * tileCount.x)) + ((global_id.z * tileCount.x) * tileCount.y));
+  var clusterLightCount = 0u;
+  var cluserLightIndices : array<u32, 256>;
+  for(var i = 0u; (i < globalLights.lightCount); i = (i + 1u)) {
+    let range = globalLights.lights[i].range;
+    var lightInCluster : bool = (range <= 0.0);
+    if (!(lightInCluster)) {
+      let lightViewPos = (camera.view * vec4(globalLights.lights[i].position, 1.0));
+      let sqDist = sqDistPointAABB(lightViewPos.xyz, clusters.bounds[tileIndex].minAABB, clusters.bounds[tileIndex].maxAABB);
+      lightInCluster = (sqDist <= (range * range));
+    }
+    if (lightInCluster) {
+      cluserLightIndices[clusterLightCount] = i;
+      clusterLightCount = (clusterLightCount + 1u);
+    }
+    if ((clusterLightCount == 256u)) {
+      break;
+    }
+  }
+  let lightCount = clusterLightCount;
+  var offset = atomicAdd(&(clusterLights.offset), lightCount);
+  if ((offset >= 1769472u)) {
+    return;
+  }
+  for(var i = 0u; (i < clusterLightCount); i = (i + 1u)) {
+    clusterLights.indices[(offset + i)] = cluserLightIndices[i];
+  }
+  clusterLights.lights[tileIndex].offset = offset;
+  clusterLights.lights[tileIndex].count = clusterLightCount;
+}
diff --git a/test/benchmark/empty.wgsl.expected.glsl b/test/benchmark/empty.wgsl.expected.glsl
new file mode 100644
index 0000000..c15c453
--- /dev/null
+++ b/test/benchmark/empty.wgsl.expected.glsl
@@ -0,0 +1,12 @@
+#version 310 es
+precision mediump float;
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void unused_entry_point() {
+  return;
+}
+void main() {
+  unused_entry_point();
+}
+
+
diff --git a/test/benchmark/metaball-isosurface.wgsl b/test/benchmark/metaball-isosurface.wgsl
new file mode 100644
index 0000000..40374f4
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl
@@ -0,0 +1,205 @@
+struct Tables {
+  edges : array<u32, 256>;
+  tris : array<i32, 4096>;
+}
+
+@group(0) @binding(0) var<storage> tables : Tables;
+
+struct IsosurfaceVolume {
+  min : vec3<f32>;
+  max : vec3<f32>;
+  step : vec3<f32>;
+  size : vec3<u32>;
+  threshold : f32;
+  values : array<f32>;
+}
+
+@group(0) @binding(1) var<storage, write> volume : IsosurfaceVolume;
+
+struct PositionBuffer {
+  values : array<f32>;
+}
+
+@group(0) @binding(2) var<storage, write> positionsOut : PositionBuffer;
+
+struct NormalBuffer {
+  values : array<f32>;
+}
+
+@group(0) @binding(3) var<storage, write> normalsOut : NormalBuffer;
+
+struct IndexBuffer {
+  tris : array<u32>;
+}
+
+@group(0) @binding(4) var<storage, write> indicesOut : IndexBuffer;
+
+struct DrawIndirectArgs {
+  vc : u32;
+  vertexCount : atomic<u32>;
+  firstVertex : u32;
+  firstInstance : u32;
+  indexCount : atomic<u32>;
+  indexedInstanceCount : u32;
+  indexedFirstIndex : u32;
+  indexedBaseVertex : u32;
+  indexedFirstInstance : u32;
+}
+
+@group(0) @binding(5) var<storage, read_write> drawOut : DrawIndirectArgs;
+
+fn valueAt(index : vec3<u32>) -> f32 {
+  if (any((index >= volume.size))) {
+    return 0.0;
+  }
+  let valueIndex = ((index.x + (index.y * volume.size.x)) + ((index.z * volume.size.x) * volume.size.y));
+  return volume.values[valueIndex];
+}
+
+fn positionAt(index : vec3<u32>) -> vec3<f32> {
+  return (volume.min + (volume.step * vec3<f32>(index.xyz)));
+}
+
+fn normalAt(index : vec3<u32>) -> vec3<f32> {
+  return vec3<f32>((valueAt((index - vec3<u32>(1u, 0u, 0u))) - valueAt((index + vec3<u32>(1u, 0u, 0u)))), (valueAt((index - vec3<u32>(0u, 1u, 0u))) - valueAt((index + vec3<u32>(0u, 1u, 0u)))), (valueAt((index - vec3<u32>(0u, 0u, 1u))) - valueAt((index + vec3<u32>(0u, 0u, 1u)))));
+}
+
+var<private> positions : array<vec3<f32>, 12>;
+
+var<private> normals : array<vec3<f32>, 12>;
+
+var<private> indices : array<u32, 12>;
+
+var<private> cubeVerts : u32 = 0u;
+
+fn interpX(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>((volume.step.x * mu), 0.0, 0.0));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(1u, 0u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+fn interpY(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>(0.0, (volume.step.y * mu), 0.0));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(0u, 1u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+fn interpZ(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>(0.0, 0.0, (volume.step.z * mu)));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(0u, 0u, 1u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+@stage(compute) @workgroup_size(4, 4, 4)
+fn computeMain(@builtin(global_invocation_id) global_id : vec3<u32>) {
+  let i0 = global_id;
+  let i1 = (global_id + vec3<u32>(1u, 0u, 0u));
+  let i2 = (global_id + vec3<u32>(1u, 1u, 0u));
+  let i3 = (global_id + vec3<u32>(0u, 1u, 0u));
+  let i4 = (global_id + vec3<u32>(0u, 0u, 1u));
+  let i5 = (global_id + vec3<u32>(1u, 0u, 1u));
+  let i6 = (global_id + vec3<u32>(1u, 1u, 1u));
+  let i7 = (global_id + vec3<u32>(0u, 1u, 1u));
+  let v0 = valueAt(i0);
+  let v1 = valueAt(i1);
+  let v2 = valueAt(i2);
+  let v3 = valueAt(i3);
+  let v4 = valueAt(i4);
+  let v5 = valueAt(i5);
+  let v6 = valueAt(i6);
+  let v7 = valueAt(i7);
+  var cubeIndex = 0u;
+  if ((v0 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 1u);
+  }
+  if ((v1 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 2u);
+  }
+  if ((v2 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 4u);
+  }
+  if ((v3 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 8u);
+  }
+  if ((v4 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 16u);
+  }
+  if ((v5 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 32u);
+  }
+  if ((v6 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 64u);
+  }
+  if ((v7 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 128u);
+  }
+  let edges = tables.edges[cubeIndex];
+  if (((edges & 1u) != 0u)) {
+    interpX(0u, i0, v0, v1);
+  }
+  if (((edges & 2u) != 0u)) {
+    interpY(1u, i1, v1, v2);
+  }
+  if (((edges & 4u) != 0u)) {
+    interpX(2u, i3, v3, v2);
+  }
+  if (((edges & 8u) != 0u)) {
+    interpY(3u, i0, v0, v3);
+  }
+  if (((edges & 16u) != 0u)) {
+    interpX(4u, i4, v4, v5);
+  }
+  if (((edges & 32u) != 0u)) {
+    interpY(5u, i5, v5, v6);
+  }
+  if (((edges & 64u) != 0u)) {
+    interpX(6u, i7, v7, v6);
+  }
+  if (((edges & 128u) != 0u)) {
+    interpY(7u, i4, v4, v7);
+  }
+  if (((edges & 256u) != 0u)) {
+    interpZ(8u, i0, v0, v4);
+  }
+  if (((edges & 512u) != 0u)) {
+    interpZ(9u, i1, v1, v5);
+  }
+  if (((edges & 1024u) != 0u)) {
+    interpZ(10u, i2, v2, v6);
+  }
+  if (((edges & 2048u) != 0u)) {
+    interpZ(11u, i3, v3, v7);
+  }
+  let triTableOffset = ((cubeIndex << 4u) + 1u);
+  let indexCount = u32(tables.tris[(triTableOffset - 1u)]);
+  var firstVertex = atomicAdd(&(drawOut.vertexCount), cubeVerts);
+  let bufferOffset = ((global_id.x + (global_id.y * volume.size.x)) + ((global_id.z * volume.size.x) * volume.size.y));
+  let firstIndex = (bufferOffset * 15u);
+  for(var i = 0u; (i < cubeVerts); i = (i + 1u)) {
+    positionsOut.values[((firstVertex * 3u) + (i * 3u))] = positions[i].x;
+    positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = positions[i].y;
+    positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = positions[i].z;
+    normalsOut.values[((firstVertex * 3u) + (i * 3u))] = normals[i].x;
+    normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = normals[i].y;
+    normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = normals[i].z;
+  }
+  for(var i = 0u; (i < indexCount); i = (i + 1u)) {
+    let index = tables.tris[(triTableOffset + i)];
+    indicesOut.tris[(firstIndex + i)] = (firstVertex + indices[index]);
+  }
+  for(var i = indexCount; (i < 15u); i = (i + 1u)) {
+    indicesOut.tris[(firstIndex + i)] = firstVertex;
+  }
+}
diff --git a/test/benchmark/metaball-isosurface.wgsl.expected.glsl b/test/benchmark/metaball-isosurface.wgsl.expected.glsl
new file mode 100644
index 0000000..1e033af
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl.expected.glsl
@@ -0,0 +1,232 @@
+#version 310 es
+precision mediump float;
+
+struct Tables {
+  uint edges[256];
+  int tris[4096];
+};
+
+layout (binding = 0) buffer Tables_1 {
+  uint edges[256];
+  int tris[4096];
+} tables;
+
+layout (binding = 1) buffer IsosurfaceVolume_1 {
+  vec3 tint_symbol;
+  vec3 tint_symbol_1;
+  vec3 tint_symbol_2;
+  uvec3 size;
+  float threshold;
+  float values[];
+} volume;
+
+layout (binding = 2) buffer PositionBuffer_1 {
+  float values[];
+} positionsOut;
+
+layout (binding = 3) buffer NormalBuffer_1 {
+  float values[];
+} normalsOut;
+
+layout (binding = 4) buffer IndexBuffer_1 {
+  uint tris[];
+} indicesOut;
+
+struct DrawIndirectArgs {
+  uint vc;
+  uint vertexCount;
+  uint firstVertex;
+  uint firstInstance;
+  uint indexCount;
+  uint indexedInstanceCount;
+  uint indexedFirstIndex;
+  uint indexedBaseVertex;
+  uint indexedFirstInstance;
+};
+
+layout (binding = 5) buffer DrawIndirectArgs_1 {
+  uint vc;
+  uint vertexCount;
+  uint firstVertex;
+  uint firstInstance;
+  uint indexCount;
+  uint indexedInstanceCount;
+  uint indexedFirstIndex;
+  uint indexedBaseVertex;
+  uint indexedFirstInstance;
+} drawOut;
+
+float valueAt(uvec3 index) {
+  if (any(greaterThanEqual(index, volume.size))) {
+    return 0.0f;
+  }
+  uint valueIndex = ((index.x + (index.y * volume.size.x)) + ((index.z * volume.size.x) * volume.size.y));
+  return volume.values[valueIndex];
+}
+
+vec3 positionAt(uvec3 index) {
+  return (volume.tint_symbol + (volume.tint_symbol_2 * vec3(index.xyz)));
+}
+
+vec3 normalAt(uvec3 index) {
+  return vec3((valueAt((index - uvec3(1u, 0u, 0u))) - valueAt((index + uvec3(1u, 0u, 0u)))), (valueAt((index - uvec3(0u, 1u, 0u))) - valueAt((index + uvec3(0u, 1u, 0u)))), (valueAt((index - uvec3(0u, 0u, 1u))) - valueAt((index + uvec3(0u, 0u, 1u)))));
+}
+
+vec3 positions[12] = vec3[12](vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f));
+vec3 normals[12] = vec3[12](vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f));
+uint indices[12] = uint[12](0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u);
+uint cubeVerts = 0u;
+
+void interpX(uint index, uvec3 i, float va, float vb) {
+  float mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3((volume.tint_symbol_2.x * mu), 0.0f, 0.0f));
+  vec3 na = normalAt(i);
+  vec3 nb = normalAt((i + uvec3(1u, 0u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+void interpY(uint index, uvec3 i, float va, float vb) {
+  float mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3(0.0f, (volume.tint_symbol_2.y * mu), 0.0f));
+  vec3 na = normalAt(i);
+  vec3 nb = normalAt((i + uvec3(0u, 1u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+void interpZ(uint index, uvec3 i, float va, float vb) {
+  float mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3(0.0f, 0.0f, (volume.tint_symbol_2.z * mu)));
+  vec3 na = normalAt(i);
+  vec3 nb = normalAt((i + uvec3(0u, 0u, 1u)));
+  normals[cubeVerts] = mix(na, nb, vec3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+struct tint_symbol_4 {
+  uvec3 global_id;
+};
+
+void computeMain_inner(uvec3 global_id) {
+  uvec3 i0 = global_id;
+  uvec3 i1 = (global_id + uvec3(1u, 0u, 0u));
+  uvec3 i2 = (global_id + uvec3(1u, 1u, 0u));
+  uvec3 i3 = (global_id + uvec3(0u, 1u, 0u));
+  uvec3 i4 = (global_id + uvec3(0u, 0u, 1u));
+  uvec3 i5 = (global_id + uvec3(1u, 0u, 1u));
+  uvec3 i6 = (global_id + uvec3(1u, 1u, 1u));
+  uvec3 i7 = (global_id + uvec3(0u, 1u, 1u));
+  float v0 = valueAt(i0);
+  float v1 = valueAt(i1);
+  float v2 = valueAt(i2);
+  float v3 = valueAt(i3);
+  float v4 = valueAt(i4);
+  float v5 = valueAt(i5);
+  float v6 = valueAt(i6);
+  float v7 = valueAt(i7);
+  uint cubeIndex = 0u;
+  if ((v0 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 1u);
+  }
+  if ((v1 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 2u);
+  }
+  if ((v2 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 4u);
+  }
+  if ((v3 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 8u);
+  }
+  if ((v4 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 16u);
+  }
+  if ((v5 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 32u);
+  }
+  if ((v6 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 64u);
+  }
+  if ((v7 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 128u);
+  }
+  uint edges = tables.edges[cubeIndex];
+  if (((edges & 1u) != 0u)) {
+    interpX(0u, i0, v0, v1);
+  }
+  if (((edges & 2u) != 0u)) {
+    interpY(1u, i1, v1, v2);
+  }
+  if (((edges & 4u) != 0u)) {
+    interpX(2u, i3, v3, v2);
+  }
+  if (((edges & 8u) != 0u)) {
+    interpY(3u, i0, v0, v3);
+  }
+  if (((edges & 16u) != 0u)) {
+    interpX(4u, i4, v4, v5);
+  }
+  if (((edges & 32u) != 0u)) {
+    interpY(5u, i5, v5, v6);
+  }
+  if (((edges & 64u) != 0u)) {
+    interpX(6u, i7, v7, v6);
+  }
+  if (((edges & 128u) != 0u)) {
+    interpY(7u, i4, v4, v7);
+  }
+  if (((edges & 256u) != 0u)) {
+    interpZ(8u, i0, v0, v4);
+  }
+  if (((edges & 512u) != 0u)) {
+    interpZ(9u, i1, v1, v5);
+  }
+  if (((edges & 1024u) != 0u)) {
+    interpZ(10u, i2, v2, v6);
+  }
+  if (((edges & 2048u) != 0u)) {
+    interpZ(11u, i3, v3, v7);
+  }
+  uint triTableOffset = ((cubeIndex << 4u) + 1u);
+  uint indexCount = uint(tables.tris[(triTableOffset - 1u)]);
+  uint firstVertex = atomicAdd(drawOut.vertexCount, cubeVerts);
+  uint bufferOffset = ((global_id.x + (global_id.y * volume.size.x)) + ((global_id.z * volume.size.x) * volume.size.y));
+  uint firstIndex = (bufferOffset * 15u);
+  {
+    for(uint i = 0u; (i < cubeVerts); i = (i + 1u)) {
+      positionsOut.values[((firstVertex * 3u) + (i * 3u))] = positions[i].x;
+      positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = positions[i].y;
+      positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = positions[i].z;
+      normalsOut.values[((firstVertex * 3u) + (i * 3u))] = normals[i].x;
+      normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = normals[i].y;
+      normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = normals[i].z;
+    }
+  }
+  {
+    for(uint i = 0u; (i < indexCount); i = (i + 1u)) {
+      int index = tables.tris[(triTableOffset + i)];
+      indicesOut.tris[(firstIndex + i)] = (firstVertex + indices[index]);
+    }
+  }
+  {
+    for(uint i = indexCount; (i < 15u); i = (i + 1u)) {
+      indicesOut.tris[(firstIndex + i)] = firstVertex;
+    }
+  }
+}
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+void computeMain(tint_symbol_4 tint_symbol_3) {
+  computeMain_inner(tint_symbol_3.global_id);
+  return;
+}
+void main() {
+  tint_symbol_4 inputs;
+  inputs.global_id = gl_GlobalInvocationID;
+  computeMain(inputs);
+}
+
+
diff --git a/test/benchmark/metaball-isosurface.wgsl.expected.hlsl b/test/benchmark/metaball-isosurface.wgsl.expected.hlsl
new file mode 100644
index 0000000..b260229
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl.expected.hlsl
@@ -0,0 +1,185 @@
+uint atomicAdd_1(RWByteAddressBuffer buffer, uint offset, uint value) {
+  uint original_value = 0;
+  buffer.InterlockedAdd(offset, value, original_value);
+  return original_value;
+}
+
+ByteAddressBuffer tables : register(t0, space0);
+
+RWByteAddressBuffer volume : register(u1, space0);
+
+RWByteAddressBuffer positionsOut : register(u2, space0);
+
+RWByteAddressBuffer normalsOut : register(u3, space0);
+
+RWByteAddressBuffer indicesOut : register(u4, space0);
+
+RWByteAddressBuffer drawOut : register(u5, space0);
+
+float valueAt(uint3 index) {
+  if (any((index >= volume.Load3(48u)))) {
+    return 0.0f;
+  }
+  const uint valueIndex = ((index.x + (index.y * volume.Load(48u))) + ((index.z * volume.Load(48u)) * volume.Load(52u)));
+  return asfloat(volume.Load((64u + (4u * valueIndex))));
+}
+
+float3 positionAt(uint3 index) {
+  return (asfloat(volume.Load3(0u)) + (asfloat(volume.Load3(32u)) * float3(index.xyz)));
+}
+
+float3 normalAt(uint3 index) {
+  return float3((valueAt((index - uint3(1u, 0u, 0u))) - valueAt((index + uint3(1u, 0u, 0u)))), (valueAt((index - uint3(0u, 1u, 0u))) - valueAt((index + uint3(0u, 1u, 0u)))), (valueAt((index - uint3(0u, 0u, 1u))) - valueAt((index + uint3(0u, 0u, 1u)))));
+}
+
+static float3 positions[12] = (float3[12])0;
+static float3 normals[12] = (float3[12])0;
+static uint indices[12] = (uint[12])0;
+static uint cubeVerts = 0u;
+
+void interpX(uint index, uint3 i, float va, float vb) {
+  const float mu = ((asfloat(volume.Load(60u)) - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + float3((asfloat(volume.Load(32u)) * mu), 0.0f, 0.0f));
+  const float3 na = normalAt(i);
+  const float3 nb = normalAt((i + uint3(1u, 0u, 0u)));
+  normals[cubeVerts] = lerp(na, nb, float3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+void interpY(uint index, uint3 i, float va, float vb) {
+  const float mu = ((asfloat(volume.Load(60u)) - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + float3(0.0f, (asfloat(volume.Load(36u)) * mu), 0.0f));
+  const float3 na = normalAt(i);
+  const float3 nb = normalAt((i + uint3(0u, 1u, 0u)));
+  normals[cubeVerts] = lerp(na, nb, float3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+void interpZ(uint index, uint3 i, float va, float vb) {
+  const float mu = ((asfloat(volume.Load(60u)) - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + float3(0.0f, 0.0f, (asfloat(volume.Load(40u)) * mu)));
+  const float3 na = normalAt(i);
+  const float3 nb = normalAt((i + uint3(0u, 0u, 1u)));
+  normals[cubeVerts] = lerp(na, nb, float3(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+struct tint_symbol_1 {
+  uint3 global_id : SV_DispatchThreadID;
+};
+
+void computeMain_inner(uint3 global_id) {
+  const uint3 i0 = global_id;
+  const uint3 i1 = (global_id + uint3(1u, 0u, 0u));
+  const uint3 i2 = (global_id + uint3(1u, 1u, 0u));
+  const uint3 i3 = (global_id + uint3(0u, 1u, 0u));
+  const uint3 i4 = (global_id + uint3(0u, 0u, 1u));
+  const uint3 i5 = (global_id + uint3(1u, 0u, 1u));
+  const uint3 i6 = (global_id + uint3(1u, 1u, 1u));
+  const uint3 i7 = (global_id + uint3(0u, 1u, 1u));
+  const float v0 = valueAt(i0);
+  const float v1 = valueAt(i1);
+  const float v2 = valueAt(i2);
+  const float v3 = valueAt(i3);
+  const float v4 = valueAt(i4);
+  const float v5 = valueAt(i5);
+  const float v6 = valueAt(i6);
+  const float v7 = valueAt(i7);
+  uint cubeIndex = 0u;
+  if ((v0 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 1u);
+  }
+  if ((v1 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 2u);
+  }
+  if ((v2 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 4u);
+  }
+  if ((v3 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 8u);
+  }
+  if ((v4 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 16u);
+  }
+  if ((v5 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 32u);
+  }
+  if ((v6 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 64u);
+  }
+  if ((v7 < asfloat(volume.Load(60u)))) {
+    cubeIndex = (cubeIndex | 128u);
+  }
+  const uint edges = tables.Load((4u * cubeIndex));
+  if (((edges & 1u) != 0u)) {
+    interpX(0u, i0, v0, v1);
+  }
+  if (((edges & 2u) != 0u)) {
+    interpY(1u, i1, v1, v2);
+  }
+  if (((edges & 4u) != 0u)) {
+    interpX(2u, i3, v3, v2);
+  }
+  if (((edges & 8u) != 0u)) {
+    interpY(3u, i0, v0, v3);
+  }
+  if (((edges & 16u) != 0u)) {
+    interpX(4u, i4, v4, v5);
+  }
+  if (((edges & 32u) != 0u)) {
+    interpY(5u, i5, v5, v6);
+  }
+  if (((edges & 64u) != 0u)) {
+    interpX(6u, i7, v7, v6);
+  }
+  if (((edges & 128u) != 0u)) {
+    interpY(7u, i4, v4, v7);
+  }
+  if (((edges & 256u) != 0u)) {
+    interpZ(8u, i0, v0, v4);
+  }
+  if (((edges & 512u) != 0u)) {
+    interpZ(9u, i1, v1, v5);
+  }
+  if (((edges & 1024u) != 0u)) {
+    interpZ(10u, i2, v2, v6);
+  }
+  if (((edges & 2048u) != 0u)) {
+    interpZ(11u, i3, v3, v7);
+  }
+  const uint triTableOffset = ((cubeIndex << 4u) + 1u);
+  const uint indexCount = uint(asint(tables.Load((1024u + (4u * (triTableOffset - 1u))))));
+  uint firstVertex = atomicAdd_1(drawOut, 4u, cubeVerts);
+  const uint bufferOffset = ((global_id.x + (global_id.y * volume.Load(48u))) + ((global_id.z * volume.Load(48u)) * volume.Load(52u)));
+  const uint firstIndex = (bufferOffset * 15u);
+  {
+    [loop] for(uint i = 0u; (i < cubeVerts); i = (i + 1u)) {
+      positionsOut.Store((4u * ((firstVertex * 3u) + (i * 3u))), asuint(positions[i].x));
+      positionsOut.Store((4u * (((firstVertex * 3u) + (i * 3u)) + 1u)), asuint(positions[i].y));
+      positionsOut.Store((4u * (((firstVertex * 3u) + (i * 3u)) + 2u)), asuint(positions[i].z));
+      normalsOut.Store((4u * ((firstVertex * 3u) + (i * 3u))), asuint(normals[i].x));
+      normalsOut.Store((4u * (((firstVertex * 3u) + (i * 3u)) + 1u)), asuint(normals[i].y));
+      normalsOut.Store((4u * (((firstVertex * 3u) + (i * 3u)) + 2u)), asuint(normals[i].z));
+    }
+  }
+  {
+    [loop] for(uint i = 0u; (i < indexCount); i = (i + 1u)) {
+      const int index = asint(tables.Load((1024u + (4u * (triTableOffset + i)))));
+      indicesOut.Store((4u * (firstIndex + i)), asuint((firstVertex + indices[index])));
+    }
+  }
+  {
+    [loop] for(uint i = indexCount; (i < 15u); i = (i + 1u)) {
+      indicesOut.Store((4u * (firstIndex + i)), asuint(firstVertex));
+    }
+  }
+}
+
+[numthreads(4, 4, 4)]
+void computeMain(tint_symbol_1 tint_symbol) {
+  computeMain_inner(tint_symbol.global_id);
+  return;
+}
diff --git a/test/benchmark/metaball-isosurface.wgsl.expected.msl b/test/benchmark/metaball-isosurface.wgsl.expected.msl
new file mode 100644
index 0000000..f4838bb
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl.expected.msl
@@ -0,0 +1,219 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+
+struct tint_array_wrapper {
+  /* 0x0000 */ uint arr[256];
+};
+struct tint_array_wrapper_1 {
+  /* 0x0000 */ int arr[4096];
+};
+struct Tables {
+  /* 0x0000 */ tint_array_wrapper edges;
+  /* 0x0400 */ tint_array_wrapper_1 tris;
+};
+struct IsosurfaceVolume {
+  /* 0x0000 */ packed_float3 min;
+  /* 0x000c */ int8_t tint_pad[4];
+  /* 0x0010 */ packed_float3 max;
+  /* 0x001c */ int8_t tint_pad_1[4];
+  /* 0x0020 */ packed_float3 step;
+  /* 0x002c */ int8_t tint_pad_2[4];
+  /* 0x0030 */ packed_uint3 size;
+  /* 0x003c */ float threshold;
+  /* 0x0040 */ float values[1];
+  /* 0x0044 */ int8_t tint_pad_3[12];
+};
+struct PositionBuffer {
+  /* 0x0000 */ float values[1];
+};
+struct NormalBuffer {
+  /* 0x0000 */ float values[1];
+};
+struct IndexBuffer {
+  /* 0x0000 */ uint tris[1];
+};
+struct DrawIndirectArgs {
+  /* 0x0000 */ uint vc;
+  /* 0x0004 */ atomic_uint vertexCount;
+  /* 0x0008 */ uint firstVertex;
+  /* 0x000c */ uint firstInstance;
+  /* 0x0010 */ atomic_uint indexCount;
+  /* 0x0014 */ uint indexedInstanceCount;
+  /* 0x0018 */ uint indexedFirstIndex;
+  /* 0x001c */ uint indexedBaseVertex;
+  /* 0x0020 */ uint indexedFirstInstance;
+};
+struct tint_array_wrapper_2 {
+  float3 arr[12];
+};
+struct tint_array_wrapper_3 {
+  uint arr[12];
+};
+
+float valueAt(uint3 index, device IsosurfaceVolume* const tint_symbol) {
+  if (any((index >= (*(tint_symbol)).size))) {
+    return 0.0f;
+  }
+  uint const valueIndex = ((index[0] + (index[1] * (*(tint_symbol)).size[0])) + ((index[2] * (*(tint_symbol)).size[0]) * (*(tint_symbol)).size[1]));
+  return (*(tint_symbol)).values[valueIndex];
+}
+
+float3 positionAt(uint3 index, device IsosurfaceVolume* const tint_symbol_1) {
+  return ((*(tint_symbol_1)).min + ((*(tint_symbol_1)).step * float3(uint3(index).xyz)));
+}
+
+float3 normalAt(uint3 index, device IsosurfaceVolume* const tint_symbol_2) {
+  return float3((valueAt((index - uint3(1u, 0u, 0u)), tint_symbol_2) - valueAt((index + uint3(1u, 0u, 0u)), tint_symbol_2)), (valueAt((index - uint3(0u, 1u, 0u)), tint_symbol_2) - valueAt((index + uint3(0u, 1u, 0u)), tint_symbol_2)), (valueAt((index - uint3(0u, 0u, 1u)), tint_symbol_2) - valueAt((index + uint3(0u, 0u, 1u)), tint_symbol_2)));
+}
+
+void interpX(uint index, uint3 i, float va, float vb, device IsosurfaceVolume* const tint_symbol_3, thread tint_array_wrapper_2* const tint_symbol_4, thread uint* const tint_symbol_5, thread tint_array_wrapper_2* const tint_symbol_6, thread tint_array_wrapper_3* const tint_symbol_7) {
+  float const mu = (((*(tint_symbol_3)).threshold - va) / (vb - va));
+  (*(tint_symbol_4)).arr[*(tint_symbol_5)] = (positionAt(i, tint_symbol_3) + float3(((*(tint_symbol_3)).step[0] * mu), 0.0f, 0.0f));
+  float3 const na = normalAt(i, tint_symbol_3);
+  float3 const nb = normalAt((i + uint3(1u, 0u, 0u)), tint_symbol_3);
+  (*(tint_symbol_6)).arr[*(tint_symbol_5)] = mix(na, nb, float3(mu, mu, mu));
+  (*(tint_symbol_7)).arr[index] = *(tint_symbol_5);
+  *(tint_symbol_5) = (*(tint_symbol_5) + 1u);
+}
+
+void interpY(uint index, uint3 i, float va, float vb, device IsosurfaceVolume* const tint_symbol_8, thread tint_array_wrapper_2* const tint_symbol_9, thread uint* const tint_symbol_10, thread tint_array_wrapper_2* const tint_symbol_11, thread tint_array_wrapper_3* const tint_symbol_12) {
+  float const mu = (((*(tint_symbol_8)).threshold - va) / (vb - va));
+  (*(tint_symbol_9)).arr[*(tint_symbol_10)] = (positionAt(i, tint_symbol_8) + float3(0.0f, ((*(tint_symbol_8)).step[1] * mu), 0.0f));
+  float3 const na = normalAt(i, tint_symbol_8);
+  float3 const nb = normalAt((i + uint3(0u, 1u, 0u)), tint_symbol_8);
+  (*(tint_symbol_11)).arr[*(tint_symbol_10)] = mix(na, nb, float3(mu, mu, mu));
+  (*(tint_symbol_12)).arr[index] = *(tint_symbol_10);
+  *(tint_symbol_10) = (*(tint_symbol_10) + 1u);
+}
+
+void interpZ(uint index, uint3 i, float va, float vb, device IsosurfaceVolume* const tint_symbol_13, thread tint_array_wrapper_2* const tint_symbol_14, thread uint* const tint_symbol_15, thread tint_array_wrapper_2* const tint_symbol_16, thread tint_array_wrapper_3* const tint_symbol_17) {
+  float const mu = (((*(tint_symbol_13)).threshold - va) / (vb - va));
+  (*(tint_symbol_14)).arr[*(tint_symbol_15)] = (positionAt(i, tint_symbol_13) + float3(0.0f, 0.0f, ((*(tint_symbol_13)).step[2] * mu)));
+  float3 const na = normalAt(i, tint_symbol_13);
+  float3 const nb = normalAt((i + uint3(0u, 0u, 1u)), tint_symbol_13);
+  (*(tint_symbol_16)).arr[*(tint_symbol_15)] = mix(na, nb, float3(mu, mu, mu));
+  (*(tint_symbol_17)).arr[index] = *(tint_symbol_15);
+  *(tint_symbol_15) = (*(tint_symbol_15) + 1u);
+}
+
+void computeMain_inner(uint3 global_id, device IsosurfaceVolume* const tint_symbol_18, const device Tables* const tint_symbol_19, thread tint_array_wrapper_2* const tint_symbol_20, thread uint* const tint_symbol_21, thread tint_array_wrapper_2* const tint_symbol_22, thread tint_array_wrapper_3* const tint_symbol_23, device DrawIndirectArgs* const tint_symbol_24, device PositionBuffer* const tint_symbol_25, device NormalBuffer* const tint_symbol_26, device IndexBuffer* const tint_symbol_27) {
+  uint3 const i0 = global_id;
+  uint3 const i1 = (global_id + uint3(1u, 0u, 0u));
+  uint3 const i2 = (global_id + uint3(1u, 1u, 0u));
+  uint3 const i3 = (global_id + uint3(0u, 1u, 0u));
+  uint3 const i4 = (global_id + uint3(0u, 0u, 1u));
+  uint3 const i5 = (global_id + uint3(1u, 0u, 1u));
+  uint3 const i6 = (global_id + uint3(1u, 1u, 1u));
+  uint3 const i7 = (global_id + uint3(0u, 1u, 1u));
+  float const v0 = valueAt(i0, tint_symbol_18);
+  float const v1 = valueAt(i1, tint_symbol_18);
+  float const v2 = valueAt(i2, tint_symbol_18);
+  float const v3 = valueAt(i3, tint_symbol_18);
+  float const v4 = valueAt(i4, tint_symbol_18);
+  float const v5 = valueAt(i5, tint_symbol_18);
+  float const v6 = valueAt(i6, tint_symbol_18);
+  float const v7 = valueAt(i7, tint_symbol_18);
+  uint cubeIndex = 0u;
+  if ((v0 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 1u);
+  }
+  if ((v1 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 2u);
+  }
+  if ((v2 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 4u);
+  }
+  if ((v3 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 8u);
+  }
+  if ((v4 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 16u);
+  }
+  if ((v5 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 32u);
+  }
+  if ((v6 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 64u);
+  }
+  if ((v7 < (*(tint_symbol_18)).threshold)) {
+    cubeIndex = (cubeIndex | 128u);
+  }
+  uint const edges = (*(tint_symbol_19)).edges.arr[cubeIndex];
+  if (((edges & 1u) != 0u)) {
+    interpX(0u, i0, v0, v1, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 2u) != 0u)) {
+    interpY(1u, i1, v1, v2, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 4u) != 0u)) {
+    interpX(2u, i3, v3, v2, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 8u) != 0u)) {
+    interpY(3u, i0, v0, v3, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 16u) != 0u)) {
+    interpX(4u, i4, v4, v5, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 32u) != 0u)) {
+    interpY(5u, i5, v5, v6, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 64u) != 0u)) {
+    interpX(6u, i7, v7, v6, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 128u) != 0u)) {
+    interpY(7u, i4, v4, v7, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 256u) != 0u)) {
+    interpZ(8u, i0, v0, v4, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 512u) != 0u)) {
+    interpZ(9u, i1, v1, v5, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 1024u) != 0u)) {
+    interpZ(10u, i2, v2, v6, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  if (((edges & 2048u) != 0u)) {
+    interpZ(11u, i3, v3, v7, tint_symbol_18, tint_symbol_20, tint_symbol_21, tint_symbol_22, tint_symbol_23);
+  }
+  uint const triTableOffset = ((cubeIndex << 4u) + 1u);
+  uint const indexCount = uint((*(tint_symbol_19)).tris.arr[(triTableOffset - 1u)]);
+  uint firstVertex = atomic_fetch_add_explicit(&((*(tint_symbol_24)).vertexCount), *(tint_symbol_21), memory_order_relaxed);
+  uint const bufferOffset = ((global_id[0] + (global_id[1] * (*(tint_symbol_18)).size[0])) + ((global_id[2] * (*(tint_symbol_18)).size[0]) * (*(tint_symbol_18)).size[1]));
+  uint const firstIndex = (bufferOffset * 15u);
+  for(uint i = 0u; (i < *(tint_symbol_21)); i = (i + 1u)) {
+    (*(tint_symbol_25)).values[((firstVertex * 3u) + (i * 3u))] = (*(tint_symbol_20)).arr[i][0];
+    (*(tint_symbol_25)).values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = (*(tint_symbol_20)).arr[i][1];
+    (*(tint_symbol_25)).values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = (*(tint_symbol_20)).arr[i][2];
+    (*(tint_symbol_26)).values[((firstVertex * 3u) + (i * 3u))] = (*(tint_symbol_22)).arr[i][0];
+    (*(tint_symbol_26)).values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = (*(tint_symbol_22)).arr[i][1];
+    (*(tint_symbol_26)).values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = (*(tint_symbol_22)).arr[i][2];
+  }
+  for(uint i = 0u; (i < indexCount); i = (i + 1u)) {
+    int const index = (*(tint_symbol_19)).tris.arr[(triTableOffset + i)];
+    (*(tint_symbol_27)).tris[(firstIndex + i)] = (firstVertex + (*(tint_symbol_23)).arr[index]);
+  }
+  for(uint i = indexCount; (i < 15u); i = (i + 1u)) {
+    (*(tint_symbol_27)).tris[(firstIndex + i)] = firstVertex;
+  }
+}
+
+kernel void computeMain(device IsosurfaceVolume* tint_symbol_28 [[buffer(0)]], const device Tables* tint_symbol_29 [[buffer(5)]], device DrawIndirectArgs* tint_symbol_34 [[buffer(1)]], device PositionBuffer* tint_symbol_35 [[buffer(2)]], device NormalBuffer* tint_symbol_36 [[buffer(3)]], device IndexBuffer* tint_symbol_37 [[buffer(4)]], uint3 global_id [[thread_position_in_grid]]) {
+  thread tint_array_wrapper_2 tint_symbol_30 = {};
+  thread uint tint_symbol_31 = 0u;
+  thread tint_array_wrapper_2 tint_symbol_32 = {};
+  thread tint_array_wrapper_3 tint_symbol_33 = {};
+  computeMain_inner(global_id, tint_symbol_28, tint_symbol_29, &(tint_symbol_30), &(tint_symbol_31), &(tint_symbol_32), &(tint_symbol_33), tint_symbol_34, tint_symbol_35, tint_symbol_36, tint_symbol_37);
+  return;
+}
+
diff --git a/test/benchmark/metaball-isosurface.wgsl.expected.spvasm b/test/benchmark/metaball-isosurface.wgsl.expected.spvasm
new file mode 100644
index 0000000..d27494e
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl.expected.spvasm
@@ -0,0 +1,777 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 515
+; Schema: 0
+               OpCapability Shader
+        %145 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %computeMain "computeMain" %global_id_1
+               OpExecutionMode %computeMain LocalSize 4 4 4
+               OpName %global_id_1 "global_id_1"
+               OpName %Tables "Tables"
+               OpMemberName %Tables 0 "edges"
+               OpMemberName %Tables 1 "tris"
+               OpName %tables "tables"
+               OpName %IsosurfaceVolume "IsosurfaceVolume"
+               OpMemberName %IsosurfaceVolume 0 "min"
+               OpMemberName %IsosurfaceVolume 1 "max"
+               OpMemberName %IsosurfaceVolume 2 "step"
+               OpMemberName %IsosurfaceVolume 3 "size"
+               OpMemberName %IsosurfaceVolume 4 "threshold"
+               OpMemberName %IsosurfaceVolume 5 "values"
+               OpName %volume "volume"
+               OpName %PositionBuffer "PositionBuffer"
+               OpMemberName %PositionBuffer 0 "values"
+               OpName %positionsOut "positionsOut"
+               OpName %NormalBuffer "NormalBuffer"
+               OpMemberName %NormalBuffer 0 "values"
+               OpName %normalsOut "normalsOut"
+               OpName %IndexBuffer "IndexBuffer"
+               OpMemberName %IndexBuffer 0 "tris"
+               OpName %indicesOut "indicesOut"
+               OpName %DrawIndirectArgs "DrawIndirectArgs"
+               OpMemberName %DrawIndirectArgs 0 "vc"
+               OpMemberName %DrawIndirectArgs 1 "vertexCount"
+               OpMemberName %DrawIndirectArgs 2 "firstVertex"
+               OpMemberName %DrawIndirectArgs 3 "firstInstance"
+               OpMemberName %DrawIndirectArgs 4 "indexCount"
+               OpMemberName %DrawIndirectArgs 5 "indexedInstanceCount"
+               OpMemberName %DrawIndirectArgs 6 "indexedFirstIndex"
+               OpMemberName %DrawIndirectArgs 7 "indexedBaseVertex"
+               OpMemberName %DrawIndirectArgs 8 "indexedFirstInstance"
+               OpName %drawOut "drawOut"
+               OpName %positions "positions"
+               OpName %normals "normals"
+               OpName %indices "indices"
+               OpName %cubeVerts "cubeVerts"
+               OpName %valueAt "valueAt"
+               OpName %index "index"
+               OpName %positionAt "positionAt"
+               OpName %index_0 "index"
+               OpName %normalAt "normalAt"
+               OpName %index_1 "index"
+               OpName %interpX "interpX"
+               OpName %index_2 "index"
+               OpName %i "i"
+               OpName %va "va"
+               OpName %vb "vb"
+               OpName %interpY "interpY"
+               OpName %index_3 "index"
+               OpName %i_0 "i"
+               OpName %va_0 "va"
+               OpName %vb_0 "vb"
+               OpName %interpZ "interpZ"
+               OpName %index_4 "index"
+               OpName %i_1 "i"
+               OpName %va_1 "va"
+               OpName %vb_1 "vb"
+               OpName %computeMain_inner "computeMain_inner"
+               OpName %global_id "global_id"
+               OpName %cubeIndex "cubeIndex"
+               OpName %firstVertex "firstVertex"
+               OpName %i_2 "i"
+               OpName %i_3 "i"
+               OpName %i_4 "i"
+               OpName %computeMain "computeMain"
+               OpDecorate %global_id_1 BuiltIn GlobalInvocationId
+               OpDecorate %Tables Block
+               OpMemberDecorate %Tables 0 Offset 0
+               OpDecorate %_arr_uint_uint_256 ArrayStride 4
+               OpMemberDecorate %Tables 1 Offset 1024
+               OpDecorate %_arr_int_uint_4096 ArrayStride 4
+               OpDecorate %tables NonWritable
+               OpDecorate %tables DescriptorSet 0
+               OpDecorate %tables Binding 0
+               OpDecorate %IsosurfaceVolume Block
+               OpMemberDecorate %IsosurfaceVolume 0 Offset 0
+               OpMemberDecorate %IsosurfaceVolume 1 Offset 16
+               OpMemberDecorate %IsosurfaceVolume 2 Offset 32
+               OpMemberDecorate %IsosurfaceVolume 3 Offset 48
+               OpMemberDecorate %IsosurfaceVolume 4 Offset 60
+               OpMemberDecorate %IsosurfaceVolume 5 Offset 64
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpDecorate %volume NonReadable
+               OpDecorate %volume DescriptorSet 0
+               OpDecorate %volume Binding 1
+               OpDecorate %PositionBuffer Block
+               OpMemberDecorate %PositionBuffer 0 Offset 0
+               OpDecorate %positionsOut NonReadable
+               OpDecorate %positionsOut DescriptorSet 0
+               OpDecorate %positionsOut Binding 2
+               OpDecorate %NormalBuffer Block
+               OpMemberDecorate %NormalBuffer 0 Offset 0
+               OpDecorate %normalsOut NonReadable
+               OpDecorate %normalsOut DescriptorSet 0
+               OpDecorate %normalsOut Binding 3
+               OpDecorate %IndexBuffer Block
+               OpMemberDecorate %IndexBuffer 0 Offset 0
+               OpDecorate %_runtimearr_uint ArrayStride 4
+               OpDecorate %indicesOut NonReadable
+               OpDecorate %indicesOut DescriptorSet 0
+               OpDecorate %indicesOut Binding 4
+               OpDecorate %DrawIndirectArgs Block
+               OpMemberDecorate %DrawIndirectArgs 0 Offset 0
+               OpMemberDecorate %DrawIndirectArgs 1 Offset 4
+               OpMemberDecorate %DrawIndirectArgs 2 Offset 8
+               OpMemberDecorate %DrawIndirectArgs 3 Offset 12
+               OpMemberDecorate %DrawIndirectArgs 4 Offset 16
+               OpMemberDecorate %DrawIndirectArgs 5 Offset 20
+               OpMemberDecorate %DrawIndirectArgs 6 Offset 24
+               OpMemberDecorate %DrawIndirectArgs 7 Offset 28
+               OpMemberDecorate %DrawIndirectArgs 8 Offset 32
+               OpDecorate %drawOut DescriptorSet 0
+               OpDecorate %drawOut Binding 5
+               OpDecorate %_arr_v3float_uint_12 ArrayStride 16
+               OpDecorate %_arr_uint_uint_12 ArrayStride 4
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%global_id_1 = OpVariable %_ptr_Input_v3uint Input
+   %uint_256 = OpConstant %uint 256
+%_arr_uint_uint_256 = OpTypeArray %uint %uint_256
+        %int = OpTypeInt 32 1
+  %uint_4096 = OpConstant %uint 4096
+%_arr_int_uint_4096 = OpTypeArray %int %uint_4096
+     %Tables = OpTypeStruct %_arr_uint_uint_256 %_arr_int_uint_4096
+%_ptr_StorageBuffer_Tables = OpTypePointer StorageBuffer %Tables
+     %tables = OpVariable %_ptr_StorageBuffer_Tables StorageBuffer
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%_runtimearr_float = OpTypeRuntimeArray %float
+%IsosurfaceVolume = OpTypeStruct %v3float %v3float %v3float %v3uint %float %_runtimearr_float
+%_ptr_StorageBuffer_IsosurfaceVolume = OpTypePointer StorageBuffer %IsosurfaceVolume
+     %volume = OpVariable %_ptr_StorageBuffer_IsosurfaceVolume StorageBuffer
+%PositionBuffer = OpTypeStruct %_runtimearr_float
+%_ptr_StorageBuffer_PositionBuffer = OpTypePointer StorageBuffer %PositionBuffer
+%positionsOut = OpVariable %_ptr_StorageBuffer_PositionBuffer StorageBuffer
+%NormalBuffer = OpTypeStruct %_runtimearr_float
+%_ptr_StorageBuffer_NormalBuffer = OpTypePointer StorageBuffer %NormalBuffer
+ %normalsOut = OpVariable %_ptr_StorageBuffer_NormalBuffer StorageBuffer
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%IndexBuffer = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer_IndexBuffer = OpTypePointer StorageBuffer %IndexBuffer
+ %indicesOut = OpVariable %_ptr_StorageBuffer_IndexBuffer StorageBuffer
+%DrawIndirectArgs = OpTypeStruct %uint %uint %uint %uint %uint %uint %uint %uint %uint
+%_ptr_StorageBuffer_DrawIndirectArgs = OpTypePointer StorageBuffer %DrawIndirectArgs
+    %drawOut = OpVariable %_ptr_StorageBuffer_DrawIndirectArgs StorageBuffer
+    %uint_12 = OpConstant %uint 12
+%_arr_v3float_uint_12 = OpTypeArray %v3float %uint_12
+%_ptr_Private__arr_v3float_uint_12 = OpTypePointer Private %_arr_v3float_uint_12
+         %36 = OpConstantNull %_arr_v3float_uint_12
+  %positions = OpVariable %_ptr_Private__arr_v3float_uint_12 Private %36
+    %normals = OpVariable %_ptr_Private__arr_v3float_uint_12 Private %36
+%_arr_uint_uint_12 = OpTypeArray %uint %uint_12
+%_ptr_Private__arr_uint_uint_12 = OpTypePointer Private %_arr_uint_uint_12
+         %41 = OpConstantNull %_arr_uint_uint_12
+    %indices = OpVariable %_ptr_Private__arr_uint_uint_12 Private %41
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private_uint = OpTypePointer Private %uint
+  %cubeVerts = OpVariable %_ptr_Private_uint Private %uint_0
+         %45 = OpTypeFunction %float %v3uint
+       %bool = OpTypeBool
+     %uint_3 = OpConstant %uint 3
+%_ptr_StorageBuffer_v3uint = OpTypePointer StorageBuffer %v3uint
+     %v3bool = OpTypeVector %bool 3
+    %float_0 = OpConstant %float 0
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+     %uint_1 = OpConstant %uint 1
+     %uint_5 = OpConstant %uint 5
+%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+         %80 = OpTypeFunction %v3float %v3uint
+%_ptr_StorageBuffer_v3float = OpTypePointer StorageBuffer %v3float
+     %uint_2 = OpConstant %uint 2
+         %98 = OpConstantComposite %v3uint %uint_1 %uint_0 %uint_0
+        %104 = OpConstantComposite %v3uint %uint_0 %uint_1 %uint_0
+        %110 = OpConstantComposite %v3uint %uint_0 %uint_0 %uint_1
+       %void = OpTypeVoid
+        %116 = OpTypeFunction %void %uint %v3uint %float %float
+     %uint_4 = OpConstant %uint 4
+%_ptr_Private_v3float = OpTypePointer Private %v3float
+        %211 = OpTypeFunction %void %v3uint
+        %216 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_0
+        %220 = OpConstantComposite %v3uint %uint_1 %uint_0 %uint_1
+        %222 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+        %224 = OpConstantComposite %v3uint %uint_0 %uint_1 %uint_1
+%_ptr_Function_uint = OpTypePointer Function %uint
+        %236 = OpConstantNull %uint
+     %uint_8 = OpConstant %uint 8
+    %uint_16 = OpConstant %uint 16
+    %uint_32 = OpConstant %uint 32
+    %uint_64 = OpConstant %uint 64
+   %uint_128 = OpConstant %uint 128
+     %uint_6 = OpConstant %uint 6
+     %uint_7 = OpConstant %uint 7
+   %uint_512 = OpConstant %uint 512
+     %uint_9 = OpConstant %uint 9
+  %uint_1024 = OpConstant %uint 1024
+    %uint_10 = OpConstant %uint 10
+  %uint_2048 = OpConstant %uint 2048
+    %uint_11 = OpConstant %uint 11
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%_ptr_StorageBuffer_uint_0 = OpTypePointer StorageBuffer %uint
+    %uint_15 = OpConstant %uint 15
+%_ptr_Private_float = OpTypePointer Private %float
+        %510 = OpTypeFunction %void
+    %valueAt = OpFunction %float None %45
+      %index = OpFunctionParameter %v3uint
+         %48 = OpLabel
+         %53 = OpAccessChain %_ptr_StorageBuffer_v3uint %volume %uint_3
+         %54 = OpLoad %v3uint %53
+         %55 = OpUGreaterThanEqual %v3bool %index %54
+         %49 = OpAny %bool %55
+               OpSelectionMerge %57 None
+               OpBranchConditional %49 %58 %57
+         %58 = OpLabel
+               OpReturnValue %float_0
+         %57 = OpLabel
+         %60 = OpCompositeExtract %uint %index 0
+         %61 = OpCompositeExtract %uint %index 1
+         %63 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_0
+         %64 = OpLoad %uint %63
+         %65 = OpIMul %uint %61 %64
+         %66 = OpIAdd %uint %60 %65
+         %67 = OpCompositeExtract %uint %index 2
+         %68 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_0
+         %69 = OpLoad %uint %68
+         %70 = OpIMul %uint %67 %69
+         %72 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_1
+         %73 = OpLoad %uint %72
+         %74 = OpIMul %uint %70 %73
+         %75 = OpIAdd %uint %66 %74
+         %78 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_5 %75
+         %79 = OpLoad %float %78
+               OpReturnValue %79
+               OpFunctionEnd
+ %positionAt = OpFunction %v3float None %80
+    %index_0 = OpFunctionParameter %v3uint
+         %83 = OpLabel
+         %85 = OpAccessChain %_ptr_StorageBuffer_v3float %volume %uint_0
+         %86 = OpLoad %v3float %85
+         %88 = OpAccessChain %_ptr_StorageBuffer_v3float %volume %uint_2
+         %89 = OpLoad %v3float %88
+         %91 = OpVectorShuffle %v3uint %index_0 %index_0 0 1 2
+         %90 = OpConvertUToF %v3float %91
+         %92 = OpFMul %v3float %89 %90
+         %93 = OpFAdd %v3float %86 %92
+               OpReturnValue %93
+               OpFunctionEnd
+   %normalAt = OpFunction %v3float None %80
+    %index_1 = OpFunctionParameter %v3uint
+         %96 = OpLabel
+         %99 = OpISub %v3uint %index_1 %98
+         %97 = OpFunctionCall %float %valueAt %99
+        %101 = OpIAdd %v3uint %index_1 %98
+        %100 = OpFunctionCall %float %valueAt %101
+        %102 = OpFSub %float %97 %100
+        %105 = OpISub %v3uint %index_1 %104
+        %103 = OpFunctionCall %float %valueAt %105
+        %107 = OpIAdd %v3uint %index_1 %104
+        %106 = OpFunctionCall %float %valueAt %107
+        %108 = OpFSub %float %103 %106
+        %111 = OpISub %v3uint %index_1 %110
+        %109 = OpFunctionCall %float %valueAt %111
+        %113 = OpIAdd %v3uint %index_1 %110
+        %112 = OpFunctionCall %float %valueAt %113
+        %114 = OpFSub %float %109 %112
+        %115 = OpCompositeConstruct %v3float %102 %108 %114
+               OpReturnValue %115
+               OpFunctionEnd
+    %interpX = OpFunction %void None %116
+    %index_2 = OpFunctionParameter %uint
+          %i = OpFunctionParameter %v3uint
+         %va = OpFunctionParameter %float
+         %vb = OpFunctionParameter %float
+        %123 = OpLabel
+        %125 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %126 = OpLoad %float %125
+        %127 = OpFSub %float %126 %va
+        %128 = OpFSub %float %vb %va
+        %129 = OpFDiv %float %127 %128
+        %130 = OpLoad %uint %cubeVerts
+        %132 = OpAccessChain %_ptr_Private_v3float %positions %130
+        %133 = OpFunctionCall %v3float %positionAt %i
+        %134 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_2 %uint_0
+        %135 = OpLoad %float %134
+        %136 = OpFMul %float %135 %129
+        %137 = OpCompositeConstruct %v3float %136 %float_0 %float_0
+        %138 = OpFAdd %v3float %133 %137
+               OpStore %132 %138
+        %139 = OpFunctionCall %v3float %normalAt %i
+        %141 = OpIAdd %v3uint %i %98
+        %140 = OpFunctionCall %v3float %normalAt %141
+        %142 = OpLoad %uint %cubeVerts
+        %143 = OpAccessChain %_ptr_Private_v3float %normals %142
+        %146 = OpCompositeConstruct %v3float %129 %129 %129
+        %144 = OpExtInst %v3float %145 FMix %139 %140 %146
+               OpStore %143 %144
+        %147 = OpAccessChain %_ptr_Private_uint %indices %index_2
+        %148 = OpLoad %uint %cubeVerts
+               OpStore %147 %148
+        %149 = OpLoad %uint %cubeVerts
+        %150 = OpIAdd %uint %149 %uint_1
+               OpStore %cubeVerts %150
+               OpReturn
+               OpFunctionEnd
+    %interpY = OpFunction %void None %116
+    %index_3 = OpFunctionParameter %uint
+        %i_0 = OpFunctionParameter %v3uint
+       %va_0 = OpFunctionParameter %float
+       %vb_0 = OpFunctionParameter %float
+        %156 = OpLabel
+        %157 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %158 = OpLoad %float %157
+        %159 = OpFSub %float %158 %va_0
+        %160 = OpFSub %float %vb_0 %va_0
+        %161 = OpFDiv %float %159 %160
+        %162 = OpLoad %uint %cubeVerts
+        %163 = OpAccessChain %_ptr_Private_v3float %positions %162
+        %164 = OpFunctionCall %v3float %positionAt %i_0
+        %165 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_2 %uint_1
+        %166 = OpLoad %float %165
+        %167 = OpFMul %float %166 %161
+        %168 = OpCompositeConstruct %v3float %float_0 %167 %float_0
+        %169 = OpFAdd %v3float %164 %168
+               OpStore %163 %169
+        %170 = OpFunctionCall %v3float %normalAt %i_0
+        %172 = OpIAdd %v3uint %i_0 %104
+        %171 = OpFunctionCall %v3float %normalAt %172
+        %173 = OpLoad %uint %cubeVerts
+        %174 = OpAccessChain %_ptr_Private_v3float %normals %173
+        %176 = OpCompositeConstruct %v3float %161 %161 %161
+        %175 = OpExtInst %v3float %145 FMix %170 %171 %176
+               OpStore %174 %175
+        %177 = OpAccessChain %_ptr_Private_uint %indices %index_3
+        %178 = OpLoad %uint %cubeVerts
+               OpStore %177 %178
+        %179 = OpLoad %uint %cubeVerts
+        %180 = OpIAdd %uint %179 %uint_1
+               OpStore %cubeVerts %180
+               OpReturn
+               OpFunctionEnd
+    %interpZ = OpFunction %void None %116
+    %index_4 = OpFunctionParameter %uint
+        %i_1 = OpFunctionParameter %v3uint
+       %va_1 = OpFunctionParameter %float
+       %vb_1 = OpFunctionParameter %float
+        %186 = OpLabel
+        %187 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %188 = OpLoad %float %187
+        %189 = OpFSub %float %188 %va_1
+        %190 = OpFSub %float %vb_1 %va_1
+        %191 = OpFDiv %float %189 %190
+        %192 = OpLoad %uint %cubeVerts
+        %193 = OpAccessChain %_ptr_Private_v3float %positions %192
+        %194 = OpFunctionCall %v3float %positionAt %i_1
+        %195 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_2 %uint_2
+        %196 = OpLoad %float %195
+        %197 = OpFMul %float %196 %191
+        %198 = OpCompositeConstruct %v3float %float_0 %float_0 %197
+        %199 = OpFAdd %v3float %194 %198
+               OpStore %193 %199
+        %200 = OpFunctionCall %v3float %normalAt %i_1
+        %202 = OpIAdd %v3uint %i_1 %110
+        %201 = OpFunctionCall %v3float %normalAt %202
+        %203 = OpLoad %uint %cubeVerts
+        %204 = OpAccessChain %_ptr_Private_v3float %normals %203
+        %206 = OpCompositeConstruct %v3float %191 %191 %191
+        %205 = OpExtInst %v3float %145 FMix %200 %201 %206
+               OpStore %204 %205
+        %207 = OpAccessChain %_ptr_Private_uint %indices %index_4
+        %208 = OpLoad %uint %cubeVerts
+               OpStore %207 %208
+        %209 = OpLoad %uint %cubeVerts
+        %210 = OpIAdd %uint %209 %uint_1
+               OpStore %cubeVerts %210
+               OpReturn
+               OpFunctionEnd
+%computeMain_inner = OpFunction %void None %211
+  %global_id = OpFunctionParameter %v3uint
+        %214 = OpLabel
+  %cubeIndex = OpVariable %_ptr_Function_uint Function %236
+%firstVertex = OpVariable %_ptr_Function_uint Function %236
+        %i_2 = OpVariable %_ptr_Function_uint Function %236
+        %i_3 = OpVariable %_ptr_Function_uint Function %236
+        %i_4 = OpVariable %_ptr_Function_uint Function %236
+        %215 = OpIAdd %v3uint %global_id %98
+        %217 = OpIAdd %v3uint %global_id %216
+        %218 = OpIAdd %v3uint %global_id %104
+        %219 = OpIAdd %v3uint %global_id %110
+        %221 = OpIAdd %v3uint %global_id %220
+        %223 = OpIAdd %v3uint %global_id %222
+        %225 = OpIAdd %v3uint %global_id %224
+        %226 = OpFunctionCall %float %valueAt %global_id
+        %227 = OpFunctionCall %float %valueAt %215
+        %228 = OpFunctionCall %float %valueAt %217
+        %229 = OpFunctionCall %float %valueAt %218
+        %230 = OpFunctionCall %float %valueAt %219
+        %231 = OpFunctionCall %float %valueAt %221
+        %232 = OpFunctionCall %float %valueAt %223
+        %233 = OpFunctionCall %float %valueAt %225
+               OpStore %cubeIndex %uint_0
+        %237 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %238 = OpLoad %float %237
+        %239 = OpFOrdLessThan %bool %226 %238
+               OpSelectionMerge %240 None
+               OpBranchConditional %239 %241 %240
+        %241 = OpLabel
+        %242 = OpLoad %uint %cubeIndex
+        %243 = OpBitwiseOr %uint %242 %uint_1
+               OpStore %cubeIndex %243
+               OpBranch %240
+        %240 = OpLabel
+        %244 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %245 = OpLoad %float %244
+        %246 = OpFOrdLessThan %bool %227 %245
+               OpSelectionMerge %247 None
+               OpBranchConditional %246 %248 %247
+        %248 = OpLabel
+        %249 = OpLoad %uint %cubeIndex
+        %250 = OpBitwiseOr %uint %249 %uint_2
+               OpStore %cubeIndex %250
+               OpBranch %247
+        %247 = OpLabel
+        %251 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %252 = OpLoad %float %251
+        %253 = OpFOrdLessThan %bool %228 %252
+               OpSelectionMerge %254 None
+               OpBranchConditional %253 %255 %254
+        %255 = OpLabel
+        %256 = OpLoad %uint %cubeIndex
+        %257 = OpBitwiseOr %uint %256 %uint_4
+               OpStore %cubeIndex %257
+               OpBranch %254
+        %254 = OpLabel
+        %258 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %259 = OpLoad %float %258
+        %260 = OpFOrdLessThan %bool %229 %259
+               OpSelectionMerge %261 None
+               OpBranchConditional %260 %262 %261
+        %262 = OpLabel
+        %263 = OpLoad %uint %cubeIndex
+        %265 = OpBitwiseOr %uint %263 %uint_8
+               OpStore %cubeIndex %265
+               OpBranch %261
+        %261 = OpLabel
+        %266 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %267 = OpLoad %float %266
+        %268 = OpFOrdLessThan %bool %230 %267
+               OpSelectionMerge %269 None
+               OpBranchConditional %268 %270 %269
+        %270 = OpLabel
+        %271 = OpLoad %uint %cubeIndex
+        %273 = OpBitwiseOr %uint %271 %uint_16
+               OpStore %cubeIndex %273
+               OpBranch %269
+        %269 = OpLabel
+        %274 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %275 = OpLoad %float %274
+        %276 = OpFOrdLessThan %bool %231 %275
+               OpSelectionMerge %277 None
+               OpBranchConditional %276 %278 %277
+        %278 = OpLabel
+        %279 = OpLoad %uint %cubeIndex
+        %281 = OpBitwiseOr %uint %279 %uint_32
+               OpStore %cubeIndex %281
+               OpBranch %277
+        %277 = OpLabel
+        %282 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %283 = OpLoad %float %282
+        %284 = OpFOrdLessThan %bool %232 %283
+               OpSelectionMerge %285 None
+               OpBranchConditional %284 %286 %285
+        %286 = OpLabel
+        %287 = OpLoad %uint %cubeIndex
+        %289 = OpBitwiseOr %uint %287 %uint_64
+               OpStore %cubeIndex %289
+               OpBranch %285
+        %285 = OpLabel
+        %290 = OpAccessChain %_ptr_StorageBuffer_float %volume %uint_4
+        %291 = OpLoad %float %290
+        %292 = OpFOrdLessThan %bool %233 %291
+               OpSelectionMerge %293 None
+               OpBranchConditional %292 %294 %293
+        %294 = OpLabel
+        %295 = OpLoad %uint %cubeIndex
+        %297 = OpBitwiseOr %uint %295 %uint_128
+               OpStore %cubeIndex %297
+               OpBranch %293
+        %293 = OpLabel
+        %298 = OpLoad %uint %cubeIndex
+        %299 = OpAccessChain %_ptr_StorageBuffer_uint %tables %uint_0 %298
+        %300 = OpLoad %uint %299
+        %301 = OpBitwiseAnd %uint %300 %uint_1
+        %302 = OpINotEqual %bool %301 %uint_0
+               OpSelectionMerge %303 None
+               OpBranchConditional %302 %304 %303
+        %304 = OpLabel
+        %305 = OpFunctionCall %void %interpX %uint_0 %global_id %226 %227
+               OpBranch %303
+        %303 = OpLabel
+        %306 = OpBitwiseAnd %uint %300 %uint_2
+        %307 = OpINotEqual %bool %306 %uint_0
+               OpSelectionMerge %308 None
+               OpBranchConditional %307 %309 %308
+        %309 = OpLabel
+        %310 = OpFunctionCall %void %interpY %uint_1 %215 %227 %228
+               OpBranch %308
+        %308 = OpLabel
+        %311 = OpBitwiseAnd %uint %300 %uint_4
+        %312 = OpINotEqual %bool %311 %uint_0
+               OpSelectionMerge %313 None
+               OpBranchConditional %312 %314 %313
+        %314 = OpLabel
+        %315 = OpFunctionCall %void %interpX %uint_2 %218 %229 %228
+               OpBranch %313
+        %313 = OpLabel
+        %316 = OpBitwiseAnd %uint %300 %uint_8
+        %317 = OpINotEqual %bool %316 %uint_0
+               OpSelectionMerge %318 None
+               OpBranchConditional %317 %319 %318
+        %319 = OpLabel
+        %320 = OpFunctionCall %void %interpY %uint_3 %global_id %226 %229
+               OpBranch %318
+        %318 = OpLabel
+        %321 = OpBitwiseAnd %uint %300 %uint_16
+        %322 = OpINotEqual %bool %321 %uint_0
+               OpSelectionMerge %323 None
+               OpBranchConditional %322 %324 %323
+        %324 = OpLabel
+        %325 = OpFunctionCall %void %interpX %uint_4 %219 %230 %231
+               OpBranch %323
+        %323 = OpLabel
+        %326 = OpBitwiseAnd %uint %300 %uint_32
+        %327 = OpINotEqual %bool %326 %uint_0
+               OpSelectionMerge %328 None
+               OpBranchConditional %327 %329 %328
+        %329 = OpLabel
+        %330 = OpFunctionCall %void %interpY %uint_5 %221 %231 %232
+               OpBranch %328
+        %328 = OpLabel
+        %331 = OpBitwiseAnd %uint %300 %uint_64
+        %332 = OpINotEqual %bool %331 %uint_0
+               OpSelectionMerge %333 None
+               OpBranchConditional %332 %334 %333
+        %334 = OpLabel
+        %335 = OpFunctionCall %void %interpX %uint_6 %225 %233 %232
+               OpBranch %333
+        %333 = OpLabel
+        %337 = OpBitwiseAnd %uint %300 %uint_128
+        %338 = OpINotEqual %bool %337 %uint_0
+               OpSelectionMerge %339 None
+               OpBranchConditional %338 %340 %339
+        %340 = OpLabel
+        %341 = OpFunctionCall %void %interpY %uint_7 %219 %230 %233
+               OpBranch %339
+        %339 = OpLabel
+        %343 = OpBitwiseAnd %uint %300 %uint_256
+        %344 = OpINotEqual %bool %343 %uint_0
+               OpSelectionMerge %345 None
+               OpBranchConditional %344 %346 %345
+        %346 = OpLabel
+        %347 = OpFunctionCall %void %interpZ %uint_8 %global_id %226 %230
+               OpBranch %345
+        %345 = OpLabel
+        %349 = OpBitwiseAnd %uint %300 %uint_512
+        %350 = OpINotEqual %bool %349 %uint_0
+               OpSelectionMerge %351 None
+               OpBranchConditional %350 %352 %351
+        %352 = OpLabel
+        %353 = OpFunctionCall %void %interpZ %uint_9 %215 %227 %231
+               OpBranch %351
+        %351 = OpLabel
+        %356 = OpBitwiseAnd %uint %300 %uint_1024
+        %357 = OpINotEqual %bool %356 %uint_0
+               OpSelectionMerge %358 None
+               OpBranchConditional %357 %359 %358
+        %359 = OpLabel
+        %360 = OpFunctionCall %void %interpZ %uint_10 %217 %228 %232
+               OpBranch %358
+        %358 = OpLabel
+        %363 = OpBitwiseAnd %uint %300 %uint_2048
+        %364 = OpINotEqual %bool %363 %uint_0
+               OpSelectionMerge %365 None
+               OpBranchConditional %364 %366 %365
+        %366 = OpLabel
+        %367 = OpFunctionCall %void %interpZ %uint_11 %218 %229 %233
+               OpBranch %365
+        %365 = OpLabel
+        %369 = OpLoad %uint %cubeIndex
+        %370 = OpShiftLeftLogical %uint %369 %uint_4
+        %371 = OpIAdd %uint %370 %uint_1
+        %373 = OpISub %uint %371 %uint_1
+        %375 = OpAccessChain %_ptr_StorageBuffer_int %tables %uint_1 %373
+        %376 = OpLoad %int %375
+        %372 = OpBitcast %uint %376
+        %380 = OpAccessChain %_ptr_StorageBuffer_uint_0 %drawOut %uint_1
+        %381 = OpLoad %uint %cubeVerts
+        %377 = OpAtomicIAdd %uint %380 %uint_1 %uint_0 %381
+               OpStore %firstVertex %377
+        %383 = OpCompositeExtract %uint %global_id 0
+        %384 = OpCompositeExtract %uint %global_id 1
+        %385 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_0
+        %386 = OpLoad %uint %385
+        %387 = OpIMul %uint %384 %386
+        %388 = OpIAdd %uint %383 %387
+        %389 = OpCompositeExtract %uint %global_id 2
+        %390 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_0
+        %391 = OpLoad %uint %390
+        %392 = OpIMul %uint %389 %391
+        %393 = OpAccessChain %_ptr_StorageBuffer_uint %volume %uint_3 %uint_1
+        %394 = OpLoad %uint %393
+        %395 = OpIMul %uint %392 %394
+        %396 = OpIAdd %uint %388 %395
+        %398 = OpIMul %uint %396 %uint_15
+               OpStore %i_2 %uint_0
+               OpBranch %400
+        %400 = OpLabel
+               OpLoopMerge %401 %402 None
+               OpBranch %403
+        %403 = OpLabel
+        %405 = OpLoad %uint %i_2
+        %406 = OpLoad %uint %cubeVerts
+        %407 = OpULessThan %bool %405 %406
+        %404 = OpLogicalNot %bool %407
+               OpSelectionMerge %408 None
+               OpBranchConditional %404 %409 %408
+        %409 = OpLabel
+               OpBranch %401
+        %408 = OpLabel
+        %410 = OpLoad %uint %firstVertex
+        %411 = OpIMul %uint %410 %uint_3
+        %412 = OpLoad %uint %i_2
+        %413 = OpIMul %uint %412 %uint_3
+        %414 = OpIAdd %uint %411 %413
+        %415 = OpAccessChain %_ptr_StorageBuffer_float %positionsOut %uint_0 %414
+        %416 = OpLoad %uint %i_2
+        %418 = OpAccessChain %_ptr_Private_float %positions %416 %uint_0
+        %419 = OpLoad %float %418
+               OpStore %415 %419
+        %420 = OpLoad %uint %firstVertex
+        %421 = OpIMul %uint %420 %uint_3
+        %422 = OpLoad %uint %i_2
+        %423 = OpIMul %uint %422 %uint_3
+        %424 = OpIAdd %uint %421 %423
+        %425 = OpIAdd %uint %424 %uint_1
+        %426 = OpAccessChain %_ptr_StorageBuffer_float %positionsOut %uint_0 %425
+        %427 = OpLoad %uint %i_2
+        %428 = OpAccessChain %_ptr_Private_float %positions %427 %uint_1
+        %429 = OpLoad %float %428
+               OpStore %426 %429
+        %430 = OpLoad %uint %firstVertex
+        %431 = OpIMul %uint %430 %uint_3
+        %432 = OpLoad %uint %i_2
+        %433 = OpIMul %uint %432 %uint_3
+        %434 = OpIAdd %uint %431 %433
+        %435 = OpIAdd %uint %434 %uint_2
+        %436 = OpAccessChain %_ptr_StorageBuffer_float %positionsOut %uint_0 %435
+        %437 = OpLoad %uint %i_2
+        %438 = OpAccessChain %_ptr_Private_float %positions %437 %uint_2
+        %439 = OpLoad %float %438
+               OpStore %436 %439
+        %440 = OpLoad %uint %firstVertex
+        %441 = OpIMul %uint %440 %uint_3
+        %442 = OpLoad %uint %i_2
+        %443 = OpIMul %uint %442 %uint_3
+        %444 = OpIAdd %uint %441 %443
+        %445 = OpAccessChain %_ptr_StorageBuffer_float %normalsOut %uint_0 %444
+        %446 = OpLoad %uint %i_2
+        %447 = OpAccessChain %_ptr_Private_float %normals %446 %uint_0
+        %448 = OpLoad %float %447
+               OpStore %445 %448
+        %449 = OpLoad %uint %firstVertex
+        %450 = OpIMul %uint %449 %uint_3
+        %451 = OpLoad %uint %i_2
+        %452 = OpIMul %uint %451 %uint_3
+        %453 = OpIAdd %uint %450 %452
+        %454 = OpIAdd %uint %453 %uint_1
+        %455 = OpAccessChain %_ptr_StorageBuffer_float %normalsOut %uint_0 %454
+        %456 = OpLoad %uint %i_2
+        %457 = OpAccessChain %_ptr_Private_float %normals %456 %uint_1
+        %458 = OpLoad %float %457
+               OpStore %455 %458
+        %459 = OpLoad %uint %firstVertex
+        %460 = OpIMul %uint %459 %uint_3
+        %461 = OpLoad %uint %i_2
+        %462 = OpIMul %uint %461 %uint_3
+        %463 = OpIAdd %uint %460 %462
+        %464 = OpIAdd %uint %463 %uint_2
+        %465 = OpAccessChain %_ptr_StorageBuffer_float %normalsOut %uint_0 %464
+        %466 = OpLoad %uint %i_2
+        %467 = OpAccessChain %_ptr_Private_float %normals %466 %uint_2
+        %468 = OpLoad %float %467
+               OpStore %465 %468
+               OpBranch %402
+        %402 = OpLabel
+        %469 = OpLoad %uint %i_2
+        %470 = OpIAdd %uint %469 %uint_1
+               OpStore %i_2 %470
+               OpBranch %400
+        %401 = OpLabel
+               OpStore %i_3 %uint_0
+               OpBranch %472
+        %472 = OpLabel
+               OpLoopMerge %473 %474 None
+               OpBranch %475
+        %475 = OpLabel
+        %477 = OpLoad %uint %i_3
+        %478 = OpULessThan %bool %477 %372
+        %476 = OpLogicalNot %bool %478
+               OpSelectionMerge %479 None
+               OpBranchConditional %476 %480 %479
+        %480 = OpLabel
+               OpBranch %473
+        %479 = OpLabel
+        %481 = OpLoad %uint %i_3
+        %482 = OpIAdd %uint %371 %481
+        %483 = OpAccessChain %_ptr_StorageBuffer_int %tables %uint_1 %482
+        %484 = OpLoad %int %483
+        %485 = OpLoad %uint %i_3
+        %486 = OpIAdd %uint %398 %485
+        %487 = OpAccessChain %_ptr_StorageBuffer_uint %indicesOut %uint_0 %486
+        %488 = OpLoad %uint %firstVertex
+        %489 = OpAccessChain %_ptr_Private_uint %indices %484
+        %490 = OpLoad %uint %489
+        %491 = OpIAdd %uint %488 %490
+               OpStore %487 %491
+               OpBranch %474
+        %474 = OpLabel
+        %492 = OpLoad %uint %i_3
+        %493 = OpIAdd %uint %492 %uint_1
+               OpStore %i_3 %493
+               OpBranch %472
+        %473 = OpLabel
+               OpStore %i_4 %372
+               OpBranch %495
+        %495 = OpLabel
+               OpLoopMerge %496 %497 None
+               OpBranch %498
+        %498 = OpLabel
+        %500 = OpLoad %uint %i_4
+        %501 = OpULessThan %bool %500 %uint_15
+        %499 = OpLogicalNot %bool %501
+               OpSelectionMerge %502 None
+               OpBranchConditional %499 %503 %502
+        %503 = OpLabel
+               OpBranch %496
+        %502 = OpLabel
+        %504 = OpLoad %uint %i_4
+        %505 = OpIAdd %uint %398 %504
+        %506 = OpAccessChain %_ptr_StorageBuffer_uint %indicesOut %uint_0 %505
+        %507 = OpLoad %uint %firstVertex
+               OpStore %506 %507
+               OpBranch %497
+        %497 = OpLabel
+        %508 = OpLoad %uint %i_4
+        %509 = OpIAdd %uint %508 %uint_1
+               OpStore %i_4 %509
+               OpBranch %495
+        %496 = OpLabel
+               OpReturn
+               OpFunctionEnd
+%computeMain = OpFunction %void None %510
+        %512 = OpLabel
+        %514 = OpLoad %v3uint %global_id_1
+        %513 = OpFunctionCall %void %computeMain_inner %514
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/metaball-isosurface.wgsl.expected.wgsl b/test/benchmark/metaball-isosurface.wgsl.expected.wgsl
new file mode 100644
index 0000000..40374f4
--- /dev/null
+++ b/test/benchmark/metaball-isosurface.wgsl.expected.wgsl
@@ -0,0 +1,205 @@
+struct Tables {
+  edges : array<u32, 256>;
+  tris : array<i32, 4096>;
+}
+
+@group(0) @binding(0) var<storage> tables : Tables;
+
+struct IsosurfaceVolume {
+  min : vec3<f32>;
+  max : vec3<f32>;
+  step : vec3<f32>;
+  size : vec3<u32>;
+  threshold : f32;
+  values : array<f32>;
+}
+
+@group(0) @binding(1) var<storage, write> volume : IsosurfaceVolume;
+
+struct PositionBuffer {
+  values : array<f32>;
+}
+
+@group(0) @binding(2) var<storage, write> positionsOut : PositionBuffer;
+
+struct NormalBuffer {
+  values : array<f32>;
+}
+
+@group(0) @binding(3) var<storage, write> normalsOut : NormalBuffer;
+
+struct IndexBuffer {
+  tris : array<u32>;
+}
+
+@group(0) @binding(4) var<storage, write> indicesOut : IndexBuffer;
+
+struct DrawIndirectArgs {
+  vc : u32;
+  vertexCount : atomic<u32>;
+  firstVertex : u32;
+  firstInstance : u32;
+  indexCount : atomic<u32>;
+  indexedInstanceCount : u32;
+  indexedFirstIndex : u32;
+  indexedBaseVertex : u32;
+  indexedFirstInstance : u32;
+}
+
+@group(0) @binding(5) var<storage, read_write> drawOut : DrawIndirectArgs;
+
+fn valueAt(index : vec3<u32>) -> f32 {
+  if (any((index >= volume.size))) {
+    return 0.0;
+  }
+  let valueIndex = ((index.x + (index.y * volume.size.x)) + ((index.z * volume.size.x) * volume.size.y));
+  return volume.values[valueIndex];
+}
+
+fn positionAt(index : vec3<u32>) -> vec3<f32> {
+  return (volume.min + (volume.step * vec3<f32>(index.xyz)));
+}
+
+fn normalAt(index : vec3<u32>) -> vec3<f32> {
+  return vec3<f32>((valueAt((index - vec3<u32>(1u, 0u, 0u))) - valueAt((index + vec3<u32>(1u, 0u, 0u)))), (valueAt((index - vec3<u32>(0u, 1u, 0u))) - valueAt((index + vec3<u32>(0u, 1u, 0u)))), (valueAt((index - vec3<u32>(0u, 0u, 1u))) - valueAt((index + vec3<u32>(0u, 0u, 1u)))));
+}
+
+var<private> positions : array<vec3<f32>, 12>;
+
+var<private> normals : array<vec3<f32>, 12>;
+
+var<private> indices : array<u32, 12>;
+
+var<private> cubeVerts : u32 = 0u;
+
+fn interpX(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>((volume.step.x * mu), 0.0, 0.0));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(1u, 0u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+fn interpY(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>(0.0, (volume.step.y * mu), 0.0));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(0u, 1u, 0u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+fn interpZ(index : u32, i : vec3<u32>, va : f32, vb : f32) {
+  let mu = ((volume.threshold - va) / (vb - va));
+  positions[cubeVerts] = (positionAt(i) + vec3<f32>(0.0, 0.0, (volume.step.z * mu)));
+  let na = normalAt(i);
+  let nb = normalAt((i + vec3<u32>(0u, 0u, 1u)));
+  normals[cubeVerts] = mix(na, nb, vec3<f32>(mu, mu, mu));
+  indices[index] = cubeVerts;
+  cubeVerts = (cubeVerts + 1u);
+}
+
+@stage(compute) @workgroup_size(4, 4, 4)
+fn computeMain(@builtin(global_invocation_id) global_id : vec3<u32>) {
+  let i0 = global_id;
+  let i1 = (global_id + vec3<u32>(1u, 0u, 0u));
+  let i2 = (global_id + vec3<u32>(1u, 1u, 0u));
+  let i3 = (global_id + vec3<u32>(0u, 1u, 0u));
+  let i4 = (global_id + vec3<u32>(0u, 0u, 1u));
+  let i5 = (global_id + vec3<u32>(1u, 0u, 1u));
+  let i6 = (global_id + vec3<u32>(1u, 1u, 1u));
+  let i7 = (global_id + vec3<u32>(0u, 1u, 1u));
+  let v0 = valueAt(i0);
+  let v1 = valueAt(i1);
+  let v2 = valueAt(i2);
+  let v3 = valueAt(i3);
+  let v4 = valueAt(i4);
+  let v5 = valueAt(i5);
+  let v6 = valueAt(i6);
+  let v7 = valueAt(i7);
+  var cubeIndex = 0u;
+  if ((v0 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 1u);
+  }
+  if ((v1 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 2u);
+  }
+  if ((v2 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 4u);
+  }
+  if ((v3 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 8u);
+  }
+  if ((v4 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 16u);
+  }
+  if ((v5 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 32u);
+  }
+  if ((v6 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 64u);
+  }
+  if ((v7 < volume.threshold)) {
+    cubeIndex = (cubeIndex | 128u);
+  }
+  let edges = tables.edges[cubeIndex];
+  if (((edges & 1u) != 0u)) {
+    interpX(0u, i0, v0, v1);
+  }
+  if (((edges & 2u) != 0u)) {
+    interpY(1u, i1, v1, v2);
+  }
+  if (((edges & 4u) != 0u)) {
+    interpX(2u, i3, v3, v2);
+  }
+  if (((edges & 8u) != 0u)) {
+    interpY(3u, i0, v0, v3);
+  }
+  if (((edges & 16u) != 0u)) {
+    interpX(4u, i4, v4, v5);
+  }
+  if (((edges & 32u) != 0u)) {
+    interpY(5u, i5, v5, v6);
+  }
+  if (((edges & 64u) != 0u)) {
+    interpX(6u, i7, v7, v6);
+  }
+  if (((edges & 128u) != 0u)) {
+    interpY(7u, i4, v4, v7);
+  }
+  if (((edges & 256u) != 0u)) {
+    interpZ(8u, i0, v0, v4);
+  }
+  if (((edges & 512u) != 0u)) {
+    interpZ(9u, i1, v1, v5);
+  }
+  if (((edges & 1024u) != 0u)) {
+    interpZ(10u, i2, v2, v6);
+  }
+  if (((edges & 2048u) != 0u)) {
+    interpZ(11u, i3, v3, v7);
+  }
+  let triTableOffset = ((cubeIndex << 4u) + 1u);
+  let indexCount = u32(tables.tris[(triTableOffset - 1u)]);
+  var firstVertex = atomicAdd(&(drawOut.vertexCount), cubeVerts);
+  let bufferOffset = ((global_id.x + (global_id.y * volume.size.x)) + ((global_id.z * volume.size.x) * volume.size.y));
+  let firstIndex = (bufferOffset * 15u);
+  for(var i = 0u; (i < cubeVerts); i = (i + 1u)) {
+    positionsOut.values[((firstVertex * 3u) + (i * 3u))] = positions[i].x;
+    positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = positions[i].y;
+    positionsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = positions[i].z;
+    normalsOut.values[((firstVertex * 3u) + (i * 3u))] = normals[i].x;
+    normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 1u)] = normals[i].y;
+    normalsOut.values[(((firstVertex * 3u) + (i * 3u)) + 2u)] = normals[i].z;
+  }
+  for(var i = 0u; (i < indexCount); i = (i + 1u)) {
+    let index = tables.tris[(triTableOffset + i)];
+    indicesOut.tris[(firstIndex + i)] = (firstVertex + indices[index]);
+  }
+  for(var i = indexCount; (i < 15u); i = (i + 1u)) {
+    indicesOut.tris[(firstIndex + i)] = firstVertex;
+  }
+}
diff --git a/test/benchmark/particles.wgsl.expected.glsl b/test/benchmark/particles.wgsl.expected.glsl
new file mode 100644
index 0000000..7afb6e1
--- /dev/null
+++ b/test/benchmark/particles.wgsl.expected.glsl
@@ -0,0 +1,515 @@
+SKIP: FAILED
+
+#version 310 es
+precision mediump float;
+
+struct RenderParams {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+};
+
+layout (binding = 0) uniform RenderParams_1 {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+} render_params;
+
+struct VertexInput {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct VertexOutput {
+  vec4 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_4 {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+
+VertexOutput vs_main_inner(VertexInput tint_symbol) {
+  vec3 quad_pos = (mat2x3(render_params.right, render_params.up) * tint_symbol.quad_pos);
+  vec3 position = (tint_symbol.position + (quad_pos * 0.01f));
+  VertexOutput tint_symbol_1 = VertexOutput(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f), vec2(0.0f, 0.0f));
+  tint_symbol_1.position = (render_params.modelViewProjectionMatrix * vec4(position, 1.0f));
+  tint_symbol_1.color = tint_symbol.color;
+  tint_symbol_1.quad_pos = tint_symbol.quad_pos;
+  return tint_symbol_1;
+}
+
+struct tint_symbol_7 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_8 {
+  vec4 value;
+};
+struct SimulationParams {
+  float deltaTime;
+  vec4 seed;
+};
+struct Particle {
+  vec3 position;
+  float lifetime;
+  vec4 color;
+  vec3 velocity;
+};
+struct tint_symbol_10 {
+  uvec3 GlobalInvocationID;
+};
+struct UBO {
+  uint width;
+};
+struct tint_symbol_12 {
+  uvec3 coord;
+};
+struct tint_symbol_14 {
+  uvec3 coord;
+};
+
+tint_symbol_5 vs_main(tint_symbol_4 tint_symbol_3) {
+  VertexInput tint_symbol_15 = VertexInput(tint_symbol_3.position, tint_symbol_3.color, tint_symbol_3.quad_pos);
+  VertexOutput inner_result = vs_main_inner(tint_symbol_15);
+  tint_symbol_5 wrapper_result = tint_symbol_5(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec2(0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.position = inner_result.position;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.quad_pos = inner_result.quad_pos;
+  return wrapper_result;
+}
+in vec3 position;
+in vec4 color;
+in vec2 quad_pos;
+out vec4 color;
+out vec2 quad_pos;
+void main() {
+  tint_symbol_4 inputs;
+  inputs.position = position;
+  inputs.color = color;
+  inputs.quad_pos = quad_pos;
+  tint_symbol_5 outputs;
+  outputs = vs_main(inputs);
+  color = outputs.color;
+  quad_pos = outputs.quad_pos;
+  gl_Position = outputs.position;
+  gl_Position.y = -gl_Position.y;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:90: 'color' : redefinition 
+ERROR: 1 compilation errors.  No code generated.
+
+
+
+#version 310 es
+precision mediump float;
+
+struct RenderParams {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+};
+struct VertexInput {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct VertexOutput {
+  vec4 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_4 {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_7 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_8 {
+  vec4 value;
+};
+
+vec4 fs_main_inner(VertexOutput tint_symbol) {
+  vec4 color = tint_symbol.color;
+  color.a = (color.a * max((1.0f - length(tint_symbol.quad_pos)), 0.0f));
+  return color;
+}
+
+struct SimulationParams {
+  float deltaTime;
+  vec4 seed;
+};
+struct Particle {
+  vec3 position;
+  float lifetime;
+  vec4 color;
+  vec3 velocity;
+};
+struct tint_symbol_10 {
+  uvec3 GlobalInvocationID;
+};
+struct UBO {
+  uint width;
+};
+struct tint_symbol_12 {
+  uvec3 coord;
+};
+struct tint_symbol_14 {
+  uvec3 coord;
+};
+
+tint_symbol_8 fs_main(tint_symbol_7 tint_symbol_6) {
+  VertexOutput tint_symbol_15 = VertexOutput(tint_symbol_6.position, tint_symbol_6.color, tint_symbol_6.quad_pos);
+  vec4 inner_result_1 = fs_main_inner(tint_symbol_15);
+  tint_symbol_8 wrapper_result_1 = tint_symbol_8(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+in vec4 color;
+in vec2 quad_pos;
+out vec4 value;
+void main() {
+  tint_symbol_7 inputs;
+  inputs.color = color;
+  inputs.quad_pos = quad_pos;
+  inputs.position = gl_FragCoord;
+  tint_symbol_8 outputs;
+  outputs = fs_main(inputs);
+  value = outputs.value;
+}
+
+
+#version 310 es
+precision mediump float;
+
+vec2 rand_seed = vec2(0.0f, 0.0f);
+
+float rand() {
+  rand_seed.x = frac((cos(dot(rand_seed, vec2(23.140779495f, 232.616897583f))) * 136.816802979f));
+  rand_seed.y = frac((cos(dot(rand_seed, vec2(54.478565216f, 345.841522217f))) * 534.764526367f));
+  return rand_seed.y;
+}
+
+struct RenderParams {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+};
+struct VertexInput {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct VertexOutput {
+  vec4 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_4 {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_7 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_8 {
+  vec4 value;
+};
+struct SimulationParams {
+  float deltaTime;
+  vec4 seed;
+};
+struct Particle {
+  vec3 position;
+  float lifetime;
+  vec4 color;
+  vec3 velocity;
+};
+
+layout (binding = 0) uniform SimulationParams_1 {
+  float deltaTime;
+  vec4 seed;
+} sim_params;
+layout (binding = 1) buffer Particles_1 {
+  Particle particles[];
+} data;
+uniform highp sampler2D tint_symbol_2;
+
+struct tint_symbol_10 {
+  uvec3 GlobalInvocationID;
+};
+
+void simulate_inner(uvec3 GlobalInvocationID) {
+  rand_seed = ((sim_params.seed.xy + vec2(GlobalInvocationID.xy)) * sim_params.seed.zw);
+  uint idx = GlobalInvocationID.x;
+  Particle particle = data.particles[idx];
+  particle.velocity.z = (particle.velocity.z - (sim_params.deltaTime * 0.5f));
+  particle.position = (particle.position + (sim_params.deltaTime * particle.velocity));
+  particle.lifetime = (particle.lifetime - sim_params.deltaTime);
+  particle.color.a = smoothstep(0.0f, 0.5f, particle.lifetime);
+  if ((particle.lifetime < 0.0f)) {
+    ivec2 coord = ivec2(0, 0);
+    {
+      for(int level = (textureQueryLevels(tint_symbol_2); - 1); (level > 0); level = (level - 1)) {
+        vec4 probabilites = texelFetch(tint_symbol_2, coord, level);
+        vec4 value = vec4(rand());
+        bvec4 mask = (greaterThanEqual(value, vec4(0.0f, probabilites.xyz)) & lessThan(value, probabilites));
+        coord = (coord * 2);
+        coord.x = (coord.x + (any(mask.yw) ? 1 : 0));
+        coord.y = (coord.y + (any(mask.zw) ? 1 : 0));
+      }
+    }
+    vec2 uv = (vec2(coord) / vec2(textureSize(tint_symbol_2, 0)));
+    particle.position = vec3((((uv - 0.5f) * 3.0f) * vec2(1.0f, -1.0f)), 0.0f);
+    particle.color = texelFetch(tint_symbol_2, coord, 0);
+    particle.velocity.x = ((rand() - 0.5f) * 0.100000001f);
+    particle.velocity.y = ((rand() - 0.5f) * 0.100000001f);
+    particle.velocity.z = (rand() * 0.300000012f);
+    particle.lifetime = (0.5f + (rand() * 2.0f));
+  }
+  data.particles[idx] = particle;
+}
+
+struct UBO {
+  uint width;
+};
+struct tint_symbol_12 {
+  uvec3 coord;
+};
+struct tint_symbol_14 {
+  uvec3 coord;
+};
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+void simulate(tint_symbol_10 tint_symbol_9) {
+  simulate_inner(tint_symbol_9.GlobalInvocationID);
+  return;
+}
+void main() {
+  tint_symbol_10 inputs;
+  inputs.GlobalInvocationID = gl_GlobalInvocationID;
+  simulate(inputs);
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:7: 'frac' : no matching overloaded function found 
+ERROR: 0:7: '' : compilation terminated 
+ERROR: 2 compilation errors.  No code generated.
+
+
+
+#version 310 es
+precision mediump float;
+
+struct RenderParams {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+};
+struct VertexInput {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct VertexOutput {
+  vec4 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_4 {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_7 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_8 {
+  vec4 value;
+};
+struct SimulationParams {
+  float deltaTime;
+  vec4 seed;
+};
+struct Particle {
+  vec3 position;
+  float lifetime;
+  vec4 color;
+  vec3 velocity;
+};
+struct tint_symbol_10 {
+  uvec3 GlobalInvocationID;
+};
+struct UBO {
+  uint width;
+};
+
+layout (binding = 3) uniform UBO_1 {
+  uint width;
+} ubo;
+layout (binding = 4) buffer Buffer_1 {
+  float weights[];
+} buf_in;
+layout (binding = 5) buffer Buffer_2 {
+  float weights[];
+} buf_out;
+uniform highp sampler2D tex_in;
+
+struct tint_symbol_12 {
+  uvec3 coord;
+};
+
+void import_level_inner(uvec3 coord) {
+  uint offset = (coord.x + (coord.y * ubo.width));
+  buf_out.weights[offset] = texelFetch(tex_in, ivec2(coord.xy), 0).w;
+}
+
+struct tint_symbol_14 {
+  uvec3 coord;
+};
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+void import_level(tint_symbol_12 tint_symbol_11) {
+  import_level_inner(tint_symbol_11.coord);
+  return;
+}
+void main() {
+  tint_symbol_12 inputs;
+  inputs.coord = gl_GlobalInvocationID;
+  import_level(inputs);
+}
+
+
+#version 310 es
+precision mediump float;
+
+struct RenderParams {
+  mat4 modelViewProjectionMatrix;
+  vec3 right;
+  vec3 up;
+};
+struct VertexInput {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct VertexOutput {
+  vec4 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_4 {
+  vec3 position;
+  vec4 color;
+  vec2 quad_pos;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_7 {
+  vec4 color;
+  vec2 quad_pos;
+  vec4 position;
+};
+struct tint_symbol_8 {
+  vec4 value;
+};
+struct SimulationParams {
+  float deltaTime;
+  vec4 seed;
+};
+struct Particle {
+  vec3 position;
+  float lifetime;
+  vec4 color;
+  vec3 velocity;
+};
+struct tint_symbol_10 {
+  uvec3 GlobalInvocationID;
+};
+struct UBO {
+  uint width;
+};
+
+layout (binding = 3) uniform UBO_1 {
+  uint width;
+} ubo;
+layout (binding = 4) buffer Buffer_1 {
+  float weights[];
+} buf_in;
+layout (binding = 5) buffer Buffer_2 {
+  float weights[];
+} buf_out;
+uniform highp writeonly image2D tex_out;
+
+struct tint_symbol_12 {
+  uvec3 coord;
+};
+struct tint_symbol_14 {
+  uvec3 coord;
+};
+
+void export_level_inner(uvec3 coord) {
+  if (all(lessThan(coord.xy, uvec2(imageSize(tex_out))))) {
+    uint dst_offset = (coord.x + (coord.y * ubo.width));
+    uint src_offset = ((coord.x * 2u) + ((coord.y * 2u) * ubo.width));
+    float a_1 = buf_in.weights[(src_offset + 0u)];
+    float b = buf_in.weights[(src_offset + 1u)];
+    float c = buf_in.weights[((src_offset + 0u) + ubo.width)];
+    float d = buf_in.weights[((src_offset + 1u) + ubo.width)];
+    float sum = dot(vec4(a_1, b, c, d), vec4(1.0f));
+    buf_out.weights[dst_offset] = (sum / 4.0f);
+    vec4 probabilities = (vec4(a_1, (a_1 + b), ((a_1 + b) + c), sum) / max(sum, 0.0001f));
+    imageStore(tex_out, ivec2(coord.xy), probabilities);
+  }
+}
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+void export_level(tint_symbol_14 tint_symbol_13) {
+  export_level_inner(tint_symbol_13.coord);
+  return;
+}
+void main() {
+  tint_symbol_14 inputs;
+  inputs.coord = gl_GlobalInvocationID;
+  export_level(inputs);
+}
+
+
diff --git a/test/benchmark/shadow-fragment.wgsl b/test/benchmark/shadow-fragment.wgsl
new file mode 100644
index 0000000..e1a0b6e
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl
@@ -0,0 +1,42 @@
+let shadowDepthTextureSize : f32 = 1024.0;
+
+struct Scene {
+  lightViewProjMatrix : mat4x4<f32>;
+  cameraViewProjMatrix : mat4x4<f32>;
+  lightPos : vec3<f32>;
+}
+
+@group(0) @binding(0) var<uniform> scene : Scene;
+
+@group(0) @binding(1) var shadowMap : texture_depth_2d;
+
+@group(0) @binding(2) var shadowSampler : sampler_comparison;
+
+struct FragmentInput {
+  @location(0)
+  shadowPos : vec3<f32>;
+  @location(1)
+  fragPos : vec3<f32>;
+  @location(2)
+  fragNorm : vec3<f32>;
+}
+
+let albedo : vec3<f32> = vec3<f32>(0.899999976, 0.899999976, 0.899999976);
+
+let ambientFactor : f32 = 0.200000003;
+
+@stage(fragment)
+fn main(input : FragmentInput) -> @location(0) vec4<f32> {
+  var visibility : f32 = 0.0;
+  let oneOverShadowDepthTextureSize = (1.0 / shadowDepthTextureSize);
+  for(var y : i32 = -1; (y <= 1); y = (y + 1)) {
+    for(var x : i32 = -1; (x <= 1); x = (x + 1)) {
+      let offset : vec2<f32> = vec2<f32>((f32(x) * oneOverShadowDepthTextureSize), (f32(y) * oneOverShadowDepthTextureSize));
+      visibility = (visibility + textureSampleCompare(shadowMap, shadowSampler, (input.shadowPos.xy + offset), (input.shadowPos.z - 0.007)));
+    }
+  }
+  visibility = (visibility / 9.0);
+  let lambertFactor : f32 = max(dot(normalize((scene.lightPos - input.fragPos)), input.fragNorm), 0.0);
+  let lightingFactor : f32 = min((ambientFactor + (visibility * lambertFactor)), 1.0);
+  return vec4<f32>((lightingFactor * albedo), 1.0);
+}
diff --git a/test/benchmark/shadow-fragment.wgsl.expected.glsl b/test/benchmark/shadow-fragment.wgsl.expected.glsl
new file mode 100644
index 0000000..f2248c4
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl.expected.glsl
@@ -0,0 +1,87 @@
+SKIP: FAILED
+
+#version 310 es
+precision mediump float;
+
+const float shadowDepthTextureSize = 1024.0f;
+
+struct Scene {
+  mat4 lightViewProjMatrix;
+  mat4 cameraViewProjMatrix;
+  vec3 lightPos;
+};
+
+layout (binding = 0) uniform Scene_1 {
+  mat4 lightViewProjMatrix;
+  mat4 cameraViewProjMatrix;
+  vec3 lightPos;
+} scene;
+uniform highp sampler2D shadowMap;
+
+
+struct FragmentInput {
+  vec3 shadowPos;
+  vec3 fragPos;
+  vec3 fragNorm;
+};
+
+const vec3 albedo = vec3(0.899999976f, 0.899999976f, 0.899999976f);
+const float ambientFactor = 0.200000003f;
+
+struct tint_symbol_3 {
+  vec3 shadowPos;
+  vec3 fragPos;
+  vec3 fragNorm;
+};
+struct tint_symbol_4 {
+  vec4 value;
+};
+
+vec4 tint_symbol_inner(FragmentInput tint_symbol_1) {
+  float visibility = 0.0f;
+  float oneOverShadowDepthTextureSize = (1.0f / shadowDepthTextureSize);
+  {
+    for(int y = -1; (y <= 1); y = (y + 1)) {
+      {
+        for(int x = -1; (x <= 1); x = (x + 1)) {
+          vec2 offset = vec2((float(x) * oneOverShadowDepthTextureSize), (float(y) * oneOverShadowDepthTextureSize));
+          visibility = (visibility + texture(shadowMap, (tint_symbol_1.shadowPos.xy + offset), (tint_symbol_1.shadowPos.z - 0.007f)));
+        }
+      }
+    }
+  }
+  visibility = (visibility / 9.0f);
+  float lambertFactor = max(dot(normalize((scene.lightPos - tint_symbol_1.fragPos)), tint_symbol_1.fragNorm), 0.0f);
+  float lightingFactor = min((ambientFactor + (visibility * lambertFactor)), 1.0f);
+  return vec4((lightingFactor * albedo), 1.0f);
+}
+
+tint_symbol_4 tint_symbol(tint_symbol_3 tint_symbol_2) {
+  FragmentInput tint_symbol_5 = FragmentInput(tint_symbol_2.shadowPos, tint_symbol_2.fragPos, tint_symbol_2.fragNorm);
+  vec4 inner_result = tint_symbol_inner(tint_symbol_5);
+  tint_symbol_4 wrapper_result = tint_symbol_4(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+in vec3 shadowPos;
+in vec3 fragPos;
+in vec3 fragNorm;
+out vec4 value;
+void main() {
+  tint_symbol_3 inputs;
+  inputs.shadowPos = shadowPos;
+  inputs.fragPos = fragPos;
+  inputs.fragNorm = fragNorm;
+  tint_symbol_4 outputs;
+  outputs = tint_symbol(inputs);
+  value = outputs.value;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:46: 'assign' :  cannot convert from ' temp highp 4-component vector of float' to ' temp mediump float'
+ERROR: 0:46: '' : compilation terminated 
+ERROR: 2 compilation errors.  No code generated.
+
+
+
diff --git a/test/benchmark/shadow-fragment.wgsl.expected.hlsl b/test/benchmark/shadow-fragment.wgsl.expected.hlsl
new file mode 100644
index 0000000..58baa9e
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl.expected.hlsl
@@ -0,0 +1,52 @@
+static const float shadowDepthTextureSize = 1024.0f;
+
+cbuffer cbuffer_scene : register(b0, space0) {
+  uint4 scene[9];
+};
+Texture2D shadowMap : register(t1, space0);
+SamplerComparisonState shadowSampler : register(s2, space0);
+
+struct FragmentInput {
+  float3 shadowPos;
+  float3 fragPos;
+  float3 fragNorm;
+};
+
+static const float3 albedo = float3(0.899999976f, 0.899999976f, 0.899999976f);
+static const float ambientFactor = 0.200000003f;
+
+struct tint_symbol_1 {
+  float3 shadowPos : TEXCOORD0;
+  float3 fragPos : TEXCOORD1;
+  float3 fragNorm : TEXCOORD2;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Target0;
+};
+
+float4 main_inner(FragmentInput input) {
+  float visibility = 0.0f;
+  const float oneOverShadowDepthTextureSize = (1.0f / shadowDepthTextureSize);
+  {
+    [loop] for(int y = -1; (y <= 1); y = (y + 1)) {
+      {
+        [loop] for(int x = -1; (x <= 1); x = (x + 1)) {
+          const float2 offset = float2((float(x) * oneOverShadowDepthTextureSize), (float(y) * oneOverShadowDepthTextureSize));
+          visibility = (visibility + shadowMap.SampleCmp(shadowSampler, (input.shadowPos.xy + offset), (input.shadowPos.z - 0.007f)));
+        }
+      }
+    }
+  }
+  visibility = (visibility / 9.0f);
+  const float lambertFactor = max(dot(normalize((asfloat(scene[8].xyz) - input.fragPos)), input.fragNorm), 0.0f);
+  const float lightingFactor = min((ambientFactor + (visibility * lambertFactor)), 1.0f);
+  return float4((lightingFactor * albedo), 1.0f);
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const FragmentInput tint_symbol_4 = {tint_symbol.shadowPos, tint_symbol.fragPos, tint_symbol.fragNorm};
+  const float4 inner_result = main_inner(tint_symbol_4);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
diff --git a/test/benchmark/shadow-fragment.wgsl.expected.msl b/test/benchmark/shadow-fragment.wgsl.expected.msl
new file mode 100644
index 0000000..409f8fd
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl.expected.msl
@@ -0,0 +1,60 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+
+struct Scene {
+  /* 0x0000 */ float4x4 lightViewProjMatrix;
+  /* 0x0040 */ float4x4 cameraViewProjMatrix;
+  /* 0x0080 */ packed_float3 lightPos;
+  /* 0x008c */ int8_t tint_pad[4];
+};
+struct FragmentInput {
+  float3 shadowPos;
+  float3 fragPos;
+  float3 fragNorm;
+};
+struct tint_symbol_2 {
+  float3 shadowPos [[user(locn0)]];
+  float3 fragPos [[user(locn1)]];
+  float3 fragNorm [[user(locn2)]];
+};
+struct tint_symbol_3 {
+  float4 value [[color(0)]];
+};
+
+constant float shadowDepthTextureSize = 1024.0f;
+constant float3 albedo = float3(0.899999976f, 0.899999976f, 0.899999976f);
+constant float ambientFactor = 0.200000003f;
+float4 tint_symbol_inner(FragmentInput input, depth2d<float, access::sample> tint_symbol_5, sampler tint_symbol_6, const constant Scene* const tint_symbol_7) {
+  float visibility = 0.0f;
+  float const oneOverShadowDepthTextureSize = (1.0f / shadowDepthTextureSize);
+  for(int y = -1; (y <= 1); y = as_type<int>((as_type<uint>(y) + as_type<uint>(1)))) {
+    for(int x = -1; (x <= 1); x = as_type<int>((as_type<uint>(x) + as_type<uint>(1)))) {
+      float2 const offset = float2((float(x) * oneOverShadowDepthTextureSize), (float(y) * oneOverShadowDepthTextureSize));
+      visibility = (visibility + tint_symbol_5.sample_compare(tint_symbol_6, (float3(input.shadowPos).xy + offset), (input.shadowPos[2] - 0.007f)));
+    }
+  }
+  visibility = (visibility / 9.0f);
+  float const lambertFactor = fmax(dot(normalize(((*(tint_symbol_7)).lightPos - input.fragPos)), input.fragNorm), 0.0f);
+  float const lightingFactor = fmin((ambientFactor + (visibility * lambertFactor)), 1.0f);
+  return float4((lightingFactor * albedo), 1.0f);
+}
+
+fragment tint_symbol_3 tint_symbol(depth2d<float, access::sample> tint_symbol_8 [[texture(0)]], sampler tint_symbol_9 [[sampler(0)]], const constant Scene* tint_symbol_10 [[buffer(0)]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  FragmentInput const tint_symbol_4 = {.shadowPos=tint_symbol_1.shadowPos, .fragPos=tint_symbol_1.fragPos, .fragNorm=tint_symbol_1.fragNorm};
+  float4 const inner_result = tint_symbol_inner(tint_symbol_4, tint_symbol_8, tint_symbol_9, tint_symbol_10);
+  tint_symbol_3 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
diff --git a/test/benchmark/shadow-fragment.wgsl.expected.spvasm b/test/benchmark/shadow-fragment.wgsl.expected.spvasm
new file mode 100644
index 0000000..d6b1d1f
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl.expected.spvasm
@@ -0,0 +1,203 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 121
+; Schema: 0
+               OpCapability Shader
+         %92 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %shadowPos_1 %fragPos_1 %fragNorm_1 %value
+               OpExecutionMode %main OriginUpperLeft
+               OpName %shadowPos_1 "shadowPos_1"
+               OpName %fragPos_1 "fragPos_1"
+               OpName %fragNorm_1 "fragNorm_1"
+               OpName %value "value"
+               OpName %shadowDepthTextureSize "shadowDepthTextureSize"
+               OpName %Scene "Scene"
+               OpMemberName %Scene 0 "lightViewProjMatrix"
+               OpMemberName %Scene 1 "cameraViewProjMatrix"
+               OpMemberName %Scene 2 "lightPos"
+               OpName %scene "scene"
+               OpName %shadowMap "shadowMap"
+               OpName %shadowSampler "shadowSampler"
+               OpName %albedo "albedo"
+               OpName %ambientFactor "ambientFactor"
+               OpName %FragmentInput "FragmentInput"
+               OpMemberName %FragmentInput 0 "shadowPos"
+               OpMemberName %FragmentInput 1 "fragPos"
+               OpMemberName %FragmentInput 2 "fragNorm"
+               OpName %main_inner "main_inner"
+               OpName %input "input"
+               OpName %visibility "visibility"
+               OpName %y "y"
+               OpName %x "x"
+               OpName %main "main"
+               OpDecorate %shadowPos_1 Location 0
+               OpDecorate %fragPos_1 Location 1
+               OpDecorate %fragNorm_1 Location 2
+               OpDecorate %value Location 0
+               OpDecorate %Scene Block
+               OpMemberDecorate %Scene 0 Offset 0
+               OpMemberDecorate %Scene 0 ColMajor
+               OpMemberDecorate %Scene 0 MatrixStride 16
+               OpMemberDecorate %Scene 1 Offset 64
+               OpMemberDecorate %Scene 1 ColMajor
+               OpMemberDecorate %Scene 1 MatrixStride 16
+               OpMemberDecorate %Scene 2 Offset 128
+               OpDecorate %scene NonWritable
+               OpDecorate %scene DescriptorSet 0
+               OpDecorate %scene Binding 0
+               OpDecorate %shadowMap DescriptorSet 0
+               OpDecorate %shadowMap Binding 1
+               OpDecorate %shadowSampler DescriptorSet 0
+               OpDecorate %shadowSampler Binding 2
+               OpMemberDecorate %FragmentInput 0 Offset 0
+               OpMemberDecorate %FragmentInput 1 Offset 16
+               OpMemberDecorate %FragmentInput 2 Offset 32
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+%shadowPos_1 = OpVariable %_ptr_Input_v3float Input
+  %fragPos_1 = OpVariable %_ptr_Input_v3float Input
+ %fragNorm_1 = OpVariable %_ptr_Input_v3float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %10 = OpConstantNull %v4float
+      %value = OpVariable %_ptr_Output_v4float Output %10
+%shadowDepthTextureSize = OpConstant %float 1024
+%mat4v4float = OpTypeMatrix %v4float 4
+      %Scene = OpTypeStruct %mat4v4float %mat4v4float %v3float
+%_ptr_Uniform_Scene = OpTypePointer Uniform %Scene
+      %scene = OpVariable %_ptr_Uniform_Scene Uniform
+         %18 = OpTypeImage %float 2D 1 0 0 1 Unknown
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+  %shadowMap = OpVariable %_ptr_UniformConstant_18 UniformConstant
+         %21 = OpTypeSampler
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%shadowSampler = OpVariable %_ptr_UniformConstant_21 UniformConstant
+%float_0_899999976 = OpConstant %float 0.899999976
+     %albedo = OpConstantComposite %v3float %float_0_899999976 %float_0_899999976 %float_0_899999976
+%ambientFactor = OpConstant %float 0.200000003
+%FragmentInput = OpTypeStruct %v3float %v3float %v3float
+         %25 = OpTypeFunction %v4float %FragmentInput
+    %float_0 = OpConstant %float 0
+%_ptr_Function_float = OpTypePointer Function %float
+         %33 = OpConstantNull %float
+    %float_1 = OpConstant %float 1
+        %int = OpTypeInt 32 1
+     %int_n1 = OpConstant %int -1
+%_ptr_Function_int = OpTypePointer Function %int
+         %40 = OpConstantNull %int
+      %int_1 = OpConstant %int 1
+       %bool = OpTypeBool
+    %v2float = OpTypeVector %float 2
+         %74 = OpTypeSampledImage %18
+%float_0_00700000022 = OpConstant %float 0.00700000022
+    %float_9 = OpConstant %float 9
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
+       %void = OpTypeVoid
+        %112 = OpTypeFunction %void
+ %main_inner = OpFunction %v4float None %25
+      %input = OpFunctionParameter %FragmentInput
+         %29 = OpLabel
+ %visibility = OpVariable %_ptr_Function_float Function %33
+          %y = OpVariable %_ptr_Function_int Function %40
+          %x = OpVariable %_ptr_Function_int Function %40
+               OpStore %visibility %float_0
+         %35 = OpFDiv %float %float_1 %shadowDepthTextureSize
+               OpStore %y %int_n1
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %42 %43 None
+               OpBranch %44
+         %44 = OpLabel
+         %46 = OpLoad %int %y
+         %48 = OpSLessThanEqual %bool %46 %int_1
+         %45 = OpLogicalNot %bool %48
+               OpSelectionMerge %50 None
+               OpBranchConditional %45 %51 %50
+         %51 = OpLabel
+               OpBranch %42
+         %50 = OpLabel
+               OpStore %x %int_n1
+               OpBranch %53
+         %53 = OpLabel
+               OpLoopMerge %54 %55 None
+               OpBranch %56
+         %56 = OpLabel
+         %58 = OpLoad %int %x
+         %59 = OpSLessThanEqual %bool %58 %int_1
+         %57 = OpLogicalNot %bool %59
+               OpSelectionMerge %60 None
+               OpBranchConditional %57 %61 %60
+         %61 = OpLabel
+               OpBranch %54
+         %60 = OpLabel
+         %64 = OpLoad %int %x
+         %63 = OpConvertSToF %float %64
+         %65 = OpFMul %float %63 %35
+         %67 = OpLoad %int %y
+         %66 = OpConvertSToF %float %67
+         %68 = OpFMul %float %66 %35
+         %69 = OpCompositeConstruct %v2float %65 %68
+         %70 = OpLoad %float %visibility
+         %72 = OpLoad %21 %shadowSampler
+         %73 = OpLoad %18 %shadowMap
+         %75 = OpSampledImage %74 %73 %72
+         %76 = OpCompositeExtract %v3float %input 0
+         %77 = OpVectorShuffle %v2float %76 %76 0 1
+         %78 = OpFAdd %v2float %77 %69
+         %79 = OpCompositeExtract %v3float %input 0
+         %80 = OpCompositeExtract %float %79 2
+         %82 = OpFSub %float %80 %float_0_00700000022
+         %71 = OpImageSampleDrefImplicitLod %float %75 %78 %82
+         %83 = OpFAdd %float %70 %71
+               OpStore %visibility %83
+               OpBranch %55
+         %55 = OpLabel
+         %84 = OpLoad %int %x
+         %85 = OpIAdd %int %84 %int_1
+               OpStore %x %85
+               OpBranch %53
+         %54 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+         %86 = OpLoad %int %y
+         %87 = OpIAdd %int %86 %int_1
+               OpStore %y %87
+               OpBranch %41
+         %42 = OpLabel
+         %88 = OpLoad %float %visibility
+         %90 = OpFDiv %float %88 %float_9
+               OpStore %visibility %90
+         %98 = OpAccessChain %_ptr_Uniform_v3float %scene %uint_2
+         %99 = OpLoad %v3float %98
+        %100 = OpCompositeExtract %v3float %input 1
+        %101 = OpFSub %v3float %99 %100
+         %94 = OpExtInst %v3float %92 Normalize %101
+        %102 = OpCompositeExtract %v3float %input 2
+         %93 = OpDot %float %94 %102
+         %91 = OpExtInst %float %92 NMax %93 %float_0
+        %104 = OpLoad %float %visibility
+        %105 = OpFMul %float %104 %91
+        %106 = OpFAdd %float %ambientFactor %105
+        %103 = OpExtInst %float %92 NMin %106 %float_1
+        %107 = OpVectorTimesScalar %v3float %albedo %103
+        %108 = OpCompositeExtract %float %107 0
+        %109 = OpCompositeExtract %float %107 1
+        %110 = OpCompositeExtract %float %107 2
+        %111 = OpCompositeConstruct %v4float %108 %109 %110 %float_1
+               OpReturnValue %111
+               OpFunctionEnd
+       %main = OpFunction %void None %112
+        %115 = OpLabel
+        %117 = OpLoad %v3float %shadowPos_1
+        %118 = OpLoad %v3float %fragPos_1
+        %119 = OpLoad %v3float %fragNorm_1
+        %120 = OpCompositeConstruct %FragmentInput %117 %118 %119
+        %116 = OpFunctionCall %v4float %main_inner %120
+               OpStore %value %116
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/shadow-fragment.wgsl.expected.wgsl b/test/benchmark/shadow-fragment.wgsl.expected.wgsl
new file mode 100644
index 0000000..e1a0b6e
--- /dev/null
+++ b/test/benchmark/shadow-fragment.wgsl.expected.wgsl
@@ -0,0 +1,42 @@
+let shadowDepthTextureSize : f32 = 1024.0;
+
+struct Scene {
+  lightViewProjMatrix : mat4x4<f32>;
+  cameraViewProjMatrix : mat4x4<f32>;
+  lightPos : vec3<f32>;
+}
+
+@group(0) @binding(0) var<uniform> scene : Scene;
+
+@group(0) @binding(1) var shadowMap : texture_depth_2d;
+
+@group(0) @binding(2) var shadowSampler : sampler_comparison;
+
+struct FragmentInput {
+  @location(0)
+  shadowPos : vec3<f32>;
+  @location(1)
+  fragPos : vec3<f32>;
+  @location(2)
+  fragNorm : vec3<f32>;
+}
+
+let albedo : vec3<f32> = vec3<f32>(0.899999976, 0.899999976, 0.899999976);
+
+let ambientFactor : f32 = 0.200000003;
+
+@stage(fragment)
+fn main(input : FragmentInput) -> @location(0) vec4<f32> {
+  var visibility : f32 = 0.0;
+  let oneOverShadowDepthTextureSize = (1.0 / shadowDepthTextureSize);
+  for(var y : i32 = -1; (y <= 1); y = (y + 1)) {
+    for(var x : i32 = -1; (x <= 1); x = (x + 1)) {
+      let offset : vec2<f32> = vec2<f32>((f32(x) * oneOverShadowDepthTextureSize), (f32(y) * oneOverShadowDepthTextureSize));
+      visibility = (visibility + textureSampleCompare(shadowMap, shadowSampler, (input.shadowPos.xy + offset), (input.shadowPos.z - 0.007)));
+    }
+  }
+  visibility = (visibility / 9.0);
+  let lambertFactor : f32 = max(dot(normalize((scene.lightPos - input.fragPos)), input.fragNorm), 0.0);
+  let lightingFactor : f32 = min((ambientFactor + (visibility * lambertFactor)), 1.0);
+  return vec4<f32>((lightingFactor * albedo), 1.0);
+}
diff --git a/test/benchmark/simple_compute.wgsl b/test/benchmark/simple-compute.wgsl
similarity index 100%
rename from test/benchmark/simple_compute.wgsl
rename to test/benchmark/simple-compute.wgsl
diff --git a/test/benchmark/simple-compute.wgsl.expected.glsl b/test/benchmark/simple-compute.wgsl.expected.glsl
new file mode 100644
index 0000000..cd8d2ea
--- /dev/null
+++ b/test/benchmark/simple-compute.wgsl.expected.glsl
@@ -0,0 +1,28 @@
+#version 310 es
+precision mediump float;
+
+
+layout (binding = 0) buffer SB_1 {
+  int data[];
+} tint_symbol;
+
+struct tint_symbol_3 {
+  uvec3 id;
+};
+
+void tint_symbol_1_inner(uvec3 id) {
+  tint_symbol.data[id.x] = (tint_symbol.data[id.x] + 1);
+}
+
+layout(local_size_x = 1, local_size_y = 2, local_size_z = 3) in;
+void tint_symbol_1(tint_symbol_3 tint_symbol_2) {
+  tint_symbol_1_inner(tint_symbol_2.id);
+  return;
+}
+void main() {
+  tint_symbol_3 inputs;
+  inputs.id = gl_GlobalInvocationID;
+  tint_symbol_1(inputs);
+}
+
+
diff --git a/test/benchmark/simple_compute.wgsl.expected.hlsl b/test/benchmark/simple-compute.wgsl.expected.hlsl
similarity index 100%
rename from test/benchmark/simple_compute.wgsl.expected.hlsl
rename to test/benchmark/simple-compute.wgsl.expected.hlsl
diff --git a/test/benchmark/simple_compute.wgsl.expected.msl b/test/benchmark/simple-compute.wgsl.expected.msl
similarity index 100%
rename from test/benchmark/simple_compute.wgsl.expected.msl
rename to test/benchmark/simple-compute.wgsl.expected.msl
diff --git a/test/benchmark/simple_compute.wgsl.expected.spvasm b/test/benchmark/simple-compute.wgsl.expected.spvasm
similarity index 100%
rename from test/benchmark/simple_compute.wgsl.expected.spvasm
rename to test/benchmark/simple-compute.wgsl.expected.spvasm
diff --git a/test/benchmark/simple_compute.wgsl.expected.wgsl b/test/benchmark/simple-compute.wgsl.expected.wgsl
similarity index 100%
rename from test/benchmark/simple_compute.wgsl.expected.wgsl
rename to test/benchmark/simple-compute.wgsl.expected.wgsl
diff --git a/test/benchmark/simple_fragment.wgsl b/test/benchmark/simple-fragment.wgsl
similarity index 100%
rename from test/benchmark/simple_fragment.wgsl
rename to test/benchmark/simple-fragment.wgsl
diff --git a/test/benchmark/simple-fragment.wgsl.expected.glsl b/test/benchmark/simple-fragment.wgsl.expected.glsl
new file mode 100644
index 0000000..af5753a
--- /dev/null
+++ b/test/benchmark/simple-fragment.wgsl.expected.glsl
@@ -0,0 +1,47 @@
+SKIP: FAILED
+
+#version 310 es
+precision mediump float;
+
+struct Input {
+  vec4 color;
+};
+struct Output {
+  vec4 color;
+};
+struct tint_symbol_3 {
+  vec4 color;
+};
+struct tint_symbol_4 {
+  vec4 color;
+};
+
+Output tint_symbol_inner(Input tint_symbol_1) {
+  Output tint_symbol_5 = Output(tint_symbol_1.color);
+  return tint_symbol_5;
+}
+
+tint_symbol_4 tint_symbol(tint_symbol_3 tint_symbol_2) {
+  Input tint_symbol_6 = Input(tint_symbol_2.color);
+  Output inner_result = tint_symbol_inner(tint_symbol_6);
+  tint_symbol_4 wrapper_result = tint_symbol_4(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.color = inner_result.color;
+  return wrapper_result;
+}
+in vec4 color;
+out vec4 color;
+void main() {
+  tint_symbol_3 inputs;
+  inputs.color = color;
+  tint_symbol_4 outputs;
+  outputs = tint_symbol(inputs);
+  color = outputs.color;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:30: 'color' : redefinition 
+ERROR: 1 compilation errors.  No code generated.
+
+
+
diff --git a/test/benchmark/simple_fragment.wgsl.expected.hlsl b/test/benchmark/simple-fragment.wgsl.expected.hlsl
similarity index 100%
rename from test/benchmark/simple_fragment.wgsl.expected.hlsl
rename to test/benchmark/simple-fragment.wgsl.expected.hlsl
diff --git a/test/benchmark/simple_fragment.wgsl.expected.msl b/test/benchmark/simple-fragment.wgsl.expected.msl
similarity index 100%
rename from test/benchmark/simple_fragment.wgsl.expected.msl
rename to test/benchmark/simple-fragment.wgsl.expected.msl
diff --git a/test/benchmark/simple_fragment.wgsl.expected.spvasm b/test/benchmark/simple-fragment.wgsl.expected.spvasm
similarity index 100%
rename from test/benchmark/simple_fragment.wgsl.expected.spvasm
rename to test/benchmark/simple-fragment.wgsl.expected.spvasm
diff --git a/test/benchmark/simple_fragment.wgsl.expected.wgsl b/test/benchmark/simple-fragment.wgsl.expected.wgsl
similarity index 100%
rename from test/benchmark/simple_fragment.wgsl.expected.wgsl
rename to test/benchmark/simple-fragment.wgsl.expected.wgsl
diff --git a/test/benchmark/simple_vertex.wgsl b/test/benchmark/simple-vertex.wgsl
similarity index 100%
rename from test/benchmark/simple_vertex.wgsl
rename to test/benchmark/simple-vertex.wgsl
diff --git a/test/benchmark/simple-vertex.wgsl.expected.glsl b/test/benchmark/simple-vertex.wgsl.expected.glsl
new file mode 100644
index 0000000..a870df4
--- /dev/null
+++ b/test/benchmark/simple-vertex.wgsl.expected.glsl
@@ -0,0 +1,39 @@
+#version 310 es
+precision mediump float;
+
+struct Input {
+  vec4 position;
+};
+struct Output {
+  vec4 position;
+};
+struct tint_symbol_3 {
+  vec4 position;
+};
+struct tint_symbol_4 {
+  vec4 position;
+};
+
+Output tint_symbol_inner(Input tint_symbol_1) {
+  Output tint_symbol_5 = Output(tint_symbol_1.position);
+  return tint_symbol_5;
+}
+
+tint_symbol_4 tint_symbol(tint_symbol_3 tint_symbol_2) {
+  Input tint_symbol_6 = Input(tint_symbol_2.position);
+  Output inner_result = tint_symbol_inner(tint_symbol_6);
+  tint_symbol_4 wrapper_result = tint_symbol_4(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.position = inner_result.position;
+  return wrapper_result;
+}
+in vec4 position;
+void main() {
+  tint_symbol_3 inputs;
+  inputs.position = position;
+  tint_symbol_4 outputs;
+  outputs = tint_symbol(inputs);
+  gl_Position = outputs.position;
+  gl_Position.y = -gl_Position.y;
+}
+
+
diff --git a/test/benchmark/simple_vertex.wgsl.expected.hlsl b/test/benchmark/simple-vertex.wgsl.expected.hlsl
similarity index 100%
rename from test/benchmark/simple_vertex.wgsl.expected.hlsl
rename to test/benchmark/simple-vertex.wgsl.expected.hlsl
diff --git a/test/benchmark/simple_vertex.wgsl.expected.msl b/test/benchmark/simple-vertex.wgsl.expected.msl
similarity index 100%
rename from test/benchmark/simple_vertex.wgsl.expected.msl
rename to test/benchmark/simple-vertex.wgsl.expected.msl
diff --git a/test/benchmark/simple_vertex.wgsl.expected.spvasm b/test/benchmark/simple-vertex.wgsl.expected.spvasm
similarity index 100%
rename from test/benchmark/simple_vertex.wgsl.expected.spvasm
rename to test/benchmark/simple-vertex.wgsl.expected.spvasm
diff --git a/test/benchmark/simple_vertex.wgsl.expected.wgsl b/test/benchmark/simple-vertex.wgsl.expected.wgsl
similarity index 100%
rename from test/benchmark/simple_vertex.wgsl.expected.wgsl
rename to test/benchmark/simple-vertex.wgsl.expected.wgsl
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl
new file mode 100644
index 0000000..4021327
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl
@@ -0,0 +1,368 @@
+let GAMMA = 2.200000048;
+
+fn linearTosRGB(linear : vec3<f32>) -> vec3<f32> {
+  let INV_GAMMA = (1.0 / GAMMA);
+  return pow(linear, vec3(INV_GAMMA));
+}
+
+fn sRGBToLinear(srgb : vec3<f32>) -> vec3<f32> {
+  return pow(srgb, vec3(GAMMA));
+}
+
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@binding(0) @group(0) var<uniform> camera : Camera;
+
+struct ClusterLights {
+  offset : u32;
+  count : u32;
+}
+
+struct ClusterLightGroup {
+  offset : u32;
+  lights : array<ClusterLights, 27648>;
+  indices : array<u32, 1769472>;
+}
+
+@binding(1) @group(0) var<storage, read> clusterLights : ClusterLightGroup;
+
+struct Light {
+  position : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+struct GlobalLights {
+  ambient : vec3<f32>;
+  dirColor : vec3<f32>;
+  dirIntensity : f32;
+  dirDirection : vec3<f32>;
+  lightCount : u32;
+  lights : @stride(32) array<Light>;
+}
+
+@binding(2) @group(0) var<storage, read> globalLights : GlobalLights;
+
+let tileCount = vec3(32u, 18u, 48u);
+
+fn linearDepth(depthSample : f32) -> f32 {
+  return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar));
+}
+
+fn getTile(fragCoord : vec4<f32>) -> vec3<u32> {
+  let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear)));
+  let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear))));
+  let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0));
+  return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile);
+}
+
+fn getClusterIndex(fragCoord : vec4<f32>) -> u32 {
+  let tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+@binding(3) @group(0) var defaultSampler : sampler;
+
+@binding(4) @group(0) var shadowTexture : texture_depth_2d;
+
+@binding(5) @group(0) var shadowSampler : sampler_comparison;
+
+struct LightShadowTable {
+  light : array<i32>;
+}
+
+@binding(6) @group(0) var<storage, read> lightShadowTable : LightShadowTable;
+
+var<private> shadowSampleOffsets : array<vec2<f32>, 16> = array<vec2<f32>, 16>(vec2(-1.5, -1.5), vec2(-1.5, -0.5), vec2(-1.5, 0.5), vec2(-1.5, 1.5), vec2(-0.5, -1.5), vec2(-0.5, -0.5), vec2(-0.5, 0.5), vec2(-0.5, 1.5), vec2(0.5, -1.5), vec2(0.5, -0.5), vec2(0.5, 0.5), vec2(0.5, 1.5), vec2(1.5, -1.5), vec2(1.5, -0.5), vec2(1.5, 0.5), vec2(1.5, 1.5));
+
+let shadowSampleCount = 16u;
+
+struct ShadowProperties {
+  viewport : vec4<f32>;
+  viewProj : mat4x4<f32>;
+}
+
+struct LightShadows {
+  properties : array<ShadowProperties>;
+}
+
+@binding(7) @group(0) var<storage, read> shadow : LightShadows;
+
+fn dirLightVisibility(worldPos : vec3<f32>) -> f32 {
+  let shadowIndex = lightShadowTable.light[0u];
+  if ((shadowIndex == -1)) {
+    return 1.0;
+  }
+  let viewport = shadow.properties[shadowIndex].viewport;
+  let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0));
+  let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w));
+  let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0)));
+  let clampRect = vec4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize));
+  var visibility = 0.0;
+  for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003)));
+  }
+  return (visibility / f32(shadowSampleCount));
+}
+
+fn getCubeFace(v : vec3<f32>) -> i32 {
+  let vAbs = abs(v);
+  if (((vAbs.z >= vAbs.x) && (vAbs.z >= vAbs.y))) {
+    if ((v.z < 0.0)) {
+      return 5;
+    }
+    return 4;
+  }
+  if ((vAbs.y >= vAbs.x)) {
+    if ((v.y < 0.0)) {
+      return 3;
+    }
+    return 2;
+  }
+  if ((v.x < 0.0)) {
+    return 1;
+  }
+  return 0;
+}
+
+fn pointLightVisibility(lightIndex : u32, worldPos : vec3<f32>, pointToLight : vec3<f32>) -> f32 {
+  var shadowIndex = lightShadowTable.light[(lightIndex + 1u)];
+  if ((shadowIndex == -1)) {
+    return 1.0;
+  }
+  shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0)));
+  let viewport = shadow.properties[shadowIndex].viewport;
+  let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0));
+  let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w));
+  let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0)));
+  let clampRect = vec4(viewport.xy, (viewport.xy + viewport.zw));
+  var visibility = 0.0;
+  for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01)));
+  }
+  return (visibility / f32(shadowSampleCount));
+}
+
+struct VertexOutput {
+  @builtin(position)
+  position : vec4<f32>;
+  @location(0)
+  worldPos : vec3<f32>;
+  @location(1)
+  view : vec3<f32>;
+  @location(2)
+  texcoord : vec2<f32>;
+  @location(3)
+  texcoord2 : vec2<f32>;
+  @location(4)
+  color : vec4<f32>;
+  @location(5)
+  instanceColor : vec4<f32>;
+  @location(6)
+  normal : vec3<f32>;
+  @location(7)
+  tangent : vec3<f32>;
+  @location(8)
+  bitangent : vec3<f32>;
+}
+
+struct Material {
+  baseColorFactor : vec4<f32>;
+  emissiveFactor : vec3<f32>;
+  occlusionStrength : f32;
+  metallicRoughnessFactor : vec2<f32>;
+  alphaCutoff : f32;
+}
+
+@binding(8) @group(0) var<uniform> material : Material;
+
+@binding(9) @group(0) var baseColorTexture : texture_2d<f32>;
+
+@binding(10) @group(0) var baseColorSampler : sampler;
+
+@binding(11) @group(0) var normalTexture : texture_2d<f32>;
+
+@binding(12) @group(0) var normalSampler : sampler;
+
+@binding(13) @group(0) var metallicRoughnessTexture : texture_2d<f32>;
+
+@binding(14) @group(0) var metallicRoughnessSampler : sampler;
+
+@binding(15) @group(0) var occlusionTexture : texture_2d<f32>;
+
+@binding(16) @group(0) var occlusionSampler : sampler;
+
+@binding(17) @group(0) var emissiveTexture : texture_2d<f32>;
+
+@binding(18) @group(0) var emissiveSampler : sampler;
+
+struct SurfaceInfo {
+  baseColor : vec4<f32>;
+  albedo : vec3<f32>;
+  metallic : f32;
+  roughness : f32;
+  normal : vec3<f32>;
+  f0 : vec3<f32>;
+  ao : f32;
+  emissive : vec3<f32>;
+  v : vec3<f32>;
+}
+
+fn GetSurfaceInfo(input : VertexOutput) -> SurfaceInfo {
+  var surface : SurfaceInfo;
+  surface.v = normalize(input.view);
+  let tbn = mat3x3(input.tangent, input.bitangent, input.normal);
+  let normalMap = textureSample(normalTexture, normalSampler, input.texcoord).rgb;
+  surface.normal = normalize((tbn * ((2.0 * normalMap) - vec3(1.0))));
+  let baseColorMap = textureSample(baseColorTexture, baseColorSampler, input.texcoord);
+  surface.baseColor = ((input.color * material.baseColorFactor) * baseColorMap);
+  if ((surface.baseColor.a < material.alphaCutoff)) {
+    discard;
+  }
+  surface.albedo = surface.baseColor.rgb;
+  let metallicRoughnessMap = textureSample(metallicRoughnessTexture, metallicRoughnessSampler, input.texcoord);
+  surface.metallic = (material.metallicRoughnessFactor.x * metallicRoughnessMap.b);
+  surface.roughness = (material.metallicRoughnessFactor.y * metallicRoughnessMap.g);
+  let dielectricSpec = vec3(0.039999999);
+  surface.f0 = mix(dielectricSpec, surface.albedo, vec3(surface.metallic));
+  let occlusionMap = textureSample(occlusionTexture, occlusionSampler, input.texcoord);
+  surface.ao = (material.occlusionStrength * occlusionMap.r);
+  let emissiveMap = textureSample(emissiveTexture, emissiveSampler, input.texcoord);
+  surface.emissive = (material.emissiveFactor * emissiveMap.rgb);
+  if ((input.instanceColor.a == 0.0)) {
+    surface.albedo = (surface.albedo + input.instanceColor.rgb);
+  } else {
+    surface.albedo = (surface.albedo * input.instanceColor.rgb);
+  }
+  return surface;
+}
+
+let PI = 3.141592741;
+
+let LightType_Point = 0u;
+
+let LightType_Spot = 1u;
+
+let LightType_Directional = 2u;
+
+struct PuctualLight {
+  lightType : u32;
+  pointToLight : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+fn FresnelSchlick(cosTheta : f32, F0 : vec3<f32>) -> vec3<f32> {
+  return (F0 + ((vec3(1.0) - F0) * pow((1.0 - cosTheta), 5.0)));
+}
+
+fn DistributionGGX(N : vec3<f32>, H : vec3<f32>, roughness : f32) -> f32 {
+  let a = (roughness * roughness);
+  let a2 = (a * a);
+  let NdotH = max(dot(N, H), 0.0);
+  let NdotH2 = (NdotH * NdotH);
+  let num = a2;
+  let denom = ((NdotH2 * (a2 - 1.0)) + 1.0);
+  return (num / ((PI * denom) * denom));
+}
+
+fn GeometrySchlickGGX(NdotV : f32, roughness : f32) -> f32 {
+  let r = (roughness + 1.0);
+  let k = ((r * r) / 8.0);
+  let num = NdotV;
+  let denom = ((NdotV * (1.0 - k)) + k);
+  return (num / denom);
+}
+
+fn GeometrySmith(N : vec3<f32>, V : vec3<f32>, L : vec3<f32>, roughness : f32) -> f32 {
+  let NdotV = max(dot(N, V), 0.0);
+  let NdotL = max(dot(N, L), 0.0);
+  let ggx2 = GeometrySchlickGGX(NdotV, roughness);
+  let ggx1 = GeometrySchlickGGX(NdotL, roughness);
+  return (ggx1 * ggx2);
+}
+
+fn lightAttenuation(light : PuctualLight) -> f32 {
+  if ((light.lightType == LightType_Directional)) {
+    return 1.0;
+  }
+  let distance = length(light.pointToLight);
+  if ((light.range <= 0.0)) {
+    return (1.0 / pow(distance, 2.0));
+  }
+  return (clamp((1.0 - pow((distance / light.range), 4.0)), 0.0, 1.0) / pow(distance, 2.0));
+}
+
+fn lightRadiance(light : PuctualLight, surface : SurfaceInfo) -> vec3<f32> {
+  let L = normalize(light.pointToLight);
+  let H = normalize((surface.v + L));
+  let NDF = DistributionGGX(surface.normal, H, surface.roughness);
+  let G = GeometrySmith(surface.normal, surface.v, L, surface.roughness);
+  let F = FresnelSchlick(max(dot(H, surface.v), 0.0), surface.f0);
+  let kD = ((vec3(1.0) - F) * (1.0 - surface.metallic));
+  let NdotL = max(dot(surface.normal, L), 0.0);
+  let numerator = ((NDF * G) * F);
+  let denominator = max(((4.0 * max(dot(surface.normal, surface.v), 0.0)) * NdotL), 0.001);
+  let specular = (numerator / vec3(denominator));
+  let radiance = ((light.color * light.intensity) * lightAttenuation(light));
+  return (((((kD * surface.albedo) / vec3(PI)) + specular) * radiance) * NdotL);
+}
+
+@binding(19) @group(0) var ssaoTexture : texture_2d<f32>;
+
+struct FragmentOutput {
+  @location(0)
+  color : vec4<f32>;
+  @location(1)
+  emissive : vec4<f32>;
+}
+
+@stage(fragment)
+fn fragmentMain(input : VertexOutput) -> FragmentOutput {
+  let surface = GetSurfaceInfo(input);
+  var Lo = vec3(0.0, 0.0, 0.0);
+  if ((globalLights.dirIntensity > 0.0)) {
+    var light : PuctualLight;
+    light.lightType = LightType_Directional;
+    light.pointToLight = globalLights.dirDirection;
+    light.color = globalLights.dirColor;
+    light.intensity = globalLights.dirIntensity;
+    let lightVis = dirLightVisibility(input.worldPos);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  let clusterIndex = getClusterIndex(input.position);
+  let lightOffset = clusterLights.lights[clusterIndex].offset;
+  let lightCount = clusterLights.lights[clusterIndex].count;
+  for(var lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) {
+    let i = clusterLights.indices[(lightOffset + lightIndex)];
+    var light : PuctualLight;
+    light.lightType = LightType_Point;
+    light.pointToLight = (globalLights.lights[i].position.xyz - input.worldPos);
+    light.range = globalLights.lights[i].range;
+    light.color = globalLights.lights[i].color;
+    light.intensity = globalLights.lights[i].intensity;
+    let lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  let ssaoCoord = (input.position.xy / vec2<f32>(textureDimensions(ssaoTexture).xy));
+  let ssaoFactor = textureSample(ssaoTexture, defaultSampler, ssaoCoord).r;
+  let ambient = (((globalLights.ambient * surface.albedo) * surface.ao) * ssaoFactor);
+  let color = linearTosRGB(((Lo + ambient) + surface.emissive));
+  var out : FragmentOutput;
+  out.color = vec4(color, surface.baseColor.a);
+  out.emissive = vec4(surface.emissive, surface.baseColor.a);
+  return out;
+}
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl
new file mode 100644
index 0000000..fe68044
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.glsl
@@ -0,0 +1,424 @@
+SKIP: FAILED
+
+benchmark/skinned-shadowed-pbr-fragment.wgsl:51:13 warning: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+  lights : @stride(32) array<Light>;
+            ^^^^^^
+
+#version 310 es
+precision mediump float;
+
+const float GAMMA = 2.200000048f;
+
+vec3 linearTosRGB(vec3 linear) {
+  float INV_GAMMA = (1.0f / GAMMA);
+  return pow(linear, vec3(INV_GAMMA));
+}
+
+struct Camera {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+};
+
+layout (binding = 0) uniform Camera_1 {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+} camera;
+
+struct ClusterLights {
+  uint offset;
+  uint count;
+};
+struct ClusterLightGroup {
+  uint offset;
+  ClusterLights lights[27648];
+  uint indices[1769472];
+};
+
+layout (binding = 1) buffer ClusterLightGroup_1 {
+  uint offset;
+  ClusterLights lights[27648];
+  uint indices[1769472];
+} clusterLights;
+
+struct Light {
+  vec3 position;
+  float range;
+  vec3 color;
+  float intensity;
+};
+
+layout (binding = 2) buffer GlobalLights_1 {
+  vec3 ambient;
+  vec3 dirColor;
+  float dirIntensity;
+  vec3 dirDirection;
+  uint lightCount;
+  Light lights[];
+} globalLights;
+const uvec3 tileCount = uvec3(32u, 18u, 48u);
+
+float linearDepth(float depthSample) {
+  return ((camera.zFar * camera.zNear) / mad(depthSample, (camera.zNear - camera.zFar), camera.zFar));
+}
+
+uvec3 getTile(vec4 fragCoord) {
+  float sliceScale = (float(tileCount.z) / log2((camera.zFar / camera.zNear)));
+  float sliceBias = -(((float(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear))));
+  uint zTile = uint(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0f));
+  return uvec3(uint((fragCoord.x / (camera.outputSize.x / float(tileCount.x)))), uint((fragCoord.y / (camera.outputSize.y / float(tileCount.y)))), zTile);
+}
+
+uint getClusterIndex(vec4 fragCoord) {
+  uvec3 tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+
+uniform highp sampler2D shadowTexture;
+
+
+layout (binding = 6) buffer LightShadowTable_1 {
+  int light[];
+} lightShadowTable;
+vec2 shadowSampleOffsets[16] = vec2[16](vec2(-1.5f, -1.5f), vec2(-1.5f, -0.5f), vec2(-1.5f, 0.5f), vec2(-1.5f, 1.5f), vec2(-0.5f, -1.5f), vec2(-0.5f, -0.5f), vec2(-0.5f, 0.5f), vec2(-0.5f, 1.5f), vec2(0.5f, -1.5f), vec2(0.5f, -0.5f), vec2(0.5f, 0.5f), vec2(0.5f, 1.5f), vec2(1.5f, -1.5f), vec2(1.5f, -0.5f), vec2(1.5f, 0.5f), vec2(1.5f, 1.5f));
+const uint shadowSampleCount = 16u;
+
+struct ShadowProperties {
+  vec4 viewport;
+  mat4 viewProj;
+};
+
+layout (binding = 7) buffer LightShadows_1 {
+  ShadowProperties properties[];
+} shadow;
+
+float dirLightVisibility(vec3 worldPos) {
+  int shadowIndex = lightShadowTable.light[0u];
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  vec4 viewport = shadow.properties[shadowIndex].viewport;
+  vec4 lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0f));
+  vec3 shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5f, -0.5f)) + vec2(0.5f, 0.5f)), (lightPos.z / lightPos.w));
+  vec2 viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  vec2 texelSize = (1.0f / vec2(textureSize(shadowTexture, 0)));
+  vec4 clampRect = vec4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize));
+  float visibility = 0.0f;
+  {
+    for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+      visibility = (visibility + texture(shadowTexture, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003f)));
+    }
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+int getCubeFace(vec3 v) {
+  vec3 vAbs = abs(v);
+  bool tint_tmp = (vAbs.z >= vAbs.x);
+  if (tint_tmp) {
+    tint_tmp = (vAbs.z >= vAbs.y);
+  }
+  if ((tint_tmp)) {
+    if ((v.z < 0.0f)) {
+      return 5;
+    }
+    return 4;
+  }
+  if ((vAbs.y >= vAbs.x)) {
+    if ((v.y < 0.0f)) {
+      return 3;
+    }
+    return 2;
+  }
+  if ((v.x < 0.0f)) {
+    return 1;
+  }
+  return 0;
+}
+
+float pointLightVisibility(uint lightIndex, vec3 worldPos, vec3 pointToLight) {
+  int shadowIndex = lightShadowTable.light[(lightIndex + 1u)];
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0f)));
+  vec4 viewport = shadow.properties[shadowIndex].viewport;
+  vec4 lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0f));
+  vec3 shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5f, -0.5f)) + vec2(0.5f, 0.5f)), (lightPos.z / lightPos.w));
+  vec2 viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  vec2 texelSize = (1.0f / vec2(textureSize(shadowTexture, 0)));
+  vec4 clampRect = vec4(viewport.xy, (viewport.xy + viewport.zw));
+  float visibility = 0.0f;
+  {
+    for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+      visibility = (visibility + texture(shadowTexture, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01f)));
+    }
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+struct VertexOutput {
+  vec4 position;
+  vec3 worldPos;
+  vec3 view;
+  vec2 texcoord;
+  vec2 texcoord2;
+  vec4 color;
+  vec4 instanceColor;
+  vec3 normal;
+  vec3 tangent;
+  vec3 bitangent;
+};
+struct Material {
+  vec4 baseColorFactor;
+  vec3 emissiveFactor;
+  float occlusionStrength;
+  vec2 metallicRoughnessFactor;
+  float alphaCutoff;
+};
+
+layout (binding = 8) uniform Material_1 {
+  vec4 baseColorFactor;
+  vec3 emissiveFactor;
+  float occlusionStrength;
+  vec2 metallicRoughnessFactor;
+  float alphaCutoff;
+} material;
+uniform highp sampler2D baseColorTexture;
+
+uniform highp sampler2D normalTexture;
+
+uniform highp sampler2D metallicRoughnessTexture;
+
+uniform highp sampler2D occlusionTexture;
+
+uniform highp sampler2D emissiveTexture;
+
+
+struct SurfaceInfo {
+  vec4 baseColor;
+  vec3 albedo;
+  float metallic;
+  float roughness;
+  vec3 normal;
+  vec3 f0;
+  float ao;
+  vec3 emissive;
+  vec3 v;
+};
+
+SurfaceInfo GetSurfaceInfo(VertexOutput tint_symbol) {
+  SurfaceInfo surface = SurfaceInfo(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), 0.0f, 0.0f, vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), 0.0f, vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f));
+  surface.v = normalize(tint_symbol.view);
+  mat3 tbn = mat3(tint_symbol.tangent, tint_symbol.bitangent, tint_symbol.normal);
+  vec3 normalMap = texture(normalTexture, tint_symbol.texcoord).rgb;
+  surface.normal = normalize((tbn * ((2.0f * normalMap) - vec3(1.0f))));
+  vec4 baseColorMap = texture(baseColorTexture, tint_symbol.texcoord);
+  surface.baseColor = ((tint_symbol.color * material.baseColorFactor) * baseColorMap);
+  if ((surface.baseColor.a < material.alphaCutoff)) {
+    discard;
+  }
+  surface.albedo = surface.baseColor.rgb;
+  vec4 metallicRoughnessMap = texture(metallicRoughnessTexture, tint_symbol.texcoord);
+  surface.metallic = (material.metallicRoughnessFactor.x * metallicRoughnessMap.b);
+  surface.roughness = (material.metallicRoughnessFactor.y * metallicRoughnessMap.g);
+  vec3 dielectricSpec = vec3(0.039999999f);
+  surface.f0 = mix(dielectricSpec, surface.albedo, vec3(surface.metallic));
+  vec4 occlusionMap = texture(occlusionTexture, tint_symbol.texcoord);
+  surface.ao = (material.occlusionStrength * occlusionMap.r);
+  vec4 emissiveMap = texture(emissiveTexture, tint_symbol.texcoord);
+  surface.emissive = (material.emissiveFactor * emissiveMap.rgb);
+  if ((tint_symbol.instanceColor.a == 0.0f)) {
+    surface.albedo = (surface.albedo + tint_symbol.instanceColor.rgb);
+  } else {
+    surface.albedo = (surface.albedo * tint_symbol.instanceColor.rgb);
+  }
+  return surface;
+}
+
+const float PI = 3.141592741f;
+const uint LightType_Point = 0u;
+const uint LightType_Directional = 2u;
+
+struct PuctualLight {
+  uint lightType;
+  vec3 pointToLight;
+  float range;
+  vec3 color;
+  float intensity;
+};
+
+vec3 FresnelSchlick(float cosTheta, vec3 F0) {
+  return (F0 + ((vec3(1.0f) - F0) * pow((1.0f - cosTheta), 5.0f)));
+}
+
+float DistributionGGX(vec3 N, vec3 H, float roughness) {
+  float a_1 = (roughness * roughness);
+  float a2 = (a_1 * a_1);
+  float NdotH = max(dot(N, H), 0.0f);
+  float NdotH2 = (NdotH * NdotH);
+  float num = a2;
+  float denom = ((NdotH2 * (a2 - 1.0f)) + 1.0f);
+  return (num / ((PI * denom) * denom));
+}
+
+float GeometrySchlickGGX(float NdotV, float roughness) {
+  float r_1 = (roughness + 1.0f);
+  float k = ((r_1 * r_1) / 8.0f);
+  float num = NdotV;
+  float denom = ((NdotV * (1.0f - k)) + k);
+  return (num / denom);
+}
+
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
+  float NdotV = max(dot(N, V), 0.0f);
+  float NdotL = max(dot(N, L), 0.0f);
+  float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+  float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+  return (ggx1 * ggx2);
+}
+
+float lightAttenuation(PuctualLight light) {
+  if ((light.lightType == LightType_Directional)) {
+    return 1.0f;
+  }
+  float tint_symbol_1 = length(light.pointToLight);
+  if ((light.range <= 0.0f)) {
+    return (1.0f / pow(tint_symbol_1, 2.0f));
+  }
+  return (clamp((1.0f - pow((tint_symbol_1 / light.range), 4.0f)), 0.0f, 1.0f) / pow(tint_symbol_1, 2.0f));
+}
+
+vec3 lightRadiance(PuctualLight light, SurfaceInfo surface) {
+  vec3 L = normalize(light.pointToLight);
+  vec3 H = normalize((surface.v + L));
+  float NDF = DistributionGGX(surface.normal, H, surface.roughness);
+  float G = GeometrySmith(surface.normal, surface.v, L, surface.roughness);
+  vec3 F = FresnelSchlick(max(dot(H, surface.v), 0.0f), surface.f0);
+  vec3 kD = ((vec3(1.0f) - F) * (1.0f - surface.metallic));
+  float NdotL = max(dot(surface.normal, L), 0.0f);
+  vec3 numerator = ((NDF * G) * F);
+  float denominator = max(((4.0f * max(dot(surface.normal, surface.v), 0.0f)) * NdotL), 0.001f);
+  vec3 specular = (numerator / vec3(denominator));
+  vec3 radiance = ((light.color * light.intensity) * lightAttenuation(light));
+  return (((((kD * surface.albedo) / vec3(PI)) + specular) * radiance) * NdotL);
+}
+
+uniform highp sampler2D ssaoTexture;
+
+struct FragmentOutput {
+  vec4 color;
+  vec4 emissive;
+};
+struct tint_symbol_4 {
+  vec3 worldPos;
+  vec3 view;
+  vec2 texcoord;
+  vec2 texcoord2;
+  vec4 color;
+  vec4 instanceColor;
+  vec3 normal;
+  vec3 tangent;
+  vec3 bitangent;
+  vec4 position;
+};
+struct tint_symbol_5 {
+  vec4 color;
+  vec4 emissive;
+};
+
+FragmentOutput fragmentMain_inner(VertexOutput tint_symbol) {
+  SurfaceInfo surface = GetSurfaceInfo(tint_symbol);
+  vec3 Lo = vec3(0.0f, 0.0f, 0.0f);
+  if ((globalLights.dirIntensity > 0.0f)) {
+    PuctualLight light = PuctualLight(0u, vec3(0.0f, 0.0f, 0.0f), 0.0f, vec3(0.0f, 0.0f, 0.0f), 0.0f);
+    light.lightType = LightType_Directional;
+    light.pointToLight = globalLights.dirDirection;
+    light.color = globalLights.dirColor;
+    light.intensity = globalLights.dirIntensity;
+    float lightVis = dirLightVisibility(tint_symbol.worldPos);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  uint clusterIndex = getClusterIndex(tint_symbol.position);
+  uint lightOffset = clusterLights.lights[clusterIndex].offset;
+  uint lightCount = clusterLights.lights[clusterIndex].count;
+  {
+    for(uint lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) {
+      uint i = clusterLights.indices[(lightOffset + lightIndex)];
+      PuctualLight light = PuctualLight(0u, vec3(0.0f, 0.0f, 0.0f), 0.0f, vec3(0.0f, 0.0f, 0.0f), 0.0f);
+      light.lightType = LightType_Point;
+      light.pointToLight = (globalLights.lights[i].position.xyz - tint_symbol.worldPos);
+      light.range = globalLights.lights[i].range;
+      light.color = globalLights.lights[i].color;
+      light.intensity = globalLights.lights[i].intensity;
+      float lightVis = pointLightVisibility(i, tint_symbol.worldPos, light.pointToLight);
+      Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+    }
+  }
+  vec2 ssaoCoord = (tint_symbol.position.xy / vec2(textureSize(ssaoTexture, 0).xy));
+  float ssaoFactor = texture(ssaoTexture, ssaoCoord).r;
+  vec3 ambient = (((globalLights.ambient * surface.albedo) * surface.ao) * ssaoFactor);
+  vec3 color = linearTosRGB(((Lo + ambient) + surface.emissive));
+  FragmentOutput tint_symbol_2 = FragmentOutput(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  tint_symbol_2.color = vec4(color, surface.baseColor.a);
+  tint_symbol_2.emissive = vec4(surface.emissive, surface.baseColor.a);
+  return tint_symbol_2;
+}
+
+tint_symbol_5 fragmentMain(tint_symbol_4 tint_symbol_3) {
+  VertexOutput tint_symbol_6 = VertexOutput(tint_symbol_3.position, tint_symbol_3.worldPos, tint_symbol_3.view, tint_symbol_3.texcoord, tint_symbol_3.texcoord2, tint_symbol_3.color, tint_symbol_3.instanceColor, tint_symbol_3.normal, tint_symbol_3.tangent, tint_symbol_3.bitangent);
+  FragmentOutput inner_result = fragmentMain_inner(tint_symbol_6);
+  tint_symbol_5 wrapper_result = tint_symbol_5(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.color = inner_result.color;
+  wrapper_result.emissive = inner_result.emissive;
+  return wrapper_result;
+}
+in vec3 worldPos;
+in vec3 view;
+in vec2 texcoord;
+in vec2 texcoord2;
+in vec4 color;
+in vec4 instanceColor;
+in vec3 normal;
+in vec3 tangent;
+in vec3 bitangent;
+out vec4 color;
+out vec4 emissive;
+void main() {
+  tint_symbol_4 inputs;
+  inputs.worldPos = worldPos;
+  inputs.view = view;
+  inputs.texcoord = texcoord;
+  inputs.texcoord2 = texcoord2;
+  inputs.color = color;
+  inputs.instanceColor = instanceColor;
+  inputs.normal = normal;
+  inputs.tangent = tangent;
+  inputs.bitangent = bitangent;
+  inputs.position = gl_FragCoord;
+  tint_symbol_5 outputs;
+  outputs = fragmentMain(inputs);
+  color = outputs.color;
+  emissive = outputs.emissive;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:67: 'mad' : no matching overloaded function found 
+ERROR: 0:67: '' : compilation terminated 
+ERROR: 2 compilation errors.  No code generated.
+
+
+
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.hlsl b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.hlsl
new file mode 100644
index 0000000..5385888
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.hlsl
@@ -0,0 +1,335 @@
+benchmark/skinned-shadowed-pbr-fragment.wgsl:51:13 warning: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+  lights : @stride(32) array<Light>;
+            ^^^^^^
+
+static const float GAMMA = 2.200000048f;
+
+float3 linearTosRGB(float3 tint_symbol) {
+  const float INV_GAMMA = (1.0f / GAMMA);
+  return pow(tint_symbol, float3((INV_GAMMA).xxx));
+}
+
+float3 sRGBToLinear(float3 srgb) {
+  return pow(srgb, float3((GAMMA).xxx));
+}
+
+cbuffer cbuffer_camera : register(b0, space0) {
+  uint4 camera[14];
+};
+
+ByteAddressBuffer clusterLights : register(t1, space0);
+
+ByteAddressBuffer globalLights : register(t2, space0);
+static const uint3 tileCount = uint3(32u, 18u, 48u);
+
+float linearDepth(float depthSample) {
+  return ((asfloat(camera[13].w) * asfloat(camera[13].z)) / mad(depthSample, (asfloat(camera[13].z) - asfloat(camera[13].w)), asfloat(camera[13].w)));
+}
+
+uint3 getTile(float4 fragCoord) {
+  const float sliceScale = (float(tileCount.z) / log2((asfloat(camera[13].w) / asfloat(camera[13].z))));
+  const float sliceBias = -(((float(tileCount.z) * log2(asfloat(camera[13].z))) / log2((asfloat(camera[13].w) / asfloat(camera[13].z)))));
+  const uint zTile = uint(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0f));
+  return uint3(uint((fragCoord.x / (asfloat(camera[13].x) / float(tileCount.x)))), uint((fragCoord.y / (asfloat(camera[13].y) / float(tileCount.y)))), zTile);
+}
+
+uint getClusterIndex(float4 fragCoord) {
+  const uint3 tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+SamplerState defaultSampler : register(s3, space0);
+Texture2D shadowTexture : register(t4, space0);
+SamplerComparisonState shadowSampler : register(s5, space0);
+
+ByteAddressBuffer lightShadowTable : register(t6, space0);
+static float2 shadowSampleOffsets[16] = {float2(-1.5f, -1.5f), float2(-1.5f, -0.5f), float2(-1.5f, 0.5f), float2(-1.5f, 1.5f), float2(-0.5f, -1.5f), float2(-0.5f, -0.5f), float2(-0.5f, 0.5f), float2(-0.5f, 1.5f), float2(0.5f, -1.5f), float2(0.5f, -0.5f), float2(0.5f, 0.5f), float2(0.5f, 1.5f), float2(1.5f, -1.5f), float2(1.5f, -0.5f), float2(1.5f, 0.5f), float2(1.5f, 1.5f)};
+static const uint shadowSampleCount = 16u;
+
+ByteAddressBuffer shadow : register(t7, space0);
+
+float4x4 tint_symbol_8(ByteAddressBuffer buffer, uint offset) {
+  return float4x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))), asfloat(buffer.Load4((offset + 48u))));
+}
+
+float dirLightVisibility(float3 worldPos) {
+  const int shadowIndex = asint(lightShadowTable.Load(0u));
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  const float4 viewport = asfloat(shadow.Load4((80u * uint(shadowIndex))));
+  const float4 lightPos = mul(float4(worldPos, 1.0f), tint_symbol_8(shadow, ((80u * uint(shadowIndex)) + 16u)));
+  const float3 shadowPos = float3((((lightPos.xy / lightPos.w) * float2(0.5f, -0.5f)) + float2(0.5f, 0.5f)), (lightPos.z / lightPos.w));
+  const float2 viewportPos = float2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  int3 tint_tmp;
+  shadowTexture.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z);
+  const float2 texelSize = (1.0f / float2(tint_tmp.xy));
+  const float4 clampRect = float4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize));
+  float visibility = 0.0f;
+  {
+    [loop] for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+      visibility = (visibility + shadowTexture.SampleCmpLevelZero(shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003f)));
+    }
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+int getCubeFace(float3 v) {
+  const float3 vAbs = abs(v);
+  bool tint_tmp_1 = (vAbs.z >= vAbs.x);
+  if (tint_tmp_1) {
+    tint_tmp_1 = (vAbs.z >= vAbs.y);
+  }
+  if ((tint_tmp_1)) {
+    if ((v.z < 0.0f)) {
+      return 5;
+    }
+    return 4;
+  }
+  if ((vAbs.y >= vAbs.x)) {
+    if ((v.y < 0.0f)) {
+      return 3;
+    }
+    return 2;
+  }
+  if ((v.x < 0.0f)) {
+    return 1;
+  }
+  return 0;
+}
+
+float pointLightVisibility(uint lightIndex, float3 worldPos, float3 pointToLight) {
+  int shadowIndex = asint(lightShadowTable.Load((4u * (lightIndex + 1u))));
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0f)));
+  const float4 viewport = asfloat(shadow.Load4((80u * uint(shadowIndex))));
+  const float4 lightPos = mul(float4(worldPos, 1.0f), tint_symbol_8(shadow, ((80u * uint(shadowIndex)) + 16u)));
+  const float3 shadowPos = float3((((lightPos.xy / lightPos.w) * float2(0.5f, -0.5f)) + float2(0.5f, 0.5f)), (lightPos.z / lightPos.w));
+  const float2 viewportPos = float2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  int3 tint_tmp_2;
+  shadowTexture.GetDimensions(0, tint_tmp_2.x, tint_tmp_2.y, tint_tmp_2.z);
+  const float2 texelSize = (1.0f / float2(tint_tmp_2.xy));
+  const float4 clampRect = float4(viewport.xy, (viewport.xy + viewport.zw));
+  float visibility = 0.0f;
+  {
+    [loop] for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+      visibility = (visibility + shadowTexture.SampleCmpLevelZero(shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01f)));
+    }
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+struct VertexOutput {
+  float4 position;
+  float3 worldPos;
+  float3 view;
+  float2 texcoord;
+  float2 texcoord2;
+  float4 color;
+  float4 instanceColor;
+  float3 normal;
+  float3 tangent;
+  float3 bitangent;
+};
+
+cbuffer cbuffer_material : register(b8, space0) {
+  uint4 material[3];
+};
+Texture2D<float4> baseColorTexture : register(t9, space0);
+SamplerState baseColorSampler : register(s10, space0);
+Texture2D<float4> normalTexture : register(t11, space0);
+SamplerState normalSampler : register(s12, space0);
+Texture2D<float4> metallicRoughnessTexture : register(t13, space0);
+SamplerState metallicRoughnessSampler : register(s14, space0);
+Texture2D<float4> occlusionTexture : register(t15, space0);
+SamplerState occlusionSampler : register(s16, space0);
+Texture2D<float4> emissiveTexture : register(t17, space0);
+SamplerState emissiveSampler : register(s18, space0);
+
+struct SurfaceInfo {
+  float4 baseColor;
+  float3 albedo;
+  float metallic;
+  float roughness;
+  float3 normal;
+  float3 f0;
+  float ao;
+  float3 emissive;
+  float3 v;
+};
+
+SurfaceInfo GetSurfaceInfo(VertexOutput input) {
+  if (true) {
+    SurfaceInfo surface = (SurfaceInfo)0;
+    surface.v = normalize(input.view);
+    const float3x3 tbn = float3x3(input.tangent, input.bitangent, input.normal);
+    const float3 normalMap = normalTexture.Sample(normalSampler, input.texcoord).rgb;
+    surface.normal = normalize(mul(((2.0f * normalMap) - float3((1.0f).xxx)), tbn));
+    const float4 baseColorMap = baseColorTexture.Sample(baseColorSampler, input.texcoord);
+    surface.baseColor = ((input.color * asfloat(material[0])) * baseColorMap);
+    if ((surface.baseColor.a < asfloat(material[2].z))) {
+      discard;
+    }
+    surface.albedo = surface.baseColor.rgb;
+    const float4 metallicRoughnessMap = metallicRoughnessTexture.Sample(metallicRoughnessSampler, input.texcoord);
+    surface.metallic = (asfloat(material[2].x) * metallicRoughnessMap.b);
+    surface.roughness = (asfloat(material[2].y) * metallicRoughnessMap.g);
+    const float3 dielectricSpec = float3((0.039999999f).xxx);
+    surface.f0 = lerp(dielectricSpec, surface.albedo, float3((surface.metallic).xxx));
+    const float4 occlusionMap = occlusionTexture.Sample(occlusionSampler, input.texcoord);
+    surface.ao = (asfloat(material[1].w) * occlusionMap.r);
+    const float4 emissiveMap = emissiveTexture.Sample(emissiveSampler, input.texcoord);
+    surface.emissive = (asfloat(material[1].xyz) * emissiveMap.rgb);
+    if ((input.instanceColor.a == 0.0f)) {
+      surface.albedo = (surface.albedo + input.instanceColor.rgb);
+    } else {
+      surface.albedo = (surface.albedo * input.instanceColor.rgb);
+    }
+    return surface;
+  }
+  SurfaceInfo unused;
+  return unused;
+}
+
+static const float PI = 3.141592741f;
+static const uint LightType_Point = 0u;
+static const uint LightType_Spot = 1u;
+static const uint LightType_Directional = 2u;
+
+struct PuctualLight {
+  uint lightType;
+  float3 pointToLight;
+  float range;
+  float3 color;
+  float intensity;
+};
+
+float3 FresnelSchlick(float cosTheta, float3 F0) {
+  return (F0 + ((float3((1.0f).xxx) - F0) * pow((1.0f - cosTheta), 5.0f)));
+}
+
+float DistributionGGX(float3 N, float3 H, float roughness) {
+  const float a_1 = (roughness * roughness);
+  const float a2 = (a_1 * a_1);
+  const float NdotH = max(dot(N, H), 0.0f);
+  const float NdotH2 = (NdotH * NdotH);
+  const float num = a2;
+  const float denom = ((NdotH2 * (a2 - 1.0f)) + 1.0f);
+  return (num / ((PI * denom) * denom));
+}
+
+float GeometrySchlickGGX(float NdotV, float roughness) {
+  const float r_1 = (roughness + 1.0f);
+  const float k = ((r_1 * r_1) / 8.0f);
+  const float num = NdotV;
+  const float denom = ((NdotV * (1.0f - k)) + k);
+  return (num / denom);
+}
+
+float GeometrySmith(float3 N, float3 V, float3 L, float roughness) {
+  const float NdotV = max(dot(N, V), 0.0f);
+  const float NdotL = max(dot(N, L), 0.0f);
+  const float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+  const float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+  return (ggx1 * ggx2);
+}
+
+float lightAttenuation(PuctualLight light) {
+  if ((light.lightType == LightType_Directional)) {
+    return 1.0f;
+  }
+  const float distance = length(light.pointToLight);
+  if ((light.range <= 0.0f)) {
+    return (1.0f / pow(distance, 2.0f));
+  }
+  return (clamp((1.0f - pow((distance / light.range), 4.0f)), 0.0f, 1.0f) / pow(distance, 2.0f));
+}
+
+float3 lightRadiance(PuctualLight light, SurfaceInfo surface) {
+  const float3 L = normalize(light.pointToLight);
+  const float3 H = normalize((surface.v + L));
+  const float NDF = DistributionGGX(surface.normal, H, surface.roughness);
+  const float G = GeometrySmith(surface.normal, surface.v, L, surface.roughness);
+  const float3 F = FresnelSchlick(max(dot(H, surface.v), 0.0f), surface.f0);
+  const float3 kD = ((float3((1.0f).xxx) - F) * (1.0f - surface.metallic));
+  const float NdotL = max(dot(surface.normal, L), 0.0f);
+  const float3 numerator = ((NDF * G) * F);
+  const float denominator = max(((4.0f * max(dot(surface.normal, surface.v), 0.0f)) * NdotL), 0.001f);
+  const float3 specular = (numerator / float3((denominator).xxx));
+  const float3 radiance = ((light.color * light.intensity) * lightAttenuation(light));
+  return (((((kD * surface.albedo) / float3((PI).xxx)) + specular) * radiance) * NdotL);
+}
+
+Texture2D<float4> ssaoTexture : register(t19, space0);
+
+struct FragmentOutput {
+  float4 color;
+  float4 emissive;
+};
+struct tint_symbol_3 {
+  float3 worldPos : TEXCOORD0;
+  float3 view : TEXCOORD1;
+  float2 texcoord : TEXCOORD2;
+  float2 texcoord2 : TEXCOORD3;
+  float4 color : TEXCOORD4;
+  float4 instanceColor : TEXCOORD5;
+  float3 normal : TEXCOORD6;
+  float3 tangent : TEXCOORD7;
+  float3 bitangent : TEXCOORD8;
+  float4 position : SV_Position;
+};
+struct tint_symbol_4 {
+  float4 color : SV_Target0;
+  float4 emissive : SV_Target1;
+};
+
+FragmentOutput fragmentMain_inner(VertexOutput input) {
+  const SurfaceInfo surface = GetSurfaceInfo(input);
+  float3 Lo = float3(0.0f, 0.0f, 0.0f);
+  if ((asfloat(globalLights.Load(28u)) > 0.0f)) {
+    PuctualLight light = (PuctualLight)0;
+    light.lightType = LightType_Directional;
+    light.pointToLight = asfloat(globalLights.Load3(32u));
+    light.color = asfloat(globalLights.Load3(16u));
+    light.intensity = asfloat(globalLights.Load(28u));
+    const float lightVis = dirLightVisibility(input.worldPos);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  const uint clusterIndex = getClusterIndex(input.position);
+  const uint lightOffset = clusterLights.Load((4u + (8u * clusterIndex)));
+  const uint lightCount = clusterLights.Load(((4u + (8u * clusterIndex)) + 4u));
+  {
+    [loop] for(uint lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) {
+      const uint i = clusterLights.Load((221188u + (4u * (lightOffset + lightIndex))));
+      PuctualLight light = (PuctualLight)0;
+      light.lightType = LightType_Point;
+      light.pointToLight = (asfloat(globalLights.Load3((48u + (32u * i)))).xyz - input.worldPos);
+      light.range = asfloat(globalLights.Load(((48u + (32u * i)) + 12u)));
+      light.color = asfloat(globalLights.Load3(((48u + (32u * i)) + 16u)));
+      light.intensity = asfloat(globalLights.Load(((48u + (32u * i)) + 28u)));
+      const float lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight);
+      Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+    }
+  }
+  int2 tint_tmp_3;
+  ssaoTexture.GetDimensions(tint_tmp_3.x, tint_tmp_3.y);
+  const float2 ssaoCoord = (input.position.xy / float2(tint_tmp_3.xy));
+  const float ssaoFactor = ssaoTexture.Sample(defaultSampler, ssaoCoord).r;
+  const float3 ambient = (((asfloat(globalLights.Load3(0u)) * surface.albedo) * surface.ao) * ssaoFactor);
+  const float3 color = linearTosRGB(((Lo + ambient) + surface.emissive));
+  FragmentOutput tint_symbol_1 = (FragmentOutput)0;
+  tint_symbol_1.color = float4(color, surface.baseColor.a);
+  tint_symbol_1.emissive = float4(surface.emissive, surface.baseColor.a);
+  return tint_symbol_1;
+}
+
+tint_symbol_4 fragmentMain(tint_symbol_3 tint_symbol_2) {
+  const VertexOutput tint_symbol_15 = {tint_symbol_2.position, tint_symbol_2.worldPos, tint_symbol_2.view, tint_symbol_2.texcoord, tint_symbol_2.texcoord2, tint_symbol_2.color, tint_symbol_2.instanceColor, tint_symbol_2.normal, tint_symbol_2.tangent, tint_symbol_2.bitangent};
+  const FragmentOutput inner_result = fragmentMain_inner(tint_symbol_15);
+  tint_symbol_4 wrapper_result = (tint_symbol_4)0;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.emissive = inner_result.emissive;
+  return wrapper_result;
+}
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.msl b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.msl
new file mode 100644
index 0000000..80f6efc
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.msl
@@ -0,0 +1,349 @@
+benchmark/skinned-shadowed-pbr-fragment.wgsl:51:13 warning: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+  lights : @stride(32) array<Light>;
+            ^^^^^^
+
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+
+struct Camera {
+  /* 0x0000 */ float4x4 projection;
+  /* 0x0040 */ float4x4 inverseProjection;
+  /* 0x0080 */ float4x4 view;
+  /* 0x00c0 */ packed_float3 position;
+  /* 0x00cc */ float time;
+  /* 0x00d0 */ float2 outputSize;
+  /* 0x00d8 */ float zNear;
+  /* 0x00dc */ float zFar;
+};
+struct ClusterLights {
+  /* 0x0000 */ uint offset;
+  /* 0x0004 */ uint count;
+};
+struct tint_array_wrapper {
+  /* 0x0000 */ ClusterLights arr[27648];
+};
+struct tint_array_wrapper_1 {
+  /* 0x0000 */ uint arr[1769472];
+};
+struct ClusterLightGroup {
+  /* 0x0000 */ uint offset;
+  /* 0x0004 */ tint_array_wrapper lights;
+  /* 0x36004 */ tint_array_wrapper_1 indices;
+};
+struct Light {
+  /* 0x0000 */ packed_float3 position;
+  /* 0x000c */ float range;
+  /* 0x0010 */ packed_float3 color;
+  /* 0x001c */ float intensity;
+};
+struct GlobalLights {
+  /* 0x0000 */ packed_float3 ambient;
+  /* 0x000c */ int8_t tint_pad[4];
+  /* 0x0010 */ packed_float3 dirColor;
+  /* 0x001c */ float dirIntensity;
+  /* 0x0020 */ packed_float3 dirDirection;
+  /* 0x002c */ uint lightCount;
+  /* 0x0030 */ Light lights[1];
+};
+struct LightShadowTable {
+  /* 0x0000 */ int light[1];
+};
+struct tint_array_wrapper_2 {
+  float2 arr[16];
+};
+struct ShadowProperties {
+  /* 0x0000 */ float4 viewport;
+  /* 0x0010 */ float4x4 viewProj;
+};
+struct LightShadows {
+  /* 0x0000 */ ShadowProperties properties[1];
+};
+struct VertexOutput {
+  float4 position;
+  float3 worldPos;
+  float3 view;
+  float2 texcoord;
+  float2 texcoord2;
+  float4 color;
+  float4 instanceColor;
+  float3 normal;
+  float3 tangent;
+  float3 bitangent;
+};
+struct Material {
+  /* 0x0000 */ float4 baseColorFactor;
+  /* 0x0010 */ packed_float3 emissiveFactor;
+  /* 0x001c */ float occlusionStrength;
+  /* 0x0020 */ float2 metallicRoughnessFactor;
+  /* 0x0028 */ float alphaCutoff;
+  /* 0x002c */ int8_t tint_pad_1[4];
+};
+struct SurfaceInfo {
+  float4 baseColor;
+  float3 albedo;
+  float metallic;
+  float roughness;
+  float3 normal;
+  float3 f0;
+  float ao;
+  float3 emissive;
+  float3 v;
+};
+struct PuctualLight {
+  uint lightType;
+  float3 pointToLight;
+  float range;
+  float3 color;
+  float intensity;
+};
+struct FragmentOutput {
+  float4 color;
+  float4 emissive;
+};
+struct tint_symbol_1 {
+  float3 worldPos [[user(locn0)]];
+  float3 view [[user(locn1)]];
+  float2 texcoord [[user(locn2)]];
+  float2 texcoord2 [[user(locn3)]];
+  float4 color [[user(locn4)]];
+  float4 instanceColor [[user(locn5)]];
+  float3 normal [[user(locn6)]];
+  float3 tangent [[user(locn7)]];
+  float3 bitangent [[user(locn8)]];
+};
+struct tint_symbol_2 {
+  float4 color [[color(0)]];
+  float4 emissive [[color(1)]];
+};
+
+constant float GAMMA = 2.200000048f;
+constant uint3 tileCount = uint3(32u, 18u, 48u);
+constant uint shadowSampleCount = 16u;
+constant float PI = 3.141592741f;
+constant uint LightType_Point = 0u;
+constant uint LightType_Spot = 1u;
+constant uint LightType_Directional = 2u;
+float3 linearTosRGB(float3 linear) {
+  float const INV_GAMMA = (1.0f / GAMMA);
+  return pow(linear, float3(INV_GAMMA));
+}
+
+float3 sRGBToLinear(float3 srgb) {
+  return pow(srgb, float3(GAMMA));
+}
+
+float linearDepth(float depthSample, const constant Camera* const tint_symbol_4) {
+  return (((*(tint_symbol_4)).zFar * (*(tint_symbol_4)).zNear) / fma(depthSample, ((*(tint_symbol_4)).zNear - (*(tint_symbol_4)).zFar), (*(tint_symbol_4)).zFar));
+}
+
+uint3 getTile(float4 fragCoord, const constant Camera* const tint_symbol_5) {
+  float const sliceScale = (float(tileCount[2]) / log2(((*(tint_symbol_5)).zFar / (*(tint_symbol_5)).zNear)));
+  float const sliceBias = -(((float(tileCount[2]) * log2((*(tint_symbol_5)).zNear)) / log2(((*(tint_symbol_5)).zFar / (*(tint_symbol_5)).zNear))));
+  uint const zTile = uint(fmax(((log2(linearDepth(fragCoord[2], tint_symbol_5)) * sliceScale) + sliceBias), 0.0f));
+  return uint3(uint((fragCoord[0] / ((*(tint_symbol_5)).outputSize[0] / float(tileCount[0])))), uint((fragCoord[1] / ((*(tint_symbol_5)).outputSize[1] / float(tileCount[1])))), zTile);
+}
+
+uint getClusterIndex(float4 fragCoord, const constant Camera* const tint_symbol_6) {
+  uint3 const tile = getTile(fragCoord, tint_symbol_6);
+  return ((tile[0] + (tile[1] * tileCount[0])) + ((tile[2] * tileCount[0]) * tileCount[1]));
+}
+
+float dirLightVisibility(float3 worldPos, const device LightShadowTable* const tint_symbol_7, const device LightShadows* const tint_symbol_8, depth2d<float, access::sample> tint_symbol_9, sampler tint_symbol_10, thread tint_array_wrapper_2* const tint_symbol_11) {
+  int const shadowIndex = (*(tint_symbol_7)).light[0u];
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  float4 const viewport = (*(tint_symbol_8)).properties[shadowIndex].viewport;
+  float4 const lightPos = ((*(tint_symbol_8)).properties[shadowIndex].viewProj * float4(worldPos, 1.0f));
+  float3 const shadowPos = float3((((float4(lightPos).xy / lightPos[3]) * float2(0.5f, -0.5f)) + float2(0.5f, 0.5f)), (lightPos[2] / lightPos[3]));
+  float2 const viewportPos = float2((float4(viewport).xy + (float3(shadowPos).xy * float4(viewport).zw)));
+  float2 const texelSize = (1.0f / float2(int2(tint_symbol_9.get_width(0), tint_symbol_9.get_height(0))));
+  float4 const clampRect = float4((float4(viewport).xy - texelSize), ((float4(viewport).xy + float4(viewport).zw) + texelSize));
+  float visibility = 0.0f;
+  for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + tint_symbol_9.sample_compare(tint_symbol_10, clamp((viewportPos + ((*(tint_symbol_11)).arr[i] * texelSize)), float4(clampRect).xy, float4(clampRect).zw), (shadowPos[2] - 0.003f), level(0)));
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+int getCubeFace(float3 v) {
+  float3 const vAbs = fabs(v);
+  if (((vAbs[2] >= vAbs[0]) && (vAbs[2] >= vAbs[1]))) {
+    if ((v[2] < 0.0f)) {
+      return 5;
+    }
+    return 4;
+  }
+  if ((vAbs[1] >= vAbs[0])) {
+    if ((v[1] < 0.0f)) {
+      return 3;
+    }
+    return 2;
+  }
+  if ((v[0] < 0.0f)) {
+    return 1;
+  }
+  return 0;
+}
+
+float pointLightVisibility(uint lightIndex, float3 worldPos, float3 pointToLight, const device LightShadowTable* const tint_symbol_12, const device LightShadows* const tint_symbol_13, depth2d<float, access::sample> tint_symbol_14, sampler tint_symbol_15, thread tint_array_wrapper_2* const tint_symbol_16) {
+  int shadowIndex = (*(tint_symbol_12)).light[(lightIndex + 1u)];
+  if ((shadowIndex == -1)) {
+    return 1.0f;
+  }
+  shadowIndex = as_type<int>((as_type<uint>(shadowIndex) + as_type<uint>(getCubeFace((pointToLight * -1.0f)))));
+  float4 const viewport = (*(tint_symbol_13)).properties[shadowIndex].viewport;
+  float4 const lightPos = ((*(tint_symbol_13)).properties[shadowIndex].viewProj * float4(worldPos, 1.0f));
+  float3 const shadowPos = float3((((float4(lightPos).xy / lightPos[3]) * float2(0.5f, -0.5f)) + float2(0.5f, 0.5f)), (lightPos[2] / lightPos[3]));
+  float2 const viewportPos = float2((float4(viewport).xy + (float3(shadowPos).xy * float4(viewport).zw)));
+  float2 const texelSize = (1.0f / float2(int2(tint_symbol_14.get_width(0), tint_symbol_14.get_height(0))));
+  float4 const clampRect = float4(float4(viewport).xy, (float4(viewport).xy + float4(viewport).zw));
+  float visibility = 0.0f;
+  for(uint i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + tint_symbol_14.sample_compare(tint_symbol_15, clamp((viewportPos + ((*(tint_symbol_16)).arr[i] * texelSize)), float4(clampRect).xy, float4(clampRect).zw), (shadowPos[2] - 0.01f), level(0)));
+  }
+  return (visibility / float(shadowSampleCount));
+}
+
+SurfaceInfo GetSurfaceInfo(VertexOutput input, texture2d<float, access::sample> tint_symbol_17, sampler tint_symbol_18, texture2d<float, access::sample> tint_symbol_19, sampler tint_symbol_20, const constant Material* const tint_symbol_21, texture2d<float, access::sample> tint_symbol_22, sampler tint_symbol_23, texture2d<float, access::sample> tint_symbol_24, sampler tint_symbol_25, texture2d<float, access::sample> tint_symbol_26, sampler tint_symbol_27) {
+  SurfaceInfo surface = {};
+  surface.v = normalize(input.view);
+  float3x3 const tbn = float3x3(input.tangent, input.bitangent, input.normal);
+  float3 const normalMap = float4(tint_symbol_17.sample(tint_symbol_18, input.texcoord)).rgb;
+  surface.normal = normalize((tbn * ((2.0f * normalMap) - float3(1.0f))));
+  float4 const baseColorMap = tint_symbol_19.sample(tint_symbol_20, input.texcoord);
+  surface.baseColor = ((input.color * (*(tint_symbol_21)).baseColorFactor) * baseColorMap);
+  if ((surface.baseColor[3] < (*(tint_symbol_21)).alphaCutoff)) {
+    discard_fragment();
+  }
+  surface.albedo = float4(surface.baseColor).rgb;
+  float4 const metallicRoughnessMap = tint_symbol_22.sample(tint_symbol_23, input.texcoord);
+  surface.metallic = ((*(tint_symbol_21)).metallicRoughnessFactor[0] * metallicRoughnessMap[2]);
+  surface.roughness = ((*(tint_symbol_21)).metallicRoughnessFactor[1] * metallicRoughnessMap[1]);
+  float3 const dielectricSpec = float3(0.039999999f);
+  surface.f0 = mix(dielectricSpec, surface.albedo, float3(surface.metallic));
+  float4 const occlusionMap = tint_symbol_24.sample(tint_symbol_25, input.texcoord);
+  surface.ao = ((*(tint_symbol_21)).occlusionStrength * occlusionMap[0]);
+  float4 const emissiveMap = tint_symbol_26.sample(tint_symbol_27, input.texcoord);
+  surface.emissive = ((*(tint_symbol_21)).emissiveFactor * float4(emissiveMap).rgb);
+  if ((input.instanceColor[3] == 0.0f)) {
+    surface.albedo = (surface.albedo + float4(input.instanceColor).rgb);
+  } else {
+    surface.albedo = (surface.albedo * float4(input.instanceColor).rgb);
+  }
+  return surface;
+}
+
+float3 FresnelSchlick(float cosTheta, float3 F0) {
+  return (F0 + ((float3(1.0f) - F0) * pow((1.0f - cosTheta), 5.0f)));
+}
+
+float DistributionGGX(float3 N, float3 H, float roughness) {
+  float const a_1 = (roughness * roughness);
+  float const a2 = (a_1 * a_1);
+  float const NdotH = fmax(dot(N, H), 0.0f);
+  float const NdotH2 = (NdotH * NdotH);
+  float const num = a2;
+  float const denom = ((NdotH2 * (a2 - 1.0f)) + 1.0f);
+  return (num / ((PI * denom) * denom));
+}
+
+float GeometrySchlickGGX(float NdotV, float roughness) {
+  float const r_1 = (roughness + 1.0f);
+  float const k = ((r_1 * r_1) / 8.0f);
+  float const num = NdotV;
+  float const denom = ((NdotV * (1.0f - k)) + k);
+  return (num / denom);
+}
+
+float GeometrySmith(float3 N, float3 V, float3 L, float roughness) {
+  float const NdotV = fmax(dot(N, V), 0.0f);
+  float const NdotL = fmax(dot(N, L), 0.0f);
+  float const ggx2 = GeometrySchlickGGX(NdotV, roughness);
+  float const ggx1 = GeometrySchlickGGX(NdotL, roughness);
+  return (ggx1 * ggx2);
+}
+
+float lightAttenuation(PuctualLight light) {
+  if ((light.lightType == LightType_Directional)) {
+    return 1.0f;
+  }
+  float const distance = length(light.pointToLight);
+  if ((light.range <= 0.0f)) {
+    return (1.0f / pow(distance, 2.0f));
+  }
+  return (clamp((1.0f - pow((distance / light.range), 4.0f)), 0.0f, 1.0f) / pow(distance, 2.0f));
+}
+
+float3 lightRadiance(PuctualLight light, SurfaceInfo surface) {
+  float3 const L = normalize(light.pointToLight);
+  float3 const H = normalize((surface.v + L));
+  float const NDF = DistributionGGX(surface.normal, H, surface.roughness);
+  float const G = GeometrySmith(surface.normal, surface.v, L, surface.roughness);
+  float3 const F = FresnelSchlick(fmax(dot(H, surface.v), 0.0f), surface.f0);
+  float3 const kD = ((float3(1.0f) - F) * (1.0f - surface.metallic));
+  float const NdotL = fmax(dot(surface.normal, L), 0.0f);
+  float3 const numerator = ((NDF * G) * F);
+  float const denominator = fmax(((4.0f * fmax(dot(surface.normal, surface.v), 0.0f)) * NdotL), 0.001f);
+  float3 const specular = (numerator / float3(denominator));
+  float3 const radiance = ((light.color * light.intensity) * lightAttenuation(light));
+  return (((((kD * surface.albedo) / float3(PI)) + specular) * radiance) * NdotL);
+}
+
+FragmentOutput fragmentMain_inner(VertexOutput input, texture2d<float, access::sample> tint_symbol_28, sampler tint_symbol_29, texture2d<float, access::sample> tint_symbol_30, sampler tint_symbol_31, const constant Material* const tint_symbol_32, texture2d<float, access::sample> tint_symbol_33, sampler tint_symbol_34, texture2d<float, access::sample> tint_symbol_35, sampler tint_symbol_36, texture2d<float, access::sample> tint_symbol_37, sampler tint_symbol_38, const device GlobalLights* const tint_symbol_39, const device LightShadowTable* const tint_symbol_40, const device LightShadows* const tint_symbol_41, depth2d<float, access::sample> tint_symbol_42, sampler tint_symbol_43, thread tint_array_wrapper_2* const tint_symbol_44, const constant Camera* const tint_symbol_45, const device ClusterLightGroup* const tint_symbol_46, texture2d<float, access::sample> tint_symbol_47, sampler tint_symbol_48) {
+  SurfaceInfo const surface = GetSurfaceInfo(input, tint_symbol_28, tint_symbol_29, tint_symbol_30, tint_symbol_31, tint_symbol_32, tint_symbol_33, tint_symbol_34, tint_symbol_35, tint_symbol_36, tint_symbol_37, tint_symbol_38);
+  float3 Lo = float3(0.0f, 0.0f, 0.0f);
+  if (((*(tint_symbol_39)).dirIntensity > 0.0f)) {
+    PuctualLight light = {};
+    light.lightType = LightType_Directional;
+    light.pointToLight = (*(tint_symbol_39)).dirDirection;
+    light.color = (*(tint_symbol_39)).dirColor;
+    light.intensity = (*(tint_symbol_39)).dirIntensity;
+    float const lightVis = dirLightVisibility(input.worldPos, tint_symbol_40, tint_symbol_41, tint_symbol_42, tint_symbol_43, tint_symbol_44);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  uint const clusterIndex = getClusterIndex(input.position, tint_symbol_45);
+  uint const lightOffset = (*(tint_symbol_46)).lights.arr[clusterIndex].offset;
+  uint const lightCount = (*(tint_symbol_46)).lights.arr[clusterIndex].count;
+  for(uint lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) {
+    uint const i = (*(tint_symbol_46)).indices.arr[(lightOffset + lightIndex)];
+    PuctualLight light = {};
+    light.lightType = LightType_Point;
+    light.pointToLight = (float3((*(tint_symbol_39)).lights[i].position).xyz - input.worldPos);
+    light.range = (*(tint_symbol_39)).lights[i].range;
+    light.color = (*(tint_symbol_39)).lights[i].color;
+    light.intensity = (*(tint_symbol_39)).lights[i].intensity;
+    float const lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight, tint_symbol_40, tint_symbol_41, tint_symbol_42, tint_symbol_43, tint_symbol_44);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  float2 const ssaoCoord = (float4(input.position).xy / float2(int2(int2(tint_symbol_47.get_width(), tint_symbol_47.get_height())).xy));
+  float const ssaoFactor = tint_symbol_47.sample(tint_symbol_48, ssaoCoord)[0];
+  float3 const ambient = ((((*(tint_symbol_39)).ambient * surface.albedo) * surface.ao) * ssaoFactor);
+  float3 const color = linearTosRGB(((Lo + ambient) + surface.emissive));
+  FragmentOutput out = {};
+  out.color = float4(color, surface.baseColor[3]);
+  out.emissive = float4(surface.emissive, surface.baseColor[3]);
+  return out;
+}
+
+fragment tint_symbol_2 fragmentMain(texture2d<float, access::sample> tint_symbol_49 [[texture(0)]], sampler tint_symbol_50 [[sampler(0)]], texture2d<float, access::sample> tint_symbol_51 [[texture(1)]], sampler tint_symbol_52 [[sampler(1)]], const constant Material* tint_symbol_53 [[buffer(0)]], texture2d<float, access::sample> tint_symbol_54 [[texture(2)]], sampler tint_symbol_55 [[sampler(2)]], texture2d<float, access::sample> tint_symbol_56 [[texture(3)]], sampler tint_symbol_57 [[sampler(3)]], texture2d<float, access::sample> tint_symbol_58 [[texture(4)]], sampler tint_symbol_59 [[sampler(4)]], const device GlobalLights* tint_symbol_60 [[buffer(2)]], const device LightShadowTable* tint_symbol_61 [[buffer(3)]], const device LightShadows* tint_symbol_62 [[buffer(4)]], depth2d<float, access::sample> tint_symbol_63 [[texture(6)]], sampler tint_symbol_64 [[sampler(6)]], const constant Camera* tint_symbol_66 [[buffer(1)]], const device ClusterLightGroup* tint_symbol_67 [[buffer(5)]], texture2d<float, access::sample> tint_symbol_68 [[texture(5)]], sampler tint_symbol_69 [[sampler(5)]], float4 position [[position]], tint_symbol_1 tint_symbol [[stage_in]]) {
+  thread tint_array_wrapper_2 tint_symbol_65 = {.arr={float2(-1.5f, -1.5f), float2(-1.5f, -0.5f), float2(-1.5f, 0.5f), float2(-1.5f, 1.5f), float2(-0.5f, -1.5f), float2(-0.5f, -0.5f), float2(-0.5f, 0.5f), float2(-0.5f, 1.5f), float2(0.5f, -1.5f), float2(0.5f, -0.5f), float2(0.5f, 0.5f), float2(0.5f, 1.5f), float2(1.5f, -1.5f), float2(1.5f, -0.5f), float2(1.5f, 0.5f), float2(1.5f, 1.5f)}};
+  VertexOutput const tint_symbol_3 = {.position=position, .worldPos=tint_symbol.worldPos, .view=tint_symbol.view, .texcoord=tint_symbol.texcoord, .texcoord2=tint_symbol.texcoord2, .color=tint_symbol.color, .instanceColor=tint_symbol.instanceColor, .normal=tint_symbol.normal, .tangent=tint_symbol.tangent, .bitangent=tint_symbol.bitangent};
+  FragmentOutput const inner_result = fragmentMain_inner(tint_symbol_3, tint_symbol_49, tint_symbol_50, tint_symbol_51, tint_symbol_52, tint_symbol_53, tint_symbol_54, tint_symbol_55, tint_symbol_56, tint_symbol_57, tint_symbol_58, tint_symbol_59, tint_symbol_60, tint_symbol_61, tint_symbol_62, tint_symbol_63, tint_symbol_64, &(tint_symbol_65), tint_symbol_66, tint_symbol_67, tint_symbol_68, tint_symbol_69);
+  tint_symbol_2 wrapper_result = {};
+  wrapper_result.color = inner_result.color;
+  wrapper_result.emissive = inner_result.emissive;
+  return wrapper_result;
+}
+
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.spvasm b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.spvasm
new file mode 100644
index 0000000..6a4dc79
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.spvasm
@@ -0,0 +1,1302 @@
+benchmark/skinned-shadowed-pbr-fragment.wgsl:51:13 warning: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+  lights : @stride(32) array<Light>;
+            ^^^^^^
+
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 853
+; Schema: 0
+               OpCapability Shader
+               OpCapability ImageQuery
+        %116 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %fragmentMain "fragmentMain" %position_1 %worldPos_1 %view_1 %texcoord_1 %texcoord2_1 %color_1 %instanceColor_1 %normal_1 %tangent_1 %bitangent_1 %color_2 %emissive_1
+               OpExecutionMode %fragmentMain OriginUpperLeft
+               OpName %position_1 "position_1"
+               OpName %worldPos_1 "worldPos_1"
+               OpName %view_1 "view_1"
+               OpName %texcoord_1 "texcoord_1"
+               OpName %texcoord2_1 "texcoord2_1"
+               OpName %color_1 "color_1"
+               OpName %instanceColor_1 "instanceColor_1"
+               OpName %normal_1 "normal_1"
+               OpName %tangent_1 "tangent_1"
+               OpName %bitangent_1 "bitangent_1"
+               OpName %color_2 "color_2"
+               OpName %emissive_1 "emissive_1"
+               OpName %GAMMA "GAMMA"
+               OpName %Camera "Camera"
+               OpMemberName %Camera 0 "projection"
+               OpMemberName %Camera 1 "inverseProjection"
+               OpMemberName %Camera 2 "view"
+               OpMemberName %Camera 3 "position"
+               OpMemberName %Camera 4 "time"
+               OpMemberName %Camera 5 "outputSize"
+               OpMemberName %Camera 6 "zNear"
+               OpMemberName %Camera 7 "zFar"
+               OpName %camera "camera"
+               OpName %ClusterLightGroup "ClusterLightGroup"
+               OpMemberName %ClusterLightGroup 0 "offset"
+               OpMemberName %ClusterLightGroup 1 "lights"
+               OpName %ClusterLights "ClusterLights"
+               OpMemberName %ClusterLights 0 "offset"
+               OpMemberName %ClusterLights 1 "count"
+               OpMemberName %ClusterLightGroup 2 "indices"
+               OpName %clusterLights "clusterLights"
+               OpName %GlobalLights "GlobalLights"
+               OpMemberName %GlobalLights 0 "ambient"
+               OpMemberName %GlobalLights 1 "dirColor"
+               OpMemberName %GlobalLights 2 "dirIntensity"
+               OpMemberName %GlobalLights 3 "dirDirection"
+               OpMemberName %GlobalLights 4 "lightCount"
+               OpMemberName %GlobalLights 5 "lights"
+               OpName %Light "Light"
+               OpMemberName %Light 0 "position"
+               OpMemberName %Light 1 "range"
+               OpMemberName %Light 2 "color"
+               OpMemberName %Light 3 "intensity"
+               OpName %globalLights "globalLights"
+               OpName %tileCount "tileCount"
+               OpName %defaultSampler "defaultSampler"
+               OpName %shadowTexture "shadowTexture"
+               OpName %shadowSampler "shadowSampler"
+               OpName %LightShadowTable "LightShadowTable"
+               OpMemberName %LightShadowTable 0 "light"
+               OpName %lightShadowTable "lightShadowTable"
+               OpName %shadowSampleOffsets "shadowSampleOffsets"
+               OpName %shadowSampleCount "shadowSampleCount"
+               OpName %LightShadows "LightShadows"
+               OpMemberName %LightShadows 0 "properties"
+               OpName %ShadowProperties "ShadowProperties"
+               OpMemberName %ShadowProperties 0 "viewport"
+               OpMemberName %ShadowProperties 1 "viewProj"
+               OpName %shadow "shadow"
+               OpName %Material "Material"
+               OpMemberName %Material 0 "baseColorFactor"
+               OpMemberName %Material 1 "emissiveFactor"
+               OpMemberName %Material 2 "occlusionStrength"
+               OpMemberName %Material 3 "metallicRoughnessFactor"
+               OpMemberName %Material 4 "alphaCutoff"
+               OpName %material "material"
+               OpName %baseColorTexture "baseColorTexture"
+               OpName %baseColorSampler "baseColorSampler"
+               OpName %normalTexture "normalTexture"
+               OpName %normalSampler "normalSampler"
+               OpName %metallicRoughnessTexture "metallicRoughnessTexture"
+               OpName %metallicRoughnessSampler "metallicRoughnessSampler"
+               OpName %occlusionTexture "occlusionTexture"
+               OpName %occlusionSampler "occlusionSampler"
+               OpName %emissiveTexture "emissiveTexture"
+               OpName %emissiveSampler "emissiveSampler"
+               OpName %PI "PI"
+               OpName %LightType_Point "LightType_Point"
+               OpName %LightType_Spot "LightType_Spot"
+               OpName %LightType_Directional "LightType_Directional"
+               OpName %ssaoTexture "ssaoTexture"
+               OpName %linearTosRGB "linearTosRGB"
+               OpName %linear "linear"
+               OpName %sRGBToLinear "sRGBToLinear"
+               OpName %srgb "srgb"
+               OpName %linearDepth "linearDepth"
+               OpName %depthSample "depthSample"
+               OpName %getTile "getTile"
+               OpName %fragCoord "fragCoord"
+               OpName %getClusterIndex "getClusterIndex"
+               OpName %fragCoord_0 "fragCoord"
+               OpName %dirLightVisibility "dirLightVisibility"
+               OpName %worldPos "worldPos"
+               OpName %visibility "visibility"
+               OpName %i "i"
+               OpName %getCubeFace "getCubeFace"
+               OpName %v "v"
+               OpName %pointLightVisibility "pointLightVisibility"
+               OpName %lightIndex "lightIndex"
+               OpName %worldPos_0 "worldPos"
+               OpName %pointToLight "pointToLight"
+               OpName %shadowIndex "shadowIndex"
+               OpName %visibility_0 "visibility"
+               OpName %i_0 "i"
+               OpName %SurfaceInfo "SurfaceInfo"
+               OpMemberName %SurfaceInfo 0 "baseColor"
+               OpMemberName %SurfaceInfo 1 "albedo"
+               OpMemberName %SurfaceInfo 2 "metallic"
+               OpMemberName %SurfaceInfo 3 "roughness"
+               OpMemberName %SurfaceInfo 4 "normal"
+               OpMemberName %SurfaceInfo 5 "f0"
+               OpMemberName %SurfaceInfo 6 "ao"
+               OpMemberName %SurfaceInfo 7 "emissive"
+               OpMemberName %SurfaceInfo 8 "v"
+               OpName %VertexOutput "VertexOutput"
+               OpMemberName %VertexOutput 0 "position"
+               OpMemberName %VertexOutput 1 "worldPos"
+               OpMemberName %VertexOutput 2 "view"
+               OpMemberName %VertexOutput 3 "texcoord"
+               OpMemberName %VertexOutput 4 "texcoord2"
+               OpMemberName %VertexOutput 5 "color"
+               OpMemberName %VertexOutput 6 "instanceColor"
+               OpMemberName %VertexOutput 7 "normal"
+               OpMemberName %VertexOutput 8 "tangent"
+               OpMemberName %VertexOutput 9 "bitangent"
+               OpName %GetSurfaceInfo "GetSurfaceInfo"
+               OpName %input "input"
+               OpName %surface "surface"
+               OpName %FresnelSchlick "FresnelSchlick"
+               OpName %cosTheta "cosTheta"
+               OpName %F0 "F0"
+               OpName %DistributionGGX "DistributionGGX"
+               OpName %N "N"
+               OpName %H "H"
+               OpName %roughness "roughness"
+               OpName %GeometrySchlickGGX "GeometrySchlickGGX"
+               OpName %NdotV "NdotV"
+               OpName %roughness_0 "roughness"
+               OpName %GeometrySmith "GeometrySmith"
+               OpName %N_0 "N"
+               OpName %V "V"
+               OpName %L "L"
+               OpName %roughness_1 "roughness"
+               OpName %PuctualLight "PuctualLight"
+               OpMemberName %PuctualLight 0 "lightType"
+               OpMemberName %PuctualLight 1 "pointToLight"
+               OpMemberName %PuctualLight 2 "range"
+               OpMemberName %PuctualLight 3 "color"
+               OpMemberName %PuctualLight 4 "intensity"
+               OpName %lightAttenuation "lightAttenuation"
+               OpName %light "light"
+               OpName %lightRadiance "lightRadiance"
+               OpName %light_0 "light"
+               OpName %surface_0 "surface"
+               OpName %FragmentOutput "FragmentOutput"
+               OpMemberName %FragmentOutput 0 "color"
+               OpMemberName %FragmentOutput 1 "emissive"
+               OpName %fragmentMain_inner "fragmentMain_inner"
+               OpName %input_0 "input"
+               OpName %Lo "Lo"
+               OpName %light_1 "light"
+               OpName %lightIndex_0 "lightIndex"
+               OpName %light_2 "light"
+               OpName %out "out"
+               OpName %fragmentMain "fragmentMain"
+               OpDecorate %position_1 BuiltIn FragCoord
+               OpDecorate %worldPos_1 Location 0
+               OpDecorate %view_1 Location 1
+               OpDecorate %texcoord_1 Location 2
+               OpDecorate %texcoord2_1 Location 3
+               OpDecorate %color_1 Location 4
+               OpDecorate %instanceColor_1 Location 5
+               OpDecorate %normal_1 Location 6
+               OpDecorate %tangent_1 Location 7
+               OpDecorate %bitangent_1 Location 8
+               OpDecorate %color_2 Location 0
+               OpDecorate %emissive_1 Location 1
+               OpDecorate %Camera Block
+               OpMemberDecorate %Camera 0 Offset 0
+               OpMemberDecorate %Camera 0 ColMajor
+               OpMemberDecorate %Camera 0 MatrixStride 16
+               OpMemberDecorate %Camera 1 Offset 64
+               OpMemberDecorate %Camera 1 ColMajor
+               OpMemberDecorate %Camera 1 MatrixStride 16
+               OpMemberDecorate %Camera 2 Offset 128
+               OpMemberDecorate %Camera 2 ColMajor
+               OpMemberDecorate %Camera 2 MatrixStride 16
+               OpMemberDecorate %Camera 3 Offset 192
+               OpMemberDecorate %Camera 4 Offset 204
+               OpMemberDecorate %Camera 5 Offset 208
+               OpMemberDecorate %Camera 6 Offset 216
+               OpMemberDecorate %Camera 7 Offset 220
+               OpDecorate %camera NonWritable
+               OpDecorate %camera Binding 0
+               OpDecorate %camera DescriptorSet 0
+               OpDecorate %ClusterLightGroup Block
+               OpMemberDecorate %ClusterLightGroup 0 Offset 0
+               OpMemberDecorate %ClusterLightGroup 1 Offset 4
+               OpMemberDecorate %ClusterLights 0 Offset 0
+               OpMemberDecorate %ClusterLights 1 Offset 4
+               OpDecorate %_arr_ClusterLights_uint_27648 ArrayStride 8
+               OpMemberDecorate %ClusterLightGroup 2 Offset 221188
+               OpDecorate %_arr_uint_uint_1769472 ArrayStride 4
+               OpDecorate %clusterLights NonWritable
+               OpDecorate %clusterLights Binding 1
+               OpDecorate %clusterLights DescriptorSet 0
+               OpDecorate %GlobalLights Block
+               OpMemberDecorate %GlobalLights 0 Offset 0
+               OpMemberDecorate %GlobalLights 1 Offset 16
+               OpMemberDecorate %GlobalLights 2 Offset 28
+               OpMemberDecorate %GlobalLights 3 Offset 32
+               OpMemberDecorate %GlobalLights 4 Offset 44
+               OpMemberDecorate %GlobalLights 5 Offset 48
+               OpMemberDecorate %Light 0 Offset 0
+               OpMemberDecorate %Light 1 Offset 12
+               OpMemberDecorate %Light 2 Offset 16
+               OpMemberDecorate %Light 3 Offset 28
+               OpDecorate %_runtimearr_Light ArrayStride 32
+               OpDecorate %globalLights NonWritable
+               OpDecorate %globalLights Binding 2
+               OpDecorate %globalLights DescriptorSet 0
+               OpDecorate %defaultSampler Binding 3
+               OpDecorate %defaultSampler DescriptorSet 0
+               OpDecorate %shadowTexture Binding 4
+               OpDecorate %shadowTexture DescriptorSet 0
+               OpDecorate %shadowSampler Binding 5
+               OpDecorate %shadowSampler DescriptorSet 0
+               OpDecorate %LightShadowTable Block
+               OpMemberDecorate %LightShadowTable 0 Offset 0
+               OpDecorate %_runtimearr_int ArrayStride 4
+               OpDecorate %lightShadowTable NonWritable
+               OpDecorate %lightShadowTable Binding 6
+               OpDecorate %lightShadowTable DescriptorSet 0
+               OpDecorate %_arr_v2float_shadowSampleCount ArrayStride 8
+               OpDecorate %LightShadows Block
+               OpMemberDecorate %LightShadows 0 Offset 0
+               OpMemberDecorate %ShadowProperties 0 Offset 0
+               OpMemberDecorate %ShadowProperties 1 Offset 16
+               OpMemberDecorate %ShadowProperties 1 ColMajor
+               OpMemberDecorate %ShadowProperties 1 MatrixStride 16
+               OpDecorate %_runtimearr_ShadowProperties ArrayStride 80
+               OpDecorate %shadow NonWritable
+               OpDecorate %shadow Binding 7
+               OpDecorate %shadow DescriptorSet 0
+               OpDecorate %Material Block
+               OpMemberDecorate %Material 0 Offset 0
+               OpMemberDecorate %Material 1 Offset 16
+               OpMemberDecorate %Material 2 Offset 28
+               OpMemberDecorate %Material 3 Offset 32
+               OpMemberDecorate %Material 4 Offset 40
+               OpDecorate %material NonWritable
+               OpDecorate %material Binding 8
+               OpDecorate %material DescriptorSet 0
+               OpDecorate %baseColorTexture Binding 9
+               OpDecorate %baseColorTexture DescriptorSet 0
+               OpDecorate %baseColorSampler Binding 10
+               OpDecorate %baseColorSampler DescriptorSet 0
+               OpDecorate %normalTexture Binding 11
+               OpDecorate %normalTexture DescriptorSet 0
+               OpDecorate %normalSampler Binding 12
+               OpDecorate %normalSampler DescriptorSet 0
+               OpDecorate %metallicRoughnessTexture Binding 13
+               OpDecorate %metallicRoughnessTexture DescriptorSet 0
+               OpDecorate %metallicRoughnessSampler Binding 14
+               OpDecorate %metallicRoughnessSampler DescriptorSet 0
+               OpDecorate %occlusionTexture Binding 15
+               OpDecorate %occlusionTexture DescriptorSet 0
+               OpDecorate %occlusionSampler Binding 16
+               OpDecorate %occlusionSampler DescriptorSet 0
+               OpDecorate %emissiveTexture Binding 17
+               OpDecorate %emissiveTexture DescriptorSet 0
+               OpDecorate %emissiveSampler Binding 18
+               OpDecorate %emissiveSampler DescriptorSet 0
+               OpDecorate %ssaoTexture Binding 19
+               OpDecorate %ssaoTexture DescriptorSet 0
+               OpMemberDecorate %SurfaceInfo 0 Offset 0
+               OpMemberDecorate %SurfaceInfo 1 Offset 16
+               OpMemberDecorate %SurfaceInfo 2 Offset 28
+               OpMemberDecorate %SurfaceInfo 3 Offset 32
+               OpMemberDecorate %SurfaceInfo 4 Offset 48
+               OpMemberDecorate %SurfaceInfo 5 Offset 64
+               OpMemberDecorate %SurfaceInfo 6 Offset 76
+               OpMemberDecorate %SurfaceInfo 7 Offset 80
+               OpMemberDecorate %SurfaceInfo 8 Offset 96
+               OpMemberDecorate %VertexOutput 0 Offset 0
+               OpMemberDecorate %VertexOutput 1 Offset 16
+               OpMemberDecorate %VertexOutput 2 Offset 32
+               OpMemberDecorate %VertexOutput 3 Offset 48
+               OpMemberDecorate %VertexOutput 4 Offset 56
+               OpMemberDecorate %VertexOutput 5 Offset 64
+               OpMemberDecorate %VertexOutput 6 Offset 80
+               OpMemberDecorate %VertexOutput 7 Offset 96
+               OpMemberDecorate %VertexOutput 8 Offset 112
+               OpMemberDecorate %VertexOutput 9 Offset 128
+               OpMemberDecorate %PuctualLight 0 Offset 0
+               OpMemberDecorate %PuctualLight 1 Offset 16
+               OpMemberDecorate %PuctualLight 2 Offset 28
+               OpMemberDecorate %PuctualLight 3 Offset 32
+               OpMemberDecorate %PuctualLight 4 Offset 44
+               OpMemberDecorate %FragmentOutput 0 Offset 0
+               OpMemberDecorate %FragmentOutput 1 Offset 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %position_1 = OpVariable %_ptr_Input_v4float Input
+    %v3float = OpTypeVector %float 3
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+ %worldPos_1 = OpVariable %_ptr_Input_v3float Input
+     %view_1 = OpVariable %_ptr_Input_v3float Input
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+ %texcoord_1 = OpVariable %_ptr_Input_v2float Input
+%texcoord2_1 = OpVariable %_ptr_Input_v2float Input
+    %color_1 = OpVariable %_ptr_Input_v4float Input
+%instanceColor_1 = OpVariable %_ptr_Input_v4float Input
+   %normal_1 = OpVariable %_ptr_Input_v3float Input
+  %tangent_1 = OpVariable %_ptr_Input_v3float Input
+%bitangent_1 = OpVariable %_ptr_Input_v3float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %20 = OpConstantNull %v4float
+    %color_2 = OpVariable %_ptr_Output_v4float Output %20
+ %emissive_1 = OpVariable %_ptr_Output_v4float Output %20
+      %GAMMA = OpConstant %float 2.20000005
+%mat4v4float = OpTypeMatrix %v4float 4
+     %Camera = OpTypeStruct %mat4v4float %mat4v4float %mat4v4float %v3float %float %v2float %float %float
+%_ptr_Uniform_Camera = OpTypePointer Uniform %Camera
+     %camera = OpVariable %_ptr_Uniform_Camera Uniform
+       %uint = OpTypeInt 32 0
+%ClusterLights = OpTypeStruct %uint %uint
+ %uint_27648 = OpConstant %uint 27648
+%_arr_ClusterLights_uint_27648 = OpTypeArray %ClusterLights %uint_27648
+%uint_1769472 = OpConstant %uint 1769472
+%_arr_uint_uint_1769472 = OpTypeArray %uint %uint_1769472
+%ClusterLightGroup = OpTypeStruct %uint %_arr_ClusterLights_uint_27648 %_arr_uint_uint_1769472
+%_ptr_StorageBuffer_ClusterLightGroup = OpTypePointer StorageBuffer %ClusterLightGroup
+%clusterLights = OpVariable %_ptr_StorageBuffer_ClusterLightGroup StorageBuffer
+      %Light = OpTypeStruct %v3float %float %v3float %float
+%_runtimearr_Light = OpTypeRuntimeArray %Light
+%GlobalLights = OpTypeStruct %v3float %v3float %float %v3float %uint %_runtimearr_Light
+%_ptr_StorageBuffer_GlobalLights = OpTypePointer StorageBuffer %GlobalLights
+%globalLights = OpVariable %_ptr_StorageBuffer_GlobalLights StorageBuffer
+     %v3uint = OpTypeVector %uint 3
+    %uint_32 = OpConstant %uint 32
+    %uint_18 = OpConstant %uint 18
+    %uint_48 = OpConstant %uint 48
+  %tileCount = OpConstantComposite %v3uint %uint_32 %uint_18 %uint_48
+         %48 = OpTypeSampler
+%_ptr_UniformConstant_48 = OpTypePointer UniformConstant %48
+%defaultSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+         %51 = OpTypeImage %float 2D 1 0 0 1 Unknown
+%_ptr_UniformConstant_51 = OpTypePointer UniformConstant %51
+%shadowTexture = OpVariable %_ptr_UniformConstant_51 UniformConstant
+%_ptr_UniformConstant_48_0 = OpTypePointer UniformConstant %48
+%shadowSampler = OpVariable %_ptr_UniformConstant_48_0 UniformConstant
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+%LightShadowTable = OpTypeStruct %_runtimearr_int
+%_ptr_StorageBuffer_LightShadowTable = OpTypePointer StorageBuffer %LightShadowTable
+%lightShadowTable = OpVariable %_ptr_StorageBuffer_LightShadowTable StorageBuffer
+%shadowSampleCount = OpConstant %uint 16
+%_arr_v2float_shadowSampleCount = OpTypeArray %v2float %shadowSampleCount
+ %float_n1_5 = OpConstant %float -1.5
+         %62 = OpConstantComposite %v2float %float_n1_5 %float_n1_5
+ %float_n0_5 = OpConstant %float -0.5
+         %64 = OpConstantComposite %v2float %float_n1_5 %float_n0_5
+  %float_0_5 = OpConstant %float 0.5
+         %66 = OpConstantComposite %v2float %float_n1_5 %float_0_5
+  %float_1_5 = OpConstant %float 1.5
+         %68 = OpConstantComposite %v2float %float_n1_5 %float_1_5
+         %69 = OpConstantComposite %v2float %float_n0_5 %float_n1_5
+         %70 = OpConstantComposite %v2float %float_n0_5 %float_n0_5
+         %71 = OpConstantComposite %v2float %float_n0_5 %float_0_5
+         %72 = OpConstantComposite %v2float %float_n0_5 %float_1_5
+         %73 = OpConstantComposite %v2float %float_0_5 %float_n1_5
+         %74 = OpConstantComposite %v2float %float_0_5 %float_n0_5
+         %75 = OpConstantComposite %v2float %float_0_5 %float_0_5
+         %76 = OpConstantComposite %v2float %float_0_5 %float_1_5
+         %77 = OpConstantComposite %v2float %float_1_5 %float_n1_5
+         %78 = OpConstantComposite %v2float %float_1_5 %float_n0_5
+         %79 = OpConstantComposite %v2float %float_1_5 %float_0_5
+         %80 = OpConstantComposite %v2float %float_1_5 %float_1_5
+         %81 = OpConstantComposite %_arr_v2float_shadowSampleCount %62 %64 %66 %68 %69 %70 %71 %72 %73 %74 %75 %76 %77 %78 %79 %80
+%_ptr_Private__arr_v2float_shadowSampleCount = OpTypePointer Private %_arr_v2float_shadowSampleCount
+%shadowSampleOffsets = OpVariable %_ptr_Private__arr_v2float_shadowSampleCount Private %81
+%ShadowProperties = OpTypeStruct %v4float %mat4v4float
+%_runtimearr_ShadowProperties = OpTypeRuntimeArray %ShadowProperties
+%LightShadows = OpTypeStruct %_runtimearr_ShadowProperties
+%_ptr_StorageBuffer_LightShadows = OpTypePointer StorageBuffer %LightShadows
+     %shadow = OpVariable %_ptr_StorageBuffer_LightShadows StorageBuffer
+   %Material = OpTypeStruct %v4float %v3float %float %v2float %float
+%_ptr_Uniform_Material = OpTypePointer Uniform %Material
+   %material = OpVariable %_ptr_Uniform_Material Uniform
+         %94 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_94 = OpTypePointer UniformConstant %94
+%baseColorTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+%baseColorSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+%normalTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+%normalSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+%metallicRoughnessTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+%metallicRoughnessSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+%occlusionTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+%occlusionSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+%emissiveTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+%emissiveSampler = OpVariable %_ptr_UniformConstant_48 UniformConstant
+         %PI = OpConstant %float 3.14159274
+%LightType_Point = OpConstant %uint 0
+%LightType_Spot = OpConstant %uint 1
+%LightType_Directional = OpConstant %uint 2
+%ssaoTexture = OpVariable %_ptr_UniformConstant_94 UniformConstant
+        %109 = OpTypeFunction %v3float %v3float
+    %float_1 = OpConstant %float 1
+        %122 = OpConstantComposite %v3float %GAMMA %GAMMA %GAMMA
+        %123 = OpTypeFunction %float %float
+     %uint_7 = OpConstant %uint 7
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+     %uint_6 = OpConstant %uint 6
+        %144 = OpTypeFunction %v3uint %v4float
+    %float_0 = OpConstant %float 0
+     %uint_5 = OpConstant %uint 5
+        %197 = OpTypeFunction %uint %v4float
+        %213 = OpTypeFunction %float %v3float
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+     %int_n1 = OpConstant %int -1
+       %bool = OpTypeBool
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+%_ptr_StorageBuffer_mat4v4float = OpTypePointer StorageBuffer %mat4v4float
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+        %241 = OpConstantNull %v2float
+      %v2int = OpTypeVector %int 2
+      %int_0 = OpConstant %int 0
+%_ptr_Function_float = OpTypePointer Function %float
+        %278 = OpConstantNull %float
+%_ptr_Function_uint = OpTypePointer Function %uint
+        %281 = OpConstantNull %uint
+        %295 = OpTypeSampledImage %51
+%_ptr_Private_v2float = OpTypePointer Private %v2float
+%float_0_00300000003 = OpConstant %float 0.00300000003
+   %float_16 = OpConstant %float 16
+        %315 = OpTypeFunction %int %v3float
+      %int_5 = OpConstant %int 5
+      %int_4 = OpConstant %int 4
+      %int_3 = OpConstant %int 3
+      %int_2 = OpConstant %int 2
+      %int_1 = OpConstant %int 1
+        %353 = OpTypeFunction %float %uint %v3float %v3float
+%_ptr_Function_int = OpTypePointer Function %int
+        %364 = OpConstantNull %int
+   %float_n1 = OpConstant %float -1
+%float_0_00999999978 = OpConstant %float 0.00999999978
+%SurfaceInfo = OpTypeStruct %v4float %v3float %float %float %v3float %v3float %float %v3float %v3float
+%VertexOutput = OpTypeStruct %v4float %v3float %v3float %v2float %v2float %v4float %v4float %v3float %v3float %v3float
+        %451 = OpTypeFunction %SurfaceInfo %VertexOutput
+%_ptr_Function_SurfaceInfo = OpTypePointer Function %SurfaceInfo
+        %459 = OpConstantNull %SurfaceInfo
+     %uint_8 = OpConstant %uint 8
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+%mat3v3float = OpTypeMatrix %v3float 3
+        %473 = OpTypeSampledImage %94
+     %uint_4 = OpConstant %uint 4
+    %float_2 = OpConstant %float 2
+        %482 = OpConstantComposite %v3float %float_1 %float_1 %float_1
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+     %uint_3 = OpConstant %uint 3
+%float_0_0399999991 = OpConstant %float 0.0399999991
+        %526 = OpConstantComposite %v3float %float_0_0399999991 %float_0_0399999991 %float_0_0399999991
+%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
+        %574 = OpTypeFunction %v3float %float %v3float
+    %float_5 = OpConstant %float 5
+        %585 = OpTypeFunction %float %v3float %v3float %float
+        %602 = OpTypeFunction %float %float %float
+    %float_8 = OpConstant %float 8
+        %615 = OpTypeFunction %float %v3float %v3float %v3float %float
+%PuctualLight = OpTypeStruct %uint %v3float %float %v3float %float
+        %629 = OpTypeFunction %float %PuctualLight
+    %float_4 = OpConstant %float 4
+        %654 = OpTypeFunction %v3float %PuctualLight %SurfaceInfo
+%float_0_00100000005 = OpConstant %float 0.00100000005
+        %702 = OpConstantComposite %v3float %PI %PI %PI
+%FragmentOutput = OpTypeStruct %v4float %v4float
+        %707 = OpTypeFunction %FragmentOutput %VertexOutput
+        %713 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+        %715 = OpConstantNull %v3float
+%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+%_ptr_Function_PuctualLight = OpTypePointer Function %PuctualLight
+        %724 = OpConstantNull %PuctualLight
+%_ptr_StorageBuffer_v3float = OpTypePointer StorageBuffer %v3float
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%_ptr_Function_FragmentOutput = OpTypePointer Function %FragmentOutput
+        %818 = OpConstantNull %FragmentOutput
+       %void = OpTypeVoid
+        %835 = OpTypeFunction %void
+%linearTosRGB = OpFunction %v3float None %109
+     %linear = OpFunctionParameter %v3float
+        %112 = OpLabel
+        %114 = OpFDiv %float %float_1 %GAMMA
+        %117 = OpCompositeConstruct %v3float %114 %114 %114
+        %115 = OpExtInst %v3float %116 Pow %linear %117
+               OpReturnValue %115
+               OpFunctionEnd
+%sRGBToLinear = OpFunction %v3float None %109
+       %srgb = OpFunctionParameter %v3float
+        %120 = OpLabel
+        %121 = OpExtInst %v3float %116 Pow %srgb %122
+               OpReturnValue %121
+               OpFunctionEnd
+%linearDepth = OpFunction %float None %123
+%depthSample = OpFunctionParameter %float
+        %126 = OpLabel
+        %129 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+        %130 = OpLoad %float %129
+        %132 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+        %133 = OpLoad %float %132
+        %134 = OpFMul %float %130 %133
+        %136 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+        %137 = OpLoad %float %136
+        %138 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+        %139 = OpLoad %float %138
+        %140 = OpFSub %float %137 %139
+        %141 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+        %142 = OpLoad %float %141
+        %135 = OpExtInst %float %116 Fma %depthSample %140 %142
+        %143 = OpFDiv %float %134 %135
+               OpReturnValue %143
+               OpFunctionEnd
+    %getTile = OpFunction %v3uint None %144
+  %fragCoord = OpFunctionParameter %v4float
+        %147 = OpLabel
+        %149 = OpCompositeExtract %uint %tileCount 2
+        %148 = OpConvertUToF %float %149
+        %151 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+        %152 = OpLoad %float %151
+        %153 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+        %154 = OpLoad %float %153
+        %155 = OpFDiv %float %152 %154
+        %150 = OpExtInst %float %116 Log2 %155
+        %156 = OpFDiv %float %148 %150
+        %159 = OpCompositeExtract %uint %tileCount 2
+        %158 = OpConvertUToF %float %159
+        %161 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+        %162 = OpLoad %float %161
+        %160 = OpExtInst %float %116 Log2 %162
+        %163 = OpFMul %float %158 %160
+        %165 = OpAccessChain %_ptr_Uniform_float %camera %uint_7
+        %166 = OpLoad %float %165
+        %167 = OpAccessChain %_ptr_Uniform_float %camera %uint_6
+        %168 = OpLoad %float %167
+        %169 = OpFDiv %float %166 %168
+        %164 = OpExtInst %float %116 Log2 %169
+        %170 = OpFDiv %float %163 %164
+        %157 = OpFNegate %float %170
+        %175 = OpCompositeExtract %float %fragCoord 2
+        %174 = OpFunctionCall %float %linearDepth %175
+        %173 = OpExtInst %float %116 Log2 %174
+        %176 = OpFMul %float %173 %156
+        %177 = OpFAdd %float %176 %157
+        %172 = OpExtInst %float %116 NMax %177 %float_0
+        %171 = OpConvertFToU %uint %172
+        %180 = OpCompositeExtract %float %fragCoord 0
+        %182 = OpAccessChain %_ptr_Uniform_float %camera %uint_5 %LightType_Point
+        %183 = OpLoad %float %182
+        %185 = OpCompositeExtract %uint %tileCount 0
+        %184 = OpConvertUToF %float %185
+        %186 = OpFDiv %float %183 %184
+        %187 = OpFDiv %float %180 %186
+        %179 = OpConvertFToU %uint %187
+        %189 = OpCompositeExtract %float %fragCoord 1
+        %190 = OpAccessChain %_ptr_Uniform_float %camera %uint_5 %LightType_Spot
+        %191 = OpLoad %float %190
+        %193 = OpCompositeExtract %uint %tileCount 1
+        %192 = OpConvertUToF %float %193
+        %194 = OpFDiv %float %191 %192
+        %195 = OpFDiv %float %189 %194
+        %188 = OpConvertFToU %uint %195
+        %196 = OpCompositeConstruct %v3uint %179 %188 %171
+               OpReturnValue %196
+               OpFunctionEnd
+%getClusterIndex = OpFunction %uint None %197
+%fragCoord_0 = OpFunctionParameter %v4float
+        %200 = OpLabel
+        %201 = OpFunctionCall %v3uint %getTile %fragCoord_0
+        %202 = OpCompositeExtract %uint %201 0
+        %203 = OpCompositeExtract %uint %201 1
+        %204 = OpCompositeExtract %uint %tileCount 0
+        %205 = OpIMul %uint %203 %204
+        %206 = OpIAdd %uint %202 %205
+        %207 = OpCompositeExtract %uint %201 2
+        %208 = OpCompositeExtract %uint %tileCount 0
+        %209 = OpIMul %uint %207 %208
+        %210 = OpCompositeExtract %uint %tileCount 1
+        %211 = OpIMul %uint %209 %210
+        %212 = OpIAdd %uint %206 %211
+               OpReturnValue %212
+               OpFunctionEnd
+%dirLightVisibility = OpFunction %float None %213
+   %worldPos = OpFunctionParameter %v3float
+        %216 = OpLabel
+        %239 = OpVariable %_ptr_Function_v2float Function %241
+        %263 = OpVariable %_ptr_Function_v2float Function %241
+ %visibility = OpVariable %_ptr_Function_float Function %278
+          %i = OpVariable %_ptr_Function_uint Function %281
+        %218 = OpAccessChain %_ptr_StorageBuffer_int %lightShadowTable %LightType_Point %LightType_Point
+        %219 = OpLoad %int %218
+        %221 = OpIEqual %bool %219 %int_n1
+               OpSelectionMerge %223 None
+               OpBranchConditional %221 %224 %223
+        %224 = OpLabel
+               OpReturnValue %float_1
+        %223 = OpLabel
+        %226 = OpAccessChain %_ptr_StorageBuffer_v4float %shadow %LightType_Point %219 %LightType_Point
+        %227 = OpLoad %v4float %226
+        %229 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %shadow %LightType_Point %219 %LightType_Spot
+        %230 = OpLoad %mat4v4float %229
+        %231 = OpCompositeExtract %float %worldPos 0
+        %232 = OpCompositeExtract %float %worldPos 1
+        %233 = OpCompositeExtract %float %worldPos 2
+        %234 = OpCompositeConstruct %v4float %231 %232 %233 %float_1
+        %235 = OpMatrixTimesVector %v4float %230 %234
+        %236 = OpVectorShuffle %v2float %235 %235 0 1
+        %237 = OpCompositeExtract %float %235 3
+        %242 = OpCompositeConstruct %v2float %237 %237
+        %238 = OpFDiv %v2float %236 %242
+        %243 = OpFMul %v2float %238 %74
+        %244 = OpFAdd %v2float %243 %75
+        %245 = OpCompositeExtract %float %244 0
+        %246 = OpCompositeExtract %float %244 1
+        %247 = OpCompositeExtract %float %235 2
+        %248 = OpCompositeExtract %float %235 3
+        %249 = OpFDiv %float %247 %248
+        %250 = OpCompositeConstruct %v3float %245 %246 %249
+        %252 = OpVectorShuffle %v2float %227 %227 0 1
+        %253 = OpVectorShuffle %v2float %250 %250 0 1
+        %254 = OpVectorShuffle %v2float %227 %227 2 3
+        %255 = OpFMul %v2float %253 %254
+        %256 = OpFAdd %v2float %252 %255
+        %260 = OpLoad %51 %shadowTexture
+        %258 = OpImageQuerySizeLod %v2int %260 %int_0
+        %257 = OpConvertSToF %v2float %258
+        %264 = OpCompositeConstruct %v2float %float_1 %float_1
+        %262 = OpFDiv %v2float %264 %257
+        %265 = OpVectorShuffle %v2float %227 %227 0 1
+        %266 = OpFSub %v2float %265 %262
+        %267 = OpCompositeExtract %float %266 0
+        %268 = OpCompositeExtract %float %266 1
+        %269 = OpVectorShuffle %v2float %227 %227 0 1
+        %270 = OpVectorShuffle %v2float %227 %227 2 3
+        %271 = OpFAdd %v2float %269 %270
+        %272 = OpFAdd %v2float %271 %262
+        %273 = OpCompositeExtract %float %272 0
+        %274 = OpCompositeExtract %float %272 1
+        %275 = OpCompositeConstruct %v4float %267 %268 %273 %274
+               OpStore %visibility %float_0
+               OpStore %i %LightType_Point
+               OpBranch %282
+        %282 = OpLabel
+               OpLoopMerge %283 %284 None
+               OpBranch %285
+        %285 = OpLabel
+        %287 = OpLoad %uint %i
+        %288 = OpULessThan %bool %287 %shadowSampleCount
+        %286 = OpLogicalNot %bool %288
+               OpSelectionMerge %289 None
+               OpBranchConditional %286 %290 %289
+        %290 = OpLabel
+               OpBranch %283
+        %289 = OpLabel
+        %291 = OpLoad %float %visibility
+        %293 = OpLoad %48 %shadowSampler
+        %294 = OpLoad %51 %shadowTexture
+        %296 = OpSampledImage %295 %294 %293
+        %298 = OpLoad %uint %i
+        %300 = OpAccessChain %_ptr_Private_v2float %shadowSampleOffsets %298
+        %301 = OpLoad %v2float %300
+        %302 = OpFMul %v2float %301 %262
+        %303 = OpFAdd %v2float %256 %302
+        %304 = OpVectorShuffle %v2float %275 %275 0 1
+        %305 = OpVectorShuffle %v2float %275 %275 2 3
+        %297 = OpExtInst %v2float %116 NClamp %303 %304 %305
+        %306 = OpCompositeExtract %float %250 2
+        %308 = OpFSub %float %306 %float_0_00300000003
+        %292 = OpImageSampleDrefExplicitLod %float %296 %297 %308 Lod %float_0
+        %309 = OpFAdd %float %291 %292
+               OpStore %visibility %309
+               OpBranch %284
+        %284 = OpLabel
+        %310 = OpLoad %uint %i
+        %311 = OpIAdd %uint %310 %LightType_Spot
+               OpStore %i %311
+               OpBranch %282
+        %283 = OpLabel
+        %312 = OpLoad %float %visibility
+        %314 = OpFDiv %float %312 %float_16
+               OpReturnValue %314
+               OpFunctionEnd
+%getCubeFace = OpFunction %int None %315
+          %v = OpFunctionParameter %v3float
+        %318 = OpLabel
+        %319 = OpExtInst %v3float %116 FAbs %v
+        %320 = OpCompositeExtract %float %319 2
+        %321 = OpCompositeExtract %float %319 0
+        %322 = OpFOrdGreaterThanEqual %bool %320 %321
+               OpSelectionMerge %323 None
+               OpBranchConditional %322 %324 %323
+        %324 = OpLabel
+        %325 = OpCompositeExtract %float %319 2
+        %326 = OpCompositeExtract %float %319 1
+        %327 = OpFOrdGreaterThanEqual %bool %325 %326
+               OpBranch %323
+        %323 = OpLabel
+        %328 = OpPhi %bool %322 %318 %327 %324
+               OpSelectionMerge %329 None
+               OpBranchConditional %328 %330 %329
+        %330 = OpLabel
+        %331 = OpCompositeExtract %float %v 2
+        %332 = OpFOrdLessThan %bool %331 %float_0
+               OpSelectionMerge %333 None
+               OpBranchConditional %332 %334 %333
+        %334 = OpLabel
+               OpReturnValue %int_5
+        %333 = OpLabel
+               OpReturnValue %int_4
+        %329 = OpLabel
+        %337 = OpCompositeExtract %float %319 1
+        %338 = OpCompositeExtract %float %319 0
+        %339 = OpFOrdGreaterThanEqual %bool %337 %338
+               OpSelectionMerge %340 None
+               OpBranchConditional %339 %341 %340
+        %341 = OpLabel
+        %342 = OpCompositeExtract %float %v 1
+        %343 = OpFOrdLessThan %bool %342 %float_0
+               OpSelectionMerge %344 None
+               OpBranchConditional %343 %345 %344
+        %345 = OpLabel
+               OpReturnValue %int_3
+        %344 = OpLabel
+               OpReturnValue %int_2
+        %340 = OpLabel
+        %348 = OpCompositeExtract %float %v 0
+        %349 = OpFOrdLessThan %bool %348 %float_0
+               OpSelectionMerge %350 None
+               OpBranchConditional %349 %351 %350
+        %351 = OpLabel
+               OpReturnValue %int_1
+        %350 = OpLabel
+               OpReturnValue %int_0
+               OpFunctionEnd
+%pointLightVisibility = OpFunction %float None %353
+ %lightIndex = OpFunctionParameter %uint
+ %worldPos_0 = OpFunctionParameter %v3float
+%pointToLight = OpFunctionParameter %v3float
+        %358 = OpLabel
+%shadowIndex = OpVariable %_ptr_Function_int Function %364
+        %388 = OpVariable %_ptr_Function_v2float Function %241
+        %408 = OpVariable %_ptr_Function_v2float Function %241
+%visibility_0 = OpVariable %_ptr_Function_float Function %278
+        %i_0 = OpVariable %_ptr_Function_uint Function %281
+        %359 = OpIAdd %uint %lightIndex %LightType_Spot
+        %360 = OpAccessChain %_ptr_StorageBuffer_int %lightShadowTable %LightType_Point %359
+        %361 = OpLoad %int %360
+               OpStore %shadowIndex %361
+        %365 = OpLoad %int %shadowIndex
+        %366 = OpIEqual %bool %365 %int_n1
+               OpSelectionMerge %367 None
+               OpBranchConditional %366 %368 %367
+        %368 = OpLabel
+               OpReturnValue %float_1
+        %367 = OpLabel
+        %369 = OpLoad %int %shadowIndex
+        %372 = OpVectorTimesScalar %v3float %pointToLight %float_n1
+        %370 = OpFunctionCall %int %getCubeFace %372
+        %373 = OpIAdd %int %369 %370
+               OpStore %shadowIndex %373
+        %374 = OpLoad %int %shadowIndex
+        %375 = OpAccessChain %_ptr_StorageBuffer_v4float %shadow %LightType_Point %374 %LightType_Point
+        %376 = OpLoad %v4float %375
+        %377 = OpLoad %int %shadowIndex
+        %378 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %shadow %LightType_Point %377 %LightType_Spot
+        %379 = OpLoad %mat4v4float %378
+        %380 = OpCompositeExtract %float %worldPos_0 0
+        %381 = OpCompositeExtract %float %worldPos_0 1
+        %382 = OpCompositeExtract %float %worldPos_0 2
+        %383 = OpCompositeConstruct %v4float %380 %381 %382 %float_1
+        %384 = OpMatrixTimesVector %v4float %379 %383
+        %385 = OpVectorShuffle %v2float %384 %384 0 1
+        %386 = OpCompositeExtract %float %384 3
+        %389 = OpCompositeConstruct %v2float %386 %386
+        %387 = OpFDiv %v2float %385 %389
+        %390 = OpFMul %v2float %387 %74
+        %391 = OpFAdd %v2float %390 %75
+        %392 = OpCompositeExtract %float %391 0
+        %393 = OpCompositeExtract %float %391 1
+        %394 = OpCompositeExtract %float %384 2
+        %395 = OpCompositeExtract %float %384 3
+        %396 = OpFDiv %float %394 %395
+        %397 = OpCompositeConstruct %v3float %392 %393 %396
+        %399 = OpVectorShuffle %v2float %376 %376 0 1
+        %400 = OpVectorShuffle %v2float %397 %397 0 1
+        %401 = OpVectorShuffle %v2float %376 %376 2 3
+        %402 = OpFMul %v2float %400 %401
+        %403 = OpFAdd %v2float %399 %402
+        %406 = OpLoad %51 %shadowTexture
+        %405 = OpImageQuerySizeLod %v2int %406 %int_0
+        %404 = OpConvertSToF %v2float %405
+        %409 = OpCompositeConstruct %v2float %float_1 %float_1
+        %407 = OpFDiv %v2float %409 %404
+        %410 = OpVectorShuffle %v2float %376 %376 0 1
+        %411 = OpCompositeExtract %float %410 0
+        %412 = OpCompositeExtract %float %410 1
+        %413 = OpVectorShuffle %v2float %376 %376 0 1
+        %414 = OpVectorShuffle %v2float %376 %376 2 3
+        %415 = OpFAdd %v2float %413 %414
+        %416 = OpCompositeExtract %float %415 0
+        %417 = OpCompositeExtract %float %415 1
+        %418 = OpCompositeConstruct %v4float %411 %412 %416 %417
+               OpStore %visibility_0 %float_0
+               OpStore %i_0 %LightType_Point
+               OpBranch %421
+        %421 = OpLabel
+               OpLoopMerge %422 %423 None
+               OpBranch %424
+        %424 = OpLabel
+        %426 = OpLoad %uint %i_0
+        %427 = OpULessThan %bool %426 %shadowSampleCount
+        %425 = OpLogicalNot %bool %427
+               OpSelectionMerge %428 None
+               OpBranchConditional %425 %429 %428
+        %429 = OpLabel
+               OpBranch %422
+        %428 = OpLabel
+        %430 = OpLoad %float %visibility_0
+        %432 = OpLoad %48 %shadowSampler
+        %433 = OpLoad %51 %shadowTexture
+        %434 = OpSampledImage %295 %433 %432
+        %436 = OpLoad %uint %i_0
+        %437 = OpAccessChain %_ptr_Private_v2float %shadowSampleOffsets %436
+        %438 = OpLoad %v2float %437
+        %439 = OpFMul %v2float %438 %407
+        %440 = OpFAdd %v2float %403 %439
+        %441 = OpVectorShuffle %v2float %418 %418 0 1
+        %442 = OpVectorShuffle %v2float %418 %418 2 3
+        %435 = OpExtInst %v2float %116 NClamp %440 %441 %442
+        %443 = OpCompositeExtract %float %397 2
+        %445 = OpFSub %float %443 %float_0_00999999978
+        %431 = OpImageSampleDrefExplicitLod %float %434 %435 %445 Lod %float_0
+        %446 = OpFAdd %float %430 %431
+               OpStore %visibility_0 %446
+               OpBranch %423
+        %423 = OpLabel
+        %447 = OpLoad %uint %i_0
+        %448 = OpIAdd %uint %447 %LightType_Spot
+               OpStore %i_0 %448
+               OpBranch %421
+        %422 = OpLabel
+        %449 = OpLoad %float %visibility_0
+        %450 = OpFDiv %float %449 %float_16
+               OpReturnValue %450
+               OpFunctionEnd
+%GetSurfaceInfo = OpFunction %SurfaceInfo None %451
+      %input = OpFunctionParameter %VertexOutput
+        %456 = OpLabel
+    %surface = OpVariable %_ptr_Function_SurfaceInfo Function %459
+        %462 = OpAccessChain %_ptr_Function_v3float %surface %uint_8
+        %464 = OpCompositeExtract %v3float %input 2
+        %463 = OpExtInst %v3float %116 Normalize %464
+               OpStore %462 %463
+        %466 = OpCompositeExtract %v3float %input 8
+        %467 = OpCompositeExtract %v3float %input 9
+        %468 = OpCompositeExtract %v3float %input 7
+        %469 = OpCompositeConstruct %mat3v3float %466 %467 %468
+        %471 = OpLoad %48 %normalSampler
+        %472 = OpLoad %94 %normalTexture
+        %474 = OpSampledImage %473 %472 %471
+        %475 = OpCompositeExtract %v2float %input 3
+        %470 = OpImageSampleImplicitLod %v4float %474 %475
+        %476 = OpVectorShuffle %v3float %470 %470 0 1 2
+        %478 = OpAccessChain %_ptr_Function_v3float %surface %uint_4
+        %481 = OpVectorTimesScalar %v3float %476 %float_2
+        %483 = OpFSub %v3float %481 %482
+        %484 = OpMatrixTimesVector %v3float %469 %483
+        %479 = OpExtInst %v3float %116 Normalize %484
+               OpStore %478 %479
+        %486 = OpLoad %48 %baseColorSampler
+        %487 = OpLoad %94 %baseColorTexture
+        %488 = OpSampledImage %473 %487 %486
+        %489 = OpCompositeExtract %v2float %input 3
+        %485 = OpImageSampleImplicitLod %v4float %488 %489
+        %491 = OpAccessChain %_ptr_Function_v4float %surface %LightType_Point
+        %492 = OpCompositeExtract %v4float %input 5
+        %494 = OpAccessChain %_ptr_Uniform_v4float %material %LightType_Point
+        %495 = OpLoad %v4float %494
+        %496 = OpFMul %v4float %492 %495
+        %497 = OpFMul %v4float %496 %485
+               OpStore %491 %497
+        %499 = OpAccessChain %_ptr_Function_float %surface %LightType_Point %uint_3
+        %500 = OpLoad %float %499
+        %501 = OpAccessChain %_ptr_Uniform_float %material %uint_4
+        %502 = OpLoad %float %501
+        %503 = OpFOrdLessThan %bool %500 %502
+               OpSelectionMerge %504 None
+               OpBranchConditional %503 %505 %504
+        %505 = OpLabel
+               OpKill
+        %504 = OpLabel
+        %506 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %507 = OpAccessChain %_ptr_Function_v4float %surface %LightType_Point
+        %508 = OpLoad %v4float %507
+        %509 = OpVectorShuffle %v3float %508 %508 0 1 2
+               OpStore %506 %509
+        %511 = OpLoad %48 %metallicRoughnessSampler
+        %512 = OpLoad %94 %metallicRoughnessTexture
+        %513 = OpSampledImage %473 %512 %511
+        %514 = OpCompositeExtract %v2float %input 3
+        %510 = OpImageSampleImplicitLod %v4float %513 %514
+        %515 = OpAccessChain %_ptr_Function_float %surface %LightType_Directional
+        %516 = OpAccessChain %_ptr_Uniform_float %material %uint_3 %LightType_Point
+        %517 = OpLoad %float %516
+        %518 = OpCompositeExtract %float %510 2
+        %519 = OpFMul %float %517 %518
+               OpStore %515 %519
+        %520 = OpAccessChain %_ptr_Function_float %surface %uint_3
+        %521 = OpAccessChain %_ptr_Uniform_float %material %uint_3 %LightType_Spot
+        %522 = OpLoad %float %521
+        %523 = OpCompositeExtract %float %510 1
+        %524 = OpFMul %float %522 %523
+               OpStore %520 %524
+        %527 = OpAccessChain %_ptr_Function_v3float %surface %uint_5
+        %529 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %530 = OpLoad %v3float %529
+        %531 = OpAccessChain %_ptr_Function_float %surface %LightType_Directional
+        %532 = OpLoad %float %531
+        %533 = OpCompositeConstruct %v3float %532 %532 %532
+        %528 = OpExtInst %v3float %116 FMix %526 %530 %533
+               OpStore %527 %528
+        %535 = OpLoad %48 %occlusionSampler
+        %536 = OpLoad %94 %occlusionTexture
+        %537 = OpSampledImage %473 %536 %535
+        %538 = OpCompositeExtract %v2float %input 3
+        %534 = OpImageSampleImplicitLod %v4float %537 %538
+        %539 = OpAccessChain %_ptr_Function_float %surface %uint_6
+        %540 = OpAccessChain %_ptr_Uniform_float %material %LightType_Directional
+        %541 = OpLoad %float %540
+        %542 = OpCompositeExtract %float %534 0
+        %543 = OpFMul %float %541 %542
+               OpStore %539 %543
+        %545 = OpLoad %48 %emissiveSampler
+        %546 = OpLoad %94 %emissiveTexture
+        %547 = OpSampledImage %473 %546 %545
+        %548 = OpCompositeExtract %v2float %input 3
+        %544 = OpImageSampleImplicitLod %v4float %547 %548
+        %549 = OpAccessChain %_ptr_Function_v3float %surface %uint_7
+        %551 = OpAccessChain %_ptr_Uniform_v3float %material %LightType_Spot
+        %552 = OpLoad %v3float %551
+        %553 = OpVectorShuffle %v3float %544 %544 0 1 2
+        %554 = OpFMul %v3float %552 %553
+               OpStore %549 %554
+        %555 = OpCompositeExtract %v4float %input 6
+        %556 = OpCompositeExtract %float %555 3
+        %557 = OpFOrdEqual %bool %556 %float_0
+               OpSelectionMerge %558 None
+               OpBranchConditional %557 %559 %560
+        %559 = OpLabel
+        %561 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %562 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %563 = OpLoad %v3float %562
+        %564 = OpCompositeExtract %v4float %input 6
+        %565 = OpVectorShuffle %v3float %564 %564 0 1 2
+        %566 = OpFAdd %v3float %563 %565
+               OpStore %561 %566
+               OpBranch %558
+        %560 = OpLabel
+        %567 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %568 = OpAccessChain %_ptr_Function_v3float %surface %LightType_Spot
+        %569 = OpLoad %v3float %568
+        %570 = OpCompositeExtract %v4float %input 6
+        %571 = OpVectorShuffle %v3float %570 %570 0 1 2
+        %572 = OpFMul %v3float %569 %571
+               OpStore %567 %572
+               OpBranch %558
+        %558 = OpLabel
+        %573 = OpLoad %SurfaceInfo %surface
+               OpReturnValue %573
+               OpFunctionEnd
+%FresnelSchlick = OpFunction %v3float None %574
+   %cosTheta = OpFunctionParameter %float
+         %F0 = OpFunctionParameter %v3float
+        %578 = OpLabel
+        %579 = OpFSub %v3float %482 %F0
+        %581 = OpFSub %float %float_1 %cosTheta
+        %580 = OpExtInst %float %116 Pow %581 %float_5
+        %583 = OpVectorTimesScalar %v3float %579 %580
+        %584 = OpFAdd %v3float %F0 %583
+               OpReturnValue %584
+               OpFunctionEnd
+%DistributionGGX = OpFunction %float None %585
+          %N = OpFunctionParameter %v3float
+          %H = OpFunctionParameter %v3float
+  %roughness = OpFunctionParameter %float
+        %590 = OpLabel
+        %591 = OpFMul %float %roughness %roughness
+        %592 = OpFMul %float %591 %591
+        %594 = OpDot %float %N %H
+        %593 = OpExtInst %float %116 NMax %594 %float_0
+        %595 = OpFMul %float %593 %593
+        %596 = OpFSub %float %592 %float_1
+        %597 = OpFMul %float %595 %596
+        %598 = OpFAdd %float %597 %float_1
+        %599 = OpFMul %float %PI %598
+        %600 = OpFMul %float %599 %598
+        %601 = OpFDiv %float %592 %600
+               OpReturnValue %601
+               OpFunctionEnd
+%GeometrySchlickGGX = OpFunction %float None %602
+      %NdotV = OpFunctionParameter %float
+%roughness_0 = OpFunctionParameter %float
+        %606 = OpLabel
+        %607 = OpFAdd %float %roughness_0 %float_1
+        %608 = OpFMul %float %607 %607
+        %610 = OpFDiv %float %608 %float_8
+        %611 = OpFSub %float %float_1 %610
+        %612 = OpFMul %float %NdotV %611
+        %613 = OpFAdd %float %612 %610
+        %614 = OpFDiv %float %NdotV %613
+               OpReturnValue %614
+               OpFunctionEnd
+%GeometrySmith = OpFunction %float None %615
+        %N_0 = OpFunctionParameter %v3float
+          %V = OpFunctionParameter %v3float
+          %L = OpFunctionParameter %v3float
+%roughness_1 = OpFunctionParameter %float
+        %621 = OpLabel
+        %623 = OpDot %float %N_0 %V
+        %622 = OpExtInst %float %116 NMax %623 %float_0
+        %625 = OpDot %float %N_0 %L
+        %624 = OpExtInst %float %116 NMax %625 %float_0
+        %626 = OpFunctionCall %float %GeometrySchlickGGX %622 %roughness_1
+        %627 = OpFunctionCall %float %GeometrySchlickGGX %624 %roughness_1
+        %628 = OpFMul %float %627 %626
+               OpReturnValue %628
+               OpFunctionEnd
+%lightAttenuation = OpFunction %float None %629
+      %light = OpFunctionParameter %PuctualLight
+        %633 = OpLabel
+        %634 = OpCompositeExtract %uint %light 0
+        %635 = OpIEqual %bool %634 %LightType_Directional
+               OpSelectionMerge %636 None
+               OpBranchConditional %635 %637 %636
+        %637 = OpLabel
+               OpReturnValue %float_1
+        %636 = OpLabel
+        %639 = OpCompositeExtract %v3float %light 1
+        %638 = OpExtInst %float %116 Length %639
+        %640 = OpCompositeExtract %float %light 2
+        %641 = OpFOrdLessThanEqual %bool %640 %float_0
+               OpSelectionMerge %642 None
+               OpBranchConditional %641 %643 %642
+        %643 = OpLabel
+        %644 = OpExtInst %float %116 Pow %638 %float_2
+        %645 = OpFDiv %float %float_1 %644
+               OpReturnValue %645
+        %642 = OpLabel
+        %648 = OpCompositeExtract %float %light 2
+        %649 = OpFDiv %float %638 %648
+        %647 = OpExtInst %float %116 Pow %649 %float_4
+        %651 = OpFSub %float %float_1 %647
+        %646 = OpExtInst %float %116 NClamp %651 %float_0 %float_1
+        %652 = OpExtInst %float %116 Pow %638 %float_2
+        %653 = OpFDiv %float %646 %652
+               OpReturnValue %653
+               OpFunctionEnd
+%lightRadiance = OpFunction %v3float None %654
+    %light_0 = OpFunctionParameter %PuctualLight
+  %surface_0 = OpFunctionParameter %SurfaceInfo
+        %658 = OpLabel
+        %660 = OpCompositeExtract %v3float %light_0 1
+        %659 = OpExtInst %v3float %116 Normalize %660
+        %662 = OpCompositeExtract %v3float %surface_0 8
+        %663 = OpFAdd %v3float %662 %659
+        %661 = OpExtInst %v3float %116 Normalize %663
+        %665 = OpCompositeExtract %v3float %surface_0 4
+        %666 = OpCompositeExtract %float %surface_0 3
+        %664 = OpFunctionCall %float %DistributionGGX %665 %661 %666
+        %668 = OpCompositeExtract %v3float %surface_0 4
+        %669 = OpCompositeExtract %v3float %surface_0 8
+        %670 = OpCompositeExtract %float %surface_0 3
+        %667 = OpFunctionCall %float %GeometrySmith %668 %669 %659 %670
+        %674 = OpCompositeExtract %v3float %surface_0 8
+        %673 = OpDot %float %661 %674
+        %672 = OpExtInst %float %116 NMax %673 %float_0
+        %675 = OpCompositeExtract %v3float %surface_0 5
+        %671 = OpFunctionCall %v3float %FresnelSchlick %672 %675
+        %676 = OpFSub %v3float %482 %671
+        %677 = OpCompositeExtract %float %surface_0 2
+        %678 = OpFSub %float %float_1 %677
+        %679 = OpVectorTimesScalar %v3float %676 %678
+        %682 = OpCompositeExtract %v3float %surface_0 4
+        %681 = OpDot %float %682 %659
+        %680 = OpExtInst %float %116 NMax %681 %float_0
+        %683 = OpFMul %float %664 %667
+        %684 = OpVectorTimesScalar %v3float %671 %683
+        %688 = OpCompositeExtract %v3float %surface_0 4
+        %689 = OpCompositeExtract %v3float %surface_0 8
+        %687 = OpDot %float %688 %689
+        %686 = OpExtInst %float %116 NMax %687 %float_0
+        %690 = OpFMul %float %float_4 %686
+        %691 = OpFMul %float %690 %680
+        %685 = OpExtInst %float %116 NMax %691 %float_0_00100000005
+        %693 = OpCompositeConstruct %v3float %685 %685 %685
+        %694 = OpFDiv %v3float %684 %693
+        %695 = OpCompositeExtract %v3float %light_0 3
+        %696 = OpCompositeExtract %float %light_0 4
+        %697 = OpVectorTimesScalar %v3float %695 %696
+        %698 = OpFunctionCall %float %lightAttenuation %light_0
+        %699 = OpVectorTimesScalar %v3float %697 %698
+        %700 = OpCompositeExtract %v3float %surface_0 1
+        %701 = OpFMul %v3float %679 %700
+        %703 = OpFDiv %v3float %701 %702
+        %704 = OpFAdd %v3float %703 %694
+        %705 = OpFMul %v3float %704 %699
+        %706 = OpVectorTimesScalar %v3float %705 %680
+               OpReturnValue %706
+               OpFunctionEnd
+%fragmentMain_inner = OpFunction %FragmentOutput None %707
+    %input_0 = OpFunctionParameter %VertexOutput
+        %711 = OpLabel
+         %Lo = OpVariable %_ptr_Function_v3float Function %715
+    %light_1 = OpVariable %_ptr_Function_PuctualLight Function %724
+%lightIndex_0 = OpVariable %_ptr_Function_uint Function %281
+    %light_2 = OpVariable %_ptr_Function_PuctualLight Function %724
+        %out = OpVariable %_ptr_Function_FragmentOutput Function %818
+        %712 = OpFunctionCall %SurfaceInfo %GetSurfaceInfo %input_0
+               OpStore %Lo %713
+        %717 = OpAccessChain %_ptr_StorageBuffer_float %globalLights %LightType_Directional
+        %718 = OpLoad %float %717
+        %719 = OpFOrdGreaterThan %bool %718 %float_0
+               OpSelectionMerge %720 None
+               OpBranchConditional %719 %721 %720
+        %721 = OpLabel
+        %725 = OpAccessChain %_ptr_Function_uint %light_1 %LightType_Point
+               OpStore %725 %LightType_Directional
+        %726 = OpAccessChain %_ptr_Function_v3float %light_1 %LightType_Spot
+        %728 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %uint_3
+        %729 = OpLoad %v3float %728
+               OpStore %726 %729
+        %730 = OpAccessChain %_ptr_Function_v3float %light_1 %uint_3
+        %731 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %LightType_Spot
+        %732 = OpLoad %v3float %731
+               OpStore %730 %732
+        %733 = OpAccessChain %_ptr_Function_float %light_1 %uint_4
+        %734 = OpAccessChain %_ptr_StorageBuffer_float %globalLights %LightType_Directional
+        %735 = OpLoad %float %734
+               OpStore %733 %735
+        %737 = OpCompositeExtract %v3float %input_0 1
+        %736 = OpFunctionCall %float %dirLightVisibility %737
+        %738 = OpLoad %v3float %Lo
+        %740 = OpLoad %PuctualLight %light_1
+        %739 = OpFunctionCall %v3float %lightRadiance %740 %712
+        %741 = OpVectorTimesScalar %v3float %739 %736
+        %742 = OpFAdd %v3float %738 %741
+               OpStore %Lo %742
+               OpBranch %720
+        %720 = OpLabel
+        %744 = OpCompositeExtract %v4float %input_0 0
+        %743 = OpFunctionCall %uint %getClusterIndex %744
+        %746 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %LightType_Spot %743 %LightType_Point
+        %747 = OpLoad %uint %746
+        %748 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %LightType_Spot %743 %LightType_Spot
+        %749 = OpLoad %uint %748
+               OpStore %lightIndex_0 %LightType_Point
+               OpBranch %751
+        %751 = OpLabel
+               OpLoopMerge %752 %753 None
+               OpBranch %754
+        %754 = OpLabel
+        %756 = OpLoad %uint %lightIndex_0
+        %757 = OpULessThan %bool %756 %749
+        %755 = OpLogicalNot %bool %757
+               OpSelectionMerge %758 None
+               OpBranchConditional %755 %759 %758
+        %759 = OpLabel
+               OpBranch %752
+        %758 = OpLabel
+        %760 = OpLoad %uint %lightIndex_0
+        %761 = OpIAdd %uint %747 %760
+        %762 = OpAccessChain %_ptr_StorageBuffer_uint %clusterLights %LightType_Directional %761
+        %763 = OpLoad %uint %762
+        %765 = OpAccessChain %_ptr_Function_uint %light_2 %LightType_Point
+               OpStore %765 %LightType_Point
+        %766 = OpAccessChain %_ptr_Function_v3float %light_2 %LightType_Spot
+        %767 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %uint_5 %763 %LightType_Point
+        %768 = OpLoad %v3float %767
+        %769 = OpVectorShuffle %v3float %768 %768 0 1 2
+        %770 = OpCompositeExtract %v3float %input_0 1
+        %771 = OpFSub %v3float %769 %770
+               OpStore %766 %771
+        %772 = OpAccessChain %_ptr_Function_float %light_2 %LightType_Directional
+        %773 = OpAccessChain %_ptr_StorageBuffer_float %globalLights %uint_5 %763 %LightType_Spot
+        %774 = OpLoad %float %773
+               OpStore %772 %774
+        %775 = OpAccessChain %_ptr_Function_v3float %light_2 %uint_3
+        %776 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %uint_5 %763 %LightType_Directional
+        %777 = OpLoad %v3float %776
+               OpStore %775 %777
+        %778 = OpAccessChain %_ptr_Function_float %light_2 %uint_4
+        %779 = OpAccessChain %_ptr_StorageBuffer_float %globalLights %uint_5 %763 %uint_3
+        %780 = OpLoad %float %779
+               OpStore %778 %780
+        %782 = OpCompositeExtract %v3float %input_0 1
+        %783 = OpAccessChain %_ptr_Function_v3float %light_2 %LightType_Spot
+        %784 = OpLoad %v3float %783
+        %781 = OpFunctionCall %float %pointLightVisibility %763 %782 %784
+        %785 = OpLoad %v3float %Lo
+        %787 = OpLoad %PuctualLight %light_2
+        %786 = OpFunctionCall %v3float %lightRadiance %787 %712
+        %788 = OpVectorTimesScalar %v3float %786 %781
+        %789 = OpFAdd %v3float %785 %788
+               OpStore %Lo %789
+               OpBranch %753
+        %753 = OpLabel
+        %790 = OpLoad %uint %lightIndex_0
+        %791 = OpIAdd %uint %790 %LightType_Spot
+               OpStore %lightIndex_0 %791
+               OpBranch %751
+        %752 = OpLabel
+        %792 = OpCompositeExtract %v4float %input_0 0
+        %793 = OpVectorShuffle %v2float %792 %792 0 1
+        %796 = OpLoad %94 %ssaoTexture
+        %795 = OpImageQuerySizeLod %v2int %796 %int_0
+        %797 = OpVectorShuffle %v2int %795 %795 0 1
+        %794 = OpConvertSToF %v2float %797
+        %798 = OpFDiv %v2float %793 %794
+        %800 = OpLoad %48 %defaultSampler
+        %801 = OpLoad %94 %ssaoTexture
+        %802 = OpSampledImage %473 %801 %800
+        %799 = OpImageSampleImplicitLod %v4float %802 %798
+        %803 = OpCompositeExtract %float %799 0
+        %804 = OpAccessChain %_ptr_StorageBuffer_v3float %globalLights %LightType_Point
+        %805 = OpLoad %v3float %804
+        %806 = OpCompositeExtract %v3float %712 1
+        %807 = OpFMul %v3float %805 %806
+        %808 = OpCompositeExtract %float %712 6
+        %809 = OpVectorTimesScalar %v3float %807 %808
+        %810 = OpVectorTimesScalar %v3float %809 %803
+        %812 = OpLoad %v3float %Lo
+        %813 = OpFAdd %v3float %812 %810
+        %814 = OpCompositeExtract %v3float %712 7
+        %815 = OpFAdd %v3float %813 %814
+        %811 = OpFunctionCall %v3float %linearTosRGB %815
+        %819 = OpAccessChain %_ptr_Function_v4float %out %LightType_Point
+        %820 = OpCompositeExtract %float %811 0
+        %821 = OpCompositeExtract %float %811 1
+        %822 = OpCompositeExtract %float %811 2
+        %823 = OpCompositeExtract %v4float %712 0
+        %824 = OpCompositeExtract %float %823 3
+        %825 = OpCompositeConstruct %v4float %820 %821 %822 %824
+               OpStore %819 %825
+        %826 = OpAccessChain %_ptr_Function_v4float %out %LightType_Spot
+        %827 = OpCompositeExtract %v3float %712 7
+        %828 = OpCompositeExtract %float %827 0
+        %829 = OpCompositeExtract %float %827 1
+        %830 = OpCompositeExtract %float %827 2
+        %831 = OpCompositeExtract %v4float %712 0
+        %832 = OpCompositeExtract %float %831 3
+        %833 = OpCompositeConstruct %v4float %828 %829 %830 %832
+               OpStore %826 %833
+        %834 = OpLoad %FragmentOutput %out
+               OpReturnValue %834
+               OpFunctionEnd
+%fragmentMain = OpFunction %void None %835
+        %838 = OpLabel
+        %840 = OpLoad %v4float %position_1
+        %841 = OpLoad %v3float %worldPos_1
+        %842 = OpLoad %v3float %view_1
+        %843 = OpLoad %v2float %texcoord_1
+        %844 = OpLoad %v2float %texcoord2_1
+        %845 = OpLoad %v4float %color_1
+        %846 = OpLoad %v4float %instanceColor_1
+        %847 = OpLoad %v3float %normal_1
+        %848 = OpLoad %v3float %tangent_1
+        %849 = OpLoad %v3float %bitangent_1
+        %850 = OpCompositeConstruct %VertexOutput %840 %841 %842 %843 %844 %845 %846 %847 %848 %849
+        %839 = OpFunctionCall %FragmentOutput %fragmentMain_inner %850
+        %851 = OpCompositeExtract %v4float %839 0
+               OpStore %color_2 %851
+        %852 = OpCompositeExtract %v4float %839 1
+               OpStore %emissive_1 %852
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.wgsl b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.wgsl
new file mode 100644
index 0000000..a38a624
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-fragment.wgsl.expected.wgsl
@@ -0,0 +1,372 @@
+benchmark/skinned-shadowed-pbr-fragment.wgsl:51:13 warning: use of deprecated language feature: the @stride attribute is deprecated; use a larger type if necessary
+  lights : @stride(32) array<Light>;
+            ^^^^^^
+
+let GAMMA = 2.200000048;
+
+fn linearTosRGB(linear : vec3<f32>) -> vec3<f32> {
+  let INV_GAMMA = (1.0 / GAMMA);
+  return pow(linear, vec3(INV_GAMMA));
+}
+
+fn sRGBToLinear(srgb : vec3<f32>) -> vec3<f32> {
+  return pow(srgb, vec3(GAMMA));
+}
+
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@binding(0) @group(0) var<uniform> camera : Camera;
+
+struct ClusterLights {
+  offset : u32;
+  count : u32;
+}
+
+struct ClusterLightGroup {
+  offset : u32;
+  lights : array<ClusterLights, 27648>;
+  indices : array<u32, 1769472>;
+}
+
+@binding(1) @group(0) var<storage, read> clusterLights : ClusterLightGroup;
+
+struct Light {
+  position : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+struct GlobalLights {
+  ambient : vec3<f32>;
+  dirColor : vec3<f32>;
+  dirIntensity : f32;
+  dirDirection : vec3<f32>;
+  lightCount : u32;
+  lights : @stride(32) array<Light>;
+}
+
+@binding(2) @group(0) var<storage, read> globalLights : GlobalLights;
+
+let tileCount = vec3(32u, 18u, 48u);
+
+fn linearDepth(depthSample : f32) -> f32 {
+  return ((camera.zFar * camera.zNear) / fma(depthSample, (camera.zNear - camera.zFar), camera.zFar));
+}
+
+fn getTile(fragCoord : vec4<f32>) -> vec3<u32> {
+  let sliceScale = (f32(tileCount.z) / log2((camera.zFar / camera.zNear)));
+  let sliceBias = -(((f32(tileCount.z) * log2(camera.zNear)) / log2((camera.zFar / camera.zNear))));
+  let zTile = u32(max(((log2(linearDepth(fragCoord.z)) * sliceScale) + sliceBias), 0.0));
+  return vec3(u32((fragCoord.x / (camera.outputSize.x / f32(tileCount.x)))), u32((fragCoord.y / (camera.outputSize.y / f32(tileCount.y)))), zTile);
+}
+
+fn getClusterIndex(fragCoord : vec4<f32>) -> u32 {
+  let tile = getTile(fragCoord);
+  return ((tile.x + (tile.y * tileCount.x)) + ((tile.z * tileCount.x) * tileCount.y));
+}
+
+@binding(3) @group(0) var defaultSampler : sampler;
+
+@binding(4) @group(0) var shadowTexture : texture_depth_2d;
+
+@binding(5) @group(0) var shadowSampler : sampler_comparison;
+
+struct LightShadowTable {
+  light : array<i32>;
+}
+
+@binding(6) @group(0) var<storage, read> lightShadowTable : LightShadowTable;
+
+var<private> shadowSampleOffsets : array<vec2<f32>, 16> = array<vec2<f32>, 16>(vec2(-1.5, -1.5), vec2(-1.5, -0.5), vec2(-1.5, 0.5), vec2(-1.5, 1.5), vec2(-0.5, -1.5), vec2(-0.5, -0.5), vec2(-0.5, 0.5), vec2(-0.5, 1.5), vec2(0.5, -1.5), vec2(0.5, -0.5), vec2(0.5, 0.5), vec2(0.5, 1.5), vec2(1.5, -1.5), vec2(1.5, -0.5), vec2(1.5, 0.5), vec2(1.5, 1.5));
+
+let shadowSampleCount = 16u;
+
+struct ShadowProperties {
+  viewport : vec4<f32>;
+  viewProj : mat4x4<f32>;
+}
+
+struct LightShadows {
+  properties : array<ShadowProperties>;
+}
+
+@binding(7) @group(0) var<storage, read> shadow : LightShadows;
+
+fn dirLightVisibility(worldPos : vec3<f32>) -> f32 {
+  let shadowIndex = lightShadowTable.light[0u];
+  if ((shadowIndex == -1)) {
+    return 1.0;
+  }
+  let viewport = shadow.properties[shadowIndex].viewport;
+  let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0));
+  let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w));
+  let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0)));
+  let clampRect = vec4((viewport.xy - texelSize), ((viewport.xy + viewport.zw) + texelSize));
+  var visibility = 0.0;
+  for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.003)));
+  }
+  return (visibility / f32(shadowSampleCount));
+}
+
+fn getCubeFace(v : vec3<f32>) -> i32 {
+  let vAbs = abs(v);
+  if (((vAbs.z >= vAbs.x) && (vAbs.z >= vAbs.y))) {
+    if ((v.z < 0.0)) {
+      return 5;
+    }
+    return 4;
+  }
+  if ((vAbs.y >= vAbs.x)) {
+    if ((v.y < 0.0)) {
+      return 3;
+    }
+    return 2;
+  }
+  if ((v.x < 0.0)) {
+    return 1;
+  }
+  return 0;
+}
+
+fn pointLightVisibility(lightIndex : u32, worldPos : vec3<f32>, pointToLight : vec3<f32>) -> f32 {
+  var shadowIndex = lightShadowTable.light[(lightIndex + 1u)];
+  if ((shadowIndex == -1)) {
+    return 1.0;
+  }
+  shadowIndex = (shadowIndex + getCubeFace((pointToLight * -1.0)));
+  let viewport = shadow.properties[shadowIndex].viewport;
+  let lightPos = (shadow.properties[shadowIndex].viewProj * vec4(worldPos, 1.0));
+  let shadowPos = vec3((((lightPos.xy / lightPos.w) * vec2(0.5, -0.5)) + vec2(0.5, 0.5)), (lightPos.z / lightPos.w));
+  let viewportPos = vec2((viewport.xy + (shadowPos.xy * viewport.zw)));
+  let texelSize = (1.0 / vec2<f32>(textureDimensions(shadowTexture, 0)));
+  let clampRect = vec4(viewport.xy, (viewport.xy + viewport.zw));
+  var visibility = 0.0;
+  for(var i = 0u; (i < shadowSampleCount); i = (i + 1u)) {
+    visibility = (visibility + textureSampleCompareLevel(shadowTexture, shadowSampler, clamp((viewportPos + (shadowSampleOffsets[i] * texelSize)), clampRect.xy, clampRect.zw), (shadowPos.z - 0.01)));
+  }
+  return (visibility / f32(shadowSampleCount));
+}
+
+struct VertexOutput {
+  @builtin(position)
+  position : vec4<f32>;
+  @location(0)
+  worldPos : vec3<f32>;
+  @location(1)
+  view : vec3<f32>;
+  @location(2)
+  texcoord : vec2<f32>;
+  @location(3)
+  texcoord2 : vec2<f32>;
+  @location(4)
+  color : vec4<f32>;
+  @location(5)
+  instanceColor : vec4<f32>;
+  @location(6)
+  normal : vec3<f32>;
+  @location(7)
+  tangent : vec3<f32>;
+  @location(8)
+  bitangent : vec3<f32>;
+}
+
+struct Material {
+  baseColorFactor : vec4<f32>;
+  emissiveFactor : vec3<f32>;
+  occlusionStrength : f32;
+  metallicRoughnessFactor : vec2<f32>;
+  alphaCutoff : f32;
+}
+
+@binding(8) @group(0) var<uniform> material : Material;
+
+@binding(9) @group(0) var baseColorTexture : texture_2d<f32>;
+
+@binding(10) @group(0) var baseColorSampler : sampler;
+
+@binding(11) @group(0) var normalTexture : texture_2d<f32>;
+
+@binding(12) @group(0) var normalSampler : sampler;
+
+@binding(13) @group(0) var metallicRoughnessTexture : texture_2d<f32>;
+
+@binding(14) @group(0) var metallicRoughnessSampler : sampler;
+
+@binding(15) @group(0) var occlusionTexture : texture_2d<f32>;
+
+@binding(16) @group(0) var occlusionSampler : sampler;
+
+@binding(17) @group(0) var emissiveTexture : texture_2d<f32>;
+
+@binding(18) @group(0) var emissiveSampler : sampler;
+
+struct SurfaceInfo {
+  baseColor : vec4<f32>;
+  albedo : vec3<f32>;
+  metallic : f32;
+  roughness : f32;
+  normal : vec3<f32>;
+  f0 : vec3<f32>;
+  ao : f32;
+  emissive : vec3<f32>;
+  v : vec3<f32>;
+}
+
+fn GetSurfaceInfo(input : VertexOutput) -> SurfaceInfo {
+  var surface : SurfaceInfo;
+  surface.v = normalize(input.view);
+  let tbn = mat3x3(input.tangent, input.bitangent, input.normal);
+  let normalMap = textureSample(normalTexture, normalSampler, input.texcoord).rgb;
+  surface.normal = normalize((tbn * ((2.0 * normalMap) - vec3(1.0))));
+  let baseColorMap = textureSample(baseColorTexture, baseColorSampler, input.texcoord);
+  surface.baseColor = ((input.color * material.baseColorFactor) * baseColorMap);
+  if ((surface.baseColor.a < material.alphaCutoff)) {
+    discard;
+  }
+  surface.albedo = surface.baseColor.rgb;
+  let metallicRoughnessMap = textureSample(metallicRoughnessTexture, metallicRoughnessSampler, input.texcoord);
+  surface.metallic = (material.metallicRoughnessFactor.x * metallicRoughnessMap.b);
+  surface.roughness = (material.metallicRoughnessFactor.y * metallicRoughnessMap.g);
+  let dielectricSpec = vec3(0.039999999);
+  surface.f0 = mix(dielectricSpec, surface.albedo, vec3(surface.metallic));
+  let occlusionMap = textureSample(occlusionTexture, occlusionSampler, input.texcoord);
+  surface.ao = (material.occlusionStrength * occlusionMap.r);
+  let emissiveMap = textureSample(emissiveTexture, emissiveSampler, input.texcoord);
+  surface.emissive = (material.emissiveFactor * emissiveMap.rgb);
+  if ((input.instanceColor.a == 0.0)) {
+    surface.albedo = (surface.albedo + input.instanceColor.rgb);
+  } else {
+    surface.albedo = (surface.albedo * input.instanceColor.rgb);
+  }
+  return surface;
+}
+
+let PI = 3.141592741;
+
+let LightType_Point = 0u;
+
+let LightType_Spot = 1u;
+
+let LightType_Directional = 2u;
+
+struct PuctualLight {
+  lightType : u32;
+  pointToLight : vec3<f32>;
+  range : f32;
+  color : vec3<f32>;
+  intensity : f32;
+}
+
+fn FresnelSchlick(cosTheta : f32, F0 : vec3<f32>) -> vec3<f32> {
+  return (F0 + ((vec3(1.0) - F0) * pow((1.0 - cosTheta), 5.0)));
+}
+
+fn DistributionGGX(N : vec3<f32>, H : vec3<f32>, roughness : f32) -> f32 {
+  let a = (roughness * roughness);
+  let a2 = (a * a);
+  let NdotH = max(dot(N, H), 0.0);
+  let NdotH2 = (NdotH * NdotH);
+  let num = a2;
+  let denom = ((NdotH2 * (a2 - 1.0)) + 1.0);
+  return (num / ((PI * denom) * denom));
+}
+
+fn GeometrySchlickGGX(NdotV : f32, roughness : f32) -> f32 {
+  let r = (roughness + 1.0);
+  let k = ((r * r) / 8.0);
+  let num = NdotV;
+  let denom = ((NdotV * (1.0 - k)) + k);
+  return (num / denom);
+}
+
+fn GeometrySmith(N : vec3<f32>, V : vec3<f32>, L : vec3<f32>, roughness : f32) -> f32 {
+  let NdotV = max(dot(N, V), 0.0);
+  let NdotL = max(dot(N, L), 0.0);
+  let ggx2 = GeometrySchlickGGX(NdotV, roughness);
+  let ggx1 = GeometrySchlickGGX(NdotL, roughness);
+  return (ggx1 * ggx2);
+}
+
+fn lightAttenuation(light : PuctualLight) -> f32 {
+  if ((light.lightType == LightType_Directional)) {
+    return 1.0;
+  }
+  let distance = length(light.pointToLight);
+  if ((light.range <= 0.0)) {
+    return (1.0 / pow(distance, 2.0));
+  }
+  return (clamp((1.0 - pow((distance / light.range), 4.0)), 0.0, 1.0) / pow(distance, 2.0));
+}
+
+fn lightRadiance(light : PuctualLight, surface : SurfaceInfo) -> vec3<f32> {
+  let L = normalize(light.pointToLight);
+  let H = normalize((surface.v + L));
+  let NDF = DistributionGGX(surface.normal, H, surface.roughness);
+  let G = GeometrySmith(surface.normal, surface.v, L, surface.roughness);
+  let F = FresnelSchlick(max(dot(H, surface.v), 0.0), surface.f0);
+  let kD = ((vec3(1.0) - F) * (1.0 - surface.metallic));
+  let NdotL = max(dot(surface.normal, L), 0.0);
+  let numerator = ((NDF * G) * F);
+  let denominator = max(((4.0 * max(dot(surface.normal, surface.v), 0.0)) * NdotL), 0.001);
+  let specular = (numerator / vec3(denominator));
+  let radiance = ((light.color * light.intensity) * lightAttenuation(light));
+  return (((((kD * surface.albedo) / vec3(PI)) + specular) * radiance) * NdotL);
+}
+
+@binding(19) @group(0) var ssaoTexture : texture_2d<f32>;
+
+struct FragmentOutput {
+  @location(0)
+  color : vec4<f32>;
+  @location(1)
+  emissive : vec4<f32>;
+}
+
+@stage(fragment)
+fn fragmentMain(input : VertexOutput) -> FragmentOutput {
+  let surface = GetSurfaceInfo(input);
+  var Lo = vec3(0.0, 0.0, 0.0);
+  if ((globalLights.dirIntensity > 0.0)) {
+    var light : PuctualLight;
+    light.lightType = LightType_Directional;
+    light.pointToLight = globalLights.dirDirection;
+    light.color = globalLights.dirColor;
+    light.intensity = globalLights.dirIntensity;
+    let lightVis = dirLightVisibility(input.worldPos);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  let clusterIndex = getClusterIndex(input.position);
+  let lightOffset = clusterLights.lights[clusterIndex].offset;
+  let lightCount = clusterLights.lights[clusterIndex].count;
+  for(var lightIndex = 0u; (lightIndex < lightCount); lightIndex = (lightIndex + 1u)) {
+    let i = clusterLights.indices[(lightOffset + lightIndex)];
+    var light : PuctualLight;
+    light.lightType = LightType_Point;
+    light.pointToLight = (globalLights.lights[i].position.xyz - input.worldPos);
+    light.range = globalLights.lights[i].range;
+    light.color = globalLights.lights[i].color;
+    light.intensity = globalLights.lights[i].intensity;
+    let lightVis = pointLightVisibility(i, input.worldPos, light.pointToLight);
+    Lo = (Lo + (lightRadiance(light, surface) * lightVis));
+  }
+  let ssaoCoord = (input.position.xy / vec2<f32>(textureDimensions(ssaoTexture).xy));
+  let ssaoFactor = textureSample(ssaoTexture, defaultSampler, ssaoCoord).r;
+  let ambient = (((globalLights.ambient * surface.albedo) * surface.ao) * ssaoFactor);
+  let color = linearTosRGB(((Lo + ambient) + surface.emissive));
+  var out : FragmentOutput;
+  out.color = vec4(color, surface.baseColor.a);
+  out.emissive = vec4(surface.emissive, surface.baseColor.a);
+  return out;
+}
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl
new file mode 100644
index 0000000..5230459
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl
@@ -0,0 +1,98 @@
+struct VertexInput {
+  @location(0)
+  position : vec4<f32>;
+  @location(1)
+  normal : vec3<f32>;
+  @location(2)
+  tangent : vec4<f32>;
+  @location(3)
+  texcoord : vec2<f32>;
+  @location(6)
+  joints : vec4<u32>;
+  @location(7)
+  weights : vec4<f32>;
+  @location(8)
+  instance0 : vec4<f32>;
+  @location(9)
+  instance1 : vec4<f32>;
+  @location(10)
+  instance2 : vec4<f32>;
+  @location(11)
+  instance3 : vec4<f32>;
+  @location(12)
+  instanceColor : vec4<f32>;
+}
+
+struct VertexOutput {
+  @builtin(position)
+  position : vec4<f32>;
+  @location(0)
+  worldPos : vec3<f32>;
+  @location(1)
+  view : vec3<f32>;
+  @location(2)
+  texcoord : vec2<f32>;
+  @location(3)
+  texcoord2 : vec2<f32>;
+  @location(4)
+  color : vec4<f32>;
+  @location(5)
+  instanceColor : vec4<f32>;
+  @location(6)
+  normal : vec3<f32>;
+  @location(7)
+  tangent : vec3<f32>;
+  @location(8)
+  bitangent : vec3<f32>;
+}
+
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@binding(0) @group(0) var<uniform> camera : Camera;
+
+fn getInstanceMatrix(input : VertexInput) -> mat4x4<f32> {
+  return mat4x4(input.instance0, input.instance1, input.instance2, input.instance3);
+}
+
+struct Joints {
+  matrices : array<mat4x4<f32>>;
+}
+
+@binding(1) @group(0) var<storage, read> joint : Joints;
+
+@binding(2) @group(0) var<storage, read> inverseBind : Joints;
+
+fn getSkinMatrix(input : VertexInput) -> mat4x4<f32> {
+  let joint0 = (joint.matrices[input.joints.x] * inverseBind.matrices[input.joints.x]);
+  let joint1 = (joint.matrices[input.joints.y] * inverseBind.matrices[input.joints.y]);
+  let joint2 = (joint.matrices[input.joints.z] * inverseBind.matrices[input.joints.z]);
+  let joint3 = (joint.matrices[input.joints.w] * inverseBind.matrices[input.joints.w]);
+  let skinMatrix = ((((joint0 * input.weights.x) + (joint1 * input.weights.y)) + (joint2 * input.weights.z)) + (joint3 * input.weights.w));
+  return skinMatrix;
+}
+
+@stage(vertex)
+fn vertexMain(input : VertexInput) -> VertexOutput {
+  var output : VertexOutput;
+  let modelMatrix = getSkinMatrix(input);
+  output.normal = normalize(((modelMatrix * vec4(input.normal, 0.0))).xyz);
+  output.tangent = normalize(((modelMatrix * vec4(input.tangent.xyz, 0.0))).xyz);
+  output.bitangent = (cross(output.normal, output.tangent) * input.tangent.w);
+  output.color = vec4(1.0);
+  output.texcoord = input.texcoord;
+  output.instanceColor = input.instanceColor;
+  let modelPos = (modelMatrix * input.position);
+  output.worldPos = modelPos.xyz;
+  output.view = (camera.position - modelPos.xyz);
+  output.position = ((camera.projection * camera.view) * modelPos);
+  return output;
+}
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.glsl b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.glsl
new file mode 100644
index 0000000..7c0e6d7
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.glsl
@@ -0,0 +1,181 @@
+SKIP: FAILED
+
+#version 310 es
+precision mediump float;
+
+struct VertexInput {
+  vec4 position;
+  vec3 normal;
+  vec4 tangent;
+  vec2 texcoord;
+  uvec4 joints;
+  vec4 weights;
+  vec4 instance0;
+  vec4 instance1;
+  vec4 instance2;
+  vec4 instance3;
+  vec4 instanceColor;
+};
+struct VertexOutput {
+  vec4 position;
+  vec3 worldPos;
+  vec3 view;
+  vec2 texcoord;
+  vec2 texcoord2;
+  vec4 color;
+  vec4 instanceColor;
+  vec3 normal;
+  vec3 tangent;
+  vec3 bitangent;
+};
+struct Camera {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+};
+
+layout (binding = 0) uniform Camera_1 {
+  mat4 projection;
+  mat4 inverseProjection;
+  mat4 view;
+  vec3 position;
+  float time;
+  vec2 outputSize;
+  float zNear;
+  float zFar;
+} camera;
+
+layout (binding = 1) buffer Joints_1 {
+  mat4 matrices[];
+} joint;
+layout (binding = 2) buffer Joints_2 {
+  mat4 matrices[];
+} inverseBind;
+
+mat4 getSkinMatrix(VertexInput tint_symbol) {
+  mat4 joint0 = (joint.matrices[tint_symbol.joints.x] * inverseBind.matrices[tint_symbol.joints.x]);
+  mat4 joint1 = (joint.matrices[tint_symbol.joints.y] * inverseBind.matrices[tint_symbol.joints.y]);
+  mat4 joint2 = (joint.matrices[tint_symbol.joints.z] * inverseBind.matrices[tint_symbol.joints.z]);
+  mat4 joint3 = (joint.matrices[tint_symbol.joints.w] * inverseBind.matrices[tint_symbol.joints.w]);
+  mat4 skinMatrix = ((((joint0 * tint_symbol.weights.x) + (joint1 * tint_symbol.weights.y)) + (joint2 * tint_symbol.weights.z)) + (joint3 * tint_symbol.weights.w));
+  return skinMatrix;
+}
+
+struct tint_symbol_3 {
+  vec4 position;
+  vec3 normal;
+  vec4 tangent;
+  vec2 texcoord;
+  uvec4 joints;
+  vec4 weights;
+  vec4 instance0;
+  vec4 instance1;
+  vec4 instance2;
+  vec4 instance3;
+  vec4 instanceColor;
+};
+struct tint_symbol_4 {
+  vec3 worldPos;
+  vec3 view;
+  vec2 texcoord;
+  vec2 texcoord2;
+  vec4 color;
+  vec4 instanceColor;
+  vec3 normal;
+  vec3 tangent;
+  vec3 bitangent;
+  vec4 position;
+};
+
+VertexOutput vertexMain_inner(VertexInput tint_symbol) {
+  VertexOutput tint_symbol_1 = VertexOutput(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec2(0.0f, 0.0f), vec2(0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f));
+  mat4 modelMatrix = getSkinMatrix(tint_symbol);
+  tint_symbol_1.normal = normalize((modelMatrix * vec4(tint_symbol.normal, 0.0f)).xyz);
+  tint_symbol_1.tangent = normalize((modelMatrix * vec4(tint_symbol.tangent.xyz, 0.0f)).xyz);
+  tint_symbol_1.bitangent = (cross(tint_symbol_1.normal, tint_symbol_1.tangent) * tint_symbol.tangent.w);
+  tint_symbol_1.color = vec4(1.0f);
+  tint_symbol_1.texcoord = tint_symbol.texcoord;
+  tint_symbol_1.instanceColor = tint_symbol.instanceColor;
+  vec4 modelPos = (modelMatrix * tint_symbol.position);
+  tint_symbol_1.worldPos = modelPos.xyz;
+  tint_symbol_1.view = (camera.position - modelPos.xyz);
+  tint_symbol_1.position = ((camera.projection * camera.view) * modelPos);
+  return tint_symbol_1;
+}
+
+tint_symbol_4 vertexMain(tint_symbol_3 tint_symbol_2) {
+  VertexInput tint_symbol_5 = VertexInput(tint_symbol_2.position, tint_symbol_2.normal, tint_symbol_2.tangent, tint_symbol_2.texcoord, tint_symbol_2.joints, tint_symbol_2.weights, tint_symbol_2.instance0, tint_symbol_2.instance1, tint_symbol_2.instance2, tint_symbol_2.instance3, tint_symbol_2.instanceColor);
+  VertexOutput inner_result = vertexMain_inner(tint_symbol_5);
+  tint_symbol_4 wrapper_result = tint_symbol_4(vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec2(0.0f, 0.0f), vec2(0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 0.0f, 0.0f), vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  wrapper_result.position = inner_result.position;
+  wrapper_result.worldPos = inner_result.worldPos;
+  wrapper_result.view = inner_result.view;
+  wrapper_result.texcoord = inner_result.texcoord;
+  wrapper_result.texcoord2 = inner_result.texcoord2;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.instanceColor = inner_result.instanceColor;
+  wrapper_result.normal = inner_result.normal;
+  wrapper_result.tangent = inner_result.tangent;
+  wrapper_result.bitangent = inner_result.bitangent;
+  return wrapper_result;
+}
+in vec4 position;
+in vec3 normal;
+in vec4 tangent;
+in vec2 texcoord;
+in uvec4 joints;
+in vec4 weights;
+in vec4 instance0;
+in vec4 instance1;
+in vec4 instance2;
+in vec4 instance3;
+in vec4 instanceColor;
+out vec3 worldPos;
+out vec3 view;
+out vec2 texcoord;
+out vec2 texcoord2;
+out vec4 color;
+out vec4 instanceColor;
+out vec3 normal;
+out vec3 tangent;
+out vec3 bitangent;
+void main() {
+  tint_symbol_3 inputs;
+  inputs.position = position;
+  inputs.normal = normal;
+  inputs.tangent = tangent;
+  inputs.texcoord = texcoord;
+  inputs.joints = joints;
+  inputs.weights = weights;
+  inputs.instance0 = instance0;
+  inputs.instance1 = instance1;
+  inputs.instance2 = instance2;
+  inputs.instance3 = instance3;
+  inputs.instanceColor = instanceColor;
+  tint_symbol_4 outputs;
+  outputs = vertexMain(inputs);
+  worldPos = outputs.worldPos;
+  view = outputs.view;
+  texcoord = outputs.texcoord;
+  texcoord2 = outputs.texcoord2;
+  color = outputs.color;
+  instanceColor = outputs.instanceColor;
+  normal = outputs.normal;
+  tangent = outputs.tangent;
+  bitangent = outputs.bitangent;
+  gl_Position = outputs.position;
+  gl_Position.y = -gl_Position.y;
+}
+
+
+Error parsing GLSL shader:
+ERROR: 0:138: 'texcoord' : redefinition 
+ERROR: 1 compilation errors.  No code generated.
+
+
+
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.hlsl b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.hlsl
new file mode 100644
index 0000000..a1ce769
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.hlsl
@@ -0,0 +1,116 @@
+struct VertexInput {
+  float4 position;
+  float3 normal;
+  float4 tangent;
+  float2 texcoord;
+  uint4 joints;
+  float4 weights;
+  float4 instance0;
+  float4 instance1;
+  float4 instance2;
+  float4 instance3;
+  float4 instanceColor;
+};
+struct VertexOutput {
+  float4 position;
+  float3 worldPos;
+  float3 view;
+  float2 texcoord;
+  float2 texcoord2;
+  float4 color;
+  float4 instanceColor;
+  float3 normal;
+  float3 tangent;
+  float3 bitangent;
+};
+
+cbuffer cbuffer_camera : register(b0, space0) {
+  uint4 camera[14];
+};
+
+float4x4 getInstanceMatrix(VertexInput input) {
+  return float4x4(input.instance0, input.instance1, input.instance2, input.instance3);
+}
+
+ByteAddressBuffer joint : register(t1, space0);
+ByteAddressBuffer inverseBind : register(t2, space0);
+
+float4x4 tint_symbol_3(ByteAddressBuffer buffer, uint offset) {
+  return float4x4(asfloat(buffer.Load4((offset + 0u))), asfloat(buffer.Load4((offset + 16u))), asfloat(buffer.Load4((offset + 32u))), asfloat(buffer.Load4((offset + 48u))));
+}
+
+float4x4 getSkinMatrix(VertexInput input) {
+  const float4x4 joint0 = mul(tint_symbol_3(inverseBind, (64u * input.joints.x)), tint_symbol_3(joint, (64u * input.joints.x)));
+  const float4x4 joint1 = mul(tint_symbol_3(inverseBind, (64u * input.joints.y)), tint_symbol_3(joint, (64u * input.joints.y)));
+  const float4x4 joint2 = mul(tint_symbol_3(inverseBind, (64u * input.joints.z)), tint_symbol_3(joint, (64u * input.joints.z)));
+  const float4x4 joint3 = mul(tint_symbol_3(inverseBind, (64u * input.joints.w)), tint_symbol_3(joint, (64u * input.joints.w)));
+  const float4x4 skinMatrix = ((((joint0 * input.weights.x) + (joint1 * input.weights.y)) + (joint2 * input.weights.z)) + (joint3 * input.weights.w));
+  return skinMatrix;
+}
+
+struct tint_symbol_1 {
+  float4 position : TEXCOORD0;
+  float3 normal : TEXCOORD1;
+  float4 tangent : TEXCOORD2;
+  float2 texcoord : TEXCOORD3;
+  uint4 joints : TEXCOORD6;
+  float4 weights : TEXCOORD7;
+  float4 instance0 : TEXCOORD8;
+  float4 instance1 : TEXCOORD9;
+  float4 instance2 : TEXCOORD10;
+  float4 instance3 : TEXCOORD11;
+  float4 instanceColor : TEXCOORD12;
+};
+struct tint_symbol_2 {
+  float3 worldPos : TEXCOORD0;
+  float3 view : TEXCOORD1;
+  float2 texcoord : TEXCOORD2;
+  float2 texcoord2 : TEXCOORD3;
+  float4 color : TEXCOORD4;
+  float4 instanceColor : TEXCOORD5;
+  float3 normal : TEXCOORD6;
+  float3 tangent : TEXCOORD7;
+  float3 bitangent : TEXCOORD8;
+  float4 position : SV_Position;
+};
+
+float4x4 tint_symbol_6(uint4 buffer[14], uint offset) {
+  const uint scalar_offset = ((offset + 0u)) / 4;
+  const uint scalar_offset_1 = ((offset + 16u)) / 4;
+  const uint scalar_offset_2 = ((offset + 32u)) / 4;
+  const uint scalar_offset_3 = ((offset + 48u)) / 4;
+  return float4x4(asfloat(buffer[scalar_offset / 4]), asfloat(buffer[scalar_offset_1 / 4]), asfloat(buffer[scalar_offset_2 / 4]), asfloat(buffer[scalar_offset_3 / 4]));
+}
+
+VertexOutput vertexMain_inner(VertexInput input) {
+  VertexOutput output = (VertexOutput)0;
+  const float4x4 modelMatrix = getSkinMatrix(input);
+  output.normal = normalize(mul(float4(input.normal, 0.0f), modelMatrix).xyz);
+  output.tangent = normalize(mul(float4(input.tangent.xyz, 0.0f), modelMatrix).xyz);
+  output.bitangent = (cross(output.normal, output.tangent) * input.tangent.w);
+  output.color = float4((1.0f).xxxx);
+  output.texcoord = input.texcoord;
+  output.instanceColor = input.instanceColor;
+  const float4 modelPos = mul(input.position, modelMatrix);
+  output.worldPos = modelPos.xyz;
+  output.view = (asfloat(camera[12].xyz) - modelPos.xyz);
+  output.position = mul(modelPos, mul(tint_symbol_6(camera, 128u), tint_symbol_6(camera, 0u)));
+  return output;
+}
+
+tint_symbol_2 vertexMain(tint_symbol_1 tint_symbol) {
+  const VertexInput tint_symbol_8 = {tint_symbol.position, tint_symbol.normal, tint_symbol.tangent, tint_symbol.texcoord, tint_symbol.joints, tint_symbol.weights, tint_symbol.instance0, tint_symbol.instance1, tint_symbol.instance2, tint_symbol.instance3, tint_symbol.instanceColor};
+  const VertexOutput inner_result = vertexMain_inner(tint_symbol_8);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.position = inner_result.position;
+  wrapper_result.worldPos = inner_result.worldPos;
+  wrapper_result.view = inner_result.view;
+  wrapper_result.texcoord = inner_result.texcoord;
+  wrapper_result.texcoord2 = inner_result.texcoord2;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.instanceColor = inner_result.instanceColor;
+  wrapper_result.normal = inner_result.normal;
+  wrapper_result.tangent = inner_result.tangent;
+  wrapper_result.bitangent = inner_result.bitangent;
+  return wrapper_result;
+}
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.msl b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.msl
new file mode 100644
index 0000000..14e2480
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.msl
@@ -0,0 +1,124 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, int N, int M>
+inline vec<T, M> operator*(matrix<T, N, M> lhs, packed_vec<T, N> rhs) {
+  return lhs * vec<T, N>(rhs);
+}
+
+template<typename T, int N, int M>
+inline vec<T, N> operator*(packed_vec<T, M> lhs, matrix<T, N, M> rhs) {
+  return vec<T, M>(lhs) * rhs;
+}
+
+struct VertexInput {
+  float4 position;
+  float3 normal;
+  float4 tangent;
+  float2 texcoord;
+  uint4 joints;
+  float4 weights;
+  float4 instance0;
+  float4 instance1;
+  float4 instance2;
+  float4 instance3;
+  float4 instanceColor;
+};
+struct VertexOutput {
+  float4 position;
+  float3 worldPos;
+  float3 view;
+  float2 texcoord;
+  float2 texcoord2;
+  float4 color;
+  float4 instanceColor;
+  float3 normal;
+  float3 tangent;
+  float3 bitangent;
+};
+struct Camera {
+  /* 0x0000 */ float4x4 projection;
+  /* 0x0040 */ float4x4 inverseProjection;
+  /* 0x0080 */ float4x4 view;
+  /* 0x00c0 */ packed_float3 position;
+  /* 0x00cc */ float time;
+  /* 0x00d0 */ float2 outputSize;
+  /* 0x00d8 */ float zNear;
+  /* 0x00dc */ float zFar;
+};
+struct Joints {
+  /* 0x0000 */ float4x4 matrices[1];
+};
+struct tint_symbol_1 {
+  float4 position [[attribute(0)]];
+  float3 normal [[attribute(1)]];
+  float4 tangent [[attribute(2)]];
+  float2 texcoord [[attribute(3)]];
+  uint4 joints [[attribute(6)]];
+  float4 weights [[attribute(7)]];
+  float4 instance0 [[attribute(8)]];
+  float4 instance1 [[attribute(9)]];
+  float4 instance2 [[attribute(10)]];
+  float4 instance3 [[attribute(11)]];
+  float4 instanceColor [[attribute(12)]];
+};
+struct tint_symbol_2 {
+  float3 worldPos [[user(locn0)]];
+  float3 view [[user(locn1)]];
+  float2 texcoord [[user(locn2)]];
+  float2 texcoord2 [[user(locn3)]];
+  float4 color [[user(locn4)]];
+  float4 instanceColor [[user(locn5)]];
+  float3 normal [[user(locn6)]];
+  float3 tangent [[user(locn7)]];
+  float3 bitangent [[user(locn8)]];
+  float4 position [[position]];
+};
+
+float4x4 getInstanceMatrix(VertexInput input) {
+  return float4x4(input.instance0, input.instance1, input.instance2, input.instance3);
+}
+
+float4x4 getSkinMatrix(VertexInput input, const device Joints* const tint_symbol_4, const device Joints* const tint_symbol_5) {
+  float4x4 const joint0 = ((*(tint_symbol_4)).matrices[input.joints[0]] * (*(tint_symbol_5)).matrices[input.joints[0]]);
+  float4x4 const joint1 = ((*(tint_symbol_4)).matrices[input.joints[1]] * (*(tint_symbol_5)).matrices[input.joints[1]]);
+  float4x4 const joint2 = ((*(tint_symbol_4)).matrices[input.joints[2]] * (*(tint_symbol_5)).matrices[input.joints[2]]);
+  float4x4 const joint3 = ((*(tint_symbol_4)).matrices[input.joints[3]] * (*(tint_symbol_5)).matrices[input.joints[3]]);
+  float4x4 const skinMatrix = ((((joint0 * input.weights[0]) + (joint1 * input.weights[1])) + (joint2 * input.weights[2])) + (joint3 * input.weights[3]));
+  return skinMatrix;
+}
+
+VertexOutput vertexMain_inner(VertexInput input, const device Joints* const tint_symbol_6, const device Joints* const tint_symbol_7, const constant Camera* const tint_symbol_8) {
+  VertexOutput output = {};
+  float4x4 const modelMatrix = getSkinMatrix(input, tint_symbol_6, tint_symbol_7);
+  output.normal = normalize(float4(((modelMatrix * float4(input.normal, 0.0f)))).xyz);
+  output.tangent = normalize(float4(((modelMatrix * float4(float4(input.tangent).xyz, 0.0f)))).xyz);
+  output.bitangent = (cross(output.normal, output.tangent) * input.tangent[3]);
+  output.color = float4(1.0f);
+  output.texcoord = input.texcoord;
+  output.instanceColor = input.instanceColor;
+  float4 const modelPos = (modelMatrix * input.position);
+  output.worldPos = float4(modelPos).xyz;
+  output.view = ((*(tint_symbol_8)).position - float4(modelPos).xyz);
+  output.position = (((*(tint_symbol_8)).projection * (*(tint_symbol_8)).view) * modelPos);
+  return output;
+}
+
+vertex tint_symbol_2 vertexMain(const device Joints* tint_symbol_9 [[buffer(1)]], const device Joints* tint_symbol_10 [[buffer(2)]], const constant Camera* tint_symbol_11 [[buffer(0)]], tint_symbol_1 tint_symbol [[stage_in]]) {
+  VertexInput const tint_symbol_3 = {.position=tint_symbol.position, .normal=tint_symbol.normal, .tangent=tint_symbol.tangent, .texcoord=tint_symbol.texcoord, .joints=tint_symbol.joints, .weights=tint_symbol.weights, .instance0=tint_symbol.instance0, .instance1=tint_symbol.instance1, .instance2=tint_symbol.instance2, .instance3=tint_symbol.instance3, .instanceColor=tint_symbol.instanceColor};
+  VertexOutput const inner_result = vertexMain_inner(tint_symbol_3, tint_symbol_9, tint_symbol_10, tint_symbol_11);
+  tint_symbol_2 wrapper_result = {};
+  wrapper_result.position = inner_result.position;
+  wrapper_result.worldPos = inner_result.worldPos;
+  wrapper_result.view = inner_result.view;
+  wrapper_result.texcoord = inner_result.texcoord;
+  wrapper_result.texcoord2 = inner_result.texcoord2;
+  wrapper_result.color = inner_result.color;
+  wrapper_result.instanceColor = inner_result.instanceColor;
+  wrapper_result.normal = inner_result.normal;
+  wrapper_result.tangent = inner_result.tangent;
+  wrapper_result.bitangent = inner_result.bitangent;
+  return wrapper_result;
+}
+
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.spvasm b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.spvasm
new file mode 100644
index 0000000..243bda2
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.spvasm
@@ -0,0 +1,429 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 258
+; Schema: 0
+               OpCapability Shader
+        %168 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %vertexMain "vertexMain" %position_1 %normal_1 %tangent_1 %texcoord_1 %joints_1 %weights_1 %instance0_1 %instance1_1 %instance2_1 %instance3_1 %instanceColor_1 %position_2 %worldPos_1 %view_1 %texcoord_2 %texcoord2_1 %color_1 %instanceColor_2 %normal_2 %tangent_2 %bitangent_1 %vertex_point_size
+               OpName %position_1 "position_1"
+               OpName %normal_1 "normal_1"
+               OpName %tangent_1 "tangent_1"
+               OpName %texcoord_1 "texcoord_1"
+               OpName %joints_1 "joints_1"
+               OpName %weights_1 "weights_1"
+               OpName %instance0_1 "instance0_1"
+               OpName %instance1_1 "instance1_1"
+               OpName %instance2_1 "instance2_1"
+               OpName %instance3_1 "instance3_1"
+               OpName %instanceColor_1 "instanceColor_1"
+               OpName %position_2 "position_2"
+               OpName %worldPos_1 "worldPos_1"
+               OpName %view_1 "view_1"
+               OpName %texcoord_2 "texcoord_2"
+               OpName %texcoord2_1 "texcoord2_1"
+               OpName %color_1 "color_1"
+               OpName %instanceColor_2 "instanceColor_2"
+               OpName %normal_2 "normal_2"
+               OpName %tangent_2 "tangent_2"
+               OpName %bitangent_1 "bitangent_1"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %Camera "Camera"
+               OpMemberName %Camera 0 "projection"
+               OpMemberName %Camera 1 "inverseProjection"
+               OpMemberName %Camera 2 "view"
+               OpMemberName %Camera 3 "position"
+               OpMemberName %Camera 4 "time"
+               OpMemberName %Camera 5 "outputSize"
+               OpMemberName %Camera 6 "zNear"
+               OpMemberName %Camera 7 "zFar"
+               OpName %camera "camera"
+               OpName %Joints "Joints"
+               OpMemberName %Joints 0 "matrices"
+               OpName %joint "joint"
+               OpName %inverseBind "inverseBind"
+               OpName %VertexInput "VertexInput"
+               OpMemberName %VertexInput 0 "position"
+               OpMemberName %VertexInput 1 "normal"
+               OpMemberName %VertexInput 2 "tangent"
+               OpMemberName %VertexInput 3 "texcoord"
+               OpMemberName %VertexInput 4 "joints"
+               OpMemberName %VertexInput 5 "weights"
+               OpMemberName %VertexInput 6 "instance0"
+               OpMemberName %VertexInput 7 "instance1"
+               OpMemberName %VertexInput 8 "instance2"
+               OpMemberName %VertexInput 9 "instance3"
+               OpMemberName %VertexInput 10 "instanceColor"
+               OpName %getInstanceMatrix "getInstanceMatrix"
+               OpName %input "input"
+               OpName %getSkinMatrix "getSkinMatrix"
+               OpName %input_0 "input"
+               OpName %VertexOutput "VertexOutput"
+               OpMemberName %VertexOutput 0 "position"
+               OpMemberName %VertexOutput 1 "worldPos"
+               OpMemberName %VertexOutput 2 "view"
+               OpMemberName %VertexOutput 3 "texcoord"
+               OpMemberName %VertexOutput 4 "texcoord2"
+               OpMemberName %VertexOutput 5 "color"
+               OpMemberName %VertexOutput 6 "instanceColor"
+               OpMemberName %VertexOutput 7 "normal"
+               OpMemberName %VertexOutput 8 "tangent"
+               OpMemberName %VertexOutput 9 "bitangent"
+               OpName %vertexMain_inner "vertexMain_inner"
+               OpName %input_1 "input"
+               OpName %output "output"
+               OpName %vertexMain "vertexMain"
+               OpDecorate %position_1 Location 0
+               OpDecorate %normal_1 Location 1
+               OpDecorate %tangent_1 Location 2
+               OpDecorate %texcoord_1 Location 3
+               OpDecorate %joints_1 Location 6
+               OpDecorate %weights_1 Location 7
+               OpDecorate %instance0_1 Location 8
+               OpDecorate %instance1_1 Location 9
+               OpDecorate %instance2_1 Location 10
+               OpDecorate %instance3_1 Location 11
+               OpDecorate %instanceColor_1 Location 12
+               OpDecorate %position_2 BuiltIn Position
+               OpDecorate %worldPos_1 Location 0
+               OpDecorate %view_1 Location 1
+               OpDecorate %texcoord_2 Location 2
+               OpDecorate %texcoord2_1 Location 3
+               OpDecorate %color_1 Location 4
+               OpDecorate %instanceColor_2 Location 5
+               OpDecorate %normal_2 Location 6
+               OpDecorate %tangent_2 Location 7
+               OpDecorate %bitangent_1 Location 8
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpDecorate %Camera Block
+               OpMemberDecorate %Camera 0 Offset 0
+               OpMemberDecorate %Camera 0 ColMajor
+               OpMemberDecorate %Camera 0 MatrixStride 16
+               OpMemberDecorate %Camera 1 Offset 64
+               OpMemberDecorate %Camera 1 ColMajor
+               OpMemberDecorate %Camera 1 MatrixStride 16
+               OpMemberDecorate %Camera 2 Offset 128
+               OpMemberDecorate %Camera 2 ColMajor
+               OpMemberDecorate %Camera 2 MatrixStride 16
+               OpMemberDecorate %Camera 3 Offset 192
+               OpMemberDecorate %Camera 4 Offset 204
+               OpMemberDecorate %Camera 5 Offset 208
+               OpMemberDecorate %Camera 6 Offset 216
+               OpMemberDecorate %Camera 7 Offset 220
+               OpDecorate %camera NonWritable
+               OpDecorate %camera Binding 0
+               OpDecorate %camera DescriptorSet 0
+               OpDecorate %Joints Block
+               OpMemberDecorate %Joints 0 Offset 0
+               OpMemberDecorate %Joints 0 ColMajor
+               OpMemberDecorate %Joints 0 MatrixStride 16
+               OpDecorate %_runtimearr_mat4v4float ArrayStride 64
+               OpDecorate %joint NonWritable
+               OpDecorate %joint Binding 1
+               OpDecorate %joint DescriptorSet 0
+               OpDecorate %inverseBind NonWritable
+               OpDecorate %inverseBind Binding 2
+               OpDecorate %inverseBind DescriptorSet 0
+               OpMemberDecorate %VertexInput 0 Offset 0
+               OpMemberDecorate %VertexInput 1 Offset 16
+               OpMemberDecorate %VertexInput 2 Offset 32
+               OpMemberDecorate %VertexInput 3 Offset 48
+               OpMemberDecorate %VertexInput 4 Offset 64
+               OpMemberDecorate %VertexInput 5 Offset 80
+               OpMemberDecorate %VertexInput 6 Offset 96
+               OpMemberDecorate %VertexInput 7 Offset 112
+               OpMemberDecorate %VertexInput 8 Offset 128
+               OpMemberDecorate %VertexInput 9 Offset 144
+               OpMemberDecorate %VertexInput 10 Offset 160
+               OpMemberDecorate %VertexOutput 0 Offset 0
+               OpMemberDecorate %VertexOutput 1 Offset 16
+               OpMemberDecorate %VertexOutput 2 Offset 32
+               OpMemberDecorate %VertexOutput 3 Offset 48
+               OpMemberDecorate %VertexOutput 4 Offset 56
+               OpMemberDecorate %VertexOutput 5 Offset 64
+               OpMemberDecorate %VertexOutput 6 Offset 80
+               OpMemberDecorate %VertexOutput 7 Offset 96
+               OpMemberDecorate %VertexOutput 8 Offset 112
+               OpMemberDecorate %VertexOutput 9 Offset 128
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %position_1 = OpVariable %_ptr_Input_v4float Input
+    %v3float = OpTypeVector %float 3
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+   %normal_1 = OpVariable %_ptr_Input_v3float Input
+  %tangent_1 = OpVariable %_ptr_Input_v4float Input
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+ %texcoord_1 = OpVariable %_ptr_Input_v2float Input
+       %uint = OpTypeInt 32 0
+     %v4uint = OpTypeVector %uint 4
+%_ptr_Input_v4uint = OpTypePointer Input %v4uint
+   %joints_1 = OpVariable %_ptr_Input_v4uint Input
+  %weights_1 = OpVariable %_ptr_Input_v4float Input
+%instance0_1 = OpVariable %_ptr_Input_v4float Input
+%instance1_1 = OpVariable %_ptr_Input_v4float Input
+%instance2_1 = OpVariable %_ptr_Input_v4float Input
+%instance3_1 = OpVariable %_ptr_Input_v4float Input
+%instanceColor_1 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %24 = OpConstantNull %v4float
+ %position_2 = OpVariable %_ptr_Output_v4float Output %24
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+         %27 = OpConstantNull %v3float
+ %worldPos_1 = OpVariable %_ptr_Output_v3float Output %27
+     %view_1 = OpVariable %_ptr_Output_v3float Output %27
+%_ptr_Output_v2float = OpTypePointer Output %v2float
+         %31 = OpConstantNull %v2float
+ %texcoord_2 = OpVariable %_ptr_Output_v2float Output %31
+%texcoord2_1 = OpVariable %_ptr_Output_v2float Output %31
+    %color_1 = OpVariable %_ptr_Output_v4float Output %24
+%instanceColor_2 = OpVariable %_ptr_Output_v4float Output %24
+   %normal_2 = OpVariable %_ptr_Output_v3float Output %27
+  %tangent_2 = OpVariable %_ptr_Output_v3float Output %27
+%bitangent_1 = OpVariable %_ptr_Output_v3float Output %27
+%_ptr_Output_float = OpTypePointer Output %float
+         %40 = OpConstantNull %float
+%vertex_point_size = OpVariable %_ptr_Output_float Output %40
+%mat4v4float = OpTypeMatrix %v4float 4
+     %Camera = OpTypeStruct %mat4v4float %mat4v4float %mat4v4float %v3float %float %v2float %float %float
+%_ptr_Uniform_Camera = OpTypePointer Uniform %Camera
+     %camera = OpVariable %_ptr_Uniform_Camera Uniform
+%_runtimearr_mat4v4float = OpTypeRuntimeArray %mat4v4float
+     %Joints = OpTypeStruct %_runtimearr_mat4v4float
+%_ptr_StorageBuffer_Joints = OpTypePointer StorageBuffer %Joints
+      %joint = OpVariable %_ptr_StorageBuffer_Joints StorageBuffer
+%inverseBind = OpVariable %_ptr_StorageBuffer_Joints StorageBuffer
+%VertexInput = OpTypeStruct %v4float %v3float %v4float %v2float %v4uint %v4float %v4float %v4float %v4float %v4float %v4float
+         %50 = OpTypeFunction %mat4v4float %VertexInput
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_mat4v4float = OpTypePointer StorageBuffer %mat4v4float
+%VertexOutput = OpTypeStruct %v4float %v3float %v3float %v2float %v2float %v4float %v4float %v3float %v3float %v3float
+        %155 = OpTypeFunction %VertexOutput %VertexInput
+%_ptr_Function_VertexOutput = OpTypePointer Function %VertexOutput
+        %162 = OpConstantNull %VertexOutput
+     %uint_7 = OpConstant %uint 7
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+    %float_0 = OpConstant %float 0
+     %uint_8 = OpConstant %uint 8
+     %uint_9 = OpConstant %uint 9
+     %uint_5 = OpConstant %uint 5
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+    %float_1 = OpConstant %float 1
+        %202 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+     %uint_3 = OpConstant %uint 3
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+     %uint_6 = OpConstant %uint 6
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
+%_ptr_Uniform_mat4v4float = OpTypePointer Uniform %mat4v4float
+       %void = OpTypeVoid
+        %231 = OpTypeFunction %void
+%getInstanceMatrix = OpFunction %mat4v4float None %50
+      %input = OpFunctionParameter %VertexInput
+         %54 = OpLabel
+         %55 = OpCompositeExtract %v4float %input 6
+         %56 = OpCompositeExtract %v4float %input 7
+         %57 = OpCompositeExtract %v4float %input 8
+         %58 = OpCompositeExtract %v4float %input 9
+         %59 = OpCompositeConstruct %mat4v4float %55 %56 %57 %58
+               OpReturnValue %59
+               OpFunctionEnd
+%getSkinMatrix = OpFunction %mat4v4float None %50
+    %input_0 = OpFunctionParameter %VertexInput
+         %62 = OpLabel
+         %64 = OpCompositeExtract %v4uint %input_0 4
+         %65 = OpCompositeExtract %uint %64 0
+         %67 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %joint %uint_0 %65
+         %68 = OpLoad %mat4v4float %67
+         %69 = OpCompositeExtract %v4uint %input_0 4
+         %70 = OpCompositeExtract %uint %69 0
+         %71 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %inverseBind %uint_0 %70
+         %72 = OpLoad %mat4v4float %71
+         %73 = OpMatrixTimesMatrix %mat4v4float %68 %72
+         %74 = OpCompositeExtract %v4uint %input_0 4
+         %75 = OpCompositeExtract %uint %74 1
+         %76 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %joint %uint_0 %75
+         %77 = OpLoad %mat4v4float %76
+         %78 = OpCompositeExtract %v4uint %input_0 4
+         %79 = OpCompositeExtract %uint %78 1
+         %80 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %inverseBind %uint_0 %79
+         %81 = OpLoad %mat4v4float %80
+         %82 = OpMatrixTimesMatrix %mat4v4float %77 %81
+         %83 = OpCompositeExtract %v4uint %input_0 4
+         %84 = OpCompositeExtract %uint %83 2
+         %85 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %joint %uint_0 %84
+         %86 = OpLoad %mat4v4float %85
+         %87 = OpCompositeExtract %v4uint %input_0 4
+         %88 = OpCompositeExtract %uint %87 2
+         %89 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %inverseBind %uint_0 %88
+         %90 = OpLoad %mat4v4float %89
+         %91 = OpMatrixTimesMatrix %mat4v4float %86 %90
+         %92 = OpCompositeExtract %v4uint %input_0 4
+         %93 = OpCompositeExtract %uint %92 3
+         %94 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %joint %uint_0 %93
+         %95 = OpLoad %mat4v4float %94
+         %96 = OpCompositeExtract %v4uint %input_0 4
+         %97 = OpCompositeExtract %uint %96 3
+         %98 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %inverseBind %uint_0 %97
+         %99 = OpLoad %mat4v4float %98
+        %100 = OpMatrixTimesMatrix %mat4v4float %95 %99
+        %101 = OpCompositeExtract %v4float %input_0 5
+        %102 = OpCompositeExtract %float %101 0
+        %103 = OpMatrixTimesScalar %mat4v4float %73 %102
+        %104 = OpCompositeExtract %v4float %input_0 5
+        %105 = OpCompositeExtract %float %104 1
+        %106 = OpMatrixTimesScalar %mat4v4float %82 %105
+        %108 = OpCompositeExtract %v4float %103 0
+        %109 = OpCompositeExtract %v4float %106 0
+        %110 = OpFAdd %v4float %108 %109
+        %111 = OpCompositeExtract %v4float %103 1
+        %112 = OpCompositeExtract %v4float %106 1
+        %113 = OpFAdd %v4float %111 %112
+        %114 = OpCompositeExtract %v4float %103 2
+        %115 = OpCompositeExtract %v4float %106 2
+        %116 = OpFAdd %v4float %114 %115
+        %117 = OpCompositeExtract %v4float %103 3
+        %118 = OpCompositeExtract %v4float %106 3
+        %119 = OpFAdd %v4float %117 %118
+        %120 = OpCompositeConstruct %mat4v4float %110 %113 %116 %119
+        %121 = OpCompositeExtract %v4float %input_0 5
+        %122 = OpCompositeExtract %float %121 2
+        %123 = OpMatrixTimesScalar %mat4v4float %91 %122
+        %125 = OpCompositeExtract %v4float %120 0
+        %126 = OpCompositeExtract %v4float %123 0
+        %127 = OpFAdd %v4float %125 %126
+        %128 = OpCompositeExtract %v4float %120 1
+        %129 = OpCompositeExtract %v4float %123 1
+        %130 = OpFAdd %v4float %128 %129
+        %131 = OpCompositeExtract %v4float %120 2
+        %132 = OpCompositeExtract %v4float %123 2
+        %133 = OpFAdd %v4float %131 %132
+        %134 = OpCompositeExtract %v4float %120 3
+        %135 = OpCompositeExtract %v4float %123 3
+        %136 = OpFAdd %v4float %134 %135
+        %137 = OpCompositeConstruct %mat4v4float %127 %130 %133 %136
+        %138 = OpCompositeExtract %v4float %input_0 5
+        %139 = OpCompositeExtract %float %138 3
+        %140 = OpMatrixTimesScalar %mat4v4float %100 %139
+        %142 = OpCompositeExtract %v4float %137 0
+        %143 = OpCompositeExtract %v4float %140 0
+        %144 = OpFAdd %v4float %142 %143
+        %145 = OpCompositeExtract %v4float %137 1
+        %146 = OpCompositeExtract %v4float %140 1
+        %147 = OpFAdd %v4float %145 %146
+        %148 = OpCompositeExtract %v4float %137 2
+        %149 = OpCompositeExtract %v4float %140 2
+        %150 = OpFAdd %v4float %148 %149
+        %151 = OpCompositeExtract %v4float %137 3
+        %152 = OpCompositeExtract %v4float %140 3
+        %153 = OpFAdd %v4float %151 %152
+        %154 = OpCompositeConstruct %mat4v4float %144 %147 %150 %153
+               OpReturnValue %154
+               OpFunctionEnd
+%vertexMain_inner = OpFunction %VertexOutput None %155
+    %input_1 = OpFunctionParameter %VertexInput
+        %159 = OpLabel
+     %output = OpVariable %_ptr_Function_VertexOutput Function %162
+        %163 = OpFunctionCall %mat4v4float %getSkinMatrix %input_1
+        %166 = OpAccessChain %_ptr_Function_v3float %output %uint_7
+        %169 = OpCompositeExtract %v3float %input_1 1
+        %170 = OpCompositeExtract %float %169 0
+        %171 = OpCompositeExtract %float %169 1
+        %172 = OpCompositeExtract %float %169 2
+        %174 = OpCompositeConstruct %v4float %170 %171 %172 %float_0
+        %175 = OpMatrixTimesVector %v4float %163 %174
+        %176 = OpVectorShuffle %v3float %175 %175 0 1 2
+        %167 = OpExtInst %v3float %168 Normalize %176
+               OpStore %166 %167
+        %178 = OpAccessChain %_ptr_Function_v3float %output %uint_8
+        %180 = OpCompositeExtract %v4float %input_1 2
+        %181 = OpVectorShuffle %v3float %180 %180 0 1 2
+        %182 = OpCompositeExtract %float %181 0
+        %183 = OpCompositeExtract %float %181 1
+        %184 = OpCompositeExtract %float %181 2
+        %185 = OpCompositeConstruct %v4float %182 %183 %184 %float_0
+        %186 = OpMatrixTimesVector %v4float %163 %185
+        %187 = OpVectorShuffle %v3float %186 %186 0 1 2
+        %179 = OpExtInst %v3float %168 Normalize %187
+               OpStore %178 %179
+        %189 = OpAccessChain %_ptr_Function_v3float %output %uint_9
+        %191 = OpAccessChain %_ptr_Function_v3float %output %uint_7
+        %192 = OpLoad %v3float %191
+        %193 = OpAccessChain %_ptr_Function_v3float %output %uint_8
+        %194 = OpLoad %v3float %193
+        %190 = OpExtInst %v3float %168 Cross %192 %194
+        %195 = OpCompositeExtract %v4float %input_1 2
+        %196 = OpCompositeExtract %float %195 3
+        %197 = OpVectorTimesScalar %v3float %190 %196
+               OpStore %189 %197
+        %200 = OpAccessChain %_ptr_Function_v4float %output %uint_5
+               OpStore %200 %202
+        %205 = OpAccessChain %_ptr_Function_v2float %output %uint_3
+        %206 = OpCompositeExtract %v2float %input_1 3
+               OpStore %205 %206
+        %208 = OpAccessChain %_ptr_Function_v4float %output %uint_6
+        %209 = OpCompositeExtract %v4float %input_1 10
+               OpStore %208 %209
+        %210 = OpCompositeExtract %v4float %input_1 0
+        %211 = OpMatrixTimesVector %v4float %163 %210
+        %213 = OpAccessChain %_ptr_Function_v3float %output %uint_1
+        %214 = OpVectorShuffle %v3float %211 %211 0 1 2
+               OpStore %213 %214
+        %216 = OpAccessChain %_ptr_Function_v3float %output %uint_2
+        %218 = OpAccessChain %_ptr_Uniform_v3float %camera %uint_3
+        %219 = OpLoad %v3float %218
+        %220 = OpVectorShuffle %v3float %211 %211 0 1 2
+        %221 = OpFSub %v3float %219 %220
+               OpStore %216 %221
+        %222 = OpAccessChain %_ptr_Function_v4float %output %uint_0
+        %224 = OpAccessChain %_ptr_Uniform_mat4v4float %camera %uint_0
+        %225 = OpLoad %mat4v4float %224
+        %226 = OpAccessChain %_ptr_Uniform_mat4v4float %camera %uint_2
+        %227 = OpLoad %mat4v4float %226
+        %228 = OpMatrixTimesMatrix %mat4v4float %225 %227
+        %229 = OpMatrixTimesVector %v4float %228 %211
+               OpStore %222 %229
+        %230 = OpLoad %VertexOutput %output
+               OpReturnValue %230
+               OpFunctionEnd
+ %vertexMain = OpFunction %void None %231
+        %234 = OpLabel
+        %236 = OpLoad %v4float %position_1
+        %237 = OpLoad %v3float %normal_1
+        %238 = OpLoad %v4float %tangent_1
+        %239 = OpLoad %v2float %texcoord_1
+        %240 = OpLoad %v4uint %joints_1
+        %241 = OpLoad %v4float %weights_1
+        %242 = OpLoad %v4float %instance0_1
+        %243 = OpLoad %v4float %instance1_1
+        %244 = OpLoad %v4float %instance2_1
+        %245 = OpLoad %v4float %instance3_1
+        %246 = OpLoad %v4float %instanceColor_1
+        %247 = OpCompositeConstruct %VertexInput %236 %237 %238 %239 %240 %241 %242 %243 %244 %245 %246
+        %235 = OpFunctionCall %VertexOutput %vertexMain_inner %247
+        %248 = OpCompositeExtract %v4float %235 0
+               OpStore %position_2 %248
+        %249 = OpCompositeExtract %v3float %235 1
+               OpStore %worldPos_1 %249
+        %250 = OpCompositeExtract %v3float %235 2
+               OpStore %view_1 %250
+        %251 = OpCompositeExtract %v2float %235 3
+               OpStore %texcoord_2 %251
+        %252 = OpCompositeExtract %v2float %235 4
+               OpStore %texcoord2_1 %252
+        %253 = OpCompositeExtract %v4float %235 5
+               OpStore %color_1 %253
+        %254 = OpCompositeExtract %v4float %235 6
+               OpStore %instanceColor_2 %254
+        %255 = OpCompositeExtract %v3float %235 7
+               OpStore %normal_2 %255
+        %256 = OpCompositeExtract %v3float %235 8
+               OpStore %tangent_2 %256
+        %257 = OpCompositeExtract %v3float %235 9
+               OpStore %bitangent_1 %257
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
diff --git a/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.wgsl b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.wgsl
new file mode 100644
index 0000000..5230459
--- /dev/null
+++ b/test/benchmark/skinned-shadowed-pbr-vertex.wgsl.expected.wgsl
@@ -0,0 +1,98 @@
+struct VertexInput {
+  @location(0)
+  position : vec4<f32>;
+  @location(1)
+  normal : vec3<f32>;
+  @location(2)
+  tangent : vec4<f32>;
+  @location(3)
+  texcoord : vec2<f32>;
+  @location(6)
+  joints : vec4<u32>;
+  @location(7)
+  weights : vec4<f32>;
+  @location(8)
+  instance0 : vec4<f32>;
+  @location(9)
+  instance1 : vec4<f32>;
+  @location(10)
+  instance2 : vec4<f32>;
+  @location(11)
+  instance3 : vec4<f32>;
+  @location(12)
+  instanceColor : vec4<f32>;
+}
+
+struct VertexOutput {
+  @builtin(position)
+  position : vec4<f32>;
+  @location(0)
+  worldPos : vec3<f32>;
+  @location(1)
+  view : vec3<f32>;
+  @location(2)
+  texcoord : vec2<f32>;
+  @location(3)
+  texcoord2 : vec2<f32>;
+  @location(4)
+  color : vec4<f32>;
+  @location(5)
+  instanceColor : vec4<f32>;
+  @location(6)
+  normal : vec3<f32>;
+  @location(7)
+  tangent : vec3<f32>;
+  @location(8)
+  bitangent : vec3<f32>;
+}
+
+struct Camera {
+  projection : mat4x4<f32>;
+  inverseProjection : mat4x4<f32>;
+  view : mat4x4<f32>;
+  position : vec3<f32>;
+  time : f32;
+  outputSize : vec2<f32>;
+  zNear : f32;
+  zFar : f32;
+}
+
+@binding(0) @group(0) var<uniform> camera : Camera;
+
+fn getInstanceMatrix(input : VertexInput) -> mat4x4<f32> {
+  return mat4x4(input.instance0, input.instance1, input.instance2, input.instance3);
+}
+
+struct Joints {
+  matrices : array<mat4x4<f32>>;
+}
+
+@binding(1) @group(0) var<storage, read> joint : Joints;
+
+@binding(2) @group(0) var<storage, read> inverseBind : Joints;
+
+fn getSkinMatrix(input : VertexInput) -> mat4x4<f32> {
+  let joint0 = (joint.matrices[input.joints.x] * inverseBind.matrices[input.joints.x]);
+  let joint1 = (joint.matrices[input.joints.y] * inverseBind.matrices[input.joints.y]);
+  let joint2 = (joint.matrices[input.joints.z] * inverseBind.matrices[input.joints.z]);
+  let joint3 = (joint.matrices[input.joints.w] * inverseBind.matrices[input.joints.w]);
+  let skinMatrix = ((((joint0 * input.weights.x) + (joint1 * input.weights.y)) + (joint2 * input.weights.z)) + (joint3 * input.weights.w));
+  return skinMatrix;
+}
+
+@stage(vertex)
+fn vertexMain(input : VertexInput) -> VertexOutput {
+  var output : VertexOutput;
+  let modelMatrix = getSkinMatrix(input);
+  output.normal = normalize(((modelMatrix * vec4(input.normal, 0.0))).xyz);
+  output.tangent = normalize(((modelMatrix * vec4(input.tangent.xyz, 0.0))).xyz);
+  output.bitangent = (cross(output.normal, output.tangent) * input.tangent.w);
+  output.color = vec4(1.0);
+  output.texcoord = input.texcoord;
+  output.instanceColor = input.instanceColor;
+  let modelPos = (modelMatrix * input.position);
+  output.worldPos = modelPos.xyz;
+  output.view = (camera.position - modelPos.xyz);
+  output.position = ((camera.projection * camera.view) * modelPos);
+  return output;
+}