Path: blob/master/tests/servers/rendering/test_shader_preprocessor.h
10279 views
/**************************************************************************/1/* test_shader_preprocessor.h */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#pragma once3132#include "servers/rendering/shader_preprocessor.h"3334#include "tests/test_macros.h"3536#include <cctype>3738namespace TestShaderPreprocessor {3940void erase_all_empty(Vector<String> &p_vec) {41int idx = p_vec.find(" ");42while (idx >= 0) {43p_vec.remove_at(idx);44idx = p_vec.find(" ");45}46}4748bool is_variable_char(unsigned char c) {49return std::isalnum(c) || c == '_';50}5152bool is_operator_char(unsigned char c) {53return (c == '*') || (c == '+') || (c == '-') || (c == '/') || ((c >= '<') && (c <= '>'));54}5556// Remove unnecessary spaces from a line.57String remove_spaces(String &p_str) {58String res;59// Result is guaranteed to not be longer than the input.60res.resize_uninitialized(p_str.size());61int wp = 0;62char32_t last = 0;63bool has_removed = false;6465for (int n = 0; n < p_str.size(); n++) {66// These test cases only use ASCII.67unsigned char c = static_cast<unsigned char>(p_str[n]);68if (std::isblank(c)) {69has_removed = true;70} else {71if (has_removed) {72// Insert a space to avoid joining things that could potentially form a new token.73// E.g. "float x" or "- -".74if ((is_variable_char(c) && is_variable_char(last)) ||75(is_operator_char(c) && is_operator_char(last))) {76res[wp++] = ' ';77}78has_removed = false;79}80res[wp++] = c;81last = c;82}83}84res.resize_uninitialized(wp);85return res;86}8788// The pre-processor changes indentation and inserts spaces when inserting macros.89// Re-format the code, without changing its meaning, to make it easier to compare.90String compact_spaces(String &p_str) {91Vector<String> lines = p_str.split("\n", false);92erase_all_empty(lines);93for (String &line : lines) {94line = remove_spaces(line);95}96return String("\n").join(lines);97}9899#define CHECK_SHADER_EQ(a, b) CHECK_EQ(compact_spaces(a), compact_spaces(b))100#define CHECK_SHADER_NE(a, b) CHECK_NE(compact_spaces(a), compact_spaces(b))101102TEST_CASE("[ShaderPreprocessor] Simple defines") {103String code(104"#define X 1.0 // comment\n"105"#define Y mix\n"106"#define Z X\n"107"\n"108"#define func0 \\\n"109" vec3 my_fun(vec3 arg) {\\\n"110" return pow(arg, 2.2);\\\n"111" }\n"112"\n"113"func0\n"114"\n"115"fragment() {\n"116" ALBEDO = vec3(X);\n"117" float x = Y(0., Z, X);\n"118" #undef X\n"119" float X = x;\n"120" x = -Z;\n"121"}\n");122String expected(123"vec3 my_fun(vec3 arg) { return pow(arg, 2.2); }\n"124"\n"125"fragment() {\n"126" ALBEDO = vec3( 1.0 );\n"127" float x = mix(0., 1.0 , 1.0 );\n"128" float X = x;\n"129" x = -X;\n"130"}\n");131String result;132133ShaderPreprocessor preprocessor;134CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);135136CHECK_SHADER_EQ(result, expected);137}138139TEST_CASE("[ShaderPreprocessor] Avoid merging adjacent tokens") {140String code(141"#define X -10\n"142"#define Y(s) s\n"143"\n"144"fragment() {\n"145" float v = 1.0-X-Y(-2);\n"146"}\n");147String expected(148"fragment() {\n"149" float v = 1.0 - -10 - -2;\n"150"}\n");151String result;152153ShaderPreprocessor preprocessor;154CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);155156CHECK_SHADER_EQ(result, expected);157}158159TEST_CASE("[ShaderPreprocessor] Complex defines") {160String code(161"const float X = 2.0;\n"162"#define A(X) X*2.\n"163"#define X 1.0\n"164"#define Y Z(X, W)\n"165"#define Z max\n"166"#define C(X, Y) Z(A(Y), B(X))\n"167"#define W -X\n"168"#define B(X) X*3.\n"169"\n"170"fragment() {\n"171" float x = Y;\n"172" float y = C(5., 7.0);\n"173"}\n");174String expected(175"const float X = 2.0;\n"176"fragment() {\n"177" float x = max(1.0, - 1.0);\n"178" float y = max(7.0*2. , 5.*3.);\n"179"}\n");180String result;181182ShaderPreprocessor preprocessor;183CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);184185CHECK_SHADER_EQ(result, expected);186}187188TEST_CASE("[ShaderPreprocessor] Concatenation") {189String code(190"fragment() {\n"191" #define X 1 // this is fine ##\n"192" #define y 2\n"193" #define z 3##.## 1## 4 ## 59\n"194" #define Z(y) X ## y\n"195" #define Z2(y) y##X\n"196" #define W(y) X, y\n"197" #define A(x) fl## oat a = 1##x ##.3 ## x\n"198" #define C(x, y) x##.##y\n"199" #define J(x) x##=\n"200" float Z(y) = 1.2;\n"201" float Z(z) = 2.3;\n"202" float Z2(y) = z;\n"203" float Z2(z) = 2.3;\n"204" int b = max(W(3));\n"205" Xy J(+) b J(=) 3 ? 0.1 : 0.2;\n"206" A(9);\n"207" Xy = C(X, y);\n"208"}\n");209String expected(210"fragment() {\n"211" float Xy = 1.2;\n"212" float Xz = 2.3;\n"213" float yX = 3.1459;\n"214" float zX = 2.3;\n"215" int b = max(1, 3);\n"216" Xy += b == 3 ? 0.1 : 0.2;\n"217" float a = 19.39;\n"218" Xy = 1.2;\n"219"}\n");220String result;221222ShaderPreprocessor preprocessor;223CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);224225CHECK_SHADER_EQ(result, expected);226}227228TEST_CASE("[ShaderPreprocessor] Nested concatenation") {229// Concatenation ## should not expand adjacent tokens if they are macros,230// but this is currently not implemented in Godot's shader preprocessor.231// To force expanding, an extra macro should be required (B in this case).232233String code(234"fragment() {\n"235" vec2 X = vec2(0);\n"236" #define X 1\n"237" #define y 2\n"238" #define B(x, y) C(x, y)\n"239" #define C(x, y) x##.##y\n"240" C(X, y) = B(X, y);\n"241"}\n");242String expected(243"fragment() {\n"244" vec2 X = vec2(0);\n"245" X.y = 1.2;\n"246"}\n");247String result;248249ShaderPreprocessor preprocessor;250CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);251252// TODO: Reverse the check when/if this is changed.253CHECK_SHADER_NE(result, expected);254}255256TEST_CASE("[ShaderPreprocessor] Concatenation sorting network") {257String code(258"fragment() {\n"259" #define ARR(X) test##X\n"260" #define ACMP(a, b) ARR(a) > ARR(b)\n"261" #define ASWAP(a, b) tmp = ARR(b); ARR(b) = ARR(a); ARR(a) = tmp;\n"262" #define ACSWAP(a, b) if(ACMP(a, b)) { ASWAP(a, b) }\n"263" float test0 = 1.2;\n"264" float test1 = 0.34;\n"265" float test3 = 0.8;\n"266" float test4 = 2.9;\n"267" float tmp;\n"268" ACSWAP(0,2)\n"269" ACSWAP(1,3)\n"270" ACSWAP(0,1)\n"271" ACSWAP(2,3)\n"272" ACSWAP(1,2)\n"273"}\n");274String expected(275"fragment() {\n"276" float test0 = 1.2;\n"277" float test1 = 0.34;\n"278" float test3 = 0.8;\n"279" float test4 = 2.9;\n"280" float tmp;\n"281" if(test0 > test2) { tmp = test2; test2 = test0; test0 = tmp; }\n"282" if(test1 > test3) { tmp = test3; test3 = test1; test1 = tmp; }\n"283" if(test0 > test1) { tmp = test1; test1 = test0; test0 = tmp; }\n"284" if(test2 > test3) { tmp = test3; test3 = test2; test2 = tmp; }\n"285" if(test1 > test2) { tmp = test2; test2 = test1; test1 = tmp; }\n"286"}\n");287String result;288289ShaderPreprocessor preprocessor;290CHECK_EQ(preprocessor.preprocess(code, String("file.gdshader"), result), Error::OK);291292CHECK_SHADER_EQ(result, expected);293}294295TEST_CASE("[ShaderPreprocessor] Undefined behavior") {296// None of these are valid concatenation, nor valid shader code.297// Don't care about results, just make sure there's no crash.298const String filename("somefile.gdshader");299String result;300ShaderPreprocessor preprocessor;301302preprocessor.preprocess("#define X ###\nX\n", filename, result);303preprocessor.preprocess("#define X ####\nX\n", filename, result);304preprocessor.preprocess("#define X #####\nX\n", filename, result);305preprocessor.preprocess("#define X 1 ### 2\nX\n", filename, result);306preprocessor.preprocess("#define X 1 #### 2\nX\n", filename, result);307preprocessor.preprocess("#define X 1 ##### 2\nX\n", filename, result);308preprocessor.preprocess("#define X ### 2\nX\n", filename, result);309preprocessor.preprocess("#define X #### 2\nX\n", filename, result);310preprocessor.preprocess("#define X ##### 2\nX\n", filename, result);311preprocessor.preprocess("#define X 1 ###\nX\n", filename, result);312preprocessor.preprocess("#define X 1 ####\nX\n", filename, result);313preprocessor.preprocess("#define X 1 #####\nX\n", filename, result);314}315316TEST_CASE("[ShaderPreprocessor] Invalid concatenations") {317const String filename("somefile.gdshader");318String result;319ShaderPreprocessor preprocessor;320321CHECK_NE(preprocessor.preprocess("#define X ##", filename, result), Error::OK);322CHECK_NE(preprocessor.preprocess("#define X 1 ##", filename, result), Error::OK);323CHECK_NE(preprocessor.preprocess("#define X ## 1", filename, result), Error::OK);324CHECK_NE(preprocessor.preprocess("#define X(y) ## ", filename, result), Error::OK);325CHECK_NE(preprocessor.preprocess("#define X(y) y ## ", filename, result), Error::OK);326CHECK_NE(preprocessor.preprocess("#define X(y) ## y", filename, result), Error::OK);327}328329} // namespace TestShaderPreprocessor330331332