ShaderUnit.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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 log10(x) (log2(x) * 0.301029995663981)\n" \
  44. "#define atan2 atan\n" \
  45. "#define fmod(x, y) ((x)-(y)*trunc((x)/(y)))\n" \
  46. "#define ddx dFdx\n" \
  47. "#define ddy(x) dFdy(-(x))\n" \
  48. "#define GLSL 1\n\n";
  49. #define FRAGMENT_SHADER_DEFINES \
  50. "out vec4 out_FragColor;\n" \
  51. "#define varying in\n"
  52. #define VERTEX_SHADER_DEFINES \
  53. "#define attribute in\n" \
  54. "#define varying out\n"
  55. #define DEFINE_COMBO(name, value) "#define " + name + " " + std::to_string (value) + "\n";
  56. using namespace WallpaperEngine::Render;
  57. using namespace WallpaperEngine::Data::Builders;
  58. using namespace WallpaperEngine::Render::Shaders;
  59. ShaderUnit::ShaderUnit (
  60. const GLSLContext::UnitType type, std::string file, std::string content, const AssetLocator& assetLocator,
  61. const ShaderConstantMap& constants, const TextureMap& passTextures, const TextureMap& overrideTextures,
  62. const ComboMap& combos, const ComboMap& overrideCombos
  63. ) :
  64. m_type (type), m_file (std::move (file)), m_content (std::move (content)), m_combos (combos),
  65. m_overrideCombos (overrideCombos), m_constants (constants), m_passTextures (passTextures),
  66. m_overrideTextures (overrideTextures), m_link (nullptr), m_assetLocator (assetLocator) {
  67. // pre-process the shader so the units are clear
  68. this->preprocess ();
  69. }
  70. void ShaderUnit::preprocess () {
  71. this->m_preprocessed = this->m_content;
  72. this->m_includes = "";
  73. this->preprocessIncludes ();
  74. this->preprocessRequires ();
  75. this->preprocessVariables ();
  76. // replace gl_FragColor with the equivalent
  77. const std::string from = "gl_FragColor";
  78. const std::string to = "out_FragColor";
  79. size_t start_pos = 0;
  80. while ((start_pos = this->m_preprocessed.find (from, start_pos)) != std::string::npos) {
  81. this->m_preprocessed.replace (start_pos, from.length (), to);
  82. start_pos += to.length (); // Handles case where 'to' is a substring of 'from'
  83. }
  84. }
  85. void ShaderUnit::preprocessVariables () {
  86. size_t start = 0, end = 0;
  87. while ((end = this->m_preprocessed.find ('\n', start)) != std::string::npos) {
  88. // Extract a line from the string
  89. std::string line = this->m_preprocessed.substr (start, end - start);
  90. const size_t combo = line.find ("// [COMBO] ");
  91. const size_t uniform = line.find ("uniform ");
  92. const size_t comment = line.find ("// ");
  93. const size_t semicolon = line.find (';');
  94. if (combo != std::string::npos) {
  95. this->parseComboConfiguration (line.substr (combo + strlen ("// [COMBO] ")), 0);
  96. } else if (
  97. uniform != std::string::npos && comment != std::string::npos && semicolon != std::string::npos &&
  98. // this check ensures that the comment is after the semicolon (so it's not a commented-out line)
  99. // this needs further refining as it's not taking into account block comments
  100. semicolon < comment
  101. ) {
  102. // uniforms with comments should never have a value assigned, use this fact to detect the required parts
  103. const size_t last_space = line.find_last_of (' ', semicolon);
  104. if (last_space != std::string::npos) {
  105. const size_t previous_space = line.find_last_of (' ', last_space - 1);
  106. if (previous_space != std::string::npos) {
  107. // extract type and name
  108. std::string type = line.substr (previous_space + 1, last_space - previous_space - 1);
  109. std::string name = line.substr (last_space + 1, semicolon - last_space - 1);
  110. std::string json = line.substr (comment + 2);
  111. this->parseParameterConfiguration (type, name, json);
  112. }
  113. }
  114. }
  115. // Move to the next line
  116. start = end + 1;
  117. }
  118. }
  119. void ShaderUnit::preprocessIncludes () {
  120. size_t start = 0, end = 0;
  121. // prepare the include content
  122. while ((start = this->m_preprocessed.find ("#include", end)) != std::string::npos) {
  123. // TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
  124. const size_t quoteStart = this->m_preprocessed.find_first_of ('"', start) + 1;
  125. const size_t quoteEnd = this->m_preprocessed.find_first_of ('"', quoteStart);
  126. const std::string filename = this->m_preprocessed.substr (quoteStart, quoteEnd - quoteStart);
  127. // some includes might not be present
  128. // and that should not be treated as an error mainly because these could come from
  129. // commented out content
  130. std::string content;
  131. try {
  132. content += "// begin of include from file ";
  133. content += filename;
  134. content += "\n";
  135. content += this->m_assetLocator.includeShader (filename);
  136. content += "\n// end of included from file ";
  137. content += filename;
  138. content += "\n";
  139. } catch (AssetLoadException&) {
  140. content += "// tried including file ";
  141. content += filename;
  142. content += " but was not found\n";
  143. }
  144. // replace the first two letters with a comment so the filelength doesn't change
  145. this->m_preprocessed = this->m_preprocessed.replace (start, 2, "//");
  146. this->m_includes += content;
  147. // go to the end of the line
  148. end = start;
  149. }
  150. // ensure the included files do not include other files
  151. end = 0;
  152. // then apply includes in-place
  153. while ((start = this->m_includes.find ("#include", end)) != std::string::npos) {
  154. const size_t lineEnd = this->m_includes.find_first_of ('\n', start);
  155. // TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
  156. const size_t quoteStart = this->m_includes.find_first_of ('"', start) + 1;
  157. const size_t quoteEnd = this->m_includes.find_first_of ('"', quoteStart);
  158. const std::string filename = this->m_includes.substr (quoteStart, quoteEnd - quoteStart);
  159. // some includes might not be present
  160. // and that should not be treated as an error mainly because these could come from
  161. // commented out content
  162. std::string content;
  163. try {
  164. content = "// begin of include from file ";
  165. content += filename;
  166. content += "\n";
  167. content += this->m_assetLocator.includeShader (filename);
  168. content += "\n// end of included from file ";
  169. content += filename;
  170. content += "\n";
  171. } catch (AssetLoadException&) {
  172. content = "// tried including file ";
  173. content += filename;
  174. content += " but was not found\n";
  175. }
  176. // file contents ready, replace things
  177. this->m_includes = this->m_includes.replace (start, lineEnd - start, content);
  178. // go back to the beginning of the line to properly continue detecting things
  179. end = start;
  180. }
  181. // search for the main function and add the includes before that for now
  182. end = 0;
  183. bool includesAdded = false;
  184. // finally, try to place the include contents before the main function
  185. while ((start = this->m_preprocessed.find (" main", end)) != std::string::npos) {
  186. char value = this->m_preprocessed.at (start + 5);
  187. end = start + 5;
  188. if (value != ' ' && value != '(') {
  189. continue;
  190. }
  191. // main located, search for uniforms and find the latest one available
  192. size_t lastAttribute = this->m_preprocessed.rfind ("attribute", start);
  193. size_t lastVarying = this->m_preprocessed.rfind ("varying", start);
  194. size_t lastUniform = this->m_preprocessed.rfind ("uniform", start);
  195. size_t latest = lastAttribute;
  196. if (latest == std::string::npos) {
  197. latest = lastVarying;
  198. } else if (latest < lastVarying && lastVarying != std::string::npos) {
  199. latest = lastVarying;
  200. }
  201. if (latest == std::string::npos) {
  202. latest = lastUniform;
  203. } else if (latest < lastUniform && lastUniform != std::string::npos) {
  204. latest = lastUniform;
  205. }
  206. if (latest < start) {
  207. // find the end of the current line
  208. latest = this->m_preprocessed.find ('\n', latest);
  209. } else {
  210. // find the end of the previous line
  211. latest = this->m_preprocessed.rfind ('\n', start);
  212. }
  213. // update the function start to point to the end of the previous line
  214. // as this will be used to determine the position of the includes
  215. start = this->m_preprocessed.rfind ('\n', start);
  216. // keeps track of the start and end of ifdefs to look for the right
  217. // place to put the includes in
  218. std::stack<size_t> ifdefStack;
  219. // start looking for #if and #endif results and add to the stack so we find the start of the current chain of
  220. // ifdefs and use that as point
  221. // for this we'll use regex
  222. const std::regex ifdef (R"((#if|#endif))");
  223. std::smatch match;
  224. size_t current = 0;
  225. while (
  226. std::regex_search (this->m_preprocessed.cbegin () + current, this->m_preprocessed.cend (), match, ifdef)) {
  227. current += match.position ();
  228. // if it's opening an #ifdef keep track of the start of the block
  229. // and that's it
  230. if (this->m_preprocessed.substr (current, 3) == "#if") {
  231. // go to the next character so the regex doesn't match with the same thing again
  232. ifdefStack.push (current++);
  233. continue;
  234. }
  235. // go to the next character so the regex doesn't match with the same thing again
  236. current++;
  237. // most likely a syntax error, but we'll ignore it for now...
  238. if (ifdefStack.empty ()) {
  239. continue;
  240. }
  241. size_t stackStart = ifdefStack.top ();
  242. ifdefStack.pop ();
  243. if (latest > stackStart && latest <= current) {
  244. // The insertion point is inside a conditional block.
  245. // Move to BEFORE the #if so includes are available to all branches
  246. // (e.g. genericropeparticle.vert has #if GS_ENABLED wrapping two main() functions).
  247. size_t beforeIfdef = this->m_preprocessed.rfind ('\n', stackStart);
  248. latest = (beforeIfdef != std::string::npos) ? beforeIfdef : 0;
  249. }
  250. }
  251. // no more matches, get the one that happens the earliest
  252. // TODO: IS THIS GOOD ENOUGH? MAYBE WE SHOULD BE GETTING THE FIRST #IF BLOCK INSTEAD?
  253. latest = std::min (latest, start);
  254. // finally insert it there
  255. this->m_preprocessed.insert (latest + 1, this->m_includes + '\n');
  256. includesAdded = true;
  257. break;
  258. }
  259. if (!includesAdded) {
  260. sLog.exception ("Could not find where to place includes for shader unit ", this->m_file);
  261. }
  262. }
  263. void ShaderUnit::preprocessRequires () {
  264. size_t start = 0, end = 0;
  265. while ((start = this->m_preprocessed.find ("#require", end)) != std::string::npos) {
  266. const size_t lineEnd = this->m_preprocessed.find_first_of ('\n', start);
  267. const size_t nameStart = start + std::string ("#require ").length ();
  268. if (nameStart >= lineEnd) {
  269. sLog.error ("Malformed #require directive (no module name) in shader ", this->m_file);
  270. end = lineEnd;
  271. continue;
  272. }
  273. std::string moduleName = this->m_preprocessed.substr (nameStart, lineEnd - nameStart);
  274. while (!moduleName.empty () && (moduleName.back () == ' ' || moduleName.back () == '\r')) {
  275. moduleName.pop_back ();
  276. }
  277. if (moduleName.empty ()) {
  278. sLog.error ("Malformed #require directive (empty module name) in shader ", this->m_file);
  279. end = lineEnd;
  280. continue;
  281. }
  282. sLog.out ("Resolving require module: ", moduleName, " in shader ", this->m_file);
  283. std::string moduleCode = this->resolveRequireModule (moduleName);
  284. // comment out the #require directive
  285. this->m_preprocessed = this->m_preprocessed.replace (start, 2, "//");
  286. if (!moduleCode.empty ()) {
  287. // insert the generated code directly into m_preprocessed at the #require location
  288. // (m_includes was already consumed by preprocessIncludes, so appending there would be lost)
  289. this->m_preprocessed.insert (start, moduleCode);
  290. end = start + moduleCode.length ();
  291. } else {
  292. end = lineEnd;
  293. }
  294. }
  295. }
  296. std::string ShaderUnit::resolveRequireModule (const std::string& moduleName) const {
  297. if (moduleName == "LightingV1") {
  298. return this->generateLightingV1 ();
  299. }
  300. sLog.error ("Unknown #require module: ", moduleName, " in shader ", this->m_file);
  301. return "";
  302. }
  303. std::string ShaderUnit::generateLightingV1 () const {
  304. // PerformLighting_V1 is dynamically generated by Wallpaper Engine based on the scene's
  305. // light sources. Since linux-wallpaperengine does not yet support light objects, we
  306. // generate a stub that returns no dynamic light contribution.
  307. return "// begin of generated module LightingV1\n"
  308. "vec3 PerformLighting_V1(vec3 worldPos, vec3 albedo, vec3 normal, vec3 viewDir,\n"
  309. " vec3 specularTint, vec3 baseReflectance, float roughness, float metallic)\n"
  310. "{\n"
  311. " return vec3(0.0);\n"
  312. "}\n"
  313. "// end of generated module LightingV1\n";
  314. }
  315. std::string ShaderUnit::applyLinkedVaryingCompatibility (std::string source) const {
  316. if (this->m_type != GLSLContext::UnitType_Vertex || this->m_link == nullptr) {
  317. return source;
  318. }
  319. std::regex fragmentVec4Varying (R"(\bvarying\s+vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
  320. std::smatch varyingMatch;
  321. std::string linked = this->m_link->m_preprocessed;
  322. size_t linkedOffset = 0;
  323. while (std::regex_search (linked.cbegin () + linkedOffset, linked.cend (), varyingMatch, fragmentVec4Varying)) {
  324. const std::string name = varyingMatch[1].str ();
  325. linkedOffset += varyingMatch.position () + varyingMatch.length ();
  326. const std::regex vertexVec2Decl ("\\bvarying\\s+vec2\\s+" + name + "\\s*;");
  327. if (!std::regex_search (source, vertexVec2Decl)) {
  328. continue;
  329. }
  330. source = std::regex_replace (source, vertexVec2Decl, "varying vec4 " + name + ";");
  331. const std::regex assignment ("(^|\\n)([ \\t]*)" + name + "\\s*=\\s*([^;\\n]+);");
  332. std::smatch assignmentMatch;
  333. size_t offset = 0;
  334. while (std::regex_search (source.cbegin () + offset, source.cend (), assignmentMatch, assignment)) {
  335. const std::string prefix = assignmentMatch[1].str ();
  336. const std::string indent = assignmentMatch[2].str ();
  337. const std::string expression = assignmentMatch[3].str ();
  338. const std::string replacement = prefix + indent + name + " = vec4(" + expression + ", 0.0, 1.0);";
  339. const size_t position = offset + assignmentMatch.position ();
  340. source.replace (position, assignmentMatch.length (), replacement);
  341. offset = position + replacement.length ();
  342. }
  343. }
  344. return source;
  345. }
  346. std::string ShaderUnit::applyFragmentTexCoordCompatibility (std::string source) const {
  347. if (this->m_type != GLSLContext::UnitType_Fragment) {
  348. return source;
  349. }
  350. const std::regex texCoordBeforeCast2 (R"(\bv_TexCoord\b(\s*[-+*/]\s*CAST2\s*\())");
  351. const std::regex cast2BeforeTexCoord (R"((CAST2\s*\([^)]+\)\s*[-+*/]\s*)\bv_TexCoord\b)");
  352. const std::regex wideTexCoordDecl (R"(\bvarying\s+vec[34]\s+v_TexCoord\s*;)");
  353. if (!std::regex_search (source, wideTexCoordDecl)
  354. || (!std::regex_search (source, texCoordBeforeCast2) && !std::regex_search (source, cast2BeforeTexCoord))) {
  355. return source;
  356. }
  357. const std::string original = source;
  358. source = std::regex_replace (source, texCoordBeforeCast2, "v_TexCoord.xy$1");
  359. source = std::regex_replace (source, cast2BeforeTexCoord, "$1v_TexCoord.xy");
  360. if (source != original) {
  361. sLog.out ("Applied fragment TexCoord vec2 compatibility in ", this->m_file);
  362. }
  363. return source;
  364. }
  365. void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
  366. // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
  367. JSON data;
  368. try {
  369. data = JSON::parse (content);
  370. } catch (const std::exception& e) {
  371. sLog.error ("Cannot parse combo metadata in shader ", this->m_file, ": ", e.what ());
  372. return;
  373. }
  374. const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
  375. // ignore type as it seems to be used only on the editor
  376. // const auto type = data.find ("type");
  377. const auto defvalue = data.find ("default");
  378. // check the combos
  379. const auto entry = this->m_combos.find (combo);
  380. const auto entryOverride = this->m_overrideCombos.find (combo);
  381. // add the combo to the found list
  382. this->m_usedCombos.emplace (combo, true);
  383. // if the combo was not found in the predefined values this means that the default value in the JSON data can be
  384. // used so only define the ones that are not already defined
  385. if (entry == this->m_combos.end () && entryOverride == this->m_overrideCombos.end ()) {
  386. // if no combo is defined just load the default settings
  387. if (defvalue == data.end ()) {
  388. // TODO: PROPERLY SUPPORT EMPTY COMBOS
  389. this->m_discoveredCombos.emplace (combo, defaultValue);
  390. } else if (defvalue->is_number_float ()) {
  391. sLog.exception ("float combos are not supported in shader ", this->m_file, ". ", combo);
  392. } else if (defvalue->is_number_integer ()) {
  393. this->m_discoveredCombos.emplace (combo, defvalue->get<int> ());
  394. } else if (defvalue->is_string ()) {
  395. sLog.exception ("string combos are not supported in shader ", this->m_file, ". ", combo);
  396. } else {
  397. sLog.exception ("cannot parse combo information ", combo, ". unknown type for ", defvalue->dump ());
  398. }
  399. }
  400. }
  401. void ShaderUnit::parseParameterConfiguration (
  402. const std::string& type, const std::string& name, const std::string& content
  403. ) {
  404. JSON data;
  405. try {
  406. data = JSON::parse (content);
  407. } catch (const std::exception& e) {
  408. sLog.error ("Cannot parse parameter metadata for ", name, " in shader ", this->m_file, ": ", e.what ());
  409. return;
  410. }
  411. const auto material = data.optional ("material");
  412. const auto defvalue = data.optional ("default");
  413. // auto range = data.find ("range");
  414. const auto combo = data.find ("combo");
  415. // this is not a real parameter
  416. auto constant = this->m_constants.end ();
  417. if (material.has_value ()) {
  418. constant = this->m_constants.find (*material);
  419. }
  420. if (constant == this->m_constants.end () && !defvalue.has_value ()) {
  421. if (type != "sampler2D") {
  422. sLog.exception ("Cannot parse parameter data for ", name, " in shader ", this->m_file);
  423. }
  424. }
  425. Variables::ShaderVariable* parameter = nullptr;
  426. if (type == "vec4") {
  427. parameter
  428. = new Variables::ShaderVariableVector4 (VectorBuilder::parse<glm::vec4> (defvalue->get<std::string> ()));
  429. } else if (type == "vec3") {
  430. parameter = new Variables::ShaderVariableVector3 (VectorBuilder::parse<glm::vec3> (*defvalue));
  431. } else if (type == "vec2") {
  432. parameter = new Variables::ShaderVariableVector2 (VectorBuilder::parse<glm::vec2> (*defvalue));
  433. } else if (type == "float") {
  434. if (defvalue->is_string ()) {
  435. parameter = new Variables::ShaderVariableFloat (std::stoi (defvalue->get<std::string> ()));
  436. } else {
  437. parameter = new Variables::ShaderVariableFloat (defvalue->get<float> ());
  438. }
  439. } else if (type == "int") {
  440. if (defvalue->is_string ()) {
  441. parameter = new Variables::ShaderVariableInteger (std::stoi (defvalue->get<std::string> ()));
  442. } else {
  443. parameter = new Variables::ShaderVariableInteger (defvalue->get<int> ());
  444. }
  445. } else if (type == "sampler2D" || type == "sampler2DComparison") {
  446. // samplers can have special requirements, check what sampler we're working with and create definitions
  447. // if needed
  448. const auto textureName = data.find ("default");
  449. // TODO: CREATE TEXTURE WITH THE GIVEN COLOR
  450. // extract the texture number from the name
  451. const char value = name.at (std::string ("g_Texture").length ());
  452. const auto requireany = data.find ("requireany");
  453. const auto require = data.find ("require");
  454. // now convert it to integer
  455. // TODO: BETTER CONVERSION HERE
  456. size_t index = value - '0';
  457. // TODO: SUPPORT USER TEXTURES!!
  458. if (combo != data.end ()) {
  459. // TODO: CLEANUP HOW THIS IS DETERMINED FIRST
  460. // if the texture exists (and is not null), add to the combo
  461. const auto textureSlotUsed
  462. = this->m_passTextures.contains (index) || this->m_overrideTextures.contains (index);
  463. bool isRequired = false;
  464. int comboValue = 1;
  465. if (textureSlotUsed) {
  466. // nothing extra to do, the texture exists, the combo must be set
  467. // these tend to not have default value
  468. isRequired = true;
  469. } else if (require != data.end ()) {
  470. // this is required based on certain conditions
  471. if (requireany != data.end () && requireany->get<bool> ()) {
  472. // any of the values set are valid, check for them
  473. for (const auto& item : require->items ()) {
  474. const std::string& macro = item.key ();
  475. const auto it = this->m_combos.find (macro);
  476. // if any of the values matched, this option is required
  477. if (it == this->m_combos.end () || this->m_overrideCombos.contains (macro)
  478. || it->second != item.value ()) {
  479. isRequired = true;
  480. break;
  481. }
  482. }
  483. } else {
  484. isRequired = true;
  485. // all values must match for it to be required
  486. for (const auto& item : require->items ()) {
  487. const std::string& macro = item.key ();
  488. const auto it = this->m_combos.find (macro);
  489. // these can not exist and that'd be fine, we just care about the values
  490. if ((it != this->m_combos.end () || this->m_overrideCombos.contains (macro))
  491. && it->second == item.value ()) {
  492. isRequired = false;
  493. break;
  494. }
  495. }
  496. }
  497. }
  498. if (isRequired && !textureSlotUsed) {
  499. if (!defvalue.has_value ()) {
  500. isRequired = false;
  501. } else {
  502. // is the combo registered already?
  503. // if not, add it with the default value
  504. // there's already a combo providing this value, so it doesn't need to be added
  505. if (this->m_combos.contains (*combo) || this->m_overrideCombos.contains (*combo)) {
  506. isRequired = false;
  507. // otherwise a default value must be used
  508. } else if (defvalue->is_string ()) {
  509. comboValue = std::stoi (defvalue->get<std::string> ().c_str ());
  510. } else if (defvalue->is_number ()) {
  511. comboValue = *defvalue;
  512. } else {
  513. sLog.exception (
  514. "Cannot determine default value for combo ", combo->get<std::string> (),
  515. " because it's not specified by the shader and is not given a default value: ", this->m_file
  516. );
  517. }
  518. }
  519. }
  520. if (isRequired) {
  521. // add the new combo to the list
  522. this->m_discoveredCombos.emplace (*combo, comboValue);
  523. // textures linked to combos need to be tracked too
  524. this->m_usedCombos.emplace (*combo, true);
  525. }
  526. }
  527. if (textureName != data.end ()) {
  528. this->m_defaultTextures.emplace (index, *textureName);
  529. }
  530. // samplers are not saved, we can ignore them for now
  531. return;
  532. } else {
  533. sLog.error ("Unknown parameter type: ", type, " for ", name, " in shader ", this->m_file);
  534. return;
  535. }
  536. if (material.has_value () && parameter != nullptr) {
  537. parameter->setIdentifierName (*material);
  538. parameter->setName (name);
  539. this->m_parameters.push_back (parameter);
  540. }
  541. }
  542. const ComboMap& ShaderUnit::getCombos () const { return this->m_combos; }
  543. const ComboMap& ShaderUnit::getDiscoveredCombos () const { return this->m_discoveredCombos; }
  544. void ShaderUnit::linkToUnit (const ShaderUnit* unit) { this->m_link = unit; }
  545. const ShaderUnit* ShaderUnit::getLinkedUnit () const { return this->m_link; }
  546. const std::string& ShaderUnit::compile () {
  547. if (!this->m_final.empty ()) {
  548. return this->m_final;
  549. }
  550. this->m_final = SHADER_HEADER (this->m_file);
  551. if (this->m_type == GLSLContext::UnitType_Fragment) {
  552. this->m_final += FRAGMENT_SHADER_DEFINES;
  553. } else {
  554. this->m_final += VERTEX_SHADER_DEFINES;
  555. }
  556. std::map<std::string, bool> addedCombos;
  557. for (const auto& [name, value] : this->m_overrideCombos) {
  558. std::string uppercase;
  559. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  560. if (!addedCombos.contains (uppercase)) {
  561. this->m_final += DEFINE_COMBO (uppercase, value);
  562. addedCombos.emplace (uppercase, true);
  563. }
  564. }
  565. // now add all the combos to the source
  566. for (const auto& [name, value] : this->m_combos) {
  567. std::string uppercase;
  568. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  569. if (!addedCombos.contains (uppercase)) {
  570. this->m_final += DEFINE_COMBO (uppercase, value);
  571. addedCombos.emplace (uppercase, true);
  572. }
  573. }
  574. for (const auto& [name, value] : this->m_discoveredCombos) {
  575. std::string uppercase;
  576. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  577. if (!addedCombos.contains (uppercase)) {
  578. this->m_final += DEFINE_COMBO (uppercase, value);
  579. addedCombos.emplace (uppercase, true);
  580. }
  581. }
  582. if (this->m_link != nullptr) {
  583. for (const auto& [name, value] : this->m_link->getCombos ()) {
  584. std::string uppercase;
  585. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  586. if (!addedCombos.contains (uppercase)) {
  587. this->m_final += DEFINE_COMBO (uppercase, value);
  588. addedCombos.emplace (uppercase, true);
  589. }
  590. }
  591. for (const auto& [name, value] : this->m_link->getDiscoveredCombos ()) {
  592. std::string uppercase;
  593. std::ranges::transform (name, std::back_inserter (uppercase), ::toupper);
  594. if (!addedCombos.contains (uppercase)) {
  595. this->m_final += DEFINE_COMBO (uppercase, value);
  596. addedCombos.emplace (uppercase, true);
  597. }
  598. }
  599. }
  600. // this should be the rest of the shader
  601. this->m_final
  602. += this->applyFragmentTexCoordCompatibility (this->applyLinkedVaryingCompatibility (this->m_preprocessed));
  603. // the pass itself handles shader compilation, the unit doesn't have enough information for this step
  604. return this->m_final;
  605. }
  606. const std::vector<Variables::ShaderVariable*>& ShaderUnit::getParameters () const { return this->m_parameters; }
  607. const TextureMap& ShaderUnit::getTextures () const { return this->m_defaultTextures; }