output.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // Copyright 2019-2020 Inaban Authors <https://hacktivis.me/git/inaban>
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Based on wlroots's TinyWL which is distributed under CC0
  4. #include "inaban.h"
  5. #include "config.h"
  6. #include <stdlib.h>
  7. static void
  8. render_surface(struct wlr_surface *surface, int sx, int sy, void *data)
  9. {
  10. /* This function is called for every surface that needs to be rendered. */
  11. struct render_data *rdata = data;
  12. struct inaban_view *view = rdata->view;
  13. struct wlr_output *output = rdata->output;
  14. /* We first obtain a wlr_texture, which is a GPU resource. wlroots
  15. * automatically handles negotiating these with the client. The underlying
  16. * resource could be an opaque handle passed from the client, or the client
  17. * could have sent a pixel buffer which we copied to the GPU, or a few other
  18. * means. You don't have to worry about this, wlroots takes care of it. */
  19. struct wlr_texture *texture = wlr_surface_get_texture(surface);
  20. if(texture == NULL) return;
  21. /* The view has a position in layout coordinates. If you have two displays,
  22. * one next to the other, both 1080p, a view on the rightmost display might
  23. * have layout coordinates of 2000,100. We need to translate that to
  24. * output-local coordinates, or (2000 - 1920). */
  25. double ox = 0, oy = 0;
  26. wlr_output_layout_output_coords(view->server->output_layout, output, &ox, &oy);
  27. ox += view->x + sx, oy += view->y + sy;
  28. /* We also have to apply the scale factor for HiDPI outputs. This is only
  29. * part of the puzzle, TinyWL does not fully support HiDPI. */
  30. struct wlr_box box = {
  31. .x = ox * output->scale,
  32. .y = oy * output->scale,
  33. .width = surface->current.width * output->scale,
  34. .height = surface->current.height * output->scale,
  35. };
  36. /*
  37. * Those familiar with OpenGL are also familiar with the role of matricies
  38. * in graphics programming. We need to prepare a matrix to render the view
  39. * with. wlr_matrix_project_box is a helper which takes a box with a desired
  40. * x, y coordinates, width and height, and an output geometry, then
  41. * prepares an orthographic projection and multiplies the necessary
  42. * transforms to produce a model-view-projection matrix.
  43. *
  44. * Naturally you can do this any way you like, for example to make a 3D
  45. * compositor.
  46. */
  47. float matrix[9];
  48. enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform);
  49. wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix);
  50. /* This takes our matrix, the texture, and an alpha, and performs the actual
  51. * rendering on the GPU. */
  52. wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1);
  53. /* This lets the client know that we've displayed that frame and it can
  54. * prepare another one now if it likes. */
  55. wlr_surface_send_frame_done(surface, rdata->when);
  56. }
  57. static void
  58. output_frame(struct wl_listener *listener, void *data)
  59. {
  60. (void)data;
  61. /* This function is called every time an output is ready to display a frame,
  62. * generally at the output's refresh rate (e.g. 60Hz). */
  63. struct inaban_output *output = wl_container_of(listener, output, frame);
  64. struct wlr_renderer *renderer = output->server->renderer;
  65. struct timespec now;
  66. clock_gettime(CLOCK_MONOTONIC, &now);
  67. /* wlr_output_attach_render makes the OpenGL context current. */
  68. if(!wlr_output_attach_render(output->wlr_output, NULL)) return;
  69. /* The "effective" resolution can change if you rotate your outputs. */
  70. int width, height;
  71. wlr_output_effective_resolution(output->wlr_output, &width, &height);
  72. /* Begin the renderer (calls glViewport and some other GL sanity checks) */
  73. wlr_renderer_begin(renderer, width, height);
  74. float color[4] = {0.11f, 0.11f, 0.11f, 1.0f}; // approx. gruvbox hard-dark
  75. wlr_renderer_clear(renderer, color);
  76. /* Each subsequent window we render is rendered on top of the last. Because
  77. * our view list is ordered front-to-back, we iterate over it backwards. */
  78. struct inaban_view *view;
  79. wl_list_for_each_reverse(view, &output->server->views, link)
  80. {
  81. if(!view->mapped) continue; /* An unmapped view should not be rendered. */
  82. struct render_data rdata = {
  83. .output = output->wlr_output,
  84. .view = view,
  85. .renderer = renderer,
  86. .when = &now,
  87. };
  88. /* This calls our render_surface function for each surface among the
  89. * xdg_surface's toplevel and popups. */
  90. wlr_xdg_surface_for_each_surface(view->xdg_surface, render_surface, &rdata);
  91. }
  92. /* Hardware cursors are rendered by the GPU on a separate plane, and can be
  93. * moved around without re-rendering what's beneath them - which is more
  94. * efficient. However, not all hardware supports hardware cursors. For this
  95. * reason, wlroots provides a software fallback, which we ask it to render
  96. * here. wlr_cursor handles configuring hardware vs software cursors for you,
  97. * and this function is a no-op when hardware cursors are in use. */
  98. wlr_output_render_software_cursors(output->wlr_output, NULL);
  99. /* Conclude rendering and swap the buffers, showing the final frame
  100. * on-screen. */
  101. wlr_renderer_end(renderer);
  102. wlr_output_commit(output->wlr_output);
  103. }
  104. void
  105. server_new_output(struct wl_listener *listener, void *data)
  106. {
  107. /* This event is rasied by the backend when a new output (aka a display or
  108. * monitor) becomes available. */
  109. struct inaban_server *server = wl_container_of(listener, server, new_output);
  110. struct wlr_output *wlr_output = data;
  111. /* Some backends don't have modes. DRM+KMS does, and we need to set a mode
  112. * before we can use the output. The mode is a tuple of (width, height,
  113. * refresh rate), and each monitor supports only a specific set of modes. We
  114. * just pick the first, a more sophisticated compositor would let the user
  115. * configure it or pick the mode the display advertises as preferred. */
  116. if(!wl_list_empty(&wlr_output->modes))
  117. {
  118. struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.prev, mode, link);
  119. wlr_output_set_mode(wlr_output, mode);
  120. }
  121. /* Allocates and configures our state for this output */
  122. struct inaban_output *output = calloc(1, sizeof(struct inaban_output));
  123. output->wlr_output = wlr_output;
  124. output->server = server;
  125. /* Sets up a listener for the frame notify event. */
  126. output->frame.notify = output_frame;
  127. wl_signal_add(&wlr_output->events.frame, &output->frame);
  128. wl_list_insert(&server->outputs, &output->link);
  129. /* Adds this to the output layout. The add_auto function arranges outputs
  130. * from left-to-right in the order they appear. A more sophisticated
  131. * compositor would let the user configure the arrangement of outputs in the
  132. * layout. */
  133. wlr_output_layout_add_auto(server->output_layout, wlr_output);
  134. /* Creating the global adds a wl_output global to the display, which Wayland
  135. * clients can see to find out information about the output (such as
  136. * DPI, scale factor, manufacturer, etc). */
  137. wlr_output_create_global(wlr_output);
  138. }