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