xref: /oneTBB/examples/common/gui/xvideo.cpp (revision c21e688a)
1 /*
2     Copyright (c) 2005-2022 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.hpp"
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 #endif
59 static char *display_name = nullptr;
60 static Display *dpy = nullptr;
61 static Screen *scrn;
62 static Visual *vis;
63 static Colormap cmap;
64 static GC gc;
65 static Window win, rootW;
66 static int dispdepth = 0;
67 static XGCValues xgcv;
68 static XImage *ximage;
69 static int x_error = 0;
70 static int vidtype = 3;
71 int g_sizex, g_sizey;
72 static video *g_video = 0;
73 unsigned int *g_pImg = 0;
74 static int g_fps = 0;
75 struct timeval g_time;
76 static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
77 Atom _XA_WM_DELETE_WINDOW = 0; // like in Xatom.h
78 
79 ///////////////////////////////////////////// public methods of video class ///////////////////////
80 
video()81 video::video() {
82     assert(g_video == 0);
83     g_video = this;
84     title = "Video";
85     calc_fps = running = false;
86     updating = true;
87 }
88 
mask2bits(unsigned int mask,unsigned int & save,depth_t & shift)89 inline void mask2bits(unsigned int mask, unsigned int &save, depth_t &shift) {
90     save = mask;
91     if (!mask) {
92         shift = dispdepth / 3;
93         return;
94     }
95     shift = 0;
96     while (!(mask & 1))
97         ++shift, mask >>= 1;
98     int bits = 0;
99     while (mask & 1)
100         ++bits, mask >>= 1;
101     shift += bits - 8;
102 }
103 
xerr_handler(Display * dpy_,XErrorEvent * error)104 int xerr_handler(Display *dpy_, XErrorEvent *error) {
105     x_error = error->error_code;
106     if (g_video)
107         g_video->running = false;
108 #ifndef X_NOSHMEM
109     if (error->minor_code == 1 /*X_ShmAttach*/ && already_called_X_ShmAttach &&
110         !already_advised_about_NOSHMEM_workarounds) {
111         char err[256];
112         XGetErrorText(dpy_, x_error, err, 255);
113         fprintf(stderr, "Warning: Can't attach shared memory to display: %s (%d)\n", err, x_error);
114         fprintf(
115             stderr,
116             "If you are seeing a black output window, сheck if you have installed Xext library and rebuild project");
117         already_advised_about_NOSHMEM_workarounds = true;
118     }
119 #else
120     (void)dpy_; // warning prevention
121 #endif
122     return 0;
123 }
124 
init_window(int xsize,int ysize)125 bool video::init_window(int xsize, int ysize) {
126     { //enclose local variables before fail label
127         g_sizex = xsize;
128         g_sizey = ysize;
129 
130         // Open the display
131         if (!dpy) {
132             dpy = XOpenDisplay(display_name);
133             if (!dpy) {
134                 fprintf(stderr, "Can't open X11 display %s\n", XDisplayName(display_name));
135                 goto fail;
136             }
137         }
138         int theScreen = DefaultScreen(dpy);
139         scrn = ScreenOfDisplay(dpy, theScreen);
140         dispdepth = DefaultDepth(dpy, theScreen);
141         XVisualInfo vinfo;
142         if (!((dispdepth >= 15 && dispdepth <= 32 &&
143                XMatchVisualInfo(dpy, theScreen, dispdepth, TrueColor, &vinfo)) ||
144               XMatchVisualInfo(dpy, theScreen, 24, TrueColor, &vinfo) ||
145               XMatchVisualInfo(dpy, theScreen, 32, TrueColor, &vinfo) ||
146               XMatchVisualInfo(dpy, theScreen, 16, TrueColor, &vinfo) ||
147               XMatchVisualInfo(dpy, theScreen, 15, TrueColor, &vinfo))) {
148             fprintf(stderr, "Display has no appropriate True Color visual\n");
149             goto fail;
150         }
151         vis = vinfo.visual;
152         depth = dispdepth = vinfo.depth;
153         mask2bits(vinfo.red_mask, red_mask, red_shift);
154         mask2bits(vinfo.green_mask, green_mask, green_shift);
155         mask2bits(vinfo.blue_mask, blue_mask, blue_shift);
156         rootW = RootWindow(dpy, theScreen);
157         cmap = XCreateColormap(dpy, rootW, vis, AllocNone);
158         XSetWindowAttributes attrs;
159         attrs.backing_store = Always;
160         attrs.colormap = cmap;
161         attrs.event_mask = StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask;
162         attrs.background_pixel = BlackPixelOfScreen(scrn);
163         attrs.border_pixel = WhitePixelOfScreen(scrn);
164         win = XCreateWindow(dpy,
165                             rootW,
166                             0,
167                             0,
168                             xsize,
169                             ysize,
170                             2,
171                             dispdepth,
172                             InputOutput,
173                             vis,
174                             CWBackingStore | CWColormap | CWEventMask | CWBackPixel | CWBorderPixel,
175                             &attrs);
176         if (!win) {
177             fprintf(stderr, "Can't create the window\n");
178             goto fail;
179         }
180         XSizeHints sh;
181         sh.flags = PSize | PMinSize | PMaxSize;
182         sh.width = sh.min_width = sh.max_width = xsize;
183         sh.height = sh.min_height = sh.max_height = ysize;
184         XSetStandardProperties(dpy, win, g_video->title, g_video->title, None, nullptr, 0, &sh);
185         _XA_WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", false);
186         XSetWMProtocols(dpy, win, &_XA_WM_DELETE_WINDOW, 1);
187         gc = XCreateGC(dpy, win, 0L, &xgcv);
188         XMapRaised(dpy, win);
189         XFlush(dpy);
190 #ifdef X_FULLSYNC
191         XSynchronize(dpy, true);
192 #endif
193         XSetErrorHandler(xerr_handler);
194 
195         int imgbytes = xsize * ysize * (dispdepth <= 16 ? 2 : 4);
196         const char *vidstr;
197 #ifndef X_NOSHMEM
198         int major, minor, pixmaps;
199         if (XShmQueryExtension(dpy) &&
200             XShmQueryVersion(dpy, &major, &minor, &pixmaps)) { // Shared memory
201             shmseginfo.shmid = shmget(IPC_PRIVATE, imgbytes, IPC_CREAT | 0777);
202             if (shmseginfo.shmid < 0) {
203                 fprintf(stderr, "Warning: Can't get shared memory: %s\n", strerror(errno));
204                 goto generic;
205             }
206             g_pImg = (unsigned int *)(shmseginfo.shmaddr = (char *)shmat(shmseginfo.shmid, 0, 0));
207             if (g_pImg == (unsigned int *)-1) {
208                 fprintf(stderr, "Warning: Can't attach to shared memory: %s\n", strerror(errno));
209                 shmctl(shmseginfo.shmid, IPC_RMID, nullptr);
210                 goto generic;
211             }
212             shmseginfo.readOnly = false;
213             if (!XShmAttach(dpy, &shmseginfo) || x_error) {
214                 char err[256];
215                 XGetErrorText(dpy, x_error, err, 255);
216                 fprintf(stderr,
217                         "Warning: Can't attach shared memory to display: %s (%d)\n",
218                         err,
219                         x_error);
220                 shmdt(shmseginfo.shmaddr);
221                 shmctl(shmseginfo.shmid, IPC_RMID, nullptr);
222                 goto generic;
223             }
224             already_called_X_ShmAttach = true;
225 
226 #ifndef X_NOSHMPIX
227             if (pixmaps && XShmPixmapFormat(dpy) == ZPixmap) { // Pixmaps
228                 vidtype = 2;
229                 vidstr = "X11 shared memory pixmap";
230                 pixmap = XShmCreatePixmap(
231                     dpy, win, (char *)g_pImg, &shmseginfo, xsize, ysize, dispdepth);
232                 XSetWindowBackgroundPixmap(dpy, win, pixmap);
233             }
234             else
235 #endif //!X_NOSHMPIX
236             { // Standard
237                 vidtype = 1;
238                 vidstr = "X11 shared memory";
239                 ximage =
240                     XShmCreateImage(dpy, vis, dispdepth, ZPixmap, 0, &shmseginfo, xsize, ysize);
241                 if (!ximage) {
242                     fprintf(stderr, "Can't create the shared image\n");
243                     goto fail;
244                 }
245                 assert(ximage->bytes_per_line == xsize * (dispdepth <= 16 ? 2 : 4));
246                 ximage->data = shmseginfo.shmaddr;
247             }
248         }
249         else
250 #endif
251         {
252 #ifndef X_NOSHMEM
253         generic:
254 #endif
255             vidtype = 0;
256             vidstr = "generic X11";
257             g_pImg = new unsigned int[imgbytes / sizeof(int)];
258             ximage = XCreateImage(dpy,
259                                   vis,
260                                   dispdepth,
261                                   ZPixmap,
262                                   0,
263                                   (char *)g_pImg,
264                                   xsize,
265                                   ysize,
266                                   32,
267                                   imgbytes / ysize);
268             if (!ximage) {
269                 fprintf(stderr, "Can't create the image\n");
270                 goto fail;
271             }
272         }
273         if (ximage) {
274             // Note: It may be more efficient to adopt the server's byte order
275             //       and swap once per get_color() call instead of once per pixel.
276             const uint32_t probe = 0x03020100;
277             const bool big_endian = (((const char *)(&probe))[0] == 0x03);
278             ximage->byte_order = big_endian ? MSBFirst : LSBFirst;
279         }
280         printf("Note: using %s with %s visual for %d-bit color depth\n",
281                vidstr,
282                vis == DefaultVisual(dpy, theScreen) ? "default" : "non-default",
283                dispdepth);
284         running = true;
285         return true;
286     } // end of enclosing local variables
287 fail:
288     terminate();
289     init_console();
290     return false;
291 }
292 
init_console()293 bool video::init_console() {
294     if (!g_pImg && g_sizex && g_sizey) {
295         dispdepth = 24;
296         red_shift = 16;
297         vidtype = 3; // fake video
298         g_pImg = new unsigned int[g_sizex * g_sizey];
299         running = true;
300     }
301     return true;
302 }
303 
terminate()304 void video::terminate() {
305     running = false;
306     if (dpy) {
307         vidtype = 3; // stop video
308         if (threaded) {
309             pthread_mutex_lock(&g_mutex);
310             pthread_mutex_unlock(&g_mutex);
311         }
312         if (ximage) {
313             XDestroyImage(ximage);
314             ximage = 0;
315             g_pImg = 0;
316         } // it frees g_pImg for vidtype == 0
317 #ifndef X_NOSHMEM
318         if (pixmap)
319             XFreePixmap(dpy, pixmap);
320         if (shmseginfo.shmaddr) {
321             XShmDetach(dpy, &shmseginfo);
322             shmdt(shmseginfo.shmaddr);
323             g_pImg = 0;
324         }
325         if (shmseginfo.shmid >= 0)
326             shmctl(shmseginfo.shmid, IPC_RMID, nullptr);
327 #endif
328         if (gc)
329             XFreeGC(dpy, gc);
330         if (win)
331             XDestroyWindow(dpy, win);
332         XCloseDisplay(dpy);
333         dpy = 0;
334     }
335     if (g_pImg) {
336         delete[] g_pImg;
337         g_pImg = 0;
338     } // if was allocated for console mode
339 }
340 
~video()341 video::~video() {
342     if (g_video)
343         terminate();
344     g_video = 0;
345 }
346 
347 //! Do standard event loop
main_loop()348 void video::main_loop() {
349     struct timezone tz;
350     gettimeofday(&g_time, &tz);
351     on_process();
352 }
353 
354 //! Check for pending events once
next_frame()355 bool video::next_frame() {
356     if (!running)
357         return false;
358     //! try acquire mutex if threaded code, returns on failure
359     if (vidtype == 3 || threaded && pthread_mutex_trylock(&g_mutex))
360         return running;
361     //! Refresh screen picture
362     g_fps++;
363 #ifndef X_NOSHMPIX
364     if (vidtype == 2 && updating)
365         XClearWindow(dpy, win);
366 #endif
367     while (XPending(dpy)) {
368         XEvent report;
369         XNextEvent(dpy, &report);
370         switch (report.type) {
371             case ClientMessage:
372                 if (report.xclient.format != 32 || report.xclient.data.l[0] != _XA_WM_DELETE_WINDOW)
373                     break;
374             case DestroyNotify: running = false;
375             case KeyPress: on_key(XLookupKeysym(&report.xkey, 0)); break;
376             case ButtonPress:
377                 on_mouse(report.xbutton.x, report.xbutton.y, report.xbutton.button);
378                 break;
379             case ButtonRelease:
380                 on_mouse(report.xbutton.x, report.xbutton.y, -report.xbutton.button);
381                 break;
382         }
383     }
384     struct timezone tz;
385     struct timeval now_time;
386     gettimeofday(&now_time, &tz);
387     double sec = (now_time.tv_sec + 1.0 * now_time.tv_usec / 1000000.0) -
388                  (g_time.tv_sec + 1.0 * g_time.tv_usec / 1000000.0);
389     if (sec > 1) {
390         memcpy(&g_time, &now_time, sizeof(g_time));
391         if (calc_fps) {
392             double fps = g_fps;
393             g_fps = 0;
394             char buffer[256];
395             snprintf(buffer,
396                      256,
397                      "%s%s: %d fps",
398                      title,
399                      updating ? "" : " (no updating)",
400                      int(fps / sec));
401             XStoreName(dpy, win, buffer);
402         }
403 #ifndef X_FULLSYNC
404         XSync(dpy, false); // It is often better then using XSynchronize(dpy, true)
405 #endif //X_FULLSYNC
406     }
407     if (threaded)
408         pthread_mutex_unlock(&g_mutex);
409     return true;
410 }
411 
412 //! Change window title
show_title()413 void video::show_title() {
414     if (vidtype < 3)
415         XStoreName(dpy, win, title);
416 }
417 
drawing_area(int x,int y,int sizex,int sizey)418 drawing_area::drawing_area(int x, int y, int sizex, int sizey)
419         : base_index(y * g_sizex + x),
420           max_index(g_sizex * g_sizey),
421           index_stride(g_sizex),
422           pixel_depth(dispdepth),
423           ptr32(g_pImg),
424           start_x(x),
425           start_y(y),
426           size_x(sizex),
427           size_y(sizey) {
428     assert(x < g_sizex);
429     assert(y < g_sizey);
430     assert(x + sizex <= g_sizex);
431     assert(y + sizey <= g_sizey);
432 
433     index = base_index; // current index
434 }
435 
update()436 void drawing_area::update() {
437     if (!g_video->updating)
438         return;
439 #ifndef X_NOSHMEM
440     switch (vidtype) {
441         case 0:
442 #endif
443             pthread_mutex_lock(&g_mutex);
444             if (vidtype == 0)
445                 XPutImage(dpy, win, gc, ximage, start_x, start_y, start_x, start_y, size_x, size_y);
446             pthread_mutex_unlock(&g_mutex);
447 #ifndef X_NOSHMEM
448             break;
449         case 1:
450             pthread_mutex_lock(&g_mutex);
451             if (vidtype == 1)
452                 XShmPutImage(dpy,
453                              win,
454                              gc,
455                              ximage,
456                              start_x,
457                              start_y,
458                              start_x,
459                              start_y,
460                              size_x,
461                              size_y,
462                              false);
463             pthread_mutex_unlock(&g_mutex);
464             break;
465             /*case 2: make it in next_frame(); break;*/
466     }
467 #endif
468 }
469