c1f4b8732b9b381fef4589b99cb4c5a3db17fa15
[retrobench] / src / x11 / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <signal.h>
5 #include <sys/time.h>
6 #include <sys/ipc.h>
7 #include <sys/shm.h>
8 #include <X11/Xlib.h>
9 #include <X11/Xutil.h>
10 #include <X11/keysym.h>
11 #include <X11/extensions/XShm.h>
12 #include "rbench.h"
13 #include "util.h"
14
15 enum { QUIT = 1, REDRAW = 2 };
16
17 static Window create_win(int width, int height, int bpp);
18 static void handle_event(XEvent *ev);
19 static int translate_keysym(KeySym sym);
20 static int parse_args(int argc, char **argv);
21 static void sig(int s);
22
23 static int win_width, win_height;
24 static int mapped;
25 static unsigned int pending;
26 static Display *dpy;
27 static Window win, root;
28 static GC gc;
29 static Visual *vis;
30 static Atom xa_wm_proto, xa_wm_delwin;
31
32 static XImage *ximg;
33 static XShmSegmentInfo shm;
34 static int wait_putimg;
35 static int xshm_ev_completion;
36
37 int main(int argc, char **argv)
38 {
39         int num_frames = 0;
40         XEvent ev;
41         struct timeval tv, tv0;
42
43         shm.shmid = -1;
44         shm.shmaddr = (void*)-1;
45
46         signal(SIGINT, sig);
47
48         read_config("rbench.cfg");
49
50         if(parse_args(argc, argv) == -1) {
51                 return 1;
52         }
53
54         if(!(dpy = XOpenDisplay(0))) {
55                 fprintf(stderr, "failed to connect to the X server\n");
56                 return 1;
57         }
58         root = DefaultRootWindow(dpy);
59         xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", 0);
60         xa_wm_delwin = XInternAtom(dpy, "WM_DELETE_WINDOW", 0);
61
62         if(!XShmQueryExtension(dpy)) {
63                 fprintf(stderr, "X shared memory extension is not available\n");
64                 XCloseDisplay(dpy);
65                 return 1;
66         }
67         xshm_ev_completion = XShmGetEventBase(dpy) + ShmCompletion;
68
69         if(!(win = create_win(opt.width, opt.height, opt.bpp))) {
70                 return 1;
71         }
72         gc = XCreateGC(dpy, win, 0, 0);
73
74         if(!(ximg = XShmCreateImage(dpy, vis, opt.bpp, ZPixmap, 0, &shm, opt.width, opt.height))) {
75                 fprintf(stderr, "failed to create shared memory image\n");
76                 goto end;
77         }
78         if((shm.shmid = shmget(IPC_PRIVATE, ximg->bytes_per_line * ximg->height, IPC_CREAT | 0777)) == -1) {
79                 fprintf(stderr, "failed to create shared memory block\n");
80                 goto end;
81         }
82         if((shm.shmaddr = ximg->data = shmat(shm.shmid, 0, 0)) == (void*)-1) {
83                 fprintf(stderr, "failed to attach shared memory block\n");
84                 goto end;
85         }
86         shm.readOnly = True;
87         if(!XShmAttach(dpy, &shm)) {
88                 fprintf(stderr, "XShmAttach failed");
89                 goto end;
90         }
91
92         fb_width = opt.width;
93         fb_height = opt.height;
94         if(opt.bpp >= 24) {
95                 fb_bpp = ximg->bytes_per_line < fb_width * 4 ? 24 : 32;
96         } else {
97                 fb_bpp = opt.bpp;
98         }
99         framebuf = ximg->data;
100         fb_pitch = ximg->bytes_per_line;
101         fb_rmask = ximg->red_mask;
102         fb_gmask = ximg->green_mask;
103         fb_bmask = ximg->blue_mask;
104         fb_rshift = mask_to_shift(fb_rmask);
105         fb_gshift = mask_to_shift(fb_gmask);
106         fb_bshift = mask_to_shift(fb_bmask);
107
108         if(init() == -1) {
109                 goto end;
110         }
111
112         gettimeofday(&tv0, 0);
113
114         while(!(pending & QUIT)) {
115                 if(mapped) {/* && !wait_putimg) { */
116                         while(XPending(dpy)) {
117                                 XNextEvent(dpy, &ev);
118                                 handle_event(&ev);
119                                 if(pending & QUIT) goto end;
120                         }
121
122                         if(!wait_putimg) {
123                                 gettimeofday(&tv, 0);
124                                 time_msec = (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
125                                 num_frames++;
126
127                                 redraw();
128
129                                 XShmPutImage(dpy, win, gc, ximg, 0, 0, 0, 0, ximg->width, ximg->height, True);
130                                 wait_putimg = 1;
131                         }
132                 } else {
133                         XNextEvent(dpy, &ev);
134                         handle_event(&ev);
135                         if(pending & QUIT) goto end;
136                 }
137         }
138
139 end:
140         cleanup();
141         if(ximg) {
142                 XShmDetach(dpy, &shm);
143                 XDestroyImage(ximg);
144                 if(shm.shmaddr != (void*)-1) {
145                         shmdt(shm.shmaddr);
146                 }
147                 if(shm.shmid != -1) {
148                         shmctl(shm.shmid, IPC_RMID, 0);
149                 }
150         }
151         if(win) {
152                 XFreeGC(dpy, gc);
153                 XDestroyWindow(dpy, win);
154         }
155         XCloseDisplay(dpy);
156
157         if(num_frames) {
158                 printf("avg framerate: %.1f fps\n", (10000 * num_frames / time_msec) / 10.0f);
159         }
160         return 0;
161 }
162
163 static Window create_win(int width, int height, int bpp)
164 {
165         int scr, num_vis;
166         Window win;
167         XVisualInfo *vinf, vtmpl;
168         unsigned int vinf_mask;
169         XSetWindowAttributes xattr;
170         XTextProperty txname;
171         Colormap cmap;
172         const char *name = "retrobench X11";
173
174         scr = DefaultScreen(dpy);
175
176         vtmpl.screen = scr;
177         vtmpl.depth = bpp;
178         vtmpl.class = bpp <= 8 ? PseudoColor : TrueColor;
179         vinf_mask = VisualScreenMask | VisualDepthMask | VisualClassMask;
180         if(!(vinf = XGetVisualInfo(dpy, vinf_mask, &vtmpl, &num_vis))) {
181                 fprintf(stderr, "failed to find appropriate visual for %d bpp\n", bpp);
182                 return 0;
183         }
184         vis = vinf->visual;
185
186         if(!(cmap = XCreateColormap(dpy, root, vis, bpp <= 8 ? AllocAll : AllocNone))) {
187                 fprintf(stderr, "failed to allocate colormap\n");
188                 return 0;
189         }
190
191         xattr.background_pixel = BlackPixel(dpy, scr);
192         xattr.colormap = cmap;
193         xattr.override_redirect = True;
194         win = XCreateWindow(dpy, root, 0, 0, width, height, 0, vinf->depth,
195                         InputOutput, vis, CWColormap | CWBackPixel | CWOverrideRedirect, &xattr);
196         if(!win) return 0;
197
198         XSelectInput(dpy, win, StructureNotifyMask | ExposureMask | KeyPressMask |
199                         KeyReleaseMask);
200
201         XStringListToTextProperty((char**)&name, 1, &txname);
202         XSetWMName(dpy, win, &txname);
203         XSetWMIconName(dpy, win, &txname);
204         XFree(txname.value);
205
206         XSetWMProtocols(dpy, win, &xa_wm_delwin, 1);
207
208         XMapWindow(dpy, win);
209         return win;
210 }
211
212 static void handle_event(XEvent *ev)
213 {
214         int key;
215         KeySym sym;
216
217         switch(ev->type) {
218         case MapNotify:
219                 mapped = 1;
220                 break;
221
222         case UnmapNotify:
223                 mapped = 0;
224                 break;
225
226         case Expose:
227                 pending |= REDRAW;
228                 break;
229
230         case ConfigureNotify:
231                 if(ev->xconfigure.width != win_width || ev->xconfigure.height != win_height) {
232                         win_width = ev->xconfigure.width;
233                         win_height = ev->xconfigure.height;
234                         /* TODO */
235                 }
236                 break;
237
238         case KeyPress:
239         case KeyRelease:
240                 if((sym = XKeycodeToKeysym(dpy, ev->xkey.keycode, 0))) {
241                         if(sym == XK_Escape) {
242                                 pending |= QUIT;
243                                 break;
244                         }
245                         if((key = translate_keysym(sym))) {
246                                 key_event(key, ev->xkey.type == KeyPress);
247                         }
248                 }
249                 break;
250
251         case ClientMessage:
252                 if(ev->xclient.message_type == xa_wm_proto) {
253                         if(ev->xclient.data.l[0] == xa_wm_delwin) {
254                                 pending |= QUIT;
255                         }
256                 }
257                 break;
258
259         default:
260                 if(ev->type == xshm_ev_completion) {
261                         wait_putimg = 0;
262                 }
263                 break;
264         }
265 }
266
267 static int translate_keysym(KeySym sym)
268 {
269         if(sym < 128) return sym;
270
271         switch(sym) {
272         case XK_Escape:
273                 return 27;
274
275         default:
276                 break;
277         }
278         return 0;
279 }
280
281 static const char *usage_str =
282         "Usage: %s [options]\n"
283         "Options:\n"
284         "  -s <WxH>: resolution\n"
285         "  -b <bpp>: color depth\n"
286         "  -h: print usage and exit\n";
287
288 static int parse_args(int argc, char **argv)
289 {
290         int i;
291
292         for(i=1; i<argc; i++) {
293                 if(argv[i][0] == '-') {
294                         if(argv[i][2]) {
295                                 goto inval_arg;
296                         }
297                         switch(argv[i][1]) {
298                         case 's':
299                                 if(!argv[++i] || sscanf(argv[i], "%dx%d", &opt.width, &opt.height) != 2) {
300                                         fprintf(stderr, "-s must be followed by WxH\n");
301                                         return -1;
302                                 }
303                                 break;
304
305                         case 'b':
306                                 if(!argv[++i] || !(opt.bpp = atoi(argv[i]))) {
307                                         fprintf(stderr, "-b must be followed by the color depth\n");
308                                         return -1;
309                                 }
310                                 break;
311
312                         case 'h':
313                                 printf(usage_str, argv[0]);
314                                 exit(0);
315
316                         default:
317                                 goto inval_arg;
318                         }
319                 } else {
320 inval_arg:      fprintf(stderr, "invalid argument: %s\n", argv[i]);
321                         return -1;
322                 }
323         }
324         return 0;
325 }
326
327 static void sig(int s)
328 {
329         pending |= QUIT;
330 }