CPass.cpp 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522
  1. #include "CPass.h"
  2. #include <algorithm>
  3. #include <cctype>
  4. #include <cmath>
  5. #include <sstream>
  6. #include <type_traits>
  7. #include <utility>
  8. #include "WallpaperEngine/Desktop/UserShortcut.h"
  9. #include "WallpaperEngine/Render/Helpers/ContextAware.h"
  10. #include "WallpaperEngine/Data/Model/Effect.h"
  11. #include "WallpaperEngine/Data/Model/Material.h"
  12. #include "WallpaperEngine/Data/Model/Project.h"
  13. #include "WallpaperEngine/Data/Model/Property.h"
  14. #include "WallpaperEngine/Render/Wallpapers/CScene.h"
  15. #include "WallpaperEngine/Render/CFBO.h"
  16. #include "WallpaperEngine/Render/Objects/CImage.h"
  17. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariable.h"
  18. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableFloat.h"
  19. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableInteger.h"
  20. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector2.h"
  21. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector3.h"
  22. #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariableVector4.h"
  23. #include "WallpaperEngine/Logging/Log.h"
  24. using namespace WallpaperEngine;
  25. using namespace WallpaperEngine::Render;
  26. using namespace WallpaperEngine::Render::Objects;
  27. using namespace WallpaperEngine::Render::Shaders::Variables;
  28. using namespace WallpaperEngine::Render::Objects::Effects;
  29. extern float g_Time;
  30. extern float g_Daytime;
  31. CPass::UniformEntry::~UniformEntry () {
  32. if (!this->owned) {
  33. return;
  34. }
  35. switch (this->type) {
  36. case Double:
  37. delete static_cast<const double*> (this->value);
  38. break;
  39. case Float:
  40. delete static_cast<const float*> (this->value);
  41. break;
  42. case Integer:
  43. delete static_cast<const int*> (this->value);
  44. break;
  45. case Vector4:
  46. delete static_cast<const glm::vec4*> (this->value);
  47. break;
  48. case Vector3:
  49. delete static_cast<const glm::vec3*> (this->value);
  50. break;
  51. case Vector2:
  52. delete static_cast<const glm::vec2*> (this->value);
  53. break;
  54. case Matrix4:
  55. delete static_cast<const glm::mat4*> (this->value);
  56. break;
  57. case Matrix3:
  58. delete static_cast<const glm::mat3*> (this->value);
  59. break;
  60. }
  61. }
  62. const TextureMap DEFAULT_BINDS = {};
  63. const ImageEffectPassOverride DEFAULT_OVERRIDE = {};
  64. // objects that don't provide their own layer-to-screen mapping (text, particles) keep the old identity
  65. const glm::mat4 IDENTITY_MATRIX = glm::mat4 (1.0);
  66. const glm::mat3 IDENTITY_MATRIX3 = glm::mat3 (1.0);
  67. namespace {
  68. std::string textureSizeLabel (const std::shared_ptr<const TextureProvider>& texture) {
  69. if (texture == nullptr) {
  70. return "<null>";
  71. }
  72. return std::to_string (texture->getRealWidth ()) + "x" + std::to_string (texture->getRealHeight ());
  73. }
  74. // shader used by Wallpaper Engine's built-in "X-Ray" interactive effect (effects/xray/effect.json)
  75. const std::string XRAY_EFFECT_SHADER = "effects/xray";
  76. // The xray fragment shader gates its reveal on a projector-style sample of a small halo sprite
  77. // (g_Texture2, e.g. "particle/halo_6") taken around the pointer; g_PointerScale only controls how
  78. // far that sample zooms into the sprite, not any on-screen radius, so there's no uniform value that
  79. // makes it cover the whole screen. Instead this patches the compiled fragment source to add a
  80. // g_XrayFullReveal uniform that bypasses the halo sample entirely, forcing the hidden layer
  81. // (g_Texture1) to blend in everywhere once toggled - see patchXrayFullRevealBypass() below.
  82. const std::string XRAY_MULTIPLY_UNIFORM = "uniform float g_Multiply;";
  83. const std::string XRAY_FULL_REVEAL_UNIFORM_DECL = "uniform float g_Multiply;\nuniform float g_XrayFullReveal;";
  84. const std::string XRAY_BLEND_LINE = "blend *= (blendSample.x * blendSample.y);";
  85. const std::string XRAY_BLEND_LINE_PATCHED = "blend *= mix (blendSample.x * blendSample.y, 1.0, g_XrayFullReveal);";
  86. // Returns false (leaving fragmentSource untouched) if the anchors weren't found, e.g. because a
  87. // different spirv-cross/glslang version formats the compiled output differently - callers should
  88. // treat that as "full xray toggle becomes a no-op" rather than fail the whole shader compile.
  89. bool patchXrayFullRevealBypass (std::string& fragmentSource) {
  90. const auto blendPos = fragmentSource.find (XRAY_BLEND_LINE);
  91. const auto uniformPos = fragmentSource.find (XRAY_MULTIPLY_UNIFORM);
  92. if (blendPos == std::string::npos || uniformPos == std::string::npos) {
  93. return false;
  94. }
  95. // replace the later occurrence first so the earlier one's position stays valid
  96. fragmentSource.replace (blendPos, XRAY_BLEND_LINE.size (), XRAY_BLEND_LINE_PATCHED);
  97. fragmentSource.replace (uniformPos, XRAY_MULTIPLY_UNIFORM.size (), XRAY_FULL_REVEAL_UNIFORM_DECL);
  98. return true;
  99. }
  100. }
  101. CPass::CPass (
  102. CRenderable& renderable, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
  103. std::optional<std::reference_wrapper<const ImageEffectPassOverride>> override,
  104. std::optional<std::reference_wrapper<const TextureMap>> binds,
  105. std::optional<std::reference_wrapper<std::string>> target
  106. ) :
  107. Helpers::ContextAware (renderable), m_renderable (renderable), m_fboProvider (std::move (fboProvider)),
  108. m_pass (pass), m_binds (binds.has_value () ? binds.value ().get () : DEFAULT_BINDS),
  109. m_override (override.has_value () ? override.value ().get () : DEFAULT_OVERRIDE), m_target (target),
  110. m_blendingmode (pass.blending), m_vao (GL_NONE) {
  111. this->m_effectTextureProjectionMatrix = &IDENTITY_MATRIX;
  112. this->m_effectTextureProjectionMatrixInverse = &IDENTITY_MATRIX;
  113. this->m_lightingModelMatrix = &IDENTITY_MATRIX;
  114. this->m_lightingNormalMatrix = &IDENTITY_MATRIX3;
  115. this->m_lightingViewProjectionMatrix = &IDENTITY_MATRIX;
  116. this->setupShaders ();
  117. glGenVertexArrays (1, &m_vao);
  118. }
  119. CPass::~CPass () {
  120. for (const auto& texture : this->m_playbackTextures) {
  121. texture->decrementUsageCount ();
  122. }
  123. for (const auto& value : this->m_uniforms | std::views::values) {
  124. delete value;
  125. }
  126. for (const auto& value : this->m_referenceUniforms | std::views::values) {
  127. delete value;
  128. }
  129. for (const auto* attrib : this->m_attribs) {
  130. delete attrib;
  131. }
  132. // text layers rebuild their passes on every glyph texture resize, so this can't be left to leak
  133. delete this->m_shader;
  134. this->m_shader = nullptr;
  135. glDeleteVertexArrays (1, &m_vao);
  136. this->m_vao = GL_NONE;
  137. if (!glIsProgram (this->m_programID)) {
  138. return; // program already invalid or deleted
  139. }
  140. GLint shaderCount = 0;
  141. glGetProgramiv (this->m_programID, GL_ATTACHED_SHADERS, &shaderCount);
  142. if (shaderCount > 0) {
  143. std::vector<GLuint> attachedShaders (shaderCount);
  144. glGetAttachedShaders (this->m_programID, shaderCount, nullptr, attachedShaders.data ());
  145. for (GLuint s : attachedShaders) {
  146. if (glIsShader (s)) {
  147. glDeleteShader (s);
  148. }
  149. }
  150. }
  151. glDeleteProgram (this->m_programID);
  152. this->m_programID = 0;
  153. }
  154. std::shared_ptr<const TextureProvider> CPass::resolveTexture (
  155. std::shared_ptr<const TextureProvider> expected, int index, std::shared_ptr<const TextureProvider> previous
  156. ) {
  157. if (expected == nullptr) {
  158. if (const auto it = this->m_fbos.find (index); it != this->m_fbos.end ()) {
  159. expected = it->second;
  160. }
  161. }
  162. const auto it = this->m_binds.find (index);
  163. if (it == this->m_binds.end ()) {
  164. return expected;
  165. }
  166. // a bind named "previous" is just another way of telling it to use whatever texture there was already
  167. if (it->second == "previous") {
  168. return this->m_previousInput ?: (previous ?: expected);
  169. }
  170. return this->resolveFBO (it->second);
  171. }
  172. void CPass::trackPlayback (const std::shared_ptr<const TextureProvider>& texture) {
  173. // the renderable already counts its own texture, and frame buffers have nothing to play back
  174. if (texture == nullptr || texture == this->m_renderable.getTexture ()
  175. || std::ranges::find (this->m_playbackTextures, texture) != this->m_playbackTextures.end ()) {
  176. return;
  177. }
  178. texture->incrementUsageCount ();
  179. this->m_playbackTextures.push_back (texture);
  180. }
  181. void CPass::updatePlaybackTextures () const {
  182. for (const auto& texture : this->m_playbackTextures) {
  183. texture->update ();
  184. }
  185. }
  186. std::optional<std::string> CPass::resolveUserTextureName (const std::string& propertyName) const {
  187. const auto& properties = this->m_renderable.getScene ().getScene ().project.properties;
  188. const auto it = properties.find (propertyName);
  189. if (it == properties.end ()) {
  190. // not actually a property reference, treat it as a literal texture name like before
  191. return propertyName;
  192. }
  193. // an assigned shortcut shows its icon, TextureCache loads it from outside the wallpaper
  194. if (it->second->is<PropertyUserShortcut> ()) {
  195. const auto shortcut = Desktop::UserShortcut::parse (it->second->getString ());
  196. const auto icon = shortcut.has_value () ? shortcut->iconPath () : std::nullopt;
  197. return icon.has_value () ? std::optional<std::string> ("$usershortcut:" + icon->string ()) : std::nullopt;
  198. }
  199. const std::string& value = it->second->getString ();
  200. if (value.empty ()) {
  201. // the "scenetexture" property exists but the user hasn't imported an image for it -
  202. // this is the normal state for most wallpapers that expose this as an optional slot
  203. return std::nullopt;
  204. }
  205. return value;
  206. }
  207. std::shared_ptr<const CFBO> CPass::resolveFBO (const std::string& name) const {
  208. auto fbo = this->m_fboProvider->find (name);
  209. if (fbo == nullptr) {
  210. sLog.exception ("Tried to resolve and FBO without any luck: ", name);
  211. }
  212. return fbo;
  213. }
  214. void CPass::setupRenderFramebuffer () const {
  215. glBindFramebuffer (GL_FRAMEBUFFER, this->m_drawTo->getFramebuffer ());
  216. // Private per-object FBOs are never cleared elsewhere, so a blending pass would otherwise
  217. // accumulate stale alpha across frames. The shared scene FBO must not be cleared here though,
  218. // since it accumulates every object drawn this frame.
  219. if (this->m_drawTo != this->m_renderable.getScene ().getFBO ()) {
  220. GLfloat previousClearColor[4] = {};
  221. glGetFloatv (GL_COLOR_CLEAR_VALUE, previousClearColor);
  222. glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
  223. glClear (GL_COLOR_BUFFER_BIT);
  224. glClearColor (previousClearColor[0], previousClearColor[1], previousClearColor[2], previousClearColor[3]);
  225. }
  226. glViewport (0, 0, this->m_drawTo->getRealWidth (), this->m_drawTo->getRealHeight ());
  227. // the alpha source factor must be GL_ONE, GL_SRC_ALPHA squares every blended pass's alpha and compounds through chained effects
  228. switch (this->getBlendingMode ()) {
  229. case BlendingMode_Translucent:
  230. glEnable (GL_BLEND);
  231. glBlendFuncSeparate (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  232. break;
  233. case BlendingMode_Additive:
  234. glEnable (GL_BLEND);
  235. glBlendFuncSeparate (GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
  236. break;
  237. case BlendingMode_Normal:
  238. // "Normal" is standard alpha compositing, not a raw replace - GL_ONE/GL_ZERO discarded
  239. // the destination outright regardless of source alpha, which broke passes whose source
  240. // texture is partially transparent (e.g. unconfigured/placeholder effect textures).
  241. // Passes that always output alpha=1 render identically either way.
  242. // except into an intermediate target: blending there premultiplies RGB and darkens soft alpha edges in the final pass
  243. if (this->m_drawTo == this->m_renderable.getScene ().getFBO ()) {
  244. glEnable (GL_BLEND);
  245. glBlendFuncSeparate (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  246. } else {
  247. glDisable (GL_BLEND);
  248. }
  249. break;
  250. default:
  251. glDisable (GL_BLEND);
  252. break;
  253. }
  254. switch (this->m_pass.depthtest) {
  255. case DepthtestMode_Enabled:
  256. glEnable (GL_DEPTH_TEST);
  257. glDepthFunc (GL_LEQUAL);
  258. break;
  259. case DepthtestMode_Disabled:
  260. default:
  261. glDisable (GL_DEPTH_TEST);
  262. break;
  263. }
  264. switch (this->m_pass.cullmode) {
  265. case CullingMode_Normal:
  266. glEnable (GL_CULL_FACE);
  267. break;
  268. case CullingMode_Disable:
  269. default:
  270. glDisable (GL_CULL_FACE);
  271. break;
  272. }
  273. switch (this->m_pass.depthwrite) {
  274. case DepthwriteMode_Enabled:
  275. glDepthMask (true);
  276. break;
  277. case DepthwriteMode_Disabled:
  278. default:
  279. glDepthMask (false);
  280. break;
  281. }
  282. }
  283. void CPass::setupRenderTexture () {
  284. glUseProgram (this->m_programID);
  285. auto texture0 = this->resolveTexture0 ();
  286. const auto animation = this->resolveTextureAnimationState (texture0);
  287. this->bindTextureUnit (0, texture0, animation.currentTexture);
  288. this->bindTextureOverrides (animation.currentTexture, texture0);
  289. if (texture0 != nullptr) {
  290. this->m_texture0Resolution = *texture0->getResolution ();
  291. }
  292. // used in animations when one of the frames is vertical instead of horizontal
  293. // rotation with translation = origin and end of the image to display
  294. if (this->g_Texture0Rotation != -1) {
  295. glUniform4f (
  296. this->g_Texture0Rotation, animation.rotation.x, animation.rotation.y, animation.rotation.z,
  297. animation.rotation.w
  298. );
  299. }
  300. // this actually picks the origin point of the image from the atlast
  301. if (this->g_Texture0Translation != -1) {
  302. glUniform2f (this->g_Texture0Translation, animation.translation.x, animation.translation.y);
  303. }
  304. }
  305. std::shared_ptr<const TextureProvider> CPass::resolveTexture0 () {
  306. auto texture0 = this->resolveTexture (this->m_input, 0, this->m_input);
  307. const auto it = this->m_textures.find (0);
  308. if (it == this->m_textures.end ()) {
  309. return texture0;
  310. }
  311. auto& chain = it->second;
  312. do {
  313. texture0 = chain->texture;
  314. if (texture0 == nullptr) {
  315. if (this->m_previousInput != nullptr && this->m_previousInput->isReady ()) {
  316. return this->m_previousInput;
  317. }
  318. if (this->m_input != nullptr && this->m_input->isReady ()) {
  319. return this->m_input;
  320. }
  321. } else if (texture0->isReady ()) {
  322. return texture0;
  323. }
  324. chain = chain->next;
  325. } while (chain != nullptr);
  326. // got to the end of the chain, use previous input or current input if available
  327. if (this->m_previousInput != nullptr && this->m_previousInput->isReady ()) {
  328. return this->m_previousInput;
  329. }
  330. // last resort, doesn't matter if the input is ready or not
  331. return this->m_input;
  332. }
  333. CPass::TextureAnimationState
  334. CPass::resolveTextureAnimationState (const std::shared_ptr<const TextureProvider>& texture) const {
  335. TextureAnimationState state;
  336. if (texture == nullptr || !texture->isAnimated ()) {
  337. return state;
  338. }
  339. // scene time like every other animation, so --speed, --disable-animations and pausing apply
  340. double currentRenderTime = fmod (static_cast<double> (g_Time), this->m_renderable.getAnimationTime ());
  341. for (const auto& frameCur : texture->getFrames ()) {
  342. currentRenderTime -= frameCur->frametime;
  343. if (currentRenderTime > 0.0f) {
  344. continue;
  345. }
  346. state.currentTexture = frameCur->frameNumber;
  347. state.translation.x = frameCur->x / texture->getTextureWidth (state.currentTexture);
  348. state.translation.y = frameCur->y / texture->getTextureHeight (state.currentTexture);
  349. state.rotation.x = frameCur->width1 / static_cast<float> (texture->getTextureWidth (state.currentTexture));
  350. state.rotation.y = frameCur->width2 / static_cast<float> (texture->getTextureWidth (state.currentTexture));
  351. state.rotation.z = frameCur->height2 / static_cast<float> (texture->getTextureHeight (state.currentTexture));
  352. state.rotation.w = frameCur->height1 / static_cast<float> (texture->getTextureHeight (state.currentTexture));
  353. break;
  354. }
  355. return state;
  356. }
  357. void CPass::bindTextureUnit (int index, const std::shared_ptr<const TextureProvider>& texture, uint32_t frame) const {
  358. if (texture == nullptr) {
  359. return;
  360. }
  361. glActiveTexture (GL_TEXTURE0 + index);
  362. glBindTexture (GL_TEXTURE_2D, texture->getTextureID (frame));
  363. }
  364. void CPass::bindTextureOverrides (uint32_t currentTexture, std::shared_ptr<const TextureProvider>& texture0) const {
  365. for (auto [index, chain] : this->m_textures) {
  366. auto expectedTexture = chain->texture;
  367. do {
  368. if (expectedTexture == nullptr) {
  369. if (this->m_previousInput != nullptr && this->m_previousInput->isReady ()) {
  370. expectedTexture = this->m_previousInput;
  371. break;
  372. }
  373. if (this->m_input != nullptr && this->m_input->isReady ()) {
  374. expectedTexture = this->m_input;
  375. break;
  376. }
  377. } else if (expectedTexture->isReady ()) {
  378. break;
  379. }
  380. chain = chain->next;
  381. expectedTexture = chain == nullptr ? nullptr : chain->texture;
  382. } while (chain != nullptr);
  383. if (expectedTexture == nullptr && this->m_previousInput != nullptr && this->m_previousInput->isReady ()) {
  384. expectedTexture = this->m_previousInput;
  385. }
  386. if (expectedTexture == nullptr) {
  387. expectedTexture = this->m_input;
  388. }
  389. this->bindTextureUnit (index, expectedTexture, index == 0 ? currentTexture : 0);
  390. if (index == 0) {
  391. texture0 = expectedTexture;
  392. }
  393. }
  394. }
  395. void CPass::setupRenderReferenceUniforms () {
  396. for (const auto& value : this->m_referenceUniforms | std::views::values) {
  397. switch (value->type) {
  398. case Double:
  399. glUniform1d (value->id, *static_cast<const double*> (*value->value));
  400. break;
  401. case Float:
  402. glUniform1f (value->id, *static_cast<const float*> (*value->value));
  403. break;
  404. case Integer:
  405. glUniform1i (value->id, *static_cast<const int*> (*value->value));
  406. break;
  407. case Vector4:
  408. glUniform4fv (value->id, 1, glm::value_ptr (*static_cast<const glm::vec4*> (*value->value)));
  409. break;
  410. case Vector3:
  411. glUniform3fv (value->id, 1, glm::value_ptr (*static_cast<const glm::vec3*> (*value->value)));
  412. break;
  413. case Vector2:
  414. glUniform2fv (value->id, 1, glm::value_ptr (*static_cast<const glm::vec2*> (*value->value)));
  415. break;
  416. case Matrix4:
  417. glUniformMatrix4fv (
  418. value->id, 1, GL_FALSE, glm::value_ptr (*static_cast<const glm::mat4*> (*value->value))
  419. );
  420. break;
  421. case Matrix3:
  422. glUniformMatrix3fv (
  423. value->id, 1, GL_FALSE, glm::value_ptr (*static_cast<const glm::mat3*> (*value->value))
  424. );
  425. break;
  426. }
  427. }
  428. }
  429. void CPass::setupRenderUniforms () {
  430. for (const auto& value : this->m_uniforms | std::views::values) {
  431. switch (value->type) {
  432. case Double:
  433. glUniform1dv (value->id, value->count, static_cast<const double*> (value->value));
  434. break;
  435. case Float:
  436. glUniform1fv (value->id, value->count, static_cast<const float*> (value->value));
  437. break;
  438. case Integer:
  439. glUniform1iv (value->id, value->count, static_cast<const int*> (value->value));
  440. break;
  441. case Vector4:
  442. glUniform4fv (value->id, value->count, glm::value_ptr (*static_cast<const glm::vec4*> (value->value)));
  443. break;
  444. case Vector3:
  445. glUniform3fv (value->id, value->count, glm::value_ptr (*static_cast<const glm::vec3*> (value->value)));
  446. break;
  447. case Vector2:
  448. glUniform2fv (value->id, value->count, glm::value_ptr (*static_cast<const glm::vec2*> (value->value)));
  449. break;
  450. case Matrix4:
  451. glUniformMatrix4fv (
  452. value->id, value->count, GL_FALSE, glm::value_ptr (*static_cast<const glm::mat4*> (value->value))
  453. );
  454. break;
  455. case Matrix3:
  456. glUniformMatrix3fv (
  457. value->id, value->count, GL_FALSE, glm::value_ptr (*static_cast<const glm::mat3*> (value->value))
  458. );
  459. break;
  460. }
  461. }
  462. }
  463. void CPass::setupRenderAttributes () const {
  464. if (this->m_setupAttribsCallback) {
  465. this->m_setupAttribsCallback ();
  466. return;
  467. }
  468. for (const auto& cur : this->m_attribs) {
  469. glEnableVertexAttribArray (cur->id);
  470. glBindBuffer (GL_ARRAY_BUFFER, *cur->value);
  471. glVertexAttribPointer (cur->id, cur->elements, cur->type, GL_FALSE, 0, nullptr);
  472. #if !NDEBUG
  473. glObjectLabel (
  474. GL_BUFFER, *cur->value, -1,
  475. ("Image " + std::to_string (this->m_renderable.getId ()) + " Pass " + this->m_pass.shader + " " + cur->name)
  476. .c_str ()
  477. );
  478. #endif /* DEBUG */
  479. }
  480. }
  481. void CPass::renderGeometry () const {
  482. if (this->m_drawGeometryCallback) {
  483. this->m_drawGeometryCallback ();
  484. return;
  485. }
  486. glBindBuffer (GL_ARRAY_BUFFER, this->a_Position);
  487. glDrawArrays (GL_TRIANGLES, 0, 6);
  488. }
  489. void CPass::cleanupRenderSetup () {
  490. if (this->m_cleanupAttribsCallback) {
  491. this->m_cleanupAttribsCallback ();
  492. } else {
  493. // disable vertex attribs array
  494. for (const auto& cur : this->m_attribs) {
  495. glDisableVertexAttribArray (cur->id);
  496. }
  497. }
  498. glActiveTexture (GL_TEXTURE0);
  499. glBindTexture (GL_TEXTURE_2D, 0);
  500. for (const auto& index : this->m_textures | std::views::keys) {
  501. glActiveTexture (GL_TEXTURE0 + index);
  502. glBindTexture (GL_TEXTURE_2D, 0);
  503. }
  504. }
  505. void CPass::refreshRenderableUniforms () {
  506. const auto update = [this] (const char* name, const auto& value) {
  507. const auto it = this->m_uniforms.find (name);
  508. if (it != this->m_uniforms.end () && it->second->owned) {
  509. using Value = std::remove_cvref_t<decltype (value)>;
  510. *static_cast<Value*> (const_cast<void*> (it->second->value)) = value;
  511. }
  512. };
  513. update ("g_UserAlpha", this->m_renderable.getUserAlpha ());
  514. update ("g_Alpha", this->m_renderable.getAlpha ());
  515. update ("g_Color", this->m_renderable.getColor ());
  516. update ("g_Color4", this->m_renderable.getColor4 ());
  517. }
  518. void CPass::render () {
  519. glBindVertexArray (this->m_vao);
  520. // copied when the pass was built, color and alpha scripts or animations change them every frame
  521. this->refreshRenderableUniforms ();
  522. if (this->m_pass.shader == XRAY_EFFECT_SHADER) {
  523. const bool fullReveal = this->getContext ().getApp ().getContext ().state.xray.fullReveal;
  524. this->m_xrayFullReveal = fullReveal ? 1.0f : 0.0f;
  525. }
  526. const auto& debug = this->getContext ().getApp ().getContext ().settings.render.debug;
  527. if (debug.passLog) {
  528. sLog.out (
  529. "Render pass object=", this->m_renderable.getId (), " shader=", this->m_pass.shader,
  530. " target=", this->m_target.has_value () ? this->m_target.value ().get () : std::string ("<screen/local>"),
  531. " drawTo=", this->m_drawTo ? this->m_drawTo->getName () : std::string ("<null>"),
  532. " drawSize=", textureSizeLabel (this->m_drawTo), " inputSize=", textureSizeLabel (this->m_input)
  533. );
  534. for (const auto* uniformName : { "g_TintColor", "g_CompositeColor", "g_BlendAlpha", "g_CompositeAlpha" }) {
  535. const auto uniform = this->m_uniforms.find (uniformName);
  536. if (uniform == this->m_uniforms.end ()) {
  537. continue;
  538. }
  539. switch (uniform->second->type) {
  540. case Vector3:
  541. {
  542. const auto* v = static_cast<const glm::vec3*> (uniform->second->value);
  543. sLog.out (" uniform ", uniformName, "=", v->x, " ", v->y, " ", v->z);
  544. break;
  545. }
  546. case Float:
  547. {
  548. const auto* v = static_cast<const float*> (uniform->second->value);
  549. sLog.out (" uniform ", uniformName, "=", *v);
  550. break;
  551. }
  552. default:
  553. break;
  554. }
  555. }
  556. }
  557. if (this->m_drawTo == nullptr) {
  558. sLog.error ("Skipping render pass for object ", this->m_renderable.getId (), ": no destination FBO set");
  559. return;
  560. }
  561. if (this->m_input == nullptr) {
  562. sLog.error ("Skipping render pass for object ", this->m_renderable.getId (), ": no input texture set");
  563. return;
  564. }
  565. this->setupRenderFramebuffer ();
  566. this->setupRenderTexture ();
  567. this->setupRenderUniforms ();
  568. this->setupRenderReferenceUniforms ();
  569. this->setupRenderAttributes ();
  570. this->renderGeometry ();
  571. this->cleanupRenderSetup ();
  572. }
  573. std::shared_ptr<const FBOProvider> CPass::getFBOProvider () const { return this->m_fboProvider; }
  574. const CRenderable& CPass::getRenderable () const { return this->m_renderable; }
  575. void CPass::setDestination (std::shared_ptr<const CFBO> drawTo) { this->m_drawTo = std::move (drawTo); }
  576. void CPass::setInput (std::shared_ptr<const TextureProvider> input) { this->m_input = std::move (input); }
  577. void CPass::setPreviousInput (std::shared_ptr<const TextureProvider> input) {
  578. this->m_previousInput = std::move (input);
  579. }
  580. void CPass::setModelViewProjectionMatrix (const glm::mat4* projection) {
  581. this->m_modelViewProjectionMatrix = projection;
  582. }
  583. void CPass::setModelViewProjectionMatrixInverse (const glm::mat4* projection) {
  584. this->m_modelViewProjectionMatrixInverse = projection;
  585. }
  586. void CPass::setModelMatrix (const glm::mat4* model) { this->m_modelMatrix = model; }
  587. void CPass::setViewProjectionMatrix (const glm::mat4* viewProjection) { this->m_viewProjectionMatrix = viewProjection; }
  588. void CPass::setLightingTransform (const glm::mat4* model, const glm::mat3* normal, const glm::mat4* viewProjection) {
  589. this->m_lightingModelMatrix = model;
  590. this->m_lightingNormalMatrix = normal;
  591. this->m_lightingViewProjectionMatrix = viewProjection;
  592. }
  593. void CPass::setEffectTextureProjectionMatrix (const glm::mat4* projection, const glm::mat4* inverse) {
  594. this->m_effectTextureProjectionMatrix = projection;
  595. this->m_effectTextureProjectionMatrixInverse = inverse;
  596. }
  597. void CPass::setBlendingMode (BlendingMode blendingmode) { this->m_blendingmode = blendingmode; }
  598. BlendingMode CPass::getBlendingMode () const { return this->m_blendingmode; }
  599. void CPass::setTexCoord (GLuint texcoord) { this->a_TexCoord = texcoord; }
  600. void CPass::setPosition (GLuint position) { this->a_Position = position; }
  601. const MaterialPass& CPass::getPass () const { return this->m_pass; }
  602. std::optional<std::reference_wrapper<std::string>> CPass::getTarget () const { return this->m_target; }
  603. Render::Shaders::Shader* CPass::getShader () const { return this->m_shader; }
  604. GLuint CPass::getProgramID () const { return this->m_programID; }
  605. void CPass::setGeometryCallback (
  606. GeometryCallback setupAttribs, GeometryCallback drawGeometry, GeometryCallback cleanupAttribs
  607. ) {
  608. this->m_setupAttribsCallback = std::move (setupAttribs);
  609. this->m_drawGeometryCallback = std::move (drawGeometry);
  610. this->m_cleanupAttribsCallback = std::move (cleanupAttribs);
  611. }
  612. GLuint CPass::compileShader (const char* shader, GLuint type) {
  613. const GLuint shaderID = glCreateShader (type);
  614. // Mesa mis-reads a vec3 uniform followed by a float used as vec4(vec3, float), use the g_Color4 the engine already exposes
  615. std::string patched;
  616. if (type == GL_FRAGMENT_SHADER) {
  617. patched = shader;
  618. const std::string declarations = "uniform vec3 g_Color;\nuniform float g_Alpha;";
  619. const std::string construction = "vec4(g_Color, g_Alpha)";
  620. const auto declarationsAt = patched.find (declarations);
  621. const auto constructionAt = patched.find (construction);
  622. if (declarationsAt != std::string::npos && constructionAt != std::string::npos) {
  623. patched.replace (constructionAt, construction.size (), "g_Color4");
  624. patched.replace (declarationsAt, declarations.size (), "uniform vec4 g_Color4;");
  625. // only safe when that was the sole use of both
  626. // the generated source keeps the original as an "#if 0" block at the end, that doesn't count
  627. const size_t codeEnd = std::min (patched.size (), patched.find ("#if 0"));
  628. const auto stillUsed = [&patched, codeEnd] (const std::string& name) {
  629. size_t pos = 0;
  630. while ((pos = patched.find (name, pos)) != std::string::npos && pos < codeEnd) {
  631. const bool wordStart = pos == 0 || !(std::isalnum (static_cast<unsigned char> (patched[pos - 1])) || patched[pos - 1] == '_');
  632. const size_t end = pos + name.size ();
  633. const bool wordEnd = end >= patched.size () || !(std::isalnum (static_cast<unsigned char> (patched[end])) || patched[end] == '_');
  634. if (wordStart && wordEnd) {
  635. return true;
  636. }
  637. pos = end;
  638. }
  639. return false;
  640. };
  641. if (stillUsed ("g_Color") || stillUsed ("g_Alpha")) {
  642. patched = shader;
  643. }
  644. }
  645. shader = patched.c_str ();
  646. }
  647. glShaderSource (shaderID, 1, &shader, nullptr);
  648. glCompileShader (shaderID);
  649. GLint result = GL_FALSE;
  650. int infoLogLength = 0;
  651. glGetShaderiv (shaderID, GL_COMPILE_STATUS, &result);
  652. glGetShaderiv (shaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
  653. if (infoLogLength > 0) {
  654. const auto logBuffer = new char[infoLogLength + 1];
  655. memset (logBuffer, 0, infoLogLength + 1);
  656. glGetShaderInfoLog (shaderID, infoLogLength, nullptr, logBuffer);
  657. std::stringstream buffer;
  658. buffer << logBuffer << std::endl << "Compiled source code:" << std::endl << shader;
  659. delete[] logBuffer;
  660. if (result == GL_FALSE) {
  661. sLog.exception (buffer.str ());
  662. } else {
  663. sLog.error (buffer.str ());
  664. }
  665. }
  666. return shaderID;
  667. }
  668. void CPass::setupShaders () {
  669. const auto texture0 = this->m_renderable.getTexture ();
  670. this->m_combos.insert (this->m_pass.combos.begin (), this->m_pass.combos.end ());
  671. const auto comboEnabled = [this] (const std::string& name) {
  672. const auto override = this->m_override.combos.find (name);
  673. if (override != this->m_override.combos.end ()) {
  674. return override->second != 0;
  675. }
  676. const auto combo = this->m_combos.find (name);
  677. return combo != this->m_combos.end () && combo->second != 0;
  678. };
  679. if (comboEnabled ("LIGHTING") || comboEnabled ("REFLECTION")) {
  680. this->m_combos.insert_or_assign ("PRELIGHTING", 1);
  681. }
  682. // particle shaders read TEX0FORMAT without a formatcombo sampler, the other slots are handled below
  683. if (texture0 != nullptr) {
  684. if (texture0->getFormat () == TextureFormat_RG88) {
  685. this->m_combos.insert_or_assign ("TEX0FORMAT", 8);
  686. } else if (texture0->getFormat () == TextureFormat_R8) {
  687. this->m_combos.insert_or_assign ("TEX0FORMAT", 9);
  688. }
  689. }
  690. // TODO: review the shader textures here; ones passed to the shader shouldn't be in this list
  691. // (used later to build the textures)
  692. // use the combos copied from the pass so it includes the texture format
  693. const std::string& shaderName
  694. = this->m_override.shaderOverride.has_value () ? this->m_override.shaderOverride.value () : this->m_pass.shader;
  695. TextureMap passTextures = this->m_pass.textures;
  696. for (const auto& [index, propertyName] : this->m_pass.usertextures) {
  697. // leave the default texture (if any) in place when the user hasn't provided an override,
  698. // same rule applied when the actual texture chain gets built in setupTextureUniforms()
  699. if (const auto resolved = this->resolveUserTextureName (propertyName); resolved.has_value ()) {
  700. passTextures.insert_or_assign (index, *resolved);
  701. }
  702. }
  703. // same for the object's own user textures, otherwise the combo that enables their slot stays off
  704. TextureMap overrideTextures = this->m_override.textures;
  705. for (const auto& [index, propertyName] : this->m_override.usertextures) {
  706. if (const auto resolved = this->resolveUserTextureName (propertyName); resolved.has_value ()) {
  707. overrideTextures.insert_or_assign (index, *resolved);
  708. }
  709. }
  710. this->m_shader = new Render::Shaders::Shader (
  711. this->m_renderable.getAssetLocator (), shaderName, this->m_combos, this->m_override.combos, passTextures,
  712. overrideTextures, this->m_override.constants
  713. );
  714. // samplers marked "formatcombo" get TEX<slot>FORMAT set to their texture's format, like wallpaper64.exe
  715. // (sub_14015EC30). Which slots those are is only known once the shader is parsed, so rebuild it when one changed
  716. if (this->applyFormatCombos (passTextures, overrideTextures)) {
  717. delete this->m_shader;
  718. this->m_shader = new Render::Shaders::Shader (
  719. this->m_renderable.getAssetLocator (), shaderName, this->m_combos, this->m_override.combos, passTextures,
  720. overrideTextures, this->m_override.constants
  721. );
  722. }
  723. auto [vertex, fragment] = Shaders::GLSLContext::get ().toGlsl (this->m_shader->vertex (), this->m_shader->fragment ());
  724. if (shaderName == XRAY_EFFECT_SHADER) {
  725. this->m_xrayFullRevealPatched = patchXrayFullRevealBypass (fragment);
  726. if (!this->m_xrayFullRevealPatched) {
  727. sLog.error ("Full xray toggle unavailable: couldn't find the expected reveal blend line in the "
  728. "compiled effects/xray shader (spirv-cross output format may have changed)");
  729. }
  730. }
  731. const GLuint vertexShaderID = compileShader (vertex.c_str (), GL_VERTEX_SHADER);
  732. const GLuint fragmentShaderID = compileShader (fragment.c_str (), GL_FRAGMENT_SHADER);
  733. this->m_programID = glCreateProgram ();
  734. glAttachShader (this->m_programID, vertexShaderID);
  735. glAttachShader (this->m_programID, fragmentShaderID);
  736. glLinkProgram (this->m_programID);
  737. GLint result = GL_FALSE;
  738. int infoLogLength = 0;
  739. glGetProgramiv (this->m_programID, GL_LINK_STATUS, &result);
  740. glGetProgramiv (this->m_programID, GL_INFO_LOG_LENGTH, &infoLogLength);
  741. if (infoLogLength > 0) {
  742. const auto logBuffer = new char[infoLogLength + 1];
  743. memset (logBuffer, 0, infoLogLength + 1);
  744. glGetProgramInfoLog (this->m_programID, infoLogLength, nullptr, logBuffer);
  745. const std::string message = logBuffer;
  746. delete[] logBuffer;
  747. if (result == GL_FALSE) {
  748. sLog.exception (message);
  749. } else {
  750. sLog.error (message);
  751. }
  752. }
  753. #if !NDEBUG
  754. glObjectLabel (GL_PROGRAM, this->m_programID, -1, shaderName.c_str ());
  755. glObjectLabel (GL_SHADER, vertexShaderID, -1, (shaderName + ".vert").c_str ());
  756. glObjectLabel (GL_SHADER, fragmentShaderID, -1, (shaderName + ".frag").c_str ());
  757. #endif /* DEBUG */
  758. // once linked, the shaders themselves are no longer needed and can be detached/deleted
  759. glDetachShader (this->m_programID, vertexShaderID);
  760. glDetachShader (this->m_programID, fragmentShaderID);
  761. glDeleteShader (vertexShaderID);
  762. glDeleteShader (fragmentShaderID);
  763. // bind each g_TextureN sampler to unit N explicitly, the translated GLSL collapses every layout(binding) to 0
  764. {
  765. glUseProgram (this->m_programID);
  766. for (int index = 0; index <= 9; index++) {
  767. const std::string name = "g_Texture" + std::to_string (index);
  768. const GLint loc = glGetUniformLocation (this->m_programID, name.c_str ());
  769. if (loc != -1) {
  770. glUniform1i (loc, index);
  771. }
  772. }
  773. }
  774. // first setup the default values, these will be overwritten by future values
  775. this->setupShaderVariables ();
  776. this->setupUniforms ();
  777. this->setupAttributes ();
  778. this->g_Texture0Rotation = glGetUniformLocation (this->m_programID, "g_Texture0Rotation");
  779. this->g_Texture0Translation = glGetUniformLocation (this->m_programID, "g_Texture0Translation");
  780. }
  781. bool CPass::applyFormatCombos (const TextureMap& passTextures, const TextureMap& overrideTextures) {
  782. const auto& fragment = this->m_shader->getFragment ();
  783. bool changed = false;
  784. for (const int slot : fragment.getFormatComboSlots ()) {
  785. std::shared_ptr<const TextureProvider> texture;
  786. if (slot == 0) {
  787. texture = this->m_renderable.getTexture ();
  788. } else {
  789. std::optional<std::string> name;
  790. for (const TextureMap* map : { &overrideTextures, &passTextures, &fragment.getTextures () }) {
  791. if (const auto it = map->find (slot); it != map->end () && !it->second.empty ()) {
  792. name = it->second;
  793. break;
  794. }
  795. }
  796. if (!name.has_value () || name->starts_with ("_rt_") || name->starts_with ("_alias_")) {
  797. continue;
  798. }
  799. try {
  800. texture = this->getContext ().resolveTexture (*name, this->m_renderable.getScene ().getScene ().project);
  801. } catch (const std::exception&) {
  802. continue;
  803. }
  804. }
  805. if (texture == nullptr || texture->getFormat () == TextureFormat_UNKNOWN) {
  806. continue;
  807. }
  808. const std::string combo = "TEX" + std::to_string (slot) + "FORMAT";
  809. const int format = static_cast<int> (texture->getFormat ());
  810. if (const auto it = this->m_combos.find (combo); it == this->m_combos.end () || it->second != format) {
  811. this->m_combos.insert_or_assign (combo, format);
  812. changed = true;
  813. }
  814. }
  815. return changed;
  816. }
  817. void CPass::setupAttributes () {
  818. this->addAttribute ("a_TexCoord", GL_FLOAT, 2, &this->a_TexCoord);
  819. this->addAttribute ("a_Position", GL_FLOAT, 3, &this->a_Position);
  820. }
  821. void CPass::setupTextureUniforms () {
  822. // Vertex shaders don't carry texture info in practice, but check them first anyway;
  823. // fragment textures are checked after and override/extend the chain.
  824. for (const auto& [index, textureName] : this->m_shader->getVertex ().getTextures ()) {
  825. try {
  826. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  827. ? this->resolveFBO (textureName)
  828. : this->getContext ().resolveTexture (
  829. textureName, this->m_renderable.getScene ().getScene ().project
  830. );
  831. // create chain entry
  832. this->m_textures[index] = std::make_shared<TextureChainEntry> (TextureChainEntry {
  833. .texture = texture,
  834. .next = nullptr,
  835. });
  836. } catch (std::runtime_error& ex) {
  837. sLog.error (
  838. "Cannot resolve texture '", textureName, "' (index=", index, ", object id=",
  839. this->m_renderable.getId (), ") for fragment shader ", ex.what ()
  840. );
  841. }
  842. }
  843. for (const auto& [index, textureName] : this->m_shader->getFragment ().getTextures ()) {
  844. try {
  845. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  846. ? this->resolveFBO (textureName)
  847. : this->getContext ().resolveTexture (
  848. textureName, this->m_renderable.getScene ().getScene ().project
  849. );
  850. const auto it = this->m_textures.find (index);
  851. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  852. .texture = texture,
  853. .next = it != this->m_textures.end () ? it->second : nullptr,
  854. });
  855. this->m_textures[index] = chain;
  856. } catch (std::runtime_error& ex) {
  857. sLog.error (
  858. "Cannot resolve texture '", textureName, "' (index=", index, ", object id=",
  859. this->m_renderable.getId (), ") for fragment shader ", ex.what ()
  860. );
  861. }
  862. }
  863. for (const auto& [index, textureName] : this->m_pass.textures) {
  864. try {
  865. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  866. ? this->resolveFBO (textureName)
  867. : this->getContext ().resolveTexture (
  868. textureName, this->m_renderable.getScene ().getScene ().project
  869. );
  870. const auto it = this->m_textures.find (index);
  871. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  872. .texture = texture,
  873. .next = it != this->m_textures.end () ? it->second : nullptr,
  874. });
  875. this->m_textures[index] = chain;
  876. if (textureName.find ("_rt_") != 0 && textureName.find ("_alias_") != 0) {
  877. this->trackPlayback (texture);
  878. }
  879. } catch (std::runtime_error& ex) {
  880. sLog.error (
  881. "Cannot resolve texture '", textureName, "' (index=", index, ", object id=",
  882. this->m_renderable.getId (), ") for pass ", ex.what ()
  883. );
  884. }
  885. }
  886. for (const auto& [index, propertyName] : this->m_pass.usertextures) {
  887. const auto resolvedName = this->resolveUserTextureName (propertyName);
  888. if (!resolvedName.has_value ()) {
  889. // optional user-provided texture slot, nothing configured - keep whatever the
  890. // regular "textures" entry already set for this index (if any)
  891. continue;
  892. }
  893. const std::string& textureName = *resolvedName;
  894. try {
  895. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  896. ? this->resolveFBO (textureName)
  897. : this->getContext ().resolveTexture (
  898. textureName, this->m_renderable.getScene ().getScene ().project
  899. );
  900. const auto it = this->m_textures.find (index);
  901. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  902. .texture = texture,
  903. .next = it != this->m_textures.end () ? it->second : nullptr,
  904. });
  905. this->m_textures[index] = chain;
  906. if (textureName.find ("_rt_") != 0 && textureName.find ("_alias_") != 0) {
  907. this->trackPlayback (texture);
  908. }
  909. } catch (std::runtime_error& ex) {
  910. sLog.error (
  911. "Cannot resolve user texture '", textureName, "' (index=", index, ", object id=",
  912. this->m_renderable.getId (), ") for pass ", ex.what ()
  913. );
  914. }
  915. }
  916. // override any texture
  917. for (const auto& [index, textureName] : this->m_override.textures) {
  918. try {
  919. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  920. ? this->resolveFBO (textureName)
  921. : this->getContext ().resolveTexture (
  922. textureName, this->m_renderable.getScene ().getScene ().project
  923. );
  924. const auto it = this->m_textures.find (index);
  925. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  926. .texture = texture,
  927. .next = it != this->m_textures.end () ? it->second : nullptr,
  928. });
  929. this->m_textures[index] = chain;
  930. if (textureName.find ("_rt_") != 0 && textureName.find ("_alias_") != 0) {
  931. this->trackPlayback (texture);
  932. }
  933. } catch (std::runtime_error& ex) {
  934. sLog.error (
  935. "Cannot resolve texture '", textureName, "' (index=", index, ", object id=",
  936. this->m_renderable.getId (), ") for override ", ex.what ()
  937. );
  938. }
  939. }
  940. for (const auto& [index, propertyName] : this->m_override.usertextures) {
  941. const auto resolvedName = this->resolveUserTextureName (propertyName);
  942. if (!resolvedName.has_value ()) {
  943. continue;
  944. }
  945. const std::string& textureName = *resolvedName;
  946. try {
  947. auto texture = textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0
  948. ? this->resolveFBO (textureName)
  949. : this->getContext ().resolveTexture (
  950. textureName, this->m_renderable.getScene ().getScene ().project
  951. );
  952. const auto it = this->m_textures.find (index);
  953. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  954. .texture = texture,
  955. .next = it != this->m_textures.end () ? it->second : nullptr,
  956. });
  957. this->m_textures[index] = chain;
  958. if (textureName.find ("_rt_") != 0 && textureName.find ("_alias_") != 0) {
  959. this->trackPlayback (texture);
  960. }
  961. } catch (std::runtime_error& ex) {
  962. sLog.error (
  963. "Cannot resolve user texture '", textureName, "' (index=", index, ", object id=",
  964. this->m_renderable.getId (), ") for override ", ex.what ()
  965. );
  966. }
  967. }
  968. // binds are set last as they're the most important to be set
  969. for (const auto& [index, bind] : this->m_binds) {
  970. const auto texture = bind == "previous" ? nullptr : this->resolveFBO (bind);
  971. const auto it = this->m_textures.find (index);
  972. const auto chain = std::make_shared<TextureChainEntry> (TextureChainEntry {
  973. .texture = texture,
  974. .next = it != this->m_textures.end () ? it->second : nullptr,
  975. });
  976. this->m_textures[index] = chain;
  977. }
  978. std::shared_ptr<const TextureProvider> texture = this->resolveTexture (this->m_renderable.getTexture (), 0);
  979. this->addUniform ("g_Texture0", 0);
  980. this->addUniform ("g_Texture1", 1);
  981. this->addUniform ("g_Texture2", 2);
  982. this->addUniform ("g_Texture3", 3);
  983. this->addUniform ("g_Texture4", 4);
  984. this->addUniform ("g_Texture5", 5);
  985. this->addUniform ("g_Texture6", 6);
  986. this->addUniform ("g_Texture7", 7);
  987. this->addUniform ("g_TextureReductionScale", 1.0f);
  988. this->m_texture0Resolution = *texture->getResolution ();
  989. this->addUniform ("g_Texture0Resolution", &this->m_texture0Resolution);
  990. for (const auto& [textureIndex, expectedTexture] : this->m_textures) {
  991. std::ostringstream namestream;
  992. namestream << "g_Texture" << textureIndex << "Resolution";
  993. texture = this->resolveTexture (expectedTexture->texture, textureIndex, texture);
  994. const glm::vec4* res = texture->getResolution ();
  995. this->addUniform (namestream.str (), res);
  996. }
  997. this->addUniform ("g_Texture0Resolution", &this->m_texture0Resolution);
  998. }
  999. void CPass::setupUniforms () {
  1000. this->setupTextureUniforms ();
  1001. const auto& renderable = this->m_renderable;
  1002. const auto& scene = this->m_renderable.getScene ();
  1003. const auto& sceneData = this->m_renderable.getScene ().getScene ();
  1004. const auto& recorder = this->m_renderable.getScene ().getAudioContext ().getRecorder ();
  1005. // lighting variables
  1006. this->addUniform ("g_LightAmbientColor", sceneData.colors.ambient->value->getVec3 ());
  1007. this->addUniform ("g_LightSkylightColor", sceneData.colors.skylight->value->getVec3 ());
  1008. this->addUniform ("g_LightsPosition", UniformType::Vector3, scene.getLightsPosition (), 4);
  1009. this->addUniform ("g_LightsColorPremultiplied", UniformType::Vector4, scene.getLightsColorPremultiplied (), 3);
  1010. this->addUniform ("g_AltModelMatrix", &this->m_lightingModelMatrix);
  1011. this->addUniform ("g_AltNormalModelMatrix", &this->m_lightingNormalMatrix);
  1012. this->addUniform ("g_AltViewProjectionMatrix", &this->m_lightingViewProjectionMatrix);
  1013. this->addUniform (
  1014. "g_Screen",
  1015. glm::vec3 (
  1016. scene.getWidth (), scene.getHeight (),
  1017. static_cast<float> (scene.getWidth ()) / static_cast<float> (std::max (scene.getHeight (), 1))
  1018. )
  1019. );
  1020. // register variables like brightness and alpha with some default value
  1021. this->addUniform ("g_Brightness", renderable.getBrightness ());
  1022. this->addUniform ("g_UserAlpha", renderable.getUserAlpha ());
  1023. this->addUniform ("g_Alpha", renderable.getAlpha ());
  1024. this->addUniform ("g_Color", renderable.getColor ());
  1025. this->addUniform ("g_Color4", renderable.getColor4 ());
  1026. if (!this->m_uniforms.contains ("g_CompositeColor")) {
  1027. this->addUniform ("g_CompositeColor", renderable.getCompositeColor ());
  1028. }
  1029. // add some external variables
  1030. this->addUniform ("g_Time", &g_Time);
  1031. this->addUniform ("g_Daytime", &g_Daytime);
  1032. // add model-view-projection matrix
  1033. this->addUniform ("g_ModelViewProjectionMatrixInverse", &this->m_modelViewProjectionMatrixInverse);
  1034. this->addUniform ("g_ModelViewProjectionMatrix", &this->m_modelViewProjectionMatrix);
  1035. this->addUniform ("g_EffectModelViewProjectionMatrix", &this->m_modelViewProjectionMatrix);
  1036. this->addUniform ("g_ModelMatrix", &this->m_modelMatrix);
  1037. this->addUniform ("g_EffectModelMatrix", &this->m_modelMatrix);
  1038. this->addUniform ("g_NormalModelMatrix", glm::identity<glm::mat3> ());
  1039. this->addUniform ("g_ViewProjectionMatrix", &this->m_viewProjectionMatrix);
  1040. this->addUniform ("g_PointerPosition", scene.getMousePosition ());
  1041. this->addUniform ("g_PointerPositionLast", scene.getMousePositionLast ());
  1042. this->addUniform ("g_ParallaxPosition", scene.getParallaxPosition ());
  1043. this->addUniform ("g_EffectTextureProjectionMatrix", &this->m_effectTextureProjectionMatrix);
  1044. this->addUniform ("g_EffectTextureProjectionMatrixInverse", &this->m_effectTextureProjectionMatrixInverse);
  1045. this->addUniform ("g_TexelSize", glm::vec2 (1.0 / scene.getWidth (), 1.0 / scene.getHeight ()));
  1046. this->addUniform ("g_TexelSizeHalf", glm::vec2 (0.5 / scene.getWidth (), 0.5 / scene.getHeight ()));
  1047. this->addUniform ("g_AudioSpectrum16Left", recorder.audio16, 16);
  1048. this->addUniform ("g_AudioSpectrum16Right", recorder.audio16 + 16, 16);
  1049. this->addUniform ("g_AudioSpectrum32Left", recorder.audio32, 32);
  1050. this->addUniform ("g_AudioSpectrum32Right", recorder.audio32 + 32, 32);
  1051. this->addUniform ("g_AudioSpectrum64Left", recorder.audio64, 64);
  1052. this->addUniform ("g_AudioSpectrum64Right", recorder.audio64 + 64, 64);
  1053. }
  1054. void CPass::addAttribute (const std::string& name, GLint type, GLint elements, const GLuint* value) {
  1055. const GLint id = glGetAttribLocation (this->m_programID, name.c_str ());
  1056. if (id == -1) {
  1057. return;
  1058. }
  1059. this->m_attribs.emplace_back (new AttribEntry (id, name, type, elements, value));
  1060. }
  1061. template <typename T> void CPass::addUniform (const std::string& name, UniformType type, T value) {
  1062. GLint id = glGetUniformLocation (this->m_programID, name.c_str ());
  1063. // parameter not found, can be ignored
  1064. if (id == -1) {
  1065. return;
  1066. }
  1067. // frees any previously registered value for this uniform name
  1068. const auto it = this->m_uniforms.find (name);
  1069. if (it != this->m_uniforms.end ()) {
  1070. delete it->second;
  1071. }
  1072. T* newValue = new T (value);
  1073. this->m_uniforms.insert_or_assign (name, new UniformEntry (id, name, type, newValue, 1, true));
  1074. }
  1075. template <typename T> void CPass::addUniform (const std::string& name, UniformType type, T* value, int count) {
  1076. // this version is used to reference to system variables so things like g_Time works fine
  1077. GLint id = glGetUniformLocation (this->m_programID, name.c_str ());
  1078. // parameter not found, can be ignored
  1079. if (id == -1) {
  1080. return;
  1081. }
  1082. if (const auto it = this->m_uniforms.find (name); it != this->m_uniforms.end ()) {
  1083. delete it->second;
  1084. }
  1085. this->m_uniforms.insert_or_assign (name, new UniformEntry (id, name, type, value, count));
  1086. }
  1087. template <typename T> void CPass::addUniform (const std::string& name, UniformType type, T** value) {
  1088. // this version is used to reference to system variables so things like g_Time works fine
  1089. const GLint id = glGetUniformLocation (this->m_programID, name.c_str ());
  1090. // parameter not found, can be ignored
  1091. if (id == -1) {
  1092. return;
  1093. }
  1094. if (const auto it = this->m_uniforms.find (name); it != this->m_uniforms.end ()) {
  1095. delete it->second;
  1096. }
  1097. this->m_referenceUniforms.insert_or_assign (
  1098. name, new ReferenceUniformEntry (id, name, type, reinterpret_cast<const void**> (value))
  1099. );
  1100. }
  1101. void CPass::setupShaderVariables () {
  1102. for (const auto& cur : this->m_shader->getVertex ().getParameters ()) {
  1103. if (!this->m_uniforms.contains (cur->getName ())) {
  1104. this->addUniform (cur);
  1105. }
  1106. }
  1107. for (const auto& cur : this->m_shader->getFragment ().getParameters ()) {
  1108. if (!this->m_uniforms.contains (cur->getName ())) {
  1109. this->addUniform (cur);
  1110. }
  1111. }
  1112. // apply material pass constants (e.g. constantshadervalues from the material JSON)
  1113. for (const auto& [name, value] : this->m_pass.constants) {
  1114. const auto [vertex, fragment] = this->m_shader->findParameter (name);
  1115. if (vertex == nullptr && fragment == nullptr) {
  1116. continue;
  1117. }
  1118. ShaderVariable* var = vertex == nullptr ? fragment : vertex;
  1119. this->addUniform (var, value->value.get ());
  1120. }
  1121. // apply override constants (highest priority, overrides both defaults and pass constants)
  1122. for (const auto& [name, value] : this->m_override.constants) {
  1123. const auto [vertex, fragment] = this->m_shader->findParameter (name);
  1124. if (vertex == nullptr && fragment == nullptr) {
  1125. continue;
  1126. }
  1127. ShaderVariable* var = vertex == nullptr ? fragment : vertex;
  1128. this->addUniform (var, value->value.get ());
  1129. }
  1130. // bind the full-reveal bypass uniform injected by patchXrayFullRevealBypass() (see setupShaders());
  1131. // a no-op if the patch didn't find its anchors, since the uniform then doesn't exist in the shader
  1132. if (this->m_pass.shader == XRAY_EFFECT_SHADER) {
  1133. this->addUniform ("g_XrayFullReveal", &this->m_xrayFullReveal);
  1134. }
  1135. }
  1136. void CPass::addUniform (ShaderVariable* value) {
  1137. // delegates to the (ShaderVariable*, DynamicValue*) overload, which handles the casting
  1138. this->addUniform (value, value);
  1139. }
  1140. void CPass::addUniform (const ShaderVariable* value, const DynamicValue* setting) {
  1141. if (value->is<ShaderVariableFloat> ()) {
  1142. this->addUniform (value->getName (), &setting->getFloat ());
  1143. } else if (value->is<ShaderVariableInteger> ()) {
  1144. this->addUniform (value->getName (), &setting->getInt ());
  1145. } else if (value->is<ShaderVariableVector2> ()) {
  1146. this->addUniform (value->getName (), &setting->getVec2 ());
  1147. } else if (value->is<ShaderVariableVector3> ()) {
  1148. this->addUniform (value->getName (), &setting->getVec3 ());
  1149. } else if (value->is<ShaderVariableVector4> ()) {
  1150. this->addUniform (value->getName (), &setting->getVec4 ());
  1151. } else {
  1152. sLog.error ("Cannot convert setting dynamic value to ", value->getName (), ". Using default value");
  1153. }
  1154. }
  1155. void CPass::addUniform (const std::string& name, int value) { this->addUniform (name, UniformType::Integer, value); }
  1156. void CPass::addUniform (const std::string& name, const int* value, int count) {
  1157. this->addUniform (name, UniformType::Integer, value, count);
  1158. }
  1159. void CPass::addUniform (const std::string& name, const int** value) {
  1160. this->addUniform (name, UniformType::Integer, value);
  1161. }
  1162. void CPass::addUniform (const std::string& name, double value) { this->addUniform (name, UniformType::Double, value); }
  1163. void CPass::addUniform (const std::string& name, const double* value, int count) {
  1164. this->addUniform (name, UniformType::Double, value, count);
  1165. }
  1166. void CPass::addUniform (const std::string& name, const double** value) {
  1167. this->addUniform (name, UniformType::Double, value);
  1168. }
  1169. void CPass::addUniform (const std::string& name, float value) { this->addUniform (name, UniformType::Float, value); }
  1170. void CPass::addUniform (const std::string& name, const float* value, int count) {
  1171. this->addUniform (name, UniformType::Float, value, count);
  1172. }
  1173. void CPass::addUniform (const std::string& name, const float** value) {
  1174. this->addUniform (name, UniformType::Float, value);
  1175. }
  1176. void CPass::addUniform (const std::string& name, glm::vec2 value) {
  1177. this->addUniform (name, UniformType::Vector2, value);
  1178. }
  1179. void CPass::addUniform (const std::string& name, const glm::vec2* value) {
  1180. this->addUniform (name, UniformType::Vector2, value, 1);
  1181. }
  1182. void CPass::addUniform (const std::string& name, const glm::vec2** value) {
  1183. this->addUniform (name, UniformType::Vector2, value, 1);
  1184. }
  1185. void CPass::addUniform (const std::string& name, glm::vec3 value) {
  1186. this->addUniform (name, UniformType::Vector3, value);
  1187. }
  1188. void CPass::addUniform (const std::string& name, const glm::vec3* value) {
  1189. this->addUniform (name, UniformType::Vector3, value, 1);
  1190. }
  1191. void CPass::addUniform (const std::string& name, const glm::vec3** value) {
  1192. this->addUniform (name, UniformType::Vector3, value);
  1193. }
  1194. void CPass::addUniform (const std::string& name, const glm::vec4 value) {
  1195. this->addUniform (name, UniformType::Vector4, value);
  1196. }
  1197. GLenum CPass::getDeclaredUniformType (const std::string& name) const {
  1198. GLint count = 0;
  1199. glGetProgramiv (this->m_programID, GL_ACTIVE_UNIFORMS, &count);
  1200. for (GLint index = 0; index < count; index++) {
  1201. char buffer[256];
  1202. GLsizei length = 0;
  1203. GLint size = 0;
  1204. GLenum type = GL_NONE;
  1205. glGetActiveUniform (this->m_programID, index, sizeof (buffer), &length, &size, &type, buffer);
  1206. if (name == buffer) {
  1207. return type;
  1208. }
  1209. }
  1210. return GL_NONE;
  1211. }
  1212. void CPass::addUniform (const std::string& name, const glm::vec4* value) {
  1213. // resolution uniforms are always kept as a vec4 (texture size + real size), but shaders often declare
  1214. // them as a vec2 and glUniform4 on that is a GL error that leaves the uniform at 0 (color_key_plus
  1215. // then divides by a zero resolution and the whole layer comes out NaN)
  1216. switch (this->getDeclaredUniformType (name)) {
  1217. case GL_FLOAT_VEC2:
  1218. this->addUniform (name, UniformType::Vector2, reinterpret_cast<const glm::vec2*> (value), 1);
  1219. return;
  1220. case GL_FLOAT_VEC3:
  1221. this->addUniform (name, UniformType::Vector3, reinterpret_cast<const glm::vec3*> (value), 1);
  1222. return;
  1223. default:
  1224. break;
  1225. }
  1226. this->addUniform (name, UniformType::Vector4, value, 1);
  1227. }
  1228. void CPass::addUniform (const std::string& name, const glm::vec4** value) {
  1229. this->addUniform (name, UniformType::Vector4, value);
  1230. }
  1231. void CPass::addUniform (const std::string& name, const glm::mat3& value) {
  1232. this->addUniform (name, UniformType::Matrix3, value);
  1233. }
  1234. void CPass::addUniform (const std::string& name, const glm::mat3* value) {
  1235. this->addUniform (name, UniformType::Matrix3, value, 1);
  1236. }
  1237. void CPass::addUniform (const std::string& name, const glm::mat3** value) {
  1238. this->addUniform (name, UniformType::Matrix3, value);
  1239. }
  1240. void CPass::addUniform (const std::string& name, const glm::mat4 value) {
  1241. this->addUniform (name, UniformType::Matrix4, value);
  1242. }
  1243. void CPass::addUniform (const std::string& name, const glm::mat4* value) {
  1244. this->addUniform (name, UniformType::Matrix4, value, 1);
  1245. }
  1246. void CPass::addUniform (const std::string& name, const glm::mat4** value) {
  1247. this->addUniform (name, UniformType::Matrix4, value);
  1248. }