present-speed.c revision fe8aea9e
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
59static int _x_error_occurred;
60static uint32_t stamp;
61
62struct list {
63    struct list *next, *prev;
64};
65
66static void
67list_init(struct list *list)
68{
69    list->next = list->prev = list;
70}
71
72static inline void
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
83static inline void
84list_add(struct list *entry, struct list *head)
85{
86    __list_add(entry, head, head->next);
87}
88
89static inline void
90__list_del(struct list *prev, struct list *next)
91{
92	next->prev = prev;
93	prev->next = next;
94}
95
96static inline void
97_list_del(struct list *entry)
98{
99    __list_del(entry->prev, entry->next);
100}
101
102static inline void
103list_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
119static int
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
136static 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
142struct 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
154static 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, *b = NULL;
230retry:
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;
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
375struct 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
384static 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, *b = NULL;
468retry:
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;
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
622static int isqrt(int x)
623{
624	int i;
625
626	for (i = 2; i*i < x; i++)
627		;
628	return i;
629}
630
631struct sibling {
632	pthread_t thread;
633	Display *dpy;
634	int x, y;
635	int width, height;
636	unsigned options;
637};
638
639static 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
654static 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
680static 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
716static 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
738static 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
754static 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
778static 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
793static 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
813static 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
824static 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
836static 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
845static 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
957int 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