dri2-swap.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/Xutil.h>
33#include <X11/Xlibint.h>
34#include <X11/extensions/dpms.h>
35#include <X11/extensions/randr.h>
36#include <X11/extensions/Xcomposite.h>
37#include <X11/extensions/Xdamage.h>
38#include <X11/extensions/Xrandr.h>
39#include <xcb/xcb.h>
40#include <xcb/dri2.h>
41#include <xf86drm.h>
42
43#include <stdio.h>
44#include <string.h>
45#include <fcntl.h>
46#include <unistd.h>
47#include <assert.h>
48#include <errno.h>
49#include <setjmp.h>
50#include <signal.h>
51
52#include <X11/Xlibint.h>
53#include <X11/extensions/Xext.h>
54#include <X11/extensions/extutil.h>
55#include <X11/extensions/dri2proto.h>
56#include <X11/extensions/dri2tokens.h>
57#include <X11/extensions/Xfixes.h>
58
59static char dri2ExtensionName[] = DRI2_NAME;
60static XExtensionInfo *dri2Info;
61static XEXT_GENERATE_CLOSE_DISPLAY (DRI2CloseDisplay, dri2Info)
62
63static Bool
64DRI2WireToEvent(Display *dpy, XEvent *event, xEvent *wire);
65static Status
66DRI2EventToWire(Display *dpy, XEvent *event, xEvent *wire);
67static int
68DRI2Error(Display *display, xError *err, XExtCodes *codes, int *ret_code);
69
70static /* const */ XExtensionHooks dri2ExtensionHooks = {
71  NULL,                   /* create_gc */
72  NULL,                   /* copy_gc */
73  NULL,                   /* flush_gc */
74  NULL,                   /* free_gc */
75  NULL,                   /* create_font */
76  NULL,                   /* free_font */
77  DRI2CloseDisplay,       /* close_display */
78  DRI2WireToEvent,        /* wire_to_event */
79  DRI2EventToWire,        /* event_to_wire */
80  DRI2Error,              /* error */
81  NULL,                   /* error_string */
82};
83
84static XEXT_GENERATE_FIND_DISPLAY (DRI2FindDisplay,
85                                   dri2Info,
86                                   dri2ExtensionName,
87                                   &dri2ExtensionHooks,
88                                   0, NULL)
89
90static Bool
91DRI2WireToEvent(Display *dpy, XEvent *event, xEvent *wire)
92{
93   XExtDisplayInfo *info = DRI2FindDisplay(dpy);
94
95   XextCheckExtension(dpy, info, dri2ExtensionName, False);
96
97   switch ((wire->u.u.type & 0x7f) - info->codes->first_event) {
98#ifdef X_DRI2SwapBuffers
99   case DRI2_BufferSwapComplete:
100      return False;
101#endif
102#ifdef DRI2_InvalidateBuffers
103   case DRI2_InvalidateBuffers:
104      return False;
105#endif
106   default:
107      /* client doesn't support server event */
108      break;
109   }
110
111   return False;
112}
113
114/* We don't actually support this.  It doesn't make sense for clients to
115 * send each other DRI2 events.
116 */
117static Status
118DRI2EventToWire(Display *dpy, XEvent *event, xEvent *wire)
119{
120   XExtDisplayInfo *info = DRI2FindDisplay(dpy);
121
122   XextCheckExtension(dpy, info, dri2ExtensionName, False);
123
124   switch (event->type) {
125   default:
126      /* client doesn't support server event */
127      break;
128   }
129
130   return Success;
131}
132
133static int
134DRI2Error(Display *display, xError *err, XExtCodes *codes, int *ret_code)
135{
136	if (err->majorCode == codes->major_opcode &&
137	    err->errorCode == BadDrawable &&
138	    err->minorCode == X_DRI2CopyRegion)
139		return True;
140
141	/* If the X drawable was destroyed before the GLX drawable, the
142	 * DRI2 drawble will be gone by the time we call
143	 * DRI2DestroyDrawable.  So just ignore BadDrawable here. */
144	if (err->majorCode == codes->major_opcode &&
145	    err->errorCode == BadDrawable &&
146	    err->minorCode == X_DRI2DestroyDrawable)
147		return True;
148
149	/* If the server is non-local DRI2Connect will raise BadRequest.
150	 * Swallow this so that DRI2Connect can signal this in its return code */
151	if (err->majorCode == codes->major_opcode &&
152	    err->minorCode == X_DRI2Connect &&
153	    err->errorCode == BadRequest) {
154		*ret_code = False;
155		return True;
156	}
157
158	return False;
159}
160
161static Bool
162DRI2QueryExtension(Display * dpy, int *eventBase, int *errorBase)
163{
164	XExtDisplayInfo *info = DRI2FindDisplay(dpy);
165
166	if (XextHasExtension(info)) {
167		*eventBase = info->codes->first_event;
168		*errorBase = info->codes->first_error;
169		return True;
170	}
171
172	return False;
173}
174
175static Bool
176DRI2Connect(Display * dpy, XID window, char **driverName, char **deviceName)
177{
178	XExtDisplayInfo *info = DRI2FindDisplay(dpy);
179	xDRI2ConnectReply rep;
180	xDRI2ConnectReq *req;
181
182	XextCheckExtension(dpy, info, dri2ExtensionName, False);
183
184	LockDisplay(dpy);
185	GetReq(DRI2Connect, req);
186	req->reqType = info->codes->major_opcode;
187	req->dri2ReqType = X_DRI2Connect;
188	req->window = window;
189	req->driverType = DRI2DriverDRI;
190	if (!_XReply(dpy, (xReply *) & rep, 0, xFalse)) {
191		UnlockDisplay(dpy);
192		SyncHandle();
193		return False;
194	}
195
196	if (rep.driverNameLength == 0 && rep.deviceNameLength == 0) {
197		UnlockDisplay(dpy);
198		SyncHandle();
199		return False;
200	}
201
202	*driverName = Xmalloc(rep.driverNameLength + 1);
203	if (*driverName == NULL) {
204		_XEatData(dpy,
205			  ((rep.driverNameLength + 3) & ~3) +
206			  ((rep.deviceNameLength + 3) & ~3));
207		UnlockDisplay(dpy);
208		SyncHandle();
209		return False;
210	}
211	_XReadPad(dpy, *driverName, rep.driverNameLength);
212	(*driverName)[rep.driverNameLength] = '\0';
213
214	*deviceName = Xmalloc(rep.deviceNameLength + 1);
215	if (*deviceName == NULL) {
216		Xfree(*driverName);
217		_XEatData(dpy, ((rep.deviceNameLength + 3) & ~3));
218		UnlockDisplay(dpy);
219		SyncHandle();
220		return False;
221	}
222	_XReadPad(dpy, *deviceName, rep.deviceNameLength);
223	(*deviceName)[rep.deviceNameLength] = '\0';
224
225	UnlockDisplay(dpy);
226	SyncHandle();
227
228	return True;
229}
230
231static Bool
232DRI2Authenticate(Display * dpy, XID window, unsigned int magic)
233{
234	XExtDisplayInfo *info = DRI2FindDisplay(dpy);
235	xDRI2AuthenticateReq *req;
236	xDRI2AuthenticateReply rep;
237
238	XextCheckExtension(dpy, info, dri2ExtensionName, False);
239
240	LockDisplay(dpy);
241	GetReq(DRI2Authenticate, req);
242	req->reqType = info->codes->major_opcode;
243	req->dri2ReqType = X_DRI2Authenticate;
244	req->window = window;
245	req->magic = magic;
246
247	if (!_XReply(dpy, (xReply *) & rep, 0, xFalse)) {
248		UnlockDisplay(dpy);
249		SyncHandle();
250		return False;
251	}
252
253	UnlockDisplay(dpy);
254	SyncHandle();
255
256	return rep.authenticated;
257}
258
259static void
260DRI2CreateDrawable(Display * dpy, XID drawable)
261{
262	XExtDisplayInfo *info = DRI2FindDisplay(dpy);
263	xDRI2CreateDrawableReq *req;
264
265	XextSimpleCheckExtension(dpy, info, dri2ExtensionName);
266
267	LockDisplay(dpy);
268	GetReq(DRI2CreateDrawable, req);
269	req->reqType = info->codes->major_opcode;
270	req->dri2ReqType = X_DRI2CreateDrawable;
271	req->drawable = drawable;
272	UnlockDisplay(dpy);
273	SyncHandle();
274}
275
276static void DRI2SwapInterval(Display *dpy, XID drawable, int interval)
277{
278    XExtDisplayInfo *info = DRI2FindDisplay(dpy);
279    xDRI2SwapIntervalReq *req;
280
281    XextSimpleCheckExtension (dpy, info, dri2ExtensionName);
282
283    LockDisplay(dpy);
284    GetReq(DRI2SwapInterval, req);
285    req->reqType = info->codes->major_opcode;
286    req->dri2ReqType = X_DRI2SwapInterval;
287    req->drawable = drawable;
288    req->interval = interval;
289    UnlockDisplay(dpy);
290    SyncHandle();
291}
292
293static int _x_error_occurred;
294
295static int
296_check_error_handler(Display     *display,
297		     XErrorEvent *event)
298{
299	fprintf(stderr,
300		"X11 error from display %s, serial=%ld, error=%d, req=%d.%d\n",
301	       DisplayString(display),
302	       event->serial,
303	       event->error_code,
304	       event->request_code,
305	       event->minor_code);
306	_x_error_occurred++;
307	return False; /* ignored */
308}
309
310static double elapsed(const struct timespec *start,
311		      const struct timespec *end)
312{
313	return 1e6*(end->tv_sec - start->tv_sec) + (end->tv_nsec - start->tv_nsec)/1000;
314}
315
316static void run(Display *dpy, Window win)
317{
318	xcb_connection_t *c = XGetXCBConnection(dpy);
319	struct timespec start, end;
320	int n, completed = 0;
321
322	clock_gettime(CLOCK_MONOTONIC, &start);
323	do {
324		for (n = 0; n < 1000; n++) {
325			unsigned int attachments[] = { DRI2BufferBackLeft };
326			unsigned int seq[2];
327
328			seq[0] = xcb_dri2_swap_buffers_unchecked(c, win,
329								 0, 0, 0, 0, 0, 0).sequence;
330
331
332			seq[1] = xcb_dri2_get_buffers_unchecked(c, win,
333								1, 1, attachments).sequence;
334
335			xcb_flush(c);
336			xcb_discard_reply(c, seq[0]);
337			xcb_discard_reply(c, seq[1]);
338			completed++;
339		}
340		clock_gettime(CLOCK_MONOTONIC, &end);
341	} while (end.tv_sec < start.tv_sec + 10);
342
343	printf("%f\n", completed / (elapsed(&start, &end) / 1000000));
344}
345
346static inline XRRScreenResources *_XRRGetScreenResourcesCurrent(Display *dpy, Window window)
347{
348	XRRScreenResources *res;
349
350	res = XRRGetScreenResourcesCurrent(dpy, window);
351	if (res == NULL)
352		res = XRRGetScreenResources(dpy, window);
353
354	return res;
355}
356
357static XRRModeInfo *lookup_mode(XRRScreenResources *res, int id)
358{
359	int i;
360
361	for (i = 0; i < res->nmode; i++) {
362		if (res->modes[i].id == id)
363			return &res->modes[i];
364	}
365
366	return NULL;
367}
368
369static int dri2_open(Display *dpy)
370{
371	drm_auth_t auth;
372	char *driver, *device;
373	int fd;
374
375	if (!DRI2QueryExtension(dpy, &fd, &fd))
376		return -1;
377
378	if (!DRI2Connect(dpy, DefaultRootWindow(dpy), &driver, &device))
379		return -1;
380
381	fd = open(device, O_RDWR);
382	if (fd < 0)
383		return -1;
384
385	if (drmIoctl(fd, DRM_IOCTL_GET_MAGIC, &auth))
386		return -1;
387
388	if (!DRI2Authenticate(dpy, DefaultRootWindow(dpy), auth.magic))
389		return -1;
390
391	return fd;
392}
393
394static void fullscreen(Display *dpy, Window win)
395{
396	Atom atom = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
397	XChangeProperty(dpy, win,
398			XInternAtom(dpy, "_NET_WM_STATE", False),
399			XA_ATOM, 32, PropModeReplace,
400			(unsigned char *)&atom, 1);
401}
402
403static int has_composite(Display *dpy)
404{
405	int event, error;
406	int major, minor;
407
408	if (!XDamageQueryExtension (dpy, &event, &error))
409		return 0;
410
411	if (!XCompositeQueryExtension(dpy, &event, &error))
412		return 0;
413
414	XCompositeQueryVersion(dpy, &major, &minor);
415
416	return major > 0 || minor >= 4;
417}
418
419int main(int argc, char **argv)
420{
421	Display *dpy;
422	Window root, win;
423	XRRScreenResources *res;
424	XRRCrtcInfo **original_crtc;
425	XSetWindowAttributes attr;
426	enum window { ROOT, FULLSCREEN, WINDOW } w = FULLSCREEN;
427	enum visible {REDIRECTED, NORMAL } v = NORMAL;
428	enum display { OFF, ON } d = OFF;
429	int width, height;
430	int i, fd;
431	int c;
432
433	while ((c = getopt(argc, argv, "d:v:w:")) != -1) {
434		switch (c) {
435		case 'd':
436			if (strcmp(optarg, "off") == 0)
437				d = OFF;
438			else if (strcmp(optarg, "on") == 0)
439				d = ON;
440			else
441				abort();
442			break;
443
444		case 'v':
445			if (strcmp(optarg, "redirected") == 0)
446				v = REDIRECTED;
447			else if (strcmp(optarg, "normal") == 0)
448				v = NORMAL;
449			else
450				abort();
451			break;
452
453		case 'w':
454			if (strcmp(optarg, "fullscreen") == 0)
455				w = FULLSCREEN;
456			else if (strcmp(optarg, "window") == 0)
457				w = WINDOW;
458			else if (strcmp(optarg, "root") == 0)
459				w = ROOT;
460			else
461				abort();
462			break;
463		}
464	}
465
466	attr.override_redirect = 1;
467
468	dpy = XOpenDisplay(NULL);
469	if (dpy == NULL)
470		return 77;
471
472	width = DisplayWidth(dpy, DefaultScreen(dpy));
473	height = DisplayHeight(dpy, DefaultScreen(dpy));
474
475	fd = dri2_open(dpy);
476	if (fd < 0)
477		return 77;
478
479	if (DPMSQueryExtension(dpy, &i, &i))
480		DPMSDisable(dpy);
481
482	root = DefaultRootWindow(dpy);
483
484	signal(SIGALRM, SIG_IGN);
485	XSetErrorHandler(_check_error_handler);
486
487	res = NULL;
488	if (XRRQueryVersion(dpy, &i, &i))
489		res = _XRRGetScreenResourcesCurrent(dpy, root);
490	if (res == NULL)
491		return 77;
492
493	if (v == REDIRECTED && !has_composite(dpy))
494		return 77;
495
496	original_crtc = malloc(sizeof(XRRCrtcInfo *)*res->ncrtc);
497	for (i = 0; i < res->ncrtc; i++)
498		original_crtc[i] = XRRGetCrtcInfo(dpy, res, res->crtcs[i]);
499
500	for (i = 0; i < res->ncrtc; i++)
501		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
502				 0, 0, None, RR_Rotate_0, NULL, 0);
503
504	DRI2CreateDrawable(dpy, root);
505	DRI2SwapInterval(dpy, root, 0);
506
507	if (d != OFF) {
508		for (i = 0; i < res->noutput; i++) {
509			XRROutputInfo *output;
510			XRRModeInfo *mode;
511
512			output = XRRGetOutputInfo(dpy, res, res->outputs[i]);
513			if (output == NULL)
514				continue;
515
516			mode = NULL;
517			if (res->nmode)
518				mode = lookup_mode(res, output->modes[0]);
519			if (mode == NULL)
520				continue;
521
522			XRRSetCrtcConfig(dpy, res, output->crtcs[0], CurrentTime,
523					 0, 0, output->modes[0], RR_Rotate_0, &res->outputs[i], 1);
524			width = mode->width;
525			height = mode->height;
526			break;
527		}
528		if (i == res->noutput) {
529			_x_error_occurred = 77;
530			goto restore;
531		}
532	}
533
534	if (w == ROOT) {
535		run(dpy, root);
536	} else if (w == FULLSCREEN) {
537		win = XCreateWindow(dpy, root,
538				    0, 0, width, height, 0,
539				    DefaultDepth(dpy, DefaultScreen(dpy)),
540				    InputOutput,
541				    DefaultVisual(dpy, DefaultScreen(dpy)),
542				    CWOverrideRedirect, &attr);
543		DRI2CreateDrawable(dpy, win);
544		DRI2SwapInterval(dpy, win, 0);
545		if (v == REDIRECTED) {
546			XCompositeRedirectWindow(dpy, win, CompositeRedirectManual);
547			XDamageCreate(dpy, win, XDamageReportRawRectangles);
548		} else
549			fullscreen(dpy, win);
550		XMapWindow(dpy, win);
551		run(dpy, win);
552	} else if (w == WINDOW) {
553		win = XCreateWindow(dpy, root,
554				    0, 0, width/2, height/2, 0,
555				    DefaultDepth(dpy, DefaultScreen(dpy)),
556				    InputOutput,
557				    DefaultVisual(dpy, DefaultScreen(dpy)),
558				    CWOverrideRedirect, &attr);
559		DRI2CreateDrawable(dpy, win);
560		DRI2SwapInterval(dpy, win, 0);
561		if (v == REDIRECTED) {
562			XCompositeRedirectWindow(dpy, win, CompositeRedirectManual);
563			XDamageCreate(dpy, win, XDamageReportRawRectangles);
564		}
565		XMapWindow(dpy, win);
566		run(dpy, win);
567	}
568
569restore:
570	for (i = 0; i < res->ncrtc; i++)
571		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
572				 0, 0, None, RR_Rotate_0, NULL, 0);
573
574	for (i = 0; i < res->ncrtc; i++)
575		XRRSetCrtcConfig(dpy, res, res->crtcs[i], CurrentTime,
576				 original_crtc[i]->x,
577				 original_crtc[i]->y,
578				 original_crtc[i]->mode,
579				 original_crtc[i]->rotation,
580				 original_crtc[i]->outputs,
581				 original_crtc[i]->noutput);
582
583	if (DPMSQueryExtension(dpy, &i, &i))
584		DPMSEnable(dpy);
585
586	XSync(dpy, True);
587	return _x_error_occurred;
588}
589