logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Writing-a-Wayland-compositor-1.md (14997B)


  1. ---
  2. date: 2018-02-17
  3. title: "Writing a Wayland Compositor, Part 1: Hello wlroots"
  4. layout: post
  5. tags: [wayland, wlroots, instructional]
  6. ---
  7. This is the first in a series of *many* articles I'm writing on the subject of
  8. building a functional Wayland compositor from scratch. As you may know, I am the
  9. lead maintainer of [sway](https://github.com/swaywm/sway), a reasonably popular
  10. Wayland compositor. Along with many other talented developers, we've been
  11. working on [wlroots](https://github.com/swaywm/wlroots) over the past few
  12. months. This is a powerful tool for creating new Wayland compositors, but it is
  13. very dense and difficult to understand. Do not despair! The intention of these
  14. articles is to make you understand and feel comfortable using it.
  15. Before we dive in, a quick note: the wlroots team is starting a crowdfunding
  16. campaign today to fund travel for each of our core contributors to meet in
  17. person and work for two weeks on a hackathon. Please consider contributing to
  18. [the campaign](https://www.indiegogo.com/projects/sway-hackathon-software/x/1059863)!
  19. You **must** read and comprehend my earlier article, [An introduction to
  20. Wayland](/2017/06/10/Introduction-to-Wayland.html), before attempting to
  21. understand this series of blog posts, as I will be relying on concepts and
  22. terminology introduced there to speed things up. Some background in OpenGL is
  23. helpful, but not required. A good understanding of C is mandatory. If you have
  24. any questions about any of the articles in this series, please reach out to me
  25. directly via [sir@cmpwn.com](mailto:sir@cmpwn.com) or to the wlroots team at
  26. [#sway-devel on irc.freenode.net](http://webchat.freenode.net/?channels=sway-devel&uio=d4).
  27. During this series of articles, the compositor we're building will live on
  28. GitHub: [Wayland McWayface](https://github.com/SirCmpwn/mcwayface). Each article
  29. in this series will be presented as a breakdown of a single commit between zero
  30. and a fully functional Wayland compositor. The commit for this article is
  31. [f89092e](https://github.com/SirCmpwn/mcwayland/commit/f89092e).
  32. I'm only going to explain the important parts - I suggest you review
  33. the entire commit separately.
  34. Let's get started. First, I'm going to define a struct for holding our
  35. compositor's state:
  36. ```diff
  37. +struct mcw_server {
  38. + struct wl_display *wl_display;
  39. + struct wl_event_loop *wl_event_loop;
  40. +};
  41. ```
  42. Note: mcw is short for McWayface. We'll be using this acronym throughout the
  43. article series. We'll set one of these aside and initialize the Wayland display
  44. for it[^1]:
  45. ```diff
  46. int main(int argc, char **argv) {
  47. + struct mcw_server server;
  48. +
  49. + server.wl_display = wl_display_create();
  50. + assert(server.wl_display);
  51. + server.wl_event_loop = wl_display_get_event_loop(server.wl_display);
  52. + assert(server.wl_event_loop);
  53. return 0;
  54. }
  55. ```
  56. The Wayland display gives us a number of things, but for now all we care about
  57. is the event loop. This event loop is deeply integrated into wlroots, and is
  58. used for things like dispatching signals across the application, being notified
  59. when data is available on various file descriptors, and so on.
  60. Next, we need to create the backend:
  61. ```diff
  62. struct mcw_server {
  63. struct wl_display *wl_display;
  64. struct wl_event_loop *wl_event_loop;
  65. + struct wlr_backend *backend;
  66. };
  67. ```
  68. The **backend** is our first wlroots concept. The backend is responsible for
  69. abstracting the low level *input* and *output* implementations from you. Each
  70. backend can generate zero or more input devices (such as mice, keyboards, etc)
  71. and zero or more output devices (such as monitors on your desk). Backends have
  72. nothing to do with Wayland - their purpose is to help you with the *other* APIs
  73. you need to use as a Wayland compositor. There are various backends with various
  74. purposes:
  75. - The **drm** backend utilizes the Linux DRM subsystem to render directly to
  76. your physical displays.
  77. - The **libinput** backend utilizes libinput to enumerate and control physical
  78. input devices.
  79. - The **wayland** backend creates "outputs" as windows on another running
  80. Wayland compositors, allowing you to nest compositors. Useful for debugging.
  81. - The **x11** backend is similar to the Wayland backend, but opens an x11 window
  82. on an x11 server rather than a Wayland window on a Wayland server.
  83. Another important backend is the **multi** backend, which allows you to
  84. initialize several backends at once and aggregate their input and output
  85. devices. This is necessary, for example, to utilize both drm and libinput
  86. simultaneously.
  87. wlroots provides a helper function for automatically choosing the most
  88. appropriate backend based on the user's environment:
  89. ```diff
  90. server.wl_event_loop = wl_display_get_event_loop(server.wl_display);
  91. assert(server.wl_event_loop);
  92. + server.backend = wlr_backend_autocreate(server.wl_display);
  93. + assert(server.backend);
  94. return 0;
  95. }
  96. ```
  97. I would generally suggest using either the Wayland or X11 backends during
  98. development, especially before we have a way of exiting the compositor. If you
  99. call `wlr_backend_autocreate` from a running Wayland or X11 session, the
  100. respective backends will be automatically chosen.
  101. We can now start the backend and enter the Wayland event loop:
  102. ```diff
  103. + if (!wlr_backend_start(server.backend)) {
  104. + fprintf(stderr, "Failed to start backend\n");
  105. + wl_display_destroy(server.wl_display);
  106. + return 1;
  107. + }
  108. +
  109. + wl_display_run(server.wl_display);
  110. + wl_display_destroy(server.wl_display);
  111. return 0;
  112. ```
  113. If you run your compositor at this point, you should see the backend start up
  114. and... do nothing. It'll open a window if you run from a running Wayland or X11
  115. server. If you run it on DRM, it'll probably do very little and you won't even
  116. be able to switch to another TTY to kill it.
  117. In order to render something, we need to know about the outputs we can render
  118. on. The backend provides a **wl_signal** that notifies us when it gets a new
  119. output. This will happen on startup and as any outputs are hotplugged at
  120. runtime.
  121. Let's add this to our server struct:
  122. ```diff
  123. struct mcw_server {
  124. struct wl_display *wl_display;
  125. struct wl_event_loop *wl_event_loop;
  126. struct wlr_backend *backend;
  127. +
  128. + struct wl_listener new_output;
  129. +
  130. + struct wl_list outputs; // mcw_output::link
  131. };
  132. ```
  133. This adds a `wl_listeners` which is signalled when new outputs are added. We
  134. also add a `wl_list` (which is just a linked list provided by libwayland-server)
  135. which we'll later store some state in. To be notified, we must use
  136. `wl_signal_add`:
  137. ```diff
  138. assert(server.backend);
  139. + wl_list_init(&server.outputs);
  140. +
  141. + server.new_output.notify = new_output_notify;
  142. + wl_signal_add(&server.backend->events.new_output, &server.new_output);
  143. if (!wlr_backend_start(server.backend)) {
  144. ```
  145. We specify here the function to be notified, `new_output_notify`:
  146. ```diff
  147. +static void new_output_notify(struct wl_listener *listener, void *data) {
  148. + struct mcw_server *server = wl_container_of(
  149. + listener, server, new_output);
  150. + struct wlr_output *wlr_output = data;
  151. +
  152. + if (!wl_list_empty(&wlr_output->modes)) {
  153. + struct wlr_output_mode *mode =
  154. + wl_container_of(wlr_output->modes.prev, mode, link);
  155. + wlr_output_set_mode(wlr_output, mode);
  156. + }
  157. +
  158. + struct mcw_output *output = calloc(1, sizeof(struct mcw_output));
  159. + clock_gettime(CLOCK_MONOTONIC, &output->last_frame);
  160. + output->server = server;
  161. + output->wlr_output = wlr_output;
  162. + wl_list_insert(&server->outputs, &output->link);
  163. +}
  164. ```
  165. This is a little bit complicated! This function has several roles when dealing
  166. with the incoming `wlr_output`. When the signal is raised, a pointer to the
  167. listener that was signaled is passed in, as well as the `wlr_output` which was
  168. created. `wl_container_of` uses some `offsetof`-based magic to get the
  169. `mcw_server` reference from the listener pointer, and we cast `data` to the
  170. actual type, `wlr_output`.
  171. The next thing we have to do is set the **output mode**. Some backends (notably
  172. x11 and Wayland) do not support modes, but they are necessary for DRM. Output
  173. modes specify a size and refresh rate supported by the output, such as
  174. `1920x1080@60Hz`. The body of this if statement just chooses the last one (which
  175. is usually the highest resolution and refresh rate) and applies it to the output
  176. with `wlr_output_set_mode`. We *must* set the output mode in order to render to
  177. it.
  178. Then, we set up some state for us to keep track of this output with in our
  179. compositor. I added this struct definition at the top of the file:
  180. ```diff
  181. +struct mcw_output {
  182. + struct wlr_output *wlr_output;
  183. + struct mcw_server *server;
  184. + struct timespec last_frame;
  185. +
  186. + struct wl_list link;
  187. +};
  188. ```
  189. This will be the structure we use to store any state we have for this output
  190. that is specific to our compositor's needs. We include a reference to the
  191. `wlr_output`, a reference to the `mcw_server` that owns this output, and the
  192. time of the last frame, which will be useful later. We also set aside a
  193. `wl_list`, which is used by libwayland for linked lists.
  194. Finally, we add this output to the server's list of outputs.
  195. We could use this now, but it would leak memory. We also need to handle output
  196. *removal*, with a signal provided by wlr_output. We add the listener to the
  197. mcw_output struct:
  198. ```diff
  199. struct mcw_output {
  200. struct wlr_output *wlr_output;
  201. struct mcw_server *server;
  202. struct timespec last_frame;
  203. +
  204. + struct wl_listener destroy;
  205. struct wl_list link;
  206. };
  207. ```
  208. Then we hook it up when the output is added:
  209. ```diff
  210. wl_list_insert(&server->outputs, &output->link);
  211. + output->destroy.notify = output_destroy_notify;
  212. + wl_signal_add(&wlr_output->events.destroy, &output->destroy);
  213. }
  214. ```
  215. This will call our output_destroy_notify function to handle cleanup when the
  216. output is unplugged or otherwise removed from wlroots. Our handler looks like
  217. this:
  218. ```diff
  219. +static void output_destroy_notify(struct wl_listener *listener, void *data) {
  220. + struct mcw_output *output = wl_container_of(listener, output, destroy);
  221. + wl_list_remove(&output->link);
  222. + wl_list_remove(&output->destroy.link);
  223. + wl_list_remove(&output->frame.link);
  224. + free(output);
  225. +}
  226. ```
  227. This one should be pretty self-explanatory.
  228. So, we now have a reference to the output. However, we are still not rendering
  229. anything - if you run the compositor again you'll notice the same behavior. In
  230. order to render things, we have to listen for the **frame signal**. Depending on
  231. the selected mode, the output can only receive new frames at a certain rate. We
  232. keep track of this for you in wlroots, and emit the frame signal when it's time
  233. to draw a new frame.
  234. Let's add a listener to the `mcw_output` struct for this purpose:
  235. ```diff
  236. struct mcw_output {
  237. struct wlr_output *wlr_output;
  238. struct mcw_server *server;
  239. struct wl_listener destroy;
  240. + struct wl_listener frame;
  241. struct wl_list link;
  242. };
  243. ```
  244. We can then extend `new_output_notify` to register the listener to the frame
  245. signal:
  246. ```diff
  247. output->destroy.notify = output_destroy_notify;
  248. wl_signal_add(&wlr_output->events.destroy, &output->destroy);
  249. + output->frame.notify = output_frame_notify;
  250. + wl_signal_add(&wlr_output->events.frame, &output->frame);
  251. }
  252. ```
  253. Now, whenever an output is ready for a new frame, `output_frame_notify` will be
  254. called. We still need to write this function, though. Let's start with the
  255. basics:
  256. ```diff
  257. +static void output_frame_notify(struct wl_listener *listener, void *data) {
  258. + struct mcw_output *output = wl_container_of(listener, output, frame);
  259. + struct wlr_output *wlr_output = data;
  260. +}
  261. ```
  262. In order to render anything here, we need to first obtain a wlr_renderer[^2].
  263. We can obtain one from the backend:
  264. ```diff
  265. static void output_frame_notify(struct wl_listener *listener, void *data) {
  266. struct mcw_output *output = wl_container_of(listener, output, frame);
  267. struct wlr_output *wlr_output = data;
  268. + struct wlr_renderer *renderer = wlr_backend_get_renderer(
  269. + wlr_output->backend);
  270. }
  271. ```
  272. We can now take advantage of this renderer to draw something on the output.
  273. ```diff
  274. static void output_frame_notify(struct wl_listener *listener, void *data) {
  275. struct mcw_output *output = wl_container_of(listener, output, frame);
  276. struct wlr_output *wlr_output = data;
  277. struct wlr_renderer *renderer = wlr_backend_get_renderer(
  278. wlr_output->backend);
  279. +
  280. + wlr_output_make_current(wlr_output, NULL);
  281. + wlr_renderer_begin(renderer, wlr_output);
  282. +
  283. + float color[4] = {1.0, 0, 0, 1.0};
  284. + wlr_renderer_clear(renderer, color);
  285. +
  286. + wlr_output_swap_buffers(wlr_output, NULL, NULL);
  287. + wlr_renderer_end(renderer);
  288. }
  289. ```
  290. Calling `wlr_output_make_current` makes the output's OpenGL context "current",
  291. and from here you can use OpenGL calls to render to the output's buffer. We call
  292. `wlr_renderer_begin` to configure some sane OpenGL defaults for us[^3].
  293. At this point we can start rendering. We'll expand more on what you can
  294. do with `wlr_renderer` later, but for now we'll be satisified with clearing the
  295. output to a solid red color.
  296. When we're done rendering, we call `wlr_output_swap_buffers` to swap the
  297. output's front and back buffers, committing what we've rendered to the actual
  298. screen. We call `wlr_renderer_end` to clean up the OpenGL context and we're
  299. done. Running our compositor now should show you a solid red screen!
  300. ---
  301. This concludes today's article. If you take a look at [the
  302. commit](https://github.com/SirCmpwn/mcwayland/commit/f89092e) that this article
  303. describes, you'll see that I took it a little further with some code that clears
  304. the display to a different color every frame. Feel free to experiment with
  305. similar changes!
  306. Over the next two articles, we'll finish wiring up the Wayland server and render
  307. a Wayland client on screen. Please look forward to it!
  308. <p style="text-align: right">
  309. Next &mdash;
  310. <a href="/2018/02/22/Writing-a-wayland-compositor-part-2.html">
  311. Part 2: Rigging up the server
  312. </a>
  313. </p>
  314. [^1]: It's entirely possible to utilize a wlroots backend to make applications which are not Wayland compositors. However, we require a wayland display anyway because the event loop is necessary for a lot of wlroots internals.
  315. [^2]: wlr_renderer is optional. When you call wlr_output_make_current, the OpenGL context is made current and from here you can use any approach you prefer. wlr_renderer is provided to help compositors with simple rendering requirements.
  316. [^3]: Namely: the viewport and blend mode.