├── LICENSE ├── README.md ├── main.c └── meson.build /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Scott Anderson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compositor-killer 2 | 3 | A malicious example Wayland client, designed to take a very long time to 4 | complete its drawing. Because of implicit synchronization, this can bring the 5 | Wayland compositor to a crawl. Although implicit synchronization affects X11 too. 6 | 7 | This is done by drawing a very large number of iterations of the Mandlebrot set. 8 | 9 | ### Options 10 | 11 | - `-i `: Number of iterations of the Mandlebrot set. Default: 1000. 12 | - `-f x`: Run at a fixed size with these dimensions. 13 | - `-l `: Draw this many frames before quitting. 14 | - `-u`: Run unsynchronized, i.e. ignore wl_surface.frame events. 15 | - '-a : Anitialias n times. 16 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "xdg-shell-protocol.h" 22 | 23 | struct wl_state { 24 | struct wl_compositor *wl_compositor; 25 | struct xdg_wm_base *xdg_wm_base; 26 | 27 | bool close; 28 | uint32_t serial; 29 | int32_t width; 30 | int32_t height; 31 | 32 | struct wl_callback *frame; 33 | }; 34 | 35 | static const GLchar *vert_src = 36 | "precision highp float;\n" 37 | "attribute vec2 in_pos;\n" 38 | "void main() {\n" 39 | " gl_Position = vec4(in_pos, 0.0, 1.0);\n" 40 | "}\n"; 41 | 42 | /* 43 | * https://iquilezles.org/www/articles/mset_smooth/mset_smooth.htm 44 | * https://shadertoy.com/view/4df3Rn 45 | */ 46 | static const GLchar *frag_src = 47 | "precision highp float;\n" 48 | "uniform int frame_num;\n" 49 | "uniform int iter;\n" 50 | "uniform int aa;\n" 51 | "uniform vec2 win_size;\n" 52 | "void main() {\n" 53 | " vec3 col = vec3(0.0, 0.0, 0.0);\n" 54 | " for (int m = 0; m < aa; ++m)\n" 55 | " for (int n = 0; n < aa; ++n) {\n" 56 | " float ftime = float(frame_num) / 10.0;\n" 57 | " vec2 p = (-win_size + 2.0 * (gl_FragCoord.xy + vec2(float(m), float(n)) / float(aa))) / win_size.y;\n" 58 | " float w = float(aa * m + n);\n" 59 | " float time = ftime + 0.5 * (1.0 / 24.0) * w / float(aa * aa);\n" 60 | "\n" 61 | " float zoo = 0.62 + 0.38 * cos(0.07 * time);\n" 62 | " float coa = cos(0.15 * (1.0 - zoo) * time);\n" 63 | " float sia = sin(0.15 * (1.0 - zoo) * time);\n" 64 | " zoo = pow(zoo, 8.0);\n" 65 | " vec2 xy = vec2(p.x * coa - p.y * sia, p.x * sia + p.y * coa);\n" 66 | " vec2 c = vec2(-0.745, 0.186) + xy * zoo;\n" 67 | "\n" 68 | " const float B = 256.0;\n" 69 | " float l = 0.0;\n" 70 | " vec2 z = vec2(0.0);\n" 71 | " for (int i = 0; i < iter; ++i) {\n" 72 | " z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;\n" 73 | " if (dot(z, z) > B * B)\n" 74 | " break;\n" 75 | " l += 1.0;\n" 76 | " }\n" 77 | "\n" 78 | " float sl = l - log2(log2(dot(z, z))) + 4.0;\n" 79 | " float al = smoothstep(-0.1, 0.0, sin(0.5 * 6.2831));\n" 80 | " l = mix(l, sl, al);\n" 81 | " col += 0.5 + 0.5 * cos(3.0 + l * 0.15 + vec3(0.0, 0.6, 1.0));\n" 82 | " }\n" 83 | " col /= float(aa * aa);\n" 84 | " gl_FragColor = vec4(col, 1.0);\n" 85 | "}\n"; 86 | 87 | static void xdg_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) 88 | { 89 | xdg_wm_base_pong(shell, serial); 90 | } 91 | 92 | static const struct xdg_wm_base_listener shell_listener = { 93 | .ping = xdg_ping, 94 | }; 95 | 96 | static void global_add(void *data, struct wl_registry *reg, uint32_t name, 97 | const char *iface, uint32_t version) 98 | { 99 | struct wl_state *wl_state = data; 100 | 101 | if (strcmp(iface, wl_compositor_interface.name) == 0) { 102 | wl_state->wl_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, 1); 103 | 104 | } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { 105 | wl_state->xdg_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1); 106 | xdg_wm_base_add_listener(wl_state->xdg_wm_base, &shell_listener, NULL); 107 | } 108 | } 109 | 110 | static void global_remove(void *data, struct wl_registry *reg, uint32_t name) 111 | { 112 | /* Don't care */ 113 | } 114 | 115 | static const struct wl_registry_listener registry_listener = { 116 | .global = global_add, 117 | .global_remove = global_remove, 118 | }; 119 | 120 | static void xdg_base_configure(void *data, struct xdg_surface *surf, uint32_t serial) 121 | { 122 | struct wl_state *wl_state = data; 123 | wl_state->serial = serial; 124 | } 125 | 126 | static const struct xdg_surface_listener xdg_base_listener = { 127 | .configure = xdg_base_configure, 128 | }; 129 | 130 | static void toplevel_configure(void *data, struct xdg_toplevel *top, 131 | int32_t width, int32_t height, struct wl_array *states) 132 | { 133 | struct wl_state *wl_state = data; 134 | wl_state->width = width; 135 | wl_state->height = height; 136 | } 137 | 138 | static void toplevel_close(void *data, struct xdg_toplevel *top) 139 | { 140 | struct wl_state *wl_state = data; 141 | wl_state->close = true; 142 | } 143 | 144 | static const struct xdg_toplevel_listener toplevel_listener = { 145 | .configure = toplevel_configure, 146 | .close = toplevel_close, 147 | }; 148 | 149 | static bool has_ext(const char *exts, const char *ext) 150 | { 151 | while (*exts) { 152 | const char *end = strchr(exts, ' '); 153 | size_t len = end ? (size_t)(end - exts) : strlen(exts); 154 | 155 | if (strncmp(exts, ext, len) == 0) 156 | return true; 157 | exts += len + !!end; 158 | } 159 | 160 | return false; 161 | } 162 | 163 | static GLuint compile_shader(const GLchar *src, GLenum type, const char *tag) 164 | { 165 | GLuint shader = glCreateShader(type); 166 | glShaderSource(shader, 1, &src, NULL); 167 | glCompileShader(shader); 168 | 169 | GLint status = GL_TRUE; 170 | glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 171 | if (!status) { 172 | char log[1000]; 173 | GLsizei len; 174 | glGetShaderInfoLog(shader, sizeof log, &len, log); 175 | fprintf(stderr, "%s: %s\n", tag, log); 176 | 177 | return 0; 178 | } 179 | 180 | return shader; 181 | } 182 | 183 | static void frame_done(void *data, struct wl_callback *cb, uint32_t time) 184 | { 185 | struct wl_state *wl_state = data; 186 | wl_callback_destroy(wl_state->frame); 187 | wl_state->frame = NULL; 188 | } 189 | 190 | static const struct wl_callback_listener frame_listener = { 191 | .done = frame_done, 192 | }; 193 | 194 | static uint64_t fence_timestamp(int fd) 195 | { 196 | struct sync_file_info file = {0}; 197 | 198 | if (ioctl(fd, SYNC_IOC_FILE_INFO, &file) == -1) { 199 | perror("SYNC_IOC_FILE_INFO"); 200 | return 0; 201 | } 202 | 203 | struct sync_fence_info fences[file.num_fences]; 204 | file.sync_fence_info = (__u64)(uintptr_t)fences; 205 | 206 | if (ioctl(fd, SYNC_IOC_FILE_INFO, &file) == -1) { 207 | perror("SYNC_IOC_FILE_INFO"); 208 | return 0; 209 | } 210 | 211 | uint64_t latest = 0; 212 | for (__u32 i = 0; i < file.num_fences; ++i) { 213 | if (fences[i].timestamp_ns > latest) 214 | latest = fences[i].timestamp_ns; 215 | } 216 | 217 | return latest; 218 | } 219 | 220 | int main(int argc, char *argv[]) 221 | { 222 | int iter = 1000; 223 | bool fixed_size = false; 224 | int fixed_width = 0; 225 | int fixed_height = 0; 226 | int max_frames = INT_MAX; 227 | bool unsynchronized = false; 228 | int aa = 1; 229 | 230 | /* Command line parsing */ 231 | { 232 | int opt; 233 | while ((opt = getopt(argc, argv, "i:f:l:ua:")) != -1) { 234 | switch (opt) { 235 | case 'i': 236 | iter = atoi(optarg); 237 | break; 238 | case 'f': 239 | fixed_size = true; 240 | if (sscanf(optarg, "%dx%d", &fixed_width, &fixed_height) != 2) 241 | return 1; 242 | break; 243 | case 'l': 244 | max_frames = atoi(optarg); 245 | break; 246 | case 'u': 247 | unsynchronized = true; 248 | break; 249 | case 'a': 250 | aa = atoi(optarg); 251 | break; 252 | default: 253 | return 1; 254 | } 255 | } 256 | } 257 | 258 | /* Wayland */ 259 | 260 | struct wl_display *wl_display; 261 | struct wl_state wl_state = {0}; 262 | 263 | wl_display = wl_display_connect(NULL); 264 | if (!wl_display) { 265 | perror("wl_display_connect"); 266 | return 1; 267 | } 268 | 269 | /* Fetching Wayland globals */ 270 | { 271 | struct wl_registry *wl_reg; 272 | wl_reg = wl_display_get_registry(wl_display); 273 | wl_registry_add_listener(wl_reg, ®istry_listener, &wl_state); 274 | wl_display_roundtrip(wl_display); 275 | wl_registry_destroy(wl_reg); 276 | 277 | if (!wl_state.wl_compositor) { 278 | fprintf(stderr, "wl_compositor: %s\n", strerror(EPROTONOSUPPORT)); 279 | return 1; 280 | } 281 | if (!wl_state.xdg_wm_base) { 282 | fprintf(stderr, "xdg_wm_base: %s\n", strerror(EPROTONOSUPPORT)); 283 | return 1; 284 | } 285 | } 286 | 287 | /* EGL */ 288 | 289 | EGLDisplay egl_display; 290 | EGLConfig egl_config; 291 | EGLContext egl_context; 292 | bool egl_has_fences = false; 293 | PFNEGLCREATESYNCKHRPROC egl_create_sync = NULL; 294 | PFNEGLDESTROYSYNCKHRPROC egl_destroy_sync = NULL; 295 | PFNEGLDUPNATIVEFENCEFDANDROIDPROC egl_dup_fence = NULL; 296 | 297 | /* Querying EGL client extensions */ 298 | { 299 | const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); 300 | if (!exts) { 301 | fprintf(stderr, "EGL_EXT_client_extensions: %s\n", 302 | strerror(ENOTSUP)); 303 | return 1; 304 | } 305 | 306 | if (!has_ext(exts, "EGL_EXT_platform_wayland")) { 307 | fprintf(stderr, "EGL_EXT_platform_wayland: %s\n", 308 | strerror(ENOTSUP)); 309 | return 1; 310 | } 311 | } 312 | 313 | /* Initializing EGL */ 314 | { 315 | PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_display; 316 | egl_get_display = (void *)eglGetProcAddress("eglGetPlatformDisplayEXT"); 317 | 318 | egl_display = egl_get_display(EGL_PLATFORM_WAYLAND_EXT, wl_display, NULL); 319 | if (!egl_display) { 320 | fprintf(stderr, "eglGetPlatformDisplayEXT: 0x%x\n", eglGetError()); 321 | return 1; 322 | } 323 | 324 | if (!eglInitialize(egl_display, NULL, NULL)) { 325 | fprintf(stderr, "eglInitialize: 0x%x\n", eglGetError()); 326 | return 1; 327 | } 328 | } 329 | 330 | /* Querying EGL display extensions */ 331 | { 332 | const char *exts = eglQueryString(egl_display, EGL_EXTENSIONS); 333 | 334 | if (has_ext(exts, "EGL_ANDROID_native_fence_sync")) { 335 | egl_has_fences = true; 336 | egl_create_sync = (void *)eglGetProcAddress("eglCreateSyncKHR"); 337 | egl_destroy_sync = (void *)eglGetProcAddress("eglDestroySyncKHR"); 338 | egl_dup_fence = (void *)eglGetProcAddress("eglDupNativeFenceFDANDROID"); 339 | } 340 | } 341 | 342 | /* Choosing an EGL config */ 343 | { 344 | static const EGLint conf_attribs[] = { 345 | EGL_RED_SIZE, 8, 346 | EGL_GREEN_SIZE, 8, 347 | EGL_BLUE_SIZE, 8, 348 | EGL_ALPHA_SIZE, 0, 349 | EGL_NONE 350 | }; 351 | EGLint num_confs; 352 | 353 | if (!eglChooseConfig(egl_display, conf_attribs, &egl_config, 1, &num_confs) || !num_confs) { 354 | fprintf(stderr, "eglChooseConfig: 0x%x\n", eglGetError()); 355 | return 1; 356 | } 357 | } 358 | 359 | /* Creating an EGL context */ 360 | { 361 | static const EGLint context_attribs[] = { 362 | EGL_CONTEXT_CLIENT_VERSION, 2, 363 | EGL_NONE 364 | }; 365 | 366 | egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs); 367 | if (!egl_context) { 368 | fprintf(stderr, "eglCreateContext: 0x%x\n", eglGetError()); 369 | return 1; 370 | } 371 | } 372 | 373 | /* Surface */ 374 | 375 | struct wl_surface *surface_wl; 376 | struct xdg_surface *surface_xdg_base; 377 | struct xdg_toplevel *surface_xdg_toplevel; 378 | struct wl_egl_window *surface_egl_native; 379 | EGLSurface surface_egl; 380 | 381 | /* Creating Wayland surface */ 382 | { 383 | surface_wl = wl_compositor_create_surface(wl_state.wl_compositor); 384 | surface_xdg_base = xdg_wm_base_get_xdg_surface(wl_state.xdg_wm_base, surface_wl); 385 | surface_xdg_toplevel = xdg_surface_get_toplevel(surface_xdg_base); 386 | 387 | xdg_surface_add_listener(surface_xdg_base, &xdg_base_listener, &wl_state); 388 | xdg_toplevel_add_listener(surface_xdg_toplevel, &toplevel_listener, &wl_state); 389 | 390 | xdg_toplevel_set_title(surface_xdg_toplevel, "compositor-killer"); 391 | if (fixed_size) { 392 | xdg_toplevel_set_max_size(surface_xdg_toplevel, fixed_width, fixed_height); 393 | xdg_toplevel_set_min_size(surface_xdg_toplevel, fixed_width, fixed_height); 394 | } 395 | 396 | wl_surface_commit(surface_wl); 397 | wl_display_roundtrip(wl_display); 398 | } 399 | 400 | /* Creating EGL surface */ 401 | { 402 | PFNEGLCREATEPLATFORMWINDOWSURFACEPROC egl_create_surface; 403 | egl_create_surface = (void *)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); 404 | 405 | if (fixed_size) { 406 | wl_state.width = fixed_width; 407 | wl_state.height = fixed_height; 408 | } 409 | 410 | if (wl_state.width == 0) 411 | wl_state.width = 500; 412 | if (wl_state.height == 0) 413 | wl_state.height = 500; 414 | 415 | surface_egl_native = 416 | wl_egl_window_create(surface_wl, wl_state.width, wl_state.height); 417 | if (!surface_egl_native) { 418 | perror("wl_egl_window_create"); 419 | return 1; 420 | } 421 | 422 | surface_egl = egl_create_surface(egl_display, egl_config, surface_egl_native, NULL); 423 | if (!surface_egl) { 424 | fprintf(stderr, "eglCreatePlatformWindowSurfaceEXT: 0x%x", 425 | eglGetError()); 426 | return 1; 427 | } 428 | } 429 | 430 | /* Making EGL surface current */ 431 | eglMakeCurrent(egl_display, surface_egl, surface_egl, egl_context); 432 | eglSwapInterval(egl_display, 0); 433 | 434 | /* OpenGL */ 435 | GLuint gl_program; 436 | GLuint gl_uniform_frame_num; 437 | GLuint gl_uniform_win_size; 438 | 439 | /* Compile GL shaders */ 440 | { 441 | GLuint vert; 442 | GLuint frag; 443 | GLint status = GL_TRUE; 444 | 445 | vert = compile_shader(vert_src, GL_VERTEX_SHADER, "vert_src"); 446 | frag = compile_shader(frag_src, GL_FRAGMENT_SHADER, "frag_src"); 447 | 448 | gl_program = glCreateProgram(); 449 | glAttachShader(gl_program, vert); 450 | glAttachShader(gl_program, frag); 451 | glBindAttribLocation(gl_program, 0, "in_pos"); 452 | glLinkProgram(gl_program); 453 | 454 | glGetProgramiv(gl_program, GL_LINK_STATUS, &status); 455 | if (!status) { 456 | char log[1000]; 457 | GLsizei len; 458 | glGetProgramInfoLog(gl_program, sizeof log, &len, log); 459 | printf("shader: %s\n", log); 460 | return 1; 461 | } 462 | glDeleteShader(vert); 463 | glDeleteShader(frag); 464 | } 465 | 466 | gl_uniform_frame_num = glGetUniformLocation(gl_program, "frame_num"); 467 | gl_uniform_win_size = glGetUniformLocation(gl_program, "win_size"); 468 | 469 | /* Bind all GL state now, because it will never change */ 470 | { 471 | static const GLfloat verts[] = { 472 | -1.0f, -1.0f, 473 | -1.0f, 1.0f, 474 | 1.0f, 1.0f, 475 | 1.0f, -1.0f, 476 | }; 477 | GLuint attr_in_pos = glGetAttribLocation(gl_program, "in_pos"); 478 | GLuint uniform_iter = glGetUniformLocation(gl_program, "iter"); 479 | GLuint uniform_aa = glGetUniformLocation(gl_program, "aa"); 480 | 481 | glUseProgram(gl_program); 482 | 483 | glEnableVertexAttribArray(attr_in_pos); 484 | glVertexAttribPointer(attr_in_pos, 2, GL_FLOAT, GL_FALSE, 0, verts); 485 | 486 | glUniform1i(uniform_iter, iter); 487 | glUniform1i(uniform_aa, aa); 488 | } 489 | 490 | /* Main loop */ 491 | size_t len = 1; 492 | size_t cap = 10; 493 | struct pollfd *fds; 494 | struct { int frame_num; uint64_t start_ns; } *fences; 495 | 496 | fds = calloc(cap, sizeof *fds); 497 | fences = calloc(cap, sizeof *fences); 498 | 499 | if (!fds || !fences) 500 | return 1; 501 | 502 | // fds[0] is always reserved for wl_display 503 | fds[0].fd = wl_display_get_fd(wl_display); 504 | fds[0].events = POLLIN | POLLOUT; 505 | 506 | int frame_num = 0; 507 | //float color_offset = 0.0f; 508 | 509 | while (!wl_state.close && frame_num < max_frames) { 510 | int ret; 511 | 512 | /* Render */ 513 | 514 | if (unsynchronized || !wl_state.frame) { 515 | EGLSyncKHR sync; 516 | uint64_t start_ns = 0; 517 | 518 | if (!unsynchronized) { 519 | wl_state.frame = wl_surface_frame(surface_wl); 520 | wl_callback_add_listener(wl_state.frame, &frame_listener, &wl_state); 521 | } 522 | 523 | /* Resize window */ 524 | 525 | if (wl_state.serial) { 526 | if (fixed_size) { 527 | wl_state.width = fixed_width; 528 | wl_state.height = fixed_height; 529 | } 530 | 531 | if (wl_state.width == 0) 532 | wl_state.width = 500; 533 | if (wl_state.height == 0) 534 | wl_state.height = 500; 535 | 536 | wl_egl_window_resize(surface_egl_native, wl_state.width, wl_state.height, 0, 0); 537 | 538 | xdg_surface_ack_configure(surface_xdg_base, wl_state.serial); 539 | wl_state.serial = 0; 540 | } 541 | 542 | glViewport(0, 0, wl_state.width, wl_state.height); 543 | 544 | glUniform2f(gl_uniform_win_size, wl_state.width, wl_state.height); 545 | glUniform1i(gl_uniform_frame_num, frame_num); 546 | 547 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 548 | 549 | if (egl_has_fences) { 550 | struct timespec ts = {0}; 551 | sync = egl_create_sync(egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); 552 | 553 | /* 554 | * TODO: Check if MONOTONIC is guranteed to be the right time domain. 555 | * 556 | * Sampling the clock from userspace might not be the most accurate way 557 | * to do this, but it's good enough for our purposes. 558 | */ 559 | clock_gettime(CLOCK_MONOTONIC, &ts); 560 | start_ns = ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec; 561 | } 562 | 563 | eglSwapBuffers(egl_display, surface_egl); 564 | 565 | if (egl_has_fences) { 566 | if (len == cap) { 567 | cap *= 2; 568 | fds = realloc(fds, sizeof *fds * cap); 569 | fences = realloc(fences, sizeof *fences * cap); 570 | 571 | if (!fds || !fences) 572 | return 1; 573 | } 574 | 575 | fds[len].fd = egl_dup_fence(egl_display, sync); 576 | fds[len].events = POLLIN; 577 | //fds[len].revents = 0; 578 | fences[len].frame_num = frame_num; 579 | fences[len].start_ns = start_ns; 580 | 581 | egl_destroy_sync(egl_display, sync); 582 | ++len; 583 | } 584 | 585 | ++frame_num; 586 | } 587 | 588 | while (wl_display_prepare_read(wl_display) != 0 && errno == EAGAIN) 589 | wl_display_dispatch_pending(wl_display); 590 | 591 | errno = 0; 592 | do { 593 | ret = wl_display_flush(wl_display); 594 | } while (ret > 0); 595 | 596 | if (ret == -1) { 597 | if (errno == EAGAIN) { 598 | fds[0].events |= POLLOUT; 599 | } else { 600 | wl_display_cancel_read(wl_display); 601 | break; 602 | } 603 | } else { 604 | /* Don't read POLLOUT if we don't need to. It wakes up poll too often. */ 605 | fds[0].events &= ~POLLOUT; 606 | } 607 | 608 | ret = poll(fds, len, unsynchronized ? 0 : -1); 609 | if (ret == -1 && errno != EINTR) { 610 | perror("poll"); 611 | wl_display_cancel_read(wl_display); 612 | break; 613 | } 614 | 615 | if (fds[0].revents & (POLLERR | POLLHUP)) { 616 | wl_display_cancel_read(wl_display); 617 | break; 618 | } 619 | 620 | wl_display_read_events(wl_display); 621 | wl_display_dispatch_pending(wl_display); 622 | 623 | /* Read out rendering times where complete */ 624 | 625 | for (size_t i = 1; i < len;) { 626 | uint64_t end_ns; 627 | 628 | if (!(fds[i].revents & POLLIN)) { 629 | ++i; 630 | continue; 631 | } 632 | 633 | end_ns = fence_timestamp(fds[i].fd); 634 | close(fds[i].fd); 635 | 636 | printf("Frame %d: %f ms\n", fences[i].frame_num, 637 | (double)(end_ns - fences[i].start_ns) * 1e-6); 638 | 639 | for (size_t j = i; j < len - 1; ++j) { 640 | fds[j] = fds[j + 1]; 641 | fences[j] = fences[j + 1]; 642 | } 643 | --len; 644 | } 645 | } 646 | 647 | if (wl_state.frame) 648 | wl_callback_destroy(wl_state.frame); 649 | 650 | for (size_t i = 1; i < len; ++i) 651 | close(fds[i].fd); 652 | free(fds); 653 | free(fences); 654 | 655 | glDeleteProgram(gl_program); 656 | 657 | eglDestroySurface(egl_display, surface_egl); 658 | wl_egl_window_destroy(surface_egl_native); 659 | 660 | xdg_toplevel_destroy(surface_xdg_toplevel); 661 | xdg_surface_destroy(surface_xdg_base); 662 | wl_surface_destroy(surface_wl); 663 | 664 | eglDestroyContext(egl_display, egl_context); 665 | eglTerminate(egl_display); 666 | 667 | eglMakeCurrent(NULL, NULL, NULL, NULL); 668 | eglReleaseThread(); 669 | 670 | xdg_wm_base_destroy(wl_state.xdg_wm_base); 671 | wl_compositor_destroy(wl_state.wl_compositor); 672 | 673 | wl_display_disconnect(wl_display); 674 | } 675 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('compositor-killer', 'c', 2 | version : '0.1', 3 | default_options : ['warning_level=2']) 4 | 5 | add_project_arguments('-Wno-unused-parameter', language: 'c') 6 | 7 | wl = dependency('wayland-client') 8 | wl_egl = dependency('wayland-egl') 9 | egl = dependency('egl') 10 | gles = dependency('glesv2') 11 | 12 | scanner = dependency('wayland-scanner') 13 | scanner = scanner.get_variable(pkgconfig: 'wayland_scanner') 14 | scanner = find_program(scanner, native: true) 15 | 16 | protos = dependency('wayland-protocols') 17 | protos = protos.get_variable(pkgconfig: 'pkgdatadir') 18 | 19 | xdg_shell_c = custom_target('xdg-shell.c', 20 | input: protos / 'stable/xdg-shell/xdg-shell.xml', 21 | output: 'xdg-shell-protocol.c', 22 | command: [scanner, 'private-code', '@INPUT@', '@OUTPUT@']) 23 | 24 | xdg_shell_h = custom_target('xdg-shell.h', 25 | input: protos / 'stable/xdg-shell/xdg-shell.xml', 26 | output: 'xdg-shell-protocol.h', 27 | command: [scanner, 'client-header', '@INPUT@', '@OUTPUT@']) 28 | 29 | exe = executable('compositor-killer', 'main.c', xdg_shell_c, xdg_shell_h, 30 | dependencies: [wl, wl_egl, egl, gles]) 31 | --------------------------------------------------------------------------------