ShaderUnit.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. #include "ShaderUnit.h"
  2. #include "WallpaperEngine/Logging/Log.h"
  3. #include <exception>
  4. #include <regex>
  5. #include <stack>
  6. #include <string>
  7. #include <utility>
  8. #include "GLSLContext.h"
  9. #include "WallpaperEngine/Assets/AssetLoadException.h"
  10. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariable.h"
  11. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableFloat.h"
  12. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableInteger.h"
  13. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector2.h"
  14. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector3.h"
  15. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector4.h"
  16. #include "WallpaperEngine/Data/Builders/VectorBuilder.h"
  17. #include "WallpaperEngine/FileSystem/Container.h"
  18. #define SHADER_HEADER(filename) \
  19. "#version 330\n" \
  20. "// ======================================================\n" \
  21. "// Processed shader " \
  22. + filename \
  23. + "\n" \
  24. "// ======================================================\n" \
  25. "precision highp float;\n" \
  26. "#define mul(x, y) ((y) * (x))\n" \
  27. "#define max(x, y) max (y, x)\n" \
  28. "#define lerp mix\n" \
  29. "#define frac fract\n" \
  30. "#define CAST2(x) (vec2(x))\n" \
  31. "#define CAST3(x) (vec3(x))\n" \
  32. "#define CAST4(x) (vec4(x))\n" \
  33. "#define CAST3X3(x) (mat3(x))\n" \
  34. "#define float2 vec2\n" \
  35. "#define float3 vec3\n" \
  36. "#define float4 vec4\n" \
  37. "#define int2 ivec2\n" \
  38. "#define int3 ivec3\n" \
  39. "#define int4 ivec4\n" \
  40. "#define saturate(x) (clamp(x, 0.0, 1.0))\n" \
  41. "#define texSample2D texture\n" \
  42. "#define texSample2DLod textureLod\n" \
  43. "#define atan2 atan\n" \
  44. "#define fmod(x, y) ((x)-(y)*trunc((x)/(y)))\n" \
  45. "#define ddx dFdx\n" \
  46. "#define ddy(x) dFdy(-(x))\n" \
  47. "#define GLSL 1\n\n";
  48. #define FRAGMENT_SHADER_DEFINES \
  49. "out vec4 out_FragColor;\n" \
  50. "#define varying in\n"
  51. #define VERTEX_SHADER_DEFINES \
  52. "#define attribute in\n" \
  53. "#define varying out\n"
  54. #define DEFINE_COMBO(name, value) "#define " + name + " " + std::to_string (value) + "\n";
  55. using namespace WallpaperEngine::Render;
  56. using namespace WallpaperEngine::Data::Builders;
  57. using namespace WallpaperEngine::Render::Shaders;
  58. ShaderUnit::ShaderUnit (
  59. const GLSLContext::UnitType type, std::string file, std::string content, const AssetLocator& assetLocator,
  60. const ShaderConstantMap& constants, const TextureMap& passTextures, const TextureMap& overrideTextures,
  61. const ComboMap& combos, const ComboMap& overrideCombos
  62. ) :
  63. m_type (type), m_file (std::move (file)), m_content (std::move (content)), m_combos (combos),
  64. m_overrideCombos (overrideCombos), m_constants (constants), m_passTextures (passTextures),
  65. m_overrideTextures (overrideTextures), m_link (nullptr), m_assetLocator (assetLocator) {
  66. this->preprocess ();
  67. }
  68. void ShaderUnit::preprocess () {
  69. this->m_preprocessed = this->m_content;
  70. this->m_includes = "";
  71. this->preprocessIncludes ();
  72. this->preprocessRequires ();
  73. this->preprocessVariables ();
  74. this->preprocessBalanceConditionals ();
  75. const std::string from = "gl_FragColor";
  76. const std::string to = "out_FragColor";
  77. size_t start_pos = 0;
  78. while ((start_pos = this->m_preprocessed.find (from, start_pos)) != std::string::npos) {
  79. this->m_preprocessed.replace (start_pos, from.length (), to);
  80. start_pos += to.length (); // avoids re-matching if 'to' is a substring of 'from'
  81. }
  82. }
  83. void ShaderUnit::preprocessVariables () {
  84. size_t start = 0, end = 0;
  85. while ((end = this->m_preprocessed.find ('\n', start)) != std::string::npos) {
  86. std::string line = this->m_preprocessed.substr (start, end - start);
  87. const size_t combo = line.find ("// [COMBO] ");
  88. const size_t uniform = line.find ("uniform ");
  89. const size_t comment = line.find ("// ");
  90. const size_t semicolon = line.find (';');
  91. if (combo != std::string::npos) {
  92. this->parseComboConfiguration (line.substr (combo + strlen ("// [COMBO] ")), 0);
  93. } else if (
  94. uniform != std::string::npos && comment != std::string::npos && semicolon != std::string::npos &&
  95. // semicolon before comment means it's a trailing comment, not a commented-out line
  96. // (doesn't account for block comments)
  97. semicolon < comment
  98. ) {
  99. // uniforms with trailing comments never have a value assigned, which is what lets this find type/name
  100. const size_t last_space = line.find_last_of (' ', semicolon);
  101. if (last_space != std::string::npos) {
  102. const size_t previous_space = line.find_last_of (' ', last_space - 1);
  103. if (previous_space != std::string::npos) {
  104. std::string type = line.substr (previous_space + 1, last_space - previous_space - 1);
  105. std::string name = line.substr (last_space + 1, semicolon - last_space - 1);
  106. std::string json = line.substr (comment + 2);
  107. this->parseParameterConfiguration (type, name, json);
  108. }
  109. }
  110. }
  111. start = end + 1;
  112. }
  113. }
  114. void ShaderUnit::preprocessIncludes () {
  115. size_t start = 0, end = 0;
  116. while ((start = this->m_preprocessed.find ("#include", end)) != std::string::npos) {
  117. // TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
  118. const size_t quoteStart = this->m_preprocessed.find_first_of ('"', start) + 1;
  119. const size_t quoteEnd = this->m_preprocessed.find_first_of ('"', quoteStart);
  120. const std::string filename = this->m_preprocessed.substr (quoteStart, quoteEnd - quoteStart);
  121. // a missing include isn't necessarily an error - it may come from commented-out content
  122. std::string content;
  123. try {
  124. content += "// begin of include from file ";
  125. content += filename;
  126. content += "\n";
  127. content += this->m_assetLocator.includeShader (filename);
  128. content += "\n// end of included from file ";
  129. content += filename;
  130. content += "\n";
  131. } catch (AssetLoadException&) {
  132. content += "// tried including file ";
  133. content += filename;
  134. content += " but was not found\n";
  135. }
  136. // comment out just the "#i" so the string length/offsets are unaffected
  137. this->m_preprocessed = this->m_preprocessed.replace (start, 2, "//");
  138. this->m_includes += content;
  139. end = start;
  140. }
  141. // resolve #include directives found inside already-included content too
  142. end = 0;
  143. while ((start = this->m_includes.find ("#include", end)) != std::string::npos) {
  144. const size_t lineEnd = this->m_includes.find_first_of ('\n', start);
  145. // TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
  146. const size_t quoteStart = this->m_includes.find_first_of ('"', start) + 1;
  147. const size_t quoteEnd = this->m_includes.find_first_of ('"', quoteStart);
  148. const std::string filename = this->m_includes.substr (quoteStart, quoteEnd - quoteStart);
  149. // a missing include isn't necessarily an error - it may come from commented-out content
  150. std::string content;
  151. try {
  152. content = "// begin of include from file ";
  153. content += filename;
  154. content += "\n";
  155. content += this->m_assetLocator.includeShader (filename);
  156. content += "\n// end of included from file ";
  157. content += filename;
  158. content += "\n";
  159. } catch (AssetLoadException&) {
  160. content = "// tried including file ";
  161. content += filename;
  162. content += " but was not found\n";
  163. }
  164. this->m_includes = this->m_includes.replace (start, lineEnd - start, content);
  165. end = start;
  166. }
  167. // place the accumulated include contents right before the main function
  168. end = 0;
  169. bool includesAdded = false;
  170. while ((start = this->m_preprocessed.find (" main", end)) != std::string::npos) {
  171. char value = this->m_preprocessed.at (start + 5);
  172. end = start + 5;
  173. if (value != ' ' && value != '(') {
  174. continue;
  175. }
  176. size_t lastAttribute = this->m_preprocessed.rfind ("attribute", start);
  177. size_t lastVarying = this->m_preprocessed.rfind ("varying", start);
  178. size_t lastUniform = this->m_preprocessed.rfind ("uniform", start);
  179. size_t latest = lastAttribute;
  180. if (latest == std::string::npos) {
  181. latest = lastVarying;
  182. } else if (latest < lastVarying && lastVarying != std::string::npos) {
  183. latest = lastVarying;
  184. }
  185. if (latest == std::string::npos) {
  186. latest = lastUniform;
  187. } else if (latest < lastUniform && lastUniform != std::string::npos) {
  188. latest = lastUniform;
  189. }
  190. if (latest < start) {
  191. // find the end of the current line
  192. latest = this->m_preprocessed.find ('\n', latest);
  193. } else {
  194. // find the end of the previous line
  195. latest = this->m_preprocessed.rfind ('\n', start);
  196. }
  197. // start points at the end of the previous line, used below to place the includes
  198. start = this->m_preprocessed.rfind ('\n', start);
  199. // tracks nested #if/#endif so the includes can be moved before the start of the enclosing chain
  200. std::stack<size_t> ifdefStack;
  201. const std::regex ifdef (R"((#if|#endif))");
  202. std::smatch match;
  203. size_t current = 0;
  204. while (
  205. std::regex_search (this->m_preprocessed.cbegin () + current, this->m_preprocessed.cend (), match, ifdef)) {
  206. current += match.position ();
  207. if (this->m_preprocessed.substr (current, 3) == "#if") {
  208. ifdefStack.push (current++); // advance past this match so regex_search doesn't rematch it
  209. continue;
  210. }
  211. current++; // same reason: advance past this match
  212. // an unmatched #endif is most likely a syntax error; ignored for now
  213. if (ifdefStack.empty ()) {
  214. continue;
  215. }
  216. size_t stackStart = ifdefStack.top ();
  217. ifdefStack.pop ();
  218. if (latest > stackStart && latest <= current) {
  219. // insertion point is inside a conditional block - move before the #if so includes are
  220. // available to all branches (e.g. genericropeparticle.vert has #if GS_ENABLED wrapping two main()s)
  221. size_t beforeIfdef = this->m_preprocessed.rfind ('\n', stackStart);
  222. latest = (beforeIfdef != std::string::npos) ? beforeIfdef : 0;
  223. }
  224. }
  225. // TODO: IS THIS GOOD ENOUGH? MAYBE WE SHOULD BE GETTING THE FIRST #IF BLOCK INSTEAD?
  226. latest = std::min (latest, start);
  227. this->m_preprocessed.insert (latest + 1, this->m_includes + '\n');
  228. includesAdded = true;
  229. break;
  230. }
  231. if (!includesAdded) {
  232. sLog.exception ("Could not find where to place includes for shader unit ", this->m_file);
  233. }
  234. }
  235. void ShaderUnit::preprocessRequires () {
  236. size_t start = 0, end = 0;
  237. while ((start = this->m_preprocessed.find ("#require", end)) != std::string::npos) {
  238. const size_t lineEnd = this->m_preprocessed.find_first_of ('\n', start);
  239. const size_t nameStart = start + std::string ("#require ").length ();
  240. if (nameStart >= lineEnd) {
  241. sLog.error ("Malformed #require directive (no module name) in shader ", this->m_file);
  242. end = lineEnd;
  243. continue;
  244. }
  245. std::string moduleName = this->m_preprocessed.substr (nameStart, lineEnd - nameStart);
  246. while (!moduleName.empty () && (moduleName.back () == ' ' || moduleName.back () == '\r')) {
  247. moduleName.pop_back ();
  248. }
  249. if (moduleName.empty ()) {
  250. sLog.error ("Malformed #require directive (empty module name) in shader ", this->m_file);
  251. end = lineEnd;
  252. continue;
  253. }
  254. sLog.out ("Resolving require module: ", moduleName, " in shader ", this->m_file);
  255. std::string moduleCode = this->resolveRequireModule (moduleName);
  256. this->m_preprocessed = this->m_preprocessed.replace (start, 2, "//");
  257. if (!moduleCode.empty ()) {
  258. // inserted directly here, not appended to m_includes - that was already consumed by preprocessIncludes
  259. this->m_preprocessed.insert (start, moduleCode);
  260. end = start + moduleCode.length ();
  261. } else {
  262. end = lineEnd;
  263. }
  264. }
  265. }
  266. std::string ShaderUnit::resolveRequireModule (const std::string& moduleName) const {
  267. if (moduleName == "LightingV1") {
  268. return this->generateLightingV1 ();
  269. }
  270. sLog.error ("Unknown #require module: ", moduleName, " in shader ", this->m_file);
  271. return "";
  272. }
  273. std::string ShaderUnit::generateLightingV1 () const {
  274. // Wallpaper Engine generates this from the scene's light sources; since light objects
  275. // aren't supported yet, stub it out with no dynamic light contribution.
  276. return "// begin of generated module LightingV1\n"
  277. "vec3 PerformLighting_V1(vec3 worldPos, vec3 albedo, vec3 normal, vec3 viewDir,\n"
  278. " vec3 specularTint, vec3 baseReflectance, float roughness, float metallic)\n"
  279. "{\n"
  280. " return vec3(0.0);\n"
  281. "}\n"
  282. "// end of generated module LightingV1\n";
  283. }
  284. void ShaderUnit::preprocessBalanceConditionals () {
  285. static const std::regex directive (R"((?:^|\n)[ \t]*#(ifndef|ifdef|if|endif)\b)");
  286. int depth = 0;
  287. std::vector<size_t> extraEndifs;
  288. auto begin = std::sregex_iterator (this->m_preprocessed.cbegin (), this->m_preprocessed.cend (), directive);
  289. auto end = std::sregex_iterator ();
  290. for (auto it = begin; it != end; ++it) {
  291. const std::string& keyword = (*it)[1].str ();
  292. if (keyword == "endif") {
  293. if (depth == 0) {
  294. // position of the '#' character for this directive
  295. extraEndifs.push_back (it->position (1) - 1);
  296. } else {
  297. depth--;
  298. }
  299. } else {
  300. depth++;
  301. }
  302. }
  303. // comment out the extra #endif directives, from the end so earlier offsets stay valid
  304. for (auto it = extraEndifs.rbegin (); it != extraEndifs.rend (); ++it) {
  305. sLog.out ("Found #endif with no matching #if in shader ", this->m_file, ", ignoring it");
  306. this->m_preprocessed.replace (*it, 2, "//");
  307. }
  308. }
  309. std::string ShaderUnit::applyLinkedVaryingCompatibility (std::string source) const {
  310. if (this->m_type != GLSLContext::UnitType_Vertex || this->m_link == nullptr) {
  311. return source;
  312. }
  313. std::regex fragmentVec4Varying (R"(\bvarying\s+vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
  314. std::smatch varyingMatch;
  315. std::string linked = this->m_link->m_preprocessed;
  316. size_t linkedOffset = 0;
  317. while (std::regex_search (linked.cbegin () + linkedOffset, linked.cend (), varyingMatch, fragmentVec4Varying)) {
  318. const std::string name = varyingMatch[1].str ();
  319. linkedOffset += varyingMatch.position () + varyingMatch.length ();
  320. const std::regex vertexVec2Decl ("\\bvarying\\s+vec2\\s+" + name + "\\s*;");
  321. if (!std::regex_search (source, vertexVec2Decl)) {
  322. continue;
  323. }
  324. source = std::regex_replace (source, vertexVec2Decl, "varying vec4 " + name + ";");
  325. const std::regex assignment ("(^|\\n)([ \\t]*)" + name + "\\s*=\\s*([^;\\n]+);");
  326. std::smatch assignmentMatch;
  327. size_t offset = 0;
  328. while (std::regex_search (source.cbegin () + offset, source.cend (), assignmentMatch, assignment)) {
  329. const std::string prefix = assignmentMatch[1].str ();
  330. const std::string indent = assignmentMatch[2].str ();
  331. const std::string expression = assignmentMatch[3].str ();
  332. const std::string replacement = prefix + indent + name + " = vec4(" + expression + ", 0.0, 1.0);";
  333. const size_t position = offset + assignmentMatch.position ();
  334. source.replace (position, assignmentMatch.length (), replacement);
  335. offset = position + replacement.length ();
  336. }
  337. }
  338. return source;
  339. }
  340. std::string ShaderUnit::applyFragmentTexCoordCompatibility (std::string source) const {
  341. if (this->m_type != GLSLContext::UnitType_Fragment) {
  342. return source;
  343. }
  344. const std::regex texCoordBeforeCast2 (R"(\bv_TexCoord\b(\s*[-+*/]\s*CAST2\s*\())");
  345. const std::regex cast2BeforeTexCoord (R"((CAST2\s*\([^)]+\)\s*[-+*/]\s*)\bv_TexCoord\b)");
  346. const std::regex wideTexCoordDecl (R"(\bvarying\s+vec[34]\s+v_TexCoord\s*;)");
  347. if (!std::regex_search (source, wideTexCoordDecl)
  348. || (!std::regex_search (source, texCoordBeforeCast2) && !std::regex_search (source, cast2BeforeTexCoord))) {
  349. return source;
  350. }
  351. const std::string original = source;
  352. source = std::regex_replace (source, texCoordBeforeCast2, "v_TexCoord.xy$1");
  353. source = std::regex_replace (source, cast2BeforeTexCoord, "$1v_TexCoord.xy");
  354. if (source != original) {
  355. sLog.out ("Applied fragment TexCoord vec2 compatibility in ", this->m_file);
  356. }
  357. return source;
  358. }
  359. std::string ShaderUnit::applyFragmentVaryingShadowCompatibility (std::string source) const {
  360. if (this->m_type != GLSLContext::UnitType_Fragment) {
  361. return source;
  362. }
  363. static const std::regex varyingDecl (R"(\bvarying\s+(vec[234]|float)\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
  364. std::vector<std::pair<std::string, std::string>> shadowed;
  365. for (auto it = std::sregex_iterator (source.cbegin (), source.cend (), varyingDecl); it != std::sregex_iterator ();
  366. ++it) {
  367. const std::string type = (*it)[1].str ();
  368. const std::string name = (*it)[2].str ();
  369. // only shadow varyings the shader actually reassigns - a plain read-only "in" is fine as-is,
  370. // and touching the declaration unnecessarily risks breaking a shader that works today
  371. const std::regex assignmentUse (
  372. "\\b" + name + "\\b(?:\\.[xyzwrgba]+)?\\s*(?:=(?!=)|\\+=|-=|\\*=|/=)"
  373. );
  374. if (!std::regex_search (source, assignmentUse)) {
  375. continue;
  376. }
  377. shadowed.emplace_back (type, name);
  378. }
  379. if (shadowed.empty ()) {
  380. return source;
  381. }
  382. static const std::regex mainOpen (R"(\bvoid\s+main\s*\([^)]*\)\s*\{)");
  383. std::smatch mainMatch;
  384. if (!std::regex_search (source, mainMatch, mainOpen)) {
  385. return source;
  386. }
  387. // shadows each with a same-named local, initialized from the real (read-only) input, so the
  388. // rest of main() can keep mutating it exactly like the original compatibility-profile shader did
  389. std::string shadowCode;
  390. for (const auto& [type, name] : shadowed) {
  391. shadowCode += " " + type + " wpeShadowIn_" + name + " = " + name + "; " + type + " " + name + " = wpeShadowIn_" + name + ";";
  392. }
  393. const size_t insertAt = mainMatch.position (0) + mainMatch.length (0);
  394. source.insert (insertAt, shadowCode);
  395. std::string names;
  396. for (const auto& [type, name] : shadowed) {
  397. names += (names.empty () ? "" : ", ") + name;
  398. }
  399. sLog.out ("Applied fragment varying shadow compatibility in ", this->m_file, " for ", names);
  400. return source;
  401. }
  402. void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
  403. // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
  404. JSON data;
  405. try {
  406. data = JSON::parse (content);
  407. } catch (const std::exception& e) {
  408. sLog.error ("Cannot parse combo metadata in shader ", this->m_file, ": ", e.what ());
  409. return;
  410. }
  411. const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
  412. // "type" is ignored - appears to be editor-only metadata
  413. const auto defvalue = data.find ("default");
  414. const auto entry = this->m_combos.find (combo);
  415. const auto entryOverride = this->m_overrideCombos.find (combo);
  416. this->m_usedCombos.emplace (combo, true);
  417. // not predefined anywhere -> fall back to the JSON's own default value
  418. if (entry == this->m_combos.end () && entryOverride == this->m_overrideCombos.end ()) {
  419. if (defvalue == data.end ()) {
  420. // TODO: PROPERLY SUPPORT EMPTY COMBOS
  421. this->m_discoveredCombos.emplace (combo, defaultValue);
  422. } else if (defvalue->is_number_float ()) {
  423. sLog.exception ("float combos are not supported in shader ", this->m_file, ". ", combo);
  424. } else if (defvalue->is_number_integer ()) {
  425. this->m_discoveredCombos.emplace (combo, defvalue->get<int> ());
  426. } else if (defvalue->is_string ()) {
  427. sLog.exception ("string combos are not supported in shader ", this->m_file, ". ", combo);
  428. } else {
  429. sLog.exception ("cannot parse combo information ", combo, ". unknown type for ", defvalue->dump ());
  430. }
  431. }
  432. }
  433. void ShaderUnit::parseParameterConfiguration (
  434. const std::string& type, const std::string& name, const std::string& content
  435. ) {
  436. JSON data;
  437. try {
  438. data = JSON::parse (content);
  439. } catch (const std::exception& e) {
  440. sLog.error ("Cannot parse parameter metadata for ", name, " in shader ", this->m_file, ": ", e.what ());
  441. return;
  442. }
  443. const auto material = data.optional ("material");
  444. const auto defvalue = data.optional ("default");
  445. const auto combo = data.find ("combo");
  446. auto constant = this->m_constants.end ();
  447. if (material.has_value ()) {
  448. constant = this->m_constants.find (*material);
  449. }
  450. if (constant == this->m_constants.end () && !defvalue.has_value ()) {
  451. if (type != "sampler2D") {
  452. sLog.exception ("Cannot parse parameter data for ", name, " in shader ", this->m_file);
  453. }
  454. }
  455. Variables::ShaderVariable* parameter = nullptr;
  456. if (type == "vec4") {
  457. parameter
  458. = new Variables::ShaderVariableVector4 (VectorBuilder::parse<glm::vec4> (defvalue->get<std::string> ()));
  459. } else if (type == "vec3") {
  460. parameter = new Variables::ShaderVariableVector3 (VectorBuilder::parse<glm::vec3> (*defvalue));
  461. } else if (type == "vec2") {
  462. parameter = new Variables::ShaderVariableVector2 (VectorBuilder::parse<glm::vec2> (*defvalue));
  463. } else if (type == "float") {
  464. if (defvalue->is_string ()) {
  465. parameter = new Variables::ShaderVariableFloat (std::stoi (defvalue->get<std::string> ()));
  466. } else {
  467. parameter = new Variables::ShaderVariableFloat (defvalue->get<float> ());
  468. }
  469. } else if (type == "int") {
  470. if (defvalue->is_string ()) {
  471. parameter = new Variables::ShaderVariableInteger (std::stoi (defvalue->get<std::string> ()));
  472. } else {
  473. parameter = new Variables::ShaderVariableInteger (defvalue->get<int> ());
  474. }
  475. } else if (type == "sampler2D" || type == "sampler2DComparison") {
  476. const auto textureName = data.find ("default");
  477. // TODO: CREATE TEXTURE WITH THE GIVEN COLOR
  478. const char value = name.at (std::string ("g_Texture").length ());
  479. const auto requireany = data.find ("requireany");
  480. const auto require = data.find ("require");
  481. // TODO: BETTER CONVERSION HERE
  482. size_t index = value - '0';
  483. // TODO: SUPPORT USER TEXTURES!!
  484. if (combo != data.end ()) {
  485. // TODO: CLEANUP HOW THIS IS DETERMINED FIRST
  486. const auto textureSlotUsed
  487. = this->m_passTextures.contains (index) || this->m_overrideTextures.contains (index);
  488. bool isRequired = false;
  489. int comboValue = 1;
  490. if (textureSlotUsed) {
  491. // texture already exists, so the combo must be set; these tend to have no default value
  492. isRequired = true;
  493. } else if (require != data.end ()) {
  494. if (requireany != data.end () && requireany->get<bool> ()) {
  495. // requireany: any one mismatching value makes this required (OR semantics)
  496. for (const auto& item : require->items ()) {
  497. const std::string& macro = item.key ();
  498. const auto it = this->m_combos.find (macro);
  499. if (it == this->m_combos.end () || this->m_overrideCombos.contains (macro)
  500. || it->second != item.value ()) {
  501. isRequired = true;
  502. break;
  503. }
  504. }
  505. } else {
  506. isRequired = true;
  507. // require without requireany: every listed value must match (AND semantics)
  508. for (const auto& item : require->items ()) {
  509. const std::string& macro = item.key ();
  510. const auto it = this->m_combos.find (macro);
  511. // a missing macro is fine here, only the value comparison matters
  512. if ((it != this->m_combos.end () || this->m_overrideCombos.contains (macro))
  513. && it->second == item.value ()) {
  514. isRequired = false;
  515. break;
  516. }
  517. }
  518. }
  519. }
  520. if (isRequired && !textureSlotUsed) {
  521. if (!defvalue.has_value ()) {
  522. isRequired = false;
  523. } else {
  524. if (this->m_combos.contains (*combo) || this->m_overrideCombos.contains (*combo)) {
  525. isRequired = false;
  526. } else if (defvalue->is_string ()) {
  527. comboValue = std::stoi (defvalue->get<std::string> ().c_str ());
  528. } else if (defvalue->is_number ()) {
  529. comboValue = *defvalue;
  530. } else {
  531. sLog.exception (
  532. "Cannot determine default value for combo ", combo->get<std::string> (),
  533. " because it's not specified by the shader and is not given a default value: ", this->m_file
  534. );
  535. }
  536. }
  537. }
  538. if (isRequired) {
  539. this->m_discoveredCombos.emplace (*combo, comboValue);
  540. this->m_usedCombos.emplace (*combo, true);
  541. }
  542. }
  543. // Some shaders (e.g. effects/refract.frag's normal map) declare `"default":""` on purpose -
  544. // an explicitly empty default means "no texture unless the object's own effect config
  545. // supplies one", not "a texture literally named the empty string". Registering it anyway
  546. // sent every such object into a doomed asset lookup for a blank path on every use of that
  547. // shader, whether or not the combo gating it was even active.
  548. if (textureName != data.end () && !textureName->get<std::string> ().empty ()) {
  549. this->m_defaultTextures.emplace (index, *textureName);
  550. }
  551. return;
  552. } else {
  553. sLog.error ("Unknown parameter type: ", type, " for ", name, " in shader ", this->m_file);
  554. return;
  555. }
  556. if (material.has_value () && parameter != nullptr) {
  557. parameter->setIdentifierName (*material);
  558. parameter->setName (name);
  559. this->m_parameters.push_back (parameter);
  560. }
  561. }
  562. const ComboMap& ShaderUnit::getCombos () const { return this->m_combos; }
  563. const ComboMap& ShaderUnit::getDiscoveredCombos () const { return this->m_discoveredCombos; }
  564. void ShaderUnit::linkToUnit (const ShaderUnit* unit) { this->m_link = unit; }
  565. const ShaderUnit* ShaderUnit::getLinkedUnit () const { return this->m_link; }
  566. const std::string& ShaderUnit::compile () {
  567. if (!this->m_final.empty ()) {
  568. return this->m_final;
  569. }
  570. this->m_final = SHADER_HEADER (this->m_file);
  571. // GLSL has no builtin log10 (unlike HLSL), so some shaders provide their own under "#if GLSL"
  572. // (which is always true here). Adding a blanket compatibility macro would get expanded right
  573. // over such a shader's own function definition and mangle it, so only add it when the shader
  574. // doesn't already define log10 itself.
  575. static const std::regex log10Definition (
  576. R"(\b(?:void|float|int|uint|bool|vec[234]|ivec[234]|uvec[234]|bvec[234]|mat[234](?:x[234])?)\s+log10\s*\()"
  577. );
  578. if (!std::regex_search (this->m_content, log10Definition)) {
  579. this->m_final += "#define log10(x) (log2(x) * 0.301029995663981)\n";
  580. }
  581. if (this->m_type == GLSLContext::UnitType_Fragment) {
  582. this->m_final += FRAGMENT_SHADER_DEFINES;
  583. } else {
  584. this->m_final += VERTEX_SHADER_DEFINES;
  585. }
  586. std::map<std::string, bool> addedCombos;
  587. for (const auto& [name, value] : this->m_overrideCombos) {
  588. std::string uppercase;
  589. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  590. if (!addedCombos.contains (uppercase)) {
  591. this->m_final += DEFINE_COMBO (uppercase, value);
  592. addedCombos.emplace (uppercase, true);
  593. }
  594. }
  595. for (const auto& [name, value] : this->m_combos) {
  596. std::string uppercase;
  597. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  598. if (!addedCombos.contains (uppercase)) {
  599. this->m_final += DEFINE_COMBO (uppercase, value);
  600. addedCombos.emplace (uppercase, true);
  601. }
  602. }
  603. for (const auto& [name, value] : this->m_discoveredCombos) {
  604. std::string uppercase;
  605. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  606. if (!addedCombos.contains (uppercase)) {
  607. this->m_final += DEFINE_COMBO (uppercase, value);
  608. addedCombos.emplace (uppercase, true);
  609. }
  610. }
  611. if (this->m_link != nullptr) {
  612. for (const auto& [name, value] : this->m_link->getCombos ()) {
  613. std::string uppercase;
  614. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  615. if (!addedCombos.contains (uppercase)) {
  616. this->m_final += DEFINE_COMBO (uppercase, value);
  617. addedCombos.emplace (uppercase, true);
  618. }
  619. }
  620. for (const auto& [name, value] : this->m_link->getDiscoveredCombos ()) {
  621. std::string uppercase;
  622. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  623. if (!addedCombos.contains (uppercase)) {
  624. this->m_final += DEFINE_COMBO (uppercase, value);
  625. addedCombos.emplace (uppercase, true);
  626. }
  627. }
  628. }
  629. this->m_final += this->applyFragmentVaryingShadowCompatibility (
  630. this->applyFragmentTexCoordCompatibility (this->applyLinkedVaryingCompatibility (this->m_preprocessed))
  631. );
  632. // actual GLSL compilation happens in the pass, which has the context this unit doesn't
  633. return this->m_final;
  634. }
  635. const std::vector<Variables::ShaderVariable*>& ShaderUnit::getParameters () const { return this->m_parameters; }
  636. const TextureMap& ShaderUnit::getTextures () const { return this->m_defaultTextures; }