]> git.tdb.fi Git - libs/gl.git/blobdiff - source/glsl/spirv.cpp
Check the flat qualifier from the correct member
[libs/gl.git] / source / glsl / spirv.cpp
index 2220dcc1f012c00655fb207e841787b76be09238..4dceb9719a638c8ba6048b1a3509eb5b8555046c 100644 (file)
@@ -1,3 +1,4 @@
+#include <msp/core/algorithm.h>
 #include <msp/core/maputils.h>
 #include <msp/core/raii.h>
 #include "reflect.h"
@@ -11,134 +12,144 @@ namespace SL {
 
 const SpirVGenerator::BuiltinFunctionInfo SpirVGenerator::builtin_functions[] =
 {
-       { "radians", "f", "GLSL.std.450", GLSL450_RADIANS, { 1 }, 0 },
-       { "degrees", "f", "GLSL.std.450", GLSL450_DEGREES, { 1 }, 0 },
-       { "sin", "f", "GLSL.std.450", GLSL450_SIN, { 1 }, 0 },
-       { "cos", "f", "GLSL.std.450", GLSL450_COS, { 1 }, 0 },
-       { "tan", "f", "GLSL.std.450", GLSL450_TAN, { 1 }, 0 },
-       { "asin", "f", "GLSL.std.450", GLSL450_ASIN, { 1 }, 0 },
-       { "acos", "f", "GLSL.std.450", GLSL450_ACOS, { 1 }, 0 },
-       { "atan", "f", "GLSL.std.450", GLSL450_ATAN, { 1 }, 0 },
-       { "atan", "ff", "GLSL.std.450", GLSL450_ATAN2, { 1, 2 }, 0 },
-       { "sinh", "f", "GLSL.std.450", GLSL450_SINH, { 1 }, 0 },
-       { "cosh", "f", "GLSL.std.450", GLSL450_COSH, { 1 }, 0 },
-       { "tanh", "f", "GLSL.std.450", GLSL450_TANH, { 1 }, 0 },
-       { "asinh", "f", "GLSL.std.450", GLSL450_ASINH, { 1 }, 0 },
-       { "acosh", "f", "GLSL.std.450", GLSL450_ACOSH, { 1 }, 0 },
-       { "atanh", "f", "GLSL.std.450", GLSL450_ATANH, { 1 }, 0 },
-       { "pow", "ff", "GLSL.std.450", GLSL450_POW, { 1, 2 }, 0 },
-       { "exp", "f", "GLSL.std.450", GLSL450_EXP, { 1 }, 0 },
-       { "log", "f", "GLSL.std.450", GLSL450_LOG, { 1 }, 0 },
-       { "exp2", "f", "GLSL.std.450", GLSL450_EXP2, { 1 }, 0 },
-       { "log2", "f", "GLSL.std.450", GLSL450_LOG2, { 1 }, 0 },
-       { "sqrt", "f", "GLSL.std.450", GLSL450_SQRT, { 1 }, 0 },
-       { "inversesqrt", "f", "GLSL.std.450", GLSL450_INVERSE_SQRT, { 1 }, 0 },
-       { "abs", "f", "GLSL.std.450", GLSL450_F_ABS, { 1 }, 0 },
-       { "abs", "i", "GLSL.std.450", GLSL450_S_ABS, { 1 }, 0 },
-       { "sign", "f", "GLSL.std.450", GLSL450_F_SIGN, { 1 }, 0 },
-       { "sign", "i", "GLSL.std.450", GLSL450_S_SIGN, { 1 }, 0 },
-       { "floor", "f", "GLSL.std.450", GLSL450_FLOOR, { 1 }, 0 },
-       { "trunc", "f", "GLSL.std.450", GLSL450_TRUNC, { 1 }, 0 },
-       { "round", "f", "GLSL.std.450", GLSL450_ROUND, { 1 }, 0 },
-       { "roundEven", "f", "GLSL.std.450", GLSL450_ROUND_EVEN, { 1 }, 0 },
-       { "ceil", "f", "GLSL.std.450", GLSL450_CEIL, { 1 }, 0 },
-       { "fract", "f", "GLSL.std.450", GLSL450_FRACT, { 1 }, 0 },
-       { "mod", "f", "", OP_F_MOD, { 1, 2 }, 0 },
-       { "min", "ff", "GLSL.std.450", GLSL450_F_MIN, { 1, 2 }, 0 },
-       { "min", "ii", "GLSL.std.450", GLSL450_S_MIN, { 1, 2 }, 0 },
-       { "max", "ff", "GLSL.std.450", GLSL450_F_MAX, { 1, 2 }, 0 },
-       { "max", "ii", "GLSL.std.450", GLSL450_S_MAX, { 1, 2 }, 0 },
-       { "clamp", "fff", "GLSL.std.450", GLSL450_F_CLAMP, { 1, 2, 3 }, 0 },
-       { "clamp", "iii", "GLSL.std.450", GLSL450_S_CLAMP, { 1, 2, 3 }, 0 },
-       { "mix", "fff", "GLSL.std.450", GLSL450_F_MIX, { 1, 2, 3 }, 0 },
-       { "mix", "ffb", "", OP_SELECT, { 3, 2, 1 }, 0 },
-       { "mix", "iib", "", OP_SELECT, { 3, 2, 1 }, 0 },
-       { "step", "ff", "GLSL.std.450", GLSL450_F_STEP, { 1, 2 }, 0 },
-       { "smoothstep", "fff", "GLSL.std.450", GLSL450_F_SMOOTH_STEP, { 1, 2, 3 }, 0 },
-       { "isnan", "f", "", OP_IS_NAN, { 1 }, 0 },
-       { "isinf", "f", "", OP_IS_INF, { 1 }, 0 },
-       { "fma", "fff", "GLSL.std.450", GLSL450_F_FMA, { 1, 2, 3 }, 0 },
-       { "length", "f", "GLSL.std.450", GLSL450_LENGTH, { 1 }, 0 },
-       { "distance", "ff", "GLSL.std.450", GLSL450_DISTANCE, { 1, 2 }, 0 },
-       { "dot", "ff", "", OP_DOT, { 1, 2 }, 0 },
-       { "cross", "ff", "GLSL.std.450", GLSL450_CROSS, { 1, 2 }, 0 },
-       { "normalize", "f", "GLSL.std.450", GLSL450_NORMALIZE, { 1 }, 0 },
-       { "faceforward", "fff", "GLSL.std.450", GLSL450_FACE_FORWARD, { 1, 2, 3 }, 0 },
-       { "reflect", "ff", "GLSL.std.450", GLSL450_REFLECT, { 1, 2 }, 0 },
-       { "refract", "fff", "GLSL.std.450", GLSL450_REFRACT, { 1, 2, 3 }, 0 },
-       { "matrixCompMult", "ff", "", 0, { 0 }, &SpirVGenerator::visit_builtin_matrix_comp_mult },
-       { "outerProduct", "ff", "", OP_OUTER_PRODUCT, { 1, 2 }, 0 },
-       { "transpose", "f", "", OP_TRANSPOSE, { 1 }, 0 },
-       { "determinant", "f", "GLSL.std.450", GLSL450_DETERMINANT, { 1 }, 0 },
-       { "inverse", "f", "GLSL.std.450", GLSL450_MATRIX_INVERSE, { 1 }, 0 },
-       { "lessThan", "ff", "", OP_F_ORD_LESS_THAN, { 1, 2 }, 0 },
-       { "lessThan", "ii", "", OP_S_LESS_THAN, { 1, 2 }, 0 },
-       { "lessThanEqual", "ff", "", OP_F_ORD_LESS_THAN_EQUAL, { 1, 2 }, 0 },
-       { "lessThanEqual", "ii", "", OP_S_LESS_THAN_EQUAL, { 1, 2 }, 0 },
-       { "greaterThan", "ff", "", OP_F_ORD_GREATER_THAN, { 1, 2 }, 0 },
-       { "greaterThan", "ii", "", OP_S_GREATER_THAN, { 1, 2 }, 0 },
-       { "greaterThanEqual", "ff", "", OP_F_ORD_GREATER_THAN_EQUAL, { 1, 2 }, 0 },
-       { "greaterThanEqual", "ii", "", OP_S_GREATER_THAN_EQUAL, { 1, 2 }, 0 },
-       { "equal", "ff", "", OP_F_ORD_EQUAL, { 1, 2 }, 0 },
-       { "equal", "ii", "", OP_I_EQUAL, { 1, 2 }, 0 },
-       { "notEqual", "ff", "", OP_F_ORD_NOT_EQUAL, { 1, 2 }, 0 },
-       { "notEqual", "ii", "", OP_I_NOT_EQUAL, { 1, 2 }, 0 },
-       { "any", "b", "", OP_ANY, { 1 }, 0 },
-       { "all", "b", "", OP_ALL, { 1 }, 0 },
-       { "not", "b", "", OP_LOGICAL_NOT, { 1 }, 0 },
-       { "bitfieldExtract", "iii", "", OP_BIT_FIELD_S_EXTRACT, { 1, 2, 3 }, 0 },
-       { "bitfieldInsert", "iiii", "", OP_BIT_FIELD_INSERT, { 1, 2, 3, 4 }, 0 },
-       { "bitfieldReverse", "i", "", OP_BIT_REVERSE, { 1 }, 0 },
-       { "bitCount", "i", "", OP_BIT_COUNT, { 1 }, 0 },
-       { "findLSB", "i", "GLSL.std.450", GLSL450_FIND_I_LSB, { 1 }, 0 },
-       { "findMSB", "i", "GLSL.std.450", GLSL450_FIND_S_MSB, { 1 }, 0 },
-       { "textureSize", "", "", OP_IMAGE_QUERY_SIZE_LOD, { 1, 2 }, 0 },
-       { "texture", "", "", 0, { }, &SpirVGenerator::visit_builtin_texture },
-       { "textureLod", "", "", 0, { }, &SpirVGenerator::visit_builtin_texture },
-       { "texelFetch", "", "", 0, { }, &SpirVGenerator::visit_builtin_texel_fetch },
-       { "EmitVertex", "", "", OP_EMIT_VERTEX, { }, 0 },
-       { "EndPrimitive", "", "", OP_END_PRIMITIVE, { }, 0 },
-       { "dFdx", "f", "", OP_DP_DX, { 1 }, 0 },
-       { "dFdy", "f", "", OP_DP_DY, { 1 }, 0 },
-       { "dFdxFine", "f", "", OP_DP_DX_FINE, { 1 }, 0 },
-       { "dFdyFine", "f", "", OP_DP_DY_FINE, { 1 }, 0 },
-       { "dFdxCoarse", "f", "", OP_DP_DX_COARSE, { 1 }, 0 },
-       { "dFdyCoarse", "f", "", OP_DP_DY_COARSE, { 1 }, 0 },
-       { "fwidth", "f", "", OP_FWIDTH, { 1 }, 0 },
-       { "fwidthFine", "f", "", OP_FWIDTH_FINE, { 1 }, 0 },
-       { "fwidthCoarse", "f", "", OP_FWIDTH_COARSE, { 1 }, 0 },
-       { "interpolateAtCentroid", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
-       { "interpolateAtSample", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
-       { "interpolateAtOffset", "", "", 0, { }, &SpirVGenerator::visit_builtin_interpolate },
-       { "", "", "", 0, { }, 0 }
+       { "radians", "f", "GLSL.std.450", GLSL450_RADIANS, { 1 }, 0, 0 },
+       { "degrees", "f", "GLSL.std.450", GLSL450_DEGREES, { 1 }, 0, 0 },
+       { "sin", "f", "GLSL.std.450", GLSL450_SIN, { 1 }, 0, 0 },
+       { "cos", "f", "GLSL.std.450", GLSL450_COS, { 1 }, 0, 0 },
+       { "tan", "f", "GLSL.std.450", GLSL450_TAN, { 1 }, 0, 0 },
+       { "asin", "f", "GLSL.std.450", GLSL450_ASIN, { 1 }, 0, 0 },
+       { "acos", "f", "GLSL.std.450", GLSL450_ACOS, { 1 }, 0, 0 },
+       { "atan", "f", "GLSL.std.450", GLSL450_ATAN, { 1 }, 0, 0 },
+       { "atan", "ff", "GLSL.std.450", GLSL450_ATAN2, { 1, 2 }, 0, 0 },
+       { "sinh", "f", "GLSL.std.450", GLSL450_SINH, { 1 }, 0, 0 },
+       { "cosh", "f", "GLSL.std.450", GLSL450_COSH, { 1 }, 0, 0 },
+       { "tanh", "f", "GLSL.std.450", GLSL450_TANH, { 1 }, 0, 0 },
+       { "asinh", "f", "GLSL.std.450", GLSL450_ASINH, { 1 }, 0, 0 },
+       { "acosh", "f", "GLSL.std.450", GLSL450_ACOSH, { 1 }, 0, 0 },
+       { "atanh", "f", "GLSL.std.450", GLSL450_ATANH, { 1 }, 0, 0 },
+       { "pow", "ff", "GLSL.std.450", GLSL450_POW, { 1, 2 }, 0, 0 },
+       { "exp", "f", "GLSL.std.450", GLSL450_EXP, { 1 }, 0, 0 },
+       { "log", "f", "GLSL.std.450", GLSL450_LOG, { 1 }, 0, 0 },
+       { "exp2", "f", "GLSL.std.450", GLSL450_EXP2, { 1 }, 0, 0 },
+       { "log2", "f", "GLSL.std.450", GLSL450_LOG2, { 1 }, 0, 0 },
+       { "sqrt", "f", "GLSL.std.450", GLSL450_SQRT, { 1 }, 0, 0 },
+       { "inversesqrt", "f", "GLSL.std.450", GLSL450_INVERSE_SQRT, { 1 }, 0, 0 },
+       { "abs", "f", "GLSL.std.450", GLSL450_F_ABS, { 1 }, 0, 0 },
+       { "abs", "i", "GLSL.std.450", GLSL450_S_ABS, { 1 }, 0, 0 },
+       { "sign", "f", "GLSL.std.450", GLSL450_F_SIGN, { 1 }, 0, 0 },
+       { "sign", "i", "GLSL.std.450", GLSL450_S_SIGN, { 1 }, 0, 0 },
+       { "floor", "f", "GLSL.std.450", GLSL450_FLOOR, { 1 }, 0, 0 },
+       { "trunc", "f", "GLSL.std.450", GLSL450_TRUNC, { 1 }, 0, 0 },
+       { "round", "f", "GLSL.std.450", GLSL450_ROUND, { 1 }, 0, 0 },
+       { "roundEven", "f", "GLSL.std.450", GLSL450_ROUND_EVEN, { 1 }, 0, 0 },
+       { "ceil", "f", "GLSL.std.450", GLSL450_CEIL, { 1 }, 0, 0 },
+       { "fract", "f", "GLSL.std.450", GLSL450_FRACT, { 1 }, 0, 0 },
+       { "mod", "f", "", OP_F_MOD, { 1, 2 }, 0, 0 },
+       { "min", "ff", "GLSL.std.450", GLSL450_F_MIN, { 1, 2 }, 0, 0 },
+       { "min", "ii", "GLSL.std.450", GLSL450_S_MIN, { 1, 2 }, 0, 0 },
+       { "min", "uu", "GLSL.std.450", GLSL450_U_MIN, { 1, 2 }, 0, 0 },
+       { "max", "ff", "GLSL.std.450", GLSL450_F_MAX, { 1, 2 }, 0, 0 },
+       { "max", "ii", "GLSL.std.450", GLSL450_S_MAX, { 1, 2 }, 0, 0 },
+       { "max", "uu", "GLSL.std.450", GLSL450_U_MAX, { 1, 2 }, 0, 0 },
+       { "clamp", "fff", "GLSL.std.450", GLSL450_F_CLAMP, { 1, 2, 3 }, 0, 0 },
+       { "clamp", "iii", "GLSL.std.450", GLSL450_S_CLAMP, { 1, 2, 3 }, 0, 0 },
+       { "clamp", "uuu", "GLSL.std.450", GLSL450_U_CLAMP, { 1, 2, 3 }, 0, 0 },
+       { "mix", "fff", "GLSL.std.450", GLSL450_F_MIX, { 1, 2, 3 }, 0, 0 },
+       { "mix", "ffb", "", OP_SELECT, { 3, 2, 1 }, 0, 0 },
+       { "mix", "iib", "", OP_SELECT, { 3, 2, 1 }, 0, 0 },
+       { "mix", "uub", "", OP_SELECT, { 3, 2, 1 }, 0, 0 },
+       { "step", "ff", "GLSL.std.450", GLSL450_F_STEP, { 1, 2 }, 0, 0 },
+       { "smoothstep", "fff", "GLSL.std.450", GLSL450_F_SMOOTH_STEP, { 1, 2, 3 }, 0, 0 },
+       { "isnan", "f", "", OP_IS_NAN, { 1 }, 0, 0 },
+       { "isinf", "f", "", OP_IS_INF, { 1 }, 0, 0 },
+       { "fma", "fff", "GLSL.std.450", GLSL450_F_FMA, { 1, 2, 3 }, 0, 0 },
+       { "length", "f", "GLSL.std.450", GLSL450_LENGTH, { 1 }, 0, 0 },
+       { "distance", "ff", "GLSL.std.450", GLSL450_DISTANCE, { 1, 2 }, 0, 0 },
+       { "dot", "ff", "", OP_DOT, { 1, 2 }, 0, 0 },
+       { "cross", "ff", "GLSL.std.450", GLSL450_CROSS, { 1, 2 }, 0, 0 },
+       { "normalize", "f", "GLSL.std.450", GLSL450_NORMALIZE, { 1 }, 0, 0 },
+       { "faceforward", "fff", "GLSL.std.450", GLSL450_FACE_FORWARD, { 1, 2, 3 }, 0, 0 },
+       { "reflect", "ff", "GLSL.std.450", GLSL450_REFLECT, { 1, 2 }, 0, 0 },
+       { "refract", "fff", "GLSL.std.450", GLSL450_REFRACT, { 1, 2, 3 }, 0, 0 },
+       { "matrixCompMult", "ff", "", 0, { 0 }, 0, &SpirVGenerator::visit_builtin_matrix_comp_mult },
+       { "outerProduct", "ff", "", OP_OUTER_PRODUCT, { 1, 2 }, 0, 0 },
+       { "transpose", "f", "", OP_TRANSPOSE, { 1 }, 0, 0 },
+       { "determinant", "f", "GLSL.std.450", GLSL450_DETERMINANT, { 1 }, 0, 0 },
+       { "inverse", "f", "GLSL.std.450", GLSL450_MATRIX_INVERSE, { 1 }, 0, 0 },
+       { "lessThan", "ff", "", OP_F_ORD_LESS_THAN, { 1, 2 }, 0, 0 },
+       { "lessThan", "ii", "", OP_S_LESS_THAN, { 1, 2 }, 0, 0 },
+       { "lessThan", "uu", "", OP_U_LESS_THAN, { 1, 2 }, 0, 0 },
+       { "lessThanEqual", "ff", "", OP_F_ORD_LESS_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "lessThanEqual", "ii", "", OP_S_LESS_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "lessThanEqual", "uu", "", OP_U_LESS_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "greaterThan", "ff", "", OP_F_ORD_GREATER_THAN, { 1, 2 }, 0, 0 },
+       { "greaterThan", "ii", "", OP_S_GREATER_THAN, { 1, 2 }, 0, 0 },
+       { "greaterThan", "uu", "", OP_U_GREATER_THAN, { 1, 2 }, 0, 0 },
+       { "greaterThanEqual", "ff", "", OP_F_ORD_GREATER_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "greaterThanEqual", "ii", "", OP_S_GREATER_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "greaterThanEqual", "uu", "", OP_U_GREATER_THAN_EQUAL, { 1, 2 }, 0, 0 },
+       { "equal", "ff", "", OP_F_ORD_EQUAL, { 1, 2 }, 0, 0 },
+       { "equal", "ii", "", OP_I_EQUAL, { 1, 2 }, 0, 0 },
+       { "equal", "uu", "", OP_I_EQUAL, { 1, 2 }, 0, 0 },
+       { "notEqual", "ff", "", OP_F_ORD_NOT_EQUAL, { 1, 2 }, 0, 0 },
+       { "notEqual", "ii", "", OP_I_NOT_EQUAL, { 1, 2 }, 0, 0 },
+       { "notEqual", "uu", "", OP_I_NOT_EQUAL, { 1, 2 }, 0, 0 },
+       { "any", "b", "", OP_ANY, { 1 }, 0, 0 },
+       { "all", "b", "", OP_ALL, { 1 }, 0, 0 },
+       { "not", "b", "", OP_LOGICAL_NOT, { 1 }, 0, 0 },
+       { "bitfieldExtract", "iii", "", OP_BIT_FIELD_S_EXTRACT, { 1, 2, 3 }, 0, 0 },
+       { "bitfieldExtract", "uii", "", OP_BIT_FIELD_U_EXTRACT, { 1, 2, 3 }, 0, 0 },
+       { "bitfieldInsert", "iiii", "", OP_BIT_FIELD_INSERT, { 1, 2, 3, 4 }, 0, 0 },
+       { "bitfieldInsert", "uuii", "", OP_BIT_FIELD_INSERT, { 1, 2, 3, 4 }, 0, 0 },
+       { "bitfieldReverse", "i", "", OP_BIT_REVERSE, { 1 }, 0, 0 },
+       { "bitfieldReverse", "u", "", OP_BIT_REVERSE, { 1 }, 0, 0 },
+       { "bitCount", "i", "", OP_BIT_COUNT, { 1 }, 0, 0 },
+       { "findLSB", "i", "GLSL.std.450", GLSL450_FIND_I_LSB, { 1 }, 0, 0 },
+       { "findLSB", "u", "GLSL.std.450", GLSL450_FIND_I_LSB, { 1 }, 0, 0 },
+       { "findMSB", "i", "GLSL.std.450", GLSL450_FIND_S_MSB, { 1 }, 0, 0 },
+       { "findMSB", "u", "GLSL.std.450", GLSL450_FIND_U_MSB, { 1 }, 0, 0 },
+       { "textureSize", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "textureQueryLod", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "textureQueryLevels", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "textureSamples", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "texture", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture },
+       { "textureLod", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture },
+       { "texelFetch", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_fetch },
+       { "imageSize", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "imageSamples", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "imageLoad", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_fetch },
+       { "imageStore", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_store },
+       { "EmitVertex", "", "", OP_EMIT_VERTEX, { }, 0, 0 },
+       { "EndPrimitive", "", "", OP_END_PRIMITIVE, { }, 0, 0 },
+       { "dFdx", "f", "", OP_DP_DX, { 1 }, 0, 0 },
+       { "dFdy", "f", "", OP_DP_DY, { 1 }, 0, 0 },
+       { "dFdxFine", "f", "", OP_DP_DX_FINE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "dFdyFine", "f", "", OP_DP_DY_FINE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "dFdxCoarse", "f", "", OP_DP_DX_COARSE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "dFdyCoarse", "f", "", OP_DP_DY_COARSE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "fwidth", "f", "", OP_FWIDTH, { 1 }, 0, 0 },
+       { "fwidthFine", "f", "", OP_FWIDTH_FINE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "fwidthCoarse", "f", "", OP_FWIDTH_COARSE, { 1 }, CAP_DERIVATIVE_CONTROL, 0 },
+       { "interpolateAtCentroid", "", "", 0, { }, CAP_INTERPOLATION_FUNCTION, &SpirVGenerator::visit_builtin_interpolate },
+       { "interpolateAtSample", "", "", 0, { }, CAP_INTERPOLATION_FUNCTION, &SpirVGenerator::visit_builtin_interpolate },
+       { "interpolateAtOffset", "", "", 0, { }, CAP_INTERPOLATION_FUNCTION, &SpirVGenerator::visit_builtin_interpolate },
+       { "", "", "", 0, { }, 0, 0 }
 };
 
 SpirVGenerator::SpirVGenerator():
-       stage(0),
-       current_function(0),
-       writer(content),
-       next_id(1),
-       r_expression_result_id(0),
-       constant_expression(false),
-       spec_constant(false),
-       reachable(false),
-       composite_access(false),
-       r_composite_base_id(0),
-       r_composite_base(0),
-       assignment_source_id(0),
-       loop_merge_block_id(0),
-       loop_continue_target_id(0)
+       writer(content)
 { }
 
-void SpirVGenerator::apply(Module &module)
+void SpirVGenerator::apply(Module &module, const Features &f)
 {
+       features = f;
        use_capability(CAP_SHADER);
 
-       for(list<Stage>::iterator i=module.stages.begin(); i!=module.stages.end(); ++i)
+       for(Stage &s: module.stages)
        {
-               stage = &*i;
+               stage = &s;
                interface_layouts.clear();
-               i->content.visit(*this);
+               s.content.visit(*this);
        }
 
        writer.finalize(SPIRV_GENERATOR_MSP, next_id);
@@ -176,6 +187,14 @@ SpirVGenerator::BuiltinSemantic SpirVGenerator::get_builtin_semantic(const strin
                return BUILTIN_INVOCATION_ID;
        else if(name=="gl_Layer")
                return BUILTIN_LAYER;
+       else if(name=="gl_TessLevelOuter")
+               return BUILTIN_TESS_LEVEL_OUTER;
+       else if(name=="gl_TessLevelInner")
+               return BUILTIN_TESS_LEVEL_INNER;
+       else if(name=="gl_TessCoord")
+               return BUILTIN_TESS_COORD;
+       else if(name=="gl_PatchVerticesIn")
+               return BUILTIN_PATCH_VERTICES;
        else if(name=="gl_FragCoord")
                return BUILTIN_FRAG_COORD;
        else if(name=="gl_PointCoord")
@@ -188,10 +207,66 @@ SpirVGenerator::BuiltinSemantic SpirVGenerator::get_builtin_semantic(const strin
                return BUILTIN_SAMPLE_POSITION;
        else if(name=="gl_FragDepth")
                return BUILTIN_FRAG_DEPTH;
+       else if(name=="gl_NumWorkGroups")
+               return BUILTIN_NUM_WORKGROUPS;
+       else if(name=="gl_WorkGroupSize")
+               return BUILTIN_WORKGROUP_SIZE;
+       else if(name=="gl_WorkGroupID")
+               return BUILTIN_WORKGROUP_ID;
+       else if(name=="gl_LocalInvocationID")
+               return BUILTIN_LOCAL_INVOCATION_ID;
+       else if(name=="gl_GlobalInvocationID")
+               return BUILTIN_GLOBAL_INVOCATION_ID;
+       else if(name=="gl_LocalInvocationIndex")
+               return BUILTIN_LOCAL_INVOCATION_INDEX;
        else
                throw invalid_argument("SpirVGenerator::get_builtin_semantic");
 }
 
+SpirVFormat SpirVGenerator::get_format(const std::string &name)
+{
+       if(name.empty())
+               return FORMAT_UNKNOWN;
+       else if(name=="rgba32f")
+               return FORMAT_RGBA32F;
+       else if(name=="rgba16f")
+               return FORMAT_RGBA16F;
+       else if(name=="r32f")
+               return FORMAT_R32F;
+       else if(name=="rgba8")
+               return FORMAT_RGBA8;
+       else if(name=="rgba8_snorm")
+               return FORMAT_RGBA8_SNORM;
+       else if(name=="rg32f")
+               return FORMAT_RG32F;
+       else if(name=="rg16f")
+               return FORMAT_RG16F;
+       else if(name=="r16f")
+               return FORMAT_R16F;
+       else if(name=="rgba16")
+               return FORMAT_RGBA16;
+       else if(name=="rg16")
+               return FORMAT_RG16;
+       else if(name=="rg8")
+               return FORMAT_RG8;
+       else if(name=="r16")
+               return FORMAT_RG16;
+       else if(name=="r8")
+               return FORMAT_RG8;
+       else if(name=="rgba16_snorm")
+               return FORMAT_RGBA16_SNORM;
+       else if(name=="rg16_snorm")
+               return FORMAT_RG16_SNORM;
+       else if(name=="rg8_snorm")
+               return FORMAT_RG8_SNORM;
+       else if(name=="r16_snorm")
+               return FORMAT_RG16_SNORM;
+       else if(name=="r8_snorm")
+               return FORMAT_RG8_SNORM;
+       else
+               throw invalid_argument("SpirVGenerator::get_format");
+}
+
 void SpirVGenerator::use_capability(Capability cap)
 {
        if(used_capabilities.count(cap))
@@ -222,8 +297,28 @@ SpirVGenerator::Id SpirVGenerator::get_id(Node &node) const
 
 SpirVGenerator::Id SpirVGenerator::allocate_id(Node &node, Id type_id)
 {
+       auto i = declared_ids.find(&node);
+       if(i!=declared_ids.end())
+       {
+               if(i->second.type_id)
+                       throw key_error(&node);
+               i->second.type_id = type_id;
+               return i->second.id;
+       }
+
        Id id = next_id++;
-       insert_unique(declared_ids, &node, Declaration(id, type_id));
+       declared_ids.insert(make_pair(&node, Declaration(id, type_id)));
+       return id;
+}
+
+SpirVGenerator::Id SpirVGenerator::allocate_forward_id(Node &node)
+{
+       auto i = declared_ids.find(&node);
+       if(i!=declared_ids.end())
+               return i->second.id;
+
+       Id id = next_id++;
+       declared_ids.insert(make_pair(&node, Declaration(id, 0)));
        return id;
 }
 
@@ -250,6 +345,8 @@ SpirVGenerator::ConstantKey SpirVGenerator::get_constant_key(Id type_id, const V
                return ConstantKey(type_id, value.value<bool>());
        else if(value.check_type<int>())
                return ConstantKey(type_id, value.value<int>());
+       else if(value.check_type<unsigned>())
+               return ConstantKey(type_id, value.value<unsigned>());
        else if(value.check_type<float>())
                return ConstantKey(type_id, value.value<float>());
        else
@@ -281,10 +378,10 @@ SpirVGenerator::Id SpirVGenerator::get_vector_constant_id(Id type_id, unsigned s
        return const_id;
 }
 
-SpirVGenerator::Id SpirVGenerator::get_standard_type_id(BasicTypeDeclaration::Kind kind, unsigned size)
+SpirVGenerator::Id SpirVGenerator::get_standard_type_id(BasicTypeDeclaration::Kind kind, unsigned size, bool sign)
 {
-       Id base_id = (size>1 ? get_standard_type_id(kind, 1) : 0);
-       Id &type_id = standard_type_ids[TypeKey(base_id, (size>1 ? size : static_cast<unsigned>(kind)))];
+       Id base_id = (size>1 ? get_standard_type_id(kind, 1, sign) : 0);
+       Id &type_id = standard_type_ids[base_id ? TypeKey(base_id, size) : TypeKey(kind, sign)];
        if(!type_id)
        {
                type_id = next_id++;
@@ -295,7 +392,7 @@ SpirVGenerator::Id SpirVGenerator::get_standard_type_id(BasicTypeDeclaration::Ki
                else if(kind==BasicTypeDeclaration::BOOL)
                        writer.write_op(content.globals, OP_TYPE_BOOL, type_id);
                else if(kind==BasicTypeDeclaration::INT)
-                       writer.write_op(content.globals, OP_TYPE_INT, type_id, 32, 1);
+                       writer.write_op(content.globals, OP_TYPE_INT, type_id, 32, sign);
                else if(kind==BasicTypeDeclaration::FLOAT)
                        writer.write_op(content.globals, OP_TYPE_FLOAT, type_id, 32);
                else
@@ -306,14 +403,14 @@ SpirVGenerator::Id SpirVGenerator::get_standard_type_id(BasicTypeDeclaration::Ki
 
 bool SpirVGenerator::is_scalar_type(Id type_id, BasicTypeDeclaration::Kind kind) const
 {
-       map<TypeKey, Id>::const_iterator i = standard_type_ids.find(TypeKey(0, kind));
+       auto i = standard_type_ids.find(TypeKey(kind, true));
        return (i!=standard_type_ids.end() && i->second==type_id);
 }
 
-SpirVGenerator::Id SpirVGenerator::get_array_type_id(TypeDeclaration &base_type, Id size_id)
+SpirVGenerator::Id SpirVGenerator::get_array_type_id(TypeDeclaration &base_type, Id size_id, bool extended_align)
 {
        Id base_type_id = get_id(base_type);
-       Id &array_type_id = array_type_ids[TypeKey(base_type_id, size_id)];
+       Id &array_type_id = array_type_ids[TypeKey(base_type_id, extended_align*0x400000 | size_id)];
        if(!array_type_id)
        {
                array_type_id = next_id++;
@@ -323,6 +420,8 @@ SpirVGenerator::Id SpirVGenerator::get_array_type_id(TypeDeclaration &base_type,
                        writer.write_op(content.globals, OP_TYPE_RUNTIME_ARRAY, array_type_id, base_type_id);
 
                unsigned stride = MemoryRequirementsCalculator().apply(base_type).stride;
+               if(extended_align)
+                       stride = (stride+15)&~15U;
                writer.write_op_decorate(array_type_id, DECO_ARRAY_STRIDE, stride);
        }
 
@@ -345,17 +444,13 @@ SpirVGenerator::Id SpirVGenerator::get_variable_type_id(const VariableDeclaratio
        if(const BasicTypeDeclaration *basic = dynamic_cast<const BasicTypeDeclaration *>(var.type_declaration))
                if(basic->kind==BasicTypeDeclaration::ARRAY)
                {
-                       Id size_id = 0;
-                       if(var.array_size)
-                       {
-                               SetFlag set_const(constant_expression);
-                               r_expression_result_id = 0;
-                               var.array_size->visit(*this);
-                               size_id = r_expression_result_id;
-                       }
-                       else
-                               size_id = get_constant_id(get_standard_type_id(BasicTypeDeclaration::INT, 1), 1);
-                       return get_array_type_id(*basic->base_type, size_id);
+                       if(!var.array_size)
+                               throw logic_error("array without size");
+
+                       SetFlag set_const(constant_expression);
+                       r_expression_result_id = 0;
+                       var.array_size->visit(*this);
+                       return get_array_type_id(*basic->base_type, r_expression_result_id, basic->extended_alignment);
                }
 
        return get_id(*var.type_declaration);
@@ -374,7 +469,7 @@ SpirVGenerator::Id SpirVGenerator::get_load_id(VariableDeclaration &var)
 
 void SpirVGenerator::prune_loads(Id min_id)
 {
-       for(map<const VariableDeclaration *, Id>::iterator i=variable_load_ids.begin(); i!=variable_load_ids.end(); )
+       for(auto i=variable_load_ids.begin(); i!=variable_load_ids.end(); )
        {
                if(i->second>=min_id)
                        variable_load_ids.erase(i++);
@@ -460,8 +555,8 @@ SpirVGenerator::Id SpirVGenerator::write_construct(Id type_id, const Id *elem_id
 
 void SpirVGenerator::visit(Block &block)
 {
-       for(NodeList<Statement>::iterator i=block.body.begin(); i!=block.body.end(); ++i)
-               (*i)->visit(*this);
+       for(const RefPtr<Statement> &s: block.body)
+               s->visit(*this);
 }
 
 void SpirVGenerator::visit(Literal &literal)
@@ -491,8 +586,17 @@ void SpirVGenerator::visit(VariableReference &var)
        r_constant_result = false;
        if(composite_access)
        {
-               r_composite_base = var.declaration;
                r_expression_result_id = 0;
+               if(assignment_source_id)
+                       variable_load_ids.erase(var.declaration);
+               else
+               {
+                       auto i = variable_load_ids.find(var.declaration);
+                       if(i!=variable_load_ids.end())
+                               r_expression_result_id = i->second;
+               }
+               if(!r_expression_result_id)
+                       r_composite_base = var.declaration;
        }
        else if(assignment_source_id)
        {
@@ -504,16 +608,6 @@ void SpirVGenerator::visit(VariableReference &var)
                r_expression_result_id = get_load_id(*var.declaration);
 }
 
-void SpirVGenerator::visit(InterfaceBlockReference &iface)
-{
-       if(!composite_access || !current_function)
-               throw internal_error("invalid interface block reference");
-
-       r_composite_base = iface.declaration;
-       r_expression_result_id = 0;
-       r_constant_result = false;
-}
-
 void SpirVGenerator::generate_composite_access(TypeDeclaration &result_type)
 {
        Opcode opcode;
@@ -525,13 +619,13 @@ void SpirVGenerator::generate_composite_access(TypeDeclaration &result_type)
                        throw internal_error("composite access through pointer in constant context");
 
                Id int32_type_id = get_standard_type_id(BasicTypeDeclaration::INT, 1);
-               for(vector<unsigned>::iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
-                       *i = (*i<0x400000 ? get_constant_id(int32_type_id, static_cast<int>(*i)) : *i&0x3FFFFF);
+               for(unsigned &i: r_composite_chain)
+                       i = (i<0x400000 ? get_constant_id(int32_type_id, static_cast<int>(i)) : i&0x3FFFFF);
 
                /* Find the storage class of the base and obtain appropriate pointer type
                for the result. */
                const Declaration &base_decl = get_item(declared_ids, r_composite_base);
-               map<TypeKey, Id>::const_iterator i = pointer_type_ids.begin();
+               auto i = pointer_type_ids.begin();
                for(; (i!=pointer_type_ids.end() && i->second!=base_decl.type_id); ++i) ;
                if(i==pointer_type_ids.end())
                        throw internal_error("could not find storage class");
@@ -543,18 +637,18 @@ void SpirVGenerator::generate_composite_access(TypeDeclaration &result_type)
                throw internal_error("assignment to temporary composite");
        else
        {
-               for(vector<unsigned>::iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
-                       for(map<ConstantKey, Id>::iterator j=constant_ids.begin(); (*i>=0x400000 && j!=constant_ids.end()); ++j)
-                               if(j->second==(*i&0x3FFFFF))
-                                       *i = j->first.int_value;
+               for(unsigned &i: r_composite_chain)
+                       for(auto j=constant_ids.begin(); (i>=0x400000 && j!=constant_ids.end()); ++j)
+                               if(j->second==(i&0x3FFFFF))
+                                       i = j->first.int_value;
 
                opcode = OP_COMPOSITE_EXTRACT;
        }
 
        Id access_id = begin_expression(opcode, access_type_id, 1+r_composite_chain.size());
        writer.write(r_composite_base_id);
-       for(vector<unsigned>::const_iterator i=r_composite_chain.begin(); i!=r_composite_chain.end(); ++i)
-               writer.write(*i);
+       for(unsigned i: r_composite_chain)
+               writer.write(i);
        end_expression(opcode);
 
        r_constant_result = false;
@@ -656,6 +750,9 @@ void SpirVGenerator::visit(Swizzle &swizzle)
 
 void SpirVGenerator::visit(UnaryExpression &unary)
 {
+       if(composite_access)
+               return visit_isolated(unary);
+
        unary.expression->visit(*this);
 
        char oper = unary.oper->token[0];
@@ -740,6 +837,8 @@ void SpirVGenerator::visit(BinaryExpression &binary)
                visit_isolated(*binary.right);
                return visit_composite(*binary.left, 0x400000|r_expression_result_id, *binary.type);
        }
+       else if(composite_access)
+               return visit_isolated(binary);
 
        if(assignment_source_id)
                throw internal_error("invalid binary expression in assignment target");
@@ -769,8 +868,14 @@ void SpirVGenerator::visit(BinaryExpression &binary)
        if((oper=='<' || oper=='>') && oper2!=oper)
        {
                if(basic_left.kind==BasicTypeDeclaration::INT)
-                       opcode = (oper=='<' ? (oper2=='=' ? OP_S_LESS_THAN_EQUAL : OP_S_LESS_THAN) :
-                               (oper2=='=' ? OP_S_GREATER_THAN_EQUAL : OP_S_GREATER_THAN));
+               {
+                       if(basic_left.sign)
+                               opcode = (oper=='<' ? (oper2=='=' ? OP_S_LESS_THAN_EQUAL : OP_S_LESS_THAN) :
+                                       (oper2=='=' ? OP_S_GREATER_THAN_EQUAL : OP_S_GREATER_THAN));
+                       else
+                               opcode = (oper=='<' ? (oper2=='=' ? OP_U_LESS_THAN_EQUAL : OP_U_LESS_THAN) :
+                                       (oper2=='=' ? OP_U_GREATER_THAN_EQUAL : OP_U_GREATER_THAN));
+               }
                else if(basic_left.kind==BasicTypeDeclaration::FLOAT)
                        opcode = (oper=='<' ? (oper2=='=' ? OP_F_ORD_LESS_THAN_EQUAL : OP_F_ORD_LESS_THAN) :
                                (oper2=='=' ? OP_F_ORD_GREATER_THAN_EQUAL : OP_F_ORD_GREATER_THAN));
@@ -812,9 +917,10 @@ void SpirVGenerator::visit(BinaryExpression &binary)
 
                                compare_id = write_construct(bool_vec_type_id, column_ids, n_elems);
                        }
+                       else
+                               throw internal_error("unsupported types for non-scalar equality comparison");
 
-                       if(compare_id)
-                               r_expression_result_id = write_expression(combine_op, result_type_id, compare_id);
+                       r_expression_result_id = write_expression(combine_op, result_type_id, compare_id);
                        return;
                }
        }
@@ -835,12 +941,17 @@ void SpirVGenerator::visit(BinaryExpression &binary)
        else if(oper=='>' && oper2==oper && elem.kind==BasicTypeDeclaration::INT)
                opcode = OP_SHIFT_RIGHT_ARITHMETIC;
        else if(oper=='%' && elem.kind==BasicTypeDeclaration::INT)
-               opcode = OP_S_MOD;
+               opcode = (elem.sign ? OP_S_MOD : OP_U_MOD);
        else if(oper=='+' || oper=='-' || oper=='*' || oper=='/')
        {
                Opcode elem_op = OP_NOP;
                if(elem.kind==BasicTypeDeclaration::INT)
-                       elem_op = (oper=='+' ? OP_I_ADD : oper=='-' ? OP_I_SUB : oper=='*' ? OP_I_MUL : OP_S_DIV);
+               {
+                       if(oper=='/')
+                               elem_op = (elem.sign ? OP_S_DIV : OP_U_DIV);
+                       else
+                               elem_op = (oper=='+' ? OP_I_ADD : oper=='-' ? OP_I_SUB : OP_I_MUL);
+               }
                else if(elem.kind==BasicTypeDeclaration::FLOAT)
                        elem_op = (oper=='+' ? OP_F_ADD : oper=='-' ? OP_F_SUB : oper=='*' ? OP_F_MUL : OP_F_DIV);
 
@@ -953,6 +1064,8 @@ void SpirVGenerator::visit(Assignment &assign)
 
 void SpirVGenerator::visit(TernaryExpression &ternary)
 {
+       if(composite_access)
+               return visit_isolated(ternary);
        if(constant_expression)
        {
                ternary.condition->visit(*this);
@@ -980,16 +1093,22 @@ void SpirVGenerator::visit(TernaryExpression &ternary)
        writer.write_op(content.function_body, OP_SELECTION_MERGE, merge_block_id, 0);  // Selection control (none)
        writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, condition_id, true_label_id, false_label_id);
 
+       std::map<const VariableDeclaration *, Id> saved_load_ids = variable_load_ids;
+
        writer.write_op_label(true_label_id);
        ternary.true_expr->visit(*this);
        Id true_result_id = r_expression_result_id;
+       true_label_id = writer.get_current_block();
        writer.write_op(content.function_body, OP_BRANCH, merge_block_id);
 
+       swap(saved_load_ids, variable_load_ids);
        writer.write_op_label(false_label_id);
        ternary.false_expr->visit(*this);
        Id false_result_id = r_expression_result_id;
+       false_label_id = writer.get_current_block();
 
        writer.write_op_label(merge_block_id);
+       prune_loads(true_label_id);
        r_expression_result_id = begin_expression(OP_PHI, get_id(*ternary.type), 4);
        writer.write(true_result_id);
        writer.write(true_label_id);
@@ -1012,9 +1131,9 @@ void SpirVGenerator::visit(FunctionCall &call)
        vector<Id> argument_ids;
        argument_ids.reserve(call.arguments.size());
        bool all_args_const = true;
-       for(NodeArray<Expression>::const_iterator i=call.arguments.begin(); i!=call.arguments.end(); ++i)
+       for(const RefPtr<Expression> &a: call.arguments)
        {
-               (*i)->visit(*this);
+               a->visit(*this);
                argument_ids.push_back(r_expression_result_id);
                all_args_const &= r_constant_result;
        }
@@ -1023,20 +1142,21 @@ void SpirVGenerator::visit(FunctionCall &call)
                throw internal_error("function call in constant expression");
 
        Id result_type_id = get_id(*call.type);
+       r_constant_result = false;
 
        if(call.constructor)
                visit_constructor(call, argument_ids, all_args_const);
        else if(call.declaration->source==BUILTIN_SOURCE)
        {
                string arg_types;
-               for(NodeArray<Expression>::const_iterator i=call.arguments.begin(); i!=call.arguments.end(); ++i)
-                       if(BasicTypeDeclaration *basic_arg = dynamic_cast<BasicTypeDeclaration *>((*i)->type))
+               for(const RefPtr<Expression> &a: call.arguments)
+                       if(BasicTypeDeclaration *basic_arg = dynamic_cast<BasicTypeDeclaration *>(a->type))
                        {
                                BasicTypeDeclaration &elem_arg = *get_element_type(*basic_arg);
                                switch(elem_arg.kind)
                                {
                                case BasicTypeDeclaration::BOOL: arg_types += 'b'; break;
-                               case BasicTypeDeclaration::INT: arg_types += 'i'; break;
+                               case BasicTypeDeclaration::INT: arg_types += (elem_arg.sign ? 'i' : 'u'); break;
                                case BasicTypeDeclaration::FLOAT: arg_types += 'f'; break;
                                default: arg_types += '?';
                                }
@@ -1047,6 +1167,9 @@ void SpirVGenerator::visit(FunctionCall &call)
                        if(builtin_info->function==call.name && (!builtin_info->arg_types[0] || builtin_info->arg_types==arg_types))
                                break;
 
+               if(builtin_info->capability)
+                       use_capability(static_cast<Capability>(builtin_info->capability));
+
                if(builtin_info->opcode)
                {
                        Opcode opcode;
@@ -1083,14 +1206,14 @@ void SpirVGenerator::visit(FunctionCall &call)
        {
                r_expression_result_id = begin_expression(OP_FUNCTION_CALL, result_type_id, 1+call.arguments.size());
                writer.write(get_id(*call.declaration->definition));
-               for(vector<Id>::const_iterator i=argument_ids.begin(); i!=argument_ids.end(); ++i)
-                       writer.write(*i);
+               for(Id i: argument_ids)
+                       writer.write(i);
                end_expression(OP_FUNCTION_CALL);
 
                // Any global variables the called function uses might have changed value
                set<Node *> dependencies = DependencyCollector().apply(*call.declaration->definition);
-               for(set<Node *>::const_iterator i=dependencies.begin(); i!=dependencies.end(); ++i)
-                       if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(*i))
+               for(Node *n: dependencies)
+                       if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(n))
                                variable_load_ids.erase(var);
        }
 }
@@ -1128,7 +1251,7 @@ void SpirVGenerator::visit_constructor(FunctionCall &call, const vector<Id> &arg
                        Id zero_id = get_constant_id(get_id(elem), 0.0f);
                        for(unsigned i=0; i<n_columns; ++i)
                        {
-                               column_ids[i] = begin_expression(OP_COMPOSITE_CONSTRUCT, col_type_id, n_rows);;
+                               column_ids[i] = begin_expression(OP_COMPOSITE_CONSTRUCT, col_type_id, n_rows);
                                for(unsigned j=0; j<n_rows; ++j)
                                        writer.write(j==i ? argument_ids[0] : zero_id);
                                end_expression(OP_COMPOSITE_CONSTRUCT);
@@ -1201,9 +1324,11 @@ void SpirVGenerator::visit_constructor(FunctionCall &call, const vector<Id> &arg
                // Scalar or vector conversion between types of equal size.
                Opcode opcode;
                if(elem.kind==BasicTypeDeclaration::INT && elem_arg0.kind==BasicTypeDeclaration::FLOAT)
-                       opcode = OP_CONVERT_F_TO_S;
+                       opcode = (elem.sign ? OP_CONVERT_F_TO_S : OP_CONVERT_F_TO_U);
                else if(elem.kind==BasicTypeDeclaration::FLOAT && elem_arg0.kind==BasicTypeDeclaration::INT)
-                       opcode = OP_CONVERT_S_TO_F;
+                       opcode = (elem_arg0.sign ? OP_CONVERT_S_TO_F : OP_CONVERT_U_TO_F);
+               else if(elem.kind==BasicTypeDeclaration::INT && elem_arg0.kind==BasicTypeDeclaration::INT)
+                       opcode = OP_BITCAST;
                else
                        throw internal_error("invalid conversion");
 
@@ -1230,6 +1355,44 @@ void SpirVGenerator::visit_builtin_matrix_comp_mult(FunctionCall &call, const ve
        r_expression_result_id = write_construct(get_id(*call.type), column_ids, n_columns);
 }
 
+void SpirVGenerator::visit_builtin_texture_query(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       if(argument_ids.size()<1)
+               throw internal_error("invalid texture query call");
+
+       Opcode opcode;
+       if(call.name=="textureSize")
+               opcode = OP_IMAGE_QUERY_SIZE_LOD;
+       else if(call.name=="imageSize")
+               opcode = OP_IMAGE_QUERY_SIZE;
+       else if(call.name=="textureQueryLod")
+               opcode = OP_IMAGE_QUERY_LOD;
+       else if(call.name=="textureQueryLevels")
+               opcode = OP_IMAGE_QUERY_LEVELS;
+       else if(call.name=="textureSamples" || call.name=="imageSamples")
+               opcode = OP_IMAGE_QUERY_SAMPLES;
+       else
+               throw internal_error("invalid texture query call");
+
+       ImageTypeDeclaration &image_arg0 = dynamic_cast<ImageTypeDeclaration &>(*call.arguments[0]->type);
+
+       Id image_id;
+       if(image_arg0.sampled && opcode!=OP_IMAGE_QUERY_LOD)
+       {
+               Id image_type_id = get_item(image_type_ids, get_id(image_arg0));
+               image_id = write_expression(OP_IMAGE, image_type_id, argument_ids[0]);
+       }
+       else
+               image_id = argument_ids[0];
+
+       Id result_type_id = get_id(*call.type);
+       r_expression_result_id = begin_expression(opcode, result_type_id, argument_ids.size());
+       writer.write(image_id);
+       for(unsigned i=1; i<argument_ids.size(); ++i)
+               writer.write(argument_ids[i]);
+       end_expression(opcode);
+}
+
 void SpirVGenerator::visit_builtin_texture(FunctionCall &call, const vector<Id> &argument_ids)
 {
        if(argument_ids.size()<2)
@@ -1279,17 +1442,63 @@ void SpirVGenerator::visit_builtin_texture(FunctionCall &call, const vector<Id>
        end_expression(opcode);
 }
 
-void SpirVGenerator::visit_builtin_texel_fetch(FunctionCall &call, const vector<Id> &argument_ids)
+void SpirVGenerator::visit_builtin_texture_fetch(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       ImageTypeDeclaration &image = dynamic_cast<ImageTypeDeclaration &>(*call.arguments[0]->type);
+
+       Opcode opcode;
+       if(call.name=="texelFetch")
+               opcode = OP_IMAGE_FETCH;
+       else if(call.name=="imageLoad")
+               opcode = OP_IMAGE_READ;
+       else
+               throw internal_error("invalid texture fetch call");
+
+       bool need_sample = image.multisample;
+       bool need_lod = (opcode==OP_IMAGE_FETCH && !need_sample);
+
+       if(argument_ids.size()!=2U+need_sample+need_lod)
+               throw internal_error("invalid texture fetch call");
+
+       Id image_id;
+       if(image.sampled)
+       {
+               Id image_type_id = get_item(image_type_ids, get_id(image));
+               image_id = write_expression(OP_IMAGE, image_type_id, argument_ids[0]);
+       }
+       else
+               image_id = argument_ids[0];
+
+       r_expression_result_id = begin_expression(opcode, get_id(*call.type), 2+(need_lod|need_sample)+need_lod+need_sample);
+       writer.write(image_id);
+       writer.write(argument_ids[1]);
+       if(need_lod || need_sample)
+       {
+               writer.write(need_lod*0x02 | need_sample*0x40);
+               writer.write(argument_ids.back());
+       }
+       end_expression(opcode);
+}
+
+void SpirVGenerator::visit_builtin_texture_store(FunctionCall &call, const vector<Id> &argument_ids)
 {
        if(argument_ids.size()!=3)
-               throw internal_error("invalid texelFetch call");
+               throw internal_error("invalid texture store call");
+
+       const ImageTypeDeclaration &image = dynamic_cast<const ImageTypeDeclaration &>(*call.arguments[0]->type);
 
-       r_expression_result_id = begin_expression(OP_IMAGE_FETCH, get_id(*call.type), 4);
+       begin_expression(OP_IMAGE_WRITE, get_id(*call.type), 3+image.multisample*2);
        for(unsigned i=0; i<2; ++i)
                writer.write(argument_ids[i]);
-       writer.write(2);  // Lod
        writer.write(argument_ids.back());
-       end_expression(OP_IMAGE_FETCH);
+       if(image.multisample)
+       {
+               writer.write(0x40);  // Sample
+               writer.write(argument_ids[2]);
+       }
+       end_expression(OP_IMAGE_WRITE);
+
+       r_expression_result_id = 0;
 }
 
 void SpirVGenerator::visit_builtin_interpolate(FunctionCall &call, const vector<Id> &argument_ids)
@@ -1310,14 +1519,12 @@ void SpirVGenerator::visit_builtin_interpolate(FunctionCall &call, const vector<
        else
                throw internal_error("invalid interpolate call");
 
-       use_capability(CAP_INTERPOLATION_FUNCTION);
-
        Id ext_id = import_extension("GLSL.std.450");
        r_expression_result_id = begin_expression(OP_EXT_INST, get_id(*call.type));
        writer.write(ext_id);
        writer.write(opcode);
        writer.write(get_id(*var->declaration));
-       for(vector<Id>::const_iterator i=argument_ids.begin(); ++i!=argument_ids.end(); )
+       for(auto i=argument_ids.begin(); ++i!=argument_ids.end(); )
                writer.write(*i);
        end_expression(OP_EXT_INST);
 }
@@ -1334,11 +1541,11 @@ void SpirVGenerator::visit(InterfaceLayout &layout)
 
 bool SpirVGenerator::check_duplicate_type(TypeDeclaration &type)
 {
-       for(map<Node *, Declaration>::const_iterator i=declared_ids.begin(); i!=declared_ids.end(); ++i)
-               if(TypeDeclaration *type2 = dynamic_cast<TypeDeclaration *>(i->first))
+       for(const auto &kvp: declared_ids)
+               if(TypeDeclaration *type2 = dynamic_cast<TypeDeclaration *>(kvp.first))
                        if(TypeComparer().apply(type, *type2))
                        {
-                               insert_unique(declared_ids, &type, i->second);
+                               insert_unique(declared_ids, &type, kvp.second);
                                return true;
                        }
 
@@ -1354,7 +1561,7 @@ bool SpirVGenerator::check_standard_type(BasicTypeDeclaration &basic)
        if((elem->kind==BasicTypeDeclaration::INT || elem->kind==BasicTypeDeclaration::FLOAT) && elem->size!=32)
                return false;
 
-       Id standard_id = get_standard_type_id(elem->kind, (basic.kind==BasicTypeDeclaration::VECTOR ? basic.size : 1));
+       Id standard_id = get_standard_type_id(elem->kind, (basic.kind==BasicTypeDeclaration::VECTOR ? basic.size : 1), elem->sign);
        insert_unique(declared_ids, &basic, Declaration(standard_id, 0));
        writer.write_op_name(standard_id, basic.name);
 
@@ -1377,7 +1584,7 @@ void SpirVGenerator::visit(BasicTypeDeclaration &basic)
        switch(basic.kind)
        {
        case BasicTypeDeclaration::INT:
-               writer.write_op(content.globals, OP_TYPE_INT, type_id, basic.size, 1);
+               writer.write_op(content.globals, OP_TYPE_INT, type_id, basic.size, basic.sign);
                break;
        case BasicTypeDeclaration::FLOAT:
                writer.write_op(content.globals, OP_TYPE_FLOAT, type_id, basic.size);
@@ -1399,6 +1606,7 @@ void SpirVGenerator::visit(ImageTypeDeclaration &image)
                return;
 
        Id type_id = allocate_id(image, 0);
+       SpirVFormat format = get_format(image.format);
 
        Id image_id = (image.sampled ? next_id++ : type_id);
        writer.begin_op(content.globals, OP_TYPE_IMAGE, 9);
@@ -1407,21 +1615,28 @@ void SpirVGenerator::visit(ImageTypeDeclaration &image)
        writer.write(image.dimensions-1);
        writer.write(image.shadow);
        writer.write(image.array);
-       writer.write(false);  // Multisample
+       writer.write(image.multisample);
        writer.write(image.sampled ? 1 : 2);
-       writer.write(0);  // Format (unknown)
+       writer.write(format);
        writer.end_op(OP_TYPE_IMAGE);
 
        if(image.sampled)
        {
                writer.write_op_name(type_id, image.name);
                writer.write_op(content.globals, OP_TYPE_SAMPLED_IMAGE, type_id, image_id);
+               insert_unique(image_type_ids, type_id, image_id);
        }
 
        if(image.dimensions==ImageTypeDeclaration::ONE)
                use_capability(image.sampled ? CAP_SAMPLED_1D : CAP_IMAGE_1D);
        else if(image.dimensions==ImageTypeDeclaration::CUBE && image.array)
                use_capability(image.sampled ? CAP_SAMPLED_CUBE_ARRAY : CAP_IMAGE_CUBE_ARRAY);
+
+       if(image.multisample && !image.sampled)
+               use_capability(CAP_STORAGE_IMAGE_MULTISAMPLE);
+
+       if(format>=FORMAT_RG32F && format<=FORMAT_R8_SNORM)
+               use_capability(CAP_STORAGE_IMAGE_EXTENDED_FORMATS);
 }
 
 void SpirVGenerator::visit(StructDeclaration &strct)
@@ -1430,17 +1645,17 @@ void SpirVGenerator::visit(StructDeclaration &strct)
                return;
 
        Id type_id = allocate_id(strct, 0);
-       writer.write_op_name(type_id, strct.name);
+       writer.write_op_name(type_id, (strct.block_name.empty() ? strct.name : strct.block_name));
 
-       if(strct.interface_block)
+       if(!strct.block_name.empty())
                writer.write_op_decorate(type_id, DECO_BLOCK);
 
-       bool builtin = (strct.interface_block && !strct.interface_block->block_name.compare(0, 3, "gl_"));
+       bool builtin = !strct.block_name.compare(0, 3, "gl_");
        vector<Id> member_type_ids;
        member_type_ids.reserve(strct.members.body.size());
-       for(NodeList<Statement>::const_iterator i=strct.members.body.begin(); i!=strct.members.body.end(); ++i)
+       for(const RefPtr<Statement> &s: strct.members.body)
        {
-               const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(i->get());
+               const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(s.get());
                if(!var)
                        continue;
 
@@ -1458,14 +1673,13 @@ void SpirVGenerator::visit(StructDeclaration &strct)
                {
                        if(var->layout)
                        {
-                               const vector<Layout::Qualifier> &qualifiers = var->layout->qualifiers;
-                               for(vector<Layout::Qualifier>::const_iterator j=qualifiers.begin(); j!=qualifiers.end(); ++j)
+                               for(const Layout::Qualifier &q: var->layout->qualifiers)
                                {
-                                       if(j->name=="offset")
-                                               writer.write_op_member_decorate(type_id, index, DECO_OFFSET, j->value);
-                                       else if(j->name=="column_major")
+                                       if(q.name=="offset")
+                                               writer.write_op_member_decorate(type_id, index, DECO_OFFSET, q.value);
+                                       else if(q.name=="column_major")
                                                writer.write_op_member_decorate(type_id, index, DECO_COL_MAJOR);
-                                       else if(j->name=="row_major")
+                                       else if(q.name=="row_major")
                                                writer.write_op_member_decorate(type_id, index, DECO_ROW_MAJOR);
                                }
                        }
@@ -1483,23 +1697,13 @@ void SpirVGenerator::visit(StructDeclaration &strct)
 
        writer.begin_op(content.globals, OP_TYPE_STRUCT);
        writer.write(type_id);
-       for(vector<Id>::const_iterator i=member_type_ids.begin(); i!=member_type_ids.end(); ++i)
-               writer.write(*i);
+       for(Id i: member_type_ids)
+               writer.write(i);
        writer.end_op(OP_TYPE_STRUCT);
 }
 
 void SpirVGenerator::visit(VariableDeclaration &var)
 {
-       const vector<Layout::Qualifier> *layout_ql = (var.layout ? &var.layout->qualifiers : 0);
-
-       int spec_id = -1;
-       if(layout_ql)
-       {
-               for(vector<Layout::Qualifier>::const_iterator i=layout_ql->begin(); (spec_id<0 && i!=layout_ql->end()); ++i)
-                       if(i->name=="constant_id")
-                               spec_id = i->value;
-       }
-
        Id type_id = get_variable_type_id(var);
        Id var_id;
 
@@ -1508,26 +1712,42 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                if(!var.init_expression)
                        throw internal_error("const variable without initializer");
 
+               int spec_id = get_layout_value(var.layout.get(), "constant_id");
+               Id *spec_var_id = (spec_id>=0 ? &declared_spec_ids[spec_id] : 0);
+               if(spec_id>=0 && *spec_var_id)
+               {
+                       insert_unique(declared_ids, &var, Declaration(*spec_var_id, type_id));
+                       return;
+               }
+
                SetFlag set_const(constant_expression);
                SetFlag set_spec(spec_constant, spec_id>=0);
                r_expression_result_id = 0;
                var.init_expression->visit(*this);
                var_id = r_expression_result_id;
                insert_unique(declared_ids, &var, Declaration(var_id, type_id));
-               writer.write_op_decorate(var_id, DECO_SPEC_ID, spec_id);
-
-               /* It's unclear what should be done if a specialization constant is
-               initialized with anything other than a literal.  GLSL doesn't seem to
-               prohibit that but SPIR-V says OpSpecConstantOp can't be updated via
-               specialization. */
+               if(spec_id>=0)
+               {
+                       writer.write_op_decorate(var_id, DECO_SPEC_ID, spec_id);
+                       *spec_var_id = var_id;
+               }
        }
        else
        {
-               StorageClass storage = (current_function ? STORAGE_FUNCTION : get_interface_storage(var.interface, false));
+               bool push_const = has_layout_qualifier(var.layout.get(), "push_constant");
+
+               StorageClass storage;
+               if(current_function)
+                       storage = STORAGE_FUNCTION;
+               else if(push_const)
+                       storage = STORAGE_PUSH_CONSTANT;
+               else
+                       storage = get_interface_storage(var.interface, var.block_declaration);
+
                Id ptr_type_id = get_pointer_type_id(type_id, storage);
                if(var.interface=="uniform")
                {
-                       Id &uni_id = declared_uniform_ids["v"+var.name];
+                       Id &uni_id = declared_uniform_ids[var.block_declaration ? "b"+var.block_declaration->block_name : "v"+var.name];
                        if(uni_id)
                        {
                                insert_unique(declared_ids, &var, Declaration(uni_id, ptr_type_id));
@@ -1544,6 +1764,7 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                {
                        SetFlag set_const(constant_expression, !current_function);
                        r_expression_result_id = 0;
+                       r_constant_result = false;
                        var.init_expression->visit(*this);
                        init_id = r_expression_result_id;
                }
@@ -1557,61 +1778,39 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                        writer.write(init_id);
                writer.end_op(OP_VARIABLE);
 
-               if(layout_ql)
+               if(var.layout)
                {
-                       for(vector<Layout::Qualifier>::const_iterator i=layout_ql->begin(); i!=layout_ql->end(); ++i)
+                       for(const Layout::Qualifier &q: var.layout->qualifiers)
                        {
-                               if(i->name=="location")
-                                       writer.write_op_decorate(var_id, DECO_LOCATION, i->value);
-                               else if(i->name=="set")
-                                       writer.write_op_decorate(var_id, DECO_DESCRIPTOR_SET, i->value);
-                               else if(i->name=="binding")
-                                       writer.write_op_decorate(var_id, DECO_BINDING, i->value);
+                               if(q.name=="location")
+                                       writer.write_op_decorate(var_id, DECO_LOCATION, q.value);
+                               else if(q.name=="set")
+                                       writer.write_op_decorate(var_id, DECO_DESCRIPTOR_SET, q.value);
+                               else if(q.name=="binding")
+                                       writer.write_op_decorate(var_id, DECO_BINDING, q.value);
                        }
                }
+               if(!var.block_declaration && !var.name.compare(0, 3, "gl_"))
+               {
+                       BuiltinSemantic semantic = get_builtin_semantic(var.name);
+                       writer.write_op_decorate(var_id, DECO_BUILTIN, semantic);
+               }
+               if(var.interpolation=="flat")
+                       writer.write_op_decorate(var_id, DECO_FLAT);
+               if(var.sampling=="centroid")
+                       writer.write_op_decorate(var_id, DECO_CENTROID);
+               if(var.sampling=="patch")
+                       writer.write_op_decorate(var_id, DECO_PATCH);
 
                if(init_id && current_function)
-                       writer.write_op(content.function_body, OP_STORE, var_id, init_id);
-       }
-
-       writer.write_op_name(var_id, var.name);
-}
-
-void SpirVGenerator::visit(InterfaceBlock &iface)
-{
-       StorageClass storage = get_interface_storage(iface.interface, true);
-       Id type_id;
-       if(iface.array)
-               type_id = get_array_type_id(*iface.struct_declaration, 0);
-       else
-               type_id = get_id(*iface.struct_declaration);
-       Id ptr_type_id = get_pointer_type_id(type_id, storage);
-
-       Id block_id;
-       if(iface.interface=="uniform")
-       {
-               Id &uni_id = declared_uniform_ids["b"+iface.block_name];
-               if(uni_id)
                {
-                       insert_unique(declared_ids, &iface, Declaration(uni_id, ptr_type_id));
-                       return;
+                       writer.write_op(content.function_body, OP_STORE, var_id, init_id);
+                       variable_load_ids[&var] = init_id;
                }
-
-               uni_id = block_id = allocate_id(iface, ptr_type_id);
        }
-       else
-               block_id = allocate_id(iface, ptr_type_id);
-       writer.write_op_name(block_id, iface.instance_name);
-
-       writer.write_op(content.globals, OP_VARIABLE, ptr_type_id, block_id, storage);
 
-       if(iface.layout)
-       {
-               const vector<Layout::Qualifier> &qualifiers = iface.layout->qualifiers;
-               for(vector<Layout::Qualifier>::const_iterator i=qualifiers.begin(); i!=qualifiers.end(); ++i)
-                       if(i->name=="binding")
-                               writer.write_op_decorate(block_id, DECO_BINDING, i->value);
-       }
+       if(var.name.find(' ')==string::npos)
+               writer.write_op_name(var_id, var.name);
 }
 
 void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
@@ -1620,68 +1819,112 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
        switch(stage->type)
        {
        case Stage::VERTEX: writer.write(0); break;
+       case Stage::TESS_CONTROL: writer.write(1); break;
+       case Stage::TESS_EVAL: writer.write(2); break;
        case Stage::GEOMETRY: writer.write(3); break;
        case Stage::FRAGMENT: writer.write(4); break;
+       case Stage::COMPUTE: writer.write(5); break;
        default: throw internal_error("unknown stage");
        }
        writer.write(func_id);
        writer.write_string(func.name);
 
        set<Node *> dependencies = DependencyCollector().apply(func);
-       for(set<Node *>::const_iterator i=dependencies.begin(); i!=dependencies.end(); ++i)
-       {
-               if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(*i))
-               {
+       for(Node *n: dependencies)
+               if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(n))
                        if(!var->interface.empty())
-                               writer.write(get_id(**i));
-               }
-               else if(dynamic_cast<InterfaceBlock *>(*i))
-                       writer.write(get_id(**i));
-       }
+                               writer.write(allocate_forward_id(*n));
 
        writer.end_op(OP_ENTRY_POINT);
 
        if(stage->type==Stage::FRAGMENT)
-               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_ORIGIN_LOWER_LEFT);
+       {
+               SpirVExecutionMode origin = (features.target_api==VULKAN ? EXEC_ORIGIN_UPPER_LEFT : EXEC_ORIGIN_LOWER_LEFT);
+               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, origin);
+       }
        else if(stage->type==Stage::GEOMETRY)
+       {
                use_capability(CAP_GEOMETRY);
+               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INVOCATIONS, 1);
+       }
+       else if(stage->type==Stage::TESS_CONTROL || stage->type==Stage::TESS_EVAL)
+       {
+               use_capability(CAP_TESSELLATION);
+       }
 
-       for(vector<const InterfaceLayout *>::const_iterator i=interface_layouts.begin(); i!=interface_layouts.end(); ++i)
+       unsigned local_size[3] = { 0, 1, 1 };
+
+       for(const InterfaceLayout *i: interface_layouts)
        {
-               const vector<Layout::Qualifier> &qualifiers = (*i)->layout.qualifiers;
-               for(vector<Layout::Qualifier>::const_iterator j=qualifiers.begin(); j!=qualifiers.end(); ++j)
+               for(const Layout::Qualifier &q: i->layout.qualifiers)
                {
-                       if(j->name=="point")
+                       if(q.name=="point")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id,
-                                       ((*i)->interface=="in" ? EXEC_INPUT_POINTS : EXEC_OUTPUT_POINTS));
-                       else if(j->name=="lines")
+                                       (i->interface=="in" ? EXEC_INPUT_POINTS : EXEC_OUTPUT_POINTS));
+                       else if(q.name=="lines")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_LINES);
-                       else if(j->name=="lines_adjacency")
+                       else if(q.name=="lines_adjacency")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_LINES_ADJACENCY);
-                       else if(j->name=="triangles")
+                       else if(q.name=="triangles")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_TRIANGLES);
-                       else if(j->name=="triangles_adjacency")
+                       else if(q.name=="triangles_adjacency")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_TRIANGLES_ADJACENCY);
-                       else if(j->name=="line_strip")
+                       else if(q.name=="quads")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_QUADS);
+                       else if(q.name=="isolines")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_ISOLINES);
+                       else if(q.name=="line_strip")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_LINE_STRIP);
-                       else if(j->name=="triangle_strip")
+                       else if(q.name=="triangle_strip")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_TRIANGLE_STRIP);
-                       else if(j->name=="max_vertices")
-                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_VERTICES, j->value);
+                       else if(q.name=="max_vertices" || q.name=="vertices")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_VERTICES, q.value);
+                       else if(q.name=="cw")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_VERTEX_ORDER_CW);
+                       else if(q.name=="ccw")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_VERTEX_ORDER_CCW);
+                       else if(q.name=="equal_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_EQUAL);
+                       else if(q.name=="fractional_even_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_FRACTIONAL_EVEN);
+                       else if(q.name=="fractional_odd_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_FRACTIONAL_ODD);
+                       else if(q.name=="local_size_x")
+                               local_size[0] = q.value;
+                       else if(q.name=="local_size_y")
+                               local_size[1] = q.value;
+                       else if(q.name=="local_size_z")
+                               local_size[2] = q.value;
                }
        }
+
+       if(stage->type==Stage::COMPUTE && local_size[0])
+       {
+               writer.begin_op(content.exec_modes, OP_EXECUTION_MODE);
+               writer.write(func_id);
+               writer.write(EXEC_LOCAL_SIZE);
+               for(unsigned j=0; j<3; ++j)
+                       writer.write(local_size[j]);
+               writer.end_op(OP_EXECUTION_MODE);
+       }
 }
 
 void SpirVGenerator::visit(FunctionDeclaration &func)
 {
-       if(func.source==BUILTIN_SOURCE || func.definition!=&func)
+       if(func.source==BUILTIN_SOURCE)
+               return;
+       else if(func.definition!=&func)
+       {
+               if(func.definition)
+                       allocate_forward_id(*func.definition);
                return;
+       }
 
        Id return_type_id = get_id(*func.return_type_declaration);
        vector<unsigned> param_type_ids;
        param_type_ids.reserve(func.parameters.size());
-       for(NodeArray<VariableDeclaration>::const_iterator i=func.parameters.begin(); i!=func.parameters.end(); ++i)
-               param_type_ids.push_back(get_variable_type_id(**i));
+       for(const RefPtr<VariableDeclaration> &p: func.parameters)
+               param_type_ids.push_back(get_variable_type_id(*p));
 
        string sig_with_return = func.return_type+func.signature;
        Id &type_id = function_type_ids[sig_with_return];
@@ -1691,8 +1934,8 @@ void SpirVGenerator::visit(FunctionDeclaration &func)
                writer.begin_op(content.globals, OP_TYPE_FUNCTION);
                writer.write(type_id);
                writer.write(return_type_id);
-               for(vector<unsigned>::const_iterator i=param_type_ids.begin(); i!=param_type_ids.end(); ++i)
-                       writer.write(*i);
+               for(unsigned i: param_type_ids)
+                       writer.write(i);
                writer.end_op(OP_TYPE_FUNCTION);
 
                writer.write_op_name(type_id, sig_with_return);
@@ -1715,15 +1958,17 @@ void SpirVGenerator::visit(FunctionDeclaration &func)
        {
                Id param_id = allocate_id(*func.parameters[i], param_type_ids[i]);
                writer.write_op(content.functions, OP_FUNCTION_PARAMETER, param_type_ids[i], param_id);
+               writer.write_op_name(param_id, func.parameters[i]->name);
                // TODO This is probably incorrect if the parameter is assigned to.
                variable_load_ids[func.parameters[i].get()] = param_id;
        }
 
+       reachable = true;
        writer.begin_function_body(next_id++);
        SetForScope<FunctionDeclaration *> set_func(current_function, &func);
        func.body.visit(*this);
 
-       if(writer.has_current_block())
+       if(writer.get_current_block())
        {
                if(!reachable)
                        writer.write_op(content.function_body, OP_UNREACHABLE);
@@ -1750,9 +1995,11 @@ void SpirVGenerator::visit(Conditional &cond)
        writer.write_op(content.function_body, OP_SELECTION_MERGE, merge_block_id, 0);  // Selection control (none)
        writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, r_expression_result_id, true_label_id, false_label_id);
 
+       std::map<const VariableDeclaration *, Id> saved_load_ids = variable_load_ids;
+
        writer.write_op_label(true_label_id);
        cond.body.visit(*this);
-       if(writer.has_current_block())
+       if(writer.get_current_block())
                writer.write_op(content.function_body, OP_BRANCH, merge_block_id);
 
        bool reachable_if_true = reachable;
@@ -1760,6 +2007,7 @@ void SpirVGenerator::visit(Conditional &cond)
        reachable = true;
        if(!cond.else_body.body.empty())
        {
+               swap(saved_load_ids, variable_load_ids);
                writer.write_op_label(false_label_id);
                cond.else_body.visit(*this);
                reachable |= reachable_if_true;
@@ -1774,6 +2022,10 @@ void SpirVGenerator::visit(Iteration &iter)
        if(iter.init_statement)
                iter.init_statement->visit(*this);
 
+       for(Node *n: AssignmentCollector().apply(iter))
+               if(VariableDeclaration *var = dynamic_cast<VariableDeclaration *>(n))
+                       variable_load_ids.erase(var);
+
        Id header_id = next_id++;
        Id continue_id = next_id++;
        Id merge_block_id = next_id++;
@@ -1831,6 +2083,19 @@ void SpirVGenerator::visit(Jump &jump)
 }
 
 
+SpirVGenerator::TypeKey::TypeKey(BasicTypeDeclaration::Kind kind, bool sign):
+       type_id(0)
+{
+       switch(kind)
+       {
+       case BasicTypeDeclaration::VOID: detail = 'v'; break;
+       case BasicTypeDeclaration::BOOL: detail = 'b'; break;
+       case BasicTypeDeclaration::INT: detail = (sign ? 'i' : 'u'); break;
+       case BasicTypeDeclaration::FLOAT: detail = 'f'; break;
+       default: throw invalid_argument("TypeKey::TypeKey");
+       }
+}
+
 bool SpirVGenerator::TypeKey::operator<(const TypeKey &other) const
 {
        if(type_id!=other.type_id)