CWallpaperApplication.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. #include "CWallpaperApplication.h"
  2. #include "Steam/FileSystem/FileSystem.h"
  3. #include "WallpaperEngine/Application/CApplicationState.h"
  4. #include "WallpaperEngine/Assets/CAssetLoadException.h"
  5. #include "WallpaperEngine/Assets/CDirectory.h"
  6. #include "WallpaperEngine/Assets/CVirtualContainer.h"
  7. #include "WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h"
  8. #include "WallpaperEngine/Logging/CLog.h"
  9. #include "WallpaperEngine/Render/CRenderContext.h"
  10. #include "WallpaperEngine/PrettyPrinter/CPrettyPrinter.h"
  11. #include "WallpaperEngine/Core/Wallpapers/CWeb.h"
  12. #include "WallpaperEngine/Render/Drivers/CVideoFactories.h"
  13. #if DEMOMODE
  14. #include "recording.h"
  15. #endif /* DEMOMODE */
  16. #include <unistd.h>
  17. #define STB_IMAGE_WRITE_IMPLEMENTATION
  18. #include <stb_image_write.h>
  19. #define FULLSCREEN_CHECK_WAIT_TIME 250
  20. float g_Time;
  21. float g_TimeLast;
  22. float g_Daytime;
  23. namespace WallpaperEngine::Application {
  24. CWallpaperApplication::CWallpaperApplication (CApplicationContext& context) :
  25. m_context (context) {
  26. this->loadBackgrounds ();
  27. this->setupProperties ();
  28. this->setupBrowser();
  29. }
  30. void CWallpaperApplication::setupContainer (const std::shared_ptr<CCombinedContainer>& container, const std::string& bg) const {
  31. const std::filesystem::path basepath = bg;
  32. container->add (std::make_shared<CDirectory> (basepath));
  33. container->addPkg (basepath / "scene.pkg");
  34. container->addPkg (basepath / "gifscene.pkg");
  35. try {
  36. container->add (std::make_shared <CDirectory> (this->m_context.settings.general.assets));
  37. } catch (CAssetLoadException&) {
  38. sLog.exception ("Cannot find a valid assets folder, resolved to ", this->m_context.settings.general.assets);
  39. }
  40. // TODO: move this somewhere else?
  41. auto virtualContainer = std::make_shared <CVirtualContainer> ();
  42. //
  43. // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
  44. // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
  45. // applies the bloom effect to render it out to the screen
  46. //
  47. // add the effect file for screen bloom
  48. // add some model for the image element even if it's going to waste rendering cycles
  49. virtualContainer->add (
  50. "effects/wpenginelinux/bloomeffect.json",
  51. {
  52. {"name", "camerabloom_wpengine_linux"},
  53. {"group", "wpengine_linux_camera"},
  54. {"dependencies", json::array ()},
  55. {"passes",
  56. json::array (
  57. {
  58. {
  59. {"material", "materials/util/downsample_quarter_bloom.json"},
  60. {"target", "_rt_4FrameBuffer"},
  61. {
  62. "bind",
  63. json::array (
  64. {
  65. {
  66. {"name", "_rt_FullFrameBuffer"},
  67. {"index", 0}
  68. }
  69. }
  70. )
  71. }
  72. },
  73. {
  74. {"material", "materials/util/downsample_eighth_blur_v.json"},
  75. {"target", "_rt_8FrameBuffer"},
  76. {
  77. "bind",
  78. json::array (
  79. {
  80. {
  81. {"name", "_rt_4FrameBuffer"},
  82. {"index", 0}
  83. }
  84. }
  85. )
  86. }
  87. },
  88. {
  89. {"material", "materials/util/blur_h_bloom.json"},
  90. {"target", "_rt_Bloom"},
  91. {
  92. "bind",
  93. json::array (
  94. {
  95. {
  96. {"name", "_rt_8FrameBuffer"},
  97. {"index", 0}
  98. }
  99. }
  100. )
  101. }
  102. },
  103. {
  104. {"material", "materials/util/combine.json"},
  105. {"target", "_rt_FullFrameBuffer"},
  106. {
  107. "bind",
  108. json::array (
  109. {
  110. {
  111. {"name", "_rt_imageLayerComposite_-1_a"},
  112. {"index", 0}
  113. },
  114. {
  115. {"name", "_rt_Bloom"},
  116. {"index", 1}
  117. }
  118. }
  119. )
  120. }
  121. }
  122. }
  123. ),
  124. }
  125. }
  126. );
  127. virtualContainer->add (
  128. "models/wpenginelinux.json",
  129. {
  130. {"material","materials/wpenginelinux.json"}
  131. }
  132. );
  133. virtualContainer->add(
  134. "materials/wpenginelinux.json",
  135. {
  136. {"passes", json::array (
  137. {
  138. {
  139. {"blending", "normal"},
  140. {"cullmode", "nocull"},
  141. {"depthtest", "disabled"},
  142. {"depthwrite", "disabled"},
  143. {"shader", "genericimage2"},
  144. {"textures", json::array ({"_rt_FullFrameBuffer"})}
  145. }
  146. }
  147. )}}
  148. );
  149. virtualContainer->add(
  150. "shaders/commands/copy.frag",
  151. "uniform sampler2D g_Texture0;\n"
  152. "in vec2 v_TexCoord;\n"
  153. "void main () {\n"
  154. "out_FragColor = texture (g_Texture0, v_TexCoord);\n"
  155. "}"
  156. );
  157. virtualContainer->add(
  158. "shaders/commands/copy.vert",
  159. "in vec3 a_Position;\n"
  160. "in vec2 a_TexCoord;\n"
  161. "out vec2 v_TexCoord;\n"
  162. "void main () {\n"
  163. "gl_Position = vec4 (a_Position, 1.0);\n"
  164. "v_TexCoord = a_TexCoord;\n"
  165. "}"
  166. );
  167. container->add (virtualContainer);
  168. }
  169. void CWallpaperApplication::loadBackgrounds () {
  170. if (this->m_context.settings.render.mode == CApplicationContext::NORMAL_WINDOW ||
  171. this->m_context.settings.render.mode == CApplicationContext::EXPLICIT_WINDOW) {
  172. this->m_backgrounds ["default"] = this->loadBackground (this->m_context.settings.general.defaultBackground);
  173. return;
  174. }
  175. for (const auto& [screen, path] : this->m_context.settings.general.screenBackgrounds) {
  176. // screens with no screen should use the default
  177. if (path.empty ()) {
  178. this->m_backgrounds [screen] = this->loadBackground (this->m_context.settings.general.defaultBackground);
  179. } else {
  180. this->m_backgrounds [screen] = this->loadBackground (path);
  181. }
  182. }
  183. }
  184. std::shared_ptr<Core::CProject> CWallpaperApplication::loadBackground (const std::string& bg) {
  185. const auto container = std::make_shared <CCombinedContainer> ();
  186. this->setupContainer (container, bg);
  187. return Core::CProject::fromFile ("project.json", container);
  188. }
  189. void CWallpaperApplication::setupPropertiesForProject (const std::shared_ptr<const Core::CProject>& project) {
  190. // show properties if required
  191. for (const auto& [key, cur] : project->getProperties ()) {
  192. // update the value of the property
  193. auto override = this->m_context.settings.general.properties.find (key);
  194. if (override != this->m_context.settings.general.properties.end ()) {
  195. sLog.out ("Applying override value for ", key);
  196. cur->set (override->second);
  197. }
  198. if (this->m_context.settings.general.onlyListProperties)
  199. sLog.out (cur->dump ());
  200. }
  201. }
  202. void CWallpaperApplication::setupProperties () {
  203. for (const auto& [background, info] : this->m_backgrounds)
  204. this->setupPropertiesForProject (info);
  205. }
  206. void CWallpaperApplication::setupBrowser () {
  207. bool anyWebProject = std::any_of (
  208. this->m_backgrounds.begin (), this->m_backgrounds.end (),
  209. [](const std::pair<const std::string, std::shared_ptr<const Core::CProject>>& pair) -> bool {
  210. return pair.second->getWallpaper()->is<Core::Wallpapers::CWeb> ();
  211. });
  212. // do not perform any initialization if no web background is present
  213. if (!anyWebProject) {
  214. return;
  215. }
  216. this->m_browserContext = std::make_unique <WebBrowser::CWebBrowserContext> (*this);
  217. }
  218. void CWallpaperApplication::takeScreenshot (const std::filesystem::path& filename) const {
  219. // this should be getting called at the end of the frame, so the right thing should be bound already
  220. const int width = this->m_renderContext->getOutput ().getFullWidth ();
  221. const int height = this->m_renderContext->getOutput ().getFullHeight ();
  222. const bool vflip = this->m_renderContext->getOutput ().renderVFlip ();
  223. // build the output file with stbi_image_write
  224. auto* bitmap = new uint8_t [width * height * 3] {0};
  225. int xoffset = 0;
  226. for (const auto& [screen, viewport] : this->m_renderContext->getOutput ().getViewports ()) {
  227. // activate opengl context so we can read from the framebuffer
  228. viewport->makeCurrent ();
  229. // make room for storing the pixel of this viewport
  230. const auto bufferSize = (viewport->viewport.z - viewport->viewport.x) * (viewport->viewport.w - viewport->viewport.y) * 3;
  231. auto* buffer = new uint8_t [bufferSize];
  232. const uint8_t* pixel = buffer;
  233. // read the viewport data into the pixel buffer
  234. glPixelStorei (GL_PACK_ALIGNMENT, 1);
  235. // 4.5 supports glReadnPixels, anything older doesn't...
  236. if (GLEW_VERSION_4_5) {
  237. glReadnPixels (viewport->viewport.x, viewport->viewport.y, viewport->viewport.z, viewport->viewport.w,
  238. GL_RGB, GL_UNSIGNED_BYTE, bufferSize, buffer);
  239. } else {
  240. // fallback to old version
  241. glReadPixels (viewport->viewport.x, viewport->viewport.y, viewport->viewport.z, viewport->viewport.w,
  242. GL_RGB, GL_UNSIGNED_BYTE, buffer);
  243. }
  244. GLenum error = glGetError();
  245. if (error != GL_NO_ERROR) {
  246. sLog.error ("Cannot obtain pixel data for screen ", screen, ". OpenGL error: ", error);
  247. delete [] buffer;
  248. continue;
  249. }
  250. // now get access to the pixels
  251. for (int y = 0; y < viewport->viewport.w; y++) {
  252. for (int x = 0; x < viewport->viewport.z; x++) {
  253. int xfinal = x + xoffset;
  254. int yfinal = vflip ? (viewport->viewport.w - y - 1) : y;
  255. bitmap [yfinal * width * 3 + xfinal * 3] = *pixel++;
  256. bitmap [yfinal * width * 3 + xfinal * 3 + 1] = *pixel++;
  257. bitmap [yfinal * width * 3 + xfinal * 3 + 2] = *pixel++;
  258. }
  259. }
  260. if (viewport->single)
  261. xoffset += viewport->viewport.z;
  262. // free the buffer allocated for the viewport
  263. delete [] buffer;
  264. }
  265. auto extension = filename.extension ();
  266. if (extension == ".bmp") {
  267. stbi_write_bmp (filename.c_str (), width, height, 3, bitmap);
  268. } else if (extension == ".png") {
  269. stbi_write_png (filename.c_str (), width, height, 3, bitmap, width * 3);
  270. } else if (extension == ".jpg" || extension == ".jpeg") {
  271. stbi_write_jpg (filename.c_str (), width, height, 3, bitmap, 100);
  272. }
  273. delete [] bitmap;
  274. }
  275. void CWallpaperApplication::setupOutput () {
  276. const char* XDG_SESSION_TYPE = getenv ("XDG_SESSION_TYPE");
  277. if (!XDG_SESSION_TYPE) {
  278. sLog.exception (
  279. "Cannot read environment variable XDG_SESSION_TYPE, window server detection failed. Please ensure proper values are set");
  280. }
  281. sLog.debug ("Checking for window servers: ");
  282. for (const auto& windowServer : sVideoFactories.getRegisteredDrivers ()) {
  283. sLog.debug("\t", windowServer);
  284. }
  285. this->m_videoDriver = sVideoFactories.createVideoDriver (
  286. this->m_context.settings.render.mode, XDG_SESSION_TYPE, this->m_context, *this);
  287. this->m_fullScreenDetector = sVideoFactories.createFullscreenDetector (XDG_SESSION_TYPE, this->m_context, *this->m_videoDriver);
  288. }
  289. void CWallpaperApplication::setupAudio () {
  290. // ensure audioprocessing is required by any background, and we have it enabled
  291. bool audioProcessingRequired = std::any_of (this->m_backgrounds.begin (), this->m_backgrounds.end (), [](const auto& pair) -> bool {
  292. return pair.second->supportsAudioProcessing ();
  293. });
  294. if (audioProcessingRequired && this->m_context.settings.audio.audioprocessing) {
  295. this->m_audioRecorder = std::make_unique <WallpaperEngine::Audio::Drivers::Recorders::CPulseAudioPlaybackRecorder> ();
  296. } else {
  297. this->m_audioRecorder = std::make_unique <WallpaperEngine::Audio::Drivers::Recorders::CPlaybackRecorder> ();
  298. }
  299. if (this->m_context.settings.audio.automute) {
  300. m_audioDetector = std::make_unique <WallpaperEngine::Audio::Drivers::Detectors::CPulseAudioPlayingDetector> (this->m_context, *this->m_fullScreenDetector);
  301. } else {
  302. m_audioDetector = std::make_unique <WallpaperEngine::Audio::Drivers::Detectors::CAudioPlayingDetector> (this->m_context, *this->m_fullScreenDetector);
  303. }
  304. // initialize sdl audio driver
  305. m_audioDriver = std::make_unique <WallpaperEngine::Audio::Drivers::CSDLAudioDriver> (this->m_context, *this->m_audioDetector, *this->m_audioRecorder);
  306. // initialize audio context
  307. m_audioContext = std::make_unique <WallpaperEngine::Audio::CAudioContext> (*m_audioDriver);
  308. }
  309. void CWallpaperApplication::prepareOutputs () {
  310. // initialize render context
  311. m_renderContext = std::make_unique <WallpaperEngine::Render::CRenderContext> (*m_videoDriver, *this);
  312. // create a new background for each screen
  313. // set all the specific wallpapers required
  314. for (const auto& [background, info] : this->m_backgrounds) {
  315. m_renderContext->setWallpaper (
  316. background,
  317. WallpaperEngine::Render::CWallpaper::fromWallpaper (
  318. info->getWallpaper (), *m_renderContext, *m_audioContext, m_browserContext.get (),
  319. this->m_context.settings.general.screenScalings [background],
  320. this->m_context.settings.general.screenClamps [background]
  321. )
  322. );
  323. }
  324. }
  325. void CWallpaperApplication::show () {
  326. this->setupOutput ();
  327. this->setupAudio ();
  328. this->prepareOutputs ();
  329. static time_t seconds;
  330. static struct tm* timeinfo;
  331. if (this->m_context.settings.general.dumpStructure) {
  332. auto prettyPrinter = PrettyPrinter::CPrettyPrinter ();
  333. for (const auto& [background, info] : this->m_renderContext->getWallpapers ()) {
  334. prettyPrinter.printWallpaper (*info);
  335. }
  336. std::cout << prettyPrinter.str () << std::endl;
  337. }
  338. #if DEMOMODE
  339. // ensure only one background is running so everything can be properly caught
  340. if (this->m_renderContext->getWallpapers ().size () > 1) {
  341. sLog.exception ("Demo mode only supports one background");
  342. }
  343. int width = this->m_renderContext->getWallpapers ().begin ()->second->getWidth ();
  344. int height = this->m_renderContext->getWallpapers ().begin ()->second->getHeight ();
  345. std::vector<uint8_t> pixels(width * height * 3);
  346. bool initialized = false;
  347. int frame = 0;
  348. int elapsed_frames = 0;
  349. #endif /* DEMOMODE */
  350. while (this->m_context.state.general.keepRunning) {
  351. // update g_Daytime
  352. time (&seconds);
  353. timeinfo = localtime (&seconds);
  354. g_Daytime = ((timeinfo->tm_hour * 60.0f) + timeinfo->tm_min) / (24.0f * 60.0f);
  355. // keep track of the previous frame's time
  356. g_TimeLast = g_Time;
  357. // calculate the current time value
  358. g_Time = m_videoDriver->getRenderTime ();
  359. // update audio recorder
  360. m_audioDriver->update ();
  361. // update input information
  362. m_videoDriver->getInputContext ().update ();
  363. // process driver events
  364. m_videoDriver->dispatchEventQueue ();
  365. if (m_videoDriver->closeRequested()) {
  366. sLog.out ("Stop requested by driver");
  367. this->m_context.state.general.keepRunning = false;
  368. }
  369. #if DEMOMODE
  370. elapsed_frames ++;
  371. // wait for a full render cycle before actually starting
  372. // this gives some extra time for video and web decoders to set themselves up
  373. // because of size changes
  374. if (elapsed_frames > this->m_context.settings.render.maximumFPS) {
  375. if (!initialized) {
  376. width = this->m_renderContext->getWallpapers ().begin ()->second->getWidth ();
  377. height = this->m_renderContext->getWallpapers ().begin ()->second->getHeight ();
  378. pixels.reserve(width * height * 3);
  379. init_encoder ("output.webm", width, height);
  380. initialized = true;
  381. }
  382. glBindFramebuffer (GL_FRAMEBUFFER, this->m_renderContext->getWallpapers ().begin ()->second->getWallpaperFramebuffer());
  383. glPixelStorei (GL_PACK_ALIGNMENT, 1);
  384. glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data ());
  385. write_video_frame (pixels.data ());
  386. frame ++;
  387. // stop after the given framecount
  388. if (frame >= FRAME_COUNT) {
  389. this->m_context.state.general.keepRunning = false;
  390. }
  391. }
  392. #endif /* DEMOMODE */
  393. // check for fullscreen windows and wait until there's none fullscreen
  394. if (this->m_fullScreenDetector->anythingFullscreen () && this->m_context.state.general.keepRunning) {
  395. m_renderContext->setPause (true);
  396. while (this->m_fullScreenDetector->anythingFullscreen () && this->m_context.state.general.keepRunning)
  397. usleep (FULLSCREEN_CHECK_WAIT_TIME);
  398. m_renderContext->setPause (false);
  399. }
  400. if (!this->m_context.settings.screenshot.take || m_videoDriver->getFrameCounter () < this->m_context.settings.screenshot.delay)
  401. continue;
  402. this->takeScreenshot (this->m_context.settings.screenshot.path);
  403. this->m_context.settings.screenshot.take = false;
  404. }
  405. sLog.out ("Stopping");
  406. #if DEMOMODE
  407. close_encoder ();
  408. #endif /* DEMOMODE */
  409. SDL_Quit ();
  410. }
  411. void CWallpaperApplication::update (Render::Drivers::Output::COutputViewport* viewport) {
  412. // render the scene
  413. m_renderContext->render (viewport);
  414. }
  415. void CWallpaperApplication::signal (int signal) {
  416. sLog.out ("Stop requested by signal ", signal);
  417. this->m_context.state.general.keepRunning = false;
  418. }
  419. const std::map<std::string, std::shared_ptr<Core::CProject>>& CWallpaperApplication::getBackgrounds () const {
  420. return this->m_backgrounds;
  421. }
  422. CApplicationContext& CWallpaperApplication::getContext () const {
  423. return this->m_context;
  424. }
  425. const WallpaperEngine::Render::Drivers::Output::COutput& CWallpaperApplication::getOutput () const {
  426. return this->m_renderContext->getOutput ();
  427. }
  428. } // namespace WallpaperEngine::Application