/*
 * Copyright (C) 2014 Patrick Mours
 * SPDX-License-Identifier: BSD-3-Clause
 */

#if defined(__INTELLISENSE__) || !defined(DEFINE_INTRINSIC)
#define DEFINE_INTRINSIC(name, i, ret_type, ...)
#endif
#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_GLSL)
#define IMPLEMENT_INTRINSIC_GLSL(name, i, code)
#endif
#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_HLSL)
#define IMPLEMENT_INTRINSIC_HLSL(name, i, code)
#endif
#if defined(__INTELLISENSE__) || !defined(IMPLEMENT_INTRINSIC_SPIRV)
#define IMPLEMENT_INTRINSIC_SPIRV(name, i, code)
#endif

// ret abs(x)
DEFINE_INTRINSIC(abs, 0, int, int)
DEFINE_INTRINSIC(abs, 0, int2, int2)
DEFINE_INTRINSIC(abs, 0, int3, int3)
DEFINE_INTRINSIC(abs, 0, int4, int4)
DEFINE_INTRINSIC(abs, 1, float, float)
DEFINE_INTRINSIC(abs, 1, float2, float2)
DEFINE_INTRINSIC(abs, 1, float3, float3)
DEFINE_INTRINSIC(abs, 1, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(abs, 0, {
	code += "abs(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(abs, 1, {
	code += "abs(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(abs, 0, {
	code += "abs(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(abs, 1, {
	code += "abs(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(abs, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SAbs)
		.add(args[0].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(abs, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FAbs)
		.add(args[0].base);
	})

// ret all(x)
DEFINE_INTRINSIC(all, 0, bool, bool)
DEFINE_INTRINSIC(all, 1, bool, bool2)
DEFINE_INTRINSIC(all, 1, bool, bool3)
DEFINE_INTRINSIC(all, 1, bool, bool4)
IMPLEMENT_INTRINSIC_GLSL(all, 0, {
	code += id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_GLSL(all, 1, {
	code += "all(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(all, 0, {
	code += id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_HLSL(all, 1, {
	code += "all(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(all, 0, {
	return args[0].base;
	})
IMPLEMENT_INTRINSIC_SPIRV(all, 1, {
	return
	add_instruction(spv::OpAll, convert_type(res_type))
		.add(args[0].base);
	})

// ret any(x)
DEFINE_INTRINSIC(any, 0, bool, bool)
DEFINE_INTRINSIC(any, 1, bool, bool2)
DEFINE_INTRINSIC(any, 1, bool, bool3)
DEFINE_INTRINSIC(any, 1, bool, bool4)
IMPLEMENT_INTRINSIC_GLSL(any, 0, {
	code += id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_GLSL(any, 1, {
	code += "any(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(any, 0, {
	code += id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_HLSL(any, 1, {
	code += "any(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(any, 0, {
	return args[0].base;
	})
IMPLEMENT_INTRINSIC_SPIRV(any, 1, {
	return
	add_instruction(spv::OpAny, convert_type(res_type))
		.add(args[0].base);
	})

// ret asin(x)
DEFINE_INTRINSIC(asin, 0, float, float)
DEFINE_INTRINSIC(asin, 0, float2, float2)
DEFINE_INTRINSIC(asin, 0, float3, float3)
DEFINE_INTRINSIC(asin, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(asin, 0, {
	code += "asin(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(asin, 0, {
	code += "asin(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(asin, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Asin)
		.add(args[0].base);
	})

// ret acos(x)
DEFINE_INTRINSIC(acos, 0, float, float)
DEFINE_INTRINSIC(acos, 0, float2, float2)
DEFINE_INTRINSIC(acos, 0, float3, float3)
DEFINE_INTRINSIC(acos, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(acos, 0, {
	code += "acos(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(acos, 0, {
	code += "acos(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(acos, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Acos)
		.add(args[0].base);
	})

// ret atan(x)
DEFINE_INTRINSIC(atan, 0, float, float)
DEFINE_INTRINSIC(atan, 0, float2, float2)
DEFINE_INTRINSIC(atan, 0, float3, float3)
DEFINE_INTRINSIC(atan, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(atan, 0, {
	code += "atan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atan, 0, {
	code += "atan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atan, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Atan)
		.add(args[0].base);
	})

// ret atan2(x, y)
DEFINE_INTRINSIC(atan2, 0, float, float, float)
DEFINE_INTRINSIC(atan2, 0, float2, float2, float2)
DEFINE_INTRINSIC(atan2, 0, float3, float3, float3)
DEFINE_INTRINSIC(atan2, 0, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(atan2, 0, {
	code += "atan(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atan2, 0, {
	code += "atan2(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atan2, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Atan2)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret sin(x)
DEFINE_INTRINSIC(sin, 0, float, float)
DEFINE_INTRINSIC(sin, 0, float2, float2)
DEFINE_INTRINSIC(sin, 0, float3, float3)
DEFINE_INTRINSIC(sin, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(sin, 0, {
	code += "sin(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sin, 0, {
	code += "sin(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(sin, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Sin)
		.add(args[0].base);
	})

// ret sinh(x)
DEFINE_INTRINSIC(sinh, 0, float, float)
DEFINE_INTRINSIC(sinh, 0, float2, float2)
DEFINE_INTRINSIC(sinh, 0, float3, float3)
DEFINE_INTRINSIC(sinh, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(sinh, 0, {
	code += "sinh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sinh, 0, {
	code += "sinh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(sinh, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Sinh)
		.add(args[0].base);
	})

// ret cos(x)
DEFINE_INTRINSIC(cos, 0, float, float)
DEFINE_INTRINSIC(cos, 0, float2, float2)
DEFINE_INTRINSIC(cos, 0, float3, float3)
DEFINE_INTRINSIC(cos, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(cos, 0, {
	code += "cos(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(cos, 0, {
	code += "cos(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(cos, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Cos)
		.add(args[0].base);
	})

// ret cosh(x)
DEFINE_INTRINSIC(cosh, 0, float, float)
DEFINE_INTRINSIC(cosh, 0, float2, float2)
DEFINE_INTRINSIC(cosh, 0, float3, float3)
DEFINE_INTRINSIC(cosh, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(cosh, 0, {
	code += "cosh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(cosh, 0, {
	code += "cosh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(cosh, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Cosh)
		.add(args[0].base);
	})

// ret tan(x)
DEFINE_INTRINSIC(tan, 0, float, float)
DEFINE_INTRINSIC(tan, 0, float2, float2)
DEFINE_INTRINSIC(tan, 0, float3, float3)
DEFINE_INTRINSIC(tan, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(tan, 0, {
	code += "tan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tan, 0, {
	code += "tan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(tan, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Tan)
		.add(args[0].base);
	})

// ret tanh(x)
DEFINE_INTRINSIC(tanh, 0, float, float)
DEFINE_INTRINSIC(tanh, 0, float2, float2)
DEFINE_INTRINSIC(tanh, 0, float3, float3)
DEFINE_INTRINSIC(tanh, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(tanh, 0, {
	code += "tanh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tanh, 0, {
	code += "tanh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(tanh, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Tanh)
		.add(args[0].base);
	})

// sincos(x, out s, out c)
DEFINE_INTRINSIC(sincos, 0, void, float, out_float, out_float)
DEFINE_INTRINSIC(sincos, 0, void, float2, out_float2, out_float2)
DEFINE_INTRINSIC(sincos, 0, void, float3, out_float3, out_float3)
DEFINE_INTRINSIC(sincos, 0, void, float4, out_float4, out_float4)
IMPLEMENT_INTRINSIC_GLSL(sincos, 0, {
	code += id_to_name(args[1].base) + " = sin(" + id_to_name(args[0].base) + "), " + id_to_name(args[2].base) + " = cos(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sincos, 0, {
	code += "sincos(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(sincos, 0, {
	const spv::Id sin_result = add_instruction(spv::OpExtInst, convert_type(args[0].type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Sin)
		.add(args[0].base);
	const spv::Id cos_result = add_instruction(spv::OpExtInst, convert_type(args[0].type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Cos)
		.add(args[0].base);

	add_instruction_without_result(spv::OpStore)
		.add(args[1].base)
		.add(sin_result);
	add_instruction_without_result(spv::OpStore)
		.add(args[2].base)
		.add(cos_result);

	return 0;
	})

// ret asint(x)
DEFINE_INTRINSIC(asint, 0, int, float)
DEFINE_INTRINSIC(asint, 0, int2, float2)
DEFINE_INTRINSIC(asint, 0, int3, float3)
DEFINE_INTRINSIC(asint, 0, int4, float4)
IMPLEMENT_INTRINSIC_GLSL(asint, 0, {
	code += "floatBitsToInt(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(asint, 0, {
	_uses_bitwise_cast = true;
	if (_shader_model < 40)
		code += "__";
	code += "asint(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(asint, 0, {
	return
	add_instruction(spv::OpBitcast, convert_type(res_type))
		.add(args[0].base);
	})

// ret asuint(x)
DEFINE_INTRINSIC(asuint, 0, uint, float)
DEFINE_INTRINSIC(asuint, 0, uint2, float2)
DEFINE_INTRINSIC(asuint, 0, uint3, float3)
DEFINE_INTRINSIC(asuint, 0, uint4, float4)
IMPLEMENT_INTRINSIC_GLSL(asuint, 0, {
	code += "floatBitsToUint(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(asuint, 0, {
	_uses_bitwise_cast = true;
	if (_shader_model < 40)
		code += "__";
	code += "asuint(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(asuint, 0, {
	return
	add_instruction(spv::OpBitcast, convert_type(res_type))
		.add(args[0].base);
	})

// ret asfloat(x)
DEFINE_INTRINSIC(asfloat, 0, float, int)
DEFINE_INTRINSIC(asfloat, 0, float2, int2)
DEFINE_INTRINSIC(asfloat, 0, float3, int3)
DEFINE_INTRINSIC(asfloat, 0, float4, int4)
DEFINE_INTRINSIC(asfloat, 1, float, uint)
DEFINE_INTRINSIC(asfloat, 1, float2, uint2)
DEFINE_INTRINSIC(asfloat, 1, float3, uint3)
DEFINE_INTRINSIC(asfloat, 1, float4, uint4)
IMPLEMENT_INTRINSIC_GLSL(asfloat, 0, {
	code += "intBitsToFloat(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(asfloat, 1, {
	code += "uintBitsToFloat(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(asfloat, 0, {
	_uses_bitwise_cast = true;
	if (_shader_model < 40)
		code += "__";
	code += "asfloat(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(asfloat, 1, {
	_uses_bitwise_cast = true;
	if (_shader_model < 40)
		code += "__";
	code += "asfloat(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(asfloat, 0, {
	return
	add_instruction(spv::OpBitcast, convert_type(res_type))
		.add(args[0].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(asfloat, 1, {
	return
	add_instruction(spv::OpBitcast, convert_type(res_type))
		.add(args[0].base);
	})

// ret f16tof32(x)
DEFINE_INTRINSIC(f16tof32, 0, float, uint)
DEFINE_INTRINSIC(f16tof32, 0, float2, uint2)
DEFINE_INTRINSIC(f16tof32, 0, float3, uint3)
DEFINE_INTRINSIC(f16tof32, 0, float4, uint4)
IMPLEMENT_INTRINSIC_GLSL(f16tof32, 0, {
	if (args[0].type.rows > 1)
		code += "vec" + std::to_string(args[0].type.rows) + '(';
	for (unsigned int i = 0; i < args[0].type.rows; ++i)
	{
		assert(i < 4);
		code += "unpackHalf2x16(" + id_to_name(args[0].base) + '.' + "xyzw"[i] + ").x";
		if (i < args[0].type.rows - 1)
			code += ", ";
	}
	if (args[0].type.rows > 1)
		code += ')';
	})
IMPLEMENT_INTRINSIC_HLSL(f16tof32, 0, {
	code += "f16tof32(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(f16tof32, 0, {
	type res_scalar_type = res_type;
	res_scalar_type.rows = 1;
	type res_vector_type = res_type;
	res_vector_type.rows = 2;
	type arg_scalar_type = args[0].type;
	arg_scalar_type.rows = 1;

	spv::Id res[4] = {};
	for (unsigned int i = 0; i < args[0].type.rows; ++i)
	{
		assert(i < 4);

		spv::Id arg_scalar = args[0].base;
		if (args[0].type.rows > 1)
			arg_scalar = add_instruction(spv::OpCompositeExtract, convert_type(arg_scalar_type))
				.add(arg_scalar)
				.add(i);

		spv::Id arg_vector = add_instruction(spv::OpExtInst, convert_type(res_vector_type))
			.add(_glsl_ext)
			.add(spv::GLSLstd450UnpackHalf2x16)
			.add(arg_scalar);

		res[i] = add_instruction(spv::OpCompositeExtract, convert_type(res_scalar_type))
			.add(arg_vector)
			.add(0u);
	}

	if (res_type.rows > 1)
		return
		add_instruction(spv::OpCompositeConstruct, convert_type(res_type))
			.add(res, res + res_type.rows);
	else
		return res[0];
	})

// ret f32tof16(x)
DEFINE_INTRINSIC(f32tof16, 0, uint, float)
DEFINE_INTRINSIC(f32tof16, 0, uint2, float2)
DEFINE_INTRINSIC(f32tof16, 0, uint3, float3)
DEFINE_INTRINSIC(f32tof16, 0, uint4, float4)
IMPLEMENT_INTRINSIC_GLSL(f32tof16, 0, {
	if (args[0].type.rows > 1)
		code += "uvec" + std::to_string(args[0].type.rows) + '(';
	for (unsigned int i = 0; i < args[0].type.rows; ++i)
	{
		assert(i < 4);
		code += "packHalf2x16(vec2(" + id_to_name(args[0].base) + '.' + "xyzw"[i] + ", 0.0))";
		if (i < args[0].type.rows - 1)
			code += ", ";
	}
	if (args[0].type.rows > 1)
		code += ')';
	})
IMPLEMENT_INTRINSIC_HLSL(f32tof16, 0, {
	code += "f32tof16(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(f32tof16, 0, {
	type res_scalar_type = res_type;
	res_scalar_type.rows = 1;
	type arg_scalar_type = args[0].type;
	arg_scalar_type.rows = 1;
	type arg_vector_type = args[0].type;
	arg_vector_type.rows = 2;

	const spv::Id constant_zero = emit_constant(arg_scalar_type, 0u);

	spv::Id res[4] = {};
	for (unsigned int i = 0; i < args[0].type.rows; ++i)
	{
		assert(i < 4);

		spv::Id arg_scalar = args[0].base;
		if (args[0].type.rows > 1)
			arg_scalar = add_instruction(spv::OpCompositeExtract, convert_type(arg_scalar_type))
				.add(arg_scalar)
				.add(i);

		spv::Id arg_vector = add_instruction(spv::OpCompositeConstruct, convert_type(arg_vector_type))
			.add(arg_scalar)
			.add(constant_zero);

		res[i] = add_instruction(spv::OpExtInst, convert_type(res_scalar_type))
			.add(_glsl_ext)
			.add(spv::GLSLstd450PackHalf2x16)
			.add(arg_vector);
	}

	if (res_type.rows > 1)
		return
		add_instruction(spv::OpCompositeConstruct, convert_type(res_type))
			.add(res, res + res_type.rows);
	else
		return res[0];
	})

// ret firstbitlow
DEFINE_INTRINSIC(firstbitlow, 0, uint, uint)
DEFINE_INTRINSIC(firstbitlow, 0, uint2, uint2)
DEFINE_INTRINSIC(firstbitlow, 0, uint3, uint3)
DEFINE_INTRINSIC(firstbitlow, 0, uint4, uint4)
IMPLEMENT_INTRINSIC_GLSL(firstbitlow, 0, {
	code += "findLSB(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(firstbitlow, 0, {
	_uses_bitwise_intrinsics = true;
	if (_shader_model < 50)
		code += "__";
	code += "firstbitlow(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(firstbitlow, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FindILsb)
		.add(args[0].base);
	})

// ret firstbithigh
DEFINE_INTRINSIC(firstbithigh, 0, int, int)
DEFINE_INTRINSIC(firstbithigh, 0, int2, int2)
DEFINE_INTRINSIC(firstbithigh, 0, int3, int3)
DEFINE_INTRINSIC(firstbithigh, 0, int4, int4)
DEFINE_INTRINSIC(firstbithigh, 1, uint, uint)
DEFINE_INTRINSIC(firstbithigh, 1, uint2, uint2)
DEFINE_INTRINSIC(firstbithigh, 1, uint3, uint3)
DEFINE_INTRINSIC(firstbithigh, 1, uint4, uint4)
IMPLEMENT_INTRINSIC_GLSL(firstbithigh, 0, {
	code += "findMSB(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(firstbithigh, 1, {
	code += "findMSB(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(firstbithigh, 0, {
	_uses_bitwise_intrinsics = true;
	if (_shader_model < 50)
		code += "__";
	code += "firstbithigh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(firstbithigh, 1, {
	_uses_bitwise_intrinsics = true;
	if (_shader_model < 50)
		code += "__";
	code += "firstbithigh(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(firstbithigh, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FindSMsb)
		.add(args[0].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(firstbithigh, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FindUMsb)
		.add(args[0].base);
	})

// ret countbits
DEFINE_INTRINSIC(countbits, 0, uint, uint)
DEFINE_INTRINSIC(countbits, 0, uint2, uint2)
DEFINE_INTRINSIC(countbits, 0, uint3, uint3)
DEFINE_INTRINSIC(countbits, 0, uint4, uint4)
IMPLEMENT_INTRINSIC_GLSL(countbits, 0, {
	code += "bitCount(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(countbits, 0, {
	_uses_bitwise_intrinsics = true;
	if (_shader_model < 50)
		code += "__";
	code += "countbits(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(countbits, 0, {
	return
	add_instruction(spv::OpBitCount, convert_type(res_type))
		.add(args[0].base);
	})

// ret reversebits
DEFINE_INTRINSIC(reversebits, 0, uint, uint)
DEFINE_INTRINSIC(reversebits, 0, uint2, uint2)
DEFINE_INTRINSIC(reversebits, 0, uint3, uint3)
DEFINE_INTRINSIC(reversebits, 0, uint4, uint4)
IMPLEMENT_INTRINSIC_GLSL(reversebits, 0, {
	code += "bitfieldReverse(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(reversebits, 0, {
	_uses_bitwise_intrinsics = true;
	if (_shader_model < 50)
		code += "__";
	code += "reversebits(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(reversebits, 0, {
	return
	add_instruction(spv::OpBitReverse, convert_type(res_type))
		.add(args[0].base);
	})

// ret ceil(x)
DEFINE_INTRINSIC(ceil, 0, float, float)
DEFINE_INTRINSIC(ceil, 0, float2, float2)
DEFINE_INTRINSIC(ceil, 0, float3, float3)
DEFINE_INTRINSIC(ceil, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(ceil, 0, {
	code += "ceil(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(ceil, 0, {
	code += "ceil(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(ceil, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Ceil)
		.add(args[0].base);
	})

// ret floor(x)
DEFINE_INTRINSIC(floor, 0, float, float)
DEFINE_INTRINSIC(floor, 0, float2, float2)
DEFINE_INTRINSIC(floor, 0, float3, float3)
DEFINE_INTRINSIC(floor, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(floor, 0, {
	code += "floor(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(floor, 0, {
	if (_shader_model >= 40)
		code += "floor(" + id_to_name(args[0].base) + ')';
	else // Using the floor intrinsic sometimes causes the SM3 D3DCompiler to generate wrong code, so replace it with a custom implementation
		code += id_to_name(args[0].base) + " - frac(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(floor, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Floor)
		.add(args[0].base);
	})

// ret clamp(x, min, max)
DEFINE_INTRINSIC(clamp, 0, int, int, int, int)
DEFINE_INTRINSIC(clamp, 0, int2, int2, int2, int2)
DEFINE_INTRINSIC(clamp, 0, int3, int3, int3, int3)
DEFINE_INTRINSIC(clamp, 0, int4, int4, int4, int4)
DEFINE_INTRINSIC(clamp, 1, uint, uint, uint, uint)
DEFINE_INTRINSIC(clamp, 1, uint2, uint2, uint2, uint2)
DEFINE_INTRINSIC(clamp, 1, uint3, uint3, uint3, uint3)
DEFINE_INTRINSIC(clamp, 1, uint4, uint4, uint4, uint4)
DEFINE_INTRINSIC(clamp, 2, float, float, float, float)
DEFINE_INTRINSIC(clamp, 2, float2, float2, float2, float2)
DEFINE_INTRINSIC(clamp, 2, float3, float3, float3, float3)
DEFINE_INTRINSIC(clamp, 2, float4, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(clamp, 0, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(clamp, 1, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(clamp, 2, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(clamp, 0, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(clamp, 1, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(clamp, 2, {
	code += "clamp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(clamp, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SClamp)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(clamp, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450UClamp)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(clamp, 2, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FClamp)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret saturate(x)
DEFINE_INTRINSIC(saturate, 0, float, float)
DEFINE_INTRINSIC(saturate, 0, float2, float2)
DEFINE_INTRINSIC(saturate, 0, float3, float3)
DEFINE_INTRINSIC(saturate, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(saturate, 0, {
	code += "clamp(" + id_to_name(args[0].base) + ", 0.0, 1.0)";
	})
IMPLEMENT_INTRINSIC_HLSL(saturate, 0, {
	code += "saturate(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(saturate, 0, {
	const spv::Id constant_one = emit_constant(args[0].type, 1u);
	const spv::Id constant_zero = emit_constant(args[0].type, 0u);

	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FClamp)
		.add(args[0].base)
		.add(constant_zero)
		.add(constant_one);
	})

// ret mad(mvalue, avalue, bvalue)
DEFINE_INTRINSIC(mad, 0, float, float, float, float)
DEFINE_INTRINSIC(mad, 0, float2, float2, float2, float2)
DEFINE_INTRINSIC(mad, 0, float3, float3, float3, float3)
DEFINE_INTRINSIC(mad, 0, float4, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(mad, 0, {
	code += "fma(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mad, 0, {
	if (_shader_model >= 50)
		code += "mad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	else
		code += id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(mad, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Fma)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret rcp(x)
DEFINE_INTRINSIC(rcp, 0, float, float)
DEFINE_INTRINSIC(rcp, 0, float2, float2)
DEFINE_INTRINSIC(rcp, 0, float3, float3)
DEFINE_INTRINSIC(rcp, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(rcp, 0, {
	code += "1.0 / " + id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_HLSL(rcp, 0, {
	if (_shader_model >= 50)
		code += "rcp(" + id_to_name(args[0].base) + ')';
	else
		code += "1.0 / " + id_to_name(args[0].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(rcp, 0, {
	const spv::Id constant_one = emit_constant(args[0].type, 1u);

	return
	add_instruction(spv::OpFDiv, convert_type(res_type))
		.add(constant_one)
		.add(args[0].base);
	})

// ret pow(x, y)
DEFINE_INTRINSIC(pow, 0, float, float, float)
DEFINE_INTRINSIC(pow, 0, float2, float2, float2)
DEFINE_INTRINSIC(pow, 0, float3, float3, float3)
DEFINE_INTRINSIC(pow, 0, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(pow, 0, {
	code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(pow, 0, {
	code += "pow(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(pow, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Pow)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret exp(x)
DEFINE_INTRINSIC(exp, 0, float, float)
DEFINE_INTRINSIC(exp, 0, float2, float2)
DEFINE_INTRINSIC(exp, 0, float3, float3)
DEFINE_INTRINSIC(exp, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(exp, 0, {
	code += "exp(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(exp, 0, {
	code += "exp(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(exp, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Exp)
		.add(args[0].base);
	})

// ret exp2(x)
DEFINE_INTRINSIC(exp2, 0, float, float)
DEFINE_INTRINSIC(exp2, 0, float2, float2)
DEFINE_INTRINSIC(exp2, 0, float3, float3)
DEFINE_INTRINSIC(exp2, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(exp2, 0, {
	code += "exp2(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(exp2, 0, {
	code += "exp2(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(exp2, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Exp2)
		.add(args[0].base);
	})

// ret log(x)
DEFINE_INTRINSIC(log, 0, float, float)
DEFINE_INTRINSIC(log, 0, float2, float2)
DEFINE_INTRINSIC(log, 0, float3, float3)
DEFINE_INTRINSIC(log, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(log, 0, {
	code += "log(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(log, 0, {
	code += "log(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(log, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Log)
		.add(args[0].base);
	})

// ret log2(x)
DEFINE_INTRINSIC(log2, 0, float, float)
DEFINE_INTRINSIC(log2, 0, float2, float2)
DEFINE_INTRINSIC(log2, 0, float3, float3)
DEFINE_INTRINSIC(log2, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(log2, 0, {
	code += "log2(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(log2, 0, {
	code += "log2(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(log2, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Log2)
		.add(args[0].base);
	})

// ret log10(x)
DEFINE_INTRINSIC(log10, 0, float, float)
DEFINE_INTRINSIC(log10, 0, float2, float2)
DEFINE_INTRINSIC(log10, 0, float3, float3)
DEFINE_INTRINSIC(log10, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(log10, 0, {
	code += "(log2(" + id_to_name(args[0].base) + ") / log2(10.0))";
	})
IMPLEMENT_INTRINSIC_HLSL(log10, 0, {
	code += "(log2(" + id_to_name(args[0].base) + ") / log2(10.0))";
	})
IMPLEMENT_INTRINSIC_SPIRV(log10, 0, {
	const spv::Id log2 = add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Log2)
		.add(args[0].base);

	const spv::Id log10 = emit_constant(args[0].type, /* log2(10) */
		constant { { 3.321928f, 3.321928f, 3.321928f, 3.321928f } });

	return
	add_instruction(spv::OpFDiv, convert_type(res_type))
		.add(log2)
		.add(log10); })

// ret sign(x)
DEFINE_INTRINSIC(sign, 0, int, int)
DEFINE_INTRINSIC(sign, 0, int2, int2)
DEFINE_INTRINSIC(sign, 0, int3, int3)
DEFINE_INTRINSIC(sign, 0, int4, int4)
DEFINE_INTRINSIC(sign, 1, float, float)
DEFINE_INTRINSIC(sign, 1, float2, float2)
DEFINE_INTRINSIC(sign, 1, float3, float3)
DEFINE_INTRINSIC(sign, 1, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(sign, 0, {
	code += "sign(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(sign, 1, {
	code += "sign(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sign, 0, {
	code += "sign(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sign, 1, {
	code += "sign(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(sign, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SSign)
		.add(args[0].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(sign, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FSign)
		.add(args[0].base);
	})

// ret sqrt(x)
DEFINE_INTRINSIC(sqrt, 0, float, float)
DEFINE_INTRINSIC(sqrt, 0, float2, float2)
DEFINE_INTRINSIC(sqrt, 0, float3, float3)
DEFINE_INTRINSIC(sqrt, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(sqrt, 0, {
	code += "sqrt(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(sqrt, 0, {
	code += "sqrt(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(sqrt, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Sqrt)
		.add(args[0].base);
	})

// ret rsqrt(x)
DEFINE_INTRINSIC(rsqrt, 0, float, float)
DEFINE_INTRINSIC(rsqrt, 0, float2, float2)
DEFINE_INTRINSIC(rsqrt, 0, float3, float3)
DEFINE_INTRINSIC(rsqrt, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(rsqrt, 0, {
	code += "inversesqrt(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(rsqrt, 0, {
	code += "rsqrt(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(rsqrt, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450InverseSqrt)
		.add(args[0].base);
	})

// ret lerp(x, y, s)
DEFINE_INTRINSIC(lerp, 0, float, float, float, float)
DEFINE_INTRINSIC(lerp, 0, float2, float2, float2, float2)
DEFINE_INTRINSIC(lerp, 0, float3, float3, float3, float3)
DEFINE_INTRINSIC(lerp, 0, float4, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(lerp, 0, {
	code += "mix(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(lerp, 0, {
	code += "lerp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(lerp, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FMix)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret step(y, x)
DEFINE_INTRINSIC(step, 0, float, float, float)
DEFINE_INTRINSIC(step, 0, float2, float2, float2)
DEFINE_INTRINSIC(step, 0, float3, float3, float3)
DEFINE_INTRINSIC(step, 0, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(step, 0, {
	code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(step, 0, {
	code += "step(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(step, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Step)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret smoothstep(min, max, x)
DEFINE_INTRINSIC(smoothstep, 0, float, float, float, float)
DEFINE_INTRINSIC(smoothstep, 0, float2, float2, float2, float2)
DEFINE_INTRINSIC(smoothstep, 0, float3, float3, float3, float3)
DEFINE_INTRINSIC(smoothstep, 0, float4, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(smoothstep, 0, {
	code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(smoothstep, 0, {
	code += "smoothstep(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(smoothstep, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(args[2].type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SmoothStep)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret frac(x)
DEFINE_INTRINSIC(frac, 0, float, float)
DEFINE_INTRINSIC(frac, 0, float2, float2)
DEFINE_INTRINSIC(frac, 0, float3, float3)
DEFINE_INTRINSIC(frac, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(frac, 0, {
	code += "fract(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(frac, 0, {
	code += "frac(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(frac, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Fract)
		.add(args[0].base);
	})

// ret ldexp(x, exp)
DEFINE_INTRINSIC(ldexp, 0, float, float, int)
DEFINE_INTRINSIC(ldexp, 0, float2, float2, int2)
DEFINE_INTRINSIC(ldexp, 0, float3, float3, int3)
DEFINE_INTRINSIC(ldexp, 0, float4, float4, int4)
IMPLEMENT_INTRINSIC_GLSL(ldexp, 0, {
	code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(ldexp, 0, {
	code += "ldexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(ldexp, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Ldexp)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret modf(x, out ip)
DEFINE_INTRINSIC(modf, 0, float, float, out_float)
DEFINE_INTRINSIC(modf, 0, float2, float2, out_float2)
DEFINE_INTRINSIC(modf, 0, float3, float3, out_float3)
DEFINE_INTRINSIC(modf, 0, float4, float4, out_float4)
IMPLEMENT_INTRINSIC_GLSL(modf, 0, {
	code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(modf, 0, {
	code += "modf(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(modf, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Modf)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret frexp(x, out exp)
DEFINE_INTRINSIC(frexp, 0, float, float, out_int)
DEFINE_INTRINSIC(frexp, 0, float2, float2, out_int2)
DEFINE_INTRINSIC(frexp, 0, float3, float3, out_int3)
DEFINE_INTRINSIC(frexp, 0, float4, float4, out_int4)
IMPLEMENT_INTRINSIC_GLSL(frexp, 0, {
	code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(frexp, 0, {
	code += "frexp(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(frexp, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Frexp)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret trunc(x)
DEFINE_INTRINSIC(trunc, 0, float, float)
DEFINE_INTRINSIC(trunc, 0, float2, float2)
DEFINE_INTRINSIC(trunc, 0, float3, float3)
DEFINE_INTRINSIC(trunc, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(trunc, 0, {
	code += "trunc(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(trunc, 0, {
	code += "trunc(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(trunc, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Trunc)
		.add(args[0].base);
	})

// ret round(x)
DEFINE_INTRINSIC(round, 0, float, float)
DEFINE_INTRINSIC(round, 0, float2, float2)
DEFINE_INTRINSIC(round, 0, float3, float3)
DEFINE_INTRINSIC(round, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(round, 0, {
	code += "round(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(round, 0, {
	code += "round(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(round, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Round)
		.add(args[0].base);
	})

// ret min(x, y)
DEFINE_INTRINSIC(min, 0, int, int, int)
DEFINE_INTRINSIC(min, 0, int2, int2, int2)
DEFINE_INTRINSIC(min, 0, int3, int3, int3)
DEFINE_INTRINSIC(min, 0, int4, int4, int4)
DEFINE_INTRINSIC(min, 1, float, float, float)
DEFINE_INTRINSIC(min, 1, float2, float2, float2)
DEFINE_INTRINSIC(min, 1, float3, float3, float3)
DEFINE_INTRINSIC(min, 1, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(min, 0, {
	code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(min, 1, {
	code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(min, 0, {
	code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(min, 1, {
	code += "min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(min, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SMin)
		.add(args[0].base)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(min, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FMin)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret max(x, y)
DEFINE_INTRINSIC(max, 0, int, int, int)
DEFINE_INTRINSIC(max, 0, int2, int2, int2)
DEFINE_INTRINSIC(max, 0, int3, int3, int3)
DEFINE_INTRINSIC(max, 0, int4, int4, int4)
DEFINE_INTRINSIC(max, 1, float, float, float)
DEFINE_INTRINSIC(max, 1, float2, float2, float2)
DEFINE_INTRINSIC(max, 1, float3, float3, float3)
DEFINE_INTRINSIC(max, 1, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(max, 0, {
	code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(max, 1, {
	code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(max, 0, {
	code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(max, 1, {
	code += "max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(max, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450SMax)
		.add(args[0].base)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(max, 1, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FMax)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret degrees(x)
DEFINE_INTRINSIC(degrees, 0, float, float)
DEFINE_INTRINSIC(degrees, 0, float2, float2)
DEFINE_INTRINSIC(degrees, 0, float3, float3)
DEFINE_INTRINSIC(degrees, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(degrees, 0, {
	code += "degrees(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(degrees, 0, {
	code += "degrees(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(degrees, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Degrees)
		.add(args[0].base);
	})

// ret radians(x)
DEFINE_INTRINSIC(radians, 0, float, float)
DEFINE_INTRINSIC(radians, 0, float2, float2)
DEFINE_INTRINSIC(radians, 0, float3, float3)
DEFINE_INTRINSIC(radians, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(radians, 0, {
	code += "radians(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(radians, 0, {
	code += "radians(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(radians, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Radians)
		.add(args[0].base);
	})

// ret ddx(x)
DEFINE_INTRINSIC(ddx, 0, float, float)
DEFINE_INTRINSIC(ddx, 0, float2, float2)
DEFINE_INTRINSIC(ddx, 0, float3, float3)
DEFINE_INTRINSIC(ddx, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(ddx, 0, {
	code += "dFdx(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(ddx, 0, {
	code += "ddx(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(ddx, 0, {
	return
	add_instruction(spv::OpDPdx, convert_type(res_type))
		.add(args[0].base);
	})

// ret ddy(x)
DEFINE_INTRINSIC(ddy, 0, float, float)
DEFINE_INTRINSIC(ddy, 0, float2, float2)
DEFINE_INTRINSIC(ddy, 0, float3, float3)
DEFINE_INTRINSIC(ddy, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(ddy, 0, {
	code += "dFdy(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(ddy, 0, {
	code += "ddy(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(ddy, 0, {
	return
	add_instruction(spv::OpDPdy, convert_type(res_type))
		.add(args[0].base);
	})

// ret fwidth(x)
DEFINE_INTRINSIC(fwidth, 0, float, float)
DEFINE_INTRINSIC(fwidth, 0, float2, float2)
DEFINE_INTRINSIC(fwidth, 0, float3, float3)
DEFINE_INTRINSIC(fwidth, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(fwidth, 0, {
	code += "fwidth(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(fwidth, 0, {
	code += "fwidth(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(fwidth, 0, {
	return
	add_instruction(spv::OpFwidth, convert_type(res_type))
		.add(args[0].base);
	})

// ret dot(x, y)
DEFINE_INTRINSIC(dot, 0, float, float, float)
DEFINE_INTRINSIC(dot, 1, float, float2, float2)
DEFINE_INTRINSIC(dot, 1, float, float3, float3)
DEFINE_INTRINSIC(dot, 1, float, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(dot, 0, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(dot, 1, {
	code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(dot, 0, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(dot, 1, {
	code += "dot(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(dot, 0, {
	return
	add_instruction(spv::OpFMul, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(dot, 1, {
	return
	add_instruction(spv::OpDot, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base);
	})

// ret cross(x, y)
DEFINE_INTRINSIC(cross, 0, float3, float3, float3)
IMPLEMENT_INTRINSIC_GLSL(cross, 0, {
	code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(cross, 0, {
	code += "cross(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(cross, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Cross)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret length(x)
DEFINE_INTRINSIC(length, 0, float, float)
DEFINE_INTRINSIC(length, 0, float, float2)
DEFINE_INTRINSIC(length, 0, float, float3)
DEFINE_INTRINSIC(length, 0, float, float4)
IMPLEMENT_INTRINSIC_GLSL(length, 0, {
	code += "length(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(length, 0, {
	code += "length(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(length, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Length)
		.add(args[0].base);
	})

// ret distance(x, y)
DEFINE_INTRINSIC(distance, 0, float, float, float)
DEFINE_INTRINSIC(distance, 0, float, float2, float2)
DEFINE_INTRINSIC(distance, 0, float, float3, float3)
DEFINE_INTRINSIC(distance, 0, float, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(distance, 0, {
	code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(distance, 0, {
	code += "distance(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(distance, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Distance)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret normalize(x)
DEFINE_INTRINSIC(normalize, 0, float2, float2)
DEFINE_INTRINSIC(normalize, 0, float3, float3)
DEFINE_INTRINSIC(normalize, 0, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(normalize, 0, {
	code += "normalize(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(normalize, 0, {
	code += "normalize(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(normalize, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Normalize)
		.add(args[0].base);
	})

// ret transpose(x)
DEFINE_INTRINSIC(transpose, 0, float2x2, float2x2)
DEFINE_INTRINSIC(transpose, 0, float2x3, float3x2)
DEFINE_INTRINSIC(transpose, 0, float2x4, float4x2)
DEFINE_INTRINSIC(transpose, 0, float3x2, float2x3)
DEFINE_INTRINSIC(transpose, 0, float3x3, float3x3)
DEFINE_INTRINSIC(transpose, 0, float3x4, float4x3)
DEFINE_INTRINSIC(transpose, 0, float4x2, float2x4)
DEFINE_INTRINSIC(transpose, 0, float4x3, float3x4)
DEFINE_INTRINSIC(transpose, 0, float4x4, float4x4)
IMPLEMENT_INTRINSIC_GLSL(transpose, 0, {
	code += "transpose(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(transpose, 0, {
	code += "transpose(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(transpose, 0, {
	return
	add_instruction(spv::OpTranspose, convert_type(res_type))
		.add(args[0].base);
	})

// ret determinant(m)
DEFINE_INTRINSIC(determinant, 0, float, float2x2)
DEFINE_INTRINSIC(determinant, 0, float, float3x3)
DEFINE_INTRINSIC(determinant, 0, float, float4x4)
IMPLEMENT_INTRINSIC_GLSL(determinant, 0, {
	code += "determinant(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(determinant, 0, {
	code += "determinant(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(determinant, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Determinant)
		.add(args[0].base);
	})

// ret reflect(i, n)
DEFINE_INTRINSIC(reflect, 0, float2, float2, float2)
DEFINE_INTRINSIC(reflect, 0, float3, float3, float3)
DEFINE_INTRINSIC(reflect, 0, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(reflect, 0, {
	code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(reflect, 0, {
	code += "reflect(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(reflect, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Reflect)
		.add(args[0].base)
		.add(args[1].base);
	})

// ret refract(i, n, eta)
DEFINE_INTRINSIC(refract, 0, float2, float2, float2, float)
DEFINE_INTRINSIC(refract, 0, float3, float3, float3, float)
DEFINE_INTRINSIC(refract, 0, float4, float4, float4, float)
IMPLEMENT_INTRINSIC_GLSL(refract, 0, {
	code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(refract, 0, {
	code += "refract(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(refract, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450Refract)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret faceforward(n, i, ng)
DEFINE_INTRINSIC(faceforward, 0, float, float, float, float)
DEFINE_INTRINSIC(faceforward, 0, float2, float2, float2, float2)
DEFINE_INTRINSIC(faceforward, 0, float3, float3, float3, float3)
DEFINE_INTRINSIC(faceforward, 0, float4, float4, float4, float4)
IMPLEMENT_INTRINSIC_GLSL(faceforward, 0, {
	code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(faceforward, 0, {
	code += "faceforward(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(faceforward, 0, {
	return
	add_instruction(spv::OpExtInst, convert_type(res_type))
		.add(_glsl_ext)
		.add(spv::GLSLstd450FaceForward)
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].base);
	})

// ret mul(x, y)
DEFINE_INTRINSIC(mul, 0, int2, int, int2)
DEFINE_INTRINSIC(mul, 0, int3, int, int3)
DEFINE_INTRINSIC(mul, 0, int4, int, int4)
DEFINE_INTRINSIC(mul, 0, float2, float, float2)
DEFINE_INTRINSIC(mul, 0, float3, float, float3)
DEFINE_INTRINSIC(mul, 0, float4, float, float4)
IMPLEMENT_INTRINSIC_GLSL(mul, 0, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 0, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 0, {
	return
	add_instruction(spv::OpVectorTimesScalar, convert_type(res_type))
		.add(args[1].base)
		.add(args[0].base);
	})
DEFINE_INTRINSIC(mul, 1, int2, int2, int)
DEFINE_INTRINSIC(mul, 1, int3, int3, int)
DEFINE_INTRINSIC(mul, 1, int4, int4, int)
DEFINE_INTRINSIC(mul, 1, float2, float2, float)
DEFINE_INTRINSIC(mul, 1, float3, float3, float)
DEFINE_INTRINSIC(mul, 1, float4, float4, float)
IMPLEMENT_INTRINSIC_GLSL(mul, 1, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 1, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 1, {
	return
	add_instruction(spv::OpVectorTimesScalar, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base);
	})

DEFINE_INTRINSIC(mul, 2, int2x2, int, int2x2)
DEFINE_INTRINSIC(mul, 2, int2x3, int, int2x3)
DEFINE_INTRINSIC(mul, 2, int2x4, int, int2x4)
DEFINE_INTRINSIC(mul, 2, int3x2, int, int3x2)
DEFINE_INTRINSIC(mul, 2, int3x3, int, int3x3)
DEFINE_INTRINSIC(mul, 2, int3x4, int, int3x4)
DEFINE_INTRINSIC(mul, 2, int4x2, int, int4x2)
DEFINE_INTRINSIC(mul, 2, int4x3, int, int4x3)
DEFINE_INTRINSIC(mul, 2, int4x4, int, int4x4)
DEFINE_INTRINSIC(mul, 2, float2x2, float, float2x2)
DEFINE_INTRINSIC(mul, 2, float2x3, float, float2x3)
DEFINE_INTRINSIC(mul, 2, float2x4, float, float2x4)
DEFINE_INTRINSIC(mul, 2, float3x2, float, float3x2)
DEFINE_INTRINSIC(mul, 2, float3x3, float, float3x3)
DEFINE_INTRINSIC(mul, 2, float3x4, float, float3x4)
DEFINE_INTRINSIC(mul, 2, float4x2, float, float4x2)
DEFINE_INTRINSIC(mul, 2, float4x3, float, float4x3)
DEFINE_INTRINSIC(mul, 2, float4x4, float, float4x4)
IMPLEMENT_INTRINSIC_GLSL(mul, 2, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 2, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 2, {
	return
	add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type))
		.add(args[1].base)
		.add(args[0].base);
	})
DEFINE_INTRINSIC(mul, 3, int2x2, int2x2, int)
DEFINE_INTRINSIC(mul, 3, int2x3, int2x3, int)
DEFINE_INTRINSIC(mul, 3, int2x4, int2x4, int)
DEFINE_INTRINSIC(mul, 3, int3x2, int3x2, int)
DEFINE_INTRINSIC(mul, 3, int3x3, int3x3, int)
DEFINE_INTRINSIC(mul, 3, int3x4, int3x4, int)
DEFINE_INTRINSIC(mul, 3, int4x2, int4x2, int)
DEFINE_INTRINSIC(mul, 3, int4x3, int4x3, int)
DEFINE_INTRINSIC(mul, 3, int4x4, int4x4, int)
DEFINE_INTRINSIC(mul, 3, float2x2, float2x2, float)
DEFINE_INTRINSIC(mul, 3, float2x3, float2x3, float)
DEFINE_INTRINSIC(mul, 3, float2x4, float2x4, float)
DEFINE_INTRINSIC(mul, 3, float3x2, float3x2, float)
DEFINE_INTRINSIC(mul, 3, float3x3, float3x3, float)
DEFINE_INTRINSIC(mul, 3, float3x4, float3x4, float)
DEFINE_INTRINSIC(mul, 3, float4x2, float4x2, float)
DEFINE_INTRINSIC(mul, 3, float4x3, float4x3, float)
DEFINE_INTRINSIC(mul, 3, float4x4, float4x4, float)
IMPLEMENT_INTRINSIC_GLSL(mul, 3, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 3, {
	code += '(' + id_to_name(args[0].base) + " * " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 3, {
	return
	add_instruction(spv::OpMatrixTimesScalar, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base);
	})

DEFINE_INTRINSIC(mul, 4, int2, int2, int2x2)
DEFINE_INTRINSIC(mul, 4, int3, int2, int2x3)
DEFINE_INTRINSIC(mul, 4, int4, int2, int2x4)
DEFINE_INTRINSIC(mul, 4, int2, int3, int3x2)
DEFINE_INTRINSIC(mul, 4, int3, int3, int3x3)
DEFINE_INTRINSIC(mul, 4, int4, int3, int3x4)
DEFINE_INTRINSIC(mul, 4, int2, int4, int4x2)
DEFINE_INTRINSIC(mul, 4, int3, int4, int4x3)
DEFINE_INTRINSIC(mul, 4, int4, int4, int4x4)
DEFINE_INTRINSIC(mul, 4, float2, float2, float2x2)
DEFINE_INTRINSIC(mul, 4, float3, float2, float2x3)
DEFINE_INTRINSIC(mul, 4, float4, float2, float2x4)
DEFINE_INTRINSIC(mul, 4, float2, float3, float3x2)
DEFINE_INTRINSIC(mul, 4, float3, float3, float3x3)
DEFINE_INTRINSIC(mul, 4, float4, float3, float3x4)
DEFINE_INTRINSIC(mul, 4, float2, float4, float4x2)
DEFINE_INTRINSIC(mul, 4, float3, float4, float4x3)
DEFINE_INTRINSIC(mul, 4, float4, float4, float4x4)
IMPLEMENT_INTRINSIC_GLSL(mul, 4, {
	// Flip inputs because matrices are column-wise
	code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 4, {
	code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 4, {
	return
	add_instruction(spv::OpMatrixTimesVector, convert_type(res_type))
		.add(args[1].base) // Flip inputs because matrices are column-wise
		.add(args[0].base);
	})
DEFINE_INTRINSIC(mul, 5, int2, int2x2, int2)
DEFINE_INTRINSIC(mul, 5, int2, int2x3, int3)
DEFINE_INTRINSIC(mul, 5, int2, int2x4, int4)
DEFINE_INTRINSIC(mul, 5, int3, int3x2, int2)
DEFINE_INTRINSIC(mul, 5, int3, int3x3, int3)
DEFINE_INTRINSIC(mul, 5, int3, int3x4, int4)
DEFINE_INTRINSIC(mul, 5, int4, int4x2, int2)
DEFINE_INTRINSIC(mul, 5, int4, int4x3, int3)
DEFINE_INTRINSIC(mul, 5, int4, int4x4, int4)
DEFINE_INTRINSIC(mul, 5, float2, float2x2, float2)
DEFINE_INTRINSIC(mul, 5, float2, float2x3, float3)
DEFINE_INTRINSIC(mul, 5, float2, float2x4, float4)
DEFINE_INTRINSIC(mul, 5, float3, float3x2, float2)
DEFINE_INTRINSIC(mul, 5, float3, float3x3, float3)
DEFINE_INTRINSIC(mul, 5, float3, float3x4, float4)
DEFINE_INTRINSIC(mul, 5, float4, float4x2, float2)
DEFINE_INTRINSIC(mul, 5, float4, float4x3, float3)
DEFINE_INTRINSIC(mul, 5, float4, float4x4, float4)
IMPLEMENT_INTRINSIC_GLSL(mul, 5, {
	// Flip inputs because matrices are column-wise
	code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 5, {
	code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 5, {
	return
	add_instruction(spv::OpVectorTimesMatrix, convert_type(res_type))
		.add(args[1].base) // Flip inputs because matrices are column-wise
		.add(args[0].base);
	})

DEFINE_INTRINSIC(mul, 6, int2x2, int2x2, int2x2)
DEFINE_INTRINSIC(mul, 6, int2x3, int2x2, int2x3)
DEFINE_INTRINSIC(mul, 6, int2x4, int2x2, int2x4)
DEFINE_INTRINSIC(mul, 6, int2x2, int2x3, int3x2)
DEFINE_INTRINSIC(mul, 6, int2x3, int2x3, int3x3)
DEFINE_INTRINSIC(mul, 6, int2x4, int2x3, int3x4)
DEFINE_INTRINSIC(mul, 6, int2x2, int2x4, int4x2)
DEFINE_INTRINSIC(mul, 6, int2x3, int2x4, int4x3)
DEFINE_INTRINSIC(mul, 6, int2x4, int2x4, int4x4)
DEFINE_INTRINSIC(mul, 6, int3x2, int3x2, int2x2)
DEFINE_INTRINSIC(mul, 6, int3x3, int3x2, int2x3)
DEFINE_INTRINSIC(mul, 6, int3x4, int3x2, int2x4)
DEFINE_INTRINSIC(mul, 6, int3x2, int3x3, int3x2)
DEFINE_INTRINSIC(mul, 6, int3x3, int3x3, int3x3)
DEFINE_INTRINSIC(mul, 6, int3x4, int3x3, int3x4)
DEFINE_INTRINSIC(mul, 6, int3x2, int3x4, int4x2)
DEFINE_INTRINSIC(mul, 6, int3x3, int3x4, int4x3)
DEFINE_INTRINSIC(mul, 6, int3x4, int3x4, int4x4)
DEFINE_INTRINSIC(mul, 6, int4x2, int4x2, int2x2)
DEFINE_INTRINSIC(mul, 6, int4x3, int4x2, int2x3)
DEFINE_INTRINSIC(mul, 6, int4x4, int4x2, int2x4)
DEFINE_INTRINSIC(mul, 6, int4x2, int4x3, int3x2)
DEFINE_INTRINSIC(mul, 6, int4x3, int4x3, int3x3)
DEFINE_INTRINSIC(mul, 6, int4x4, int4x3, int3x4)
DEFINE_INTRINSIC(mul, 6, int4x2, int4x4, int4x2)
DEFINE_INTRINSIC(mul, 6, int4x3, int4x4, int4x3)
DEFINE_INTRINSIC(mul, 6, int4x4, int4x4, int4x4)
DEFINE_INTRINSIC(mul, 6, float2x2, float2x2, float2x2)
DEFINE_INTRINSIC(mul, 6, float2x3, float2x2, float2x3)
DEFINE_INTRINSIC(mul, 6, float2x4, float2x2, float2x4)
DEFINE_INTRINSIC(mul, 6, float2x2, float2x3, float3x2)
DEFINE_INTRINSIC(mul, 6, float2x3, float2x3, float3x3)
DEFINE_INTRINSIC(mul, 6, float2x4, float2x3, float3x4)
DEFINE_INTRINSIC(mul, 6, float2x2, float2x4, float4x2)
DEFINE_INTRINSIC(mul, 6, float2x3, float2x4, float4x3)
DEFINE_INTRINSIC(mul, 6, float2x4, float2x4, float4x4)
DEFINE_INTRINSIC(mul, 6, float3x2, float3x2, float2x2)
DEFINE_INTRINSIC(mul, 6, float3x3, float3x2, float2x3)
DEFINE_INTRINSIC(mul, 6, float3x4, float3x2, float2x4)
DEFINE_INTRINSIC(mul, 6, float3x2, float3x3, float3x2)
DEFINE_INTRINSIC(mul, 6, float3x3, float3x3, float3x3)
DEFINE_INTRINSIC(mul, 6, float3x4, float3x3, float3x4)
DEFINE_INTRINSIC(mul, 6, float3x2, float3x4, float4x2)
DEFINE_INTRINSIC(mul, 6, float3x3, float3x4, float4x3)
DEFINE_INTRINSIC(mul, 6, float3x4, float3x4, float4x4)
DEFINE_INTRINSIC(mul, 6, float4x2, float4x2, float2x2)
DEFINE_INTRINSIC(mul, 6, float4x3, float4x2, float2x3)
DEFINE_INTRINSIC(mul, 6, float4x4, float4x2, float2x4)
DEFINE_INTRINSIC(mul, 6, float4x2, float4x3, float3x2)
DEFINE_INTRINSIC(mul, 6, float4x3, float4x3, float3x3)
DEFINE_INTRINSIC(mul, 6, float4x4, float4x3, float3x4)
DEFINE_INTRINSIC(mul, 6, float4x2, float4x4, float4x2)
DEFINE_INTRINSIC(mul, 6, float4x3, float4x4, float4x3)
DEFINE_INTRINSIC(mul, 6, float4x4, float4x4, float4x4)
IMPLEMENT_INTRINSIC_GLSL(mul, 6, {
	// Flip inputs because matrices are column-wise
	code += '(' + id_to_name(args[1].base) + " * " + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(mul, 6, {
	code += "mul(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(mul, 6, {
	return
	add_instruction(spv::OpMatrixTimesMatrix, convert_type(res_type))
		.add(args[1].base) // Flip inputs because matrices are column-wise
		.add(args[0].base);
	})

// ret isinf(x)
DEFINE_INTRINSIC(isinf, 0, bool, float)
DEFINE_INTRINSIC(isinf, 0, bool2, float2)
DEFINE_INTRINSIC(isinf, 0, bool3, float3)
DEFINE_INTRINSIC(isinf, 0, bool4, float4)
IMPLEMENT_INTRINSIC_GLSL(isinf, 0, {
	code += "isinf(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(isinf, 0, {
	code += "isinf(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(isinf, 0, {
	return
	add_instruction(spv::OpIsInf, convert_type(res_type))
		.add(args[0].base);
	})

// ret isnan(x)
DEFINE_INTRINSIC(isnan, 0, bool, float)
DEFINE_INTRINSIC(isnan, 0, bool2, float2)
DEFINE_INTRINSIC(isnan, 0, bool3, float3)
DEFINE_INTRINSIC(isnan, 0, bool4, float4)
IMPLEMENT_INTRINSIC_GLSL(isnan, 0, {
	code += "isnan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(isnan, 0, {
	code += "isnan(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(isnan, 0, {
	return
	add_instruction(spv::OpIsNan, convert_type(res_type))
		.add(args[0].base);
	})

// ret tex1D(s, coords)
// ret tex1D(s, coords, offset)
DEFINE_INTRINSIC(tex1D, 0, int, sampler1d_int, float)
DEFINE_INTRINSIC(tex1D, 0, uint, sampler1d_uint, float)
DEFINE_INTRINSIC(tex1D, 0, float, sampler1d_float, float)
DEFINE_INTRINSIC(tex1D, 0, float4, sampler1d_float4, float)
DEFINE_INTRINSIC(tex1D, 1, int, sampler1d_int, float, int)
DEFINE_INTRINSIC(tex1D, 1, uint, sampler1d_uint, float, int)
DEFINE_INTRINSIC(tex1D, 1, float, sampler1d_float, float, int)
DEFINE_INTRINSIC(tex1D, 1, float4, sampler1d_float4, float, int)
IMPLEMENT_INTRINSIC_GLSL(tex1D, 0, {
	code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex1D, 1, {
	code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex1D, 0, {
	if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + "); " +
			id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))";
	}
	else {
		code += "tex1D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex1D, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
		else
			code += "uint temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + "); " +
			id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex1D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1D, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsMaskNone);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1D, 1, {
	// Non-constant offset operand needs extended capability
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex2D(s, coords)
// ret tex2D(s, coords, offset)
DEFINE_INTRINSIC(tex2D, 0, int, sampler2d_int, float2)
DEFINE_INTRINSIC(tex2D, 0, uint, sampler2d_uint, float2)
DEFINE_INTRINSIC(tex2D, 0, float, sampler2d_float, float2)
DEFINE_INTRINSIC(tex2D, 0, float4, sampler2d_float4, float2)
DEFINE_INTRINSIC(tex2D, 1, int, sampler2d_int, float2, int2)
DEFINE_INTRINSIC(tex2D, 1, uint, sampler2d_uint, float2, int2)
DEFINE_INTRINSIC(tex2D, 1, float, sampler2d_float, float2, int2)
DEFINE_INTRINSIC(tex2D, 1, float4, sampler2d_float4, float2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2D, 0, {
	code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex2D, 1, {
	code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2D, 0, {
	if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))";
	}
	else {
		code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex2D, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
		else
			code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex2D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2D, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsMaskNone);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2D, 1, {
	// Non-constant offset operand needs extended capability
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex3D(s, coords)
// ret tex3D(s, coords, offset)
DEFINE_INTRINSIC(tex3D, 0, int, sampler3d_int, float3)
DEFINE_INTRINSIC(tex3D, 0, uint, sampler3d_uint, float3)
DEFINE_INTRINSIC(tex3D, 0, float, sampler3d_float, float3)
DEFINE_INTRINSIC(tex3D, 0, float4, sampler3d_float4, float3)
DEFINE_INTRINSIC(tex3D, 1, int, sampler3d_int, float3, int3)
DEFINE_INTRINSIC(tex3D, 1, uint, sampler3d_uint, float3, int3)
DEFINE_INTRINSIC(tex3D, 1, float, sampler3d_float, float3, int3)
DEFINE_INTRINSIC(tex3D, 1, float4, sampler3d_float4, float3, int3)
IMPLEMENT_INTRINSIC_GLSL(tex3D, 0, {
	code += "texture(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex3D, 1, {
	code += "textureOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex3D, 0, {
	if (_shader_model >= 40) { // SM4 and higher use a more object-oriented programming model for textures
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " +
			id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0))";
	}
	else {
		code += "tex3D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex3D, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.Sample(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
		else
			code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions(temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " +
			id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + " * temp" + std::to_string(res) + ", 0), " + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex3D(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize)";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3D, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsMaskNone);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3D, 1, {
	// Non-constant offset operand needs extended capability
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleImplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex1Dgrad(s, coords, ddx, ddy)
// ret tex1Dgrad(s, coords, ddx, ddy, offset)
DEFINE_INTRINSIC(tex1Dgrad, 0, int, sampler1d_int, float, float, float)
DEFINE_INTRINSIC(tex1Dgrad, 0, uint, sampler1d_uint, float, float, float)
DEFINE_INTRINSIC(tex1Dgrad, 0, float, sampler1d_float, float, float, float)
DEFINE_INTRINSIC(tex1Dgrad, 0, float4, sampler1d_float4, float, float, float)
DEFINE_INTRINSIC(tex1Dgrad, 1, int, sampler1d_int, float, float, float, int)
DEFINE_INTRINSIC(tex1Dgrad, 1, uint, sampler1d_uint, float, float, float, int)
DEFINE_INTRINSIC(tex1Dgrad, 1, float, sampler1d_float, float, float, float, int)
DEFINE_INTRINSIC(tex1Dgrad, 1, float4, sampler1d_float4, float, float, float, int)
IMPLEMENT_INTRINSIC_GLSL(tex1Dgrad, 0, {
	code += "textureGrad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dgrad, 1, {
	code += "textureGradOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dgrad, 0, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	}
	else {
		code += "tex1Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dgrad, 1, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	}
	else {
		code += "tex1Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[4].base) + " * " + id_to_name(args[0].base) + ".pixelsize, " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dgrad, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask)
		.add(args[2].base)
		.add(args[3].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dgrad, 1, {
	if (!args[4].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask | (args[4].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex2Dgrad(s, coords, ddx, ddy)
// ret tex2Dgrad(s, coords, ddx, ddy, offset)
DEFINE_INTRINSIC(tex2Dgrad, 0, int, sampler2d_int, float2, float2, float2)
DEFINE_INTRINSIC(tex2Dgrad, 0, uint, sampler2d_uint, float2, float2, float2)
DEFINE_INTRINSIC(tex2Dgrad, 0, float, sampler2d_float, float2, float2, float2)
DEFINE_INTRINSIC(tex2Dgrad, 0, float4, sampler2d_float4, float2, float2, float2)
DEFINE_INTRINSIC(tex2Dgrad, 1, int, sampler2d_int, float2, float2, float2, int2)
DEFINE_INTRINSIC(tex2Dgrad, 1, uint, sampler2d_uint, float2, float2, float2, int2)
DEFINE_INTRINSIC(tex2Dgrad, 1, float, sampler2d_float, float2, float2, float2, int2)
DEFINE_INTRINSIC(tex2Dgrad, 1, float4, sampler2d_float4, float2, float2, float2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2Dgrad, 0, {
	code += "textureGrad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dgrad, 1, {
	code += "textureGradOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dgrad, 0, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	}
	else {
		code += "tex2Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dgrad, 1, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	}
	else {
		code += "tex2Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[4].base) + " * " + id_to_name(args[0].base) + ".pixelsize, " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dgrad, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask)
		.add(args[2].base)
		.add(args[3].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dgrad, 1, {
	if (!args[4].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask | (args[4].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex3Dgrad(s, coords, ddx, ddy)
// ret tex3Dgrad(s, coords, ddx, ddy, offset)
DEFINE_INTRINSIC(tex3Dgrad, 0, int, sampler3d_int, float3, float3, float3)
DEFINE_INTRINSIC(tex3Dgrad, 0, uint, sampler3d_uint, float3, float3, float3)
DEFINE_INTRINSIC(tex3Dgrad, 0, float, sampler3d_float, float3, float3, float3)
DEFINE_INTRINSIC(tex3Dgrad, 0, float4, sampler3d_float4, float3, float3, float3)
DEFINE_INTRINSIC(tex3Dgrad, 1, int, sampler3d_int, float3, float3, float3, int2)
DEFINE_INTRINSIC(tex3Dgrad, 1, uint, sampler3d_uint, float3, float3, float3, int2)
DEFINE_INTRINSIC(tex3Dgrad, 1, float, sampler3d_float, float3, float3, float3, int2)
DEFINE_INTRINSIC(tex3Dgrad, 1, float4, sampler3d_float4, float3, float3, float3, int2)
IMPLEMENT_INTRINSIC_GLSL(tex3Dgrad, 0, {
	code += "textureGrad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dgrad, 1, {
	code += "textureGradOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dgrad, 0, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	}
	else {
		code += "tex3Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dgrad, 1, {
	if (_shader_model >= 40) {
		code += id_to_name(args[0].base) + ".t.SampleGrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ')';
	}
	else {
		code += "tex3Dgrad(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + " + id_to_name(args[4].base) + " * " + id_to_name(args[0].base) + ".pixelsize, " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dgrad, 0, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask)
		.add(args[2].base)
		.add(args[3].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dgrad, 1, {
	if (!args[4].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(spv::ImageOperandsGradMask | (args[4].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex1Dlod(s, coords)
// ret tex1Dlod(s, coords, offset)
DEFINE_INTRINSIC(tex1Dlod, 0, int, sampler1d_int, float4)
DEFINE_INTRINSIC(tex1Dlod, 0, uint, sampler1d_uint, float4)
DEFINE_INTRINSIC(tex1Dlod, 0, float, sampler1d_float, float4)
DEFINE_INTRINSIC(tex1Dlod, 0, float4, sampler1d_float4, float4)
DEFINE_INTRINSIC(tex1Dlod, 1, int, sampler1d_int, float4, int)
DEFINE_INTRINSIC(tex1Dlod, 1, uint, sampler1d_uint, float4, int)
DEFINE_INTRINSIC(tex1Dlod, 1, float, sampler1d_float, float4, int)
DEFINE_INTRINSIC(tex1Dlod, 1, float4, sampler1d_float4, float4, int)
IMPLEMENT_INTRINSIC_GLSL(tex1Dlod, 0, {
	code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dlod, 1, {
	code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dlod, 0, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w)";
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ".x * temp" + std::to_string(res) + ".x, (int)" + id_to_name(args[1].base) + ".w))";
	}
	else {
		code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dlod, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".x, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
		else
			code += "uint2 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ".x * temp" + std::to_string(res) + ".x, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dlod, 0, {
	const spv::Id x = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(0); // .x;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(x)
		.add(spv::ImageOperandsLodMask)
		.add(lod);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dlod, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id x = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(0); // .x;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(x)
		.add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(lod)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex2Dlod(s, coords)
// ret tex2Dlod(s, coords, offset)
DEFINE_INTRINSIC(tex2Dlod, 0, int, sampler2d_int, float4)
DEFINE_INTRINSIC(tex2Dlod, 0, uint, sampler2d_uint, float4)
DEFINE_INTRINSIC(tex2Dlod, 0, float, sampler2d_float, float4)
DEFINE_INTRINSIC(tex2Dlod, 0, float4, sampler2d_float4, float4)
DEFINE_INTRINSIC(tex2Dlod, 1, int, sampler2d_int, float4, int2)
DEFINE_INTRINSIC(tex2Dlod, 1, uint, sampler2d_uint, float4, int2)
DEFINE_INTRINSIC(tex2Dlod, 1, float, sampler2d_float, float4, int2)
DEFINE_INTRINSIC(tex2Dlod, 1, float4, sampler2d_float4, float4, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 0, {
	code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dlod, 1, {
	code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 0, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w)";
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ".xy * temp" + std::to_string(res) + ".xy, (int)" + id_to_name(args[1].base) + ".w))";
	}
	else {
		code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dlod, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xy, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
		else
			code += "uint3 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ".xy * temp" + std::to_string(res) + ".xy, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 0, {
	const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 }))
		.add(args[1].base)
		.add(args[1].base)
		.add(0)  // .x
		.add(1); // .y;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(xy)
		.add(spv::ImageOperandsLodMask)
		.add(lod);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dlod, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id xy = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 2, 1 }))
		.add(args[1].base)
		.add(args[1].base)
		.add(0)  // .x
		.add(1); // .y;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(xy)
		.add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(lod)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex3Dlod(s, coords)
// ret tex3Dlod(s, coords, offset)
DEFINE_INTRINSIC(tex3Dlod, 0, int, sampler3d_int, float4)
DEFINE_INTRINSIC(tex3Dlod, 0, uint, sampler3d_uint, float4)
DEFINE_INTRINSIC(tex3Dlod, 0, float, sampler3d_float, float4)
DEFINE_INTRINSIC(tex3Dlod, 0, float4, sampler3d_float4, float4)
DEFINE_INTRINSIC(tex3Dlod, 1, int, sampler3d_int, float4, int2)
DEFINE_INTRINSIC(tex3Dlod, 1, uint, sampler3d_uint, float4, int2)
DEFINE_INTRINSIC(tex3Dlod, 1, float, sampler3d_float, float4, int2)
DEFINE_INTRINSIC(tex3Dlod, 1, float4, sampler3d_float4, float4, int2)
IMPLEMENT_INTRINSIC_GLSL(tex3Dlod, 0, {
	code += "textureLod(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dlod, 1, {
	code += "textureLodOffset(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dlod, 0, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w)";
		else // Integer sampling is not supported until SM6.7, so emulate with a texture fetch
			code += "uint4 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z, temp" + std::to_string(res) + ".w); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ".xyz * temp" + std::to_string(res) + ".xyz, (int)" + id_to_name(args[1].base) + ".w))";
	}
	else {
		code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dlod, 1, {
	if (_shader_model >= 40) {
		if (res_type.is_floating_point() || _shader_model >= 67)
			code += id_to_name(args[0].base) + ".t.SampleLevel(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ".xyz, " + id_to_name(args[1].base) + ".w, " + id_to_name(args[2].base) + ')';
		else
			code += "uint4 temp" + std::to_string(res) + "; " + id_to_name(args[0].base) + ".t.GetDimensions((int)" + id_to_name(args[1].base) + ".w, temp" + std::to_string(res) + ".x, temp" + std::to_string(res) + ".y, temp" + std::to_string(res) + ".z, temp" + std::to_string(res) + ".w); " +
				id_to_name(res) + " = " + id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ".xyz * temp" + std::to_string(res) + ".xyz, (int)" + id_to_name(args[1].base) + ".w))" + id_to_name(args[2].base) + ')';
	}
	else {
		code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + " + float4(" + id_to_name(args[2].base) + " * " + id_to_name(args[0].base) + ".pixelsize, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dlod, 0, {
	const spv::Id xyz = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 3, 1 }))
		.add(args[1].base)
		.add(args[1].base)
		.add(0)  // .x
		.add(1)  // .y
		.add(2); // .z;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(xyz)
		.add(spv::ImageOperandsLodMask)
		.add(lod);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dlod, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id xyz = add_instruction(spv::OpVectorShuffle, convert_type({ type::t_float, 3, 1 }))
		.add(args[1].base)
		.add(args[1].base)
		.add(0)  // .x
		.add(1)  // .y
		.add(2); // .z;
	const spv::Id lod = add_instruction(spv::OpCompositeExtract, convert_type({ type::t_float, 1, 1 }))
		.add(args[1].base)
		.add(3); // .w;

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageSampleExplicitLod, convert_type(res_vector_type))
		.add(args[0].base)
		.add(xyz)
		.add(spv::ImageOperandsLodMask | (args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask))
		.add(lod)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex1Dfetch(s, coords)
// ret tex1Dfetch(s, coords, lod)
DEFINE_INTRINSIC(tex1Dfetch, 0, int, sampler1d_int, int)
DEFINE_INTRINSIC(tex1Dfetch, 0, uint, sampler1d_uint, int)
DEFINE_INTRINSIC(tex1Dfetch, 0, float, sampler1d_float, int)
DEFINE_INTRINSIC(tex1Dfetch, 0, float4, sampler1d_float4, int)
DEFINE_INTRINSIC(tex1Dfetch, 1, int, sampler1d_int, int, int)
DEFINE_INTRINSIC(tex1Dfetch, 1, uint, sampler1d_uint, int, int)
DEFINE_INTRINSIC(tex1Dfetch, 1, float, sampler1d_float, int, int)
DEFINE_INTRINSIC(tex1Dfetch, 1, float4, sampler1d_float4, int, int)
DEFINE_INTRINSIC(tex1Dfetch, 2, int, storage1d_int, int)
DEFINE_INTRINSIC(tex1Dfetch, 2, uint, storage1d_uint, int)
DEFINE_INTRINSIC(tex1Dfetch, 2, float, storage1d_float, int)
DEFINE_INTRINSIC(tex1Dfetch, 2, float4, storage1d_float4, int)
IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 0, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 1, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dfetch, 2, {
	code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ")";
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ", 0))";
	else {
		// SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones
		// Also add a half-pixel offset to align texels with pixels
		//   (coords + 0.5) / size
		code += "tex1Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0, 0, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 1, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int2(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))";
	else {
		code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), 0, 0, " +
			id_to_name(args[2].base) + "))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dfetch, 2, {
	code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']';
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 0, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 1, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base)
		.add(spv::ImageOperandsLodMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dfetch, 2, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex2Dfetch(s, coords)
// ret tex2Dfetch(s, coords, lod)
DEFINE_INTRINSIC(tex2Dfetch, 0, int, sampler2d_int, int2)
DEFINE_INTRINSIC(tex2Dfetch, 0, uint, sampler2d_uint, int2)
DEFINE_INTRINSIC(tex2Dfetch, 0, float, sampler2d_float, int2)
DEFINE_INTRINSIC(tex2Dfetch, 0, float4, sampler2d_float4, int2)
DEFINE_INTRINSIC(tex2Dfetch, 1, int, sampler2d_int, int2, int)
DEFINE_INTRINSIC(tex2Dfetch, 1, uint, sampler2d_uint, int2, int)
DEFINE_INTRINSIC(tex2Dfetch, 1, float, sampler2d_float, int2, int)
DEFINE_INTRINSIC(tex2Dfetch, 1, float4, sampler2d_float4, int2, int)
DEFINE_INTRINSIC(tex2Dfetch, 2, int, storage2d_int, int2)
DEFINE_INTRINSIC(tex2Dfetch, 2, uint, storage2d_uint, int2)
DEFINE_INTRINSIC(tex2Dfetch, 2, float, storage2d_float, int2)
DEFINE_INTRINSIC(tex2Dfetch, 2, float4, storage2d_float4, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 0, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 1, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dfetch, 2, {
	code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ", 0))";
	else {
		// SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones
		// Also add a half-pixel offset to align texels with pixels
		//   (coords + 0.5) / size
		code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 1, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int3(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))";
	else {
		code += "tex2Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), 0, " +
			id_to_name(args[2].base) + "))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dfetch, 2, {
	if (_shader_model >= 50)
		code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']';
	else
		code += "{}";
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 0, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 1, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base)
		.add(spv::ImageOperandsLodMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dfetch, 2, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex3Dfetch(s, coords)
// ret tex3Dfetch(s, coords, lod)
DEFINE_INTRINSIC(tex3Dfetch, 0, int, sampler3d_int, int3)
DEFINE_INTRINSIC(tex3Dfetch, 0, uint, sampler3d_uint, int3)
DEFINE_INTRINSIC(tex3Dfetch, 0, float, sampler3d_float, int3)
DEFINE_INTRINSIC(tex3Dfetch, 0, float4, sampler3d_float4, int3)
DEFINE_INTRINSIC(tex3Dfetch, 1, int, sampler3d_int, int3, int)
DEFINE_INTRINSIC(tex3Dfetch, 1, uint, sampler3d_uint, int3, int)
DEFINE_INTRINSIC(tex3Dfetch, 1, float, sampler3d_float, int3, int)
DEFINE_INTRINSIC(tex3Dfetch, 1, float4, sampler3d_float4, int3, int)
DEFINE_INTRINSIC(tex3Dfetch, 2, int, storage3d_int, int3)
DEFINE_INTRINSIC(tex3Dfetch, 2, uint, storage3d_uint, int3)
DEFINE_INTRINSIC(tex3Dfetch, 2, float, storage3d_float, int3)
DEFINE_INTRINSIC(tex3Dfetch, 2, float4, storage3d_float4, int3)
IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 0, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", 0)";
	if (res_type.rows == 1)
		code += ".x"; // Collapse last argument from a 4-component vector
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 1, {
	code += "texelFetch(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dfetch, 2, {
	code += "imageLoad(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ")";
	if (res_type.rows == 1)
		code += ".x";
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ", 0))";
	else {
		// SM3 does not have a fetch intrinsic, so emulate it by transforming coordinates into texture space ones
		// Also add a half-pixel offset to align texels with pixels
		//   (coords + 0.5) / size
		code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize, 0))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 1, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.Load(int4(" + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + "))";
	else {
		code += "tex3Dlod(" + id_to_name(args[0].base) + ".s, float4((" +
			id_to_name(args[1].base) + " + 0.5) * " + id_to_name(args[0].base) + ".pixelsize * exp2(" + id_to_name(args[2].base) + "), " +
			id_to_name(args[2].base) + "))";
		if (res_type.rows == 1)
			code += ".x";
	}
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dfetch, 2, {
	code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']';
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 0, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base);
	if (res_type.rows == 1)
		// Collapse last argument from a 4-component vector
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 1, {
	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageFetch, convert_type(res_vector_type))
		.add(image)
		.add(args[1].base)
		.add(spv::ImageOperandsLodMask)
		.add(args[2].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dfetch, 2, {
	type res_vector_type = res_type;
	res_vector_type.rows = 4;

	const spv::Id res = add_instruction(spv::OpImageRead, convert_type(res_vector_type))
		.add(args[0].base)
		.add(args[1].base);
	if (res_type.rows == 1)
		return
		add_instruction(spv::OpCompositeExtract, convert_type(res_type))
			.add(res)
			.add(0u);
	else
		return res;
	})

// ret tex2DgatherR(s, coords)
// ret tex2DgatherR(s, coords, offset)
// ret tex2DgatherR(s, coords, offset0, offset1, offset2, offset3)
DEFINE_INTRINSIC(tex2DgatherR, 0, float4, sampler2d_float4, float2)
DEFINE_INTRINSIC(tex2DgatherR, 1, float4, sampler2d_float4, float2, int2)
DEFINE_INTRINSIC(tex2DgatherR, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 0, {
	code += "textureGather(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", 0)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 1, {
	code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base) + ", 0)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherR, 2, {
	code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " +
		"ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 0)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 0, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ')';
	else if (_shader_model >= 40) // Emulate texture gather intrinsic by sampling each location separately (SM41 has 'Gather', but that only works on single component texture formats)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0)).r)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0)).r)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 1, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0)).r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0)).r)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0)).r)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherR, 2, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherRed(" + s + ".s, " + id_to_name(args[1].base) + ", " +
			id_to_name(args[2].base) + " - int2(0, 1), " +
			id_to_name(args[3].base) + " - int2(1, 1), " +
			id_to_name(args[4].base) + " - int2(1, 0), " +
			id_to_name(args[5].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ").r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ").r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ").r, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ").r)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0)).r, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0)).r)";
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 0, {
	const spv::Id comp = emit_constant(0u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsMaskNone);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(0u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherR, 2, {
	add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(0u);
	const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants)
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base)
		.add(args[5].base);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsConstOffsetsMask)
		.add(offsets);
	})
// ret tex2DgatherG(s, coords)
// ret tex2DgatherG(s, coords, offset)
// ret tex2DgatherG(s, coords, offset0, offset1, offset2, offset3)
DEFINE_INTRINSIC(tex2DgatherG, 0, float4, sampler2d_float4, float2)
DEFINE_INTRINSIC(tex2DgatherG, 1, float4, sampler2d_float4, float2, int2)
DEFINE_INTRINSIC(tex2DgatherG, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 0, {
	code += "textureGather(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", 1)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 1, {
	code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base) + ", 1)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherG, 2, {
	code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " +
		"ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 1)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 0, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherGreen(" + id_to_name(args[0].base) + ".s, " + id_to_name(args[1].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0)).g)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0)).g)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 1, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherGreen(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0)).g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0)).g)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0)).g)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherG, 2, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherGreen(" + s + ".s, " + id_to_name(args[1].base) + ", " +
			id_to_name(args[2].base) + " - int2(0, 1), " +
			id_to_name(args[3].base) + " - int2(1, 1), " +
			id_to_name(args[4].base) + " - int2(1, 0), " +
			id_to_name(args[5].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ").g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ").g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ").g, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ").g)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0)).g, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0)).g)";
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 0, {
	const spv::Id comp = emit_constant(1u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsMaskNone);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(1u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherG, 2, {
	add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(1u);
	const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants)
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base)
		.add(args[5].base);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsConstOffsetsMask)
		.add(offsets);
	})
// ret tex2DgatherB(s, coords)
// ret tex2DgatherB(s, coords, offset)
// ret tex2DgatherB(s, coords, offset0, offset1, offset2, offset3)
DEFINE_INTRINSIC(tex2DgatherB, 0, float4, sampler2d_float4, float2)
DEFINE_INTRINSIC(tex2DgatherB, 1, float4, sampler2d_float4, float2, int2)
DEFINE_INTRINSIC(tex2DgatherB, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 0, {
	code += "textureGather(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", 2)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 1, {
	code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base) + ", 2)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherB, 2, {
	code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " +
		"ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 2)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 0, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0)).b)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0)).b)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 1, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0)).b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0)).b)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0)).b)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherB, 2, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherBlue(" + s + ".s, " + id_to_name(args[1].base) + ", " +
			id_to_name(args[2].base) + " - int2(0, 1), " +
			id_to_name(args[3].base) + " - int2(1, 1), " +
			id_to_name(args[4].base) + " - int2(1, 0), " +
			id_to_name(args[5].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ").b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ").b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ").b, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ").b)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0)).b, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0)).b)";
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 0, {
	const spv::Id comp = emit_constant(2u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsMaskNone);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(2u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherB, 2, {
	add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(2u);
	const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants)
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base)
		.add(args[5].base);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsConstOffsetsMask)
		.add(offsets);
	})
// ret tex2DgatherA(s, coords)
// ret tex2DgatherA(s, coords, offset)
// ret tex2DgatherA(s, coords, offset0, offset1, offset2, offset3)
DEFINE_INTRINSIC(tex2DgatherA, 0, float4, sampler2d_float4, float2)
DEFINE_INTRINSIC(tex2DgatherA, 1, float4, sampler2d_float4, float2, int2)
DEFINE_INTRINSIC(tex2DgatherA, 2, float4, sampler2d_float4, float2, int2, int2, int2, int2)
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 0, {
	code += "textureGather(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", 3)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 1, {
	code += "textureGatherOffset(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base) + ", 3)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2DgatherA, 2, {
	code += "textureGatherOffsets(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " +
		"ivec2[]( " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(args[4].base) + ", " + id_to_name(args[5].base) + "), 3)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 0, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 1)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 1)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(1, 0)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, int2(0, 0)).a)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 1) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 1) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(1, 0) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + float2(0, 0) * " + s + ".pixelsize, 0, 0)).a)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 1, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 1)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 1)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(1, 0)).a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + " + int2(0, 0)).a)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 1)) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 1)) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(1, 0)) * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + " + float2(0, 0)) * " + s + ".pixelsize, 0, 0)).a)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2DgatherA, 2, {
	const std::string s = id_to_name(args[0].base);
	if (_shader_model >= 50)
		code += s + ".t.GatherAlpha(" + s + ".s, " + id_to_name(args[1].base) + ", " +
			id_to_name(args[2].base) + " - int2(0, 1), " +
			id_to_name(args[3].base) + " - int2(1, 1), " +
			id_to_name(args[4].base) + " - int2(1, 0), " +
			id_to_name(args[5].base) + ')';
	else if (_shader_model >= 40)
		code += "float4(" +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[2].base) + ").a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[3].base) + ").a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[4].base) + ").a, " +
			s + ".t.SampleLevel(" + s + ".s, " + id_to_name(args[1].base) + ", 0, " + id_to_name(args[5].base) + ").a)";
	else
		code += "float4("
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[2].base) + ") * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[3].base) + ") * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[4].base) + ") * " + s + ".pixelsize, 0, 0)).a, "
			"tex2Dlod(" + s + ".s, float4(" + id_to_name(args[1].base) + " + (" + id_to_name(args[5].base) + ") * " + s + ".pixelsize, 0, 0)).a)";
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 0, {
	const spv::Id comp = emit_constant(3u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsMaskNone);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 1, {
	if (!args[2].is_constant)
		add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(3u);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(args[2].is_constant ? spv::ImageOperandsConstOffsetMask : spv::ImageOperandsOffsetMask)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2DgatherA, 2, {
	add_capability(spv::CapabilityImageGatherExtended);

	const spv::Id comp = emit_constant(3u);
	const spv::Id offsets = add_instruction(spv::OpConstantComposite, convert_type({ reshadefx::type::t_int, 2, 1, 0, 4 }), _types_and_constants)
		.add(args[2].base)
		.add(args[3].base)
		.add(args[4].base)
		.add(args[5].base);

	return
	add_instruction(spv::OpImageGather, convert_type(res_type))
		.add(args[0].base)
		.add(args[1].base)
		.add(comp)
		.add(spv::ImageOperandsConstOffsetsMask)
		.add(offsets);
	})

// tex1Dstore(s, coords, value)
DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_int, int, int)
DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_uint, int, uint)
DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_float, int, float)
DEFINE_INTRINSIC(tex1Dstore, 0, void, storage1d_float4, int, float4)
IMPLEMENT_INTRINSIC_GLSL(tex1Dstore, 0, {
	code += "imageStore(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base);
	if (args[2].type.rows == 1)
		code += ".xxxx"; // Expand last argument to a 4-component vector
	code += ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dstore, 0, {
	if (_shader_model >= 50) {
		code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base);
	}
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dstore, 0, {
	spv::Id data = args[2].base;
	if (args[2].type.rows == 1)
	{
		// Expand last argument to a 4-component vector
		auto comp_type = args[2].type;
		comp_type.rows = 4;

		data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type))
			.add(data)
			.add(data)
			.add(data)
			.add(data);
	}

	add_instruction_without_result(spv::OpImageWrite)
		.add(args[0].base)
		.add(args[1].base)
		.add(data);
	return 0;
	})

// tex2Dstore(s, coords, value)
DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_int, int2, int)
DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_uint, int2, uint)
DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_float, int2, float)
DEFINE_INTRINSIC(tex2Dstore, 0, void, storage2d_float4, int2, float4)
IMPLEMENT_INTRINSIC_GLSL(tex2Dstore, 0, {
	code += "imageStore(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base);
	if (args[2].type.rows == 1)
		code += ".xxxx"; // Expand last argument to a 4-component vector
	code += ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dstore, 0, {
	if (_shader_model >= 50)
		code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dstore, 0, {
	spv::Id data = args[2].base;
	if (args[2].type.rows == 1)
	{
		// Expand last argument to a 4-component vector
		auto comp_type = args[2].type;
		comp_type.rows = 4;

		data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type))
			.add(data)
			.add(data)
			.add(data)
			.add(data);
	}

	add_instruction_without_result(spv::OpImageWrite)
		.add(args[0].base)
		.add(args[1].base)
		.add(data);
	return 0;
	})

// tex3Dstore(s, coords, value)
DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_int, int3, int)
DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_uint, int3, uint)
DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_float, int3, float)
DEFINE_INTRINSIC(tex3Dstore, 0, void, storage3d_float4, int3, float4)
IMPLEMENT_INTRINSIC_GLSL(tex3Dstore, 0, {
	code += "imageStore(" + id_to_name(args[0].base) + ", " +
		id_to_name(args[1].base) + ", " +
		id_to_name(args[2].base);
	if (args[2].type.rows == 1)
		code += ".xxxx"; // Expand last argument to a 4-component vector
	code += ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dstore, 0, {
	code += id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + "] = " + id_to_name(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dstore, 0, {
	spv::Id data = args[2].base;
	if (args[2].type.rows == 1)
	{
		// Expand last argument to a 4-component vector
		auto comp_type = args[2].type;
		comp_type.rows = 4;

		data = add_instruction(spv::OpCompositeConstruct, convert_type(comp_type))
			.add(data)
			.add(data)
			.add(data)
			.add(data);
	}

	add_instruction_without_result(spv::OpImageWrite)
		.add(args[0].base)
		.add(args[1].base)
		.add(data);
	return 0;
	})

// ret tex1Dsize(s)
// ret tex1Dsize(s, lod)
DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_int)
DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_uint)
DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_float)
DEFINE_INTRINSIC(tex1Dsize, 0, int, sampler1d_float4)
DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_int, int)
DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_uint, int)
DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_float, int)
DEFINE_INTRINSIC(tex1Dsize, 1, int, sampler1d_float4, int)
DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_int)
DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_uint)
DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_float)
DEFINE_INTRINSIC(tex1Dsize, 2, int, storage1d_float4)
IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 0, {
	code += "textureSize(" + id_to_name(args[0].base) + ", 0)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 1, {
	code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(tex1Dsize, 2, {
	code += "imageSize(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ')';
	else
		code += "int(1.0 / " + id_to_name(args[0].base) + ".pixelsize)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 1, {
	if (_shader_model >= 40)
		code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable
			id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ", temp" + std::to_string(res) + ')';
	else
		code += "int(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex1Dsize, 2, {
	if (_shader_model >= 50)
		code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ')';
	else
		code += "0"; // Only supported on SM5+
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 0, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);
	const spv::Id level = emit_constant(0u);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(level);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 1, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex1Dsize, 2, {
	add_capability(spv::CapabilityImageQuery);

	return
	add_instruction(spv::OpImageQuerySize, convert_type(res_type))
		.add(args[0].base);
	})

// ret tex2Dsize(s)
// ret tex2Dsize(s, lod)
DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_int)
DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_uint)
DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_float)
DEFINE_INTRINSIC(tex2Dsize, 0, int2, sampler2d_float4)
DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_int, int)
DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_uint, int)
DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_float, int)
DEFINE_INTRINSIC(tex2Dsize, 1, int2, sampler2d_float4, int)
DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_int)
DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_uint)
DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_float)
DEFINE_INTRINSIC(tex2Dsize, 2, int2, storage2d_float4)
IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 0, {
	code += "textureSize(" + id_to_name(args[0].base) + ", 0)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 1, {
	code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(tex2Dsize, 2, {
	code += "imageSize(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)";
	else
		code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 1, {
	if (_shader_model >= 40)
		code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable
			id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, temp" + std::to_string(res) + ')';
	else
		code += "int2(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex2Dsize, 2, {
	if (_shader_model >= 50)
		code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y)";
	else
		code += "int2(0, 0)"; // Only supported on SM5+
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 0, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);
	const spv::Id level = emit_constant(0u);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(level);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 1, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex2Dsize, 2, {
	add_capability(spv::CapabilityImageQuery);

	return
	add_instruction(spv::OpImageQuerySize, convert_type(res_type))
		.add(args[0].base);
	})

// ret tex3Dsize(s)
// ret tex3Dsize(s, lod)
DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_int)
DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_uint)
DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_float)
DEFINE_INTRINSIC(tex3Dsize, 0, int3, sampler3d_float4)
DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_int, int)
DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_uint, int)
DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_float, int)
DEFINE_INTRINSIC(tex3Dsize, 1, int3, sampler3d_float4, int)
DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_int)
DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_uint)
DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_float)
DEFINE_INTRINSIC(tex3Dsize, 2, int3, storage3d_float4)
IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 0, {
	code += "textureSize(" + id_to_name(args[0].base) + ", 0)";
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 1, {
	code += "textureSize(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(tex3Dsize, 2, {
	code += "imageSize(" + id_to_name(args[0].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 0, {
	if (_shader_model >= 40)
		code += id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z)";
	else
		code += "int3(1.0 / " + id_to_name(args[0].base) + ".pixelsize)";
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 1, {
	if (_shader_model >= 40)
		code += "uint temp" + std::to_string(res) + "; " + // Don't need the number of levels out value, so route that to a dummy variable
			id_to_name(args[0].base) + ".t.GetDimensions(" + id_to_name(args[1].base) + ", " + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z, temp" + std::to_string(res) + ')';
	else
		code += "int3(1.0 / " + id_to_name(args[0].base) + ".pixelsize) / exp2(" + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(tex3Dsize, 2, {
	if (_shader_model >= 50)
		code += id_to_name(args[0].base) + ".GetDimensions(" + id_to_name(res) + ".x, " + id_to_name(res) + ".y, " + id_to_name(res) + ".z)";
	else
		code += "int3(0, 0, 0)"; // Only supported on SM5+
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 0, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);
	const spv::Id level = emit_constant(0u);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(level);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 1, {
	add_capability(spv::CapabilityImageQuery);

	const spv::Id image = add_instruction(spv::OpImage, convert_image_type(args[0].type))
		.add(args[0].base);

	return
	add_instruction(spv::OpImageQuerySizeLod, convert_type(res_type))
		.add(image)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(tex3Dsize, 2, {
	add_capability(spv::CapabilityImageQuery);

	return
	add_instruction(spv::OpImageQuerySize, convert_type(res_type))
		.add(args[0].base);
	})

// barrier()
DEFINE_INTRINSIC(barrier, 0, void)
IMPLEMENT_INTRINSIC_GLSL(barrier, 0, {
	code += "barrier()";
	})
IMPLEMENT_INTRINSIC_HLSL(barrier, 0, {
	if (_shader_model >= 50)
		code += "GroupMemoryBarrierWithGroupSync()";
	})
IMPLEMENT_INTRINSIC_SPIRV(barrier, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeWorkgroup);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask);

	add_instruction_without_result(spv::OpControlBarrier)
		.add(mem_scope) // Execution scope
		.add(mem_scope)
		.add(mem_semantics);
	return 0;
	})

// memoryBarrier()
DEFINE_INTRINSIC(memoryBarrier, 0, void)
IMPLEMENT_INTRINSIC_GLSL(memoryBarrier, 0, {
	code += "memoryBarrier()";
	})
IMPLEMENT_INTRINSIC_HLSL(memoryBarrier, 0, {
	if (_shader_model >= 50)
		code += "AllMemoryBarrier()";
	})
IMPLEMENT_INTRINSIC_SPIRV(memoryBarrier, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsImageMemoryMask | spv::MemorySemanticsUniformMemoryMask | spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask);

	add_instruction_without_result(spv::OpMemoryBarrier)
		.add(mem_scope)
		.add(mem_semantics);
	return 0;
	})
// groupMemoryBarrier()
DEFINE_INTRINSIC(groupMemoryBarrier, 0, void)
IMPLEMENT_INTRINSIC_GLSL(groupMemoryBarrier, 0, {
	code += "groupMemoryBarrier()";
	})
IMPLEMENT_INTRINSIC_HLSL(groupMemoryBarrier, 0, {
	if (_shader_model >= 50)
		code += "GroupMemoryBarrier()";
	})
IMPLEMENT_INTRINSIC_SPIRV(groupMemoryBarrier, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeWorkgroup);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsAcquireReleaseMask);

	add_instruction_without_result(spv::OpMemoryBarrier)
		.add(mem_scope)
		.add(mem_semantics);
	return 0;
	})

// ret atomicAdd(inout mem, data)
DEFINE_INTRINSIC(atomicAdd, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicAdd, 0, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicAdd, 0, {
	code += "atomicAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicAdd, 0, {
	if (_shader_model >= 50)
		code += "InterlockedAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " += " + id_to_name(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicAdd, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicIAdd, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicAdd(s, coords, data)
DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicAdd, 1, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicAdd, 1, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicAdd, 1, {
	code += "imageAtomicAdd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicAdd, 1, {
	if (_shader_model >= 50)
		code += "InterlockedAdd(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicAdd, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicIAdd, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicAnd(inout mem, data)
DEFINE_INTRINSIC(atomicAnd, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicAnd, 0, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicAnd, 0, {
	code += "atomicAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicAnd, 0, {
	if (_shader_model >= 50)
		code += "InterlockedAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " &= " + id_to_name(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicAnd, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicAnd, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicAnd(s, coords, data)
DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicAnd, 1, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicAnd, 1, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicAnd, 1, {
	code += "imageAtomicAnd(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicAnd, 1, {
	if (_shader_model >= 50)
		code += "InterlockedAnd(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicAnd, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicAnd, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicOr(inout mem, data)
DEFINE_INTRINSIC(atomicOr, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicOr, 0, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicOr, 0, {
	code += "atomicOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicOr, 0, {
	if (_shader_model >= 50)
		code += "InterlockedOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " |= " + id_to_name(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicOr, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicOr, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicOr(s, coords, data)
DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicOr, 1, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicOr, 1, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicOr, 1, {
	code += "imageAtomicOr(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicOr, 1, {
	if (_shader_model >= 50)
		code += "InterlockedOr(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicOr, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicOr, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicXor(inout mem, data)
DEFINE_INTRINSIC(atomicXor, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicXor, 0, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicXor, 0, {
	code += "atomicXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicXor, 0, {
	if (_shader_model >= 50)
		code += "InterlockedXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " ^= " + id_to_name(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicXor, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicXor, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicXor(s, coords, data)
DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicXor, 1, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicXor, 1, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicXor, 1, {
	code += "imageAtomicXor(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicXor, 1, {
	if (_shader_model >= 50)
		code += "InterlockedXor(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicXor, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicXor, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicMin(inout mem, data)
DEFINE_INTRINSIC(atomicMin, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicMin, 1, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicMin, 0, {
	code += "atomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(atomicMin, 1, {
	code += "atomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMin, 0, {
	if (_shader_model >= 50)
		code += "InterlockedMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMin, 1, {
	if (_shader_model >= 50)
		code += "InterlockedMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = min(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicSMin, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 1, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicUMin, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicMin(s, coords, data)
DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicMin, 2, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicMin, 3, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicMin, 2, {
	code += "imageAtomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(atomicMin, 3, {
	code += "imageAtomicMin(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMin, 2, {
	if (_shader_model >= 50)
		code += "InterlockedMin(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMin, 3, {
	if (_shader_model >= 50)
		code += "InterlockedMin(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 2, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicSMin, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMin, 3, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicUMin, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicMax(inout mem, data)
DEFINE_INTRINSIC(atomicMax, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicMax, 1, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicMax, 0, {
	code += "atomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(atomicMax, 1, {
	code += "atomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMax, 0, {
	if (_shader_model >= 50)
		code += "InterlockedMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMax, 1, {
	if (_shader_model >= 50)
		code += "InterlockedMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = max(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicSMax, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 1, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicUMax, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicMax(s, coords, data)
DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicMax, 2, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicMax, 3, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicMax, 2, {
	code += "imageAtomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_GLSL(atomicMax, 3, {
	code += "imageAtomicMax(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMax, 2, {
	if (_shader_model >= 50)
		code += "InterlockedMax(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicMax, 3, {
	if (_shader_model >= 50)
		code += "InterlockedMax(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 2, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicSMax, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicMax, 3, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicUMax, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicExchange(inout mem, data)
DEFINE_INTRINSIC(atomicExchange, 0, int, inout_int, int)
DEFINE_INTRINSIC(atomicExchange, 0, uint, inout_uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicExchange, 0, {
	code += "atomicExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicExchange, 0, {
	if (_shader_model >= 50)
		code += "InterlockedExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; " + id_to_name(args[0].base) + " = " + id_to_name(args[1].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicExchange, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicExchange, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[1].base);
	})
// ret atomicExchange(s, coords, data)
DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage1d_int, int, int)
DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage2d_int, int2, int)
DEFINE_INTRINSIC(atomicExchange, 1, int, inout_storage3d_int, int3, int)
DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage1d_uint, int, uint)
DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage2d_uint, int2, uint)
DEFINE_INTRINSIC(atomicExchange, 1, uint, inout_storage3d_uint, int3, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicExchange, 1, {
	code += "imageAtomicExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicExchange, 1, {
	if (_shader_model >= 50)
		code += "InterlockedExchange(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicExchange, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicExchange, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(args[2].base);
	})

// ret atomicCompareExchange(inout mem, compare, data)
DEFINE_INTRINSIC(atomicCompareExchange, 0, int, inout_int, int, int)
DEFINE_INTRINSIC(atomicCompareExchange, 0, uint, inout_uint, uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicCompareExchange, 0, {
	code += "atomicCompSwap(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicCompareExchange, 0, {
	if (_shader_model >= 50)
		code += "InterlockedCompareExchange(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(res) + ')';
	else
		code += id_to_name(res) + " = " + id_to_name(args[0].base) + "; if (" + id_to_name(args[0].base) + " == " + id_to_name(args[1].base) + ") " + id_to_name(args[0].base) + " = " + id_to_name(args[2].base);
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicCompareExchange, 0, {
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicCompareExchange, convert_type(res_type))
		.add(args[0].base)
		.add(mem_scope)
		.add(mem_semantics)
		.add(mem_semantics)
		.add(args[2].base)
		.add(args[1].base);
	})
// ret atomicCompareExchange(s, coords, compare, data)
DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage1d_int, int, int, int)
DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage2d_int, int2, int, int)
DEFINE_INTRINSIC(atomicCompareExchange, 1, int, inout_storage3d_int, int3, int, int)
DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage1d_uint, int, uint, uint)
DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage2d_uint, int2, uint, uint)
DEFINE_INTRINSIC(atomicCompareExchange, 1, uint, inout_storage3d_uint, int3, uint, uint)
IMPLEMENT_INTRINSIC_GLSL(atomicCompareExchange, 1, {
	code += "imageAtomicCompSwap(" + id_to_name(args[0].base) + ", " + id_to_name(args[1].base) + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ')';
	})
IMPLEMENT_INTRINSIC_HLSL(atomicCompareExchange, 1, {
	if (_shader_model >= 50)
		code += "InterlockedCompareExchange(" + id_to_name(args[0].base) + '[' + id_to_name(args[1].base) + ']' + ", " + id_to_name(args[2].base) + ", " + id_to_name(args[3].base) + ", " + id_to_name(res) + ')';
	})
IMPLEMENT_INTRINSIC_SPIRV(atomicCompareExchange, 1, {
	const spv::Id ms_sample = emit_constant(0u);

	const spv::Id texel = add_instruction(spv::OpImageTexelPointer, convert_type(res_type, true, spv::StorageClassImage))
		.add(args[0].base)
		.add(args[1].base)
		.add(ms_sample);
	const spv::Id mem_scope = emit_constant(spv::ScopeDevice);
	const spv::Id mem_semantics = emit_constant(spv::MemorySemanticsMaskNone);

	return
	add_instruction(spv::OpAtomicCompareExchange, convert_type(res_type))
		.add(texel)
		.add(mem_scope)
		.add(mem_semantics)
		.add(mem_semantics)
		.add(args[3].base)
		.add(args[2].base);
	})

#undef DEFINE_INTRINSIC
#undef IMPLEMENT_INTRINSIC_GLSL
#undef IMPLEMENT_INTRINSIC_HLSL
#undef IMPLEMENT_INTRINSIC_SPIRV
