1 /*
2  * Copyright (c) 2015 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  *
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31 #include <X11/Xlib-xcb.h>
32 #include <X11/xshmfence.h>
33 #include <X11/Xutil.h>
34 #include <X11/Xlibint.h>
35 #include <X11/extensions/Xcomposite.h>
36 #include <X11/extensions/Xdamage.h>
37 #include <X11/extensions/dpms.h>
38 #include <X11/extensions/randr.h>
39 #include <X11/extensions/Xrandr.h>
40 #include <xcb/xcb.h>
41 #include <xcb/present.h>
42 #include <xcb/dri3.h>
43 #include <xcb/xfixes.h>
44 #include <xf86drm.h>
45 #include <i915_drm.h>
46 
47 #include <stdio.h>
48 #include <string.h>
49 #include <fcntl.h>
50 #include <unistd.h>
51 #include <assert.h>
52 #include <errno.h>
53 #include <setjmp.h>
54 #include <signal.h>
55 #include <sys/wait.h>
56 
57 #include "dri3.h"
58 
59 static int _x_error_occurred;
60 static uint32_t stamp;
61 
62 struct list {
63     struct list *next, *prev;
64 };
65 
66 static void
list_init(struct list * list)67 list_init(struct list *list)
68 {
69     list->next = list->prev = list;
70 }
71 
72 static inline void
__list_add(struct list * entry,struct list * prev,struct list * next)73 __list_add(struct list *entry,
74 	    struct list *prev,
75 	    struct list *next)
76 {
77     next->prev = entry;
78     entry->next = next;
79     entry->prev = prev;
80     prev->next = entry;
81 }
82 
83 static inline void
list_add(struct list * entry,struct list * head)84 list_add(struct list *entry, struct list *head)
85 {
86     __list_add(entry, head, head->next);
87 }
88 
89 static inline void
__list_del(struct list * prev,struct list * next)90 __list_del(struct list *prev, struct list *next)
91 {
92 	next->prev = prev;
93 	prev->next = next;
94 }
95 
96 static inline void
_list_del(struct list * entry)97 _list_del(struct list *entry)
98 {
99     __list_del(entry->prev, entry->next);
100 }
101 
102 static inline void
list_move(struct list * list,struct list * head)103 list_move(struct list *list, struct list *head)
104 {
105 	if (list->prev != head) {
106 		_list_del(list);
107 		list_add(list, head);
108 	}
109 }
110 
111 #define __container_of(ptr, sample, member)				\
112     (void *)((char *)(ptr) - ((char *)&(sample)->member - (char *)(sample)))
113 
114 #define list_for_each_entry(pos, head, member)				\
115     for (pos = __container_of((head)->next, pos, member);		\
116 	 &pos->member != (head);					\
117 	 pos = __container_of(pos->member.next, pos, member))
118 
119 static int
_check_error_handler(Display * display,XErrorEvent * event)120 _check_error_handler(Display     *display,
121 		     XErrorEvent *event)
122 {
123 	if (_x_error_occurred < 0)
124 		return True;
125 
126 	printf("X11 error from display %s, serial=%ld, error=%d, req=%d.%d\n",
127 	       DisplayString(display),
128 	       event->serial,
129 	       event->error_code,
130 	       event->request_code,
131 	       event->minor_code);
132 	_x_error_occurred++;
133 	return False; /* ignored */
134 }
135 
elapsed(const struct timespec * start,const struct timespec * end)136 static double elapsed(const struct timespec *start,
137 		      const struct timespec *end)
138 {
139 	return 1e6*(end->tv_sec - start->tv_sec) + (end->tv_nsec - start->tv_nsec)/1000;
140 }
141 
142 struct buffer {
143 	struct list link;
144 	Pixmap pixmap;
145 	struct dri3_fence fence;
146 	int fd;
147 	int busy;
148 	int id;
149 };
150 
151 #define DRI3 1
152 #define NOCOPY 2
153 #define ASYNC 4
run(Display * dpy,Window win,const char * name,unsigned options)154 static void run(Display *dpy, Window win, const char *name, unsigned options)
155 {
156 	xcb_connection_t *c = XGetXCBConnection(dpy);
157 	struct timespec start, end;
158 #define N_BACK 8
159 	char test_name[128];
160 	struct buffer buffer[N_BACK];
161 	struct list mru;
162 	Window root;
163 	unsigned int width, height;
164 	unsigned border, depth;
165 	unsigned present_flags = 0;
166 	xcb_xfixes_region_t update = 0;
167 	int completed = 0;
168 	int queued = 0;
169 	uint32_t eid = 0;
170 	void *Q = NULL;
171 	int i, n;
172 
173 	list_init(&mru);
174 
175 	XGetGeometry(dpy, win,
176 		     &root, &i, &n, &width, &height, &border, &depth);
177 
178 	_x_error_occurred = 0;
179 
180 	for (n = 0; n < N_BACK; n++) {
181 		buffer[n].pixmap = xcb_generate_id(c);
182 		xcb_create_pixmap(c, depth, buffer[n].pixmap, win,
183 				  width, height);
184 		buffer[n].fence.xid = 0;
185 		buffer[n].fd = -1;
186 		buffer[n].id = n;
187 		if (options & DRI3) {
188 			xcb_dri3_buffer_from_pixmap_reply_t *reply;
189 			int *fds;
190 
191 			if (dri3_create_fence(dpy, win, &buffer[n].fence))
192 				return;
193 
194 			reply = xcb_dri3_buffer_from_pixmap_reply (c,
195 								   xcb_dri3_buffer_from_pixmap(c, buffer[n].pixmap),
196 								   NULL);
197 			if (reply == NULL)
198 				return;
199 
200 			fds = xcb_dri3_buffer_from_pixmap_reply_fds (c, reply);
201 			buffer[n].fd = fds[0];
202 			free(reply);
203 
204 			/* start idle */
205 			xshmfence_trigger(buffer[n].fence.addr);
206 		}
207 		buffer[n].busy = 0;
208 		list_add(&buffer[n].link, &mru);
209 	}
210 	if (options & ASYNC)
211 		present_flags |= XCB_PRESENT_OPTION_ASYNC;
212 	if (options & NOCOPY) {
213 		update = xcb_generate_id(c);
214 		xcb_xfixes_create_region(c, update, 0, NULL);
215 		present_flags |= XCB_PRESENT_OPTION_COPY;
216 	}
217 
218 	if (!(options & DRI3)) {
219 		eid = xcb_generate_id(c);
220 		xcb_present_select_input(c, eid, win,
221 					 (options & NOCOPY ? 0 : XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY) |
222 					 XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
223 		Q = xcb_register_for_special_xge(c, &xcb_present_id, eid, &stamp);
224 	}
225 
226 	clock_gettime(CLOCK_MONOTONIC, &start);
227 	do {
228 		for (n = 0; n < 1000; n++) {
229 			struct buffer *tmp = NULL, *b = NULL;
230 retry:
231 			list_for_each_entry(tmp, &mru, link) {
232 				if (tmp->fence.xid)
233 					tmp->busy = !xshmfence_query(tmp->fence.addr);
234 				if (!tmp->busy) {
235 					b = tmp;
236 					break;
237 				}
238 			}
239 			if (options & DRI3) {
240 				if (b == NULL)
241 					goto retry;
242 
243 				xshmfence_reset(b->fence.addr);
244 				queued--;
245 				completed++;
246 			} else while (b == NULL) {
247 				xcb_present_generic_event_t *ev;
248 
249 				ev = (xcb_present_generic_event_t *)
250 					xcb_wait_for_special_event(c, Q);
251 				if (ev == NULL)
252 					abort();
253 
254 				do {
255 					switch (ev->evtype) {
256 					case XCB_PRESENT_COMPLETE_NOTIFY:
257 						completed++;
258 						queued--;
259 						break;
260 
261 					case XCB_PRESENT_EVENT_IDLE_NOTIFY:
262 						{
263 							xcb_present_idle_notify_event_t *ie = (xcb_present_idle_notify_event_t *)ev;
264 							assert(ie->serial < N_BACK);
265 							buffer[ie->serial].busy = 0;
266 							if (b == NULL)
267 								b = &buffer[ie->serial];
268 							break;
269 						}
270 					}
271 					free(ev);
272 				} while ((ev = (xcb_present_generic_event_t *)xcb_poll_for_special_event(c, Q)));
273 			}
274 
275 			b->busy = (options & NOCOPY) == 0;
276 			xcb_present_pixmap(c, win, b->pixmap, b->id,
277 					   0, /* valid */
278 					   update, /* update */
279 					   0, /* x_off */
280 					   0, /* y_off */
281 					   None,
282 					   None, /* wait fence */
283 					   b->fence.xid,
284 					   present_flags,
285 					   0, /* target msc */
286 					   0, /* divisor */
287 					   0, /* remainder */
288 					   0, NULL);
289 			list_move(&b->link, &mru);
290 			queued++;
291 			xcb_flush(c);
292 		}
293 		clock_gettime(CLOCK_MONOTONIC, &end);
294 	} while (end.tv_sec < start.tv_sec + 10);
295 
296 	if (options & DRI3) {
297 		struct buffer *b = NULL;
298 		XID pixmap;
299 
300 		pixmap = xcb_generate_id(c);
301 		xcb_create_pixmap(c, depth, pixmap, win, width, height);
302 		xcb_present_pixmap(c, win, pixmap, 0xdeadbeef,
303 				   0, /* valid */
304 				   None, /* update */
305 				   0, /* x_off */
306 				   0, /* y_off */
307 				   None,
308 				   None, /* wait fence */
309 				   None,
310 				   0,
311 				   0, /* target msc */
312 				   0, /* divisor */
313 				   0, /* remainder */
314 				   0, NULL);
315 		xcb_flush(c);
316 
317 		list_for_each_entry(b, &mru, link)
318 			xshmfence_await(b->fence.addr);
319 
320 		xcb_free_pixmap(c, pixmap);
321 		completed += queued;
322 	} else while (queued) {
323 		xcb_present_generic_event_t *ev;
324 
325 		ev = (xcb_present_generic_event_t *)
326 			xcb_wait_for_special_event(c, Q);
327 		if (ev == NULL)
328 			abort();
329 
330 		do {
331 			switch (ev->evtype) {
332 			case XCB_PRESENT_COMPLETE_NOTIFY:
333 				completed++;
334 				queued--;
335 				break;
336 
337 			case XCB_PRESENT_EVENT_IDLE_NOTIFY:
338 				break;
339 			}
340 			free(ev);
341 		} while ((ev = (xcb_present_generic_event_t *)xcb_poll_for_special_event(c, Q)));
342 	}
343 	clock_gettime(CLOCK_MONOTONIC, &end);
344 
345 	if (update)
346 		xcb_xfixes_destroy_region(c, update);
347 	for (n = 0; n < N_BACK; n++) {
348 		if (buffer[n].fence.xid)
349 			dri3_fence_free(dpy, &buffer[n].fence);
350 		if (buffer[n].fd != -1)
351 			close(buffer[n].fd);
352 		xcb_free_pixmap(c, buffer[n].pixmap);
353 	}
354 
355 	if (Q) {
356 		xcb_discard_reply(c, xcb_present_select_input_checked(c, eid, win, 0).sequence);
357 		XSync(dpy, True);
358 		xcb_unregister_for_special_event(c, Q);
359 	}
360 
361 	test_name[0] = '\0';
362 	if (options) {
363 		snprintf(test_name, sizeof(test_name), "(%s%s%s )",
364 			 options & NOCOPY ? " no-copy" : "",
365 			 options & DRI3 ? " dri3" : "",
366 			 options & ASYNC ? " async" : "");
367 	}
368 	printf("%s%s: Completed %d presents in %.1fs, %.3fus each (%.1f FPS)\n",
369 	       name, test_name,
370 	       completed, elapsed(&start, &end) / 1000000,
371 	       elapsed(&start, &end) / completed,
372 	       completed / (elapsed(&start, &end) / 1000000));
373 }
374 
375 struct perpixel {
376 	Window win;
377 	struct buffer buffer[N_BACK];
378 	struct list mru;
379 	uint32_t eid;
380 	void *Q;
381 	int queued;
382 };
383 
perpixel(Display * dpy,int max_width,int max_height,unsigned options)384 static void perpixel(Display *dpy,
385 		     int max_width, int max_height, unsigned options)
386 {
387 	//const int sz = max_width * max_height;
388 	const int sz = 1048;
389 	struct perpixel *pp;
390 	xcb_connection_t *c = XGetXCBConnection(dpy);
391 	struct timespec start, end;
392 	char test_name[128];
393 	unsigned present_flags = 0;
394 	xcb_xfixes_region_t update = 0;
395 	int completed = 0;
396 	int i, n;
397 
398 	pp = calloc(sz, sizeof(*pp));
399 	if (!pp)
400 		return;
401 
402 	for (i = 0; i < sz; i++) {
403 		XSetWindowAttributes attr = { .override_redirect = 1 };
404 		int depth = DefaultDepth(dpy, DefaultScreen(dpy));
405 		pp[i].win = XCreateWindow(dpy, DefaultRootWindow(dpy),
406 					  i % max_width, i / max_width, 1, 1, 0, depth,
407 					  InputOutput,
408 					  DefaultVisual(dpy, DefaultScreen(dpy)),
409 					  CWOverrideRedirect, &attr);
410 		XMapWindow(dpy, pp[i].win);
411 		list_init(&pp[i].mru);
412 		for (n = 0; n < N_BACK; n++) {
413 			pp[i].buffer[n].pixmap = xcb_generate_id(c);
414 			xcb_create_pixmap(c, depth, pp[i].buffer[n].pixmap,
415 					  pp[i].win, 1, 1);
416 			pp[i].buffer[n].fence.xid = 0;
417 			pp[i].buffer[n].fd = -1;
418 			pp[i].buffer[n].id = n;
419 			if (options & DRI3) {
420 				xcb_dri3_buffer_from_pixmap_reply_t *reply;
421 				int *fds;
422 
423 				if (dri3_create_fence(dpy, pp[i].win, &pp[i].buffer[n].fence))
424 					return;
425 
426 				reply = xcb_dri3_buffer_from_pixmap_reply(c,
427 									  xcb_dri3_buffer_from_pixmap(c, pp[i].buffer[n].pixmap),
428 									  NULL);
429 				if (reply == NULL)
430 					return;
431 
432 				fds = xcb_dri3_buffer_from_pixmap_reply_fds(c, reply);
433 				pp[i].buffer[n].fd = fds[0];
434 				free(reply);
435 
436 				/* start idle */
437 				xshmfence_trigger(pp[i].buffer[n].fence.addr);
438 			}
439 			pp[i].buffer[n].busy = 0;
440 			list_add(&pp[i].buffer[n].link, &pp[i].mru);
441 		}
442 
443 		if (!(options & DRI3)) {
444 			pp[i].eid = xcb_generate_id(c);
445 			xcb_present_select_input(c, pp[i].eid, pp[i].win,
446 						 (options & NOCOPY ? 0 : XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY) |
447 						 XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
448 			pp[i].Q = xcb_register_for_special_xge(c, &xcb_present_id, pp[i].eid, &stamp);
449 		}
450 		pp[i].queued = 0;
451 	}
452 
453 	XSync(dpy, True);
454 	_x_error_occurred = 0;
455 
456 	if (options & ASYNC)
457 		present_flags |= XCB_PRESENT_OPTION_ASYNC;
458 	if (options & NOCOPY) {
459 		update = xcb_generate_id(c);
460 		xcb_xfixes_create_region(c, update, 0, NULL);
461 		present_flags |= XCB_PRESENT_OPTION_COPY;
462 	}
463 
464 	clock_gettime(CLOCK_MONOTONIC, &start);
465 	do {
466 		for (i = 0; i < sz; i++) {
467 			struct buffer *tmp = NULL, *b = NULL;
468 retry:
469 			list_for_each_entry(tmp, &pp[i].mru, link) {
470 				if (tmp->fence.xid)
471 					tmp->busy = !xshmfence_query(tmp->fence.addr);
472 				if (!tmp->busy) {
473 					b = tmp;
474 					break;
475 				}
476 			}
477 			if (options & DRI3) {
478 				if (b == NULL)
479 					goto retry;
480 
481 				xshmfence_reset(b->fence.addr);
482 				pp[i].queued--;
483 				completed++;
484 			} else while (b == NULL) {
485 				xcb_present_generic_event_t *ev;
486 
487 				ev = (xcb_present_generic_event_t *)
488 					xcb_wait_for_special_event(c, pp[i].Q);
489 				if (ev == NULL)
490 					abort();
491 
492 				do {
493 					switch (ev->evtype) {
494 					case XCB_PRESENT_COMPLETE_NOTIFY:
495 						completed++;
496 						pp[i].queued--;
497 						break;
498 
499 					case XCB_PRESENT_EVENT_IDLE_NOTIFY:
500 						{
501 							xcb_present_idle_notify_event_t *ie = (xcb_present_idle_notify_event_t *)ev;
502 							assert(ie->serial < N_BACK);
503 							pp[i].buffer[ie->serial].busy = 0;
504 							if (b == NULL)
505 								b = &pp[i].buffer[ie->serial];
506 							break;
507 						}
508 					}
509 					free(ev);
510 				} while ((ev = (xcb_present_generic_event_t *)xcb_poll_for_special_event(c, pp[i].Q)));
511 			}
512 
513 			b->busy = (options & NOCOPY) == 0;
514 			xcb_present_pixmap(c, pp[i].win, b->pixmap, b->id,
515 					   0, /* valid */
516 					   update, /* update */
517 					   0, /* x_off */
518 					   0, /* y_off */
519 					   None,
520 					   None, /* wait fence */
521 					   b->fence.xid,
522 					   present_flags,
523 					   0, /* target msc */
524 					   0, /* divisor */
525 					   0, /* remainder */
526 					   0, NULL);
527 			list_move(&b->link, &pp[i].mru);
528 			pp[i].queued++;
529 		}
530 		xcb_flush(c);
531 		clock_gettime(CLOCK_MONOTONIC, &end);
532 	} while (end.tv_sec < start.tv_sec + 10);
533 
534 	for (i = 0; i < sz; i++) {
535 		if (options & DRI3) {
536 			int depth = DefaultDepth(dpy, DefaultScreen(dpy));
537 			struct buffer *b = NULL;
538 			XID pixmap;
539 
540 			pixmap = xcb_generate_id(c);
541 			xcb_create_pixmap(c, depth, pixmap, pp[i].win, 1, 1);
542 			xcb_present_pixmap(c, pp[i].win, pixmap, 0xdeadbeef,
543 					   0, /* valid */
544 					   None, /* update */
545 					   0, /* x_off */
546 					   0, /* y_off */
547 					   None,
548 					   None, /* wait fence */
549 					   None,
550 					   0,
551 					   0, /* target msc */
552 					   0, /* divisor */
553 					   0, /* remainder */
554 					   0, NULL);
555 			xcb_flush(c);
556 
557 			list_for_each_entry(b, &pp[i].mru, link)
558 				xshmfence_await(b->fence.addr);
559 
560 			xcb_free_pixmap(c, pixmap);
561 			completed += pp[i].queued;
562 		} else while (pp[i].queued) {
563 			xcb_present_generic_event_t *ev;
564 
565 			ev = (xcb_present_generic_event_t *)
566 				xcb_wait_for_special_event(c, pp[i].Q);
567 			if (ev == NULL)
568 				abort();
569 
570 			do {
571 				switch (ev->evtype) {
572 				case XCB_PRESENT_COMPLETE_NOTIFY:
573 					completed++;
574 					pp[i].queued--;
575 					break;
576 
577 				case XCB_PRESENT_EVENT_IDLE_NOTIFY:
578 					break;
579 				}
580 				free(ev);
581 			} while ((ev = (xcb_present_generic_event_t *)xcb_poll_for_special_event(c, pp[i].Q)));
582 		}
583 	}
584 	clock_gettime(CLOCK_MONOTONIC, &end);
585 
586 	if (update)
587 		xcb_xfixes_destroy_region(c, update);
588 
589 	for (i = 0; i < sz; i++) {
590 		for (n = 0; n < N_BACK; n++) {
591 			if (pp[i].buffer[n].fence.xid)
592 				dri3_fence_free(dpy, &pp[i].buffer[n].fence);
593 			if (pp[i].buffer[n].fd != -1)
594 				close(pp[i].buffer[n].fd);
595 			xcb_free_pixmap(c, pp[i].buffer[n].pixmap);
596 		}
597 
598 		if (pp[i].Q) {
599 			xcb_discard_reply(c, xcb_present_select_input_checked(c, pp[i].eid, pp[i].win, 0).sequence);
600 			XSync(dpy, True);
601 			xcb_unregister_for_special_event(c, pp[i].Q);
602 		}
603 
604 		XDestroyWindow(dpy, pp[i].win);
605 	}
606 	free(pp);
607 
608 	test_name[0] = '\0';
609 	if (options) {
610 		snprintf(test_name, sizeof(test_name), "(%s%s%s )",
611 			 options & NOCOPY ? " no-copy" : "",
612 			 options & DRI3 ? " dri3" : "",
613 			 options & ASYNC ? " async" : "");
614 	}
615 	printf("%s%s: Completed %d presents in %.1fs, %.3fus each (%.1f FPS)\n",
616 	       __func__, test_name,
617 	       completed, elapsed(&start, &end) / 1000000,
618 	       elapsed(&start, &end) / completed,
619 	       completed / (elapsed(&start, &end) / 1000000));
620 }
621 
isqrt(int x)622 static int isqrt(int x)
623 {
624 	int i;
625 
626 	for (i = 2; i*i < x; i++)
627 		;
628 	return i;
629 }
630 
631 struct sibling {
632 	pthread_t thread;
633 	Display *dpy;
634 	int x, y;
635 	int width, height;
636 	unsigned options;
637 };
638 
sibling(void * arg)639 static void *sibling(void *arg)
640 {
641 	struct sibling *s = arg;
642 	XSetWindowAttributes attr = { .override_redirect = 1 };
643 	Window win = XCreateWindow(s->dpy, DefaultRootWindow(s->dpy),
644 				   s->x, s->y, s->width, s->height, 0,
645 				   DefaultDepth(s->dpy, DefaultScreen(s->dpy)),
646 				   InputOutput,
647 				   DefaultVisual(s->dpy, DefaultScreen(s->dpy)),
648 				   CWOverrideRedirect, &attr);
649 	XMapWindow(s->dpy, win);
650 	run(s->dpy, win, "sibling", s->options);
651 	return NULL;
652 }
653 
siblings(Display * dpy,int max_width,int max_height,int ncpus,unsigned options)654 static void siblings(Display *dpy,
655 		     int max_width, int max_height, int ncpus, unsigned options)
656 {
657 	int sq_ncpus = isqrt(ncpus);
658 	int width = max_width / sq_ncpus;
659 	int height = max_height/ sq_ncpus;
660 	struct sibling s[ncpus];
661 	int child;
662 
663 	if (ncpus <= 1)
664 		return;
665 
666 	for (child = 0; child < ncpus; child++) {
667 		s[child].dpy = dpy;
668 		s[child].x = (child % sq_ncpus) * width;
669 		s[child].y = (child / sq_ncpus) * height;
670 		s[child].width = width;
671 		s[child].height = height;
672 		s[child].options = options;
673 		pthread_create(&s[child].thread, NULL, sibling, &s[child]);
674 	}
675 
676 	for (child = 0; child < ncpus; child++)
677 		pthread_join(s[child].thread, NULL);
678 }
679 
cousins(int max_width,int max_height,int ncpus,unsigned options)680 static void cousins(int max_width, int max_height, int ncpus, unsigned options)
681 {
682 	int sq_ncpus = isqrt(ncpus);
683 	int width = max_width / sq_ncpus;
684 	int height = max_height/ sq_ncpus;
685 	int child;
686 
687 	if (ncpus <= 1)
688 		return;
689 
690 	for (child = 0; child < ncpus; child++) {
691 		for (; fork() == 0; exit(0)) {
692 			int x = (child % sq_ncpus) * width;
693 			int y = (child / sq_ncpus) * height;
694 			XSetWindowAttributes attr = { .override_redirect = 1 };
695 			Display *dpy = XOpenDisplay(NULL);
696 			Window win = XCreateWindow(dpy, DefaultRootWindow(dpy),
697 						   x, y, width, height, 0,
698 						   DefaultDepth(dpy, DefaultScreen(dpy)),
699 						   InputOutput,
700 						   DefaultVisual(dpy, DefaultScreen(dpy)),
701 						   CWOverrideRedirect, &attr);
702 			XMapWindow(dpy, win);
703 			run(dpy, win, "cousin", options);
704 		}
705 	}
706 
707 	while (child) {
708 		int status = -1;
709 		pid_t pid = wait(&status);
710 		if (pid == -1)
711 			continue;
712 		child--;
713 	}
714 }
715 
has_present(Display * dpy)716 static int has_present(Display *dpy)
717 {
718 	xcb_connection_t *c = XGetXCBConnection(dpy);
719 	xcb_generic_error_t *error = NULL;
720 	void *reply;
721 
722 	reply = xcb_present_query_version_reply(c,
723 						xcb_present_query_version(c,
724 									  XCB_PRESENT_MAJOR_VERSION,
725 									  XCB_PRESENT_MINOR_VERSION),
726 						&error);
727 
728 	free(reply);
729 	free(error);
730 	if (reply == NULL) {
731 		fprintf(stderr, "Present not supported on %s\n", DisplayString(dpy));
732 		return 0;
733 	}
734 
735 	return 1;
736 }
737 
has_composite(Display * dpy)738 static int has_composite(Display *dpy)
739 {
740 	int event, error;
741 	int major, minor;
742 
743 	if (!XDamageQueryExtension (dpy, &event, &error))
744 		return 0;
745 
746 	if (!XCompositeQueryExtension(dpy, &event, &error))
747 		return 0;
748 
749 	XCompositeQueryVersion(dpy, &major, &minor);
750 
751 	return major > 0 || minor >= 4;
752 }
753 
dri3_query_version(Display * dpy,int * major,int * minor)754 static int dri3_query_version(Display *dpy, int *major, int *minor)
755 {
756 	xcb_connection_t *c = XGetXCBConnection(dpy);
757 	xcb_dri3_query_version_reply_t *reply;
758 	xcb_generic_error_t *error;
759 
760 	*major = *minor = -1;
761 
762 	reply = xcb_dri3_query_version_reply(c,
763 					     xcb_dri3_query_version(c,
764 								    XCB_DRI3_MAJOR_VERSION,
765 								    XCB_DRI3_MINOR_VERSION),
766 					     &error);
767 	free(error);
768 	if (reply == NULL)
769 		return -1;
770 
771 	*major = reply->major_version;
772 	*minor = reply->minor_version;
773 	free(reply);
774 
775 	return 0;
776 }
777 
has_dri3(Display * dpy)778 static int has_dri3(Display *dpy)
779 {
780 	const xcb_query_extension_reply_t *ext;
781 	int major, minor;
782 
783 	ext = xcb_get_extension_data(XGetXCBConnection(dpy), &xcb_dri3_id);
784 	if (ext == NULL || !ext->present)
785 		return 0;
786 
787 	if (dri3_query_version(dpy, &major, &minor) < 0)
788 		return 0;
789 
790 	return major >= 0;
791 }
792 
has_xfixes(Display * dpy)793 static int has_xfixes(Display *dpy)
794 {
795 	xcb_connection_t *c = XGetXCBConnection(dpy);
796 	const xcb_query_extension_reply_t *ext;
797 	void *reply;
798 
799 	ext = xcb_get_extension_data(c, &xcb_xfixes_id);
800 	if (ext == NULL || !ext->present)
801 		return 0;
802 
803 	reply = xcb_xfixes_query_version_reply(c,
804 					       xcb_xfixes_query_version(c,
805 									XCB_XFIXES_MAJOR_VERSION,
806 									XCB_XFIXES_MINOR_VERSION),
807 					       NULL);
808 	free(reply);
809 
810 	return reply != NULL;
811 }
812 
_XRRGetScreenResourcesCurrent(Display * dpy,Window window)813 static inline XRRScreenResources *_XRRGetScreenResourcesCurrent(Display *dpy, Window window)
814 {
815 	XRRScreenResources *res;
816 
817 	res = XRRGetScreenResourcesCurrent(dpy, window);
818 	if (res == NULL)
819 		res = XRRGetScreenResources(dpy, window);
820 
821 	return res;
822 }
823 
lookup_mode(XRRScreenResources * res,int id)824 static XRRModeInfo *lookup_mode(XRRScreenResources *res, int id)
825 {
826 	int i;
827 
828 	for (i = 0; i < res->nmode; i++) {
829 		if (res->modes[i].id == id)
830 			return &res->modes[i];
831 	}
832 
833 	return NULL;
834 }
835 
fullscreen(Display * dpy,Window win)836 static void fullscreen(Display *dpy, Window win)
837 {
838 	Atom atom = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
839 	XChangeProperty(dpy, win,
840 			XInternAtom(dpy, "_NET_WM_STATE", False),
841 			XA_ATOM, 32, PropModeReplace,
842 			(unsigned char *)&atom, 1);
843 }
844 
loop(Display * dpy,XRRScreenResources * res,unsigned options)845 static void loop(Display *dpy, XRRScreenResources *res, unsigned options)
846 {
847 	Window root = DefaultRootWindow(dpy);
848 	Window win;
849 	XSetWindowAttributes attr;
850 	int i, j;
851 
852 	attr.override_redirect = 1;
853 
854 	run(dpy, root, "off", options);
855 	XSync(dpy, True);
856 
857 	for (i = 0; i < res->noutput; i++) {
858 		XRROutputInfo *output;
859 		XRRModeInfo *mode;
860 
861 		output = XRRGetOutputInfo(dpy, res, res->outputs[i]);
862 		if (output == NULL)
863 			continue;
864 
865 		mode = NULL;
866 		if (res->nmode)
867 			mode = lookup_mode(res, output->modes[0]);
868 
869 		for (j = 0; mode && j < 2*output->ncrtc; j++) {
870 			int c = j;
871 			if (c >= output->ncrtc)
872 				c = 2*output->ncrtc - j - 1;
873 
874 			printf("[%d, %d] -- OUTPUT:%ld, CRTC:%ld: %dx%d\n",
875 			       i, c, (long)res->outputs[i], (long)output->crtcs[c],
876 			       mode->width, mode->height);
877 			XRRSetCrtcConfig(dpy, res, output->crtcs[c], CurrentTime,
878 					 0, 0, output->modes[0], RR_Rotate_0, &res->outputs[i], 1);
879 
880 			run(dpy, root, "root", options);
881 			XSync(dpy, True);
882 
883 			win = XCreateWindow(dpy, root,
884 					    0, 0, mode->width, mode->height, 0,
885 					    DefaultDepth(dpy, DefaultScreen(dpy)),
886 					    InputOutput,
887 					    DefaultVisual(dpy, DefaultScreen(dpy)),
888 					    CWOverrideRedirect, &attr);
889 			fullscreen(dpy, win);
890 			XMapWindow(dpy, win);
891 			run(dpy, win, "fullscreen", options);
892 			XDestroyWindow(dpy, win);
893 			XSync(dpy, True);
894 
895 			win = XCreateWindow(dpy, root,
896 					    0, 0, mode->width, mode->height, 0,
897 					    DefaultDepth(dpy, DefaultScreen(dpy)),
898 					    InputOutput,
899 					    DefaultVisual(dpy, DefaultScreen(dpy)),
900 					    CWOverrideRedirect, &attr);
901 			XMapWindow(dpy, win);
902 			run(dpy, win, "windowed", options);
903 			XDestroyWindow(dpy, win);
904 			XSync(dpy, True);
905 
906 			if (has_composite(dpy)) {
907 				Damage damage;
908 
909 				_x_error_occurred = 0;
910 				win = XCreateWindow(dpy, root,
911 						    0, 0, mode->width, mode->height, 0,
912 						    DefaultDepth(dpy, DefaultScreen(dpy)),
913 						    InputOutput,
914 						    DefaultVisual(dpy, DefaultScreen(dpy)),
915 						    CWOverrideRedirect, &attr);
916 				XCompositeRedirectWindow(dpy, win, CompositeRedirectManual);
917 				damage = XDamageCreate(dpy, win, XDamageReportNonEmpty);
918 				XMapWindow(dpy, win);
919 				XSync(dpy, True);
920 				if (!_x_error_occurred)
921 					run(dpy, win, "composited", options);
922 				XDamageDestroy(dpy, damage);
923 				XDestroyWindow(dpy, win);
924 				XSync(dpy, True);
925 			}
926 
927 			win = XCreateWindow(dpy, root,
928 					    0, 0, mode->width/2, mode->height/2, 0,
929 					    DefaultDepth(dpy, DefaultScreen(dpy)),
930 					    InputOutput,
931 					    DefaultVisual(dpy, DefaultScreen(dpy)),
932 					    CWOverrideRedirect, &attr);
933 			XMapWindow(dpy, win);
934 			run(dpy, win, "half", options);
935 			XDestroyWindow(dpy, win);
936 			XSync(dpy, True);
937 
938 			perpixel(dpy, mode->width, mode->height, options);
939 
940 			siblings(dpy, mode->width, mode->height,
941 				 sysconf(_SC_NPROCESSORS_ONLN),
942 				 options);
943 
944 			cousins(mode->width, mode->height,
945 				sysconf(_SC_NPROCESSORS_ONLN),
946 				options);
947 
948 			XRRSetCrtcConfig(dpy, res, output->crtcs[c], CurrentTime,
949 					 0, 0, None, RR_Rotate_0, NULL, 0);
950 		}
951 
952 		XRRFreeOutputInfo(output);
953 	}
954 
955 }
956 
main(void)957 int main(void)
958 {
959 	Display *dpy;
960 	XRRScreenResources *res;
961 	XRRCrtcInfo **original_crtc;
962 	int i;
963 
964 	XInitThreads();
965 
966 	dpy = XOpenDisplay(NULL);
967 	if (dpy == NULL)
968 		return 77;
969 
970 	if (!has_present(dpy))
971 		return 77;
972 
973 	if (DPMSQueryExtension(dpy, &i, &i))
974 		DPMSDisable(dpy);
975 
976 	signal(SIGALRM, SIG_IGN);
977 	XSetErrorHandler(_check_error_handler);
978 
979 	res = NULL;
980 	if (XRRQueryVersion(dpy, &i, &i))
981 		res = _XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
982 	if (res == NULL)
983 		return 77;
984 
985 	original_crtc = malloc(sizeof(XRRCrtcInfo *)*res->ncrtc);
986 	for (i = 0; i < res->ncrtc; i++)
987 		original_crtc[i] = XRRGetCrtcInfo(dpy, res, res->crtcs[i]);
988 
989 	printf("noutput=%d, ncrtc=%d\n", res->noutput, res->ncrtc);
990 	for (i = 0; i < res->ncrtc; i++)
991 		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
992 				 0, 0, None, RR_Rotate_0, NULL, 0);
993 
994 	loop(dpy, res, 0);
995 	loop(dpy, res, ASYNC);
996 	if (has_xfixes(dpy))
997 		loop(dpy, res, NOCOPY);
998 	if (has_dri3(dpy)) {
999 		loop(dpy, res, DRI3);
1000 		loop(dpy, res, DRI3 | ASYNC);
1001 	}
1002 
1003 	for (i = 0; i < res->ncrtc; i++)
1004 		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
1005 				 original_crtc[i]->x,
1006 				 original_crtc[i]->y,
1007 				 original_crtc[i]->mode,
1008 				 original_crtc[i]->rotation,
1009 				 original_crtc[i]->outputs,
1010 				 original_crtc[i]->noutput);
1011 
1012 	if (DPMSQueryExtension(dpy, &i, &i))
1013 		DPMSEnable(dpy);
1014 	return 0;
1015 }
1016