1 /*
2     Copyright (c) 2005-2020 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 // Uncomment next line to disable shared memory features if you do not have libXext
18 // (http://www.xfree86.org/current/mit-shm.html)
19 //#define X_NOSHMEM
20 
21 // Note that it may happen that the build environment supports the shared-memory extension
22 // (so there's no build-time reason to disable the relevant code by defining X_NOSHMEM),
23 // but that using shared memory still fails at run time.
24 // This situation will (ultimately) cause the error handler set by XSetErrorHandler()
25 // to be invoked with XErrorEvent::minor_code==X_ShmAttach. The code below tries to make
26 // such a determination at XShmAttach() time, which seems plausible, but unfortunately
27 // it has also been observed in a specific environment that the error may be reported
28 // at a later time instead, even after video::init_window() has returned.
29 // It is not clear whether this may happen in that way in any environment where it might
30 // depend on the kind of display, e.g., local vs. over "ssh -X", so #define'ing X_NOSHMEM
31 // may not always be the appropriate solution, therefore an environment variable
32 // has been introduced to disable shared memory at run time.
33 // A diagnostic has been added to advise the user about possible workarounds.
34 // X_ShmAttach macro was changed to 1 due to recent changes to X11/extensions/XShm.h header.
35 
36 #include "video.h"
37 #include <string.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <math.h>
41 #include <X11/Xlib.h>
42 #include <X11/Xutil.h>
43 #include <X11/keysym.h>
44 #include <sys/time.h>
45 #include <signal.h>
46 #include <pthread.h>
47 
48 #ifndef X_NOSHMEM
49 #include <errno.h>
50 #include <X11/extensions/XShm.h>
51 #include <sys/ipc.h>
52 #include <sys/shm.h>
53 
54 static XShmSegmentInfo shmseginfo;
55 static Pixmap pixmap = 0;
56 static bool already_called_X_ShmAttach = false;
57 static bool already_advised_about_NOSHMEM_workarounds = false;
58 static const char* NOSHMEM_env_var_name = "TBB_EXAMPLES_X_NOSHMEM";
59 #endif
60 static char *display_name = NULL;
61 static Display *dpy = NULL;
62 static Screen *scrn;
63 static Visual *vis;
64 static Colormap cmap;
65 static GC gc;
66 static Window win, rootW;
67 static int dispdepth = 0;
68 static XGCValues xgcv;
69 static XImage *ximage;
70 static int x_error = 0;
71 static int vidtype = 3;
72 int g_sizex, g_sizey;
73 static video *g_video = 0;
74 unsigned int *g_pImg = 0;
75 static int g_fps = 0;
76 struct timeval g_time;
77 static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
78 Atom _XA_WM_DELETE_WINDOW = 0;// like in Xatom.h
79 
80 ///////////////////////////////////////////// public methods of video class ///////////////////////
81 
video()82 video::video()
83 {
84     assert(g_video == 0);
85     g_video = this; title = "Video"; calc_fps = running = false; updating = true;
86 }
87 
mask2bits(unsigned int mask,unsigned int & save,depth_t & shift)88 inline void mask2bits(unsigned int mask, unsigned int &save, depth_t &shift)
89 {
90     save  = mask; if(!mask) { shift = dispdepth/3; return; }
91     shift = 0; while(!(mask&1)) ++shift, mask >>= 1;
92     int bits = 0; while(mask&1) ++bits,  mask >>= 1;
93     shift += bits - 8;
94 }
95 
xerr_handler(Display * dpy_,XErrorEvent * error)96 int xerr_handler(Display* dpy_, XErrorEvent *error)
97 {
98     x_error = error->error_code;
99     if(g_video) g_video->running = false;
100 #ifndef X_NOSHMEM
101     if (error->minor_code==1/*X_ShmAttach*/ && already_called_X_ShmAttach && !already_advised_about_NOSHMEM_workarounds)
102     {
103         char err[256]; XGetErrorText(dpy_, x_error, err, 255);
104         fprintf(stderr, "Warning: Can't attach shared memory to display: %s (%d)\n", err, x_error);
105         fprintf(stderr, "If you are seeing a black output window, try setting %s environment variable to 1"
106                         " to disable shared memory extensions (0 to re-enable, other values undefined),"
107                         " or rebuilding with X_NOSHMEM defined in " __FILE__ "\n", NOSHMEM_env_var_name);
108         already_advised_about_NOSHMEM_workarounds = true;
109     }
110 #else
111     (void) dpy_; // warning prevention
112 #endif
113     return 0;
114 }
115 
init_window(int xsize,int ysize)116 bool video::init_window(int xsize, int ysize)
117 {
118     { //enclose local variables before fail label
119     g_sizex = xsize; g_sizey = ysize;
120 
121     // Open the display
122     if (!dpy) {
123         dpy = XOpenDisplay(display_name);
124         if (!dpy) {
125             fprintf(stderr, "Can't open X11 display %s\n", XDisplayName(display_name));
126             goto fail;
127         }
128     }
129     int theScreen = DefaultScreen(dpy);
130     scrn = ScreenOfDisplay(dpy, theScreen);
131     dispdepth = DefaultDepth(dpy, theScreen);
132     XVisualInfo vinfo;
133     if (!( (dispdepth >= 15 && dispdepth <= 32 && XMatchVisualInfo(dpy, theScreen, dispdepth, TrueColor, &vinfo) )
134         || XMatchVisualInfo(dpy, theScreen, 24, TrueColor, &vinfo)
135         || XMatchVisualInfo(dpy, theScreen, 32, TrueColor, &vinfo)
136         || XMatchVisualInfo(dpy, theScreen, 16, TrueColor, &vinfo)
137         || XMatchVisualInfo(dpy, theScreen, 15, TrueColor, &vinfo)
138         )) {
139         fprintf(stderr, "Display has no appropriate True Color visual\n");
140         goto fail;
141     }
142     vis = vinfo.visual;
143     depth = dispdepth = vinfo.depth;
144     mask2bits(vinfo.red_mask, red_mask, red_shift);
145     mask2bits(vinfo.green_mask, green_mask, green_shift);
146     mask2bits(vinfo.blue_mask, blue_mask, blue_shift);
147     rootW = RootWindow(dpy, theScreen);
148     cmap = XCreateColormap(dpy, rootW, vis, AllocNone);
149     XSetWindowAttributes attrs;
150     attrs.backing_store = Always;
151     attrs.colormap = cmap;
152     attrs.event_mask = StructureNotifyMask|KeyPressMask|ButtonPressMask|ButtonReleaseMask;
153     attrs.background_pixel = BlackPixelOfScreen(scrn);
154     attrs.border_pixel = WhitePixelOfScreen(scrn);
155     win = XCreateWindow(dpy, rootW,
156         0, 0, xsize, ysize, 2,
157         dispdepth, InputOutput, vis,
158         CWBackingStore | CWColormap | CWEventMask |
159         CWBackPixel | CWBorderPixel,
160         &attrs);
161     if(!win) {
162         fprintf(stderr, "Can't create the window\n");
163         goto fail;
164     }
165     XSizeHints sh;
166     sh.flags = PSize | PMinSize | PMaxSize;
167     sh.width = sh.min_width = sh.max_width = xsize;
168     sh.height = sh.min_height = sh.max_height = ysize;
169     XSetStandardProperties( dpy, win, g_video->title, g_video->title, None, NULL, 0, &sh );
170     _XA_WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", false);
171     XSetWMProtocols(dpy, win, &_XA_WM_DELETE_WINDOW, 1);
172     gc = XCreateGC(dpy, win, 0L, &xgcv);
173     XMapRaised(dpy, win);
174     XFlush(dpy);
175 #ifdef X_FULLSYNC
176     XSynchronize(dpy, true);
177 #endif
178     XSetErrorHandler(xerr_handler);
179 
180     int imgbytes = xsize*ysize*(dispdepth<=16?2:4);
181     const char *vidstr;
182 #ifndef X_NOSHMEM
183     int major, minor, pixmaps;
184     if(XShmQueryExtension(dpy) &&
185        XShmQueryVersion(dpy, &major, &minor, &pixmaps))
186     { // Shared memory
187         if(NULL!=getenv(NOSHMEM_env_var_name) && 0!=strcmp("0",getenv(NOSHMEM_env_var_name))) {
188             goto generic;
189         }
190         shmseginfo.shmid = shmget(IPC_PRIVATE, imgbytes, IPC_CREAT|0777);
191         if(shmseginfo.shmid < 0) {
192             fprintf(stderr, "Warning: Can't get shared memory: %s\n", strerror(errno));
193             goto generic;
194         }
195         g_pImg = (unsigned int*)(shmseginfo.shmaddr = (char*)shmat(shmseginfo.shmid, 0, 0));
196         if(g_pImg == (unsigned int*)-1) {
197             fprintf(stderr, "Warning: Can't attach to shared memory: %s\n", strerror(errno));
198             shmctl(shmseginfo.shmid, IPC_RMID, NULL);
199             goto generic;
200         }
201         shmseginfo.readOnly = false;
202         if(!XShmAttach(dpy, &shmseginfo) || x_error) {
203             char err[256]; XGetErrorText(dpy, x_error, err, 255);
204             fprintf(stderr, "Warning: Can't attach shared memory to display: %s (%d)\n", err, x_error);
205             shmdt(shmseginfo.shmaddr); shmctl(shmseginfo.shmid, IPC_RMID, NULL);
206             goto generic;
207         }
208         already_called_X_ShmAttach = true;
209 
210 #ifndef X_NOSHMPIX
211         if(pixmaps && XShmPixmapFormat(dpy) == ZPixmap)
212         { // Pixmaps
213             vidtype = 2; vidstr = "X11 shared memory pixmap";
214             pixmap = XShmCreatePixmap(dpy, win, (char*)g_pImg, &shmseginfo, xsize, ysize, dispdepth);
215             XSetWindowBackgroundPixmap(dpy, win, pixmap);
216         } else
217 #endif//!X_NOSHMPIX
218         { // Standard
219             vidtype = 1; vidstr = "X11 shared memory";
220             ximage = XShmCreateImage(dpy, vis, dispdepth,
221                 ZPixmap, 0, &shmseginfo, xsize, ysize);
222             if(!ximage) {
223                 fprintf(stderr, "Can't create the shared image\n");
224                 goto fail;
225             }
226             assert(ximage->bytes_per_line == xsize*(dispdepth<=16?2:4));
227             ximage->data = shmseginfo.shmaddr;
228         }
229     } else
230 #endif
231     {
232 #ifndef X_NOSHMEM
233 generic:
234 #endif
235         vidtype = 0; vidstr = "generic X11";
236         g_pImg = new unsigned int[imgbytes/sizeof(int)];
237         ximage = XCreateImage(dpy, vis, dispdepth, ZPixmap, 0, (char*)g_pImg, xsize, ysize, 32, imgbytes/ysize);
238         if(!ximage) {
239             fprintf(stderr, "Can't create the image\n");
240             goto fail;
241         }
242     }
243     if( ximage ) {
244         // Note: It may be more efficient to adopt the server's byte order
245         //       and swap once per get_color() call instead of once per pixel.
246         const uint32_t probe = 0x03020100;
247         const bool big_endian = (((const char*)(&probe))[0]==0x03);
248         ximage->byte_order = big_endian ? MSBFirst : LSBFirst;
249     }
250     printf("Note: using %s with %s visual for %d-bit color depth\n", vidstr, vis==DefaultVisual(dpy, theScreen)?"default":"non-default", dispdepth);
251     running = true;
252     return true;
253     } // end of enclosing local variables
254 fail:
255     terminate(); init_console();
256     return false;
257 }
258 
init_console()259 bool video::init_console()
260 {
261     if(!g_pImg && g_sizex && g_sizey) {
262         dispdepth = 24; red_shift = 16; vidtype = 3; // fake video
263         g_pImg = new unsigned int[g_sizex*g_sizey];
264         running = true;
265     }
266     return true;
267 }
268 
terminate()269 void video::terminate()
270 {
271     running = false;
272     if(dpy) {
273         vidtype = 3; // stop video
274         if(threaded) { pthread_mutex_lock(&g_mutex); pthread_mutex_unlock(&g_mutex); }
275         if(ximage) { XDestroyImage(ximage); ximage = 0; g_pImg = 0; } // it frees g_pImg for vidtype == 0
276 #ifndef X_NOSHMEM
277         if(pixmap) XFreePixmap(dpy, pixmap);
278         if(shmseginfo.shmaddr) { XShmDetach(dpy, &shmseginfo); shmdt(shmseginfo.shmaddr); g_pImg = 0; }
279         if(shmseginfo.shmid >= 0) shmctl(shmseginfo.shmid, IPC_RMID, NULL);
280 #endif
281         if(gc) XFreeGC(dpy, gc);
282         if(win) XDestroyWindow(dpy, win);
283         XCloseDisplay(dpy); dpy = 0;
284     }
285     if(g_pImg) { delete[] g_pImg; g_pImg = 0; } // if was allocated for console mode
286 }
287 
~video()288 video::~video()
289 {
290     if(g_video) terminate();
291     g_video = 0;
292 }
293 
294 //! Do standard event loop
main_loop()295 void video::main_loop()
296 {
297     struct timezone tz; gettimeofday(&g_time, &tz);
298     on_process();
299 }
300 
301 //! Check for pending events once
next_frame()302 bool video::next_frame()
303 {
304     if(!running) return false;
305     //! try acquire mutex if threaded code, returns on failure
306     if(vidtype == 3 || threaded && pthread_mutex_trylock(&g_mutex))
307         return running;
308     //! Refresh screen picture
309     g_fps++;
310 #ifndef X_NOSHMPIX
311     if(vidtype == 2 && updating) XClearWindow(dpy, win);
312 #endif
313     while( XPending(dpy) ) {
314         XEvent report; XNextEvent(dpy, &report);
315         switch( report.type ) {
316             case ClientMessage:
317                 if(report.xclient.format != 32 || report.xclient.data.l[0] != _XA_WM_DELETE_WINDOW) break;
318             case DestroyNotify:
319                 running = false;
320             case KeyPress:
321                 on_key( XLookupKeysym(&report.xkey, 0) ); break;
322             case ButtonPress:
323                 on_mouse( report.xbutton.x, report.xbutton.y, report.xbutton.button ); break;
324             case ButtonRelease:
325                 on_mouse( report.xbutton.x, report.xbutton.y, -report.xbutton.button ); break;
326         }
327     }
328     struct timezone tz; struct timeval now_time; gettimeofday(&now_time, &tz);
329     double sec = (now_time.tv_sec+1.0*now_time.tv_usec/1000000.0) - (g_time.tv_sec+1.0*g_time.tv_usec/1000000.0);
330     if(sec > 1) {
331         memcpy(&g_time, &now_time, sizeof(g_time));
332         if(calc_fps) {
333             double fps = g_fps; g_fps = 0;
334             char buffer[256]; snprintf(buffer, 256, "%s%s: %d fps", title, updating?"":" (no updating)", int(fps/sec));
335             XStoreName(dpy, win, buffer);
336         }
337 #ifndef X_FULLSYNC
338         XSync(dpy, false); // It is often better then using XSynchronize(dpy, true)
339 #endif//X_FULLSYNC
340     }
341     if(threaded) pthread_mutex_unlock(&g_mutex);
342     return true;
343 }
344 
345 //! Change window title
show_title()346 void video::show_title()
347 {
348     if(vidtype < 3)
349         XStoreName(dpy, win, title);
350 }
351 
drawing_area(int x,int y,int sizex,int sizey)352 drawing_area::drawing_area(int x, int y, int sizex, int sizey)
353     : base_index(y*g_sizex + x), max_index(g_sizex*g_sizey), index_stride(g_sizex),
354     pixel_depth(dispdepth), ptr32(g_pImg), start_x(x), start_y(y), size_x(sizex), size_y(sizey)
355 {
356     assert(x < g_sizex); assert(y < g_sizey);
357     assert(x+sizex <= g_sizex); assert(y+sizey <= g_sizey);
358 
359     index = base_index; // current index
360 }
361 
update()362 void drawing_area::update()
363 {
364     if(!g_video->updating) return;
365 #ifndef X_NOSHMEM
366     switch(vidtype) {
367     case 0:
368 #endif
369         pthread_mutex_lock(&g_mutex);
370         if(vidtype == 0) XPutImage(dpy, win, gc, ximage, start_x, start_y, start_x, start_y, size_x, size_y);
371         pthread_mutex_unlock(&g_mutex);
372 #ifndef X_NOSHMEM
373         break;
374     case 1:
375         pthread_mutex_lock(&g_mutex);
376         if(vidtype == 1) XShmPutImage(dpy, win, gc, ximage, start_x, start_y, start_x, start_y, size_x, size_y, false);
377         pthread_mutex_unlock(&g_mutex);
378         break;
379     /*case 2: make it in next_frame(); break;*/
380     }
381 #endif
382 }
383