godot/servers/visual/shader_language.cpp
Juan Linietsky 33b5c57199 Variant: Added 64-bit packed arrays, renamed Variant::REAL to FLOAT.
- Renames PackedIntArray to PackedInt32Array.
- Renames PackedFloatArray to PackedFloat32Array.
- Adds PackedInt64Array and PackedFloat64Array.
- Renames Variant::REAL to Variant::FLOAT for consistency.

Packed arrays are for storing large amount of data and creating stuff like
meshes, buffers. textures, etc. Forcing them to be 64 is a huge waste of
memory. That said, many users requested the ability to have 64 bits packed
arrays for their games, so this is just an optional added type.

For Variant, the float datatype is always 64 bits, and exposed as `float`.

We still have `real_t` which is the datatype that can change from 32 to 64
bits depending on a compile flag (not entirely working right now, but that's
the idea). It affects math related datatypes and code only.

Neither Variant nor PackedArray make use of real_t, which is only intended
for math precision, so the term is removed from there to keep only float.
2020-02-25 12:55:53 +01:00

6905 lines
213 KiB
C++

/*************************************************************************/
/* shader_language.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "shader_language.h"
#include "core/os/os.h"
#include "core/print_string.h"
#include "servers/visual_server.h"
static bool _is_text_char(CharType c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
static bool _is_number(CharType c) {
return (c >= '0' && c <= '9');
}
static bool _is_hex(CharType c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
String ShaderLanguage::get_operator_text(Operator p_op) {
static const char *op_names[OP_MAX] = { "==",
"!=",
"<",
"<=",
">",
">=",
"&&",
"||",
"!",
"-",
"+",
"-",
"*",
"/",
"%",
"<<",
">>",
"=",
"+=",
"-=",
"*=",
"/=",
"%=",
"<<=",
">>=",
"&=",
"|=",
"^=",
"&",
"|",
"^",
"~",
"++",
"--",
"?",
":",
"++",
"--",
"()",
"construct",
"index" };
return op_names[p_op];
}
const char *ShaderLanguage::token_names[TK_MAX] = {
"EMPTY",
"IDENTIFIER",
"TRUE",
"FALSE",
"REAL_CONSTANT",
"INT_CONSTANT",
"TYPE_VOID",
"TYPE_BOOL",
"TYPE_BVEC2",
"TYPE_BVEC3",
"TYPE_BVEC4",
"TYPE_INT",
"TYPE_IVEC2",
"TYPE_IVEC3",
"TYPE_IVEC4",
"TYPE_UINT",
"TYPE_UVEC2",
"TYPE_UVEC3",
"TYPE_UVEC4",
"TYPE_FLOAT",
"TYPE_VEC2",
"TYPE_VEC3",
"TYPE_VEC4",
"TYPE_MAT2",
"TYPE_MAT3",
"TYPE_MAT4",
"TYPE_SAMPLER2D",
"TYPE_ISAMPLER2D",
"TYPE_USAMPLER2D",
"TYPE_SAMPLER2DARRAY",
"TYPE_ISAMPLER2DARRAY",
"TYPE_USAMPLER2DARRAY",
"TYPE_SAMPLER3D",
"TYPE_ISAMPLER3D",
"TYPE_USAMPLER3D",
"TYPE_SAMPLERCUBE",
"INTERPOLATION_FLAT",
"INTERPOLATION_SMOOTH",
"CONST",
"PRECISION_LOW",
"PRECISION_MID",
"PRECISION_HIGH",
"OP_EQUAL",
"OP_NOT_EQUAL",
"OP_LESS",
"OP_LESS_EQUAL",
"OP_GREATER",
"OP_GREATER_EQUAL",
"OP_AND",
"OP_OR",
"OP_NOT",
"OP_ADD",
"OP_SUB",
"OP_MUL",
"OP_DIV",
"OP_MOD",
"OP_SHIFT_LEFT",
"OP_SHIFT_RIGHT",
"OP_ASSIGN",
"OP_ASSIGN_ADD",
"OP_ASSIGN_SUB",
"OP_ASSIGN_MUL",
"OP_ASSIGN_DIV",
"OP_ASSIGN_MOD",
"OP_ASSIGN_SHIFT_LEFT",
"OP_ASSIGN_SHIFT_RIGHT",
"OP_ASSIGN_BIT_AND",
"OP_ASSIGN_BIT_OR",
"OP_ASSIGN_BIT_XOR",
"OP_BIT_AND",
"OP_BIT_OR",
"OP_BIT_XOR",
"OP_BIT_INVERT",
"OP_INCREMENT",
"OP_DECREMENT",
"CF_IF",
"CF_ELSE",
"CF_FOR",
"CF_WHILE",
"CF_DO",
"CF_SWITCH",
"CF_CASE",
"CF_BREAK",
"CF_CONTINUE",
"CF_RETURN",
"CF_DISCARD",
"BRACKET_OPEN",
"BRACKET_CLOSE",
"CURLY_BRACKET_OPEN",
"CURLY_BRACKET_CLOSE",
"PARENTHESIS_OPEN",
"PARENTHESIS_CLOSE",
"QUESTION",
"COMMA",
"COLON",
"SEMICOLON",
"PERIOD",
"UNIFORM",
"VARYING",
"IN",
"OUT",
"INOUT",
"RENDER_MODE",
"HINT_WHITE_TEXTURE",
"HINT_BLACK_TEXTURE",
"HINT_NORMAL_TEXTURE",
"HINT_ANISO_TEXTURE",
"HINT_ALBEDO_TEXTURE",
"HINT_BLACK_ALBEDO_TEXTURE",
"HINT_COLOR",
"HINT_RANGE",
"FILTER_NEAREST",
"FILTER_LINEAR",
"FILTER_NEAREST_MIPMAP",
"FILTER_LINEAR_MIPMAP",
"FILTER_NEAREST_MIPMAP_ANISO",
"FILTER_LINEAR_MIPMAP_ANISO",
"REPEAT_ENABLE",
"REPEAT_DISABLE",
"SHADER_TYPE",
"CURSOR",
"ERROR",
"EOF",
};
String ShaderLanguage::get_token_text(Token p_token) {
String name = token_names[p_token.type];
if (p_token.type == TK_INT_CONSTANT || p_token.type == TK_REAL_CONSTANT) {
name += "(" + rtos(p_token.constant) + ")";
} else if (p_token.type == TK_IDENTIFIER) {
name += "(" + String(p_token.text) + ")";
} else if (p_token.type == TK_ERROR) {
name += "(" + String(p_token.text) + ")";
}
return name;
}
ShaderLanguage::Token ShaderLanguage::_make_token(TokenType p_type, const StringName &p_text) {
Token tk;
tk.type = p_type;
tk.text = p_text;
tk.line = tk_line;
if (tk.type == TK_ERROR) {
_set_error(p_text);
}
return tk;
}
const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_TRUE, "true" },
{ TK_FALSE, "false" },
{ TK_TYPE_VOID, "void" },
{ TK_TYPE_BOOL, "bool" },
{ TK_TYPE_BVEC2, "bvec2" },
{ TK_TYPE_BVEC3, "bvec3" },
{ TK_TYPE_BVEC4, "bvec4" },
{ TK_TYPE_INT, "int" },
{ TK_TYPE_IVEC2, "ivec2" },
{ TK_TYPE_IVEC3, "ivec3" },
{ TK_TYPE_IVEC4, "ivec4" },
{ TK_TYPE_UINT, "uint" },
{ TK_TYPE_UVEC2, "uvec2" },
{ TK_TYPE_UVEC3, "uvec3" },
{ TK_TYPE_UVEC4, "uvec4" },
{ TK_TYPE_FLOAT, "float" },
{ TK_TYPE_VEC2, "vec2" },
{ TK_TYPE_VEC3, "vec3" },
{ TK_TYPE_VEC4, "vec4" },
{ TK_TYPE_MAT2, "mat2" },
{ TK_TYPE_MAT3, "mat3" },
{ TK_TYPE_MAT4, "mat4" },
{ TK_TYPE_SAMPLER2D, "sampler2D" },
{ TK_TYPE_ISAMPLER2D, "isampler2D" },
{ TK_TYPE_USAMPLER2D, "usampler2D" },
{ TK_TYPE_SAMPLER2DARRAY, "sampler2DArray" },
{ TK_TYPE_ISAMPLER2DARRAY, "isampler2DArray" },
{ TK_TYPE_USAMPLER2DARRAY, "usampler2DArray" },
{ TK_TYPE_SAMPLER3D, "sampler3D" },
{ TK_TYPE_ISAMPLER3D, "isampler3D" },
{ TK_TYPE_USAMPLER3D, "usampler3D" },
{ TK_TYPE_SAMPLERCUBE, "samplerCube" },
{ TK_INTERPOLATION_FLAT, "flat" },
{ TK_INTERPOLATION_SMOOTH, "smooth" },
{ TK_CONST, "const" },
{ TK_STRUCT, "struct" },
{ TK_PRECISION_LOW, "lowp" },
{ TK_PRECISION_MID, "mediump" },
{ TK_PRECISION_HIGH, "highp" },
{ TK_CF_IF, "if" },
{ TK_CF_ELSE, "else" },
{ TK_CF_FOR, "for" },
{ TK_CF_WHILE, "while" },
{ TK_CF_DO, "do" },
{ TK_CF_SWITCH, "switch" },
{ TK_CF_CASE, "case" },
{ TK_CF_DEFAULT, "default" },
{ TK_CF_BREAK, "break" },
{ TK_CF_CONTINUE, "continue" },
{ TK_CF_RETURN, "return" },
{ TK_CF_DISCARD, "discard" },
{ TK_UNIFORM, "uniform" },
{ TK_VARYING, "varying" },
{ TK_ARG_IN, "in" },
{ TK_ARG_OUT, "out" },
{ TK_ARG_INOUT, "inout" },
{ TK_RENDER_MODE, "render_mode" },
{ TK_HINT_WHITE_TEXTURE, "hint_white" },
{ TK_HINT_BLACK_TEXTURE, "hint_black" },
{ TK_HINT_NORMAL_TEXTURE, "hint_normal" },
{ TK_HINT_ROUGHNESS_NORMAL_TEXTURE, "hint_roughness_normal" },
{ TK_HINT_ROUGHNESS_R, "hint_roughness_r" },
{ TK_HINT_ROUGHNESS_G, "hint_roughness_g" },
{ TK_HINT_ROUGHNESS_B, "hint_roughness_b" },
{ TK_HINT_ROUGHNESS_A, "hint_roughness_a" },
{ TK_HINT_ROUGHNESS_GRAY, "hint_roughness_gray" },
{ TK_HINT_ANISO_TEXTURE, "hint_aniso" },
{ TK_HINT_ALBEDO_TEXTURE, "hint_albedo" },
{ TK_HINT_BLACK_ALBEDO_TEXTURE, "hint_black_albedo" },
{ TK_HINT_COLOR, "hint_color" },
{ TK_HINT_RANGE, "hint_range" },
{ TK_FILTER_NEAREST, "filter_nearest" },
{ TK_FILTER_LINEAR, "filter_linear" },
{ TK_FILTER_NEAREST_MIPMAP, "filter_nearest_mipmap" },
{ TK_FILTER_LINEAR_MIPMAP, "filter_linear_mipmap" },
{ TK_FILTER_NEAREST_MIPMAP_ANISO, "filter_nearest_mipmap_aniso" },
{ TK_FILTER_LINEAR_MIPMAP_ANISO, "filter_linear_mipmap_aniso" },
{ TK_REPEAT_ENABLE, "repeat_enable" },
{ TK_REPEAT_DISABLE, "repeat_disable" },
{ TK_SHADER_TYPE, "shader_type" },
{ TK_ERROR, NULL }
};
ShaderLanguage::Token ShaderLanguage::_get_token() {
#define GETCHAR(m_idx) (((char_idx + m_idx) < code.length()) ? code[char_idx + m_idx] : CharType(0))
while (true) {
char_idx++;
switch (GETCHAR(-1)) {
case 0:
return _make_token(TK_EOF);
case 0xFFFF:
return _make_token(TK_CURSOR); //for completion
case '\t':
case '\r':
case ' ':
continue;
case '\n':
tk_line++;
continue;
case '/': {
switch (GETCHAR(0)) {
case '*': { // block comment
char_idx++;
while (true) {
if (GETCHAR(0) == 0) {
return _make_token(TK_EOF);
}
if (GETCHAR(0) == '*' && GETCHAR(1) == '/') {
char_idx += 2;
break;
} else if (GETCHAR(0) == '\n') {
tk_line++;
}
char_idx++;
}
} break;
case '/': { // line comment skip
while (true) {
if (GETCHAR(0) == '\n') {
tk_line++;
char_idx++;
break;
}
if (GETCHAR(0) == 0) {
return _make_token(TK_EOF);
}
char_idx++;
}
} break;
case '=': { // diveq
char_idx++;
return _make_token(TK_OP_ASSIGN_DIV);
} break;
default:
return _make_token(TK_OP_DIV);
}
continue; //a comment, continue to next token
} break;
case '=': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_EQUAL);
}
return _make_token(TK_OP_ASSIGN);
} break;
case '<': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_LESS_EQUAL);
} else if (GETCHAR(0) == '<') {
char_idx++;
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_SHIFT_LEFT);
}
return _make_token(TK_OP_SHIFT_LEFT);
}
return _make_token(TK_OP_LESS);
} break;
case '>': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_GREATER_EQUAL);
} else if (GETCHAR(0) == '>') {
char_idx++;
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_SHIFT_RIGHT);
}
return _make_token(TK_OP_SHIFT_RIGHT);
}
return _make_token(TK_OP_GREATER);
} break;
case '!': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_NOT_EQUAL);
}
return _make_token(TK_OP_NOT);
} break;
//case '"' //string - no strings in shader
//case '\'' //string - no strings in shader
case '{':
return _make_token(TK_CURLY_BRACKET_OPEN);
case '}':
return _make_token(TK_CURLY_BRACKET_CLOSE);
case '[':
return _make_token(TK_BRACKET_OPEN);
case ']':
return _make_token(TK_BRACKET_CLOSE);
case '(':
return _make_token(TK_PARENTHESIS_OPEN);
case ')':
return _make_token(TK_PARENTHESIS_CLOSE);
case ',':
return _make_token(TK_COMMA);
case ';':
return _make_token(TK_SEMICOLON);
case '?':
return _make_token(TK_QUESTION);
case ':':
return _make_token(TK_COLON);
case '^':
return _make_token(TK_OP_BIT_XOR);
case '~':
return _make_token(TK_OP_BIT_INVERT);
case '&': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_BIT_AND);
} else if (GETCHAR(0) == '&') {
char_idx++;
return _make_token(TK_OP_AND);
}
return _make_token(TK_OP_BIT_AND);
} break;
case '|': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_BIT_OR);
} else if (GETCHAR(0) == '|') {
char_idx++;
return _make_token(TK_OP_OR);
}
return _make_token(TK_OP_BIT_OR);
} break;
case '*': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_MUL);
}
return _make_token(TK_OP_MUL);
} break;
case '+': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_ADD);
} else if (GETCHAR(0) == '+') {
char_idx++;
return _make_token(TK_OP_INCREMENT);
}
return _make_token(TK_OP_ADD);
} break;
case '-': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_SUB);
} else if (GETCHAR(0) == '-') {
char_idx++;
return _make_token(TK_OP_DECREMENT);
}
return _make_token(TK_OP_SUB);
} break;
case '%': {
if (GETCHAR(0) == '=') {
char_idx++;
return _make_token(TK_OP_ASSIGN_MOD);
}
return _make_token(TK_OP_MOD);
} break;
default: {
char_idx--; //go back one, since we have no idea what this is
if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) {
// parse number
bool period_found = false;
bool exponent_found = false;
bool hexa_found = false;
bool sign_found = false;
bool float_suffix_found = false;
String str;
int i = 0;
while (true) {
if (GETCHAR(i) == '.') {
if (period_found || exponent_found || hexa_found || float_suffix_found)
return _make_token(TK_ERROR, "Invalid numeric constant");
period_found = true;
} else if (GETCHAR(i) == 'x') {
if (hexa_found || str.length() != 1 || str[0] != '0')
return _make_token(TK_ERROR, "Invalid numeric constant");
hexa_found = true;
} else if (GETCHAR(i) == 'e') {
if (hexa_found || exponent_found || float_suffix_found)
return _make_token(TK_ERROR, "Invalid numeric constant");
exponent_found = true;
} else if (GETCHAR(i) == 'f') {
if (hexa_found || exponent_found)
return _make_token(TK_ERROR, "Invalid numeric constant");
float_suffix_found = true;
} else if (_is_number(GETCHAR(i))) {
if (float_suffix_found)
return _make_token(TK_ERROR, "Invalid numeric constant");
} else if (hexa_found && _is_hex(GETCHAR(i))) {
} else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) {
if (sign_found)
return _make_token(TK_ERROR, "Invalid numeric constant");
sign_found = true;
} else
break;
str += CharType(GETCHAR(i));
i++;
}
CharType last_char = str[str.length() - 1];
if (hexa_found) {
//integer(hex)
if (str.size() > 11 || !str.is_valid_hex_number(true)) { // > 0xFFFFFFFF
return _make_token(TK_ERROR, "Invalid (hexadecimal) numeric constant");
}
} else if (period_found || exponent_found || float_suffix_found) {
//floats
if (period_found) {
if (float_suffix_found) {
//checks for eg "1.f" or "1.99f" notations
if (last_char != 'f') {
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
} else {
//checks for eg. "1." or "1.99" notations
if (last_char != '.' && !_is_number(last_char)) {
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
}
} else if (float_suffix_found) {
// if no period found the float suffix must be the last character, like in "2f" for "2.0"
if (last_char != 'f') {
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
}
if (float_suffix_found) {
//strip the suffix
str = str.left(str.length() - 1);
//compensate reading cursor position
char_idx += 1;
}
if (!str.is_valid_float()) {
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
} else {
//integers
if (!_is_number(last_char)) {
return _make_token(TK_ERROR, "Invalid (integer) numeric constant");
}
if (!str.is_valid_integer()) {
return _make_token(TK_ERROR, "Invalid numeric constant");
}
}
char_idx += str.length();
Token tk;
if (period_found || exponent_found || float_suffix_found)
tk.type = TK_REAL_CONSTANT;
else
tk.type = TK_INT_CONSTANT;
if (hexa_found) {
tk.constant = (double)str.hex_to_int64(true);
} else {
tk.constant = str.to_double();
}
tk.line = tk_line;
return tk;
}
if (GETCHAR(0) == '.') {
//parse period
char_idx++;
return _make_token(TK_PERIOD);
}
if (_is_text_char(GETCHAR(0))) {
// parse identifier
String str;
while (_is_text_char(GETCHAR(0))) {
str += CharType(GETCHAR(0));
char_idx++;
}
//see if keyword
//should be converted to a static map
int idx = 0;
while (keyword_list[idx].text) {
if (str == keyword_list[idx].text) {
return _make_token(keyword_list[idx].token);
}
idx++;
}
str = str.replace("dus_", "_");
return _make_token(TK_IDENTIFIER, str);
}
if (GETCHAR(0) > 32)
return _make_token(TK_ERROR, "Tokenizer: Unknown character #" + itos(GETCHAR(0)) + ": '" + String::chr(GETCHAR(0)) + "'");
else
return _make_token(TK_ERROR, "Tokenizer: Unknown character #" + itos(GETCHAR(0)));
} break;
}
}
ERR_PRINT("BUG");
return Token();
#undef GETCHAR
}
String ShaderLanguage::token_debug(const String &p_code) {
clear();
code = p_code;
String output;
Token tk = _get_token();
while (tk.type != TK_EOF && tk.type != TK_ERROR) {
output += itos(tk_line) + ": " + get_token_text(tk) + "\n";
tk = _get_token();
}
return output;
}
bool ShaderLanguage::is_token_variable_datatype(TokenType p_type) {
return (
p_type == TK_TYPE_VOID ||
p_type == TK_TYPE_BOOL ||
p_type == TK_TYPE_BVEC2 ||
p_type == TK_TYPE_BVEC3 ||
p_type == TK_TYPE_BVEC4 ||
p_type == TK_TYPE_INT ||
p_type == TK_TYPE_IVEC2 ||
p_type == TK_TYPE_IVEC3 ||
p_type == TK_TYPE_IVEC4 ||
p_type == TK_TYPE_UINT ||
p_type == TK_TYPE_UVEC2 ||
p_type == TK_TYPE_UVEC3 ||
p_type == TK_TYPE_UVEC4 ||
p_type == TK_TYPE_FLOAT ||
p_type == TK_TYPE_VEC2 ||
p_type == TK_TYPE_VEC3 ||
p_type == TK_TYPE_VEC4 ||
p_type == TK_TYPE_MAT2 ||
p_type == TK_TYPE_MAT3 ||
p_type == TK_TYPE_MAT4);
}
bool ShaderLanguage::is_token_datatype(TokenType p_type) {
return (
p_type == TK_TYPE_VOID ||
p_type == TK_TYPE_BOOL ||
p_type == TK_TYPE_BVEC2 ||
p_type == TK_TYPE_BVEC3 ||
p_type == TK_TYPE_BVEC4 ||
p_type == TK_TYPE_INT ||
p_type == TK_TYPE_IVEC2 ||
p_type == TK_TYPE_IVEC3 ||
p_type == TK_TYPE_IVEC4 ||
p_type == TK_TYPE_UINT ||
p_type == TK_TYPE_UVEC2 ||
p_type == TK_TYPE_UVEC3 ||
p_type == TK_TYPE_UVEC4 ||
p_type == TK_TYPE_FLOAT ||
p_type == TK_TYPE_VEC2 ||
p_type == TK_TYPE_VEC3 ||
p_type == TK_TYPE_VEC4 ||
p_type == TK_TYPE_MAT2 ||
p_type == TK_TYPE_MAT3 ||
p_type == TK_TYPE_MAT4 ||
p_type == TK_TYPE_SAMPLER2D ||
p_type == TK_TYPE_ISAMPLER2D ||
p_type == TK_TYPE_USAMPLER2D ||
p_type == TK_TYPE_SAMPLER2DARRAY ||
p_type == TK_TYPE_ISAMPLER2DARRAY ||
p_type == TK_TYPE_USAMPLER2DARRAY ||
p_type == TK_TYPE_SAMPLER3D ||
p_type == TK_TYPE_ISAMPLER3D ||
p_type == TK_TYPE_USAMPLER3D ||
p_type == TK_TYPE_SAMPLERCUBE);
}
ShaderLanguage::DataType ShaderLanguage::get_token_datatype(TokenType p_type) {
return DataType(p_type - TK_TYPE_VOID);
}
bool ShaderLanguage::is_token_interpolation(TokenType p_type) {
return (
p_type == TK_INTERPOLATION_FLAT ||
p_type == TK_INTERPOLATION_SMOOTH);
}
ShaderLanguage::DataInterpolation ShaderLanguage::get_token_interpolation(TokenType p_type) {
if (p_type == TK_INTERPOLATION_FLAT)
return INTERPOLATION_FLAT;
else
return INTERPOLATION_SMOOTH;
}
bool ShaderLanguage::is_token_precision(TokenType p_type) {
return (
p_type == TK_PRECISION_LOW ||
p_type == TK_PRECISION_MID ||
p_type == TK_PRECISION_HIGH);
}
ShaderLanguage::DataPrecision ShaderLanguage::get_token_precision(TokenType p_type) {
if (p_type == TK_PRECISION_LOW)
return PRECISION_LOWP;
else if (p_type == TK_PRECISION_HIGH)
return PRECISION_HIGHP;
else
return PRECISION_MEDIUMP;
}
String ShaderLanguage::get_precision_name(DataPrecision p_type) {
switch (p_type) {
case PRECISION_LOWP: return "lowp";
case PRECISION_MEDIUMP: return "mediump";
case PRECISION_HIGHP: return "highp";
default:
break;
}
return "";
}
String ShaderLanguage::get_datatype_name(DataType p_type) {
switch (p_type) {
case TYPE_VOID: return "void";
case TYPE_BOOL: return "bool";
case TYPE_BVEC2: return "bvec2";
case TYPE_BVEC3: return "bvec3";
case TYPE_BVEC4: return "bvec4";
case TYPE_INT: return "int";
case TYPE_IVEC2: return "ivec2";
case TYPE_IVEC3: return "ivec3";
case TYPE_IVEC4: return "ivec4";
case TYPE_UINT: return "uint";
case TYPE_UVEC2: return "uvec2";
case TYPE_UVEC3: return "uvec3";
case TYPE_UVEC4: return "uvec4";
case TYPE_FLOAT: return "float";
case TYPE_VEC2: return "vec2";
case TYPE_VEC3: return "vec3";
case TYPE_VEC4: return "vec4";
case TYPE_MAT2: return "mat2";
case TYPE_MAT3: return "mat3";
case TYPE_MAT4: return "mat4";
case TYPE_SAMPLER2D: return "sampler2D";
case TYPE_ISAMPLER2D: return "isampler2D";
case TYPE_USAMPLER2D: return "usampler2D";
case TYPE_SAMPLER2DARRAY: return "sampler2DArray";
case TYPE_ISAMPLER2DARRAY: return "isampler2DArray";
case TYPE_USAMPLER2DARRAY: return "usampler2DArray";
case TYPE_SAMPLER3D: return "sampler3D";
case TYPE_ISAMPLER3D: return "isampler3D";
case TYPE_USAMPLER3D: return "usampler3D";
case TYPE_SAMPLERCUBE: return "samplerCube";
case TYPE_STRUCT: return "struct";
}
return "";
}
bool ShaderLanguage::is_token_nonvoid_datatype(TokenType p_type) {
return is_token_datatype(p_type) && p_type != TK_TYPE_VOID;
}
void ShaderLanguage::clear() {
current_function = StringName();
completion_type = COMPLETION_NONE;
completion_block = NULL;
completion_function = StringName();
completion_class = SubClassTag::TAG_GLOBAL;
completion_struct = StringName();
error_line = 0;
tk_line = 1;
char_idx = 0;
error_set = false;
error_str = "";
while (nodes) {
Node *n = nodes;
nodes = nodes->next;
memdelete(n);
}
}
bool ShaderLanguage::_find_identifier(const BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name) {
if (p_builtin_types.has(p_identifier)) {
if (r_data_type) {
*r_data_type = p_builtin_types[p_identifier].type;
}
if (r_is_const) {
*r_is_const = p_builtin_types[p_identifier].constant;
}
if (r_type) {
*r_type = IDENTIFIER_BUILTIN_VAR;
}
return true;
}
FunctionNode *function = NULL;
while (p_block) {
if (p_block->variables.has(p_identifier)) {
if (r_data_type) {
*r_data_type = p_block->variables[p_identifier].type;
}
if (r_is_const) {
*r_is_const = p_block->variables[p_identifier].is_const;
}
if (r_array_size) {
*r_array_size = p_block->variables[p_identifier].array_size;
}
if (r_type) {
*r_type = IDENTIFIER_LOCAL_VAR;
}
if (r_struct_name) {
*r_struct_name = p_block->variables[p_identifier].struct_name;
}
return true;
}
if (p_block->parent_function) {
function = p_block->parent_function;
break;
} else {
ERR_FAIL_COND_V(!p_block->parent_block, false);
p_block = p_block->parent_block;
}
}
if (function) {
for (int i = 0; i < function->arguments.size(); i++) {
if (function->arguments[i].name == p_identifier) {
if (r_data_type) {
*r_data_type = function->arguments[i].type;
}
if (r_type) {
*r_type = IDENTIFIER_FUNCTION_ARGUMENT;
}
if (r_struct_name) {
*r_struct_name = function->arguments[i].type_str;
}
return true;
}
}
}
if (shader->varyings.has(p_identifier)) {
if (r_data_type) {
*r_data_type = shader->varyings[p_identifier].type;
}
if (r_array_size) {
*r_array_size = shader->varyings[p_identifier].array_size;
}
if (r_type) {
*r_type = IDENTIFIER_VARYING;
}
return true;
}
if (shader->uniforms.has(p_identifier)) {
if (r_data_type) {
*r_data_type = shader->uniforms[p_identifier].type;
}
if (r_type) {
*r_type = IDENTIFIER_UNIFORM;
}
return true;
}
if (shader->constants.has(p_identifier)) {
if (r_data_type) {
*r_data_type = shader->constants[p_identifier].type;
}
if (r_type) {
*r_type = IDENTIFIER_CONSTANT;
}
if (r_struct_name) {
*r_struct_name = shader->constants[p_identifier].type_str;
}
return true;
}
for (int i = 0; i < shader->functions.size(); i++) {
if (!shader->functions[i].callable)
continue;
if (shader->functions[i].name == p_identifier) {
if (r_data_type) {
*r_data_type = shader->functions[i].function->return_type;
}
if (r_type) {
*r_type = IDENTIFIER_FUNCTION;
}
return true;
}
}
return false;
}
bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type) {
bool valid = false;
DataType ret_type = TYPE_VOID;
switch (p_op->op) {
case OP_EQUAL:
case OP_NOT_EQUAL: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
valid = na == nb;
ret_type = TYPE_BOOL;
} break;
case OP_LESS:
case OP_LESS_EQUAL:
case OP_GREATER:
case OP_GREATER_EQUAL: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
valid = na == nb && (na == TYPE_UINT || na == TYPE_INT || na == TYPE_FLOAT);
ret_type = TYPE_BOOL;
} break;
case OP_AND:
case OP_OR: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
valid = na == nb && na == TYPE_BOOL;
ret_type = TYPE_BOOL;
} break;
case OP_NOT: {
DataType na = p_op->arguments[0]->get_datatype();
valid = na == TYPE_BOOL;
ret_type = TYPE_BOOL;
} break;
case OP_INCREMENT:
case OP_DECREMENT:
case OP_POST_INCREMENT:
case OP_POST_DECREMENT:
case OP_NEGATE: {
DataType na = p_op->arguments[0]->get_datatype();
valid = na > TYPE_BOOL && na < TYPE_MAT2;
ret_type = na;
} break;
case OP_ADD:
case OP_SUB:
case OP_MUL:
case OP_DIV: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na > nb) {
//make things easier;
SWAP(na, nb);
}
if (na == nb) {
valid = (na > TYPE_BOOL && na <= TYPE_MAT4);
ret_type = na;
} else if (na == TYPE_INT && nb == TYPE_IVEC2) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_INT && nb == TYPE_IVEC3) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_INT && nb == TYPE_IVEC4) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_UINT && nb == TYPE_UVEC2) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UINT && nb == TYPE_UVEC3) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UINT && nb == TYPE_UVEC4) {
valid = true;
ret_type = TYPE_UVEC4;
} else if (na == TYPE_FLOAT && nb == TYPE_VEC2) {
valid = true;
ret_type = TYPE_VEC2;
} else if (na == TYPE_FLOAT && nb == TYPE_VEC3) {
valid = true;
ret_type = TYPE_VEC3;
} else if (na == TYPE_FLOAT && nb == TYPE_VEC4) {
valid = true;
ret_type = TYPE_VEC4;
} else if (p_op->op == OP_MUL && na == TYPE_FLOAT && nb == TYPE_MAT2) {
valid = true;
ret_type = TYPE_MAT2;
} else if (p_op->op == OP_MUL && na == TYPE_FLOAT && nb == TYPE_MAT3) {
valid = true;
ret_type = TYPE_MAT3;
} else if (p_op->op == OP_MUL && na == TYPE_FLOAT && nb == TYPE_MAT4) {
valid = true;
ret_type = TYPE_MAT4;
} else if (p_op->op == OP_MUL && na == TYPE_VEC2 && nb == TYPE_MAT2) {
valid = true;
ret_type = TYPE_VEC2;
} else if (p_op->op == OP_MUL && na == TYPE_VEC3 && nb == TYPE_MAT3) {
valid = true;
ret_type = TYPE_VEC3;
} else if (p_op->op == OP_MUL && na == TYPE_VEC4 && nb == TYPE_MAT4) {
valid = true;
ret_type = TYPE_VEC4;
}
} break;
case OP_ASSIGN_MOD:
case OP_MOD: {
/*
* The operator modulus (%) operates on signed or unsigned integers or integer vectors. The operand
* types must both be signed or both be unsigned. The operands cannot be vectors of differing size. If
* one operand is a scalar and the other vector, then the scalar is applied component-wise to the vector,
* resulting in the same type as the vector. If both are vectors of the same size, the result is computed
* component-wise.
*/
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na == TYPE_INT && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_INT;
} else if (na == TYPE_IVEC2 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_IVEC2 && nb == TYPE_IVEC2) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_IVEC3) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_IVEC4) {
valid = true;
ret_type = TYPE_IVEC4;
/////
} else if (na == TYPE_UINT && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UINT;
} else if (na == TYPE_UVEC2 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC4;
} else if (na == TYPE_UVEC2 && nb == TYPE_UVEC2) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UVEC3) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UVEC4) {
valid = true;
ret_type = TYPE_UVEC4;
}
} break;
case OP_ASSIGN_SHIFT_LEFT:
case OP_ASSIGN_SHIFT_RIGHT:
case OP_SHIFT_LEFT:
case OP_SHIFT_RIGHT: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na == TYPE_INT && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_INT;
} else if (na == TYPE_IVEC2 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_IVEC2 && nb == TYPE_IVEC2) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_IVEC3) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_IVEC4) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_UINT && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UINT;
} else if (na == TYPE_UVEC2 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC4;
} else if (na == TYPE_UVEC2 && nb == TYPE_UVEC2) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UVEC3) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UVEC4) {
valid = true;
ret_type = TYPE_UVEC4;
}
} break;
case OP_ASSIGN: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na == TYPE_STRUCT || nb == TYPE_STRUCT) {
valid = p_op->arguments[0]->get_datatype_name() == p_op->arguments[1]->get_datatype_name();
} else {
valid = na == nb;
}
ret_type = na;
} break;
case OP_ASSIGN_ADD:
case OP_ASSIGN_SUB:
case OP_ASSIGN_MUL:
case OP_ASSIGN_DIV: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na == nb) {
valid = (na > TYPE_BOOL && na < TYPE_MAT2) || (p_op->op == OP_ASSIGN_MUL && na >= TYPE_MAT2 && na <= TYPE_MAT4);
ret_type = na;
} else if (na == TYPE_IVEC2 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_UVEC2 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC4;
} else if (na == TYPE_VEC2 && nb == TYPE_FLOAT) {
valid = true;
ret_type = TYPE_VEC2;
} else if (na == TYPE_VEC3 && nb == TYPE_FLOAT) {
valid = true;
ret_type = TYPE_VEC3;
} else if (na == TYPE_VEC4 && nb == TYPE_FLOAT) {
valid = true;
ret_type = TYPE_VEC4;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_MAT2 && nb == TYPE_VEC2) {
valid = true;
ret_type = TYPE_MAT2;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_MAT3 && nb == TYPE_VEC3) {
valid = true;
ret_type = TYPE_MAT3;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_MAT4 && nb == TYPE_VEC4) {
valid = true;
ret_type = TYPE_MAT4;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_VEC2 && nb == TYPE_MAT2) {
valid = true;
ret_type = TYPE_VEC2;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_VEC3 && nb == TYPE_MAT3) {
valid = true;
ret_type = TYPE_VEC3;
} else if (p_op->op == OP_ASSIGN_MUL && na == TYPE_VEC4 && nb == TYPE_MAT4) {
valid = true;
ret_type = TYPE_VEC4;
}
} break;
case OP_ASSIGN_BIT_AND:
case OP_ASSIGN_BIT_OR:
case OP_ASSIGN_BIT_XOR:
case OP_BIT_AND:
case OP_BIT_OR:
case OP_BIT_XOR: {
/*
* The bitwise operators and (&), exclusive-or (^), and inclusive-or (|). The operands must be of type
* signed or unsigned integers or integer vectors. The operands cannot be vectors of differing size. If
* one operand is a scalar and the other a vector, the scalar is applied component-wise to the vector,
* resulting in the same type as the vector. The fundamental types of the operands (signed or unsigned)
* must match.
*/
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
if (na > nb && p_op->op >= OP_BIT_AND) {
//can swap for non assign
SWAP(na, nb);
}
if (na == TYPE_INT && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_INT;
} else if (na == TYPE_IVEC2 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_INT) {
valid = true;
ret_type = TYPE_IVEC4;
} else if (na == TYPE_IVEC2 && nb == TYPE_IVEC2) {
valid = true;
ret_type = TYPE_IVEC2;
} else if (na == TYPE_IVEC3 && nb == TYPE_IVEC3) {
valid = true;
ret_type = TYPE_IVEC3;
} else if (na == TYPE_IVEC4 && nb == TYPE_IVEC4) {
valid = true;
ret_type = TYPE_IVEC4;
/////
} else if (na == TYPE_UINT && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UINT;
} else if (na == TYPE_UVEC2 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UINT) {
valid = true;
ret_type = TYPE_UVEC4;
} else if (na == TYPE_UVEC2 && nb == TYPE_UVEC2) {
valid = true;
ret_type = TYPE_UVEC2;
} else if (na == TYPE_UVEC3 && nb == TYPE_UVEC3) {
valid = true;
ret_type = TYPE_UVEC3;
} else if (na == TYPE_UVEC4 && nb == TYPE_UVEC4) {
valid = true;
ret_type = TYPE_UVEC4;
}
} break;
case OP_BIT_INVERT: { //unaries
DataType na = p_op->arguments[0]->get_datatype();
valid = na >= TYPE_INT && na < TYPE_FLOAT;
ret_type = na;
} break;
case OP_SELECT_IF: {
DataType na = p_op->arguments[0]->get_datatype();
DataType nb = p_op->arguments[1]->get_datatype();
DataType nc = p_op->arguments[2]->get_datatype();
valid = na == TYPE_BOOL && (nb == nc);
ret_type = nb;
} break;
default: {
ERR_FAIL_V(false);
}
}
if (r_ret_type)
*r_ret_type = ret_type;
return valid;
}
const ShaderLanguage::BuiltinFuncDef ShaderLanguage::builtin_func_defs[] = {
//constructors
{ "bool", TYPE_BOOL, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec2", TYPE_BVEC2, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec2", TYPE_BVEC2, { TYPE_BOOL, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_BOOL, TYPE_BOOL, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_BVEC2, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_BOOL, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BOOL, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BOOL, TYPE_BVEC2, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BVEC2, TYPE_BOOL, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BOOL, TYPE_BOOL, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BOOL, TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BVEC3, TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_BVEC2, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "float", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec2", TYPE_VEC2, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec2", TYPE_VEC2, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_FLOAT, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_FLOAT, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_VEC2, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_FLOAT, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "int", TYPE_INT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec2", TYPE_IVEC2, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec2", TYPE_IVEC2, { TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_INT, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_INT, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_INT, TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_IVEC2, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_INT, TYPE_INT, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_INT, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "uint", TYPE_UINT, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec2", TYPE_UVEC2, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec2", TYPE_UVEC2, { TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_UINT, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_UVEC2, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_UINT, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UINT, TYPE_UINT, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UINT, TYPE_UVEC2, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UVEC2, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UINT, TYPE_UINT, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UINT, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UVEC3, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "mat2", TYPE_MAT2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat3", TYPE_MAT3, { TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat4", TYPE_MAT4, { TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat2", TYPE_MAT2, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat3", TYPE_MAT3, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat4", TYPE_MAT4, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
//conversion scalars
{ "int", TYPE_INT, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "int", TYPE_INT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "int", TYPE_INT, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "int", TYPE_INT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "float", TYPE_FLOAT, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "float", TYPE_FLOAT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "float", TYPE_FLOAT, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "float", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "uint", TYPE_UINT, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, true },
{ "uint", TYPE_UINT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uint", TYPE_UINT, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uint", TYPE_UINT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "bool", TYPE_BOOL, { TYPE_BOOL, TYPE_VOID }, TAG_GLOBAL, false },
{ "bool", TYPE_BOOL, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "bool", TYPE_BOOL, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "bool", TYPE_BOOL, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
//conversion vectors
{ "ivec2", TYPE_IVEC2, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec2", TYPE_IVEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec2", TYPE_IVEC2, { TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec2", TYPE_IVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec2", TYPE_VEC2, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec2", TYPE_VEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec2", TYPE_VEC2, { TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "vec2", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "uvec2", TYPE_UVEC2, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec2", TYPE_UVEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec2", TYPE_UVEC2, { TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec2", TYPE_UVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec2", TYPE_BVEC2, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec2", TYPE_BVEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec2", TYPE_BVEC2, { TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec2", TYPE_BVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec3", TYPE_IVEC3, { TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "ivec3", TYPE_IVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec3", TYPE_VEC3, { TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "vec3", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "uvec3", TYPE_UVEC3, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec3", TYPE_UVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec3", TYPE_BVEC3, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec3", TYPE_BVEC3, { TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec3", TYPE_BVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "ivec4", TYPE_IVEC4, { TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "ivec4", TYPE_IVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "vec4", TYPE_VEC4, { TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "vec4", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "uvec4", TYPE_UVEC4, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "uvec4", TYPE_UVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec4", TYPE_BVEC4, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "bvec4", TYPE_BVEC4, { TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "bvec4", TYPE_BVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
//conversion between matrixes
{ "mat2", TYPE_MAT2, { TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat2", TYPE_MAT2, { TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat3", TYPE_MAT3, { TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat3", TYPE_MAT3, { TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat4", TYPE_MAT4, { TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mat4", TYPE_MAT4, { TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
//builtins - trigonometry
{ "radians", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "radians", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "radians", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "radians", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "degrees", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "degrees", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "degrees", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "degrees", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "sin", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "sin", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "sin", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "sin", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "cos", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "cos", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "cos", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "cos", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "tan", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "tan", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "tan", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "tan", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "asin", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "asin", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "asin", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "asin", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "acos", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "acos", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "acos", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "acos", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "atan", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "sinh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "sinh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "sinh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "sinh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "cosh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "cosh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "cosh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "cosh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "tanh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "tanh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "tanh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "tanh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "asinh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "asinh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "asinh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "asinh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "acosh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "acosh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "acosh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "acosh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "atanh", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "atanh", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "atanh", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "atanh", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
//builtins - exponential
{ "pow", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "pow", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "pow", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "pow", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "log", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "log", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "log", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "log", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp2", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp2", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp2", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "exp2", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "log2", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "log2", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "log2", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "log2", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "sqrt", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "sqrt", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "sqrt", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "sqrt", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "inversesqrt", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "inversesqrt", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "inversesqrt", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "inversesqrt", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
//builtins - common
{ "abs", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_INT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_IVEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_IVEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "abs", TYPE_IVEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_INT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_IVEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_IVEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "sign", TYPE_IVEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "floor", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "floor", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "floor", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "floor", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "trunc", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "trunc", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "trunc", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "trunc", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "round", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "round", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "round", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "round", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "roundEven", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "roundEven", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "roundEven", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "roundEven", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "ceil", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "ceil", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "ceil", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "ceil", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "fract", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "fract", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "fract", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "fract", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC2, { TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC3, { TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mod", TYPE_VEC4, { TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "modf", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "modf", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "modf", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "modf", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC2, { TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC3, { TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_VEC4, { TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_INT, { TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC2, { TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC3, { TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_IVEC4, { TYPE_IVEC4, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "min", TYPE_UINT, { TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "min", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC2, { TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC3, { TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_VEC4, { TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_INT, { TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC2, { TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC3, { TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_IVEC4, { TYPE_IVEC4, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "max", TYPE_UINT, { TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "max", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC2, { TYPE_VEC2, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC3, { TYPE_VEC3, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_VEC4, { TYPE_VEC4, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_INT, { TYPE_INT, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC2, { TYPE_IVEC2, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC3, { TYPE_IVEC3, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_IVEC4, { TYPE_IVEC4, TYPE_INT, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, false },
{ "clamp", TYPE_UINT, { TYPE_UINT, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC2, { TYPE_UVEC2, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC3, { TYPE_UVEC3, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "clamp", TYPE_UVEC4, { TYPE_UVEC4, TYPE_UINT, TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "mix", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "mix", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC2, { TYPE_FLOAT, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC3, { TYPE_FLOAT, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "step", TYPE_VEC4, { TYPE_FLOAT, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_FLOAT, { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC2, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC3, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "smoothstep", TYPE_VEC4, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "isnan", TYPE_BOOL, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "isnan", TYPE_BVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "isnan", TYPE_BVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "isnan", TYPE_BVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "isinf", TYPE_BOOL, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "isinf", TYPE_BVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "isinf", TYPE_BVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "isinf", TYPE_BVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "floatBitsToInt", TYPE_INT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToInt", TYPE_IVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToInt", TYPE_IVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToInt", TYPE_IVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToUint", TYPE_UINT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToUint", TYPE_UVEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToUint", TYPE_UVEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "floatBitsToUint", TYPE_UVEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "intBitsToFloat", TYPE_FLOAT, { TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "intBitsToFloat", TYPE_VEC2, { TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "intBitsToFloat", TYPE_VEC3, { TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "intBitsToFloat", TYPE_VEC4, { TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "uintBitsToFloat", TYPE_FLOAT, { TYPE_UINT, TYPE_VOID }, TAG_GLOBAL, true },
{ "uintBitsToFloat", TYPE_VEC2, { TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "uintBitsToFloat", TYPE_VEC3, { TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "uintBitsToFloat", TYPE_VEC4, { TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
//builtins - geometric
{ "length", TYPE_FLOAT, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "length", TYPE_FLOAT, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "length", TYPE_FLOAT, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "distance", TYPE_FLOAT, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "distance", TYPE_FLOAT, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "distance", TYPE_FLOAT, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "dot", TYPE_FLOAT, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "dot", TYPE_FLOAT, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "dot", TYPE_FLOAT, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "cross", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "normalize", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "normalize", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "normalize", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "reflect", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "refract", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "faceforward", TYPE_VEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "faceforward", TYPE_VEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "faceforward", TYPE_VEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "matrixCompMult", TYPE_MAT2, { TYPE_MAT2, TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "matrixCompMult", TYPE_MAT3, { TYPE_MAT3, TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
{ "matrixCompMult", TYPE_MAT4, { TYPE_MAT4, TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "outerProduct", TYPE_MAT2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "outerProduct", TYPE_MAT3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "outerProduct", TYPE_MAT4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "transpose", TYPE_MAT2, { TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "transpose", TYPE_MAT3, { TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
{ "transpose", TYPE_MAT4, { TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "determinant", TYPE_FLOAT, { TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "determinant", TYPE_FLOAT, { TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
{ "determinant", TYPE_FLOAT, { TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "inverse", TYPE_MAT2, { TYPE_MAT2, TYPE_VOID }, TAG_GLOBAL, false },
{ "inverse", TYPE_MAT3, { TYPE_MAT3, TYPE_VOID }, TAG_GLOBAL, false },
{ "inverse", TYPE_MAT4, { TYPE_MAT4, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThan", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "lessThan", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "lessThan", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThan", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThan", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThan", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThan", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "lessThanEqual", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "lessThanEqual", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "lessThanEqual", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "lessThanEqual", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThanEqual", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "greaterThanEqual", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThanEqual", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "greaterThanEqual", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "equal", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "equal", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "equal", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "equal", TYPE_BVEC2, { TYPE_BVEC2, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC3, { TYPE_BVEC3, TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "equal", TYPE_BVEC4, { TYPE_BVEC4, TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC2, { TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC3, { TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC4, { TYPE_VEC4, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC2, { TYPE_IVEC2, TYPE_IVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC3, { TYPE_IVEC3, TYPE_IVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC4, { TYPE_IVEC4, TYPE_IVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC2, { TYPE_UVEC2, TYPE_UVEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "notEqual", TYPE_BVEC3, { TYPE_UVEC3, TYPE_UVEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "notEqual", TYPE_BVEC4, { TYPE_UVEC4, TYPE_UVEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "notEqual", TYPE_BVEC2, { TYPE_BVEC2, TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC3, { TYPE_BVEC3, TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "notEqual", TYPE_BVEC4, { TYPE_BVEC4, TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "any", TYPE_BOOL, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "any", TYPE_BOOL, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "any", TYPE_BOOL, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "all", TYPE_BOOL, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "all", TYPE_BOOL, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "all", TYPE_BOOL, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
{ "not", TYPE_BVEC2, { TYPE_BVEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "not", TYPE_BVEC3, { TYPE_BVEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "not", TYPE_BVEC4, { TYPE_BVEC4, TYPE_VOID }, TAG_GLOBAL, false },
//builtins - texture
{ "textureSize", TYPE_IVEC2, { TYPE_SAMPLER2D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC2, { TYPE_ISAMPLER2D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC2, { TYPE_USAMPLER2D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_SAMPLER2DARRAY, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_ISAMPLER2DARRAY, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_USAMPLER2DARRAY, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_SAMPLER3D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_ISAMPLER3D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC3, { TYPE_USAMPLER3D, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureSize", TYPE_IVEC2, { TYPE_SAMPLERCUBE, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER2DARRAY, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER2DARRAY, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER2DARRAY, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texture", TYPE_VEC4, { TYPE_SAMPLERCUBE, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, false },
{ "texture", TYPE_VEC4, { TYPE_SAMPLERCUBE, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProj", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "textureLod", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC2, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_VEC4, { TYPE_SAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "textureLod", TYPE_IVEC4, { TYPE_ISAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_UVEC4, { TYPE_USAMPLER2DARRAY, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "textureLod", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureLod", TYPE_VEC4, { TYPE_SAMPLERCUBE, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, false },
{ "texelFetch", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_IVEC2, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_VEC4, { TYPE_SAMPLER2DARRAY, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_IVEC4, { TYPE_ISAMPLER2DARRAY, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_UVEC4, { TYPE_USAMPLER2DARRAY, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "texelFetch", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_IVEC3, TYPE_INT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC3, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureProjLod", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC4, TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_VEC4, { TYPE_SAMPLER2D, TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_IVEC4, { TYPE_ISAMPLER2D, TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_UVEC4, { TYPE_USAMPLER2D, TYPE_VEC2, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_VEC4, { TYPE_SAMPLER2DARRAY, TYPE_VEC3, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_IVEC4, { TYPE_ISAMPLER2DARRAY, TYPE_VEC3, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_UVEC4, { TYPE_USAMPLER2DARRAY, TYPE_VEC3, TYPE_VEC2, TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_VEC4, { TYPE_SAMPLER3D, TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_IVEC4, { TYPE_ISAMPLER3D, TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_UVEC4, { TYPE_USAMPLER3D, TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "textureGrad", TYPE_VEC4, { TYPE_SAMPLERCUBE, TYPE_VEC3, TYPE_VEC3, TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdx", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdx", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdx", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdx", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdy", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdy", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdy", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "dFdy", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
{ "fwidth", TYPE_FLOAT, { TYPE_FLOAT, TYPE_VOID }, TAG_GLOBAL, true },
{ "fwidth", TYPE_VEC2, { TYPE_VEC2, TYPE_VOID }, TAG_GLOBAL, true },
{ "fwidth", TYPE_VEC3, { TYPE_VEC3, TYPE_VOID }, TAG_GLOBAL, true },
{ "fwidth", TYPE_VEC4, { TYPE_VEC4, TYPE_VOID }, TAG_GLOBAL, true },
//sub-functions
//array
{ "length", TYPE_INT, { TYPE_VOID }, TAG_ARRAY, true },
{ NULL, TYPE_VOID, { TYPE_VOID }, TAG_GLOBAL, false }
};
const ShaderLanguage::BuiltinFuncOutArgs ShaderLanguage::builtin_func_out_args[] = {
//constructors
{ "modf", 1 },
{ NULL, 0 }
};
bool ShaderLanguage::_validate_function_call(BlockNode *p_block, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str) {
ERR_FAIL_COND_V(p_func->op != OP_CALL && p_func->op != OP_CONSTRUCT, false);
Vector<DataType> args;
Vector<StringName> args2;
ERR_FAIL_COND_V(p_func->arguments[0]->type != Node::TYPE_VARIABLE, false);
StringName name = static_cast<VariableNode *>(p_func->arguments[0])->name.operator String();
for (int i = 1; i < p_func->arguments.size(); i++) {
args.push_back(p_func->arguments[i]->get_datatype());
args2.push_back(p_func->arguments[i]->get_datatype_name());
}
int argcount = args.size();
bool failed_builtin = false;
bool unsupported_builtin = false;
int builtin_idx = 0;
if (argcount <= 4) {
// test builtins
int idx = 0;
while (builtin_func_defs[idx].name) {
if (completion_class != builtin_func_defs[idx].tag) {
idx++;
continue;
}
if (name == builtin_func_defs[idx].name) {
failed_builtin = true;
bool fail = false;
for (int i = 0; i < argcount; i++) {
if (get_scalar_type(args[i]) == args[i] && p_func->arguments[i + 1]->type == Node::TYPE_CONSTANT && convert_constant(static_cast<ConstantNode *>(p_func->arguments[i + 1]), builtin_func_defs[idx].args[i])) {
//all good, but needs implicit conversion later
} else if (args[i] != builtin_func_defs[idx].args[i]) {
fail = true;
break;
}
}
if (!fail) {
if (VisualServer::get_singleton()->is_low_end()) {
if (builtin_func_defs[idx].high_end) {
fail = true;
unsupported_builtin = true;
builtin_idx = idx;
}
}
}
if (!fail && argcount < 4 && builtin_func_defs[idx].args[argcount] != TYPE_VOID)
fail = true; //make sure the number of arguments matches
if (!fail) {
//make sure its not an out argument used in the wrong way
int outarg_idx = 0;
while (builtin_func_out_args[outarg_idx].name) {
if (String(name) == builtin_func_out_args[outarg_idx].name) {
int arg_idx = builtin_func_out_args[outarg_idx].argument;
if (arg_idx < argcount) {
if (p_func->arguments[arg_idx + 1]->type != Node::TYPE_VARIABLE) {
_set_error("Argument " + itos(arg_idx + 1) + " of function '" + String(name) + "' is not a variable");
return false;
}
StringName var_name = static_cast<const VariableNode *>(p_func->arguments[arg_idx + 1])->name;
const BlockNode *b = p_block;
bool valid = false;
while (b) {
if (b->variables.has(var_name)) {
valid = true;
break;
}
b = b->parent_block;
}
if (!valid) {
_set_error("Argument " + itos(arg_idx + 1) + " of function '" + String(name) + "' can only take a local variable");
return false;
}
}
}
outarg_idx++;
}
//implicitly convert values if possible
for (int i = 0; i < argcount; i++) {
if (get_scalar_type(args[i]) != args[i] || args[i] == builtin_func_defs[idx].args[i] || p_func->arguments[i + 1]->type != Node::TYPE_CONSTANT) {
//can't do implicit conversion here
continue;
}
//this is an implicit conversion
ConstantNode *constant = static_cast<ConstantNode *>(p_func->arguments[i + 1]);
ConstantNode *conversion = alloc_node<ConstantNode>();
conversion->datatype = builtin_func_defs[idx].args[i];
conversion->values.resize(1);
convert_constant(constant, builtin_func_defs[idx].args[i], conversion->values.ptrw());
p_func->arguments.write[i + 1] = conversion;
}
if (r_ret_type)
*r_ret_type = builtin_func_defs[idx].rettype;
return true;
}
}
idx++;
}
}
if (unsupported_builtin) {
String arglist = "";
for (int i = 0; i < argcount; i++) {
if (i > 0) {
arglist += ", ";
}
arglist += get_datatype_name(builtin_func_defs[builtin_idx].args[i]);
}
String err = "Built-in function \"" + String(name) + "(" + arglist + ")\" is supported only on high-end platform!";
_set_error(err);
return false;
}
if (failed_builtin) {
String err = "Invalid arguments for built-in function: " + String(name) + "(";
for (int i = 0; i < argcount; i++) {
if (i > 0)
err += ",";
if (p_func->arguments[i + 1]->type == Node::TYPE_CONSTANT && p_func->arguments[i + 1]->get_datatype() == TYPE_INT && static_cast<ConstantNode *>(p_func->arguments[i + 1])->values[0].sint < 0) {
err += "-";
}
err += get_datatype_name(args[i]);
}
err += ")";
_set_error(err);
return false;
}
// try existing functions..
StringName exclude_function;
BlockNode *block = p_block;
while (block) {
if (block->parent_function) {
exclude_function = block->parent_function->name;
}
block = block->parent_block;
}
if (name == exclude_function) {
_set_error("Recursion is not allowed");
return false;
}
for (int i = 0; i < shader->functions.size(); i++) {
if (name != shader->functions[i].name)
continue;
if (!shader->functions[i].callable) {
_set_error("Function '" + String(name) + " can't be called from source code.");
return false;
}
FunctionNode *pfunc = shader->functions[i].function;
if (pfunc->arguments.size() != args.size())
continue;
bool fail = false;
for (int j = 0; j < args.size(); j++) {
if (args[j] == TYPE_STRUCT && args2[j] != pfunc->arguments[j].type_str) {
fail = true;
break;
}
if (get_scalar_type(args[j]) == args[j] && p_func->arguments[j + 1]->type == Node::TYPE_CONSTANT && convert_constant(static_cast<ConstantNode *>(p_func->arguments[j + 1]), pfunc->arguments[j].type)) {
//all good, but it needs implicit conversion later
} else if (args[j] != pfunc->arguments[j].type) {
fail = true;
break;
}
}
if (!fail) {
//implicitly convert values if possible
for (int k = 0; k < args.size(); k++) {
if (get_scalar_type(args[k]) != args[k] || args[k] == pfunc->arguments[k].type || p_func->arguments[k + 1]->type != Node::TYPE_CONSTANT) {
//can't do implicit conversion here
continue;
}
//this is an implicit conversion
ConstantNode *constant = static_cast<ConstantNode *>(p_func->arguments[k + 1]);
ConstantNode *conversion = alloc_node<ConstantNode>();
conversion->datatype = pfunc->arguments[k].type;
conversion->values.resize(1);
convert_constant(constant, pfunc->arguments[k].type, conversion->values.ptrw());
p_func->arguments.write[k + 1] = conversion;
}
if (r_ret_type) {
*r_ret_type = pfunc->return_type;
if (pfunc->return_type == TYPE_STRUCT) {
*r_ret_type_str = pfunc->return_struct_name;
}
}
return true;
}
}
return false;
}
bool ShaderLanguage::_compare_datatypes_in_nodes(Node *a, Node *b) const {
if (a->get_datatype() != b->get_datatype()) {
return false;
}
if (a->get_datatype() == TYPE_STRUCT || b->get_datatype() == TYPE_STRUCT) {
if (a->get_datatype_name() != b->get_datatype_name()) {
return false;
}
}
return true;
}
bool ShaderLanguage::_parse_function_arguments(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, OperatorNode *p_func, int *r_complete_arg) {
TkPos pos = _get_tkpos();
Token tk = _get_token();
if (tk.type == TK_PARENTHESIS_CLOSE) {
return true;
}
_set_tkpos(pos);
while (true) {
if (r_complete_arg) {
pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_CURSOR) {
*r_complete_arg = p_func->arguments.size() - 1;
} else {
_set_tkpos(pos);
}
}
Node *arg = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!arg) {
return false;
}
p_func->arguments.push_back(arg);
tk = _get_token();
if (tk.type == TK_PARENTHESIS_CLOSE) {
return true;
} else if (tk.type != TK_COMMA) {
// something is broken
_set_error("Expected ',' or ')' after argument");
return false;
}
}
return true;
}
bool ShaderLanguage::is_token_operator(TokenType p_type) {
return (p_type == TK_OP_EQUAL ||
p_type == TK_OP_NOT_EQUAL ||
p_type == TK_OP_LESS ||
p_type == TK_OP_LESS_EQUAL ||
p_type == TK_OP_GREATER ||
p_type == TK_OP_GREATER_EQUAL ||
p_type == TK_OP_AND ||
p_type == TK_OP_OR ||
p_type == TK_OP_NOT ||
p_type == TK_OP_ADD ||
p_type == TK_OP_SUB ||
p_type == TK_OP_MUL ||
p_type == TK_OP_DIV ||
p_type == TK_OP_MOD ||
p_type == TK_OP_SHIFT_LEFT ||
p_type == TK_OP_SHIFT_RIGHT ||
p_type == TK_OP_ASSIGN ||
p_type == TK_OP_ASSIGN_ADD ||
p_type == TK_OP_ASSIGN_SUB ||
p_type == TK_OP_ASSIGN_MUL ||
p_type == TK_OP_ASSIGN_DIV ||
p_type == TK_OP_ASSIGN_MOD ||
p_type == TK_OP_ASSIGN_SHIFT_LEFT ||
p_type == TK_OP_ASSIGN_SHIFT_RIGHT ||
p_type == TK_OP_ASSIGN_BIT_AND ||
p_type == TK_OP_ASSIGN_BIT_OR ||
p_type == TK_OP_ASSIGN_BIT_XOR ||
p_type == TK_OP_BIT_AND ||
p_type == TK_OP_BIT_OR ||
p_type == TK_OP_BIT_XOR ||
p_type == TK_OP_BIT_INVERT ||
p_type == TK_OP_INCREMENT ||
p_type == TK_OP_DECREMENT ||
p_type == TK_QUESTION ||
p_type == TK_COLON);
}
bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, ConstantNode::Value *p_value) {
if (p_constant->datatype == p_to_type) {
if (p_value) {
for (int i = 0; i < p_constant->values.size(); i++) {
p_value[i] = p_constant->values[i];
}
}
return true;
} else if (p_constant->datatype == TYPE_INT && p_to_type == TYPE_FLOAT) {
if (p_value) {
p_value->real = p_constant->values[0].sint;
}
return true;
} else if (p_constant->datatype == TYPE_UINT && p_to_type == TYPE_FLOAT) {
if (p_value) {
p_value->real = p_constant->values[0].uint;
}
return true;
} else if (p_constant->datatype == TYPE_INT && p_to_type == TYPE_UINT) {
if (p_constant->values[0].sint < 0) {
return false;
}
if (p_value) {
p_value->uint = p_constant->values[0].sint;
}
return true;
} else if (p_constant->datatype == TYPE_UINT && p_to_type == TYPE_INT) {
if (p_constant->values[0].uint > 0x7FFFFFFF) {
return false;
}
if (p_value) {
p_value->sint = p_constant->values[0].uint;
}
return true;
} else
return false;
}
bool ShaderLanguage::is_scalar_type(DataType p_type) {
return p_type == TYPE_BOOL || p_type == TYPE_INT || p_type == TYPE_UINT || p_type == TYPE_FLOAT;
}
bool ShaderLanguage::is_sampler_type(DataType p_type) {
return p_type == TYPE_SAMPLER2D ||
p_type == TYPE_ISAMPLER2D ||
p_type == TYPE_USAMPLER2D ||
p_type == TYPE_SAMPLER2DARRAY ||
p_type == TYPE_ISAMPLER2DARRAY ||
p_type == TYPE_USAMPLER2DARRAY ||
p_type == TYPE_SAMPLER3D ||
p_type == TYPE_ISAMPLER3D ||
p_type == TYPE_USAMPLER3D ||
p_type == TYPE_SAMPLERCUBE;
}
Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, ShaderLanguage::ShaderNode::Uniform::Hint p_hint) {
if (p_value.size() > 0) {
Variant value;
switch (p_type) {
case ShaderLanguage::TYPE_BOOL:
value = Variant(p_value[0].boolean);
break;
case ShaderLanguage::TYPE_BVEC2:
case ShaderLanguage::TYPE_BVEC3:
case ShaderLanguage::TYPE_BVEC4:
case ShaderLanguage::TYPE_INT:
value = Variant(p_value[0].sint);
break;
case ShaderLanguage::TYPE_IVEC2:
value = Variant(Vector2(p_value[0].sint, p_value[1].sint));
break;
case ShaderLanguage::TYPE_IVEC3:
value = Variant(Vector3(p_value[0].sint, p_value[1].sint, p_value[2].sint));
break;
case ShaderLanguage::TYPE_IVEC4:
value = Variant(Plane(p_value[0].sint, p_value[1].sint, p_value[2].sint, p_value[3].sint));
break;
case ShaderLanguage::TYPE_UINT:
value = Variant(p_value[0].uint);
break;
case ShaderLanguage::TYPE_UVEC2:
value = Variant(Vector2(p_value[0].uint, p_value[1].uint));
break;
case ShaderLanguage::TYPE_UVEC3:
value = Variant(Vector3(p_value[0].uint, p_value[1].uint, p_value[2].uint));
break;
case ShaderLanguage::TYPE_UVEC4:
value = Variant(Plane(p_value[0].uint, p_value[1].uint, p_value[2].uint, p_value[3].uint));
break;
case ShaderLanguage::TYPE_FLOAT:
value = Variant(p_value[0].real);
break;
case ShaderLanguage::TYPE_VEC2:
value = Variant(Vector2(p_value[0].real, p_value[1].real));
break;
case ShaderLanguage::TYPE_VEC3:
value = Variant(Vector3(p_value[0].real, p_value[1].real, p_value[2].real));
break;
case ShaderLanguage::TYPE_VEC4:
if (p_hint == ShaderLanguage::ShaderNode::Uniform::HINT_COLOR) {
value = Variant(Color(p_value[0].real, p_value[1].real, p_value[2].real, p_value[3].real));
} else {
value = Variant(Plane(p_value[0].real, p_value[1].real, p_value[2].real, p_value[3].real));
}
break;
case ShaderLanguage::TYPE_MAT2:
value = Variant(Transform2D(p_value[0].real, p_value[2].real, p_value[1].real, p_value[3].real, 0.0, 0.0));
break;
case ShaderLanguage::TYPE_MAT3: {
Basis p;
p[0][0] = p_value[0].real;
p[0][1] = p_value[1].real;
p[0][2] = p_value[2].real;
p[1][0] = p_value[3].real;
p[1][1] = p_value[4].real;
p[1][2] = p_value[5].real;
p[2][0] = p_value[6].real;
p[2][1] = p_value[7].real;
p[2][2] = p_value[8].real;
value = Variant(p);
break;
}
case ShaderLanguage::TYPE_MAT4: {
Basis p;
p[0][0] = p_value[0].real;
p[0][1] = p_value[1].real;
p[0][2] = p_value[2].real;
p[1][0] = p_value[4].real;
p[1][1] = p_value[5].real;
p[1][2] = p_value[6].real;
p[2][0] = p_value[8].real;
p[2][1] = p_value[9].real;
p[2][2] = p_value[10].real;
Transform t = Transform(p, Vector3(p_value[3].real, p_value[7].real, p_value[11].real));
value = Variant(t);
break;
}
case ShaderLanguage::TYPE_ISAMPLER2DARRAY:
case ShaderLanguage::TYPE_ISAMPLER2D:
case ShaderLanguage::TYPE_ISAMPLER3D:
case ShaderLanguage::TYPE_SAMPLER2DARRAY:
case ShaderLanguage::TYPE_SAMPLER2D:
case ShaderLanguage::TYPE_SAMPLER3D:
case ShaderLanguage::TYPE_USAMPLER2DARRAY:
case ShaderLanguage::TYPE_USAMPLER2D:
case ShaderLanguage::TYPE_USAMPLER3D:
case ShaderLanguage::TYPE_SAMPLERCUBE: {
// Texture types, likely not relevant here.
break;
}
case ShaderLanguage::TYPE_STRUCT:
break;
case ShaderLanguage::TYPE_VOID:
break;
}
return value;
}
return Variant();
}
PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform &p_uniform) {
PropertyInfo pi;
switch (p_uniform.type) {
case ShaderLanguage::TYPE_VOID: pi.type = Variant::NIL; break;
case ShaderLanguage::TYPE_BOOL: pi.type = Variant::BOOL; break;
case ShaderLanguage::TYPE_BVEC2:
pi.type = Variant::INT;
pi.hint = PROPERTY_HINT_FLAGS;
pi.hint_string = "x,y";
break;
case ShaderLanguage::TYPE_BVEC3:
pi.type = Variant::INT;
pi.hint = PROPERTY_HINT_FLAGS;
pi.hint_string = "x,y,z";
break;
case ShaderLanguage::TYPE_BVEC4:
pi.type = Variant::INT;
pi.hint = PROPERTY_HINT_FLAGS;
pi.hint_string = "x,y,z,w";
break;
case ShaderLanguage::TYPE_UINT:
case ShaderLanguage::TYPE_INT: {
pi.type = Variant::INT;
if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_RANGE) {
pi.hint = PROPERTY_HINT_RANGE;
pi.hint_string = rtos(p_uniform.hint_range[0]) + "," + rtos(p_uniform.hint_range[1]);
}
} break;
case ShaderLanguage::TYPE_IVEC2:
case ShaderLanguage::TYPE_IVEC3:
case ShaderLanguage::TYPE_IVEC4:
case ShaderLanguage::TYPE_UVEC2:
case ShaderLanguage::TYPE_UVEC3:
case ShaderLanguage::TYPE_UVEC4: {
pi.type = Variant::PACKED_INT32_ARRAY;
} break;
case ShaderLanguage::TYPE_FLOAT: {
pi.type = Variant::FLOAT;
if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_RANGE) {
pi.hint = PROPERTY_HINT_RANGE;
pi.hint_string = rtos(p_uniform.hint_range[0]) + "," + rtos(p_uniform.hint_range[1]) + "," + rtos(p_uniform.hint_range[2]);
}
} break;
case ShaderLanguage::TYPE_VEC2: pi.type = Variant::VECTOR2; break;
case ShaderLanguage::TYPE_VEC3: pi.type = Variant::VECTOR3; break;
case ShaderLanguage::TYPE_VEC4: {
if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_COLOR) {
pi.type = Variant::COLOR;
} else {
pi.type = Variant::PLANE;
}
} break;
case ShaderLanguage::TYPE_MAT2: pi.type = Variant::TRANSFORM2D; break;
case ShaderLanguage::TYPE_MAT3: pi.type = Variant::BASIS; break;
case ShaderLanguage::TYPE_MAT4: pi.type = Variant::TRANSFORM; break;
case ShaderLanguage::TYPE_SAMPLER2D:
case ShaderLanguage::TYPE_ISAMPLER2D:
case ShaderLanguage::TYPE_USAMPLER2D: {
pi.type = Variant::OBJECT;
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
pi.hint_string = "Texture2D";
} break;
case ShaderLanguage::TYPE_SAMPLER2DARRAY:
case ShaderLanguage::TYPE_ISAMPLER2DARRAY:
case ShaderLanguage::TYPE_USAMPLER2DARRAY: {
pi.type = Variant::OBJECT;
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
pi.hint_string = "TextureArray";
} break;
case ShaderLanguage::TYPE_SAMPLER3D:
case ShaderLanguage::TYPE_ISAMPLER3D:
case ShaderLanguage::TYPE_USAMPLER3D: {
pi.type = Variant::OBJECT;
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
pi.hint_string = "Texture3D";
} break;
case ShaderLanguage::TYPE_SAMPLERCUBE: {
pi.type = Variant::OBJECT;
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
pi.hint_string = "CubeMap";
} break;
case ShaderLanguage::TYPE_STRUCT: {
// FIXME: Implement this.
} break;
}
return pi;
}
uint32_t ShaderLanguage::get_type_size(DataType p_type) {
switch (p_type) {
case TYPE_VOID:
return 0;
case TYPE_BOOL:
case TYPE_INT:
case TYPE_UINT:
case TYPE_FLOAT:
return 4;
case TYPE_BVEC2:
case TYPE_IVEC2:
case TYPE_UVEC2:
case TYPE_VEC2:
return 8;
case TYPE_BVEC3:
case TYPE_IVEC3:
case TYPE_UVEC3:
case TYPE_VEC3:
return 12;
case TYPE_BVEC4:
case TYPE_IVEC4:
case TYPE_UVEC4:
case TYPE_VEC4:
return 16;
case TYPE_MAT2:
return 8;
case TYPE_MAT3:
return 12;
case TYPE_MAT4:
return 16;
case TYPE_SAMPLER2D:
case TYPE_ISAMPLER2D:
case TYPE_USAMPLER2D:
case TYPE_SAMPLER2DARRAY:
case TYPE_ISAMPLER2DARRAY:
case TYPE_USAMPLER2DARRAY:
case TYPE_SAMPLER3D:
case TYPE_ISAMPLER3D:
case TYPE_USAMPLER3D:
case TYPE_SAMPLERCUBE:
return 4; //not really, but useful for indices
case TYPE_STRUCT:
// FIXME: Implement.
return 0;
}
return 0;
}
void ShaderLanguage::get_keyword_list(List<String> *r_keywords) {
Set<String> kws;
int idx = 0;
while (keyword_list[idx].text) {
kws.insert(keyword_list[idx].text);
idx++;
}
idx = 0;
while (builtin_func_defs[idx].name) {
kws.insert(builtin_func_defs[idx].name);
idx++;
}
for (Set<String>::Element *E = kws.front(); E; E = E->next()) {
r_keywords->push_back(E->get());
}
}
void ShaderLanguage::get_builtin_funcs(List<String> *r_keywords) {
Set<String> kws;
int idx = 0;
while (builtin_func_defs[idx].name) {
kws.insert(builtin_func_defs[idx].name);
idx++;
}
for (Set<String>::Element *E = kws.front(); E; E = E->next()) {
r_keywords->push_back(E->get());
}
}
ShaderLanguage::DataType ShaderLanguage::get_scalar_type(DataType p_type) {
static const DataType scalar_types[] = {
TYPE_VOID,
TYPE_BOOL,
TYPE_BOOL,
TYPE_BOOL,
TYPE_BOOL,
TYPE_INT,
TYPE_INT,
TYPE_INT,
TYPE_INT,
TYPE_UINT,
TYPE_UINT,
TYPE_UINT,
TYPE_UINT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_FLOAT,
TYPE_INT,
TYPE_UINT,
TYPE_FLOAT,
};
return scalar_types[p_type];
}
int ShaderLanguage::get_cardinality(DataType p_type) {
static const int cardinality_table[] = {
0,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
4,
9,
16,
1,
1,
1,
1,
};
return cardinality_table[p_type];
}
bool ShaderLanguage::_get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier) {
identifier = StringName();
TkPos pos = { 0, 0 };
Token tk = _get_token();
if (tk.type == TK_IDENTIFIER) {
identifier = tk.text;
pos = _get_tkpos();
tk = _get_token();
}
if (tk.type == TK_CURSOR) {
completion_type = p_type;
completion_line = tk_line;
completion_block = p_block;
pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_IDENTIFIER) {
identifier = identifier.operator String() + tk.text.operator String();
} else {
_set_tkpos(pos);
}
return true;
} else if (identifier != StringName()) {
_set_tkpos(pos);
}
return false;
}
bool ShaderLanguage::_is_operator_assign(Operator p_op) const {
switch (p_op) {
case OP_ASSIGN:
case OP_ASSIGN_ADD:
case OP_ASSIGN_SUB:
case OP_ASSIGN_MUL:
case OP_ASSIGN_DIV:
case OP_ASSIGN_MOD:
case OP_ASSIGN_SHIFT_LEFT:
case OP_ASSIGN_SHIFT_RIGHT:
case OP_ASSIGN_BIT_AND:
case OP_ASSIGN_BIT_OR:
case OP_ASSIGN_BIT_XOR:
return true;
default:
return false;
}
return false;
}
bool ShaderLanguage::_validate_assign(Node *p_node, const Map<StringName, BuiltInInfo> &p_builtin_types, String *r_message) {
if (p_node->type == Node::TYPE_OPERATOR) {
OperatorNode *op = static_cast<OperatorNode *>(p_node);
if (op->op == OP_INDEX) {
return _validate_assign(op->arguments[0], p_builtin_types, r_message);
} else if (_is_operator_assign(op->op)) {
//chained assignment
return _validate_assign(op->arguments[1], p_builtin_types, r_message);
} else if (op->op == OP_CALL) {
if (r_message)
*r_message = RTR("Assignment to function.");
return false;
}
} else if (p_node->type == Node::TYPE_MEMBER) {
MemberNode *member = static_cast<MemberNode *>(p_node);
if (member->has_swizzling_duplicates) {
if (r_message)
*r_message = RTR("Swizzling assignment contains duplicates.");
return false;
}
return _validate_assign(member->owner, p_builtin_types, r_message);
} else if (p_node->type == Node::TYPE_VARIABLE) {
VariableNode *var = static_cast<VariableNode *>(p_node);
if (shader->uniforms.has(var->name)) {
if (r_message)
*r_message = RTR("Assignment to uniform.");
return false;
}
if (shader->varyings.has(var->name) && current_function != String("vertex")) {
if (r_message)
*r_message = RTR("Varyings can only be assigned in vertex function.");
return false;
}
if (shader->constants.has(var->name) || var->is_const) {
if (r_message)
*r_message = RTR("Constants cannot be modified.");
return false;
}
if (!(p_builtin_types.has(var->name) && p_builtin_types[var->name].constant)) {
return true;
}
} else if (p_node->type == Node::TYPE_ARRAY) {
ArrayNode *arr = static_cast<ArrayNode *>(p_node);
if (arr->is_const) {
if (r_message)
*r_message = RTR("Constants cannot be modified.");
return false;
}
if (shader->varyings.has(arr->name) && current_function != String("vertex")) {
if (r_message)
*r_message = RTR("Varyings can only be assigned in vertex function.");
return false;
}
return true;
}
if (r_message)
*r_message = "Assignment to constant expression.";
return false;
}
bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat) {
for (int i = 0; shader->functions.size(); i++) {
if (shader->functions[i].name == p_name) {
ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
if (arg->tex_builtin_check) {
_set_error("Sampler argument #" + itos(p_argument) + " of function '" + String(p_name) + "' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other).");
return false;
} else if (arg->tex_argument_check) {
//was checked, verify that filter and repeat are the same
if (arg->tex_argument_filter == p_filter && arg->tex_argument_repeat == p_repeat) {
return true;
} else {
_set_error("Sampler argument #" + itos(p_argument) + " of function '" + String(p_name) + "' called more than once using textures that differ in either filter or repeat setting.");
return false;
}
} else {
arg->tex_argument_check = true;
arg->tex_argument_filter = p_filter;
arg->tex_argument_repeat = p_repeat;
for (Map<StringName, Set<int> >::Element *E = arg->tex_argument_connect.front(); E; E = E->next()) {
for (Set<int>::Element *F = E->get().front(); F; F = F->next()) {
if (!_propagate_function_call_sampler_uniform_settings(E->key(), F->get(), p_filter, p_repeat)) {
return false;
}
}
}
return true;
}
}
}
ERR_FAIL_V(false); //bug? function not found
}
bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin) {
for (int i = 0; shader->functions.size(); i++) {
if (shader->functions[i].name == p_name) {
ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
if (arg->tex_argument_check) {
_set_error("Sampler argument #" + itos(p_argument) + " of function '" + String(p_name) + "' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other).");
return false;
} else if (arg->tex_builtin_check) {
//was checked, verify that the built-in is the same
if (arg->tex_builtin == p_builtin) {
return true;
} else {
_set_error("Sampler argument #" + itos(p_argument) + " of function '" + String(p_name) + "' called more than once using different built-ins. Only calling with the same built-in is supported.");
return false;
}
} else {
arg->tex_builtin_check = true;
arg->tex_builtin = p_builtin;
for (Map<StringName, Set<int> >::Element *E = arg->tex_argument_connect.front(); E; E = E->next()) {
for (Set<int>::Element *F = E->get().front(); F; F = F->next()) {
if (!_propagate_function_call_sampler_builtin_reference(E->key(), F->get(), p_builtin)) {
return false;
}
}
}
return true;
}
}
}
ERR_FAIL_V(false); //bug? function not found
}
ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types) {
Vector<Expression> expression;
//Vector<TokenType> operators;
while (true) {
Node *expr = NULL;
TkPos prepos = _get_tkpos();
Token tk = _get_token();
TkPos pos = _get_tkpos();
bool is_const = false;
if (tk.type == TK_PARENTHESIS_OPEN) {
//handle subexpression
expr = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!expr)
return NULL;
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')' in expression");
return NULL;
}
} else if (tk.type == TK_REAL_CONSTANT) {
ConstantNode *constant = alloc_node<ConstantNode>();
ConstantNode::Value v;
v.real = tk.constant;
constant->values.push_back(v);
constant->datatype = TYPE_FLOAT;
expr = constant;
} else if (tk.type == TK_INT_CONSTANT) {
ConstantNode *constant = alloc_node<ConstantNode>();
ConstantNode::Value v;
v.sint = tk.constant;
constant->values.push_back(v);
constant->datatype = TYPE_INT;
expr = constant;
} else if (tk.type == TK_TRUE) {
//handle true constant
ConstantNode *constant = alloc_node<ConstantNode>();
ConstantNode::Value v;
v.boolean = true;
constant->values.push_back(v);
constant->datatype = TYPE_BOOL;
expr = constant;
} else if (tk.type == TK_FALSE) {
//handle false constant
ConstantNode *constant = alloc_node<ConstantNode>();
ConstantNode::Value v;
v.boolean = false;
constant->values.push_back(v);
constant->datatype = TYPE_BOOL;
expr = constant;
} else if (tk.type == TK_TYPE_VOID) {
//make sure void is not used in expression
_set_error("Void value not allowed in Expression");
return NULL;
} else if (is_token_nonvoid_datatype(tk.type)) {
//basic type constructor
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_CONSTRUCT;
if (is_token_precision(tk.type)) {
func->return_precision_cache = get_token_precision(tk.type);
tk = _get_token();
}
VariableNode *funcname = alloc_node<VariableNode>();
funcname->name = get_datatype_name(get_token_datatype(tk.type));
func->arguments.push_back(funcname);
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after type name");
return NULL;
}
int carg = -1;
bool ok = _parse_function_arguments(p_block, p_builtin_types, func, &carg);
if (carg >= 0) {
completion_type = COMPLETION_CALL_ARGUMENTS;
completion_line = tk_line;
completion_block = p_block;
completion_function = funcname->name;
completion_argument = carg;
}
if (!ok)
return NULL;
if (!_validate_function_call(p_block, func, &func->return_cache, &func->struct_name)) {
_set_error("No matching constructor found for: '" + String(funcname->name) + "'");
return NULL;
}
expr = _reduce_expression(p_block, func);
} else if (tk.type == TK_IDENTIFIER) {
_set_tkpos(prepos);
StringName identifier;
StructNode *pstruct = NULL;
bool struct_init = false;
_get_completable_identifier(p_block, COMPLETION_IDENTIFIER, identifier);
if (shader->structs.has(identifier)) {
pstruct = shader->structs[identifier].shader_struct;
struct_init = true;
}
tk = _get_token();
if (tk.type == TK_PARENTHESIS_OPEN) {
if (struct_init) { //a struct constructor
const StringName &name = identifier;
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_STRUCT;
func->struct_name = name;
func->return_cache = TYPE_STRUCT;
VariableNode *funcname = alloc_node<VariableNode>();
funcname->name = name;
func->arguments.push_back(funcname);
for (int i = 0; i < pstruct->members.size(); i++) {
Node *nexpr;
if (pstruct->members[i]->array_size != 0) {
DataType type = pstruct->members[i]->get_datatype();
String struct_name = pstruct->members[i]->struct_name;
int array_size = pstruct->members[i]->array_size;
DataType type2;
String struct_name2 = "";
int array_size2 = 0;
bool auto_size = false;
tk = _get_token();
if (tk.type == TK_CURLY_BRACKET_OPEN) {
auto_size = true;
} else {
if (shader->structs.has(tk.text)) {
type2 = TYPE_STRUCT;
struct_name2 = tk.text;
} else {
if (!is_token_variable_datatype(tk.type)) {
_set_error("Invalid data type for array");
return NULL;
}
type2 = get_token_datatype(tk.type);
}
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
TkPos pos2 = _get_tkpos();
tk = _get_token();
if (tk.type == TK_BRACKET_CLOSE) {
array_size2 = array_size;
tk = _get_token();
} else {
_set_tkpos(pos2);
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n || n->type != Node::TYPE_CONSTANT || n->get_datatype() != TYPE_INT) {
_set_error("Expected single integer constant > 0");
return NULL;
}
ConstantNode *cnode = (ConstantNode *)n;
if (cnode->values.size() == 1) {
array_size2 = cnode->values[0].sint;
if (array_size2 <= 0) {
_set_error("Expected single integer constant > 0");
return NULL;
}
} else {
_set_error("Expected single integer constant > 0");
return NULL;
}
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']'");
return NULL;
} else {
tk = _get_token();
}
}
} else {
_set_error("Expected '['");
return NULL;
}
if (type != type2 || struct_name != struct_name2 || array_size != array_size2) {
String error_str = "Cannot convert from '";
if (type2 == TYPE_STRUCT) {
error_str += struct_name2;
} else {
error_str += get_datatype_name(type2);
}
error_str += "[";
error_str += itos(array_size2);
error_str += "]'";
error_str += " to '";
if (type == TYPE_STRUCT) {
error_str += struct_name;
} else {
error_str += get_datatype_name(type);
}
error_str += "[";
error_str += itos(array_size);
error_str += "]'";
_set_error(error_str);
return NULL;
}
}
ArrayConstructNode *an = alloc_node<ArrayConstructNode>();
an->datatype = type;
an->struct_name = struct_name;
if (tk.type == TK_PARENTHESIS_OPEN || auto_size) { // initialization
while (true) {
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n) {
return NULL;
}
if (type != n->get_datatype() || struct_name != n->get_datatype_name()) {
_set_error("Invalid assignment of '" + (n->get_datatype() == TYPE_STRUCT ? n->get_datatype_name() : get_datatype_name(n->get_datatype())) + "' to '" + (type == TYPE_STRUCT ? struct_name : get_datatype_name(type)) + "'");
return NULL;
}
tk = _get_token();
if (tk.type == TK_COMMA) {
an->initializer.push_back(n);
continue;
} else if (!auto_size && tk.type == TK_PARENTHESIS_CLOSE) {
an->initializer.push_back(n);
break;
} else if (auto_size && tk.type == TK_CURLY_BRACKET_CLOSE) {
an->initializer.push_back(n);
break;
} else {
if (auto_size)
_set_error("Expected '}' or ','");
else
_set_error("Expected ')' or ','");
return NULL;
}
}
if (an->initializer.size() != array_size) {
_set_error("Array size mismatch");
return NULL;
}
} else {
_set_error("Expected array initialization!");
return NULL;
}
nexpr = an;
} else {
nexpr = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!nexpr) {
return NULL;
}
Node *node = pstruct->members[i];
if (!_compare_datatypes_in_nodes(pstruct->members[i], nexpr)) {
String type_name = nexpr->get_datatype() == TYPE_STRUCT ? nexpr->get_datatype_name() : get_datatype_name(nexpr->get_datatype());
String type_name2 = node->get_datatype() == TYPE_STRUCT ? node->get_datatype_name() : get_datatype_name(node->get_datatype());
_set_error("Invalid assignment of '" + type_name + "' to '" + type_name2 + "'");
return NULL;
}
}
if (i + 1 < pstruct->members.size()) {
tk = _get_token();
if (tk.type != TK_COMMA) {
_set_error("Expected ','");
return NULL;
}
}
func->arguments.push_back(nexpr);
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')'");
return NULL;
}
expr = func;
} else { //a function
const StringName &name = identifier;
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_CALL;
VariableNode *funcname = alloc_node<VariableNode>();
funcname->name = name;
func->arguments.push_back(funcname);
int carg = -1;
bool ok = _parse_function_arguments(p_block, p_builtin_types, func, &carg);
// Check if block has a variable with the same name as function to prevent shader crash.
ShaderLanguage::BlockNode *bnode = p_block;
while (bnode) {
if (bnode->variables.has(name)) {
_set_error("Expected function name");
return NULL;
}
bnode = bnode->parent_block;
}
//test if function was parsed first
int function_index = -1;
for (int i = 0; i < shader->functions.size(); i++) {
if (shader->functions[i].name == name) {
//add to current function as dependency
for (int j = 0; j < shader->functions.size(); j++) {
if (shader->functions[j].name == current_function) {
shader->functions.write[j].uses_function.insert(name);
break;
}
}
//see if texture arguments must connect
function_index = i;
break;
}
}
if (carg >= 0) {
completion_type = COMPLETION_CALL_ARGUMENTS;
completion_line = tk_line;
completion_block = p_block;
completion_function = funcname->name;
completion_argument = carg;
}
if (!ok)
return NULL;
if (!_validate_function_call(p_block, func, &func->return_cache, &func->struct_name)) {
_set_error("No matching function found for: '" + String(funcname->name) + "'");
return NULL;
}
completion_class = TAG_GLOBAL; // reset sub-class
if (function_index >= 0) {
//connect texture arguments, so we can cache in the
//argument what type of filter and repeat to use
FunctionNode *call_function = shader->functions[function_index].function;
if (call_function) {
//get current base function
FunctionNode *base_function = NULL;
{
BlockNode *b = p_block;
while (b) {
if (b->parent_function) {
base_function = b->parent_function;
break;
} else {
b = b->parent_block;
}
}
}
ERR_FAIL_COND_V(!base_function, NULL); //bug, wtf
for (int i = 0; i < call_function->arguments.size(); i++) {
int argidx = i + 1;
if (argidx < func->arguments.size()) {
if (call_function->arguments[i].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT || call_function->arguments[i].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT) {
bool error = false;
Node *n = func->arguments[argidx];
if (n->type == Node::TYPE_CONSTANT || n->type == Node::TYPE_OPERATOR) {
error = true;
} else if (n->type == Node::TYPE_ARRAY) {
ArrayNode *an = static_cast<ArrayNode *>(n);
if (an->call_expression != NULL) {
error = true;
}
} else if (n->type == Node::TYPE_VARIABLE) {
VariableNode *vn = static_cast<VariableNode *>(n);
if (vn->is_const) {
error = true;
} else {
StringName varname = vn->name;
if (shader->uniforms.has(varname)) {
error = true;
} else {
if (p_builtin_types.has(varname)) {
BuiltInInfo info = p_builtin_types[varname];
if (info.constant) {
error = true;
}
}
}
}
} else if (n->type == Node::TYPE_MEMBER) {
MemberNode *mn = static_cast<MemberNode *>(n);
if (mn->basetype_const) {
error = true;
}
}
if (error) {
_set_error(vformat("Constant value cannot be passed for '%s' parameter!", _get_qualifier_str(call_function->arguments[i].qualifier)));
return NULL;
}
}
if (is_sampler_type(call_function->arguments[i].type)) {
//let's see where our argument comes from
Node *n = func->arguments[argidx];
ERR_CONTINUE(n->type != Node::TYPE_VARIABLE); //bug? this should always be a variable
VariableNode *vn = static_cast<VariableNode *>(n);
StringName varname = vn->name;
if (shader->uniforms.has(varname)) {
//being sampler, this either comes from a uniform
ShaderNode::Uniform *u = &shader->uniforms[varname];
ERR_CONTINUE(u->type != call_function->arguments[i].type); //this should have been validated previously
//propagate
if (!_propagate_function_call_sampler_uniform_settings(name, i, u->filter, u->repeat)) {
return NULL;
}
} else if (p_builtin_types.has(varname)) {
//a built-in
if (!_propagate_function_call_sampler_builtin_reference(name, i, varname)) {
return NULL;
}
} else {
//or this comes from an argument, but nothing else can be a sampler
bool found = false;
for (int j = 0; j < base_function->arguments.size(); j++) {
if (base_function->arguments[j].name == varname) {
if (!base_function->arguments[j].tex_argument_connect.has(call_function->name)) {
base_function->arguments.write[j].tex_argument_connect[call_function->name] = Set<int>();
}
base_function->arguments.write[j].tex_argument_connect[call_function->name].insert(i);
found = true;
break;
}
}
ERR_CONTINUE(!found);
}
}
} else {
break;
}
}
}
}
expr = func;
}
} else {
//an identifier
_set_tkpos(pos);
DataType data_type;
IdentifierType ident_type;
int array_size = 0;
StringName struct_name;
if (p_block && p_block->block_tag != SubClassTag::TAG_GLOBAL) {
int idx = 0;
bool found = false;
while (builtin_func_defs[idx].name) {
if (builtin_func_defs[idx].tag == p_block->block_tag && builtin_func_defs[idx].name == identifier) {
found = true;
break;
}
idx++;
}
if (!found) {
_set_error("Unknown identifier in expression: " + String(identifier));
return NULL;
}
} else {
if (!_find_identifier(p_block, p_builtin_types, identifier, &data_type, &ident_type, &is_const, &array_size, &struct_name)) {
_set_error("Unknown identifier in expression: " + String(identifier));
return NULL;
}
if (ident_type == IDENTIFIER_FUNCTION) {
_set_error("Can't use function as identifier: " + String(identifier));
return NULL;
}
}
Node *index_expression = NULL;
Node *call_expression = NULL;
if (array_size > 0) {
tk = _get_token();
if (tk.type != TK_BRACKET_OPEN && tk.type != TK_PERIOD) {
_set_error("Expected '[' or '.'");
return NULL;
}
if (tk.type == TK_PERIOD) {
completion_class = TAG_ARRAY;
p_block->block_tag = SubClassTag::TAG_ARRAY;
call_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
p_block->block_tag = SubClassTag::TAG_GLOBAL;
if (!call_expression)
return NULL;
data_type = call_expression->get_datatype();
} else { // indexing
index_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!index_expression)
return NULL;
if (index_expression->get_datatype() != TYPE_INT && index_expression->get_datatype() != TYPE_UINT) {
_set_error("Only integer expressions are allowed for indexing");
return NULL;
}
if (index_expression->type == Node::TYPE_CONSTANT) {
ConstantNode *cnode = (ConstantNode *)index_expression;
if (cnode) {
if (!cnode->values.empty()) {
int value = cnode->values[0].sint;
if (value < 0 || value >= array_size) {
_set_error(vformat("Index [%s] out of range [%s..%s]", value, 0, array_size - 1));
return NULL;
}
}
}
}
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']'");
return NULL;
}
}
ArrayNode *arrname = alloc_node<ArrayNode>();
arrname->name = identifier;
arrname->datatype_cache = data_type;
arrname->struct_name = struct_name;
arrname->index_expression = index_expression;
arrname->call_expression = call_expression;
arrname->is_const = is_const;
expr = arrname;
} else {
VariableNode *varname = alloc_node<VariableNode>();
varname->name = identifier;
varname->datatype_cache = data_type;
varname->is_const = is_const;
varname->struct_name = struct_name;
expr = varname;
}
}
} else if (tk.type == TK_OP_ADD) {
continue; //this one does nothing
} else if (tk.type == TK_OP_SUB || tk.type == TK_OP_NOT || tk.type == TK_OP_BIT_INVERT || tk.type == TK_OP_INCREMENT || tk.type == TK_OP_DECREMENT) {
Expression e;
e.is_op = true;
switch (tk.type) {
case TK_OP_SUB: e.op = OP_NEGATE; break;
case TK_OP_NOT: e.op = OP_NOT; break;
case TK_OP_BIT_INVERT: e.op = OP_BIT_INVERT; break;
case TK_OP_INCREMENT: e.op = OP_INCREMENT; break;
case TK_OP_DECREMENT: e.op = OP_DECREMENT; break;
default: ERR_FAIL_V(NULL);
}
expression.push_back(e);
continue;
} else {
_set_error("Expected expression, found: " + get_token_text(tk));
return NULL;
//nothing
}
ERR_FAIL_COND_V(!expr, NULL);
/* OK now see what's NEXT to the operator.. */
/* OK now see what's NEXT to the operator.. */
/* OK now see what's NEXT to the operator.. */
while (true) {
TkPos pos2 = _get_tkpos();
tk = _get_token();
if (tk.type == TK_CURSOR) {
//do nothing
} else if (tk.type == TK_IDENTIFIER) {
} else if (tk.type == TK_PERIOD) {
DataType dt = expr->get_datatype();
String st = expr->get_datatype_name();
StringName identifier;
if (_get_completable_identifier(p_block, dt == TYPE_STRUCT ? COMPLETION_STRUCT : COMPLETION_INDEX, identifier)) {
if (dt == TYPE_STRUCT) {
completion_struct = st;
} else {
completion_base = dt;
}
}
if (identifier == StringName()) {
_set_error("Expected identifier as member");
return NULL;
}
String ident = identifier;
bool ok = true;
bool repeated = false;
DataType member_type = TYPE_VOID;
StringName member_struct_name = "";
int array_size = 0;
Set<char> position_symbols;
Set<char> color_symbols;
Set<char> texture_symbols;
bool mix_error = false;
switch (dt) {
case TYPE_STRUCT: {
ok = false;
String member_name = String(ident.ptr());
if (shader->structs.has(st)) {
StructNode *n = shader->structs[st].shader_struct;
for (List<MemberNode *>::Element *E = n->members.front(); E; E = E->next()) {
if (String(E->get()->name) == member_name) {
member_type = E->get()->datatype;
array_size = E->get()->array_size;
if (member_type == TYPE_STRUCT) {
member_struct_name = E->get()->struct_name;
}
ok = true;
break;
}
}
}
} break;
case TYPE_BVEC2:
case TYPE_IVEC2:
case TYPE_UVEC2:
case TYPE_VEC2: {
int l = ident.length();
if (l == 1) {
member_type = DataType(dt - 1);
} else if (l == 2) {
member_type = dt;
} else if (l == 3) {
member_type = DataType(dt + 1);
} else if (l == 4) {
member_type = DataType(dt + 2);
} else {
ok = false;
break;
}
const CharType *c = ident.ptr();
for (int i = 0; i < l; i++) {
switch (c[i]) {
case 'r':
case 'g':
if (position_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!color_symbols.has(c[i])) {
color_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 'x':
case 'y':
if (color_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!position_symbols.has(c[i])) {
position_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 's':
case 't':
if (color_symbols.size() > 0 || position_symbols.size() > 0) {
mix_error = true;
break;
}
if (!texture_symbols.has(c[i])) {
texture_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
default:
ok = false;
break;
}
}
} break;
case TYPE_BVEC3:
case TYPE_IVEC3:
case TYPE_UVEC3:
case TYPE_VEC3: {
int l = ident.length();
if (l == 1) {
member_type = DataType(dt - 2);
} else if (l == 2) {
member_type = DataType(dt - 1);
} else if (l == 3) {
member_type = dt;
} else if (l == 4) {
member_type = DataType(dt + 1);
} else {
ok = false;
break;
}
const CharType *c = ident.ptr();
for (int i = 0; i < l; i++) {
switch (c[i]) {
case 'r':
case 'g':
case 'b':
if (position_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!color_symbols.has(c[i])) {
color_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 'x':
case 'y':
case 'z':
if (color_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!position_symbols.has(c[i])) {
position_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 's':
case 't':
case 'p':
if (color_symbols.size() > 0 || position_symbols.size() > 0) {
mix_error = true;
break;
}
if (!texture_symbols.has(c[i])) {
texture_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
default:
ok = false;
break;
}
}
} break;
case TYPE_BVEC4:
case TYPE_IVEC4:
case TYPE_UVEC4:
case TYPE_VEC4: {
int l = ident.length();
if (l == 1) {
member_type = DataType(dt - 3);
} else if (l == 2) {
member_type = DataType(dt - 2);
} else if (l == 3) {
member_type = DataType(dt - 1);
} else if (l == 4) {
member_type = dt;
} else {
ok = false;
break;
}
const CharType *c = ident.ptr();
for (int i = 0; i < l; i++) {
switch (c[i]) {
case 'r':
case 'g':
case 'b':
case 'a':
if (position_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!color_symbols.has(c[i])) {
color_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 'x':
case 'y':
case 'z':
case 'w':
if (color_symbols.size() > 0 || texture_symbols.size() > 0) {
mix_error = true;
break;
}
if (!position_symbols.has(c[i])) {
position_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
case 's':
case 't':
case 'p':
case 'q':
if (color_symbols.size() > 0 || position_symbols.size() > 0) {
mix_error = true;
break;
}
if (!texture_symbols.has(c[i])) {
texture_symbols.insert(c[i]);
} else {
repeated = true;
}
break;
default:
ok = false;
break;
}
}
} break;
default: {
ok = false;
}
}
if (mix_error) {
_set_error("Cannot combine symbols from different sets in expression ." + ident);
return NULL;
}
if (!ok) {
_set_error("Invalid member for " + (dt == TYPE_STRUCT ? st : get_datatype_name(dt)) + " expression: ." + ident);
return NULL;
}
MemberNode *mn = alloc_node<MemberNode>();
mn->basetype = dt;
mn->basetype_const = is_const;
mn->datatype = member_type;
mn->base_struct_name = st;
mn->struct_name = member_struct_name;
mn->array_size = array_size;
mn->name = ident;
mn->owner = expr;
mn->has_swizzling_duplicates = repeated;
if (array_size > 0) {
tk = _get_token();
if (tk.type == TK_PERIOD) {
_set_error("Nested array length() is not yet implemented");
return NULL;
} else if (tk.type == TK_BRACKET_OPEN) {
Node *index_expression = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!index_expression)
return NULL;
if (index_expression->get_datatype() != TYPE_INT && index_expression->get_datatype() != TYPE_UINT) {
_set_error("Only integer expressions are allowed for indexing");
return NULL;
}
if (index_expression->type == Node::TYPE_CONSTANT) {
ConstantNode *cnode = (ConstantNode *)index_expression;
if (cnode) {
if (!cnode->values.empty()) {
int value = cnode->values[0].sint;
if (value < 0 || value >= array_size) {
_set_error(vformat("Index [%s] out of range [%s..%s]", value, 0, array_size - 1));
return NULL;
}
}
}
}
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']'");
return NULL;
}
mn->index_expression = index_expression;
} else {
_set_error("Expected '[' or '.'");
return NULL;
}
}
expr = mn;
//todo
//member (period) has priority over any operator
//creates a subindexing expression in place
/*} else if (tk.type==TK_BRACKET_OPEN) {
//todo
//subindexing has priority over any operator
//creates a subindexing expression in place
*/
} else if (tk.type == TK_BRACKET_OPEN) {
Node *index = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!index)
return NULL;
if (index->get_datatype() != TYPE_INT && index->get_datatype() != TYPE_UINT) {
_set_error("Only integer datatypes are allowed for indexing");
return NULL;
}
DataType member_type = TYPE_VOID;
switch (expr->get_datatype()) {
case TYPE_BVEC2:
case TYPE_VEC2:
case TYPE_IVEC2:
case TYPE_UVEC2:
case TYPE_MAT2:
if (index->type == Node::TYPE_CONSTANT) {
uint32_t index_constant = static_cast<ConstantNode *>(index)->values[0].uint;
if (index_constant >= 2) {
_set_error("Index out of range (0-1)");
return NULL;
}
}
switch (expr->get_datatype()) {
case TYPE_BVEC2: member_type = TYPE_BOOL; break;
case TYPE_VEC2: member_type = TYPE_FLOAT; break;
case TYPE_IVEC2: member_type = TYPE_INT; break;
case TYPE_UVEC2: member_type = TYPE_UINT; break;
case TYPE_MAT2: member_type = TYPE_VEC2; break;
default: break;
}
break;
case TYPE_BVEC3:
case TYPE_VEC3:
case TYPE_IVEC3:
case TYPE_UVEC3:
case TYPE_MAT3:
if (index->type == Node::TYPE_CONSTANT) {
uint32_t index_constant = static_cast<ConstantNode *>(index)->values[0].uint;
if (index_constant >= 3) {
_set_error("Index out of range (0-2)");
return NULL;
}
}
switch (expr->get_datatype()) {
case TYPE_BVEC3: member_type = TYPE_BOOL; break;
case TYPE_VEC3: member_type = TYPE_FLOAT; break;
case TYPE_IVEC3: member_type = TYPE_INT; break;
case TYPE_UVEC3: member_type = TYPE_UINT; break;
case TYPE_MAT3: member_type = TYPE_VEC3; break;
default: break;
}
break;
case TYPE_BVEC4:
case TYPE_VEC4:
case TYPE_IVEC4:
case TYPE_UVEC4:
case TYPE_MAT4:
if (index->type == Node::TYPE_CONSTANT) {
uint32_t index_constant = static_cast<ConstantNode *>(index)->values[0].uint;
if (index_constant >= 4) {
_set_error("Index out of range (0-3)");
return NULL;
}
}
switch (expr->get_datatype()) {
case TYPE_BVEC4: member_type = TYPE_BOOL; break;
case TYPE_VEC4: member_type = TYPE_FLOAT; break;
case TYPE_IVEC4: member_type = TYPE_INT; break;
case TYPE_UVEC4: member_type = TYPE_UINT; break;
case TYPE_MAT4: member_type = TYPE_VEC4; break;
default: break;
}
break;
default: {
_set_error("Object of type '" + (expr->get_datatype() == TYPE_STRUCT ? expr->get_datatype_name() : get_datatype_name(expr->get_datatype())) + "' can't be indexed");
return NULL;
}
}
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OP_INDEX;
op->return_cache = member_type;
op->arguments.push_back(expr);
op->arguments.push_back(index);
expr = op;
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']' after indexing expression");
return NULL;
}
} else if (tk.type == TK_OP_INCREMENT || tk.type == TK_OP_DECREMENT) {
OperatorNode *op = alloc_node<OperatorNode>();
op->op = tk.type == TK_OP_DECREMENT ? OP_POST_DECREMENT : OP_POST_INCREMENT;
op->arguments.push_back(expr);
if (!_validate_operator(op, &op->return_cache)) {
_set_error("Invalid base type for increment/decrement operator");
return NULL;
}
if (!_validate_assign(expr, p_builtin_types)) {
_set_error("Invalid use of increment/decrement operator in constant expression.");
return NULL;
}
expr = op;
} else {
_set_tkpos(pos2);
break;
}
}
Expression e;
e.is_op = false;
e.node = expr;
expression.push_back(e);
pos = _get_tkpos();
tk = _get_token();
if (is_token_operator(tk.type)) {
Expression o;
o.is_op = true;
switch (tk.type) {
case TK_OP_EQUAL: o.op = OP_EQUAL; break;
case TK_OP_NOT_EQUAL: o.op = OP_NOT_EQUAL; break;
case TK_OP_LESS: o.op = OP_LESS; break;
case TK_OP_LESS_EQUAL: o.op = OP_LESS_EQUAL; break;
case TK_OP_GREATER: o.op = OP_GREATER; break;
case TK_OP_GREATER_EQUAL: o.op = OP_GREATER_EQUAL; break;
case TK_OP_AND: o.op = OP_AND; break;
case TK_OP_OR: o.op = OP_OR; break;
case TK_OP_ADD: o.op = OP_ADD; break;
case TK_OP_SUB: o.op = OP_SUB; break;
case TK_OP_MUL: o.op = OP_MUL; break;
case TK_OP_DIV: o.op = OP_DIV; break;
case TK_OP_MOD: o.op = OP_MOD; break;
case TK_OP_SHIFT_LEFT: o.op = OP_SHIFT_LEFT; break;
case TK_OP_SHIFT_RIGHT: o.op = OP_SHIFT_RIGHT; break;
case TK_OP_ASSIGN: o.op = OP_ASSIGN; break;
case TK_OP_ASSIGN_ADD: o.op = OP_ASSIGN_ADD; break;
case TK_OP_ASSIGN_SUB: o.op = OP_ASSIGN_SUB; break;
case TK_OP_ASSIGN_MUL: o.op = OP_ASSIGN_MUL; break;
case TK_OP_ASSIGN_DIV: o.op = OP_ASSIGN_DIV; break;
case TK_OP_ASSIGN_MOD: o.op = OP_ASSIGN_MOD; break;
case TK_OP_ASSIGN_SHIFT_LEFT: o.op = OP_ASSIGN_SHIFT_LEFT; break;
case TK_OP_ASSIGN_SHIFT_RIGHT: o.op = OP_ASSIGN_SHIFT_RIGHT; break;
case TK_OP_ASSIGN_BIT_AND: o.op = OP_ASSIGN_BIT_AND; break;
case TK_OP_ASSIGN_BIT_OR: o.op = OP_ASSIGN_BIT_OR; break;
case TK_OP_ASSIGN_BIT_XOR: o.op = OP_ASSIGN_BIT_XOR; break;
case TK_OP_BIT_AND: o.op = OP_BIT_AND; break;
case TK_OP_BIT_OR: o.op = OP_BIT_OR; break;
case TK_OP_BIT_XOR: o.op = OP_BIT_XOR; break;
case TK_QUESTION: o.op = OP_SELECT_IF; break;
case TK_COLON: o.op = OP_SELECT_ELSE; break;
default: {
_set_error("Invalid token for operator: " + get_token_text(tk));
return NULL;
}
}
expression.push_back(o);
} else {
_set_tkpos(pos); //something else, so rollback and end
break;
}
}
/* Reduce the set set of expressions and place them in an operator tree, respecting precedence */
while (expression.size() > 1) {
int next_op = -1;
int min_priority = 0xFFFFF;
bool is_unary = false;
bool is_ternary = false;
for (int i = 0; i < expression.size(); i++) {
if (!expression[i].is_op) {
continue;
}
bool unary = false;
bool ternary = false;
int priority;
switch (expression[i].op) {
case OP_EQUAL: priority = 8; break;
case OP_NOT_EQUAL: priority = 8; break;
case OP_LESS: priority = 7; break;
case OP_LESS_EQUAL: priority = 7; break;
case OP_GREATER: priority = 7; break;
case OP_GREATER_EQUAL: priority = 7; break;
case OP_AND: priority = 12; break;
case OP_OR: priority = 14; break;
case OP_NOT:
priority = 3;
unary = true;
break;
case OP_NEGATE:
priority = 3;
unary = true;
break;
case OP_ADD: priority = 5; break;
case OP_SUB: priority = 5; break;
case OP_MUL: priority = 4; break;
case OP_DIV: priority = 4; break;
case OP_MOD: priority = 4; break;
case OP_SHIFT_LEFT: priority = 6; break;
case OP_SHIFT_RIGHT: priority = 6; break;
case OP_ASSIGN: priority = 16; break;
case OP_ASSIGN_ADD: priority = 16; break;
case OP_ASSIGN_SUB: priority = 16; break;
case OP_ASSIGN_MUL: priority = 16; break;
case OP_ASSIGN_DIV: priority = 16; break;
case OP_ASSIGN_MOD: priority = 16; break;
case OP_ASSIGN_SHIFT_LEFT: priority = 16; break;
case OP_ASSIGN_SHIFT_RIGHT: priority = 16; break;
case OP_ASSIGN_BIT_AND: priority = 16; break;
case OP_ASSIGN_BIT_OR: priority = 16; break;
case OP_ASSIGN_BIT_XOR: priority = 16; break;
case OP_BIT_AND: priority = 9; break;
case OP_BIT_OR: priority = 11; break;
case OP_BIT_XOR: priority = 10; break;
case OP_BIT_INVERT:
priority = 3;
unary = true;
break;
case OP_INCREMENT:
priority = 3;
unary = true;
break;
case OP_DECREMENT:
priority = 3;
unary = true;
break;
case OP_SELECT_IF:
priority = 15;
ternary = true;
break;
case OP_SELECT_ELSE:
priority = 15;
ternary = true;
break;
default:
ERR_FAIL_V(NULL); //unexpected operator
}
if (priority < min_priority) {
// < is used for left to right (default)
// <= is used for right to left
next_op = i;
min_priority = priority;
is_unary = unary;
is_ternary = ternary;
}
}
ERR_FAIL_COND_V(next_op == -1, NULL);
// OK! create operator..
// OK! create operator..
if (is_unary) {
int expr_pos = next_op;
while (expression[expr_pos].is_op) {
expr_pos++;
if (expr_pos == expression.size()) {
//can happen..
_set_error("Unexpected end of expression...");
return NULL;
}
}
//consecutively do unary opeators
for (int i = expr_pos - 1; i >= next_op; i--) {
OperatorNode *op = alloc_node<OperatorNode>();
op->op = expression[i].op;
if ((op->op == OP_INCREMENT || op->op == OP_DECREMENT) && !_validate_assign(expression[i + 1].node, p_builtin_types)) {
_set_error("Can't use increment/decrement operator in constant expression.");
return NULL;
}
op->arguments.push_back(expression[i + 1].node);
expression.write[i].is_op = false;
expression.write[i].node = op;
if (!_validate_operator(op, &op->return_cache)) {
String at;
for (int j = 0; j < op->arguments.size(); j++) {
if (j > 0)
at += " and ";
at += get_datatype_name(op->arguments[j]->get_datatype());
}
_set_error("Invalid arguments to unary operator '" + get_operator_text(op->op) + "' :" + at);
return NULL;
}
expression.remove(i + 1);
}
} else if (is_ternary) {
if (next_op < 1 || next_op >= (expression.size() - 1)) {
_set_error("Parser bug...");
ERR_FAIL_V(NULL);
}
if (next_op + 2 >= expression.size() || !expression[next_op + 2].is_op || expression[next_op + 2].op != OP_SELECT_ELSE) {
_set_error("Missing matching ':' for select operator");
return NULL;
}
OperatorNode *op = alloc_node<OperatorNode>();
op->op = expression[next_op].op;
op->arguments.push_back(expression[next_op - 1].node);
op->arguments.push_back(expression[next_op + 1].node);
op->arguments.push_back(expression[next_op + 3].node);
expression.write[next_op - 1].is_op = false;
expression.write[next_op - 1].node = op;
if (!_validate_operator(op, &op->return_cache)) {
String at;
for (int i = 0; i < op->arguments.size(); i++) {
if (i > 0)
at += " and ";
at += get_datatype_name(op->arguments[i]->get_datatype());
}
_set_error("Invalid argument to ternary ?: operator: " + at);
return NULL;
}
for (int i = 0; i < 4; i++) {
expression.remove(next_op);
}
} else {
if (next_op < 1 || next_op >= (expression.size() - 1)) {
_set_error("Parser bug...");
ERR_FAIL_V(NULL);
}
OperatorNode *op = alloc_node<OperatorNode>();
op->op = expression[next_op].op;
if (expression[next_op - 1].is_op) {
_set_error("Parser bug...");
ERR_FAIL_V(NULL);
}
if (_is_operator_assign(op->op)) {
String assign_message;
if (!_validate_assign(expression[next_op - 1].node, p_builtin_types, &assign_message)) {
_set_error(assign_message);
return NULL;
}
}
if (expression[next_op + 1].is_op) {
// this is not invalid and can really appear
// but it becomes invalid anyway because no binary op
// can be followed by a unary op in a valid combination,
// due to how precedence works, unaries will always disappear first
_set_error("Parser bug...");
}
op->arguments.push_back(expression[next_op - 1].node); //expression goes as left
op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
expression.write[next_op - 1].node = op;
//replace all 3 nodes by this operator and make it an expression
if (!_validate_operator(op, &op->return_cache)) {
String at;
for (int i = 0; i < op->arguments.size(); i++) {
if (i > 0)
at += " and ";
if (op->arguments[i]->get_datatype() == TYPE_STRUCT) {
at += op->arguments[i]->get_datatype_name();
} else {
at += get_datatype_name(op->arguments[i]->get_datatype());
}
}
_set_error("Invalid arguments to operator '" + get_operator_text(op->op) + "' :" + at);
return NULL;
}
expression.remove(next_op);
expression.remove(next_op);
}
}
return expression[0].node;
}
ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, ShaderLanguage::Node *p_node) {
if (p_node->type != Node::TYPE_OPERATOR)
return p_node;
//for now only reduce simple constructors
OperatorNode *op = static_cast<OperatorNode *>(p_node);
if (op->op == OP_CONSTRUCT) {
ERR_FAIL_COND_V(op->arguments[0]->type != Node::TYPE_VARIABLE, p_node);
DataType type = op->get_datatype();
DataType base = get_scalar_type(type);
int cardinality = get_cardinality(type);
Vector<ConstantNode::Value> values;
for (int i = 1; i < op->arguments.size(); i++) {
op->arguments.write[i] = _reduce_expression(p_block, op->arguments[i]);
if (op->arguments[i]->type == Node::TYPE_CONSTANT) {
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i]);
if (get_scalar_type(cn->datatype) == base) {
for (int j = 0; j < cn->values.size(); j++) {
values.push_back(cn->values[j]);
}
} else if (get_scalar_type(cn->datatype) == cn->datatype) {
ConstantNode::Value v;
if (!convert_constant(cn, base, &v)) {
return p_node;
}
values.push_back(v);
} else {
return p_node;
}
} else {
return p_node;
}
}
if (values.size() == 1) {
if (type >= TYPE_MAT2 && type <= TYPE_MAT4) {
ConstantNode::Value value = values[0];
ConstantNode::Value zero;
zero.real = 0.0f;
int size = 2 + (type - TYPE_MAT2);
values.clear();
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
values.push_back(i == j ? value : zero);
}
}
} else {
ConstantNode::Value value = values[0];
for (int i = 1; i < cardinality; i++) {
values.push_back(value);
}
}
} else if (values.size() != cardinality) {
ERR_PRINT("Failed to reduce expression, values and cardinality mismatch.");
return p_node;
}
ConstantNode *cn = alloc_node<ConstantNode>();
cn->datatype = op->get_datatype();
cn->values = values;
return cn;
} else if (op->op == OP_NEGATE) {
op->arguments.write[0] = _reduce_expression(p_block, op->arguments[0]);
if (op->arguments[0]->type == Node::TYPE_CONSTANT) {
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[0]);
DataType base = get_scalar_type(cn->datatype);
Vector<ConstantNode::Value> values;
for (int i = 0; i < cn->values.size(); i++) {
ConstantNode::Value nv;
switch (base) {
case TYPE_BOOL: {
nv.boolean = !cn->values[i].boolean;
} break;
case TYPE_INT: {
nv.sint = -cn->values[i].sint;
} break;
case TYPE_UINT: {
// Intentionally wrap the unsigned int value, because GLSL does.
nv.uint = 0 - cn->values[i].uint;
} break;
case TYPE_FLOAT: {
nv.real = -cn->values[i].real;
} break;
default: {
}
}
values.push_back(nv);
}
cn->values = values;
return cn;
}
}
return p_node;
}
ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types) {
ShaderLanguage::Node *expr = _parse_expression(p_block, p_builtin_types);
if (!expr) //errored
return NULL;
expr = _reduce_expression(p_block, expr);
return expr;
}
Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, bool p_just_one, bool p_can_break, bool p_can_continue) {
while (true) {
TkPos pos = _get_tkpos();
Token tk = _get_token();
if (p_block && p_block->block_type == BlockNode::BLOCK_TYPE_SWITCH) {
if (tk.type != TK_CF_CASE && tk.type != TK_CF_DEFAULT && tk.type != TK_CURLY_BRACKET_CLOSE) {
_set_error("Switch may contains only case and default blocks");
return ERR_PARSE_ERROR;
}
}
bool is_struct = shader->structs.has(tk.text);
if (tk.type == TK_CURLY_BRACKET_CLOSE) { //end of block
if (p_just_one) {
_set_error("Unexpected '}'");
return ERR_PARSE_ERROR;
}
return OK;
} else if (tk.type == TK_CONST || is_token_precision(tk.type) || is_token_nonvoid_datatype(tk.type) || is_struct) {
String struct_name = "";
if (is_struct) {
struct_name = tk.text;
}
bool is_const = false;
if (tk.type == TK_CONST) {
is_const = true;
tk = _get_token();
if (!is_struct) {
is_struct = shader->structs.has(tk.text); // check again.
struct_name = tk.text;
}
}
DataPrecision precision = PRECISION_DEFAULT;
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
tk = _get_token();
if (!is_struct) {
is_struct = shader->structs.has(tk.text); // check again.
}
if (is_struct && precision != PRECISION_DEFAULT) {
_set_error("Precision modifier cannot be used on structs.");
return ERR_PARSE_ERROR;
}
if (!is_token_nonvoid_datatype(tk.type)) {
_set_error("Expected datatype after precision");
return ERR_PARSE_ERROR;
}
}
if (!is_struct) {
if (!is_token_variable_datatype(tk.type)) {
_set_error("Invalid data type for variable (samplers not allowed)");
return ERR_PARSE_ERROR;
}
}
DataType type = is_struct ? TYPE_STRUCT : get_token_datatype(tk.type);
if (_validate_datatype(type) != OK) {
return ERR_PARSE_ERROR;
}
tk = _get_token();
Node *vardecl = NULL;
while (true) {
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier after type");
return ERR_PARSE_ERROR;
}
StringName name = tk.text;
ShaderLanguage::IdentifierType itype;
if (_find_identifier(p_block, p_builtin_types, name, (ShaderLanguage::DataType *)0, &itype)) {
if (itype != IDENTIFIER_FUNCTION) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
}
BlockNode::Variable var;
var.type = type;
var.precision = precision;
var.line = tk_line;
var.array_size = 0;
var.is_const = is_const;
var.struct_name = struct_name;
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
bool unknown_size = false;
if (VisualServer::get_singleton()->is_low_end() && is_const) {
_set_error("Local const arrays are supported only on high-end platform!");
return ERR_PARSE_ERROR;
}
ArrayDeclarationNode *node = alloc_node<ArrayDeclarationNode>();
if (is_struct) {
node->struct_name = struct_name;
node->datatype = TYPE_STRUCT;
} else {
node->datatype = type;
}
node->precision = precision;
node->is_const = is_const;
vardecl = (Node *)node;
ArrayDeclarationNode::Declaration decl;
decl.name = name;
decl.size = 0U;
tk = _get_token();
if (tk.type == TK_BRACKET_CLOSE) {
unknown_size = true;
} else {
if (tk.type != TK_INT_CONSTANT || ((int)tk.constant) <= 0) {
_set_error("Expected integer constant > 0 or ']'");
return ERR_PARSE_ERROR;
}
decl.size = ((uint32_t)tk.constant);
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']'");
return ERR_PARSE_ERROR;
}
var.array_size = decl.size;
}
bool full_def = false;
tk = _get_token();
if (tk.type == TK_OP_ASSIGN) {
if (VisualServer::get_singleton()->is_low_end()) {
_set_error("Array initialization is supported only on high-end platform!");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_CURLY_BRACKET_OPEN) {
if (unknown_size) {
_set_error("Expected '{'");
return ERR_PARSE_ERROR;
}
full_def = true;
DataPrecision precision2 = PRECISION_DEFAULT;
if (is_token_precision(tk.type)) {
precision2 = get_token_precision(tk.type);
tk = _get_token();
if (shader->structs.has(tk.text)) {
_set_error("Precision modifier cannot be used on structs.");
return ERR_PARSE_ERROR;
}
if (!is_token_nonvoid_datatype(tk.type)) {
_set_error("Expected datatype after precision");
return ERR_PARSE_ERROR;
}
}
DataType type2;
String struct_name2 = "";
if (shader->structs.has(tk.text)) {
type2 = TYPE_STRUCT;
struct_name2 = tk.text;
} else {
if (!is_token_variable_datatype(tk.type)) {
_set_error("Invalid data type for array");
return ERR_PARSE_ERROR;
}
type2 = get_token_datatype(tk.type);
}
int array_size2 = 0;
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
TkPos pos2 = _get_tkpos();
tk = _get_token();
if (tk.type == TK_BRACKET_CLOSE) {
array_size2 = var.array_size;
tk = _get_token();
} else {
_set_tkpos(pos2);
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n || n->type != Node::TYPE_CONSTANT || n->get_datatype() != TYPE_INT) {
_set_error("Expected single integer constant > 0");
return ERR_PARSE_ERROR;
}
ConstantNode *cnode = (ConstantNode *)n;
if (cnode->values.size() == 1) {
array_size2 = cnode->values[0].sint;
if (array_size2 <= 0) {
_set_error("Expected single integer constant > 0");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected single integer constant > 0");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_BRACKET_CLOSE) {
_set_error("Expected ']'");
return ERR_PARSE_ERROR;
} else {
tk = _get_token();
}
}
} else {
_set_error("Expected '['");
return ERR_PARSE_ERROR;
}
if (precision != precision2 || type != type2 || struct_name != struct_name2 || var.array_size != array_size2) {
String error_str = "Cannot convert from '";
if (precision2 != PRECISION_DEFAULT) {
error_str += get_precision_name(precision2);
error_str += " ";
}
if (type2 == TYPE_STRUCT) {
error_str += struct_name2;
} else {
error_str += get_datatype_name(type2);
}
error_str += "[";
error_str += itos(array_size2);
error_str += "]'";
error_str += " to '";
if (precision != PRECISION_DEFAULT) {
error_str += get_precision_name(precision);
error_str += " ";
}
if (type == TYPE_STRUCT) {
error_str += struct_name;
} else {
error_str += get_datatype_name(type);
}
error_str += "[";
error_str += itos(var.array_size);
error_str += "]'";
_set_error(error_str);
return ERR_PARSE_ERROR;
}
}
bool curly = tk.type == TK_CURLY_BRACKET_OPEN;
if (unknown_size) {
if (!curly) {
_set_error("Expected '{'");
return ERR_PARSE_ERROR;
}
} else {
if (full_def) {
if (curly) {
_set_error("Expected '('");
return ERR_PARSE_ERROR;
}
}
}
if (tk.type == TK_PARENTHESIS_OPEN || curly) { // initialization
while (true) {
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n) {
return ERR_PARSE_ERROR;
}
if (node->is_const && n->type == Node::TYPE_OPERATOR && ((OperatorNode *)n)->op == OP_CALL) {
_set_error("Expected constant expression");
return ERR_PARSE_ERROR;
}
if (var.type != n->get_datatype() || struct_name != n->get_datatype_name()) {
_set_error("Invalid assignment of '" + (n->get_datatype() == TYPE_STRUCT ? n->get_datatype_name() : get_datatype_name(n->get_datatype())) + "' to '" + (var.type == TYPE_STRUCT ? struct_name : get_datatype_name(var.type)) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type == TK_COMMA) {
decl.initializer.push_back(n);
continue;
} else if (!curly && tk.type == TK_PARENTHESIS_CLOSE) {
decl.initializer.push_back(n);
break;
} else if (curly && tk.type == TK_CURLY_BRACKET_CLOSE) {
decl.initializer.push_back(n);
break;
} else {
if (curly)
_set_error("Expected '}' or ','");
else
_set_error("Expected ')' or ','");
return ERR_PARSE_ERROR;
}
}
if (unknown_size) {
decl.size = decl.initializer.size();
var.array_size = decl.initializer.size();
} else if (decl.initializer.size() != var.array_size) {
_set_error("Array size mismatch");
return ERR_PARSE_ERROR;
}
tk = _get_token();
}
} else {
if (unknown_size) {
_set_error("Expected array initialization");
return ERR_PARSE_ERROR;
}
if (is_const) {
_set_error("Expected initialization of constant");
return ERR_PARSE_ERROR;
}
}
node->declarations.push_back(decl);
} else if (tk.type == TK_OP_ASSIGN) {
VariableDeclarationNode *node = alloc_node<VariableDeclarationNode>();
if (is_struct) {
node->struct_name = struct_name;
node->datatype = TYPE_STRUCT;
} else {
node->datatype = type;
}
node->precision = precision;
node->is_const = is_const;
vardecl = (Node *)node;
VariableDeclarationNode::Declaration decl;
decl.name = name;
decl.initializer = NULL;
//variable created with assignment! must parse an expression
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
if (node->is_const && n->type == Node::TYPE_OPERATOR && ((OperatorNode *)n)->op == OP_CALL) {
_set_error("Expected constant expression after '='");
return ERR_PARSE_ERROR;
}
decl.initializer = n;
if (var.type == TYPE_STRUCT ? (var.struct_name != n->get_datatype_name()) : (var.type != n->get_datatype())) {
_set_error("Invalid assignment of '" + (n->get_datatype() == TYPE_STRUCT ? n->get_datatype_name() : get_datatype_name(n->get_datatype())) + "' to '" + (var.type == TYPE_STRUCT ? String(var.struct_name) : get_datatype_name(var.type)) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
node->declarations.push_back(decl);
} else {
if (is_const) {
_set_error("Expected initialization of constant");
return ERR_PARSE_ERROR;
}
VariableDeclarationNode *node = alloc_node<VariableDeclarationNode>();
if (is_struct) {
node->struct_name = struct_name;
node->datatype = TYPE_STRUCT;
} else {
node->datatype = type;
}
node->precision = precision;
vardecl = (Node *)node;
VariableDeclarationNode::Declaration decl;
decl.name = name;
decl.initializer = NULL;
node->declarations.push_back(decl);
}
p_block->statements.push_back(vardecl);
p_block->variables[name] = var;
if (tk.type == TK_COMMA) {
tk = _get_token();
//another variable
} else if (tk.type == TK_SEMICOLON) {
break;
} else {
_set_error("Expected ',' or ';' after variable");
return ERR_PARSE_ERROR;
}
}
} else if (tk.type == TK_CURLY_BRACKET_OPEN) {
//a sub block, just because..
BlockNode *block = alloc_node<BlockNode>();
block->parent_block = p_block;
_parse_block(block, p_builtin_types, false, p_can_break, p_can_continue);
p_block->statements.push_back(block);
} else if (tk.type == TK_CF_IF) {
//if () {}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after if");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_IF;
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
if (n->get_datatype() != TYPE_BOOL) {
_set_error("Expected boolean expression");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')' after expression");
return ERR_PARSE_ERROR;
}
BlockNode *block = alloc_node<BlockNode>();
block->parent_block = p_block;
cf->expressions.push_back(n);
cf->blocks.push_back(block);
p_block->statements.push_back(cf);
Error err = _parse_block(block, p_builtin_types, true, p_can_break, p_can_continue);
if (err)
return err;
pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_CF_ELSE) {
block = alloc_node<BlockNode>();
block->parent_block = p_block;
cf->blocks.push_back(block);
err = _parse_block(block, p_builtin_types, true, p_can_break, p_can_continue);
} else {
_set_tkpos(pos); //rollback
}
} else if (tk.type == TK_CF_SWITCH) {
if (VisualServer::get_singleton()->is_low_end()) {
_set_error("\"switch\" operator is supported only on high-end platform!");
return ERR_PARSE_ERROR;
}
// switch() {}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after switch");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_SWITCH;
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
if (n->get_datatype() != TYPE_INT) {
_set_error("Expected integer expression");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')' after expression");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_CURLY_BRACKET_OPEN) {
_set_error("Expected '{' after switch statement");
return ERR_PARSE_ERROR;
}
BlockNode *switch_block = alloc_node<BlockNode>();
switch_block->block_type = BlockNode::BLOCK_TYPE_SWITCH;
switch_block->parent_block = p_block;
cf->expressions.push_back(n);
cf->blocks.push_back(switch_block);
p_block->statements.push_back(cf);
int prev_type = TK_CF_CASE;
while (true) { // Go-through multiple cases.
if (_parse_block(switch_block, p_builtin_types, true, true, false) != OK) {
return ERR_PARSE_ERROR;
}
pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_CF_CASE || tk.type == TK_CF_DEFAULT) {
if (prev_type == TK_CF_DEFAULT) {
if (tk.type == TK_CF_CASE) {
_set_error("Cases must be defined before default case.");
return ERR_PARSE_ERROR;
} else if (prev_type == TK_CF_DEFAULT) {
_set_error("Default case must be defined only once.");
return ERR_PARSE_ERROR;
}
}
prev_type = tk.type;
_set_tkpos(pos);
continue;
} else {
Set<int> constants;
for (int i = 0; i < switch_block->statements.size(); i++) { // Checks for duplicates.
ControlFlowNode *flow = (ControlFlowNode *)switch_block->statements[i];
if (flow) {
if (flow->flow_op == FLOW_OP_CASE) {
ConstantNode *n2 = static_cast<ConstantNode *>(flow->expressions[0]);
if (!n2) {
return ERR_PARSE_ERROR;
}
if (n2->values.empty()) {
return ERR_PARSE_ERROR;
}
if (constants.has(n2->values[0].sint)) {
_set_error("Duplicated case label: '" + itos(n2->values[0].sint) + "'");
return ERR_PARSE_ERROR;
}
constants.insert(n2->values[0].sint);
} else if (flow->flow_op == FLOW_OP_DEFAULT) {
continue;
} else {
return ERR_PARSE_ERROR;
}
} else {
return ERR_PARSE_ERROR;
}
}
break;
}
}
} else if (tk.type == TK_CF_CASE) {
// case x : break; | return;
if (p_block && p_block->block_type == BlockNode::BLOCK_TYPE_CASE) {
_set_tkpos(pos);
return OK;
}
if (!p_block || (p_block->block_type != BlockNode::BLOCK_TYPE_SWITCH)) {
_set_error("case must be placed within switch block");
return ERR_PARSE_ERROR;
}
tk = _get_token();
int sign = 1;
if (tk.type == TK_OP_SUB) {
sign = -1;
tk = _get_token();
}
if (tk.type != TK_INT_CONSTANT) {
_set_error("Expected integer constant");
return ERR_PARSE_ERROR;
}
int constant = (int)tk.constant * sign;
tk = _get_token();
if (tk.type != TK_COLON) {
_set_error("Expected ':'");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_CASE;
ConstantNode *n = alloc_node<ConstantNode>();
ConstantNode::Value v;
v.sint = constant;
n->values.push_back(v);
n->datatype = TYPE_INT;
BlockNode *case_block = alloc_node<BlockNode>();
case_block->block_type = BlockNode::BLOCK_TYPE_CASE;
case_block->parent_block = p_block;
cf->expressions.push_back(n);
cf->blocks.push_back(case_block);
p_block->statements.push_back(cf);
Error err = _parse_block(case_block, p_builtin_types, false, true, false);
if (err)
return err;
return OK;
} else if (tk.type == TK_CF_DEFAULT) {
if (p_block && p_block->block_type == BlockNode::BLOCK_TYPE_CASE) {
_set_tkpos(pos);
return OK;
}
if (!p_block || (p_block->block_type != BlockNode::BLOCK_TYPE_SWITCH)) {
_set_error("default must be placed within switch block");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_COLON) {
_set_error("Expected ':'");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_DEFAULT;
BlockNode *default_block = alloc_node<BlockNode>();
default_block->block_type = BlockNode::BLOCK_TYPE_DEFAULT;
default_block->parent_block = p_block;
cf->blocks.push_back(default_block);
p_block->statements.push_back(cf);
Error err = _parse_block(default_block, p_builtin_types, false, true, false);
if (err)
return err;
return OK;
} else if (tk.type == TK_CF_DO || tk.type == TK_CF_WHILE) {
// do {} while()
// while() {}
bool is_do = tk.type == TK_CF_DO;
BlockNode *do_block = NULL;
if (is_do) {
do_block = alloc_node<BlockNode>();
do_block->parent_block = p_block;
Error err = _parse_block(do_block, p_builtin_types, true, true, true);
if (err)
return err;
tk = _get_token();
if (tk.type != TK_CF_WHILE) {
_set_error("Expected while after do");
return ERR_PARSE_ERROR;
}
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after while");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
if (is_do) {
cf->flow_op = FLOW_OP_DO;
} else {
cf->flow_op = FLOW_OP_WHILE;
}
Node *n = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')' after expression");
return ERR_PARSE_ERROR;
}
if (!is_do) {
BlockNode *block = alloc_node<BlockNode>();
block->parent_block = p_block;
cf->expressions.push_back(n);
cf->blocks.push_back(block);
p_block->statements.push_back(cf);
Error err = _parse_block(block, p_builtin_types, true, true, true);
if (err)
return err;
} else {
cf->expressions.push_back(n);
cf->blocks.push_back(do_block);
p_block->statements.push_back(cf);
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';'");
return ERR_PARSE_ERROR;
}
}
} else if (tk.type == TK_CF_FOR) {
// for() {}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after for");
return ERR_PARSE_ERROR;
}
ControlFlowNode *cf = alloc_node<ControlFlowNode>();
cf->flow_op = FLOW_OP_FOR;
BlockNode *init_block = alloc_node<BlockNode>();
init_block->parent_block = p_block;
init_block->single_statement = true;
cf->blocks.push_back(init_block);
if (_parse_block(init_block, p_builtin_types, true, false, false) != OK) {
return ERR_PARSE_ERROR;
}
Node *n = _parse_and_reduce_expression(init_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
if (n->get_datatype() != TYPE_BOOL) {
_set_error("Middle expression is expected to be boolean.");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';' after middle expression");
return ERR_PARSE_ERROR;
}
cf->expressions.push_back(n);
n = _parse_and_reduce_expression(init_block, p_builtin_types);
if (!n)
return ERR_PARSE_ERROR;
cf->expressions.push_back(n);
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ')' after third expression");
return ERR_PARSE_ERROR;
}
BlockNode *block = alloc_node<BlockNode>();
block->parent_block = init_block;
cf->blocks.push_back(block);
p_block->statements.push_back(cf);
Error err = _parse_block(block, p_builtin_types, true, true, true);
if (err)
return err;
} else if (tk.type == TK_CF_RETURN) {
//check return type
BlockNode *b = p_block;
while (b && !b->parent_function) {
b = b->parent_block;
}
if (!b) {
_set_error("Bug");
return ERR_BUG;
}
ControlFlowNode *flow = alloc_node<ControlFlowNode>();
flow->flow_op = FLOW_OP_RETURN;
pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_SEMICOLON) {
//all is good
if (b->parent_function->return_type != TYPE_VOID) {
_set_error("Expected return with expression of type '" + get_datatype_name(b->parent_function->return_type) + "'");
return ERR_PARSE_ERROR;
}
} else {
_set_tkpos(pos); //rollback, wants expression
Node *expr = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!expr)
return ERR_PARSE_ERROR;
if (b->parent_function->return_type != expr->get_datatype()) {
_set_error("Expected return expression of type '" + get_datatype_name(b->parent_function->return_type) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';' after return expression");
return ERR_PARSE_ERROR;
}
flow->expressions.push_back(expr);
}
p_block->statements.push_back(flow);
BlockNode *block = p_block;
while (block) {
if (block->block_type == BlockNode::BLOCK_TYPE_CASE || block->block_type == BlockNode::BLOCK_TYPE_DEFAULT) {
return OK;
}
block = block->parent_block;
}
} else if (tk.type == TK_CF_DISCARD) {
//check return type
BlockNode *b = p_block;
while (b && !b->parent_function) {
b = b->parent_block;
}
if (!b) {
_set_error("Bug");
return ERR_BUG;
}
if (!b->parent_function->can_discard) {
_set_error("Use of 'discard' is not allowed here.");
return ERR_PARSE_ERROR;
}
ControlFlowNode *flow = alloc_node<ControlFlowNode>();
flow->flow_op = FLOW_OP_DISCARD;
pos = _get_tkpos();
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
//all is good
_set_error("Expected ';' after discard");
}
p_block->statements.push_back(flow);
} else if (tk.type == TK_CF_BREAK) {
if (!p_can_break) {
//all is good
_set_error("Breaking is not allowed here");
}
ControlFlowNode *flow = alloc_node<ControlFlowNode>();
flow->flow_op = FLOW_OP_BREAK;
pos = _get_tkpos();
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
//all is good
_set_error("Expected ';' after break");
}
p_block->statements.push_back(flow);
BlockNode *block = p_block;
while (block) {
if (block->block_type == BlockNode::BLOCK_TYPE_CASE || block->block_type == BlockNode::BLOCK_TYPE_DEFAULT) {
return OK;
}
block = block->parent_block;
}
} else if (tk.type == TK_CF_CONTINUE) {
if (!p_can_continue) {
//all is good
_set_error("Continuing is not allowed here");
}
ControlFlowNode *flow = alloc_node<ControlFlowNode>();
flow->flow_op = FLOW_OP_CONTINUE;
pos = _get_tkpos();
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
//all is good
_set_error("Expected ';' after continue");
}
p_block->statements.push_back(flow);
} else {
//nothing else, so expression
_set_tkpos(pos); //rollback
Node *expr = _parse_and_reduce_expression(p_block, p_builtin_types);
if (!expr)
return ERR_PARSE_ERROR;
p_block->statements.push_back(expr);
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';' after statement");
return ERR_PARSE_ERROR;
}
}
if (p_just_one)
break;
}
return OK;
}
String ShaderLanguage::_get_shader_type_list(const Set<String> &p_shader_types) const {
// Return a list of shader types as an human-readable string
String valid_types;
for (const Set<String>::Element *E = p_shader_types.front(); E; E = E->next()) {
if (valid_types != String()) {
valid_types += ", ";
}
valid_types += "'" + E->get() + "'";
}
return valid_types;
}
String ShaderLanguage::_get_qualifier_str(ArgumentQualifier p_qualifier) const {
switch (p_qualifier) {
case ArgumentQualifier::ARGUMENT_QUALIFIER_IN:
return "in";
case ArgumentQualifier::ARGUMENT_QUALIFIER_OUT:
return "out";
case ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT:
return "inout";
}
return "";
}
Error ShaderLanguage::_validate_datatype(DataType p_type) {
if (VisualServer::get_singleton()->is_low_end()) {
bool invalid_type = false;
switch (p_type) {
case TYPE_UINT:
case TYPE_UVEC2:
case TYPE_UVEC3:
case TYPE_UVEC4:
case TYPE_ISAMPLER2D:
case TYPE_USAMPLER2D:
case TYPE_ISAMPLER3D:
case TYPE_USAMPLER3D:
case TYPE_USAMPLER2DARRAY:
case TYPE_ISAMPLER2DARRAY:
invalid_type = true;
break;
default:
break;
}
if (invalid_type) {
_set_error(vformat("\"%s\" type is supported only on high-end platform!", get_datatype_name(p_type)));
return ERR_UNAVAILABLE;
}
}
return OK;
}
Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types) {
Token tk = _get_token();
if (tk.type != TK_SHADER_TYPE) {
_set_error("Expected 'shader_type' at the beginning of shader. Valid types are: " + _get_shader_type_list(p_shader_types));
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier after 'shader_type', indicating type of shader. Valid types are: " + _get_shader_type_list(p_shader_types));
return ERR_PARSE_ERROR;
}
String shader_type_identifier;
shader_type_identifier = tk.text;
if (!p_shader_types.has(shader_type_identifier)) {
_set_error("Invalid shader type. Valid types are: " + _get_shader_type_list(p_shader_types));
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';' after 'shader_type <type>'.");
}
tk = _get_token();
int texture_uniforms = 0;
int uniforms = 0;
while (tk.type != TK_EOF) {
switch (tk.type) {
case TK_RENDER_MODE: {
while (true) {
StringName mode;
_get_completable_identifier(NULL, COMPLETION_RENDER_MODE, mode);
if (mode == StringName()) {
_set_error("Expected identifier for render mode");
return ERR_PARSE_ERROR;
}
if (p_render_modes.find(mode) == -1) {
_set_error("Invalid render mode: '" + String(mode) + "'");
return ERR_PARSE_ERROR;
}
if (shader->render_modes.find(mode) != -1) {
_set_error("Duplicate render mode: '" + String(mode) + "'");
return ERR_PARSE_ERROR;
}
shader->render_modes.push_back(mode);
tk = _get_token();
if (tk.type == TK_COMMA) {
//all good, do nothing
} else if (tk.type == TK_SEMICOLON) {
break; //done
} else {
_set_error("Unexpected token: " + get_token_text(tk));
return ERR_PARSE_ERROR;
}
}
} break;
case TK_STRUCT: {
ShaderNode::Struct st;
DataType type;
tk = _get_token();
if (tk.type == TK_IDENTIFIER) {
st.name = tk.text;
tk = _get_token();
if (tk.type != TK_CURLY_BRACKET_OPEN) {
_set_error("Expected '{'");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected struct identifier!");
return ERR_PARSE_ERROR;
}
StructNode *st_node = alloc_node<StructNode>();
st.shader_struct = st_node;
int member_count = 0;
Set<String> member_names;
while (true) { // variables list
tk = _get_token();
if (tk.type == TK_CURLY_BRACKET_CLOSE) {
break;
}
StringName struct_name = "";
bool struct_dt = false;
bool use_precision = false;
DataPrecision precision = DataPrecision::PRECISION_DEFAULT;
if (tk.type == TK_STRUCT) {
_set_error("nested structs are not allowed!");
return ERR_PARSE_ERROR;
}
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
use_precision = true;
tk = _get_token();
}
if (shader->structs.has(tk.text)) {
struct_name = tk.text;
struct_dt = true;
if (use_precision) {
_set_error("Precision modifier cannot be used on structs.");
return ERR_PARSE_ERROR;
}
}
if (!is_token_datatype(tk.type) && !struct_dt) {
_set_error("Expected datatype.");
return ERR_PARSE_ERROR;
} else {
type = struct_dt ? TYPE_STRUCT : get_token_datatype(tk.type);
if (is_sampler_type(type)) {
_set_error("sampler datatype not allowed here");
return ERR_PARSE_ERROR;
} else if (type == TYPE_VOID) {
_set_error("void datatype not allowed here");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier!");
return ERR_PARSE_ERROR;
}
MemberNode *member = alloc_node<MemberNode>();
member->precision = precision;
member->datatype = type;
member->struct_name = struct_name;
member->name = tk.text;
if (member_names.has(member->name)) {
_set_error("Redefinition of '" + String(member->name) + "'");
return ERR_PARSE_ERROR;
}
member_names.insert(member->name);
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
tk = _get_token();
if (tk.type == TK_INT_CONSTANT && tk.constant > 0) {
member->array_size = (int)tk.constant;
tk = _get_token();
if (tk.type == TK_BRACKET_CLOSE) {
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';'");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected ']'");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected single integer constant > 0");
return ERR_PARSE_ERROR;
}
}
st_node->members.push_back(member);
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ']' or ';'");
return ERR_PARSE_ERROR;
}
member_count++;
}
}
if (member_count == 0) {
_set_error("Empty structs are not allowed!");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';'");
return ERR_PARSE_ERROR;
}
shader->structs[st.name] = st;
shader->vstructs.push_back(st); // struct's order is important!
} break;
case TK_UNIFORM:
case TK_VARYING: {
bool uniform = tk.type == TK_UNIFORM;
DataPrecision precision = PRECISION_DEFAULT;
DataInterpolation interpolation = INTERPOLATION_SMOOTH;
DataType type;
StringName name;
tk = _get_token();
if (is_token_interpolation(tk.type)) {
interpolation = get_token_interpolation(tk.type);
tk = _get_token();
}
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
tk = _get_token();
}
if (!is_token_datatype(tk.type)) {
_set_error("Expected datatype. ");
return ERR_PARSE_ERROR;
}
type = get_token_datatype(tk.type);
if (type == TYPE_VOID) {
_set_error("void datatype not allowed here");
return ERR_PARSE_ERROR;
}
if (!uniform && (type < TYPE_FLOAT || type > TYPE_MAT4)) {
_set_error("Invalid type for varying, only float,vec2,vec3,vec4,mat2,mat3,mat4 or array of these types allowed.");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier!");
return ERR_PARSE_ERROR;
}
name = tk.text;
if (_find_identifier(NULL, Map<StringName, BuiltInInfo>(), name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
if (has_builtin(p_functions, name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
if (uniform) {
ShaderNode::Uniform uniform2;
if (is_sampler_type(type)) {
uniform2.texture_order = texture_uniforms++;
uniform2.order = -1;
if (_validate_datatype(type) != OK) {
return ERR_PARSE_ERROR;
}
} else {
uniform2.texture_order = -1;
uniform2.order = uniforms++;
}
uniform2.type = type;
uniform2.precision = precision;
//todo parse default value
tk = _get_token();
if (tk.type == TK_COLON) {
//hint
do {
tk = _get_token();
if (tk.type == TK_HINT_WHITE_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_WHITE;
} else if (tk.type == TK_HINT_BLACK_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_BLACK;
} else if (tk.type == TK_HINT_NORMAL_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_NORMAL;
} else if (tk.type == TK_HINT_ROUGHNESS_NORMAL_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_NORMAL;
} else if (tk.type == TK_HINT_ROUGHNESS_R) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_R;
} else if (tk.type == TK_HINT_ROUGHNESS_G) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_G;
} else if (tk.type == TK_HINT_ROUGHNESS_B) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_B;
} else if (tk.type == TK_HINT_ROUGHNESS_A) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_A;
} else if (tk.type == TK_HINT_ROUGHNESS_GRAY) {
uniform2.hint = ShaderNode::Uniform::HINT_ROUGHNESS_GRAY;
} else if (tk.type == TK_HINT_ANISO_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_ANISO;
} else if (tk.type == TK_HINT_ALBEDO_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_ALBEDO;
} else if (tk.type == TK_HINT_BLACK_ALBEDO_TEXTURE) {
uniform2.hint = ShaderNode::Uniform::HINT_BLACK_ALBEDO;
} else if (tk.type == TK_HINT_COLOR) {
if (type != TYPE_VEC4) {
_set_error("Color hint is for vec4 only");
return ERR_PARSE_ERROR;
}
uniform2.hint = ShaderNode::Uniform::HINT_COLOR;
} else if (tk.type == TK_HINT_RANGE) {
uniform2.hint = ShaderNode::Uniform::HINT_RANGE;
if (type != TYPE_FLOAT && type != TYPE_INT) {
_set_error("Range hint is for float and int only");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
_set_error("Expected '(' after hint_range");
return ERR_PARSE_ERROR;
}
tk = _get_token();
float sign = 1.0;
if (tk.type == TK_OP_SUB) {
sign = -1.0;
tk = _get_token();
}
if (tk.type != TK_REAL_CONSTANT && tk.type != TK_INT_CONSTANT) {
_set_error("Expected integer constant");
return ERR_PARSE_ERROR;
}
uniform2.hint_range[0] = tk.constant;
uniform2.hint_range[0] *= sign;
tk = _get_token();
if (tk.type != TK_COMMA) {
_set_error("Expected ',' after integer constant");
return ERR_PARSE_ERROR;
}
tk = _get_token();
sign = 1.0;
if (tk.type == TK_OP_SUB) {
sign = -1.0;
tk = _get_token();
}
if (tk.type != TK_REAL_CONSTANT && tk.type != TK_INT_CONSTANT) {
_set_error("Expected integer constant after ','");
return ERR_PARSE_ERROR;
}
uniform2.hint_range[1] = tk.constant;
uniform2.hint_range[1] *= sign;
tk = _get_token();
if (tk.type == TK_COMMA) {
tk = _get_token();
if (tk.type != TK_REAL_CONSTANT && tk.type != TK_INT_CONSTANT) {
_set_error("Expected integer constant after ','");
return ERR_PARSE_ERROR;
}
uniform2.hint_range[2] = tk.constant;
tk = _get_token();
} else {
if (type == TYPE_INT) {
uniform2.hint_range[2] = 1;
} else {
uniform2.hint_range[2] = 0.001;
}
}
if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ','");
return ERR_PARSE_ERROR;
}
} else if (tk.type == TK_FILTER_LINEAR) {
uniform2.filter = FILTER_LINEAR;
} else if (tk.type == TK_FILTER_NEAREST) {
uniform2.filter = FILTER_NEAREST;
} else if (tk.type == TK_FILTER_NEAREST_MIPMAP) {
uniform2.filter = FILTER_NEAREST_MIPMAP;
} else if (tk.type == TK_FILTER_LINEAR_MIPMAP) {
uniform2.filter = FILTER_LINEAR_MIPMAP;
} else if (tk.type == TK_FILTER_NEAREST_MIPMAP_ANISO) {
uniform2.filter = FILTER_NEAREST_MIPMAP_ANISO;
} else if (tk.type == TK_FILTER_LINEAR_MIPMAP_ANISO) {
uniform2.filter = FILTER_LINEAR_MIPMAP_ANISO;
} else if (tk.type == TK_REPEAT_DISABLE) {
uniform2.repeat = REPEAT_DISABLE;
} else if (tk.type == TK_REPEAT_ENABLE) {
uniform2.repeat = REPEAT_ENABLE;
} else {
_set_error("Expected valid type hint after ':'.");
}
if (uniform2.hint != ShaderNode::Uniform::HINT_RANGE && uniform2.hint != ShaderNode::Uniform::HINT_NONE && uniform2.hint != ShaderNode::Uniform::HINT_COLOR && type <= TYPE_MAT4) {
_set_error("This hint is only for sampler types");
return ERR_PARSE_ERROR;
}
tk = _get_token();
} while (tk.type == TK_COMMA);
}
if (tk.type == TK_OP_ASSIGN) {
Node *expr = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>());
if (!expr)
return ERR_PARSE_ERROR;
if (expr->type != Node::TYPE_CONSTANT) {
_set_error("Expected constant expression after '='");
return ERR_PARSE_ERROR;
}
ConstantNode *cn = static_cast<ConstantNode *>(expr);
uniform2.default_value.resize(cn->values.size());
if (!convert_constant(cn, uniform2.type, uniform2.default_value.ptrw())) {
_set_error("Can't convert constant to " + get_datatype_name(uniform2.type));
return ERR_PARSE_ERROR;
}
tk = _get_token();
}
shader->uniforms[name] = uniform2;
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';'");
return ERR_PARSE_ERROR;
}
} else {
ShaderNode::Varying varying;
varying.type = type;
varying.precision = precision;
varying.interpolation = interpolation;
tk = _get_token();
if (tk.type != TK_SEMICOLON && tk.type != TK_BRACKET_OPEN) {
_set_error("Expected ';' or '['");
return ERR_PARSE_ERROR;
}
if (tk.type == TK_BRACKET_OPEN) {
tk = _get_token();
if (tk.type == TK_INT_CONSTANT && tk.constant > 0) {
varying.array_size = (int)tk.constant;
tk = _get_token();
if (tk.type == TK_BRACKET_CLOSE) {
tk = _get_token();
if (tk.type != TK_SEMICOLON) {
_set_error("Expected ';'");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected ']'");
return ERR_PARSE_ERROR;
}
} else {
_set_error("Expected single integer constant > 0");
return ERR_PARSE_ERROR;
}
}
shader->varyings[name] = varying;
}
} break;
default: {
//function or constant variable
bool is_constant = false;
bool is_struct = false;
StringName struct_name;
DataPrecision precision = PRECISION_DEFAULT;
DataType type;
StringName name;
if (tk.type == TK_CONST) {
is_constant = true;
tk = _get_token();
}
if (is_token_precision(tk.type)) {
precision = get_token_precision(tk.type);
tk = _get_token();
}
if (shader->structs.has(tk.text)) {
if (precision != PRECISION_DEFAULT) {
_set_error("Precision modifier cannot be used on structs.");
return ERR_PARSE_ERROR;
}
is_struct = true;
struct_name = tk.text;
} else {
if (!is_token_datatype(tk.type)) {
_set_error("Expected constant, function, uniform or varying");
return ERR_PARSE_ERROR;
}
if (!is_token_variable_datatype(tk.type)) {
_set_error("Invalid data type for constants or function return (samplers not allowed)");
return ERR_PARSE_ERROR;
}
}
if (is_struct) {
type = TYPE_STRUCT;
} else {
type = get_token_datatype(tk.type);
}
TkPos prev_pos = _get_tkpos();
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
_set_error("Cannot use arrays as return types");
return ERR_PARSE_ERROR;
}
_set_tkpos(prev_pos);
_get_completable_identifier(NULL, COMPLETION_MAIN_FUNCTION, name);
if (name == StringName()) {
_set_error("Expected function name after datatype");
return ERR_PARSE_ERROR;
}
if (_find_identifier(NULL, Map<StringName, BuiltInInfo>(), name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
if (has_builtin(p_functions, name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_OPEN) {
if (type == TYPE_VOID) {
_set_error("Expected '(' after function identifier");
return ERR_PARSE_ERROR;
}
//variable
while (true) {
ShaderNode::Constant constant;
constant.type = is_struct ? TYPE_STRUCT : type;
constant.type_str = struct_name;
constant.precision = precision;
constant.initializer = NULL;
if (tk.type == TK_OP_ASSIGN) {
if (!is_constant) {
_set_error("Expected 'const' keyword before constant definition");
return ERR_PARSE_ERROR;
}
//variable created with assignment! must parse an expression
Node *expr = _parse_and_reduce_expression(NULL, Map<StringName, BuiltInInfo>());
if (!expr)
return ERR_PARSE_ERROR;
if (expr->type == Node::TYPE_OPERATOR && ((OperatorNode *)expr)->op == OP_CALL) {
_set_error("Expected constant expression after '='");
return ERR_PARSE_ERROR;
}
constant.initializer = static_cast<ConstantNode *>(expr);
if (is_struct) {
if (expr->get_datatype_name() != struct_name) {
_set_error("Invalid assignment of '" + (expr->get_datatype() == TYPE_STRUCT ? expr->get_datatype_name() : get_datatype_name(expr->get_datatype())) + "' to '" + struct_name + "'");
return ERR_PARSE_ERROR;
}
} else if (type != expr->get_datatype()) {
_set_error("Invalid assignment of '" + get_datatype_name(expr->get_datatype()) + "' to '" + get_datatype_name(type) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
} else {
_set_error("Expected initialization of constant");
return ERR_PARSE_ERROR;
}
shader->constants[name] = constant;
if (tk.type == TK_COMMA) {
tk = _get_token();
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier after type");
return ERR_PARSE_ERROR;
}
name = tk.text;
if (_find_identifier(NULL, Map<StringName, BuiltInInfo>(), name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
if (has_builtin(p_functions, name)) {
_set_error("Redefinition of '" + String(name) + "'");
return ERR_PARSE_ERROR;
}
tk = _get_token();
} else if (tk.type == TK_SEMICOLON) {
break;
} else {
_set_error("Expected ',' or ';' after constant");
return ERR_PARSE_ERROR;
}
}
break;
}
Map<StringName, BuiltInInfo> builtin_types;
if (p_functions.has(name)) {
builtin_types = p_functions[name].built_ins;
}
ShaderNode::Function function;
function.callable = !p_functions.has(name);
function.name = name;
FunctionNode *func_node = alloc_node<FunctionNode>();
function.function = func_node;
shader->functions.push_back(function);
func_node->name = name;
func_node->return_type = type;
func_node->return_struct_name = struct_name;
func_node->return_precision = precision;
if (p_functions.has(name)) {
func_node->can_discard = p_functions[name].can_discard;
}
func_node->body = alloc_node<BlockNode>();
func_node->body->parent_function = func_node;
tk = _get_token();
while (true) {
if (tk.type == TK_PARENTHESIS_CLOSE) {
break;
}
ArgumentQualifier qualifier = ARGUMENT_QUALIFIER_IN;
if (tk.type == TK_ARG_IN) {
qualifier = ARGUMENT_QUALIFIER_IN;
tk = _get_token();
} else if (tk.type == TK_ARG_OUT) {
qualifier = ARGUMENT_QUALIFIER_OUT;
tk = _get_token();
} else if (tk.type == TK_ARG_INOUT) {
qualifier = ARGUMENT_QUALIFIER_INOUT;
tk = _get_token();
}
DataType ptype;
StringName pname;
StringName param_struct_name;
DataPrecision pprecision = PRECISION_DEFAULT;
bool use_precision = false;
if (is_token_precision(tk.type)) {
pprecision = get_token_precision(tk.type);
tk = _get_token();
use_precision = true;
}
is_struct = false;
if (shader->structs.has(tk.text)) {
is_struct = true;
param_struct_name = tk.text;
if (use_precision) {
_set_error("Precision modifier cannot be used on structs.");
return ERR_PARSE_ERROR;
}
}
if (!is_struct && !is_token_datatype(tk.type)) {
_set_error("Expected a valid datatype for argument");
return ERR_PARSE_ERROR;
}
if (qualifier == ARGUMENT_QUALIFIER_OUT || qualifier == ARGUMENT_QUALIFIER_INOUT) {
if (is_sampler_type(get_token_datatype(tk.type))) {
_set_error("Opaque types cannot be output parameters.");
return ERR_PARSE_ERROR;
}
}
if (is_struct) {
ptype = TYPE_STRUCT;
} else {
ptype = get_token_datatype(tk.type);
if (_validate_datatype(ptype) != OK) {
return ERR_PARSE_ERROR;
}
if (ptype == TYPE_VOID) {
_set_error("void not allowed in argument");
return ERR_PARSE_ERROR;
}
}
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
_set_error("Arrays as parameters are not implemented yet");
return ERR_PARSE_ERROR;
}
if (tk.type != TK_IDENTIFIER) {
_set_error("Expected identifier for argument name");
return ERR_PARSE_ERROR;
}
pname = tk.text;
ShaderLanguage::IdentifierType itype;
if (_find_identifier(func_node->body, builtin_types, pname, (ShaderLanguage::DataType *)0, &itype)) {
if (itype != IDENTIFIER_FUNCTION) {
_set_error("Redefinition of '" + String(pname) + "'");
return ERR_PARSE_ERROR;
}
}
if (has_builtin(p_functions, pname)) {
_set_error("Redefinition of '" + String(pname) + "'");
return ERR_PARSE_ERROR;
}
FunctionNode::Argument arg;
arg.type = ptype;
arg.name = pname;
arg.type_str = param_struct_name;
arg.precision = pprecision;
arg.qualifier = qualifier;
arg.tex_argument_check = false;
arg.tex_builtin_check = false;
arg.tex_argument_filter = FILTER_DEFAULT;
arg.tex_argument_repeat = REPEAT_DEFAULT;
func_node->arguments.push_back(arg);
tk = _get_token();
if (tk.type == TK_BRACKET_OPEN) {
_set_error("Arrays as parameters are not implemented yet");
return ERR_PARSE_ERROR;
}
if (tk.type == TK_COMMA) {
tk = _get_token();
//do none and go on
} else if (tk.type != TK_PARENTHESIS_CLOSE) {
_set_error("Expected ',' or ')' after identifier");
return ERR_PARSE_ERROR;
}
}
if (p_functions.has(name)) {
//if one of the core functions, make sure they are of the correct form
if (func_node->arguments.size() > 0) {
_set_error("Function '" + String(name) + "' expects no arguments.");
return ERR_PARSE_ERROR;
}
if (func_node->return_type != TYPE_VOID) {
_set_error("Function '" + String(name) + "' must be of void return type.");
return ERR_PARSE_ERROR;
}
}
//all good let's parse inside the function!
tk = _get_token();
if (tk.type != TK_CURLY_BRACKET_OPEN) {
_set_error("Expected '{' to begin function");
return ERR_PARSE_ERROR;
}
current_function = name;
Error err = _parse_block(func_node->body, builtin_types);
if (err)
return err;
if (func_node->return_type != DataType::TYPE_VOID) {
BlockNode *block = func_node->body;
if (_find_last_flow_op_in_block(block, FlowOperation::FLOW_OP_RETURN) != OK) {
_set_error("Expected at least one return statement in a non-void function.");
return ERR_PARSE_ERROR;
}
}
current_function = StringName();
}
}
tk = _get_token();
}
return OK;
}
bool ShaderLanguage::has_builtin(const Map<StringName, ShaderLanguage::FunctionInfo> &p_functions, const StringName &p_name) {
if (p_functions.has("vertex")) {
if (p_functions["vertex"].built_ins.has(p_name)) {
return true;
}
}
if (p_functions.has("fragment")) {
if (p_functions["fragment"].built_ins.has(p_name)) {
return true;
}
}
if (p_functions.has("light")) {
if (p_functions["light"].built_ins.has(p_name)) {
return true;
}
}
return false;
}
Error ShaderLanguage::_find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op) {
bool found = false;
for (int i = p_flow->blocks.size() - 1; i >= 0; i--) {
if (p_flow->blocks[i]->type == Node::TYPE_BLOCK) {
BlockNode *last_block = (BlockNode *)p_flow->blocks[i];
if (_find_last_flow_op_in_block(last_block, p_op) == OK) {
found = true;
break;
}
}
}
if (found) {
return OK;
}
return FAILED;
}
Error ShaderLanguage::_find_last_flow_op_in_block(BlockNode *p_block, FlowOperation p_op) {
bool found = false;
for (int i = p_block->statements.size() - 1; i >= 0; i--) {
if (p_block->statements[i]->type == Node::TYPE_CONTROL_FLOW) {
ControlFlowNode *flow = (ControlFlowNode *)p_block->statements[i];
if (flow->flow_op == p_op) {
found = true;
break;
} else {
if (_find_last_flow_op_in_op(flow, p_op) == OK) {
found = true;
break;
}
}
} else if (p_block->statements[i]->type == Node::TYPE_BLOCK) {
BlockNode *block = (BlockNode *)p_block->statements[i];
if (_find_last_flow_op_in_block(block, p_op) == OK) {
found = true;
break;
}
}
}
if (found) {
return OK;
}
return FAILED;
}
// skips over whitespace and /* */ and // comments
static int _get_first_ident_pos(const String &p_code) {
int idx = 0;
#define GETCHAR(m_idx) (((idx + m_idx) < p_code.length()) ? p_code[idx + m_idx] : CharType(0))
while (true) {
if (GETCHAR(0) == '/' && GETCHAR(1) == '/') {
idx += 2;
while (true) {
if (GETCHAR(0) == 0) return 0;
if (GETCHAR(0) == '\n') {
idx++;
break; // loop
}
idx++;
}
} else if (GETCHAR(0) == '/' && GETCHAR(1) == '*') {
idx += 2;
while (true) {
if (GETCHAR(0) == 0) return 0;
if (GETCHAR(0) == '*' && GETCHAR(1) == '/') {
idx += 2;
break; // loop
}
idx++;
}
} else {
switch (GETCHAR(0)) {
case ' ':
case '\t':
case '\r':
case '\n': {
idx++;
} break; // switch
default:
return idx;
}
}
}
#undef GETCHAR
}
String ShaderLanguage::get_shader_type(const String &p_code) {
bool reading_type = false;
String cur_identifier;
for (int i = _get_first_ident_pos(p_code); i < p_code.length(); i++) {
if (p_code[i] == ';') {
break;
} else if (p_code[i] <= 32) {
if (cur_identifier != String()) {
if (!reading_type) {
if (cur_identifier != "shader_type") {
return String();
}
reading_type = true;
cur_identifier = String();
} else {
return cur_identifier;
}
}
} else {
cur_identifier += String::chr(p_code[i]);
}
}
if (reading_type)
return cur_identifier;
return String();
}
Error ShaderLanguage::compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types) {
clear();
code = p_code;
nodes = NULL;
shader = alloc_node<ShaderNode>();
Error err = _parse_shader(p_functions, p_render_modes, p_shader_types);
if (err != OK) {
return err;
}
return OK;
}
Error ShaderLanguage::complete(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types, List<ScriptCodeCompletionOption> *r_options, String &r_call_hint) {
clear();
code = p_code;
nodes = NULL;
shader = alloc_node<ShaderNode>();
_parse_shader(p_functions, p_render_modes, p_shader_types);
switch (completion_type) {
case COMPLETION_NONE: {
//do nothing
return OK;
} break;
case COMPLETION_RENDER_MODE: {
for (int i = 0; i < p_render_modes.size(); i++) {
ScriptCodeCompletionOption option(p_render_modes[i], ScriptCodeCompletionOption::KIND_ENUM);
r_options->push_back(option);
}
return OK;
} break;
case COMPLETION_STRUCT: {
if (shader->structs.has(completion_struct)) {
StructNode *node = shader->structs[completion_struct].shader_struct;
for (int i = 0; i < node->members.size(); i++) {
ScriptCodeCompletionOption option(node->members[i]->name, ScriptCodeCompletionOption::KIND_MEMBER);
r_options->push_back(option);
}
}
return OK;
} break;
case COMPLETION_MAIN_FUNCTION: {
for (const Map<StringName, FunctionInfo>::Element *E = p_functions.front(); E; E = E->next()) {
ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_FUNCTION);
r_options->push_back(option);
}
return OK;
} break;
case COMPLETION_IDENTIFIER:
case COMPLETION_FUNCTION_CALL: {
bool comp_ident = completion_type == COMPLETION_IDENTIFIER;
Map<String, ScriptCodeCompletionOption::Kind> matches;
StringName skip_function;
BlockNode *block = completion_block;
if (completion_class == TAG_GLOBAL) {
while (block) {
if (comp_ident) {
for (const Map<StringName, BlockNode::Variable>::Element *E = block->variables.front(); E; E = E->next()) {
if (E->get().line < completion_line) {
matches.insert(E->key(), ScriptCodeCompletionOption::KIND_VARIABLE);
}
}
}
if (block->parent_function) {
if (comp_ident) {
for (int i = 0; i < block->parent_function->arguments.size(); i++) {
matches.insert(block->parent_function->arguments[i].name, ScriptCodeCompletionOption::KIND_VARIABLE);
}
}
skip_function = block->parent_function->name;
}
block = block->parent_block;
}
if (comp_ident && skip_function != StringName() && p_functions.has(skip_function)) {
for (Map<StringName, BuiltInInfo>::Element *E = p_functions[skip_function].built_ins.front(); E; E = E->next()) {
ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_MEMBER;
if (E->get().constant) {
kind = ScriptCodeCompletionOption::KIND_CONSTANT;
}
matches.insert(E->key(), kind);
}
}
if (comp_ident) {
for (const Map<StringName, ShaderNode::Varying>::Element *E = shader->varyings.front(); E; E = E->next()) {
matches.insert(E->key(), ScriptCodeCompletionOption::KIND_VARIABLE);
}
for (const Map<StringName, ShaderNode::Uniform>::Element *E = shader->uniforms.front(); E; E = E->next()) {
matches.insert(E->key(), ScriptCodeCompletionOption::KIND_MEMBER);
}
}
for (int i = 0; i < shader->functions.size(); i++) {
if (!shader->functions[i].callable || shader->functions[i].name == skip_function)
continue;
matches.insert(String(shader->functions[i].name), ScriptCodeCompletionOption::KIND_FUNCTION);
}
int idx = 0;
bool low_end = VisualServer::get_singleton()->is_low_end();
while (builtin_func_defs[idx].name) {
if (low_end && builtin_func_defs[idx].high_end) {
idx++;
continue;
}
matches.insert(String(builtin_func_defs[idx].name), ScriptCodeCompletionOption::KIND_FUNCTION);
idx++;
}
} else { // sub-class
int idx = 0;
bool low_end = VisualServer::get_singleton()->is_low_end();
while (builtin_func_defs[idx].name) {
if (low_end && builtin_func_defs[idx].high_end) {
idx++;
continue;
}
if (builtin_func_defs[idx].tag == completion_class) {
matches.insert(String(builtin_func_defs[idx].name), ScriptCodeCompletionOption::KIND_FUNCTION);
}
idx++;
}
}
for (Map<String, ScriptCodeCompletionOption::Kind>::Element *E = matches.front(); E; E = E->next()) {
ScriptCodeCompletionOption option(E->key(), E->value());
if (E->value() == ScriptCodeCompletionOption::KIND_FUNCTION) {
option.insert_text += "(";
}
r_options->push_back(option);
}
return OK;
} break;
case COMPLETION_CALL_ARGUMENTS: {
for (int i = 0; i < shader->functions.size(); i++) {
if (!shader->functions[i].callable)
continue;
if (shader->functions[i].name == completion_function) {
String calltip;
calltip += get_datatype_name(shader->functions[i].function->return_type);
calltip += " ";
calltip += shader->functions[i].name;
calltip += "(";
for (int j = 0; j < shader->functions[i].function->arguments.size(); j++) {
if (j > 0)
calltip += ", ";
else
calltip += " ";
if (j == completion_argument) {
calltip += CharType(0xFFFF);
}
calltip += get_datatype_name(shader->functions[i].function->arguments[j].type);
calltip += " ";
calltip += shader->functions[i].function->arguments[j].name;
if (j == completion_argument) {
calltip += CharType(0xFFFF);
}
}
if (shader->functions[i].function->arguments.size())
calltip += " ";
calltip += ")";
r_call_hint = calltip;
return OK;
}
}
int idx = 0;
String calltip;
bool low_end = VisualServer::get_singleton()->is_low_end();
while (builtin_func_defs[idx].name) {
if (low_end && builtin_func_defs[idx].high_end) {
idx++;
continue;
}
if (completion_function == builtin_func_defs[idx].name) {
if (builtin_func_defs[idx].tag != completion_class) {
idx++;
continue;
}
if (calltip.length())
calltip += "\n";
calltip += get_datatype_name(builtin_func_defs[idx].rettype);
calltip += " ";
calltip += builtin_func_defs[idx].name;
calltip += "(";
bool found_arg = false;
for (int i = 0; i < 4; i++) {
if (builtin_func_defs[idx].args[i] == TYPE_VOID)
break;
if (i > 0)
calltip += ", ";
else
calltip += " ";
if (i == completion_argument) {
calltip += CharType(0xFFFF);
}
calltip += get_datatype_name(builtin_func_defs[idx].args[i]);
if (i == completion_argument) {
calltip += CharType(0xFFFF);
}
found_arg = true;
}
if (found_arg)
calltip += " ";
calltip += ")";
}
idx++;
}
r_call_hint = calltip;
return OK;
} break;
case COMPLETION_INDEX: {
const char colv[4] = { 'r', 'g', 'b', 'a' };
const char coordv[4] = { 'x', 'y', 'z', 'w' };
const char coordt[4] = { 's', 't', 'p', 'q' };
int limit = 0;
switch (completion_base) {
case TYPE_BVEC2:
case TYPE_IVEC2:
case TYPE_UVEC2:
case TYPE_VEC2: {
limit = 2;
} break;
case TYPE_BVEC3:
case TYPE_IVEC3:
case TYPE_UVEC3:
case TYPE_VEC3: {
limit = 3;
} break;
case TYPE_BVEC4:
case TYPE_IVEC4:
case TYPE_UVEC4:
case TYPE_VEC4: {
limit = 4;
} break;
case TYPE_MAT2: limit = 2; break;
case TYPE_MAT3: limit = 3; break;
case TYPE_MAT4: limit = 4; break;
default: {
}
}
for (int i = 0; i < limit; i++) {
r_options->push_back(ScriptCodeCompletionOption(String::chr(colv[i]), ScriptCodeCompletionOption::KIND_PLAIN_TEXT));
r_options->push_back(ScriptCodeCompletionOption(String::chr(coordv[i]), ScriptCodeCompletionOption::KIND_PLAIN_TEXT));
r_options->push_back(ScriptCodeCompletionOption(String::chr(coordt[i]), ScriptCodeCompletionOption::KIND_PLAIN_TEXT));
}
} break;
}
return ERR_PARSE_ERROR;
}
String ShaderLanguage::get_error_text() {
return error_str;
}
int ShaderLanguage::get_error_line() {
return error_line;
}
ShaderLanguage::ShaderNode *ShaderLanguage::get_shader() {
return shader;
}
ShaderLanguage::ShaderLanguage() {
nodes = NULL;
completion_class = TAG_GLOBAL;
}
ShaderLanguage::~ShaderLanguage() {
clear();
}