1/*
2 * Mesa 3-D graphics library
3 * Version:  7.9
4 *
5 * Copyright (C) 2010 LunarG Inc.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 *
25 * Authors:
26 *    Chia-I Wu <olv@lunarg.com>
27 */
28
29/*
30 * This demo uses EGL_KHR_image_pixmap and GL_OES_EGL_image to demonstrate
31 * texture-from-pixmap.
32 */
33
34#include <assert.h>
35#include <stdlib.h>
36#include <stdio.h>
37#include <string.h>
38#include <unistd.h> /* for usleep */
39#include <sys/time.h> /* for gettimeofday */
40#include <X11/Xlib.h>
41#include <X11/Xutil.h>
42#include <X11/keysym.h>
43#include <GLES/gl.h>
44#include <GLES/glext.h>
45#include <EGL/egl.h>
46#include <EGL/eglext.h>
47
48struct app_data {
49   /* native */
50   Display *xdpy;
51   Window canvas, cube;
52   Pixmap pix;
53   unsigned int width, height, depth;
54   GC fg, bg;
55
56   /* EGL */
57   EGLDisplay dpy;
58   EGLContext ctx;
59   EGLSurface surf;
60   EGLImageKHR img;
61
62   /* OpenGL ES */
63   GLenum target;
64   GLuint texture;
65
66   PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
67   PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
68   PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
69
70   /* app state */
71   Bool loop;
72   Bool redraw, reshape;
73
74   struct {
75      Bool active;
76      unsigned long next_frame; /* in ms */
77      float view_rotx;
78      float view_roty;
79      float view_rotz;
80
81   } animate;
82
83   struct {
84      Bool active;
85      int x1, y1;
86      int x2, y2;
87   } paint;
88};
89
90static void
91gl_redraw(void)
92{
93   const GLfloat verts[4][2] = {
94      { -1, -1 },
95      {  1, -1 },
96      {  1,  1 },
97      { -1,  1 }
98   };
99   const GLfloat texcoords[4][2] = {
100      { 0, 1 },
101      { 1, 1 },
102      { 1, 0 },
103      { 0, 0 }
104   };
105   const GLfloat faces[6][4] = {
106      {   0, 0, 1, 0 },
107      {  90, 0, 1, 0 },
108      { 180, 0, 1, 0 },
109      { 270, 0, 1, 0 },
110      {  90, 1, 0, 0 },
111      { -90, 1, 0, 0 }
112   };
113   GLint i;
114
115   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
116
117   glVertexPointer(2, GL_FLOAT, 0, verts);
118   glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
119
120   glEnableClientState(GL_VERTEX_ARRAY);
121   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
122
123   for (i = 0; i < 6; i++) {
124      glPushMatrix();
125      glRotatef(faces[i][0], faces[i][1], faces[i][2], faces[i][3]);
126      glTranslatef(0, 0, 1.0);
127      glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
128      glPopMatrix();
129   }
130
131   glDisableClientState(GL_VERTEX_ARRAY);
132   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
133}
134
135static void
136gl_reshape(int width, int height)
137{
138   GLfloat ar = (GLfloat) width / (GLfloat) height;
139
140   glViewport(0, 0, width, height);
141
142   glMatrixMode(GL_PROJECTION);
143   glLoadIdentity();
144   glFrustumf(-ar, ar, -1, 1, 5.0, 60.0);
145
146   glMatrixMode(GL_MODELVIEW);
147   glLoadIdentity();
148   glTranslatef(0.0, 0.0, -10.0);
149}
150
151static void
152app_redraw(struct app_data *data)
153{
154   /* pixmap has changed */
155   if (data->reshape || data->paint.active) {
156      /*
157       * The extension only states that
158       *
159       *   If an application specifies an EGLImage sibling as the destination
160       *   for rendering and/or pixel download operations (e.g., as an
161       *   OpenGL-ES framebuffer object, glTexSubImage2D, etc.), the modified
162       *   image results will be observed by all EGLImage siblings in all
163       *   client API contexts.
164       *
165       * Though not required by the drivers I tested, I think the rules of
166       * "Propagating Changes to Objects" should apply here.  That is, the
167       * changes made by the native engine must be completed and the resource
168       * must be re-attached.
169       */
170      eglWaitNative(EGL_CORE_NATIVE_ENGINE);
171      data->glEGLImageTargetTexture2DOES(data->target,
172            (GLeglImageOES) data->img);
173   }
174
175   XCopyArea(data->xdpy, data->pix, data->canvas, data->fg,
176         0, 0, data->width, data->height, 0, 0);
177
178   glPushMatrix();
179   glRotatef(data->animate.view_rotx, 1, 0, 0);
180   glRotatef(data->animate.view_roty, 0, 1, 0);
181   glRotatef(data->animate.view_rotz, 0, 0, 1);
182   gl_redraw();
183   glPopMatrix();
184
185   eglSwapBuffers(data->dpy, data->surf);
186}
187
188static void
189app_reshape(struct app_data *data)
190{
191   const EGLint img_attribs[] = {
192      EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
193      EGL_NONE
194   };
195
196   XResizeWindow(data->xdpy, data->cube, data->width, data->height);
197   XMoveWindow(data->xdpy, data->cube, data->width, 0);
198
199   if (data->img)
200      data->eglDestroyImageKHR(data->dpy, data->img);
201   if (data->pix)
202      XFreePixmap(data->xdpy, data->pix);
203
204   data->pix = XCreatePixmap(data->xdpy, data->canvas, data->width, data->height, data->depth);
205   XFillRectangle(data->xdpy, data->pix, data->bg, 0, 0, data->width, data->height);
206
207   data->img = data->eglCreateImageKHR(data->dpy, EGL_NO_CONTEXT,
208         EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer) data->pix, img_attribs);
209
210   gl_reshape(data->width, data->height);
211}
212
213static void
214app_toggle_animate(struct app_data *data)
215{
216   data->animate.active = !data->animate.active;
217
218   if (data->animate.active) {
219      struct timeval tv;
220
221      gettimeofday(&tv, NULL);
222      data->animate.next_frame = tv.tv_sec * 1000 + tv.tv_usec / 1000;
223   }
224}
225
226static void
227app_next_event(struct app_data *data)
228{
229   XEvent event;
230
231   data->reshape = False;
232   data->redraw = False;
233   data->paint.active = False;
234
235   if (data->animate.active) {
236      struct timeval tv;
237      unsigned long now;
238
239      gettimeofday(&tv, NULL);
240      now = tv.tv_sec * 1000 + tv.tv_usec / 1000;
241
242      /* wait for next frame */
243      if (!XPending(data->xdpy) && now < data->animate.next_frame) {
244         usleep((data->animate.next_frame - now) * 1000);
245         gettimeofday(&tv, NULL);
246         now = tv.tv_sec * 1000 + tv.tv_usec / 1000;
247      }
248
249      while (now >= data->animate.next_frame) {
250         data->animate.view_rotx += 1.0;
251         data->animate.view_roty += 2.0;
252         data->animate.view_rotz += 1.5;
253
254         /* 30fps */
255         data->animate.next_frame += 1000 / 30;
256      }
257
258      /* check again in case there were events when sleeping */
259      if (!XPending(data->xdpy)) {
260         data->redraw = True;
261         return;
262      }
263   }
264
265   XNextEvent(data->xdpy, &event);
266
267   switch (event.type) {
268   case ConfigureNotify:
269      data->width = event.xconfigure.width / 2;
270      data->height = event.xconfigure.height;
271      data->reshape = True;
272      break;
273   case Expose:
274      data->redraw = True;
275      break;
276   case KeyPress:
277      {
278         int code;
279
280         code = XLookupKeysym(&event.xkey, 0);
281         switch (code) {
282         case XK_a:
283            app_toggle_animate(data);
284            break;
285         case XK_Escape:
286            data->loop = False;
287            break;
288         default:
289            break;
290         }
291      }
292      break;
293   case ButtonPress:
294      data->paint.x1 = data->paint.x2 = event.xbutton.x;
295      data->paint.y1 = data->paint.y2 = event.xbutton.y;
296      break;
297   case ButtonRelease:
298      data->paint.active = False;
299      break;
300   case MotionNotify:
301      data->paint.x1 = data->paint.x2;
302      data->paint.y1 = data->paint.y2;
303      data->paint.x2 = event.xmotion.x;
304      data->paint.y2 = event.xmotion.y;
305      data->paint.active = True;
306      break;
307   default:
308      break;
309   }
310
311   if (data->paint.active || data->reshape)
312      data->redraw = True;
313}
314
315static void
316app_init_gl(struct app_data *data)
317{
318   glClearColor(0.1, 0.1, 0.3, 0.0);
319   glColor4f(1.0, 1.0, 1.0, 1.0);
320
321   glGenTextures(1, &data->texture);
322
323   glBindTexture(data->target, data->texture);
324   glTexParameteri(data->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
325   glTexParameteri(data->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
326
327   glEnable(data->target);
328   glEnable(GL_DEPTH_TEST);
329}
330
331static Bool
332app_init_exts(struct app_data *data)
333{
334   const char *exts;
335
336   exts = eglQueryString(data->dpy, EGL_EXTENSIONS);
337   data->eglCreateImageKHR =
338      (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
339   data->eglDestroyImageKHR =
340      (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
341   if (!exts || !strstr(exts, "EGL_KHR_image_pixmap") ||
342       !data->eglCreateImageKHR || !data->eglDestroyImageKHR) {
343      printf("EGL does not support EGL_KHR_image_pixmap\n");
344      return False;
345   }
346
347   data->target = 0;
348   exts = (const char *) glGetString(GL_EXTENSIONS);
349   data->glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
350      eglGetProcAddress("glEGLImageTargetTexture2DOES");
351   if (exts && data->glEGLImageTargetTexture2DOES) {
352      if (strstr(exts, "GL_OES_EGL_image"))
353         data->target = GL_TEXTURE_2D;
354#ifdef GL_OES_EGL_image_external
355      /* prefer external texture */
356      if (strstr(exts, "GL_OES_EGL_image_external"))
357         data->target = GL_TEXTURE_EXTERNAL_OES;
358#endif
359   }
360
361   if (!data->target) {
362      printf("OpenGL ES does not support sampling from an EGLImage\n");
363      return False;
364   }
365
366   return True;
367}
368
369static void
370app_run(struct app_data *data)
371{
372   Window root;
373   int x, y;
374   unsigned int border;
375
376   if (!eglMakeCurrent(data->dpy, data->surf, data->surf, data->ctx))
377      return;
378
379   if (!app_init_exts(data))
380      return;
381
382   printf("Draw something on the left with the mouse!\n");
383
384   app_init_gl(data);
385
386   if (!XGetGeometry(data->xdpy, data->canvas, &root, &x, &y,
387            &data->width, &data->height, &border, &data->depth))
388      return;
389   data->width /= 2;
390
391   app_reshape(data);
392
393   XMapWindow(data->xdpy, data->canvas);
394   XMapWindow(data->xdpy, data->cube);
395
396   app_toggle_animate(data);
397   data->loop = True;
398
399   while (data->loop) {
400      app_next_event(data);
401
402      if (data->reshape)
403         app_reshape(data);
404      if (data->paint.active) {
405         XDrawLine(data->xdpy, data->pix, data->fg,
406               data->paint.x1, data->paint.y1,
407               data->paint.x2, data->paint.y2);
408      }
409
410      if (data->redraw)
411         app_redraw(data);
412   }
413
414   eglMakeCurrent(data->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
415}
416
417static Bool
418make_x_window(struct app_data *data, const char *name,
419              int x, int y, int width, int height)
420{
421   static const EGLint attribs[] = {
422      EGL_RED_SIZE, 1,
423      EGL_GREEN_SIZE, 1,
424      EGL_BLUE_SIZE, 1,
425      EGL_DEPTH_SIZE, 1,
426      EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
427      EGL_NONE
428   };
429   static const EGLint ctx_attribs[] = {
430      EGL_CONTEXT_CLIENT_VERSION, 1,
431      EGL_NONE
432   };
433   int scrnum;
434   XSetWindowAttributes attr;
435   unsigned long mask;
436   Window root;
437   Window win;
438   XVisualInfo *visInfo, visTemplate;
439   int num_visuals;
440   EGLConfig config;
441   EGLint num_configs;
442   EGLint vid;
443
444   scrnum = DefaultScreen( data->xdpy );
445   root = RootWindow( data->xdpy, scrnum );
446
447   if (!eglChooseConfig( data->dpy, attribs, &config, 1, &num_configs)) {
448      printf("Error: couldn't get an EGL visual config\n");
449      exit(1);
450   }
451
452   assert(config);
453   assert(num_configs > 0);
454
455   if (!eglGetConfigAttrib(data->dpy, config, EGL_NATIVE_VISUAL_ID, &vid)) {
456      printf("Error: eglGetConfigAttrib() failed\n");
457      exit(1);
458   }
459
460   /* The X window visual must match the EGL config */
461   visTemplate.visualid = vid;
462   visInfo = XGetVisualInfo(data->xdpy, VisualIDMask, &visTemplate, &num_visuals);
463   if (!visInfo) {
464      printf("Error: couldn't get X visual\n");
465      exit(1);
466   }
467
468   /* window attributes */
469   attr.background_pixel = 0;
470   attr.border_pixel = 0;
471   attr.colormap = XCreateColormap( data->xdpy, root, visInfo->visual, AllocNone);
472   attr.event_mask = StructureNotifyMask | ExposureMask |
473                     KeyPressMask | ButtonPressMask | ButtonMotionMask;
474   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
475
476   win = XCreateWindow( data->xdpy, root, 0, 0, width * 2, height,
477		        0, visInfo->depth, InputOutput,
478		        visInfo->visual, mask, &attr );
479
480   /* set hints and properties */
481   {
482      XSizeHints sizehints;
483      sizehints.x = x;
484      sizehints.y = y;
485      sizehints.width  = width;
486      sizehints.height = height;
487      sizehints.flags = USSize | USPosition;
488      XSetNormalHints(data->xdpy, win, &sizehints);
489      XSetStandardProperties(data->xdpy, win, name, name,
490                              None, (char **)NULL, 0, &sizehints);
491   }
492
493   data->canvas = win;
494
495   attr.event_mask = 0x0;
496   win = XCreateWindow( data->xdpy, win, width, 0, width, height,
497		        0, visInfo->depth, InputOutput,
498		        visInfo->visual, mask, &attr );
499   data->cube = win;
500
501   eglBindAPI(EGL_OPENGL_ES_API);
502
503   data->ctx = eglCreateContext(data->dpy, config, EGL_NO_CONTEXT, ctx_attribs );
504   if (!data->ctx) {
505      printf("Error: eglCreateContext failed\n");
506      exit(1);
507   }
508
509   data->surf = eglCreateWindowSurface(data->dpy, config, data->cube, NULL);
510   if (!data->surf) {
511      printf("Error: eglCreateWindowSurface failed\n");
512      exit(1);
513   }
514
515   XFree(visInfo);
516
517   return (data->canvas && data->cube && data->ctx && data->surf);
518}
519
520static void
521app_fini(struct app_data *data)
522{
523   if (data->img)
524      data->eglDestroyImageKHR(data->dpy, data->img);
525   if (data->pix)
526      XFreePixmap(data->xdpy, data->pix);
527
528   if (data->fg)
529      XFreeGC(data->xdpy, data->fg);
530   if (data->bg)
531      XFreeGC(data->xdpy, data->bg);
532
533   if (data->surf)
534      eglDestroySurface(data->dpy, data->surf);
535   if (data->ctx)
536      eglDestroyContext(data->dpy, data->ctx);
537
538   if (data->cube)
539      XDestroyWindow(data->xdpy, data->cube);
540   if (data->canvas)
541      XDestroyWindow(data->xdpy, data->canvas);
542
543   if (data->dpy)
544      eglTerminate(data->dpy);
545   if (data->xdpy)
546      XCloseDisplay(data->xdpy);
547}
548
549static Bool
550app_init(struct app_data *data, int argc, char **argv)
551{
552   XGCValues gc_vals;
553
554   memset(data, 0, sizeof(*data));
555
556   data->xdpy = XOpenDisplay(NULL);
557   if (!data->xdpy)
558      goto fail;
559
560   data->dpy = eglGetDisplay(data->xdpy);
561   if (!data->dpy || !eglInitialize(data->dpy, NULL, NULL))
562      goto fail;
563
564   if (!make_x_window(data, "EGLImage TFP", 0, 0, 300, 300))
565      goto fail;
566
567   gc_vals.function = GXcopy;
568   gc_vals.foreground = WhitePixel(data->xdpy, DefaultScreen(data->xdpy));
569   gc_vals.line_width = 3;
570   gc_vals.line_style = LineSolid;
571   gc_vals.fill_style = FillSolid;
572
573   data->fg = XCreateGC(data->xdpy, data->canvas,
574         GCFunction | GCForeground | GCLineWidth | GCLineStyle | GCFillStyle,
575         &gc_vals);
576   gc_vals.foreground = BlackPixel(data->xdpy, DefaultScreen(data->xdpy));
577   data->bg = XCreateGC(data->xdpy, data->canvas,
578         GCFunction | GCForeground | GCLineWidth | GCLineStyle | GCFillStyle,
579         &gc_vals);
580   if (!data->fg || !data->bg)
581      goto fail;
582
583   return True;
584
585fail:
586   app_fini(data);
587   return False;
588}
589
590int
591main(int argc, char **argv)
592{
593   struct app_data data;
594
595   if (app_init(&data, argc, argv)) {
596      app_run(&data);
597      app_fini(&data);
598   }
599
600   return 0;
601}
602