CWallpaper.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. #include <cmath>
  2. #include <tuple>
  3. #include <vector>
  4. #include <stb_image.h>
  5. #include "CWallpaper.h"
  6. #include "WallpaperEngine/Assets/AssetLoadException.h"
  7. #include "WallpaperEngine/Data/Parsers/TextureParser.h"
  8. #include "WallpaperEngine/Data/Utils/BinaryReader.h"
  9. #include "WallpaperEngine/Logging/Log.h"
  10. #include "WallpaperEngine/Render/Wallpapers/CScene.h"
  11. #include "WallpaperEngine/Render/Wallpapers/CSplat.h"
  12. #include "WallpaperEngine/Render/Wallpapers/CVideo.h"
  13. #include "WallpaperEngine/Render/Wallpapers/CWeb.h"
  14. #include "WallpaperEngine/Data/Model/Project.h"
  15. #include "WallpaperEngine/Data/Model/Wallpaper.h"
  16. using namespace WallpaperEngine::Render;
  17. using WallpaperEngine::Data::Utils::BinaryReader;
  18. CWallpaper::CWallpaper (
  19. const Wallpaper& wallpaperData, RenderContext& context, AudioContext& audioContext,
  20. const WallpaperState::TextureUVsScaling& scalingMode, const uint32_t& clampMode
  21. ) :
  22. ContextAware (context), FBOProvider (nullptr), m_wallpaperData (wallpaperData), m_audioContext (audioContext),
  23. m_state (scalingMode, clampMode) {
  24. glGenVertexArrays (1, &this->m_vaoBuffer);
  25. glBindVertexArray (this->m_vaoBuffer);
  26. this->setupShaders ();
  27. constexpr GLfloat texCoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f };
  28. // inverted positions so the final texture is rendered properly
  29. constexpr GLfloat position[] = { -1.0f, 1.0f, 0.0f, 1.0, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f,
  30. -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f };
  31. // GL_DYNAMIC_DRAW: render() rewrites this buffer's contents (via glBufferSubData) whenever
  32. // the wallpaper's UVs change. The attrib pointer/enable state below is captured by the VAO
  33. // once here rather than redone every render() call - only the buffer's contents change later.
  34. glGenBuffers (1, &this->m_texCoordBuffer);
  35. glBindBuffer (GL_ARRAY_BUFFER, this->m_texCoordBuffer);
  36. glBufferData (GL_ARRAY_BUFFER, sizeof (texCoords), texCoords, GL_DYNAMIC_DRAW);
  37. glEnableVertexAttribArray (this->a_TexCoord);
  38. glVertexAttribPointer (this->a_TexCoord, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
  39. // Static for the object's lifetime, so its attrib setup also only needs to happen once here.
  40. glGenBuffers (1, &this->m_positionBuffer);
  41. glBindBuffer (GL_ARRAY_BUFFER, this->m_positionBuffer);
  42. glBufferData (GL_ARRAY_BUFFER, sizeof (position), position, GL_STATIC_DRAW);
  43. glEnableVertexAttribArray (this->a_Position);
  44. glVertexAttribPointer (this->a_Position, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
  45. }
  46. CWallpaper::~CWallpaper () {
  47. // programs here only ever have 2 shaders attached (vertex + fragment)
  48. GLuint attachedShaders[2];
  49. GLsizei attachedCount = 0;
  50. glGetAttachedShaders (this->m_shader, 2, &attachedCount, attachedShaders);
  51. for (auto i = 0; i < attachedCount; i++) {
  52. glDeleteShader (attachedShaders[i]);
  53. }
  54. glDeleteProgram (this->m_shader);
  55. if (this->m_lutTexture != GL_NONE) {
  56. glDeleteTextures (1, &this->m_lutTexture);
  57. }
  58. glDeleteBuffers (1, &this->m_texCoordBuffer);
  59. glDeleteBuffers (1, &this->m_positionBuffer);
  60. glDeleteVertexArrays (1, &this->m_vaoBuffer);
  61. }
  62. const AssetLocator& CWallpaper::getAssetLocator () const { return *this->m_wallpaperData.project.assetLocator; }
  63. const Wallpaper& CWallpaper::getWallpaperData () const { return this->m_wallpaperData; }
  64. GLuint CWallpaper::getWallpaperFramebuffer () const { return this->m_sceneFBO->getFramebuffer (); }
  65. GLuint CWallpaper::getWallpaperTexture () const { return this->m_sceneFBO->getTextureID (0); }
  66. void CWallpaper::setupShaders () {
  67. const GLuint vertexShaderID = glCreateShader (GL_VERTEX_SHADER);
  68. const char* sourcePointer = "#version 330\n"
  69. "precision highp float;\n"
  70. "in vec3 a_Position;\n"
  71. "in vec2 a_TexCoord;\n"
  72. "out vec2 v_TexCoord;\n"
  73. "void main () {\n"
  74. "gl_Position = vec4 (a_Position, 1.0);\n"
  75. "v_TexCoord = a_TexCoord;\n"
  76. "}";
  77. glShaderSource (vertexShaderID, 1, &sourcePointer, nullptr);
  78. glCompileShader (vertexShaderID);
  79. GLint result = GL_FALSE;
  80. int infoLogLength = 0;
  81. glGetShaderiv (vertexShaderID, GL_COMPILE_STATUS, &result);
  82. glGetShaderiv (vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
  83. if (infoLogLength > 0) {
  84. const auto logBuffer = new char[infoLogLength + 1];
  85. memset (logBuffer, 0, infoLogLength + 1);
  86. glGetShaderInfoLog (vertexShaderID, infoLogLength, nullptr, logBuffer);
  87. const std::string message = logBuffer;
  88. delete[] logBuffer;
  89. sLog.exception (message);
  90. }
  91. const GLuint fragmentShaderID = glCreateShader (GL_FRAGMENT_SHADER);
  92. // image filter and color options are assets/shaders/ccsimple.frag
  93. sourcePointer = "#version 330\n"
  94. "precision highp float;\n"
  95. "uniform sampler2D g_Texture0;\n"
  96. "uniform sampler3D g_Texture1;\n"
  97. "uniform vec4 g_Params;\n"
  98. "uniform float g_LutParams;\n"
  99. "uniform bool u_ColorEnabled;\n"
  100. "uniform bool u_LutEnabled;\n"
  101. "uniform bool u_InputLinear;\n"
  102. "uniform bool u_OutputPQ;\n"
  103. "in vec2 v_TexCoord;\n"
  104. "out vec4 out_FragColor;\n"
  105. // BT.2408 reference white, what the PQ image description of HDR surfaces anchors SDR content to
  106. "const float referenceWhite = 203.0;\n"
  107. "const mat3 bt709to2020 = mat3 (0.6274, 0.0691, 0.0164, 0.3293, 0.9195, 0.0880, 0.0433, 0.0114, 0.8956);\n"
  108. "const mat3 bt2020to709 = mat3 (1.6605, -0.1246, -0.0182, -0.5876, 1.1329, -0.1006, -0.0728, -0.0083, "
  109. "1.1187);\n"
  110. "vec3 srgbToLinear (vec3 c) {\n"
  111. "return mix (c / 12.92, pow ((c + 0.055) / 1.055, vec3 (2.4)), step (0.04045, c));\n"
  112. "}\n"
  113. "vec3 linearToSrgb (vec3 c) {\n"
  114. "return mix (c * 12.92, 1.055 * pow (c, vec3 (1.0 / 2.4)) - 0.055, step (0.0031308, c));\n"
  115. "}\n"
  116. // mpv decodes SDR video with BT.1886, a pure 2.4 power, going back the same way keeps SDR videos
  117. // exactly as they look without --hdr
  118. "vec3 videoToLinear (vec3 c) { return pow (c, vec3 (2.4)); }\n"
  119. "vec3 linearToVideo (vec3 c) { return pow (c, vec3 (1.0 / 2.4)); }\n"
  120. "vec3 pqEncode (vec3 nits) {\n"
  121. "vec3 y = pow (clamp (nits / 10000.0, 0.0, 1.0), vec3 (0.1593017578125));\n"
  122. "return pow ((0.8359375 + 18.8515625 * y) / (1.0 + 18.6875 * y), vec3 (78.84375));\n"
  123. "}\n"
  124. // highlights of HDR video on an SDR output, a soft knee instead of a hard clip
  125. "vec3 softClip (vec3 x) {\n"
  126. "return mix (x, 0.8 + 0.2 * (1.0 - exp (-(x - 0.8) / 0.2)), step (0.8, x));\n"
  127. "}\n"
  128. "vec3 hsv2rgb (vec3 c) {\n"
  129. "vec4 K = vec4 (1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"
  130. "vec3 p = abs (fract (c.xxx + K.xyz) * 6.0 - K.www);\n"
  131. "return c.z * mix (K.xxx, clamp (p - K.xxx, 0.0, 1.0), c.y);\n"
  132. "}\n"
  133. "vec3 rgb2hsv (vec3 RGB) {\n"
  134. "vec4 P = (RGB.g < RGB.b) ? vec4 (RGB.bg, -1.0, 2.0 / 3.0) : vec4 (RGB.gb, 0.0, -1.0 / 3.0);\n"
  135. "vec4 Q = (RGB.r < P.x) ? vec4 (P.xyw, RGB.r) : vec4 (RGB.r, P.yzx);\n"
  136. "float C = Q.x - min (Q.w, Q.y);\n"
  137. "float H = abs ((Q.w - Q.y) / (6.0 * C + 1e-10) + Q.z);\n"
  138. "return vec3 (H, C / (Q.x + 1e-10), Q.x);\n"
  139. "}\n"
  140. "void main () {\n"
  141. "vec4 albedo = texture (g_Texture0, v_TexCoord);\n"
  142. "bool filtered = u_ColorEnabled || u_LutEnabled;\n"
  143. "vec3 overbright = vec3 (0.0);\n"
  144. // the filters work on SDR-encoded values like in Wallpaper Engine, what's past reference white is kept aside
  145. "if (u_InputLinear && filtered) {\n"
  146. "vec3 light = max (bt2020to709 * albedo.rgb, 0.0);\n"
  147. "overbright = max (light - 1.0, 0.0);\n"
  148. "albedo.rgb = linearToVideo (min (light, 1.0));\n"
  149. "}\n"
  150. "if (u_ColorEnabled) {\n"
  151. "albedo.rgb = mix (vec3 (0.5), albedo.rgb, g_Params.y);\n"
  152. "vec3 hsv = rgb2hsv (albedo.rgb);\n"
  153. "hsv.z *= g_Params.x;\n"
  154. "hsv.y *= g_Params.z;\n"
  155. "hsv.x += g_Params.w;\n"
  156. "albedo.rgb = hsv2rgb (hsv);\n"
  157. "}\n"
  158. "if (u_LutEnabled) {\n"
  159. "albedo.rgb = mix (albedo.rgb, texture (g_Texture1, albedo.rgb).rgb, g_LutParams);\n"
  160. "}\n"
  161. "if (u_InputLinear || u_OutputPQ) {\n"
  162. "vec3 encoded = clamp (albedo.rgb, 0.0, 1.0);\n"
  163. "vec3 linear2020 = !u_InputLinear ? bt709to2020 * srgbToLinear (encoded)\n"
  164. " : filtered ? bt709to2020 * (videoToLinear (encoded) + overbright) : albedo.rgb;\n"
  165. "albedo.rgb = u_OutputPQ ? pqEncode (max (linear2020, 0.0) * referenceWhite)\n"
  166. " : linearToVideo (min (softClip (max (bt2020to709 * linear2020, 0.0)), 1.0));\n"
  167. "}\n"
  168. "out_FragColor = albedo;\n"
  169. "}";
  170. glShaderSource (fragmentShaderID, 1, &sourcePointer, nullptr);
  171. glCompileShader (fragmentShaderID);
  172. result = GL_FALSE;
  173. infoLogLength = 0;
  174. glGetShaderiv (fragmentShaderID, GL_COMPILE_STATUS, &result);
  175. glGetShaderiv (fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength);
  176. if (infoLogLength > 0) {
  177. const auto logBuffer = new char[infoLogLength + 1];
  178. memset (logBuffer, 0, infoLogLength + 1);
  179. glGetShaderInfoLog (fragmentShaderID, infoLogLength, nullptr, logBuffer);
  180. const std::string message = logBuffer;
  181. delete[] logBuffer;
  182. sLog.exception (message);
  183. }
  184. this->m_shader = glCreateProgram ();
  185. glAttachShader (this->m_shader, vertexShaderID);
  186. glAttachShader (this->m_shader, fragmentShaderID);
  187. glLinkProgram (this->m_shader);
  188. result = GL_FALSE;
  189. infoLogLength = 0;
  190. glGetProgramiv (this->m_shader, GL_LINK_STATUS, &result);
  191. glGetProgramiv (this->m_shader, GL_INFO_LOG_LENGTH, &infoLogLength);
  192. if (infoLogLength > 0) {
  193. const auto logBuffer = new char[infoLogLength + 1];
  194. memset (logBuffer, 0, infoLogLength + 1);
  195. glGetProgramInfoLog (this->m_shader, infoLogLength, nullptr, logBuffer);
  196. const std::string message = logBuffer;
  197. delete[] logBuffer;
  198. sLog.exception (message);
  199. }
  200. // shaders can be detached and deleted once linked into the program
  201. glDetachShader (this->m_shader, vertexShaderID);
  202. glDetachShader (this->m_shader, fragmentShaderID);
  203. glDeleteShader (vertexShaderID);
  204. glDeleteShader (fragmentShaderID);
  205. this->g_Texture0 = glGetUniformLocation (this->m_shader, "g_Texture0");
  206. this->g_Texture1 = glGetUniformLocation (this->m_shader, "g_Texture1");
  207. this->g_Params = glGetUniformLocation (this->m_shader, "g_Params");
  208. this->g_LutParams = glGetUniformLocation (this->m_shader, "g_LutParams");
  209. this->u_ColorEnabled = glGetUniformLocation (this->m_shader, "u_ColorEnabled");
  210. this->u_LutEnabled = glGetUniformLocation (this->m_shader, "u_LutEnabled");
  211. this->u_InputLinear = glGetUniformLocation (this->m_shader, "u_InputLinear");
  212. this->u_OutputPQ = glGetUniformLocation (this->m_shader, "u_OutputPQ");
  213. this->a_Position = glGetAttribLocation (this->m_shader, "a_Position");
  214. this->a_TexCoord = glGetAttribLocation (this->m_shader, "a_TexCoord");
  215. }
  216. void CWallpaper::setDestinationFramebuffer (GLuint framebuffer) { this->m_destFramebuffer = framebuffer; }
  217. void CWallpaper::setSpanInfo (const SpanInfo& spanInfo) { this->m_spanInfo = spanInfo; }
  218. const CWallpaper::SpanInfo* CWallpaper::getSpanInfo () const {
  219. return this->m_spanInfo.has_value () ? &this->m_spanInfo.value () : nullptr;
  220. }
  221. void CWallpaper::updateUVs (const glm::ivec4& viewport, const bool vflip) {
  222. if (this->m_state.hasChanged (viewport, vflip, this->getCanvasWidth (), this->getCanvasHeight ())) {
  223. this->m_state.updateState (viewport, vflip, this->getCanvasWidth (), this->getCanvasHeight ());
  224. }
  225. }
  226. void CWallpaper::render (
  227. const glm::ivec4& viewport, const bool vflip, const glm::ivec2& globalPosition, const glm::ivec2& logicalSize
  228. ) {
  229. const uint32_t currentFrame = this->getContext ().getDriver ().getFrameCounter ();
  230. const bool needsSceneRender = (currentFrame != this->m_lastRenderedFrame);
  231. const glm::ivec4 sceneViewport = this->m_spanInfo.has_value ()
  232. ? glm::ivec4 { 0, 0, this->m_spanInfo->totalBounds.z, this->m_spanInfo->totalBounds.w }
  233. : viewport;
  234. this->m_screenSize = { sceneViewport.z, sceneViewport.w };
  235. #if !NDEBUG
  236. glPushDebugGroup (GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Rendering scene");
  237. #endif /* !NDEBUG */
  238. if (needsSceneRender) {
  239. this->renderFrame (sceneViewport);
  240. this->m_lastRenderedFrame = currentFrame;
  241. }
  242. #if !NDEBUG
  243. glPopDebugGroup ();
  244. glPushDebugGroup (GL_DEBUG_SOURCE_APPLICATION, 0, -1, "Rendering scene to output");
  245. #endif /* !NDEBUG */
  246. float ustart, uend, vstart, vend;
  247. if (this->m_spanInfo.has_value ()) {
  248. // span mode: scale the wallpaper to the bounding box using the normal scaling rules
  249. // (fill/fit/stretch/default), then slice per monitor
  250. const auto& span = this->m_spanInfo.value ();
  251. const float spanW = static_cast<float> (span.totalBounds.z);
  252. const float spanH = static_cast<float> (span.totalBounds.w);
  253. const float spanX = static_cast<float> (span.totalBounds.x);
  254. const float spanY = static_cast<float> (span.totalBounds.y);
  255. this->updateUVs (span.totalBounds, vflip);
  256. auto [baseUstart, baseUend, baseVstart, baseVend] = this->m_state.getTextureUVs ();
  257. // this viewport's relative position within the bounding box [0..1]; logicalSize is in the
  258. // same coordinate space as globalPosition and totalBounds
  259. float relLeft = (static_cast<float> (globalPosition.x) - spanX) / spanW;
  260. float relRight = (static_cast<float> (globalPosition.x + logicalSize.x) - spanX) / spanW;
  261. // a flipped span mirrors the whole group, the leftmost screen shows the right end of the image
  262. if (this->m_flipHorizontal) {
  263. std::tie (relLeft, relRight) = std::make_pair (1.0f - relLeft, 1.0f - relRight);
  264. }
  265. const float relTop = (static_cast<float> (globalPosition.y) - spanY) / spanH;
  266. const float relBottom = (static_cast<float> (globalPosition.y + logicalSize.y) - spanY) / spanH;
  267. // interpolate within the base UVs to get this viewport's slice
  268. const float baseURange = baseUend - baseUstart;
  269. const float baseVRange = baseVend - baseVstart;
  270. ustart = baseUstart + relLeft * baseURange;
  271. uend = baseUstart + relRight * baseURange;
  272. vstart = baseVstart + relTop * baseVRange;
  273. vend = baseVstart + relBottom * baseVRange;
  274. if (this->m_lastRenderedFrame < 5) {
  275. sLog.debug (
  276. "SPAN DEBUG: viewport=", viewport.z, "x", viewport.w, " globalPos=(", globalPosition.x, ",",
  277. globalPosition.y, ")", " span=(", span.totalBounds.x, ",", span.totalBounds.y, ",", span.totalBounds.z,
  278. ",", span.totalBounds.w, ")", " rel=[", relLeft, ",", relRight, "]x[", relTop, ",", relBottom, "]",
  279. " baseUV=[", baseUstart, ",", baseUend, "]x[", baseVstart, ",", baseVend, "]", " finalUV=[", ustart,
  280. ",", uend, "]x[", vstart, ",", vend, "]"
  281. );
  282. }
  283. } else {
  284. updateUVs (viewport, vflip);
  285. auto uvs = this->m_state.getTextureUVs ();
  286. ustart = uvs.ustart;
  287. uend = uvs.uend;
  288. if (this->m_flipHorizontal) {
  289. std::swap (ustart, uend);
  290. }
  291. vstart = uvs.vstart;
  292. vend = uvs.vend;
  293. }
  294. const GLfloat texCoords[] = {
  295. ustart, vstart, uend, vstart, ustart, vend, ustart, vend, uend, vstart, uend, vend,
  296. };
  297. glViewport (viewport.x, viewport.y, viewport.z, viewport.w);
  298. glBindFramebuffer (GL_FRAMEBUFFER, this->m_destFramebuffer);
  299. glBindVertexArray (this->m_vaoBuffer);
  300. if (ustart != this->m_uploadedUstart || uend != this->m_uploadedUend || vstart != this->m_uploadedVstart
  301. || vend != this->m_uploadedVend) {
  302. glBindBuffer (GL_ARRAY_BUFFER, this->m_texCoordBuffer);
  303. glBufferSubData (GL_ARRAY_BUFFER, 0, sizeof (texCoords), texCoords);
  304. this->m_uploadedUstart = ustart;
  305. this->m_uploadedUend = uend;
  306. this->m_uploadedVstart = vstart;
  307. this->m_uploadedVend = vend;
  308. }
  309. this->drawOutputQuad ();
  310. if (this->getContext ().getApp ().getContext ().settings.render.debug.brightnessLog) {
  311. static uint32_t brightnessFrameCounter = 0;
  312. if ((brightnessFrameCounter++ % 30) == 0) {
  313. std::vector<unsigned char> px (viewport.z * viewport.w * 4);
  314. glReadPixels (
  315. viewport.x, viewport.y, viewport.z, viewport.w, GL_RGBA, GL_UNSIGNED_BYTE, px.data ()
  316. );
  317. uint64_t sum = 0;
  318. for (size_t i = 0; i < px.size (); i += 4) {
  319. sum += px[i] + px[i + 1] + px[i + 2];
  320. }
  321. const double mean = static_cast<double> (sum) / (px.size () / 4 * 3);
  322. sLog.out ("[BRIGHTNESS] frame=", brightnessFrameCounter, " mean=", mean);
  323. }
  324. }
  325. #if !NDEBUG
  326. glPopDebugGroup ();
  327. #endif /* !NDEBUG */
  328. }
  329. void CWallpaper::drawOutputQuad () {
  330. glDisable (GL_BLEND);
  331. glDisable (GL_DEPTH_TEST);
  332. glDisable (GL_CULL_FACE);
  333. glUseProgram (this->m_shader);
  334. glActiveTexture (GL_TEXTURE0);
  335. glBindTexture (GL_TEXTURE_2D, this->getWallpaperTexture ());
  336. glUniform1i (this->g_Texture0, 0);
  337. // a sampler2D and a sampler3D on the same unit is an invalid draw even when the LUT is never read
  338. glUniform1i (this->g_Texture1, 1);
  339. const bool lutEnabled = this->m_lutTexture != GL_NONE && this->m_lutStrength > 0.0f;
  340. glUniform1i (this->u_ColorEnabled, this->m_colorEnabled);
  341. glUniform1i (this->u_LutEnabled, lutEnabled);
  342. glUniform1i (this->u_InputLinear, this->m_linearInput);
  343. glUniform1i (this->u_OutputPQ, this->m_outputHDR);
  344. glUniform4fv (this->g_Params, 1, &this->m_colorParams.x);
  345. glUniform1f (this->g_LutParams, this->m_lutStrength);
  346. if (lutEnabled) {
  347. glActiveTexture (GL_TEXTURE1);
  348. glBindTexture (GL_TEXTURE_3D, this->m_lutTexture);
  349. glActiveTexture (GL_TEXTURE0);
  350. }
  351. glDrawArrays (GL_TRIANGLES, 0, 6);
  352. }
  353. void CWallpaper::setPause (bool newState) { }
  354. void CWallpaper::setAudioPolicy (bool muted, std::optional<int> ambientVolume) { }
  355. void CWallpaper::setupFramebuffers (const bool depth, const TextureFormat format) {
  356. const uint32_t width = this->getCanvasWidth ();
  357. const uint32_t height = this->getCanvasHeight ();
  358. const uint32_t clamp = this->m_state.getClampingMode ();
  359. const auto sceneFBO = this->create (
  360. "_rt_FullFrameBuffer", format, clamp, 1.0, { width, height }, { width, height },
  361. this->m_cornerColor
  362. );
  363. if (depth) {
  364. sceneFBO->attachDepthBuffer ();
  365. }
  366. this->m_sceneFBO = sceneFBO;
  367. this->alias ("_rt_MipMappedFrameBuffer", "_rt_FullFrameBuffer");
  368. }
  369. AudioContext& CWallpaper::getAudioContext () const { return this->m_audioContext; }
  370. const WallpaperState& CWallpaper::getState () const { return this->m_state; }
  371. void CWallpaper::setScalingMode (WallpaperState::TextureUVsScaling mode) { this->m_state.setTextureUVsStrategy (mode); }
  372. void CWallpaper::setZoom (float zoom) { this->m_state.setZoom (zoom); }
  373. void CWallpaper::setOffset (float offsetX, float offsetY) { this->m_state.setOffset (offsetX, offsetY); }
  374. void CWallpaper::setCornerColor (const glm::vec4& color) {
  375. this->m_cornerColor = color;
  376. if (this->m_sceneFBO != nullptr) {
  377. this->m_sceneFBO->setBorderColor (color);
  378. }
  379. }
  380. void CWallpaper::setImageAdjustments (const ImageAdjustments& adjustments) {
  381. // wallpaper64.exe sub_140181F30: sliders are 0-100 around 50, contrast and saturation go through a square
  382. // root and brightness through a square before reaching ccsimple
  383. const float brightness = adjustments.brightness.value_or (50.0f) / 50.0f;
  384. const float contrast = adjustments.contrast.value_or (50.0f) / 50.0f;
  385. const float saturation = adjustments.saturation.value_or (50.0f) / 50.0f;
  386. const float hue = adjustments.hue.value_or (50.0f) / 100.0f - 0.5f;
  387. this->m_colorEnabled = adjustments.colorEnabled.value_or (false)
  388. && (contrast != 1.0f || brightness != 1.0f || saturation != 1.0f || hue != 0.0f);
  389. this->m_colorParams = {
  390. std::pow (brightness, 2.0f), std::pow (contrast, 0.5f), std::pow (saturation, 0.5f), hue
  391. };
  392. this->m_lutStrength = adjustments.filterStrength.value_or (100.0f) / 100.0f;
  393. this->m_flipHorizontal = adjustments.flipHorizontal.value_or (false);
  394. // only scenes and videos have these in Wallpaper Engine
  395. if (this->is<Wallpapers::CWeb> ()) {
  396. this->m_colorEnabled = false;
  397. this->m_flipHorizontal = false;
  398. this->loadLut ("");
  399. return;
  400. }
  401. this->loadLut (adjustments.filter.value_or (""));
  402. }
  403. bool CWallpaper::hasImageAdjustments () const {
  404. return this->m_colorEnabled || this->m_flipHorizontal
  405. || (this->m_lutTexture != GL_NONE && this->m_lutStrength > 0.0f);
  406. }
  407. GLuint CWallpaper::renderAdjustedFramebuffer () {
  408. // a linear (HDR video) framebuffer needs encoding to sRGB even without adjustments
  409. if (!this->hasImageAdjustments () && !this->m_linearInput) {
  410. return this->getWallpaperFramebuffer ();
  411. }
  412. const auto width = static_cast<uint32_t> (this->getCanvasWidth ());
  413. const auto height = static_cast<uint32_t> (this->getCanvasHeight ());
  414. if (this->m_adjustedFBO == nullptr || this->m_adjustedFBO->getRealWidth () != width
  415. || this->m_adjustedFBO->getRealHeight () != height) {
  416. this->m_adjustedFBO = std::make_unique<CFBO> (
  417. "_rt_ImageAdjustments", TextureFormat_ARGB8888, TextureFlags_ClampUVs, 1.0f, width, height, width, height
  418. );
  419. }
  420. // the output quad puts v=0 at the top, the framebuffer keeps row 0 at the bottom
  421. const float ustart = this->m_flipHorizontal ? 1.0f : 0.0f;
  422. const float uend = 1.0f - ustart;
  423. const GLfloat texCoords[] = { ustart, 1.0f, uend, 1.0f, ustart, 0.0f, ustart, 0.0f, uend, 1.0f, uend, 0.0f };
  424. // render() uploads its own UVs again next frame
  425. this->m_uploadedUstart = std::numeric_limits<float>::quiet_NaN ();
  426. const GLuint previousDestination = this->m_destFramebuffer;
  427. const bool outputHDR = this->m_outputHDR;
  428. this->m_destFramebuffer = this->m_adjustedFBO->getFramebuffer ();
  429. // screenshots are always SDR
  430. this->m_outputHDR = false;
  431. glViewport (0, 0, static_cast<GLsizei> (width), static_cast<GLsizei> (height));
  432. glBindFramebuffer (GL_FRAMEBUFFER, this->m_destFramebuffer);
  433. glBindVertexArray (this->m_vaoBuffer);
  434. glBindBuffer (GL_ARRAY_BUFFER, this->m_texCoordBuffer);
  435. glBufferSubData (GL_ARRAY_BUFFER, 0, sizeof (texCoords), texCoords);
  436. this->drawOutputQuad ();
  437. this->m_destFramebuffer = previousDestination;
  438. this->m_outputHDR = outputHDR;
  439. return this->m_adjustedFBO->getFramebuffer ();
  440. }
  441. void CWallpaper::loadLut (const std::string& name) {
  442. if (name == this->m_lutName) {
  443. return;
  444. }
  445. this->m_lutName = name;
  446. if (this->m_lutTexture != GL_NONE) {
  447. glDeleteTextures (1, &this->m_lutTexture);
  448. this->m_lutTexture = GL_NONE;
  449. }
  450. if (name.empty ()) {
  451. return;
  452. }
  453. try {
  454. const auto stream = this->getAssetLocator ().texture ("lut/" + name);
  455. const auto texture = WallpaperEngine::Data::Parsers::TextureParser::parse (BinaryReader (stream));
  456. const auto& mipmap = texture->images.at (0).at (0);
  457. if (!(texture->flags & TextureFlags_Volume)) {
  458. sLog.error ("Image filter ", name, " is not a volume texture");
  459. return;
  460. }
  461. stbi_uc* decoded = nullptr;
  462. const void* pixels = mipmap->uncompressedData.get ();
  463. const size_t expected = static_cast<size_t> (mipmap->width) * mipmap->height * mipmap->depth * 4;
  464. // the z slices are stacked vertically in the image, the order glTexImage3D wants
  465. if (texture->freeImageFormat != FIF_UNKNOWN) {
  466. int width, height, channels;
  467. decoded = stbi_load_from_memory (
  468. reinterpret_cast<const stbi_uc*> (mipmap->uncompressedData.get ()), mipmap->uncompressedSize, &width,
  469. &height, &channels, 4
  470. );
  471. if (decoded == nullptr || static_cast<size_t> (width) * height * 4 != expected) {
  472. stbi_image_free (decoded);
  473. sLog.error ("Image filter ", name, " has unexpected image data");
  474. return;
  475. }
  476. pixels = decoded;
  477. } else if (texture->format != TextureFormat_ARGB8888 || static_cast<size_t> (mipmap->uncompressedSize) < expected) {
  478. sLog.error ("Image filter ", name, " has an unsupported format");
  479. return;
  480. }
  481. glGenTextures (1, &this->m_lutTexture);
  482. glBindTexture (GL_TEXTURE_3D, this->m_lutTexture);
  483. glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  484. glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  485. glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  486. glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  487. glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
  488. glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
  489. glTexImage3D (
  490. GL_TEXTURE_3D, 0, GL_RGBA8, mipmap->width, mipmap->height, mipmap->depth, 0, GL_RGBA, GL_UNSIGNED_BYTE,
  491. pixels
  492. );
  493. glBindTexture (GL_TEXTURE_3D, 0);
  494. stbi_image_free (decoded);
  495. } catch (const std::exception& e) {
  496. sLog.error ("Cannot load image filter ", name, ": ", e.what ());
  497. }
  498. }
  499. std::shared_ptr<const CFBO> CWallpaper::findFBO (const std::string& name) const {
  500. const auto fbo = this->find (name);
  501. if (fbo == nullptr) {
  502. sLog.exception ("Cannot find FBO ", name);
  503. }
  504. return fbo;
  505. }
  506. std::shared_ptr<const CFBO> CWallpaper::getFBO () const { return this->m_sceneFBO; }
  507. std::unique_ptr<CWallpaper> CWallpaper::fromWallpaper (
  508. const Wallpaper& wallpaper, RenderContext& context, AudioContext& audioContext,
  509. const std::filesystem::path& resolvedBackgroundPath, const WallpaperState::TextureUVsScaling& scalingMode,
  510. const uint32_t& clampMode, const glm::ivec2& maxRenderSize
  511. ) {
  512. if (wallpaper.is<Scene> ()) {
  513. return std::make_unique<WallpaperEngine::Render::Wallpapers::CScene> (
  514. wallpaper, context, audioContext, scalingMode, clampMode
  515. );
  516. }
  517. if (wallpaper.is<Video> ()) {
  518. return std::make_unique<WallpaperEngine::Render::Wallpapers::CVideo> (
  519. wallpaper, context, audioContext, scalingMode, clampMode
  520. );
  521. }
  522. // SOG depth wallpapers draw the splat data natively, the CEF viewer renders badly (see CSplat)
  523. if (wallpaper.is<Web> () && WallpaperEngine::Render::Wallpapers::CSplat::supports (wallpaper.project)) {
  524. try {
  525. return std::make_unique<WallpaperEngine::Render::Wallpapers::CSplat> (
  526. wallpaper, context, audioContext, scalingMode, clampMode
  527. );
  528. } catch (const std::exception& e) {
  529. sLog.error ("Native splat renderer failed, falling back to the web page: ", e.what ());
  530. }
  531. }
  532. if (wallpaper.is<Web> ()) {
  533. return std::make_unique<WallpaperEngine::Render::Wallpapers::CWeb> (
  534. wallpaper, context, audioContext, resolvedBackgroundPath, scalingMode, clampMode, maxRenderSize
  535. );
  536. }
  537. sLog.exception ("Unsupported wallpaper type");
  538. }