primitives
[miniglut] / miniglut.c
1 /*
2 MiniGLUT - minimal GLUT subset without dependencies
3 Copyright (C) 2020  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 #ifdef MINIGLUT_USE_LIBC
19 #define _GNU_SOURCE
20 #include <stdlib.h>
21 #include <math.h>
22 #endif
23
24 #define PI      3.1415926536f
25
26 #if defined(__unix__)
27
28 #include <X11/Xlib.h>
29 #include <X11/cursorfont.h>
30 #include <GL/glx.h>
31 #define BUILD_X11
32
33 #ifndef GLX_SAMPLE_BUFFERS_ARB
34 #define GLX_SAMPLE_BUFFERS_ARB  100000
35 #define GLX_SAMPLES_ARB                 100001
36 #endif
37 #ifndef GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB
38 #define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB        0x20b2
39 #endif
40
41 static Display *dpy;
42 static Window win, root;
43 static int scr;
44 static GLXContext ctx;
45 static Atom xa_wm_proto, xa_wm_del_win;
46 static unsigned int evmask;
47
48 #elif defined(_WIN32)
49
50 #include <windows.h>
51 #define BUILD_WIN32
52
53 static HWND win;
54 static HDC dc;
55 static HGLRC ctx;
56
57 #else
58 #error unsupported platform
59 #endif
60
61 #include <GL/gl.h>
62 #include "miniglut.h"
63
64 struct ctx_info {
65         int rsize, gsize, bsize, asize;
66         int zsize, ssize;
67         int dblbuf;
68         int samples;
69         int stereo;
70         int srgb;
71 };
72
73 static void create_window(const char *title);
74 static void get_window_pos(Window win, int *x, int *y);
75 static void get_window_size(Window win, int *w, int *h);
76 static void get_screen_size(Window win, int *scrw, int *scrh);
77
78 static long get_msec(void);
79 static void panic(const char *msg);
80 static void sys_exit(int status);
81 static int sys_write(int fd, const void *buf, int count);
82
83
84 static int init_x, init_y, init_width = 256, init_height = 256;
85 static unsigned int init_mode;
86
87 static struct ctx_info ctx_info;
88 static int cur_cursor = GLUT_CURSOR_INHERIT;
89
90 static glut_cb cb_display;
91 static glut_cb cb_idle;
92 static glut_cb_reshape cb_reshape;
93 static glut_cb_state cb_vis, cb_entry;
94 static glut_cb_keyb cb_keydown, cb_keyup;
95 static glut_cb_special cb_skeydown, cb_skeyup;
96 static glut_cb_mouse cb_mouse;
97 static glut_cb_motion cb_motion, cb_passive;
98 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
99 static glut_cb_sbbutton cb_sball_button;
100
101 static int win_width, win_height;
102 static int mapped;
103 static int quit;
104 static int upd_pending, reshape_pending;
105 static int modstate;
106
107
108 void glutInit(int *argc, char **argv)
109 {
110 #ifdef BUILD_X11
111         if(!(dpy = XOpenDisplay(0))) {
112                 panic("Failed to connect to the X server\n");
113         }
114         scr = DefaultScreen(dpy);
115         root = RootWindow(dpy, scr);
116         xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", False);
117         xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
118
119         evmask = ExposureMask | StructureNotifyMask;
120 #endif
121 }
122
123 void glutInitWindowPosition(int x, int y)
124 {
125         init_x = x;
126         init_y = y;
127 }
128
129 void glutInitWindowSize(int xsz, int ysz)
130 {
131         init_width = xsz;
132         init_height = ysz;
133 }
134
135 void glutInitDisplayMode(unsigned int mode)
136 {
137         init_mode = mode;
138 }
139
140 void glutCreateWindow(const char *title)
141 {
142         create_window(title);
143 }
144
145 void glutExit(void)
146 {
147         quit = 1;
148 }
149
150 void glutMainLoop(void)
151 {
152         while(!quit) {
153                 glutMainLoopEvent();
154         }
155 }
156
157 void glutPostRedisplay(void)
158 {
159         upd_pending = 1;
160 }
161
162 #define UPD_EVMASK(x) \
163         do { \
164                 if(func) { \
165                         evmask |= x; \
166                 } else { \
167                         evmask &= ~(x); \
168                 } \
169                 if(win) XSelectInput(dpy, win, evmask); \
170         } while(0)
171
172
173 void glutIdleFunc(glut_cb func)
174 {
175         cb_idle = func;
176 }
177
178 void glutDisplayFunc(glut_cb func)
179 {
180         cb_display = func;
181 }
182
183 void glutReshapeFunc(glut_cb_reshape func)
184 {
185         cb_reshape = func;
186 }
187
188 void glutVisibilityFunc(glut_cb_state func)
189 {
190         cb_vis = func;
191 #ifdef BUILD_X11
192         UPD_EVMASK(VisibilityChangeMask);
193 #endif
194 }
195
196 void glutEntryFunc(glut_cb_state func)
197 {
198         cb_entry = func;
199 #ifdef BUILD_X11
200         UPD_EVMASK(EnterWindowMask | LeaveWindowMask);
201 #endif
202 }
203
204 void glutKeyboardFunc(glut_cb_keyb func)
205 {
206         cb_keydown = func;
207 #ifdef BUILD_X11
208         UPD_EVMASK(KeyPressMask);
209 #endif
210 }
211
212 void glutKeyboardUpFunc(glut_cb_keyb func)
213 {
214         cb_keyup = func;
215 #ifdef BUILD_X11
216         UPD_EVMASK(KeyReleaseMask);
217 #endif
218 }
219
220 void glutSpecialFunc(glut_cb_special func)
221 {
222         cb_skeydown = func;
223 #ifdef BUILD_X11
224         UPD_EVMASK(KeyPressMask);
225 #endif
226 }
227
228 void glutSpecialUpFunc(glut_cb_special func)
229 {
230         cb_skeyup = func;
231 #ifdef BUILD_X11
232         UPD_EVMASK(KeyReleaseMask);
233 #endif
234 }
235
236 void glutMouseFunc(glut_cb_mouse func)
237 {
238         cb_mouse = func;
239 #ifdef BUILD_X11
240         UPD_EVMASK(ButtonPressMask | ButtonReleaseMask);
241 #endif
242 }
243
244 void glutMotionFunc(glut_cb_motion func)
245 {
246         cb_motion = func;
247 #ifdef BUILD_X11
248         UPD_EVMASK(ButtonMotionMask);
249 #endif
250 }
251
252 void glutPassiveMotionFunc(glut_cb_motion func)
253 {
254         cb_passive = func;
255 #ifdef BUILD_X11
256         UPD_EVMASK(PointerMotionMask);
257 #endif
258 }
259
260 void glutSpaceballMotionFunc(glut_cb_sbmotion func)
261 {
262         cb_sball_motion = func;
263 }
264
265 void glutSpaceballRotateFunc(glut_cb_sbmotion func)
266 {
267         cb_sball_rotate = func;
268 }
269
270 void glutSpaceballBittonFunc(glut_cb_sbbutton func)
271 {
272         cb_sball_button = func;
273 }
274
275 int glutGet(unsigned int s)
276 {
277         int x, y;
278         switch(s) {
279         case GLUT_WINDOW_X:
280                 get_window_pos(win, &x, &y);
281                 return x;
282         case GLUT_WINDOW_Y:
283                 get_window_pos(win, &x, &y);
284                 return y;
285         case GLUT_WINDOW_WIDTH:
286                 get_window_size(win, &x, &y);
287                 return x;
288         case GLUT_WINDOW_HEIGHT:
289                 get_window_size(win, &x, &y);
290                 return y;
291         case GLUT_WINDOW_BUFFER_SIZE:
292                 return ctx_info.rsize + ctx_info.gsize + ctx_info.bsize + ctx_info.asize;
293         case GLUT_WINDOW_STENCIL_SIZE:
294                 return ctx_info.ssize;
295         case GLUT_WINDOW_DEPTH_SIZE:
296                 return ctx_info.zsize;
297         case GLUT_WINDOW_RED_SIZE:
298                 return ctx_info.rsize;
299         case GLUT_WINDOW_GREEN_SIZE:
300                 return ctx_info.gsize;
301         case GLUT_WINDOW_BLUE_SIZE:
302                 return ctx_info.bsize;
303         case GLUT_WINDOW_ALPHA_SIZE:
304                 return ctx_info.asize;
305         case GLUT_WINDOW_DOUBLEBUFFER:
306                 return ctx_info.dblbuf;
307         case GLUT_WINDOW_RGBA:
308                 return 1;
309         case GLUT_WINDOW_NUM_SAMPLES:
310                 return ctx_info.samples;
311         case GLUT_WINDOW_STEREO:
312                 return ctx_info.stereo;
313         case GLUT_WINDOW_SRGB:
314                 return ctx_info.srgb;
315         case GLUT_WINDOW_CURSOR:
316                 return cur_cursor;
317         case GLUT_SCREEN_WIDTH:
318                 get_screen_size(win, &x, &y);
319                 return x;
320         case GLUT_SCREEN_HEIGHT:
321                 get_screen_size(win, &x, &y);
322                 return y;
323         case GLUT_INIT_DISPLAY_MODE:
324                 return init_mode;
325         case GLUT_INIT_WINDOW_X:
326                 return init_x;
327         case GLUT_INIT_WINDOW_Y:
328                 return init_y;
329         case GLUT_INIT_WINDOW_WIDTH:
330                 return init_width;
331         case GLUT_INIT_WINDOW_HEIGHT:
332                 return init_height;
333         case GLUT_ELAPSED_TIME:
334                 return get_msec();
335         default:
336                 break;
337         }
338         return 0;
339 }
340
341 int glutGetModifiers(void)
342 {
343         return modstate;
344 }
345
346 static int is_space(int c)
347 {
348         return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r';
349 }
350
351 static const char *skip_space(const char *s)
352 {
353         while(*s && is_space(*s)) s++;
354         return s;
355 }
356
357 int glutExtensionSupported(char *ext)
358 {
359         const char *str, *eptr;
360
361         if(!(str = (const char*)glGetString(GL_EXTENSIONS))) {
362                 return 0;
363         }
364
365         while(*str) {
366                 str = skip_space(str);
367                 eptr = skip_space(ext);
368                 while(*str && !is_space(*str) && *eptr && *str == *eptr) {
369                         str++;
370                         eptr++;
371                 }
372                 if((!*str || is_space(*str)) && !*eptr) {
373                         return 1;
374                 }
375                 while(*str && !is_space(*str)) str++;
376         }
377
378         return 0;
379 }
380
381 /* TODO */
382 void glutSolidSphere(float rad, int slices, int stacks)
383 {
384         int i, j, k, gray;
385         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
386         float du = 1.0f / (float)slices;
387         float dv = 1.0f / (float)stacks;
388
389         glBegin(GL_QUADS);
390         for(i=0; i<stacks; i++) {
391                 v = i * dv;
392                 for(j=0; j<slices; j++) {
393                         u = j * du;
394                         for(k=0; k<4; k++) {
395                                 gray = k ^ (k >> 1);
396                                 s = gray & 1 ? u + du : u;
397                                 t = gray & 2 ? v + dv : v;
398                                 theta = s * PI * 2.0f;
399                                 phi = t * PI;
400                                 sincosf(theta, &sintheta, &costheta);
401                                 sincosf(phi, &sinphi, &cosphi);
402                                 x = sintheta * sinphi;
403                                 y = costheta * sinphi;
404                                 z = cosphi;
405
406                                 glColor3f(s, t, 1);
407                                 glTexCoord2f(s, t);
408                                 glNormal3f(x, y, z);
409                                 glVertex3f(x * rad, y * rad, z * rad);
410                         }
411                 }
412         }
413         glEnd();
414 }
415
416 void glutWireSphere(float rad, int slices, int stacks)
417 {
418         glPushAttrib(GL_POLYGON_BIT);
419         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
420         glutSolidSphere(rad, slices, stacks);
421         glPopAttrib();
422 }
423
424 void glutSolidCube(float sz)
425 {
426         int i, j, idx, gray, flip, rotx;
427         float vpos[3], norm[3];
428         float rad = sz * 0.5f;
429
430         glBegin(GL_QUADS);
431         for(i=0; i<6; i++) {
432                 flip = i & 1;
433                 rotx = i >> 2;
434                 idx = (~i & 2) - rotx;
435                 norm[0] = norm[1] = norm[2] = 0.0f;
436                 norm[idx] = flip ^ ((i >> 1) & 1) ? -1 : 1;
437                 glNormal3fv(norm);
438                 vpos[idx] = norm[idx] * rad;
439                 for(j=0; j<4; j++) {
440                         gray = j ^ (j >> 1);
441                         vpos[i & 2] = (gray ^ flip) & 1 ? rad : -rad;
442                         vpos[rotx + 1] = (gray ^ (rotx << 1)) & 2 ? rad : -rad;
443                         glTexCoord2f(gray & 1, gray >> 1);
444                         glVertex3fv(vpos);
445                 }
446         }
447         glEnd();
448 }
449
450 void glutWireCube(float sz)
451 {
452         glPushAttrib(GL_POLYGON_BIT);
453         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
454         glutSolidCube(sz);
455         glPopAttrib();
456 }
457
458 static void draw_cylinder(float rbot, float rtop, float height, int slices, int stacks)
459 {
460         int i, j, k, gray;
461         float x, y, z, s, t, u, v, theta, phi, sintheta, costheta, sinphi, cosphi, rad;
462         float du = 1.0f / (float)slices;
463         float dv = 1.0f / (float)stacks;
464
465         phi = atan(fabs(rbot - rtop) / height);
466         sincosf(phi, &sinphi, &cosphi);
467
468         glBegin(GL_QUADS);
469         for(i=0; i<stacks; i++) {
470                 v = i * dv;
471                 for(j=0; j<slices; j++) {
472                         u = j * du;
473                         for(k=0; k<4; k++) {
474                                 gray = k ^ (k >> 1);
475                                 s = gray & 2 ? u + du : u;
476                                 t = gray & 1 ? v + dv : v;
477                                 rad = rbot + (rtop - rbot) * t;
478                                 theta = s * PI * 2.0f;
479                                 sincosf(theta, &sintheta, &costheta);
480
481                                 x = sintheta * cosphi;
482                                 y = costheta * cosphi;
483                                 z = sinphi;
484
485                                 glColor3f(s, t, 1);
486                                 glTexCoord2f(s, t);
487                                 glNormal3f(x, y, z);
488                                 glVertex3f(sintheta * rad, costheta * rad, t * height);
489                         }
490                 }
491         }
492         glEnd();
493 }
494
495 void glutSolidCone(float base, float height, int slices, int stacks)
496 {
497         draw_cylinder(base, 0, height, slices, stacks);
498 }
499
500 void glutWireCone(float base, float height, int slices, int stacks)
501 {
502         glPushAttrib(GL_POLYGON_BIT);
503         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
504         glutSolidCone(base, height, slices, stacks);
505         glPopAttrib();
506 }
507
508 void glutSolidCylinder(float rad, float height, int slices, int stacks)
509 {
510         draw_cylinder(rad, rad, height, slices, stacks);
511 }
512
513 void glutWireCylinder(float rad, float height, int slices, int stacks)
514 {
515         glPushAttrib(GL_POLYGON_BIT);
516         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
517         glutSolidCylinder(rad, height, slices, stacks);
518         glPopAttrib();
519 }
520
521 void glutSolidTorus(float inner_rad, float outer_rad, int sides, int rings)
522 {
523         int i, j, k, gray;
524         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
525         float du = 1.0f / (float)rings;
526         float dv = 1.0f / (float)sides;
527
528         glBegin(GL_QUADS);
529         for(i=0; i<rings; i++) {
530                 u = i * du;
531                 for(j=0; j<sides; j++) {
532                         v = j * dv;
533                         for(k=0; k<4; k++) {
534                                 gray = k ^ (k >> 1);
535                                 s = gray & 1 ? u + du : u;
536                                 t = gray & 2 ? v + dv : v;
537                                 theta = s * PI * 2.0f;
538                                 phi = t * PI * 2.0f;
539                                 sincosf(theta, &sintheta, &costheta);
540                                 sincosf(phi, &sinphi, &cosphi);
541                                 x = sintheta * sinphi;
542                                 y = costheta * sinphi;
543                                 z = cosphi;
544
545                                 glColor3f(s, t, 1);
546                                 glTexCoord2f(s, t);
547                                 glNormal3f(x, y, z);
548
549                                 x = x * inner_rad + sintheta * outer_rad;
550                                 y = y * inner_rad + costheta * outer_rad;
551                                 z *= inner_rad;
552                                 glVertex3f(x, y, z);
553                         }
554                 }
555         }
556         glEnd();
557 }
558
559 void glutWireTorus(float inner_rad, float outer_rad, int sides, int rings)
560 {
561         glPushAttrib(GL_POLYGON_BIT);
562         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
563         glutSolidTorus(inner_rad, outer_rad, sides, rings);
564         glPopAttrib();
565 }
566
567 void glutSolidTeapot(float size)
568 {
569 }
570
571 void glutWireTeapot(float size)
572 {
573 }
574
575
576 #ifdef BUILD_X11
577 static void handle_event(XEvent *ev);
578
579 void glutMainLoopEvent(void)
580 {
581         XEvent ev;
582
583         if(!cb_display) {
584                 panic("display callback not set");
585         }
586
587         for(;;) {
588                 if(!upd_pending && !cb_idle) {
589                         XNextEvent(dpy, &ev);
590                         handle_event(&ev);
591                         if(quit) return;
592                 }
593                 while(XPending(dpy)) {
594                         XNextEvent(dpy, &ev);
595                         handle_event(&ev);
596                         if(quit) return;
597                 }
598
599                 if(cb_idle) {
600                         cb_idle();
601                 }
602
603                 if(upd_pending && mapped) {
604                         upd_pending = 0;
605                         cb_display();
606                 }
607         }
608 }
609
610 static KeySym translate_keysym(KeySym sym)
611 {
612         switch(sym) {
613         case XK_Escape:
614                 return 27;
615         case XK_BackSpace:
616                 return '\b';
617         case XK_Linefeed:
618                 return '\r';
619         case XK_Return:
620                 return '\n';
621         case XK_Delete:
622                 return 127;
623         case XK_Tab:
624                 return '\t';
625         default:
626                 break;
627         }
628         return sym;
629 }
630
631 static void handle_event(XEvent *ev)
632 {
633         KeySym sym;
634
635         switch(ev->type) {
636         case MapNotify:
637                 mapped = 1;
638                 break;
639         case UnmapNotify:
640                 mapped = 0;
641                 break;
642         case ConfigureNotify:
643                 if(cb_reshape && (ev->xconfigure.width != win_width || ev->xconfigure.height != win_height)) {
644                         win_width = ev->xconfigure.width;
645                         win_height = ev->xconfigure.height;
646                         cb_reshape(ev->xconfigure.width, ev->xconfigure.height);
647                 }
648                 break;
649
650         case ClientMessage:
651                 if(ev->xclient.message_type == xa_wm_proto) {
652                         if(ev->xclient.data.l[0] == xa_wm_del_win) {
653                                 quit = 1;
654                         }
655                 }
656                 break;
657
658         case Expose:
659                 upd_pending = 1;
660                 break;
661
662         case KeyPress:
663         case KeyRelease:
664                 modstate = ev->xkey.state & (ShiftMask | ControlMask | Mod1Mask);
665                 if(!(sym = XLookupKeysym(&ev->xkey, 0))) {
666                         break;
667                 }
668                 sym = translate_keysym(sym);
669                 if(sym < 256) {
670                         if(ev->type == KeyPress) {
671                                 if(cb_keydown) cb_keydown((unsigned char)sym, ev->xkey.x, ev->xkey.y);
672                         } else {
673                                 if(cb_keyup) cb_keyup((unsigned char)sym, ev->xkey.x, ev->xkey.y);
674                         }
675                 } else {
676                         if(ev->type == KeyPress) {
677                                 if(cb_skeydown) cb_skeydown(sym, ev->xkey.x, ev->xkey.y);
678                         } else {
679                                 if(cb_skeyup) cb_skeyup(sym, ev->xkey.x, ev->xkey.y);
680                         }
681                 }
682                 break;
683
684         case ButtonPress:
685         case ButtonRelease:
686                 modstate = ev->xbutton.state & (ShiftMask | ControlMask | Mod1Mask);
687                 if(cb_mouse) {
688                         int bn = ev->xbutton.button - Button1;
689                         cb_mouse(bn, ev->type == ButtonPress ? GLUT_DOWN : GLUT_UP,
690                                         ev->xbutton.x, ev->xbutton.y);
691                 }
692                 break;
693
694         case MotionNotify:
695                 if(ev->xmotion.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) {
696                         if(cb_motion) cb_motion(ev->xmotion.x, ev->xmotion.y);
697                 } else {
698                         if(cb_passive) cb_passive(ev->xmotion.x, ev->xmotion.y);
699                 }
700                 break;
701
702         case VisibilityNotify:
703                 if(cb_vis) {
704                         cb_vis(ev->xvisibility.state == VisibilityFullyObscured ? GLUT_NOT_VISIBLE : GLUT_VISIBLE);
705                 }
706                 break;
707         case EnterNotify:
708                 if(cb_entry) cb_entry(GLUT_ENTERED);
709                 break;
710         case LeaveNotify:
711                 if(cb_entry) cb_entry(GLUT_LEFT);
712                 break;
713         }
714 }
715
716 void glutSwapBuffers(void)
717 {
718         glXSwapBuffers(dpy, win);
719 }
720
721 void glutPositionWindow(int x, int y)
722 {
723         XMoveWindow(dpy, win, x, y);
724 }
725
726 void glutReshapeWindow(int xsz, int ysz)
727 {
728         XResizeWindow(dpy, win, xsz, ysz);
729 }
730
731 void glutFullScreen(void)
732 {
733         /* TODO */
734 }
735
736 void glutSetWindowTitle(const char *title)
737 {
738         XTextProperty tprop;
739         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
740                 return;
741         }
742         XSetWMName(dpy, win, &tprop);
743         XFree(tprop.value);
744 }
745
746 void glutSetIconTitle(const char *title)
747 {
748         XTextProperty tprop;
749         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
750                 return;
751         }
752         XSetWMIconName(dpy, win, &tprop);
753         XFree(tprop.value);
754 }
755
756 void glutSetCursor(int cidx)
757 {
758         Cursor cur = None;
759
760         switch(cidx) {
761         case GLUT_CURSOR_LEFT_ARROW:
762                 cur = XCreateFontCursor(dpy, XC_left_ptr);
763                 break;
764         case GLUT_CURSOR_INHERIT:
765                 break;
766         case GLUT_CURSOR_NONE:
767                 /* TODO */
768         default:
769                 return;
770         }
771
772         XDefineCursor(dpy, win, cur);
773         cur_cursor = cidx;
774 }
775
776 static XVisualInfo *choose_visual(unsigned int mode)
777 {
778         XVisualInfo *vi;
779         int attr[32] = {
780                 GLX_RGBA,
781                 GLX_DOUBLEBUFFER,
782                 GLX_RED_SIZE, 4,
783                 GLX_GREEN_SIZE, 4,
784                 GLX_BLUE_SIZE, 4
785         };
786         int *aptr = attr + 8;
787         int *samples = 0;
788
789         if(mode & GLUT_ALPHA) {
790                 *aptr++ = GLX_ALPHA_SIZE;
791                 *aptr++ = 4;
792         }
793         if(mode & GLUT_DEPTH) {
794                 *aptr++ = GLX_DEPTH_SIZE;
795                 *aptr++ = 16;
796         }
797         if(mode & GLUT_STENCIL) {
798                 *aptr++ = GLX_STENCIL_SIZE;
799                 *aptr++ = 1;
800         }
801         if(mode & GLUT_ACCUM) {
802                 *aptr++ = GLX_ACCUM_RED_SIZE; *aptr++ = 1;
803                 *aptr++ = GLX_ACCUM_GREEN_SIZE; *aptr++ = 1;
804                 *aptr++ = GLX_ACCUM_BLUE_SIZE; *aptr++ = 1;
805         }
806         if(mode & GLUT_STEREO) {
807                 *aptr++ = GLX_STEREO;
808         }
809         if(mode & GLUT_SRGB) {
810                 *aptr++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB;
811         }
812         if(mode & GLUT_MULTISAMPLE) {
813                 *aptr++ = GLX_SAMPLE_BUFFERS_ARB;
814                 *aptr++ = 1;
815                 *aptr++ = GLX_SAMPLES_ARB;
816                 samples = aptr;
817                 *aptr++ = 32;
818         }
819         *aptr++ = None;
820
821         if(!samples) {
822                 return glXChooseVisual(dpy, scr, attr);
823         }
824         while(!(vi = glXChooseVisual(dpy, scr, attr)) && *samples) {
825                 *samples >>= 1;
826                 if(!*samples) {
827                         aptr[-3] = None;
828                 }
829         }
830         return vi;
831 }
832
833 static void create_window(const char *title)
834 {
835         XSetWindowAttributes xattr;
836         XVisualInfo *vi;
837         unsigned int xattr_mask;
838         unsigned int mode = init_mode;
839
840         if(!(vi = choose_visual(mode))) {
841                 mode &= ~GLUT_SRGB;
842                 if(!(vi = choose_visual(mode))) {
843                         panic("Failed to find compatible visual\n");
844                 }
845         }
846
847         if(!(ctx = glXCreateContext(dpy, vi, 0, True))) {
848                 XFree(vi);
849                 panic("Failed to create OpenGL context\n");
850         }
851
852         glXGetConfig(dpy, vi, GLX_RED_SIZE, &ctx_info.rsize);
853         glXGetConfig(dpy, vi, GLX_GREEN_SIZE, &ctx_info.gsize);
854         glXGetConfig(dpy, vi, GLX_BLUE_SIZE, &ctx_info.bsize);
855         glXGetConfig(dpy, vi, GLX_ALPHA_SIZE, &ctx_info.asize);
856         glXGetConfig(dpy, vi, GLX_DEPTH_SIZE, &ctx_info.zsize);
857         glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &ctx_info.ssize);
858         glXGetConfig(dpy, vi, GLX_DOUBLEBUFFER, &ctx_info.dblbuf);
859         glXGetConfig(dpy, vi, GLX_STEREO, &ctx_info.stereo);
860         glXGetConfig(dpy, vi, GLX_SAMPLES_ARB, &ctx_info.samples);
861         glXGetConfig(dpy, vi, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
862
863         xattr.background_pixel = BlackPixel(dpy, scr);
864         xattr.colormap = XCreateColormap(dpy, root, vi->visual, AllocNone);
865         xattr_mask = CWBackPixel | CWColormap;
866         if(!(win = XCreateWindow(dpy, root, init_x, init_y, init_width, init_height, 0,
867                         vi->depth, InputOutput, vi->visual, xattr_mask, &xattr))) {
868                 XFree(vi);
869                 glXDestroyContext(dpy, ctx);
870                 panic("Failed to create window\n");
871         }
872         XFree(vi);
873
874         XSelectInput(dpy, win, evmask);
875
876         glutSetWindowTitle(title);
877         glutSetIconTitle(title);
878         XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
879         XMapWindow(dpy, win);
880
881         glXMakeCurrent(dpy, win, ctx);
882 }
883
884 static void get_window_pos(Window win, int *x, int *y)
885 {
886         XWindowAttributes wattr;
887         XGetWindowAttributes(dpy, win, &wattr);
888         *x = wattr.x;
889         *y = wattr.y;
890 }
891
892 static void get_window_size(Window win, int *w, int *h)
893 {
894         XWindowAttributes wattr;
895         XGetWindowAttributes(dpy, win, &wattr);
896         *w = wattr.width;
897         *h = wattr.height;
898 }
899
900 static void get_screen_size(Window win, int *scrw, int *scrh)
901 {
902         XWindowAttributes wattr;
903         XGetWindowAttributes(dpy, root, &wattr);
904         *scrw = wattr.width;
905         *scrh = wattr.height;
906 }
907 #endif
908
909 #if defined(__unix__) || defined(__APPLE__)
910 #include <sys/time.h>
911
912 static long get_msec(void)
913 {
914         static struct timeval tv0;
915         struct timeval tv;
916
917         gettimeofday(&tv, 0);
918         if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
919                 tv0 = tv;
920                 return 0;
921         }
922         return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
923 }
924 #endif
925 #ifdef _WIN32
926 static long get_msec(void)
927 {
928         static long t0;
929
930         if(!t0) {
931                 t0 = timeGetTime();
932                 return 0;
933         }
934         return timeGetTime() - t0;
935 }
936 #endif
937
938 static void panic(const char *msg)
939 {
940         const char *end = msg;
941         while(*end) end++;
942         sys_write(2, msg, end - msg);
943         sys_exit(1);
944 }
945
946
947 #ifdef MINIGLUT_USE_LIBC
948 static void sys_exit(int status)
949 {
950         exit(status);
951 }
952
953 static int sys_write(int fd, const void *buf, int count)
954 {
955         return write(fd, buf, count);
956 }
957 #else   /* !MINIGLUT_USE_LIBC */
958
959 #ifdef __linux__
960
961 #ifdef __x86_64__
962 static void sys_exit(int status)
963 {
964         asm volatile(
965                 "syscall\n\t"
966                 :: "a"(60), "D"(status)
967         );
968 }
969 static int sys_write(int fd, const void *buf, int count)
970 {
971         long res;
972         asm volatile(
973                 "syscall\n\t"
974                 : "=a"(res)
975                 : "a"(1), "D"(fd), "S"(buf), "d"(count)
976         );
977         return res;
978 }
979 #endif
980 #ifdef __i386__
981 static void sys_exit(int status)
982 {
983         asm volatile(
984                 "mov $1, %%eax\n\t"
985                 "int $0x80\n\t"
986                 :: "b"(status)
987                 : "eax"
988         );
989 }
990 static int sys_write(int fd, const void *buf, int count)
991 {
992         int res;
993         asm volatile(
994                 "mov $4, %%eax\n\t"
995                 "int $0x80\n\t"
996                 :: "b"(fd), "c"(buf), "d"(count));
997         return res;
998 }
999 #endif
1000
1001 #endif  /* __linux__ */
1002
1003 #ifdef _WIN32
1004 static int sys_exit(int status)
1005 {
1006         ExitProcess(status);
1007 }
1008 static int sys_write(int fd, const void *buf, int count)
1009 {
1010         int wrsz;
1011
1012         HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
1013         if(!WriteFile(out, buf, count, &wrsz, 0)) {
1014                 return -1;
1015         }
1016         return wrsz;
1017 }
1018 #endif  /* _WIN32 */
1019
1020 #endif  /* !MINIGLUT_USE_LIBC */