00001 /*************************************************************************** 00002 *cr 00003 *cr (C) Copyright 1995-2019 The Board of Trustees of the 00004 *cr University of Illinois 00005 *cr All Rights Reserved 00006 *cr 00007 ***************************************************************************/ 00008 /*************************************************************************** 00009 * RCS INFORMATION: 00010 * 00011 * $RCSfile: FltkOpenGLDisplayDevice.C,v $ 00012 * $Author: johns $ $Locker: $ $State: Exp $ 00013 * $Revision: 1.77 $ $Date: 2020年04月23日 07:07:42 $ 00014 * 00015 ***************************************************************************/ 00024 #include <stdio.h> 00025 #include <stdlib.h> 00026 #include <math.h> 00027 #include "FltkOpenGLDisplayDevice.h" 00028 #include "Inform.h" 00029 #include "utilities.h" 00030 #include "config.h" // VMD version strings etc 00031 #include "VMDApp.h" 00032 #include "FL/Fl.H" 00033 #include "FL/Fl_Gl_Window.H" 00034 #include "FL/forms.H" 00035 00036 // FLTK High-DPI display support: 00037 // FLTK versions 1.3.5 and later incorporate Apple-specific extensions 00038 // for retina display support. 00039 // 00040 // On Apple MacOS when High-DPI Retina displays are enabled, 00041 // FLTK apparently uses a hard-coded 2:1 pixel to drawing unit scale factor 00042 // 00043 // High-DPI support on Win32, Win64, and X-Windows require further 00044 // special handling available only in FLTK 1.4.x, which was redesigned with 00045 // a new per-screen scale factor: 00046 // https://www.fltk.org/doc-1.4/drawing.html 00047 // 00048 // On Windows, X-Windows, the window-specific scale factor is: 00049 // int nscreen = windows->screen_num(); 00050 // float s = Fl::screen_scale(nscreen); 00051 // 00052 // The active scale factor can be overridden with the use of the 00053 // FLTK_SCALING_FACTOR environment variable, and FLTK windows 00054 // will respond to (cmd | ctrl)/+/-/0 (like a web browser) to allow 00055 // interactive adjustment of scaling factor. 00056 // FLTK 1.4.x adds Fl_Image::scale() to impact image drawing. 00057 // 00058 // Older discussions: 00059 // https://www.oyranos.org/2016/01/high-dpi-with-fltk/index.html 00060 // https://groups.google.com/forum/#!topic/fltkgeneral/qS-A50x6owk 00061 // 00062 #if defined(__APPLE__) 00063 #if (FL_MAJOR_VERSION >= 1) && (((FL_MINOR_VERSION >= 3) && (FL_PATCH_VERSION >= 5)) || (FL_MINOR_VERSION >= 4)) 00064 #define VMDFLTKUSEHIGHDPIRETINA 1 00065 #endif 00066 #endif 00067 00070 class myglwindow : public Fl_Gl_Window { 00071 FltkOpenGLDisplayDevice *dispdev; 00072 VMDApp *app; // cached VMDApp handle for use in drag-and-drop 00073 int dragpending; // flag indicating incoming PASTE event is drag-and-drop 00074 00075 public: 00076 myglwindow(int wx, int wy, int width, int height, const char *nm, 00077 FltkOpenGLDisplayDevice *d, VMDApp *vmdapp) 00078 : Fl_Gl_Window(wx, wy, width, height, nm), dispdev(d), app(vmdapp), dragpending(0) { 00079 // XXX this may not be reliable on MacOS X with recent revs of FLTK 00080 size_range(1,1,0,0); // resizable to full screen 00081 } 00082 00083 00084 int handle(int event) { 00085 // handle paste operations 00086 if (event == FL_PASTE) { 00087 // ignore paste operations that weren't due to drag-and-drop 00088 // since they could be any arbitrary data/text, and not just filenames. 00089 if (dragpending) { 00090 int len = Fl::event_length(); 00091 00092 // ignore zero-length paste events (why do these occur???) 00093 if (len > 0) { 00094 int numfiles, i; 00095 const char *lastc; 00096 int lasti; 00097 FileSpec spec; 00098 const char *ctext = Fl::event_text(); 00099 char *filename = (char *) malloc((1 + len) * sizeof(char)); 00100 00101 for (lasti=0,lastc=ctext,numfiles=0,i=0; i<len; i++) { 00102 // parse out all but last filename, which doesn't have a CR 00103 if (ctext[i] == '\n') { 00104 memcpy(filename, lastc, (i-lasti)*sizeof(char)); 00105 filename[i-lasti] = '0円'; 00106 00107 // attempt to load the file into a new molecule 00108 app->molecule_load(-1, filename, NULL, &spec); 00109 00110 lasti=i+1; 00111 lastc=&ctext[lasti]; 00112 numfiles++; 00113 } 00114 00115 // special-case last filename, since there's no CR 00116 if (i == (len-1)) { 00117 memcpy(filename, lastc, (1+i-lasti)*sizeof(char)); 00118 filename[1+i-lasti] = '0円'; 00119 00120 // attempt to load the file into a new molecule 00121 app->molecule_load(-1, filename, NULL, &spec); 00122 numfiles++; 00123 } 00124 } 00125 00126 free(filename); 00127 } 00128 00129 dragpending = 0; // no longer waiting for drag-and-drop paste 00130 } 00131 00132 return 1; // indicate that we handled the paste operation 00133 } 00134 00135 // handle drag-and-drop operations 00136 if (event == FL_DND_ENTER || event == FL_DND_DRAG) { 00137 return 1; // indicate that we want the drag-and-drop operation 00138 } 00139 if (event == FL_DND_RELEASE) { 00140 Fl::paste(*this); 00141 dragpending = 1; // flag to expect incoming paste due to DND operation 00142 return 1; 00143 } 00144 // end of cut-paste and drag-and-drop handling 00145 00146 switch (event) { 00147 case FL_MOUSEWHEEL: 00148 dispdev->lastevent = event; 00149 dispdev->lastzdelta = Fl::event_dy(); 00150 break; 00151 case FL_PUSH: 00152 dispdev->lastevent = event; 00153 dispdev->lastbtn = Fl::event_button(); 00154 if (dispdev->lastbtn == FL_LEFT_MOUSE && Fl::event_state(FL_META)) { 00155 dispdev->lastbtn = FL_MIDDLE_MOUSE; 00156 } 00157 break; 00158 case FL_DRAG: 00159 dispdev->lastevent = event; 00160 break; 00161 case FL_RELEASE: 00162 dispdev->lastevent = event; 00163 break; 00164 #if (FL_MAJOR_VERSION >= 1) && (FL_MINOR_VERSION >= 1) 00165 case FL_KEYDOWN: 00166 #else 00167 // This event code is superceded by FL_KEYDOWN in newer revs of FLTK 00168 case FL_KEYBOARD: 00169 #endif 00170 dispdev->lastevent = event; 00171 dispdev->lastkeycode = Fl::event_key(); 00172 dispdev->lastbtn = *Fl::event_text(); 00173 break; 00174 default: 00175 return Fl_Gl_Window::handle(event); 00176 } 00177 return 1; 00178 } 00179 void draw() { 00180 dispdev->reshape(); 00181 dispdev->_needRedraw = 1; 00182 app->VMDupdate(VMD_IGNORE_EVENTS); 00183 } 00184 // override the hide() method since we have no way of getting it back 00185 void hide() { 00186 if (fl_show_question("Really Quit?", 0)) 00187 app->VMDexit("",0,0); 00188 } 00189 }; 00190 00191 00192 // static data for this object 00193 static const char *glStereoNameStr[OPENGL_STEREO_MODES] = 00194 { "Off", 00195 "QuadBuffered", 00196 "HDTV SideBySide", 00197 "Checkerboard", 00198 "ColumnInterleaved", 00199 "RowInterleaved", 00200 "Anaglyph", 00201 "SideBySide", 00202 "AboveBelow", 00203 "Left", 00204 "Right" }; 00205 00206 static const char *glRenderNameStr[OPENGL_RENDER_MODES] = 00207 { "Normal", 00208 "GLSL", 00209 "Acrobat3D" }; 00210 00211 static const char *glCacheNameStr[OPENGL_CACHE_MODES] = 00212 { "Off", 00213 "On" }; 00214 00216 00217 // constructor ... open a window and set initial default values 00218 FltkOpenGLDisplayDevice::FltkOpenGLDisplayDevice(int argc, char **argv, 00219 VMDApp *vmdapp_p, int *size, int *loc) 00220 : OpenGLRenderer((char *) "VMD " VMDVERSION " OpenGL Display") { 00221 00222 vmdapp = vmdapp_p; // save VMDApp handle for use by drag-and-drop handlers, 00223 // and GPU memory management routines 00224 00225 // set up data possible before opening window 00226 stereoNames = glStereoNameStr; 00227 stereoModes = OPENGL_STEREO_MODES; 00228 00229 // GLSL is only available on MacOS X 10.4 and later. 00230 renderNames = glRenderNameStr; 00231 renderModes = OPENGL_RENDER_MODES; 00232 00233 cacheNames = glCacheNameStr; 00234 cacheModes = OPENGL_CACHE_MODES; 00235 00236 #if defined(VMDFLTKUSEHIGHDPIRETINA) 00237 if (getenv("VMDNOHIGHDPI") != NULL) { 00238 msgWarn << "User override: High-DPI OpenGL display support disabled." << sendmsg; 00239 } else { 00240 // Enable High-DPI support on Apple Retina displays when supported by 00241 // the underlying FLTK version 00242 // Global FLTK function to activate support for high-DPI Apple Retina 00243 // displays. This must be called before _any_ Fl_Gl_Window is shown. 00244 // When active, the pixel_w() and pixel_h() methods must be used instead 00245 // of w() and h(), e.g., glViewport(0, 0, pixel_w(), pixel_h()). 00246 Fl::use_high_res_GL(1); 00247 msgInfo << "High-DPI OpenGL display support enabled." << sendmsg; 00248 } 00249 #endif 00250 00251 // open the window 00252 int SX = 100, SY = 100, W, H; 00253 00254 W = size[0]; 00255 H = size[1]; 00256 if (loc) { 00257 SX = loc[0]; 00258 SY = loc[1]; 00259 } 00260 window = new myglwindow(SX, SY, W, H, name, this, vmdapp_p); 00261 00262 ext->hasstereo = FALSE; // stereo is off initially 00263 ext->stereodrawforced = FALSE; // stereo not forced initially 00264 ext->hasmultisample = FALSE; // multisample is off initially 00265 00266 int rc=0; 00267 // FLTK stereo support only started working for MacOS X at around version 1.1.7 00268 #if (FL_MAJOR_VERSION >= 1) && (((FL_MINOR_VERSION >= 1) && (FL_PATCH_VERSION >= 7)) || ((FL_MINOR_VERSION >= 1) && (FL_PATCH_VERSION >= 7))) 00269 // find an appropriate visual and colormap ... 00270 if (getenv("VMDPREFERSTEREO") != NULL) { 00271 // Stereo limps along with FLTK 1.1.7 on MacOS X 00272 rc = window->mode(FL_RGB8 | FL_DOUBLE | FL_STENCIL | FL_STEREO); 00273 ext->hasstereo = TRUE; 00274 #if defined(__APPLE__) 00275 ext->stereodrawforced = TRUE; // forced draw in stereo all the time when on 00276 #endif 00277 // FLTK multisample antialiasing still doesn't actually work in 00278 // MacOS X as of FLTK 1.1.10... 00279 #if !defined(__APPLE__) 00280 // } else if (getenv("VMDPREFERMULTISAMPLE") != NULL) { 00281 } else if (rc != 0) { 00282 rc = window->mode(FL_RGB8 | FL_DOUBLE | FL_STENCIL | FL_MULTISAMPLE); 00283 ext->hasmultisample = TRUE; // FLTK only does SGI multisample, no ARB yet 00284 #endif 00285 } else { 00286 rc = window->mode(FL_RGB8 | FL_DOUBLE | FL_STENCIL); 00287 } 00288 #else 00289 // find an appropriate visual and colormap ... 00290 rc = window->mode(FL_RGB8 | FL_DOUBLE | FL_STENCIL); 00291 #endif 00292 00293 window->show(); 00294 // (7) bind the rendering context to the window 00295 window->make_current(); 00296 00297 // (8) actually request the window to be displayed 00298 screenX = Fl::w(); 00299 screenY = Fl::h(); 00300 00301 // (9) configure the rendering properly 00302 setup_initial_opengl_state(); // setup initial OpenGL state 00303 00304 // set flags for the capabilities of this display 00305 // whether we can do antialiasing or not. 00306 if (ext->hasmultisample) 00307 aaAvailable = TRUE; // we use multisampling over other methods 00308 else 00309 aaAvailable = FALSE; // no non-multisample implementation yet 00310 00311 // set default settings 00312 if (ext->hasmultisample) { 00313 aa_on(); // enable fast multisample based antialiasing by default 00314 // other antialiasing techniques are slow, so only multisample 00315 // makes sense to enable by default. 00316 } 00317 00318 cueingAvailable = TRUE; 00319 cueing_on(); // leave depth cueing on by default, despite the speed hit. 00320 00321 cullingAvailable = TRUE; 00322 culling_off(); 00323 00324 set_sphere_mode(sphereMode); 00325 set_sphere_res(sphereRes); 00326 set_line_width(lineWidth); 00327 set_line_style(lineStyle); 00328 00329 // reshape and clear the display, which initializes some other variables 00330 reshape(); 00331 normal(); 00332 clear(); 00333 update(); 00334 } 00335 00336 // destructor ... close the window 00337 FltkOpenGLDisplayDevice::~FltkOpenGLDisplayDevice(void) { 00338 free_opengl_ctx(); // free display lists, textures, etc 00339 delete window; 00340 } 00341 00343 00344 // 00345 // get the current state of the device's pointer (i.e. cursor if it has one) 00346 // 00347 00348 // abs pos of cursor from lower-left corner of display 00349 int FltkOpenGLDisplayDevice::x(void) { 00350 //Fl::check(); 00351 #if defined(VMDFLTKUSEHIGHDPIRETINA) 00352 // Enable High-DPI support on Apple Retina displays when supported by 00353 // the underlying FLTK version 00354 // When FLTK high-DPI support is enabled, it is necessary to multiply 00355 // values returned by event_x() by the result of 00356 // Fl_Gl_Window::pixels_per_unit() to get correct GL-window pixel 00357 // coordinates. 00358 // XXX Docs bug: FLTK docs don't specify how event_x_root() is affected, but 00359 // it appears to follow the same behavior as event_x(). 00360 return Fl::event_x_root() * window->pixels_per_unit(); 00361 #elif 1 00362 return Fl::event_x_root(); 00363 #else 00364 int x, y; 00365 Fl::get_mouse(x, y); 00366 return x; 00367 #endif 00368 } 00369 00370 00371 // same, for y direction 00372 int FltkOpenGLDisplayDevice::y(void) { 00373 //Fl::check(); 00374 #if defined(VMDFLTKUSEHIGHDPIRETINA) 00375 // Enable High-DPI support on Apple Retina displays when supported by 00376 // the underlying FLTK version 00377 // When FLTK high-DPI support is enabled, it is necessary to multiply 00378 // values returned by event_y() by the result of 00379 // Fl_Gl_Window::pixels_per_unit() to get correct GL-window pixel 00380 // coordinates. 00381 // XXX Docs bug: FLTK docs don't specify how event_y_root() is affected, but 00382 // it appears to follow the same behavior as event_y(). 00383 return screenY - (Fl::event_y_root() * window->pixels_per_unit()); 00384 #elif 1 00385 return screenY - Fl::event_y_root(); 00386 #else 00387 int x, y; 00388 Fl::get_mouse(x, y); 00389 return screenY - y; 00390 #endif 00391 } 00392 00393 // return the current state of the shift, control, and alt keys 00394 int FltkOpenGLDisplayDevice::shift_state(void) { 00395 Fl::check(); 00396 00397 int retval = 0; 00398 int keymask = (int) Fl::event_state(); 00399 if (keymask & FL_SHIFT) 00400 retval |= SHIFT; 00401 if (keymask & FL_CTRL) 00402 retval |= CONTROL; 00403 if (keymask & FL_ALT) 00404 retval |= ALT; 00405 return retval; 00406 } 00407 00408 // return the spaceball state, if any 00409 int FltkOpenGLDisplayDevice::spaceball(int *rx, int *ry, int *rz, int *tx, int *ty, 00410 int *tz, int *buttons) { 00411 // not implemented yet 00412 return 0; 00413 } 00414 00415 00416 // set the Nth cursor shape as the current one. If no arg given, the 00417 // default shape (n=0) is used. 00418 void FltkOpenGLDisplayDevice::set_cursor(int n) { 00419 switch (n) { 00420 default: 00421 case DisplayDevice::NORMAL_CURSOR: window->cursor(FL_CURSOR_ARROW); break; 00422 case DisplayDevice::TRANS_CURSOR: window->cursor(FL_CURSOR_MOVE); break; 00423 case DisplayDevice::SCALE_CURSOR: window->cursor(FL_CURSOR_WE); break; 00424 case DisplayDevice::PICK_CURSOR: window->cursor(FL_CURSOR_CROSS); break; 00425 case DisplayDevice::WAIT_CURSOR: window->cursor(FL_CURSOR_WAIT); break; 00426 } 00427 } 00428 00429 00430 // 00431 // event handling routines 00432 // 00433 00434 // read the next event ... returns an event type (one of the above ones), 00435 // and a value. Returns success, and sets arguments. 00436 int FltkOpenGLDisplayDevice::read_event(long &retdev, long &retval) { 00437 #if !defined(__APPLE__) 00438 // disabled on OSX to avoid problems with Tcl/Tk mishandling events. 00439 // XXX this code was previously being used on MacOS X for Intel, but 00440 // it seems that it should match what we do on PowerPC so we 00441 // do the same in all MacOS X cases now. 00442 Fl::check(); 00443 #endif 00444 00445 switch (lastevent) { 00446 case FL_MOUSEWHEEL: 00447 // XXX tests on the Mac show that FLTK is using a coordinate system 00448 // backwards from what is used on Windows' zDelta value. 00449 if (lastzdelta < 0) { 00450 retdev = WIN_WHEELUP; 00451 } else { 00452 retdev = WIN_WHEELDOWN; 00453 } 00454 break; 00455 case FL_PUSH: 00456 case FL_DRAG: 00457 case FL_RELEASE: 00458 if (lastbtn == FL_LEFT_MOUSE) retdev = WIN_LEFT; 00459 else if (lastbtn == FL_MIDDLE_MOUSE) retdev = WIN_MIDDLE; 00460 else if (lastbtn == FL_RIGHT_MOUSE) retdev = WIN_RIGHT; 00461 else { 00462 //printf("unknown button: %d\n", lastbtn); 00463 } 00464 retval = (lastevent == FL_PUSH || lastevent == FL_DRAG); 00465 break; 00466 00467 #if (FL_MAJOR_VERSION >= 1) && (FL_MINOR_VERSION >= 1) 00468 case FL_KEYDOWN: 00469 #else 00470 // This event code is superceded by FL_KEYDOWN in newer revs of FLTK 00471 case FL_KEYBOARD: 00472 #endif 00473 // check function keys first 00474 if (lastkeycode >= FL_F && lastkeycode <= FL_F_Last) { 00475 retdev = (lastkeycode - FL_F) + ((int) WIN_KBD_F1); 00476 } else { 00477 switch(lastkeycode) { 00478 case FL_Escape: retdev = WIN_KBD_ESCAPE; break; 00479 case FL_Up: retdev = WIN_KBD_UP; break; 00480 case FL_Down: retdev = WIN_KBD_DOWN; break; 00481 case FL_Left: retdev = WIN_KBD_LEFT; break; 00482 case FL_Right: retdev = WIN_KBD_RIGHT; break; 00483 case FL_Page_Up: retdev = WIN_KBD_PAGE_UP; break; 00484 case FL_Page_Down: retdev = WIN_KBD_PAGE_UP; break; 00485 case FL_Home: retdev = WIN_KBD_HOME; break; 00486 case FL_End: retdev = WIN_KBD_END; break; 00487 case FL_Insert: retdev = WIN_KBD_INSERT; break; 00488 case FL_Delete: retdev = WIN_KBD_DELETE; break; 00489 00490 default: 00491 retdev = WIN_KBD; 00492 break; 00493 } 00494 } 00495 retval = lastbtn; 00496 break; 00497 00498 default: 00499 return 0; 00500 } 00501 lastevent = 0; 00502 return 1; 00503 } 00504 00505 // 00506 // virtual routines for preparing to draw, drawing, and finishing drawing 00507 // 00508 00509 // reshape the display after a shape change 00510 void FltkOpenGLDisplayDevice::reshape(void) { 00511 00512 #if defined(VMDFLTKUSEHIGHDPIRETINA) 00513 // Enable High-DPI support on Apple Retina displays when supported by 00514 // the underlying FLTK version 00515 xSize = window->w() * window->pixels_per_unit(); 00516 ySize = window->h() * window->pixels_per_unit(); 00517 xOrig = window->x() * window->pixels_per_unit(); 00518 yOrig = screenY - (window->y() * window->pixels_per_unit()) - ySize; 00519 #else 00520 xSize = window->w(); 00521 ySize = window->h(); 00522 xOrig = window->x(); 00523 yOrig = screenY - window->y() - ySize; 00524 #endif 00525 00526 switch (inStereo) { 00527 case OPENGL_STEREO_SIDE: 00528 set_screen_pos(0.5f * (float)xSize / (float)ySize); 00529 break; 00530 00531 case OPENGL_STEREO_ABOVEBELOW: 00532 set_screen_pos(2.0f * (float)xSize / (float)ySize); 00533 break; 00534 00535 case OPENGL_STEREO_STENCIL_CHECKERBOARD: 00536 case OPENGL_STEREO_STENCIL_COLUMNS: 00537 case OPENGL_STEREO_STENCIL_ROWS: 00538 enable_stencil_stereo(inStereo); 00539 set_screen_pos((float)xSize / (float)ySize); 00540 break; 00541 00542 default: 00543 set_screen_pos((float)xSize / (float)ySize); 00544 break; 00545 } 00546 } 00547 00548 unsigned char * FltkOpenGLDisplayDevice::readpixels_rgb3u(int &x, int &y) { 00549 unsigned char * img; 00550 00551 x = xSize; 00552 y = ySize; 00553 00554 if ((img = (unsigned char *) malloc(x * y * 3)) != NULL) { 00555 #if !defined(WIREGL) 00556 glPixelStorei(GL_PACK_ALIGNMENT, 1); 00557 glReadPixels(0, 0, x, y, GL_RGB, GL_UNSIGNED_BYTE, img); 00558 #endif 00559 } else { 00560 x = 0; 00561 y = 0; 00562 } 00563 00564 return img; 00565 } 00566 00567 unsigned char * FltkOpenGLDisplayDevice::readpixels_rgba4u(int &x, int &y) { 00568 unsigned char * img; 00569 00570 x = xSize; 00571 y = ySize; 00572 00573 if ((img = (unsigned char *) malloc(x * y * 4)) != NULL) { 00574 #if !defined(WIREGL) 00575 glPixelStorei(GL_PACK_ALIGNMENT, 1); 00576 glReadPixels(0, 0, x, y, GL_RGBA, GL_UNSIGNED_BYTE, img); 00577 #endif 00578 } else { 00579 x = 0; 00580 y = 0; 00581 } 00582 00583 return img; 00584 } 00585 00586 00587 // update after drawing 00588 void FltkOpenGLDisplayDevice::update(int do_update) { 00589 if(do_update) 00590 window->swap_buffers(); 00591 00592 glDrawBuffer(GL_BACK); 00593 } 00594 00595 void FltkOpenGLDisplayDevice::do_resize_window(int w, int h) { 00596 #if defined(VMDFLTKUSEHIGHDPIRETINA) 00597 // when running in High-DPI / retina mode, the user specifies VMD image 00598 // resolutions in real rendered pixels, not pseudo pixels, so we have to 00599 // pre-divide out the scale factor since FLTK will multiply it back in. 00600 window->size(w / window->pixels_per_unit(), h / window->pixels_per_unit()); 00601 #else 00602 window->size(w, h); 00603 #endif 00604 00605 // XXX this may not be reliable on MacOS X with recent revs of FLTK 00606 window->size_range(1,1,0,0); // resizable to full screen 00607 } 00608 00609 void FltkOpenGLDisplayDevice::do_reposition_window(int xpos, int ypos) { 00610 window->position(xpos, ypos); 00611 // XXX this may not be reliable on MacOS X with recent revs of FLTK 00612 window->size_range(1,1,0,0); // resizable to full screen 00613 } 00614 00615