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