eqemu

view src/main.cc @ 9:fca1f126d23b

fixed message pump
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 18 Jul 2014 04:47:27 +0300
parents e9ab4861536d
children 2b559dc24c7b
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <float.h>
4 #include <assert.h>
5 #include <errno.h>
6 #include <unistd.h>
7 #include <sys/select.h>
8 #include <GL/glew.h>
9 #include <X11/Xlib.h>
10 #include <GL/glx.h>
11 #include "dev.h"
12 #include "scene.h"
13 #include "timer.h"
14 #include "fblur.h"
17 enum {
18 REGULAR_PASS,
19 GLOW_PASS
20 };
22 static bool init();
23 static void cleanup();
24 static void display();
25 static void draw_scene(int pass = REGULAR_PASS);
26 static void post_glow(void);
27 static void keyb(int key, bool pressed);
28 static void mouse(int bn, bool pressed, int x, int y);
29 static void motion(int x, int y);
30 static Ray calc_pick_ray(int x, int y);
31 static int next_pow2(int x);
33 static Window create_window(const char *title, int xsz, int ysz);
34 static void process_events();
35 static int translate_keysym(KeySym sym);
37 static int proc_args(int argc, char **argv);
39 static Display *dpy;
40 static Window win;
41 static GLXContext ctx;
42 static Atom xa_wm_prot, xa_wm_del_win;
44 static int win_width, win_height;
46 static bool draw_pending;
47 static bool win_mapped;
49 static int fakefd = -1;
50 static char *fake_devpath;
52 static float cam_theta, cam_phi, cam_dist = 140;
53 static Scene *scn;
55 enum { BN_TICKET, BN_NEXT, NUM_BUTTONS };
56 static const char *button_names[] = { "button1", "button2" };
57 static Object *button_obj[NUM_BUTTONS];
58 static Object *disp_obj[2];
59 static Object *led_obj[2];
60 static Vector3 led_on_emissive;
62 static bool opt_use_glow = true;
63 #define GLOW_SZ_DIV 3
64 static unsigned int glow_tex;
65 static int glow_tex_xsz, glow_tex_ysz, glow_xsz, glow_ysz;
66 static int glow_iter = 1;
67 static int blur_size = 5;
68 unsigned char *glow_framebuf;
71 int main(int argc, char **argv)
72 {
73 if(proc_args(argc, argv) == -1) {
74 return 1;
75 }
76 if(!init()) {
77 return 1;
78 }
79 atexit(cleanup);
81 int xfd = ConnectionNumber(dpy);
83 // run once through pending events before going into the select loop
84 process_events();
86 for(;;) {
87 fd_set rd;
88 FD_ZERO(&rd);
90 FD_SET(xfd, &rd);
91 FD_SET(fakefd, &rd);
93 struct timeval noblock = {0, 0};
94 int maxfd = xfd > fakefd ? xfd : fakefd;
95 while(!XPending(dpy) && select(maxfd + 1, &rd, 0, 0, draw_pending ? &noblock : 0) == -1 && errno == EINTR);
97 if(XPending(dpy) || FD_ISSET(xfd, &rd)) {
98 process_events();
99 }
100 if(FD_ISSET(fakefd, &rd)) {
101 proc_dev_input();
102 }
104 if(draw_pending) {
105 draw_pending = false;
106 display();
107 }
108 }
109 return 0;
110 }
112 static bool init()
113 {
114 if(fake_devpath) {
115 if((fakefd = start_dev(fake_devpath)) == -1) {
116 return false;
117 }
118 }
120 if(!(dpy = XOpenDisplay(0))) {
121 fprintf(stderr, "failed to connect to the X server!\n");
122 return false;
123 }
125 if(!(win = create_window("equeue device emulator", 512, 512))) {
126 return false;
127 }
129 glewInit();
131 scn = new Scene;
132 if(!scn->load("data/device.obj")) {
133 fprintf(stderr, "failed to load device 3D model\n");
134 return false;
135 }
137 for(int i=0; i<NUM_BUTTONS; i++) {
138 button_obj[i] = scn->get_object(button_names[i]);
139 if(!button_obj[i]) {
140 fprintf(stderr, "invalid 3D model\n");
141 return false;
142 }
143 BSphere &bs = button_obj[i]->get_mesh()->get_bounds();
144 bs.set_radius(bs.get_radius() * 1.5);
145 }
147 disp_obj[0] = scn->get_object("7seg0");
148 disp_obj[1] = scn->get_object("7seg1");
149 if(!disp_obj[0] || !disp_obj[1]) {
150 fprintf(stderr, "invalid 3D model\n");
151 return false;
152 }
153 scn->remove_object(disp_obj[0]);
154 scn->remove_object(disp_obj[1]);
156 led_obj[0] = scn->get_object("led1");
157 led_obj[1] = scn->get_object("led2");
158 if(!led_obj[0] || !led_obj[1]) {
159 fprintf(stderr, "invalid 3D model\n");
160 return false;
161 }
162 scn->remove_object(led_obj[0]);
163 scn->remove_object(led_obj[1]);
164 led_on_emissive = led_obj[0]->mtl.emissive;
166 // create the glow texture
167 glGenTextures(1, &glow_tex);
168 glBindTexture(GL_TEXTURE_2D, glow_tex);
169 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
170 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
171 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
173 glEnable(GL_DEPTH_TEST);
174 glEnable(GL_CULL_FACE);
175 glEnable(GL_LIGHTING);
176 glEnable(GL_LIGHT0);
178 glClearColor(0.1, 0.1, 0.1, 1);
180 return true;
181 }
183 static void cleanup()
184 {
185 delete scn;
187 stop_dev();
189 if(!dpy) return;
191 if(win) {
192 XDestroyWindow(dpy, win);
193 }
194 XCloseDisplay(dpy);
195 }
197 #define DIGIT_USZ (1.0 / 11.0)
198 #define MIN_REDRAW_INTERVAL (1000 / 40) /* 40fps */
200 static void display()
201 {
202 glMatrixMode(GL_MODELVIEW);
203 glLoadIdentity();
204 glTranslatef(0, 0, -cam_dist);
205 glRotatef(cam_phi, 1, 0, 0);
206 glRotatef(cam_theta, 0, 1, 0);
208 float lpos[] = {-7, 5, 10, 0};
209 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
211 if(opt_use_glow) {
212 glViewport(0, 0, glow_xsz, glow_ysz);
214 glClearColor(0, 0, 0, 1);
215 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
217 draw_scene(GLOW_PASS);
219 glReadPixels(0, 0, glow_xsz, glow_ysz, GL_RGBA, GL_UNSIGNED_BYTE, glow_framebuf);
220 glViewport(0, 0, win_width, win_height);
221 }
223 glClearColor(0.05, 0.05, 0.05, 1);
224 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
226 draw_scene();
228 if(opt_use_glow) {
229 for(int i=0; i<glow_iter; i++) {
230 fast_blur(BLUR_BOTH, blur_size, (uint32_t*)glow_framebuf, glow_xsz, glow_ysz);
231 glBindTexture(GL_TEXTURE_2D, glow_tex);
232 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, glow_xsz, glow_ysz, GL_RGBA, GL_UNSIGNED_BYTE, glow_framebuf);
234 post_glow();
235 }
236 }
238 if(get_led_state(0)) {
239 // continuously redraw until the left LED times out
240 draw_pending = true;
241 }
243 glXSwapBuffers(dpy, win);
244 assert(glGetError() == GL_NO_ERROR);
246 static long prev_msec;
247 long msec = get_msec();
248 long dt = msec - prev_msec;
250 if(dt < MIN_REDRAW_INTERVAL) {
251 wait_for(MIN_REDRAW_INTERVAL - dt);
252 }
253 prev_msec = get_msec();
254 }
256 static void draw_scene(int pass)
257 {
258 if(pass != GLOW_PASS) {
259 scn->render();
260 }
262 // shift the textures and modify the materials to make the display match our state
263 for(int i=0; i<2; i++) {
264 // 7seg
265 int digit = get_display_number();
266 for(int j=0; j<i; j++) {
267 digit /= 10;
268 }
269 digit %= 10;
271 float uoffs = DIGIT_USZ + DIGIT_USZ * digit;
273 disp_obj[i]->mtl.tex_offset[TEX_DIFFUSE] = Vector2(uoffs, 0);
274 disp_obj[i]->render();
276 // LEDs
277 if(get_led_state(i)) {
278 led_obj[i]->mtl.emissive = led_on_emissive;
279 } else {
280 led_obj[i]->mtl.emissive = Vector3(0, 0, 0);
281 }
282 led_obj[i]->render();
283 }
284 }
286 static void post_glow(void)
287 {
288 float max_s = (float)glow_xsz / (float)glow_tex_xsz;
289 float max_t = (float)glow_ysz / (float)glow_tex_ysz;
291 glPushAttrib(GL_ENABLE_BIT);
293 glBlendFunc(GL_ONE, GL_ONE);
294 glEnable(GL_BLEND);
295 glDisable(GL_CULL_FACE);
296 glDisable(GL_LIGHTING);
297 glDisable(GL_DEPTH_TEST);
299 glMatrixMode(GL_MODELVIEW);
300 glPushMatrix();
301 glLoadIdentity();
302 glMatrixMode(GL_PROJECTION);
303 glPushMatrix();
304 glLoadIdentity();
306 glEnable(GL_TEXTURE_2D);
307 glBindTexture(GL_TEXTURE_2D, glow_tex);
309 glBegin(GL_QUADS);
310 glColor4f(1, 1, 1, 1);
311 glTexCoord2f(0, 0);
312 glVertex2f(-1, -1);
313 glTexCoord2f(max_s, 0);
314 glVertex2f(1, -1);
315 glTexCoord2f(max_s, max_t);
316 glVertex2f(1, 1);
317 glTexCoord2f(0, max_t);
318 glVertex2f(-1, 1);
319 glEnd();
321 glPopMatrix();
322 glMatrixMode(GL_MODELVIEW);
323 glPopMatrix();
325 glPopAttrib();
326 }
329 static void reshape(int x, int y)
330 {
331 glViewport(0, 0, x, y);
333 glMatrixMode(GL_PROJECTION);
334 glLoadIdentity();
335 gluPerspective(50.0, (float)x / (float)y, 1.0, 1000.0);
337 win_width = x;
338 win_height = y;
340 if(opt_use_glow) {
341 glow_xsz = x / GLOW_SZ_DIV;
342 glow_ysz = y / GLOW_SZ_DIV;
343 printf("glow image size: %dx%d\n", glow_xsz, glow_ysz);
345 delete [] glow_framebuf;
346 glow_framebuf = new unsigned char[glow_xsz * glow_ysz * 4];
348 glow_tex_xsz = next_pow2(glow_xsz);
349 glow_tex_ysz = next_pow2(glow_ysz);
350 glBindTexture(GL_TEXTURE_2D, glow_tex);
351 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, glow_tex_xsz, glow_tex_ysz, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
352 }
353 }
355 static void keyb(int key, bool pressed)
356 {
357 if(pressed) {
358 switch(key) {
359 case 27:
360 exit(0);
361 }
362 }
363 }
365 static bool bnstate[32];
366 static int prev_x, prev_y;
368 static void mouse(int bn, bool pressed, int x, int y)
369 {
370 bnstate[bn] = pressed;
371 prev_x = x;
372 prev_y = y;
374 if(bn == 0 && pressed) {
375 // do picking
376 Ray ray = calc_pick_ray(x, win_height - y);
378 HitPoint minhit;
379 minhit.t = FLT_MAX;
380 int hit_found = -1;
382 for(int i=0; i<NUM_BUTTONS; i++) {
383 HitPoint hit;
384 if(button_obj[i]->get_mesh()->get_bounds().intersect(ray, &hit) && hit.t < minhit.t) {
385 minhit = hit;
386 hit_found = i;
387 }
388 }
390 if(hit_found != -1) {
391 switch(hit_found) {
392 case BN_TICKET:
393 issue_ticket();
394 printf("issue ticket\n");
395 break;
397 case BN_NEXT:
398 next_customer();
399 printf("next customer\n");
400 break;
401 }
402 draw_pending = true;
403 }
404 }
405 }
407 static void motion(int x, int y)
408 {
409 int dx = x - prev_x;
410 int dy = y - prev_y;
411 prev_x = x;
412 prev_y = y;
414 if(bnstate[0]) {
415 cam_theta += dx * 0.5;
416 cam_phi += dy * 0.5;
417 if(cam_phi < -90) cam_phi = -90;
418 if(cam_phi > 90) cam_phi = 90;
420 } else if(bnstate[2]) {
421 cam_dist += dy * 0.5;
422 if(cam_dist < 0.0) cam_dist = 0.0;
424 } else {
425 float xoffs = 2.0 * x / win_width - 1.0;
426 float yoffs = 2.0 * y / win_height - 1.0;
427 cam_theta = -xoffs * 15.0 * (win_width / win_height);
428 cam_phi = -yoffs * 15.0;
429 }
430 draw_pending = true;
431 }
433 static Ray calc_pick_ray(int x, int y)
434 {
435 double mv[16], proj[16];
436 int vp[4];
437 double resx, resy, resz;
438 Ray ray;
440 glGetDoublev(GL_MODELVIEW_MATRIX, mv);
441 glGetDoublev(GL_PROJECTION_MATRIX, proj);
442 glGetIntegerv(GL_VIEWPORT, vp);
444 gluUnProject(x, y, 0, mv, proj, vp, &resx, &resy, &resz);
445 ray.origin = Vector3(resx, resy, resz);
447 gluUnProject(x, y, 1, mv, proj, vp, &resx, &resy, &resz);
448 ray.dir = normalize(Vector3(resx, resy, resz) - ray.origin);
450 return ray;
451 }
453 static int next_pow2(int x)
454 {
455 x--;
456 x = (x >> 1) | x;
457 x = (x >> 2) | x;
458 x = (x >> 4) | x;
459 x = (x >> 8) | x;
460 x = (x >> 16) | x;
461 return x + 1;
462 }
464 static Window create_window(const char *title, int xsz, int ysz)
465 {
466 int scr = DefaultScreen(dpy);
467 Window root = RootWindow(dpy, scr);
469 int glxattr[] = {
470 GLX_RGBA,
471 GLX_RED_SIZE, 8,
472 GLX_GREEN_SIZE, 8,
473 GLX_BLUE_SIZE, 8,
474 GLX_DEPTH_SIZE, 24,
475 GLX_DOUBLEBUFFER,
476 #if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
477 GLX_SAMPLE_BUFFERS_ARB, 1,
478 GLX_SAMPLES_ARB, 1,
479 #endif
480 None
481 };
483 XVisualInfo *vis = glXChooseVisual(dpy, scr, glxattr);
484 if(!vis) {
485 fprintf(stderr, "failed to find a suitable visual\n");
486 return 0;
487 }
489 if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
490 fprintf(stderr, "failed to create OpenGL context\n");
491 XFree(vis);
492 return -1;
493 }
495 XSetWindowAttributes xattr;
496 xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr);
497 xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone);
498 unsigned int xattr_mask = CWColormap | CWBackPixel | CWBorderPixel;
500 Window win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput,
501 vis->visual, xattr_mask, &xattr);
502 if(!win) {
503 fprintf(stderr, "failed to create window\n");
504 glXDestroyContext(dpy, ctx);
505 XFree(vis);
506 return -1;
507 }
508 XFree(vis);
510 unsigned int evmask = StructureNotifyMask | VisibilityChangeMask | ExposureMask |
511 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
512 PointerMotionMask;
513 XSelectInput(dpy, win, evmask);
515 xa_wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False);
516 xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
517 XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
519 XClassHint hint;
520 hint.res_name = hint.res_class = (char*)"equeue_win";
521 XSetClassHint(dpy, win, &hint);
523 XTextProperty wm_name;
524 XStringListToTextProperty((char**)&title, 1, &wm_name);
525 XSetWMName(dpy, win, &wm_name);
526 XSetWMIconName(dpy, win, &wm_name);
527 XFree(wm_name.value);
529 XMapWindow(dpy, win);
530 glXMakeCurrent(dpy, win, ctx);
532 return win;
533 }
535 static void process_events()
536 {
537 XEvent ev;
539 while(XPending(dpy)) {
540 XNextEvent(dpy, &ev);
541 switch(ev.type) {
542 case MapNotify:
543 win_mapped = true;
544 break;
546 case UnmapNotify:
547 win_mapped = false;
548 break;
550 case Expose:
551 if(win_mapped && ev.xexpose.count == 0) {
552 draw_pending = true;
553 }
554 break;
556 case MotionNotify:
557 motion(ev.xmotion.x, ev.xmotion.y);
558 break;
560 case ButtonPress:
561 mouse(ev.xbutton.button - 1, true, ev.xbutton.x, ev.xbutton.y);
562 break;
564 case ButtonRelease:
565 mouse(ev.xbutton.button - 1, false, ev.xbutton.x, ev.xbutton.y);
566 break;
568 case KeyPress:
569 {
570 KeySym sym = XLookupKeysym(&ev.xkey, 0);
571 keyb(translate_keysym(sym), true);
572 }
573 break;
575 case KeyRelease:
576 {
577 KeySym sym = XLookupKeysym(&ev.xkey, 0);
578 keyb(translate_keysym(sym), false);
579 }
580 break;
582 case ConfigureNotify:
583 {
584 int xsz = ev.xconfigure.width;
585 int ysz = ev.xconfigure.height;
587 if(xsz != win_width || ysz != win_height) {
588 win_width = xsz;
589 win_height = ysz;
590 reshape(xsz, ysz);
591 }
592 }
593 break;
595 case ClientMessage:
596 if(ev.xclient.message_type == xa_wm_prot) {
597 if((Atom)ev.xclient.data.l[0] == xa_wm_del_win) {
598 exit(0);
599 }
600 }
601 break;
603 default:
604 break;
605 }
607 }
608 }
610 static int translate_keysym(KeySym sym)
611 {
612 switch(sym) {
613 case XK_BackSpace:
614 return '\b';
615 case XK_Tab:
616 return '\t';
617 case XK_Linefeed:
618 return '\r';
619 case XK_Return:
620 return '\n';
621 case XK_Escape:
622 return 27;
623 default:
624 break;
625 }
626 return (int)sym;
627 }
629 static int proc_args(int argc, char **argv)
630 {
631 for(int i=1; i<argc; i++) {
632 if(argv[i][0] == '-') {
633 fprintf(stderr, "unexpected option: %s\n", argv[i]);
634 return -1;
636 } else {
637 if(fake_devpath) {
638 fprintf(stderr, "unexpected argument: %s\n", argv[i]);
639 return -1;
640 }
641 fake_devpath = argv[i];
642 }
643 }
644 if(!fake_devpath) {
645 fprintf(stderr, "no device path specified, running standalone\n");
646 }
647 return 0;
648 }