1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2020 Scott Moreau
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
25 #include <math.h>
26 #include <deque>
27 #include <numeric>
28 #include <wayfire/plugin.hpp>
29 #include <wayfire/output.hpp>
30 #include <wayfire/render-manager.hpp>
31 #include <wayfire/workspace-manager.hpp>
32 #include <wayfire/plugins/common/cairo-util.hpp>
33 
34 extern "C"
35 {
36 #include <wlr/types/wlr_output.h>
37 }
38 
39 #define WIDGET_PADDING 10
40 
41 class wayfire_bench_screen : public wf::plugin_interface_t
42 {
43     cairo_t *cr = nullptr;
44     double text_y;
45     double max_fps = 0;
46     double widget_xc;
47     uint32_t last_time = wf::get_current_time();
48     double current_fps;
49     double widget_radius;
50     wf::simple_texture_t bench_tex;
51     wf::geometry_t cairo_geometry;
52     cairo_surface_t *cairo_surface;
53     cairo_text_extents_t text_extents;
54     std::deque<int> last_frame_times;
55     int frames_since_last_update = 0;
56     wf::option_wrapper_t<std::string> position{"bench/position"};
57     wf::option_wrapper_t<int> average_frames{"bench/average_frames"};
58     wf::option_wrapper_t<int> frames_per_update{"bench/frames_per_update"};
59 
60   public:
init()61     void init() override
62     {
63         grab_interface->name = "bench";
64         grab_interface->capabilities = 0;
65 
66         output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE);
67         output->render->add_effect(&overlay_hook, wf::OUTPUT_EFFECT_OVERLAY);
68         output->render->set_redraw_always();
69 
70         output->connect_signal("workarea-changed", &workarea_changed);
71         position.set_callback(position_changed);
72         update_texture_position();
73     }
74 
75     wf::config::option_base_t::updated_callback_t position_changed = [=] ()
__anon1ca865d80102() 76     {
77         update_texture_position();
78     };
79 
cairo_recreate()80     void cairo_recreate()
81     {
82         auto og = output->get_relative_geometry();
83         auto font_size = og.height * 0.05;
84 
85         if (!cr)
86         {
87             /* Setup dummy context to get initial font size */
88             cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
89             cr = cairo_create(cairo_surface);
90         }
91 
92         cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
93             CAIRO_FONT_WEIGHT_BOLD);
94         cairo_set_font_size(cr, font_size);
95 
96         cairo_text_extents(cr, "234.5", &text_extents);
97 
98         widget_xc = text_extents.width / 2 + text_extents.x_bearing + WIDGET_PADDING;
99         text_y    = text_extents.height + WIDGET_PADDING;
100         widget_radius = og.height * 0.04;
101 
102         cairo_geometry.width  = text_extents.width + WIDGET_PADDING * 2;
103         cairo_geometry.height = text_extents.height + widget_radius +
104             (widget_radius * sin(M_PI / 8)) + WIDGET_PADDING * 2;
105 
106         /* Recreate surface based on font size */
107         cairo_destroy(cr);
108         cairo_surface_destroy(cairo_surface);
109 
110         cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
111             cairo_geometry.width, cairo_geometry.height);
112         cr = cairo_create(cairo_surface);
113 
114         cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
115             CAIRO_FONT_WEIGHT_BOLD);
116         cairo_set_font_size(cr, font_size);
117     }
118 
update_texture_position()119     void update_texture_position()
120     {
121         auto workarea = output->workspace->get_workarea();
122 
123         cairo_recreate();
124 
125         if ((std::string)position == "top_left")
126         {
127             cairo_geometry.x = workarea.x;
128             cairo_geometry.y = workarea.y;
129         } else if ((std::string)position == "top_center")
130         {
131             cairo_geometry.x = workarea.x +
132                 (workarea.width / 2 - cairo_geometry.width / 2);
133             cairo_geometry.y = workarea.y;
134         } else if ((std::string)position == "top_right")
135         {
136             cairo_geometry.x = workarea.x + (workarea.width - cairo_geometry.width);
137             cairo_geometry.y = workarea.y;
138         } else if ((std::string)position == "center_left")
139         {
140             cairo_geometry.x = workarea.x;
141             cairo_geometry.y = workarea.y +
142                 (workarea.height / 2 - cairo_geometry.height / 2);
143         } else if ((std::string)position == "center")
144         {
145             cairo_geometry.x = workarea.x +
146                 (workarea.width / 2 - cairo_geometry.width / 2);
147             cairo_geometry.y = workarea.y +
148                 (workarea.height / 2 - cairo_geometry.height / 2);
149         } else if ((std::string)position == "center_right")
150         {
151             cairo_geometry.x = workarea.x + (workarea.width - cairo_geometry.width);
152             cairo_geometry.y = workarea.y +
153                 (workarea.height / 2 - cairo_geometry.height / 2);
154         } else if ((std::string)position == "bottom_left")
155         {
156             cairo_geometry.x = workarea.x;
157             cairo_geometry.y = workarea.y +
158                 (workarea.height - cairo_geometry.height);
159         } else if ((std::string)position == "bottom_center")
160         {
161             cairo_geometry.x = workarea.x +
162                 (workarea.width / 2 - cairo_geometry.width / 2);
163             cairo_geometry.y = workarea.y +
164                 (workarea.height - cairo_geometry.height);
165         } else if ((std::string)position == "bottom_right")
166         {
167             cairo_geometry.x = workarea.x + (workarea.width - cairo_geometry.width);
168             cairo_geometry.y = workarea.y +
169                 (workarea.height - cairo_geometry.height);
170         } else
171         {
172             cairo_geometry.x = workarea.x;
173             cairo_geometry.y = workarea.y;
174         }
175 
176         output->render->damage_whole();
177     }
178 
179     wf::signal_connection_t workarea_changed{[this] (wf::signal_data_t *data)
__anon1ca865d80202() 180         {
181             update_texture_position();
182         }
183     };
184 
cairo_clear(cairo_t * cr)185     void cairo_clear(cairo_t *cr)
186     {
187         cairo_set_source_rgba(cr, 0, 0, 0, 0);
188         cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
189         cairo_paint(cr);
190     }
191 
render_bench()192     void render_bench()
193     {
194         double xc     = widget_xc;
195         double yc     = widget_radius + WIDGET_PADDING;
196         double radius = widget_radius;
197         double min_angle    = M_PI / 8;
198         double max_angle    = M_PI - M_PI / 8;
199         double target_angle = 2 * M_PI - M_PI / 8;
200         double fps_angle;
201         char fps_buf[128];
202 
203         double average = std::accumulate(
204             last_frame_times.begin(), last_frame_times.end(), 0.0);
205         average /= last_frame_times.size();
206 
207         current_fps = (double)1000 / average;
208 
209         if (current_fps > max_fps)
210         {
211             max_fps = current_fps;
212         } else
213         {
214             max_fps -= 1;
215         }
216 
217         sprintf(fps_buf, "%.1f", current_fps);
218 
219         if (output->handle->current_mode)
220         {
221             fps_angle = max_angle + (current_fps /
222                 ((double)output->handle->current_mode->refresh / 1000)) *
223                 (target_angle - max_angle);
224         } else
225         {
226             fps_angle = max_angle + (current_fps / max_fps) *
227                 (target_angle - max_angle);
228         }
229 
230         cairo_clear(cr);
231 
232         cairo_set_line_width(cr, 5.0);
233 
234         cairo_set_source_rgba(cr, 0, 0, 0, 1);
235         cairo_arc_negative(cr, xc, yc, radius, min_angle, max_angle);
236         cairo_stroke(cr);
237 
238         cairo_set_source_rgba(cr, 0.7, 0.7, 0.7, 0.7);
239         cairo_move_to(cr, xc, yc);
240         cairo_arc_negative(cr, xc, yc, radius, min_angle, max_angle);
241         cairo_fill(cr);
242 
243         cairo_set_source_rgba(cr, 1.0, 0.2, 0.2, 0.7);
244         cairo_move_to(cr, xc, yc);
245         cairo_arc_negative(cr, xc, yc, radius, fps_angle, max_angle);
246         cairo_fill(cr);
247 
248         if (output->handle->current_mode)
249         {
250             cairo_set_source_rgba(cr, 0, 0, 1, 1);
251         } else
252         {
253             cairo_set_source_rgba(cr, 1, 1, 0, 1);
254         }
255 
256         cairo_text_extents(cr, fps_buf, &text_extents);
257         cairo_move_to(cr,
258             xc - (text_extents.width / 2 + text_extents.x_bearing),
259             text_y + yc);
260         cairo_show_text(cr, fps_buf);
261         cairo_stroke(cr);
262 
263         OpenGL::render_begin();
264         cairo_surface_upload_to_texture(cairo_surface, bench_tex);
265         OpenGL::render_end();
266     }
267 
268     wf::effect_hook_t pre_hook = [=] ()
__anon1ca865d80302() 269     {
270         uint32_t current_time = wf::get_current_time();
271         uint32_t elapsed = current_time - last_time;
272 
273         while ((int)last_frame_times.size() >= average_frames)
274         {
275             last_frame_times.pop_front();
276         }
277 
278         last_frame_times.push_back(elapsed);
279 
280         if (++frames_since_last_update >= frames_per_update)
281         {
282             render_bench();
283             frames_since_last_update = 0;
284         }
285 
286         last_time = current_time;
287         output->render->damage(cairo_geometry);
288     };
289 
290     wf::effect_hook_t overlay_hook = [=] ()
__anon1ca865d80402() 291     {
292         auto fb = output->render->get_target_framebuffer();
293         OpenGL::render_begin(fb);
294         OpenGL::render_transformed_texture(wf::texture_t{bench_tex.tex},
295             cairo_geometry, fb.get_orthographic_projection(), glm::vec4(1.0),
296             OpenGL::TEXTURE_TRANSFORM_INVERT_Y);
297         OpenGL::render_end();
298     };
299 
fini()300     void fini() override
301     {
302         output->render->set_redraw_always(false);
303         output->render->rem_effect(&pre_hook);
304         output->render->rem_effect(&overlay_hook);
305         cairo_surface_destroy(cairo_surface);
306         cairo_destroy(cr);
307         output->render->damage(cairo_geometry);
308     }
309 };
310 
311 DECLARE_WAYFIRE_PLUGIN(wayfire_bench_screen);
312