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-part-3.md (11060B)


  1. ---
  2. date: 2018-02-28
  3. layout: post
  4. title: "Writing a Wayland Compositor, Part 3: Rendering a window"
  5. tags: [wayland, wlroots, instructional]
  6. ---
  7. This is the third in a series of articles on the subject of writing a Wayland
  8. compositor from scratch using [wlroots](https://github.com/swaywm/wlroots).
  9. Check out [the first article](/2018/02/17/Writing-a-Wayland-compositor-1.html)
  10. if you haven't already. We left off with a Wayland server which accepts client
  11. connections and exposes a handful of globals, but does not do anything
  12. particularly interesting yet. Our goal today is to do something interesting -
  13. render a window!
  14. The commit that this article dissects is
  15. [342b7b6](https://github.com/SirCmpwn/mcwayland/commit/342b7b6).
  16. The first thing we have to do in order to render windows is establish the
  17. **compositor**. The wl_compositor global is used by clients to allocate
  18. `wl_surface`s, to which they attach `wl_buffer`s. These surfaces are just a
  19. generic mechanism for sharing buffers of pixels with compositors, and don't
  20. carry an implicit **role**, such as "application window" or "panel".
  21. wlroots provides an implementation of `wl_compositor`. Let's set aside a
  22. reference for it:
  23. ```diff
  24. struct mcw_server {
  25. struct wl_display *wl_display;
  26. struct wl_event_loop *wl_event_loop;
  27. struct wlr_backend *backend;
  28. + struct wlr_compositor *compositor;
  29. struct wl_listener new_output;
  30. ```
  31. Then rig it up:
  32. ```diff
  33. wlr_primary_selection_device_manager_create(server.wl_display);
  34. wlr_idle_create(server.wl_display);
  35. + server.compositor = wlr_compositor_create(server.wl_display,
  36. + wlr_backend_get_renderer(server.backend));
  37. +
  38. wl_display_run(server.wl_display);
  39. wl_display_destroy(server.wl_display);
  40. ```
  41. If we run mcwayface now and check out the globals with `weston-info`, we'll see
  42. a wl_compositor and wl_subcompositor have appeared:
  43. ```
  44. interface: 'wl_compositor', version: 4, name: 8
  45. interface: 'wl_subcompositor', version: 1, name: 9
  46. ```
  47. You get a wl_subcompositor for free with the wlroots wl_compositor
  48. implementation. We'll discuss subcompositors in a later article. Speaking of
  49. things we'll discuss in another article, add this too:
  50. ```diff
  51. wlr_primary_selection_device_manager_create(server.wl_display);
  52. wlr_idle_create(server.wl_display);
  53. server.compositor = wlr_compositor_create(server.wl_display,
  54. wlr_backend_get_renderer(server.backend));
  55. + wlr_xdg_shell_v6_create(server.wl_display);
  56. +
  57. wl_display_run(server.wl_display);
  58. wl_display_destroy(server.wl_display);
  59. return 0;
  60. ```
  61. Remember that I said earlier that surfaces are just globs of pixels with no
  62. role? xdg_shell is something that can give surfaces a role. We'll talk about it
  63. more in the next article. After adding this, many clients will be able to
  64. connect to your compositor and spawn a window. However, without adding anything
  65. else, these windows will never be shown on-screen. You have to render them!
  66. Something that distinguishes wlroots from libraries like wlc and libweston is
  67. that wlroots does not do any rendering for you. This gives you a lot of
  68. flexibility to render surfaces any way you like. The clients just gave you a
  69. pile of pixels, what you do with them is up to you - maybe you're making a
  70. desktop compositor, or maybe you want to draw them on an Android-style app
  71. switcher, or perhaps your compositor arranges windows in VR - all of this is
  72. possible with wlroots.
  73. Things are about to get complicated, so let's start with the easy part: in
  74. the output_frame handler, we have to get a reference to every wlr_surface we
  75. want to render. So let's iterate over every surface our `wlr_compositor` is
  76. keeping track of:
  77. ```diff
  78. wlr_renderer_begin(renderer, wlr_output);
  79. + struct wl_resource *_surface;
  80. + wl_resource_for_each(_surface, &server->compositor->surfaces) {
  81. + struct wlr_surface *surface = wlr_surface_from_resource(_surface);
  82. + if (!wlr_surface_has_buffer(surface)) {
  83. + continue;
  84. + }
  85. + // TODO: Render this surface
  86. + }
  87. wlr_output_swap_buffers(wlr_output, NULL, NULL);
  88. ```
  89. The `wlr_compositor` struct has a member named `surfaces`, which is a list of
  90. `wl_resource`s. A helper method is provided to produce a `wlr_surface` from its
  91. corresponding `wl_resource`. The `wlr_surface_has_buffer` call is just to make
  92. sure that the client has actually given us pixels to display on this surface.
  93. wlroots might make you do the rendering yourself, but some tools *are* provided
  94. to help you write compositors with simple rendering requirements:
  95. **wlr_renderer**. We've already touched on this a little bit, but now we're
  96. going to use it for real. A little bit of OpenGL knowledge is required here. If
  97. you're a complete novice with OpenGL[^1], I can recommend [this
  98. tutorial](https://learnopengl.com/) to help you out. Since you're in a hurry,
  99. we'll do a quick crash course on the concepts necessary to utilize wlr_renderer.
  100. If you get lost, just skip to the next diff and treat it as magic incantations
  101. that make your windows appear.
  102. We have a pile of pixels, and we want to put it on the screen. We can do this
  103. with a **shader**. If you're using wlr_renderer (and mcwayface will be), shaders
  104. are provided for you. To use our shaders, we feed them a **texture** (the pile
  105. of pixels) and a **matrix**. If we treat every pixel coordinate on our surface
  106. as a vector from (0, 0); top left, to (1, 1); bottom right, our goal is to
  107. produce a matrix that we can multiply a vector by to find the final coordinates
  108. on-screen for the pixel to be drawn to. We must project pixel coordinates from
  109. this 0-1 system to the coordinates of our desired rectangle on screen.
  110. There's gotcha here, however: the coordinates on-screen *also* go from 0 to 1,
  111. instead of, for example, 0-1920 and 0-1080. To project coordinates like
  112. "put my 640x480 window at coordinates 100,100" to screen coordinates, we use an
  113. **orthographic projection matrix**. I know that sounds scary, but don't worry -
  114. wlroots does all of the work for you. Your `wlr_output` already has a suitable
  115. matrix called `transform_matrix`, which incorporates into it the current
  116. resolution, scale factor, and rotation of your screen.
  117. Okay, hopefully you're still with me. This sounds a bit complicated, but the
  118. manifestation of all of this nonsense is fairly straightforward. wlroots
  119. provides some tools to make it easy for you. First, we have to prepare a
  120. `wlr_box` that represents (in output coordinates) where we want the surface to
  121. show up.
  122. ```diff
  123. struct wl_resource *_surface;
  124. wl_resource_for_each(_surface, &server->compositor->surfaces) {
  125. struct wlr_surface *surface = wlr_surface_from_resource(_surface);
  126. if (!wlr_surface_has_buffer(surface)) {
  127. continue;
  128. }
  129. - // TODO: Render this surface
  130. + struct wlr_box render_box = {
  131. + .x = 20, .y = 20,
  132. + .width = surface->current->width,
  133. + .height = surface->current->height
  134. + };
  135. }
  136. ```
  137. Now, here's the great part: all of that fancy math I was just talking about can
  138. be done with a single helper function provided by wlroots: `wlr_matrix_project_box`.
  139. ```diff
  140. struct wl_resource *_surface;
  141. wl_resource_for_each(_surface, &server->compositor->surfaces) {
  142. struct wlr_surface *surface = wlr_surface_from_resource(_surface);
  143. if (!wlr_surface_has_buffer(surface)) {
  144. continue;
  145. }
  146. struct wlr_box render_box = {
  147. .x = 20, .y = 20,
  148. .width = surface->current->width,
  149. .height = surface->current->height
  150. };
  151. + float matrix[16];
  152. + wlr_matrix_project_box(&matrix, &render_box,
  153. + surface->current->transform,
  154. + 0, &wlr_output->transform_matrix);
  155. }
  156. ```
  157. This takes a reference to a `float[16]` to store the output matrix in, a box you
  158. want to project, some other stuff that isn't important right now, and the
  159. projection you want to use - in this case, we just use the one provided by
  160. `wlr_output`.
  161. The reason we make you understand and perform these steps is because it's
  162. entirely possible that you'll want to do them differently in the future. This
  163. is only the simplest case, but remember that wlroots is designed for *every*
  164. case. Now that we've obtained this matrix, we can finally render the surface:
  165. ```diff
  166. struct wl_resource *_surface;
  167. wl_resource_for_each(_surface, &server->compositor->surfaces) {
  168. struct wlr_surface *surface = wlr_surface_from_resource(_surface);
  169. if (!wlr_surface_has_buffer(surface)) {
  170. continue;
  171. }
  172. struct wlr_box render_box = {
  173. .x = 20, .y = 20,
  174. .width = surface->current->width,
  175. .height = surface->current->height
  176. };
  177. float matrix[16];
  178. wlr_matrix_project_box(&matrix, &render_box,
  179. surface->current->transform,
  180. 0, &wlr_output->transform_matrix);
  181. + wlr_render_with_matrix(renderer, surface->texture, &matrix, 1.0f);
  182. + wlr_surface_send_frame_done(surface, &now);
  183. }
  184. ```
  185. We also throw in a `wlr_surface_send_frame_done` for good measure, which lets
  186. the client know that we're done with it so they can send another frame. We're
  187. done! Run mcwayface now, then the following commands:
  188. ```
  189. $ WAYLAND_DISPLAY=wayland-1 weston-simple-shm &
  190. $ WAYLAND_DISPLAY=wayland-1 gnome-terminal -- htop
  191. ```
  192. To see the following beautiful image:
  193. ![](https://sr.ht/y_qN.png)
  194. Run any other clients you like - many of them will work!
  195. We used a bit of a hack today by simply rendering all of the surfaces the
  196. `wl_compositor` knew of. In practice, we're going to need to extend our
  197. xdg_shell support (and add some other shells, too) to do this properly. We'll
  198. cover this in the next article.
  199. Before you go, a quick note: after this commit, I reorganized things a bit -
  200. we're going to outgrow this single-file approach pretty quickly soon. Check out
  201. That commit [here](https://github.com/SirCmpwn/mcwayface/commit/e800facb371c42d844b858af5ced456ffd6e9d08).
  202. See you next time!
  203. <p>
  204. Previous &mdash;
  205. <a href="/2018/02/22/Writing-a-wayland-compositor-part-2.html">
  206. Part 2: Rigging up the server
  207. </a>
  208. </p>
  209. [^1]: If you're not a novice, we'll cover more complex rendering scenarios in the future. But the short of it is that you can implement your own `wlr_renderer` that wlr_compositor can use to bind textures to the GPU and then you can do whatever you want.