WaylandOpenGLDriver.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. #include "WaylandOpenGLDriver.h"
  2. #include "VideoFactories.h"
  3. #include "WallpaperEngine/Application/WallpaperApplication.h"
  4. #include "WallpaperEngine/Logging/Log.h"
  5. #define class _class
  6. #define namespace _namespace
  7. #define static
  8. extern "C" {
  9. #include "color-management-v1-protocol.h"
  10. #include "wlr-layer-shell-unstable-v1-protocol.h"
  11. #include "xdg-output-unstable-v1-protocol.h"
  12. #include "xdg-shell-protocol.h"
  13. #ifdef ENABLE_KDE_FEATURES
  14. #include "plasma-shell-protocol.h"
  15. #endif
  16. #include <linux/input-event-codes.h>
  17. }
  18. #undef class
  19. #undef namespace
  20. #undef static
  21. #include <algorithm>
  22. #include <cerrno>
  23. #include <poll.h>
  24. #include <string.h>
  25. #include <unistd.h>
  26. using namespace WallpaperEngine::Render::Drivers;
  27. constexpr int EVENT_WAIT_TIMEOUT_MS = 100;
  28. constexpr auto MISSED_FRAME_TIMEOUT = std::chrono::seconds (1);
  29. static void handlePointerEnter (
  30. void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x,
  31. wl_fixed_t surface_y
  32. ) {
  33. const auto driver = static_cast<WaylandOpenGLDriver*> (data);
  34. const auto viewport = driver->surfaceToViewport (surface);
  35. driver->viewportInFocus = viewport;
  36. wl_surface_set_buffer_scale (viewport->cursorSurface, viewport->scale);
  37. wl_surface_attach (viewport->cursorSurface, wl_cursor_image_get_buffer (viewport->pointer->images[0]), 0, 0);
  38. wl_pointer_set_cursor (
  39. wl_pointer, serial, viewport->cursorSurface, viewport->pointer->images[0]->hotspot_x,
  40. viewport->pointer->images[0]->hotspot_y
  41. );
  42. wl_surface_commit (viewport->cursorSurface);
  43. }
  44. static void
  45. handlePointerLeave (void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) { }
  46. static void handlePointerAxis (void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { }
  47. static void handlePointerMotion (
  48. void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y
  49. ) {
  50. const auto driver = static_cast<WaylandOpenGLDriver*> (data);
  51. const auto x = wl_fixed_to_double (surface_x);
  52. auto y = wl_fixed_to_double (surface_y);
  53. if (!driver->viewportInFocus) {
  54. return;
  55. }
  56. // Wayland has Y=0 at the top, OpenGL has Y=0 at the bottom
  57. const double viewportHeight = static_cast<double> (driver->viewportInFocus->size.y);
  58. y = viewportHeight - y;
  59. driver->viewportInFocus->mousePos = { x * driver->viewportInFocus->scale, y * driver->viewportInFocus->scale };
  60. }
  61. static void handlePointerButton (
  62. void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state
  63. ) {
  64. const auto driver = static_cast<WaylandOpenGLDriver*> (data);
  65. if (!driver->viewportInFocus) {
  66. return;
  67. }
  68. if (button == BTN_LEFT) {
  69. if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) {
  70. driver->viewportInFocus->leftClick = WallpaperEngine::Input::MouseClickStatus::Clicked;
  71. } else if (button_state == WL_POINTER_BUTTON_STATE_RELEASED) {
  72. driver->viewportInFocus->leftClick = WallpaperEngine::Input::MouseClickStatus::Released;
  73. }
  74. } else if (button == BTN_RIGHT) {
  75. if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) {
  76. driver->viewportInFocus->rightClick = WallpaperEngine::Input::MouseClickStatus::Clicked;
  77. } else if (button_state == WL_POINTER_BUTTON_STATE_RELEASED) {
  78. driver->viewportInFocus->rightClick = WallpaperEngine::Input::MouseClickStatus::Released;
  79. }
  80. }
  81. }
  82. constexpr struct wl_pointer_listener pointerListener = { .enter = handlePointerEnter,
  83. .leave = handlePointerLeave,
  84. .motion = handlePointerMotion,
  85. .button = handlePointerButton,
  86. .axis = handlePointerAxis };
  87. static void handleCapabilities (void* data, wl_seat* wl_seat, uint32_t capabilities) {
  88. if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
  89. wl_pointer_add_listener (wl_seat_get_pointer (wl_seat), &pointerListener, data);
  90. }
  91. }
  92. constexpr struct wl_seat_listener seatListener = { .capabilities = handleCapabilities };
  93. static void colorManagerSupportedIntent (void*, wp_color_manager_v1*, uint32_t) { }
  94. static void colorManagerSupportedFeature (void* data, wp_color_manager_v1*, uint32_t feature) {
  95. if (feature == WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC) {
  96. static_cast<WaylandOpenGLDriver*> (data)->colorSupport.parametric = true;
  97. }
  98. }
  99. static void colorManagerSupportedTransferFunction (void* data, wp_color_manager_v1*, uint32_t tf) {
  100. if (tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ) {
  101. static_cast<WaylandOpenGLDriver*> (data)->colorSupport.pq = true;
  102. }
  103. }
  104. static void colorManagerSupportedPrimaries (void* data, wp_color_manager_v1*, uint32_t primaries) {
  105. if (primaries == WP_COLOR_MANAGER_V1_PRIMARIES_BT2020) {
  106. static_cast<WaylandOpenGLDriver*> (data)->colorSupport.bt2020 = true;
  107. }
  108. }
  109. static void colorManagerDone (void*, wp_color_manager_v1*) { }
  110. constexpr struct wp_color_manager_v1_listener colorManagerListener = {
  111. .supported_intent = colorManagerSupportedIntent,
  112. .supported_feature = colorManagerSupportedFeature,
  113. .supported_tf_named = colorManagerSupportedTransferFunction,
  114. .supported_primaries_named = colorManagerSupportedPrimaries,
  115. .done = colorManagerDone,
  116. };
  117. static void hdrDescriptionFailed (void*, wp_image_description_v1*, uint32_t cause, const char* message) {
  118. sLog.error ("--hdr: the compositor refused a PQ image description (", cause, "): ", message);
  119. }
  120. static void hdrDescriptionReady (void* data, wp_image_description_v1*, uint32_t) {
  121. static_cast<WaylandOpenGLDriver*> (data)->hdrDescriptionReady = true;
  122. }
  123. constexpr struct wp_image_description_v1_listener hdrDescriptionListener = {
  124. .failed = hdrDescriptionFailed,
  125. .ready = hdrDescriptionReady,
  126. };
  127. static void
  128. handleGlobal (void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
  129. const auto driver = static_cast<WaylandOpenGLDriver*> (data);
  130. if (strcmp (interface, wl_compositor_interface.name) == 0) {
  131. driver->getWaylandContext ()->compositor
  132. = static_cast<wl_compositor*> (wl_registry_bind (registry, name, &wl_compositor_interface, 4));
  133. } else if (strcmp (interface, wl_shm_interface.name) == 0) {
  134. driver->getWaylandContext ()->shm
  135. = static_cast<wl_shm*> (wl_registry_bind (registry, name, &wl_shm_interface, 1));
  136. } else if (strcmp (interface, wl_output_interface.name) == 0) {
  137. driver->m_screens.emplace_back (
  138. new WallpaperEngine::Render::Drivers::Output::WaylandOutputViewport (driver, name, registry)
  139. );
  140. } else if (strcmp (interface, zwlr_layer_shell_v1_interface.name) == 0) {
  141. driver->getWaylandContext ()->layerShell
  142. = static_cast<zwlr_layer_shell_v1*> (wl_registry_bind (registry, name, &zwlr_layer_shell_v1_interface, 1));
  143. } else if (strcmp (interface, wl_seat_interface.name) == 0) {
  144. driver->getWaylandContext ()->seat
  145. = static_cast<wl_seat*> (wl_registry_bind (registry, name, &wl_seat_interface, 1));
  146. wl_seat_add_listener (driver->getWaylandContext ()->seat, &seatListener, driver);
  147. } else if (strcmp (interface, zxdg_output_manager_v1_interface.name) == 0) {
  148. driver->getWaylandContext ()->xdgOutputManager = static_cast<zxdg_output_manager_v1*> (
  149. wl_registry_bind (registry, name, &zxdg_output_manager_v1_interface, std::min (version, 3u))
  150. );
  151. } else if (
  152. strcmp (interface, wp_color_manager_v1_interface.name) == 0
  153. && driver->getApp ().getContext ().settings.render.hdr
  154. ) {
  155. driver->getWaylandContext ()->colorManager
  156. = static_cast<wp_color_manager_v1*> (wl_registry_bind (registry, name, &wp_color_manager_v1_interface, 1));
  157. wp_color_manager_v1_add_listener (driver->getWaylandContext ()->colorManager, &colorManagerListener, driver);
  158. #ifdef ENABLE_KDE_FEATURES
  159. } else if (strcmp (interface, org_kde_plasma_shell_interface.name) == 0) {
  160. driver->getWaylandContext ()->plasmaShell = static_cast<org_kde_plasma_shell*> (
  161. wl_registry_bind (registry, name, &org_kde_plasma_shell_interface, std::min (version, 8u))
  162. );
  163. #endif
  164. }
  165. }
  166. static void handleGlobalRemoved (void* data, struct wl_registry* registry, uint32_t id) {
  167. const auto driver = static_cast<WaylandOpenGLDriver*> (data);
  168. // a monitor being unplugged/disabled removes its global; leaving the viewport around leaks its
  169. // layer-shell/EGL surfaces for the rest of this client's connection since nothing else disconnects it
  170. const auto it
  171. = std::ranges::find_if (driver->m_screens, [id] (const auto* viewport) { return viewport->waylandName == id; });
  172. if (it != driver->m_screens.end ()) {
  173. driver->onLayerClose (*it);
  174. }
  175. }
  176. constexpr struct wl_registry_listener registryListener = {
  177. .global = handleGlobal,
  178. .global_remove = handleGlobalRemoved,
  179. };
  180. void WaylandOpenGLDriver::initEGL () {
  181. const char* CLIENT_EXTENSIONS = eglQueryString (EGL_NO_DISPLAY, EGL_EXTENSIONS);
  182. if (!CLIENT_EXTENSIONS) {
  183. sLog.exception ("Failed to query EGL Extensions");
  184. }
  185. const auto CLIENTEXTENSIONS = std::string (CLIENT_EXTENSIONS);
  186. if (CLIENTEXTENSIONS.find ("EGL_EXT_platform_base") == std::string::npos) {
  187. sLog.exception ("EGL_EXT_platform_base not supported by EGL!");
  188. }
  189. if (CLIENTEXTENSIONS.find ("EGL_EXT_platform_wayland") == std::string::npos) {
  190. sLog.exception ("EGL_EXT_platform_wayland not supported by EGL!");
  191. }
  192. const auto eglGetPlatformDisplayEXT
  193. = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC> (eglGetProcAddress ("eglGetPlatformDisplayEXT"));
  194. m_eglContext.eglCreatePlatformWindowSurfaceEXT = reinterpret_cast<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC> (
  195. eglGetProcAddress ("eglCreatePlatformWindowSurfaceEXT")
  196. );
  197. if (!eglGetPlatformDisplayEXT || !m_eglContext.eglCreatePlatformWindowSurfaceEXT) {
  198. sLog.exception ("EGL did not return EXT proc pointers!");
  199. }
  200. m_eglContext.display = eglGetPlatformDisplayEXT (EGL_PLATFORM_WAYLAND_EXT, m_waylandContext.display, nullptr);
  201. if (m_eglContext.display == EGL_NO_DISPLAY) {
  202. this->finishEGL ();
  203. sLog.exception ("eglGetPlatformDisplayEXT failed!");
  204. }
  205. if (!eglInitialize (m_eglContext.display, nullptr, nullptr)) {
  206. this->finishEGL ();
  207. sLog.exception ("eglInitialize failed!");
  208. }
  209. const auto CLIENTEXTENSIONSPOSTINIT = std::string (eglQueryString (m_eglContext.display, EGL_EXTENSIONS));
  210. if (CLIENTEXTENSIONSPOSTINIT.find ("EGL_KHR_create_context") == std::string::npos) {
  211. this->finishEGL ();
  212. sLog.exception ("EGL_KHR_create_context not supported!");
  213. }
  214. EGLint matchedConfigs = 0;
  215. const EGLint CONFIG_ATTRIBUTES[] = {
  216. EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_SAMPLES, 4,
  217. EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE,
  218. };
  219. // PQ in 8 bits bands visibly, so HDR outputs want a 10 bit buffer (fine for the SDR ones too)
  220. if (this->isHDRAvailable ()) {
  221. const EGLint HDR_CONFIG_ATTRIBUTES[] = {
  222. EGL_SURFACE_TYPE,
  223. EGL_WINDOW_BIT,
  224. EGL_RED_SIZE,
  225. 10,
  226. EGL_GREEN_SIZE,
  227. 10,
  228. EGL_BLUE_SIZE,
  229. 10,
  230. EGL_SAMPLES,
  231. 4,
  232. EGL_RENDERABLE_TYPE,
  233. EGL_OPENGL_BIT,
  234. EGL_NONE,
  235. };
  236. EGLConfig configs[64];
  237. EGLint count = 0;
  238. if (eglChooseConfig (m_eglContext.display, HDR_CONFIG_ATTRIBUTES, configs, 64, &count)) {
  239. for (EGLint i = 0; i < count && matchedConfigs == 0; i++) {
  240. EGLint red = 0;
  241. eglGetConfigAttrib (m_eglContext.display, configs[i], EGL_RED_SIZE, &red);
  242. if (red == 10) {
  243. m_eglContext.config = configs[i];
  244. matchedConfigs = 1;
  245. }
  246. }
  247. }
  248. if (matchedConfigs == 0) {
  249. sLog.error ("--hdr: no 10 bit EGL config, HDR output will band");
  250. }
  251. }
  252. if (matchedConfigs == 0
  253. && !eglChooseConfig (m_eglContext.display, CONFIG_ATTRIBUTES, &m_eglContext.config, 1, &matchedConfigs)) {
  254. this->finishEGL ();
  255. sLog.exception ("eglChooseConfig failed!");
  256. }
  257. if (matchedConfigs == 0) {
  258. this->finishEGL ();
  259. sLog.exception ("eglChooseConfig failed! (matched 0 configs)");
  260. }
  261. if (!eglBindAPI (EGL_OPENGL_API)) {
  262. this->finishEGL ();
  263. sLog.exception ("eglBindAPI failed!");
  264. }
  265. const EGLint CONTEXT_ATTRIBUTES[] = {
  266. EGL_CONTEXT_MAJOR_VERSION_KHR,
  267. 3,
  268. EGL_CONTEXT_MINOR_VERSION_KHR,
  269. 3,
  270. EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
  271. EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
  272. // required for glDebugMessageCallback (WallpaperApplication::setupOpenGLDebugging) on drivers that
  273. // only emit KHR_debug output when the context is created with this flag; Mesa tends to be lenient
  274. EGL_CONTEXT_FLAGS_KHR,
  275. EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
  276. EGL_NONE,
  277. };
  278. m_eglContext.context
  279. = eglCreateContext (m_eglContext.display, m_eglContext.config, EGL_NO_CONTEXT, CONTEXT_ATTRIBUTES);
  280. if (m_eglContext.context == EGL_NO_CONTEXT) {
  281. this->finishEGL ();
  282. sLog.error ("eglCreateContext error " + std::to_string (eglGetError ()));
  283. sLog.exception ("eglCreateContext failed!");
  284. }
  285. }
  286. void WaylandOpenGLDriver::finishEGL () const {
  287. eglMakeCurrent (EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  288. if (m_eglContext.display) {
  289. eglTerminate (m_eglContext.display);
  290. }
  291. eglReleaseThread ();
  292. }
  293. void WaylandOpenGLDriver::onLayerClose (Output::WaylandOutputViewport* viewport) {
  294. sLog.error ("Compositor closed our LS, freeing data...");
  295. if (viewport->frameCallback) {
  296. wl_callback_destroy (viewport->frameCallback);
  297. viewport->frameCallback = nullptr;
  298. }
  299. if (viewport->eglSurface) {
  300. eglDestroySurface (m_eglContext.display, viewport->eglSurface);
  301. }
  302. if (viewport->eglWindow) {
  303. wl_egl_window_destroy (viewport->eglWindow);
  304. }
  305. if (viewport->colorSurface) {
  306. wp_color_management_surface_v1_destroy (viewport->colorSurface);
  307. viewport->colorSurface = nullptr;
  308. }
  309. if (viewport->colorOutput) {
  310. wp_color_management_output_v1_destroy (viewport->colorOutput);
  311. viewport->colorOutput = nullptr;
  312. }
  313. if (viewport->layerSurface) {
  314. zwlr_layer_surface_v1_destroy (viewport->layerSurface);
  315. }
  316. #ifdef ENABLE_KDE_FEATURES
  317. if (viewport->plasmaSurface) {
  318. org_kde_plasma_surface_destroy (viewport->plasmaSurface);
  319. viewport->plasmaSurface = nullptr;
  320. }
  321. #endif
  322. if (viewport->xdgOutput) {
  323. zxdg_output_v1_destroy (viewport->xdgOutput);
  324. viewport->xdgOutput = nullptr;
  325. }
  326. if (viewport->surface) {
  327. wl_surface_destroy (viewport->surface);
  328. }
  329. if (viewport->cursorSurface) {
  330. wl_surface_destroy (viewport->cursorSurface);
  331. }
  332. if (viewport->cursorTheme) {
  333. wl_cursor_theme_destroy (viewport->cursorTheme);
  334. }
  335. if (viewport->output) {
  336. wl_output_release (viewport->output);
  337. }
  338. std::erase (this->m_screens, viewport);
  339. this->getOutput ().reset ();
  340. delete viewport;
  341. }
  342. WaylandOpenGLDriver::WaylandOpenGLDriver (ApplicationContext& context, WallpaperApplication& app) :
  343. VideoDriver (app, m_mouseInput), m_output (context, *this), m_requestedExit (false), m_frameCounter (0),
  344. m_context (context), m_mouseInput (*this) {
  345. initWaylandRegistry ();
  346. initColorManagement ();
  347. initEGL ();
  348. setupOutputLayerSurfaces ();
  349. initGLEW ();
  350. }
  351. void WaylandOpenGLDriver::initWaylandRegistry () {
  352. m_waylandContext.display = wl_display_connect (nullptr);
  353. if (!m_waylandContext.display) {
  354. sLog.exception ("Failed to query wayland display");
  355. }
  356. m_waylandContext.registry = wl_display_get_registry (m_waylandContext.display);
  357. wl_registry_add_listener (m_waylandContext.registry, &registryListener, this);
  358. wl_display_dispatch (m_waylandContext.display);
  359. wl_display_roundtrip (m_waylandContext.display);
  360. if (!m_waylandContext.compositor || !m_waylandContext.shm || !m_waylandContext.layerShell
  361. || this->m_screens.empty ()) {
  362. sLog.exception ("Failed to bind to required interfaces");
  363. }
  364. if (m_waylandContext.xdgOutputManager) {
  365. for (const auto& o : this->m_screens) {
  366. o->setupXdgOutput (m_waylandContext.xdgOutputManager);
  367. }
  368. wl_display_roundtrip (m_waylandContext.display);
  369. } else if (!m_context.settings.general.spanGroups.empty ()) {
  370. sLog.error ("zxdg_output_manager_v1 is unavailable; screen-span positions will be incorrect.");
  371. }
  372. }
  373. void WaylandOpenGLDriver::initColorManagement () {
  374. if (!m_context.settings.render.hdr) {
  375. return;
  376. }
  377. if (!m_waylandContext.colorManager) {
  378. sLog.error ("--hdr: the compositor has no color management protocol (wp_color_manager_v1), staying SDR");
  379. return;
  380. }
  381. // the supported_* events and done follow the bind
  382. wl_display_roundtrip (m_waylandContext.display);
  383. if (!colorSupport.parametric || !colorSupport.pq || !colorSupport.bt2020) {
  384. sLog.error ("--hdr: the compositor can't take PQ with BT.2020 primaries, staying SDR");
  385. return;
  386. }
  387. auto* creator = wp_color_manager_v1_create_parametric_creator (m_waylandContext.colorManager);
  388. wp_image_description_creator_params_v1_set_tf_named (creator, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ);
  389. wp_image_description_creator_params_v1_set_primaries_named (creator, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020);
  390. this->m_hdrDescription = wp_image_description_creator_params_v1_create (creator);
  391. wp_image_description_v1_add_listener (this->m_hdrDescription, &hdrDescriptionListener, this);
  392. wl_display_roundtrip (m_waylandContext.display);
  393. if (!this->hdrDescriptionReady) {
  394. return;
  395. }
  396. // every output reports whether it runs in HDR, and again whenever that changes
  397. for (const auto& o : this->m_screens) {
  398. o->setupColorManagement ();
  399. }
  400. // image description ready -> get_information -> its events
  401. wl_display_roundtrip (m_waylandContext.display);
  402. wl_display_roundtrip (m_waylandContext.display);
  403. for (const auto& o : this->m_screens) {
  404. sLog.out ("Output ", o->name, o->isOutputHDR () ? " runs in HDR, drawing PQ" : " runs in SDR");
  405. }
  406. }
  407. bool WaylandOpenGLDriver::isHDRAvailable () const { return this->hdrDescriptionReady; }
  408. wp_image_description_v1* WaylandOpenGLDriver::getHDRDescription () const { return this->m_hdrDescription; }
  409. void WaylandOpenGLDriver::setupOutputLayerSurfaces () {
  410. bool any = false;
  411. for (const auto& o : this->m_screens) {
  412. if (!isScreenRequested (o->name)) {
  413. continue;
  414. }
  415. o->setupLS ();
  416. any = true;
  417. }
  418. if (!any) {
  419. sLog.error ("No outputs could be initialized, please check the parameters and try again");
  420. sLog.error ("Detected outputs:");
  421. for (const auto& o : this->m_screens) {
  422. sLog.error (" ", o->name);
  423. }
  424. sLog.error ("Requested: ");
  425. for (const auto& o : m_context.settings.general.screenBackgrounds | std::views::keys) {
  426. sLog.error (" ", o);
  427. }
  428. for (const auto& spanGroup : m_context.settings.general.spanGroups) {
  429. for (const auto& screen : spanGroup.screens) {
  430. sLog.error (" ", screen, " (span group)");
  431. }
  432. }
  433. sLog.exception ("Cannot continue...");
  434. }
  435. this->m_layerSurfacesReady = true;
  436. }
  437. bool WaylandOpenGLDriver::isScreenRequested (const std::string& name) const {
  438. if (m_context.settings.general.screenBackgrounds.contains (name)) {
  439. return true;
  440. }
  441. return std::ranges::any_of (m_context.settings.general.spanGroups, [&name] (const auto& spanGroup) {
  442. return std::ranges::find (spanGroup.screens, name) != spanGroup.screens.end ();
  443. });
  444. }
  445. void WaylandOpenGLDriver::setupLateOutputs () {
  446. // KDE often drops and re-adds outputs while it applies the screen layout at login, and monitors can
  447. // wake up after we started; the wallpapers stay registered by screen name, only the surface is missing
  448. for (const auto& o : this->m_screens) {
  449. if (o->layerSurface || !o->initialized || !isScreenRequested (o->name)) {
  450. continue;
  451. }
  452. sLog.out ("Output ", o->name, " appeared, attaching the wallpaper");
  453. if (m_waylandContext.xdgOutputManager && !o->xdgOutput) {
  454. o->setupXdgOutput (m_waylandContext.xdgOutputManager);
  455. }
  456. o->setupLS ();
  457. }
  458. }
  459. void WaylandOpenGLDriver::initGLEW () {
  460. glewExperimental = GL_TRUE;
  461. if (const GLenum result = glewInit (); result != GLEW_OK) {
  462. const char* error = reinterpret_cast<const char*> (glewGetErrorString (result));
  463. // on Wayland+EGL, GLEW may report GLEW_ERROR_NO_GLX_DISPLAY or a null string when the build
  464. // also includes X11 but no GLX display is present; non-fatal, the EGL context is already current
  465. if (result == GLEW_ERROR_NO_GLX_DISPLAY || error == nullptr) {
  466. sLog.out ("Failed to initialize GLEW, but continuing with EGL context: ", error ? error : "No GLX display");
  467. } else {
  468. sLog.error ("Failed to initialize GLEW: ", error);
  469. sLog.exception ("Cannot continue...");
  470. }
  471. }
  472. }
  473. WaylandOpenGLDriver::~WaylandOpenGLDriver () {
  474. for (const auto& screen : this->m_screens) {
  475. if (screen->xdgOutput) {
  476. zxdg_output_v1_destroy (screen->xdgOutput);
  477. screen->xdgOutput = nullptr;
  478. }
  479. }
  480. eglMakeCurrent (EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  481. if (m_eglContext.context != EGL_NO_CONTEXT) {
  482. eglDestroyContext (m_eglContext.display, m_eglContext.context);
  483. }
  484. eglTerminate (m_eglContext.display);
  485. eglReleaseThread ();
  486. if (this->m_waylandContext.display) {
  487. wl_display_disconnect (this->m_waylandContext.display);
  488. }
  489. }
  490. void WaylandOpenGLDriver::dispatchEventQueue () {
  491. if (this->m_layerSurfacesReady) {
  492. this->setupLateOutputs ();
  493. }
  494. // rendering only continues from frame callbacks, so a surface that never got its first frame or whose
  495. // callback the compositor dropped would stay blank forever; this also draws the very first frame
  496. const auto now = std::chrono::steady_clock::now ();
  497. for (const auto& o : this->m_screens) {
  498. if (o->layerSurface && now - o->lastSwap > MISSED_FRAME_TIMEOUT) {
  499. o->renderFrame ();
  500. }
  501. }
  502. // the compositor stops sending frame callbacks to hidden surfaces, a blocking dispatch would stall the main loop
  503. wl_display* display = m_waylandContext.display;
  504. while (wl_display_prepare_read (display) != 0) {
  505. if (wl_display_dispatch_pending (display) == -1) {
  506. m_requestedExit = true;
  507. return;
  508. }
  509. }
  510. wl_display_flush (display);
  511. pollfd fd = { .fd = wl_display_get_fd (display), .events = POLLIN, .revents = 0 };
  512. const int ready = poll (&fd, 1, EVENT_WAIT_TIMEOUT_MS);
  513. if (ready > 0) {
  514. if (wl_display_read_events (display) == -1) {
  515. m_requestedExit = true;
  516. return;
  517. }
  518. } else {
  519. wl_display_cancel_read (display);
  520. if (ready == -1 && errno != EINTR) {
  521. m_requestedExit = true;
  522. return;
  523. }
  524. }
  525. if (wl_display_dispatch_pending (display) == -1) {
  526. m_requestedExit = true;
  527. }
  528. m_frameCounter++;
  529. }
  530. Output::Output& WaylandOpenGLDriver::getOutput () { return this->m_output; }
  531. float WaylandOpenGLDriver::getRenderTime () const {
  532. return static_cast<float> (std::chrono::duration_cast<std::chrono::microseconds> (
  533. std::chrono::high_resolution_clock::now () - renderStart
  534. )
  535. .count ())
  536. / 1000000.0;
  537. }
  538. bool WaylandOpenGLDriver::closeRequested () { return this->m_requestedExit; }
  539. void WaylandOpenGLDriver::resizeWindow (glm::ivec2 size) { }
  540. void WaylandOpenGLDriver::resizeWindow (glm::ivec4 sizeandpos) { }
  541. void WaylandOpenGLDriver::showWindow () { }
  542. void WaylandOpenGLDriver::hideWindow () { }
  543. glm::ivec2 WaylandOpenGLDriver::getFramebufferSize () const { return glm::ivec2 { 0, 0 }; }
  544. uint32_t WaylandOpenGLDriver::getFrameCounter () const { return m_frameCounter; }
  545. WaylandOpenGLDriver::SEGLContext* WaylandOpenGLDriver::getEGLContext () { return &this->m_eglContext; }
  546. void* WaylandOpenGLDriver::getProcAddress (const char* name) const {
  547. return reinterpret_cast<void*> (eglGetProcAddress (name));
  548. }
  549. void* WaylandOpenGLDriver::getWaylandDisplay () const { return this->m_waylandContext.display; }
  550. WaylandOpenGLDriver::WaylandContext* WaylandOpenGLDriver::getWaylandContext () { return &this->m_waylandContext; }
  551. Output::WaylandOutputViewport* WaylandOpenGLDriver::surfaceToViewport (const wl_surface* surface) const {
  552. for (const auto& o : m_screens) {
  553. if (o->surface == surface) {
  554. return o;
  555. }
  556. }
  557. return nullptr;
  558. }
  559. __attribute__ ((constructor)) void registerWaylandOpenGL () {
  560. sVideoFactories.registerDriver (
  561. ApplicationContext::DESKTOP_BACKGROUND, "wayland",
  562. [] (ApplicationContext& context, WallpaperApplication& application) -> std::unique_ptr<VideoDriver> {
  563. return std::make_unique<WaylandOpenGLDriver> (context, application);
  564. }
  565. );
  566. }