Panda3D
|
00001 // Filename: tinyXGraphicsWindow.cxx 00002 // Created by: drose (03May08) 00003 // 00004 //////////////////////////////////////////////////////////////////// 00005 // 00006 // PANDA 3D SOFTWARE 00007 // Copyright (c) Carnegie Mellon University. All rights reserved. 00008 // 00009 // All use of this software is subject to the terms of the revised BSD 00010 // license. You should have received a copy of this license along 00011 // with this source code in a file named "LICENSE." 00012 // 00013 //////////////////////////////////////////////////////////////////// 00014 00015 #include "pandabase.h" 00016 00017 #ifdef HAVE_X11 00018 00019 #include "tinyXGraphicsWindow.h" 00020 #include "tinyGraphicsStateGuardian.h" 00021 #include "tinyXGraphicsPipe.h" 00022 #include "config_tinydisplay.h" 00023 00024 #include "graphicsPipe.h" 00025 #include "keyboardButton.h" 00026 #include "mouseButton.h" 00027 #include "clockObject.h" 00028 #include "pStatTimer.h" 00029 #include "textEncoder.h" 00030 #include "throw_event.h" 00031 #include "lightReMutexHolder.h" 00032 #include "nativeWindowHandle.h" 00033 00034 TypeHandle TinyXGraphicsWindow::_type_handle; 00035 00036 //////////////////////////////////////////////////////////////////// 00037 // Function: TinyXGraphicsWindow::Constructor 00038 // Access: Public 00039 // Description: 00040 //////////////////////////////////////////////////////////////////// 00041 TinyXGraphicsWindow:: 00042 TinyXGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, 00043 const string &name, 00044 const FrameBufferProperties &fb_prop, 00045 const WindowProperties &win_prop, 00046 int flags, 00047 GraphicsStateGuardian *gsg, 00048 GraphicsOutput *host) : 00049 x11GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) 00050 { 00051 _gc = (GC)NULL; 00052 00053 _reduced_frame_buffer = NULL; 00054 _full_frame_buffer = NULL; 00055 _ximage = NULL; 00056 update_pixel_factor(); 00057 } 00058 00059 //////////////////////////////////////////////////////////////////// 00060 // Function: TinyXGraphicsWindow::Destructor 00061 // Access: Public, Virtual 00062 // Description: 00063 //////////////////////////////////////////////////////////////////// 00064 TinyXGraphicsWindow:: 00065 ~TinyXGraphicsWindow() { 00066 if (_gc != NULL && _display != NULL) { 00067 XFreeGC(_display, _gc); 00068 } 00069 if (_ximage != NULL) { 00070 PANDA_FREE_ARRAY(_ximage->data); 00071 _ximage->data = NULL; 00072 XDestroyImage(_ximage); 00073 } 00074 } 00075 00076 //////////////////////////////////////////////////////////////////// 00077 // Function: TinyXGraphicsWindow::begin_frame 00078 // Access: Public, Virtual 00079 // Description: This function will be called within the draw thread 00080 // before beginning rendering for a given frame. It 00081 // should do whatever setup is required, and return true 00082 // if the frame should be rendered, or false if it 00083 // should be skipped. 00084 //////////////////////////////////////////////////////////////////// 00085 bool TinyXGraphicsWindow:: 00086 begin_frame(FrameMode mode, Thread *current_thread) { 00087 PStatTimer timer(_make_current_pcollector, current_thread); 00088 00089 if (_xwindow == (Window)NULL) { 00090 return false; 00091 } 00092 00093 begin_frame_spam(mode); 00094 if (_gsg == (GraphicsStateGuardian *)NULL) { 00095 return false; 00096 } 00097 if (_awaiting_configure) { 00098 // Don't attempt to draw while we have just reconfigured the 00099 // window and we haven't got the notification back yet. 00100 return false; 00101 } 00102 00103 TinyGraphicsStateGuardian *tinygsg; 00104 DCAST_INTO_R(tinygsg, _gsg, false); 00105 00106 if (_reduced_frame_buffer != (ZBuffer *)NULL) { 00107 tinygsg->_current_frame_buffer = _reduced_frame_buffer; 00108 } else { 00109 tinygsg->_current_frame_buffer = _full_frame_buffer; 00110 } 00111 tinygsg->reset_if_new(); 00112 00113 _gsg->set_current_properties(&get_fb_properties()); 00114 return _gsg->begin_frame(current_thread); 00115 } 00116 00117 //////////////////////////////////////////////////////////////////// 00118 // Function: TinyXGraphicsWindow::end_frame 00119 // Access: Public, Virtual 00120 // Description: This function will be called within the draw thread 00121 // after rendering is completed for a given frame. It 00122 // should do whatever finalization is required. 00123 //////////////////////////////////////////////////////////////////// 00124 void TinyXGraphicsWindow:: 00125 end_frame(FrameMode mode, Thread *current_thread) { 00126 end_frame_spam(mode); 00127 nassertv(_gsg != (GraphicsStateGuardian *)NULL); 00128 00129 if (mode == FM_render) { 00130 // end_render_texture(); 00131 copy_to_textures(); 00132 } 00133 00134 _gsg->end_frame(current_thread); 00135 00136 if (mode == FM_render) { 00137 trigger_flip(); 00138 if (_one_shot) { 00139 prepare_for_deletion(); 00140 } 00141 clear_cube_map_selection(); 00142 } 00143 } 00144 00145 //////////////////////////////////////////////////////////////////// 00146 // Function: TinyXGraphicsWindow::begin_flip 00147 // Access: Public, Virtual 00148 // Description: This function will be called within the draw thread 00149 // after end_frame() has been called on all windows, to 00150 // initiate the exchange of the front and back buffers. 00151 // 00152 // This should instruct the window to prepare for the 00153 // flip at the next video sync, but it should not wait. 00154 // 00155 // We have the two separate functions, begin_flip() and 00156 // end_flip(), to make it easier to flip all of the 00157 // windows at the same time. 00158 //////////////////////////////////////////////////////////////////// 00159 void TinyXGraphicsWindow:: 00160 begin_flip() { 00161 if (_xwindow == (Window)NULL) { 00162 return; 00163 } 00164 00165 if (_reduced_frame_buffer != (ZBuffer *)NULL) { 00166 // Zoom the reduced buffer onto the full buffer. 00167 ZB_zoomFrameBuffer(_full_frame_buffer, 0, 0, 00168 _full_frame_buffer->xsize, _full_frame_buffer->ysize, 00169 _reduced_frame_buffer, 0, 0, 00170 _reduced_frame_buffer->xsize, _reduced_frame_buffer->ysize); 00171 } 00172 00173 // We can't just point the XPutImage directly at our own framebuffer 00174 // data, even if the bytes_per_pixel matches, because some X 00175 // displays will respect the alpha channel and make the window 00176 // transparent there. We don't want transparent windows where the 00177 // alpha data happens to less than 1.0. 00178 ZB_copyFrameBufferNoAlpha(_full_frame_buffer, _ximage->data, _pitch); 00179 00180 XPutImage(_display, _xwindow, _gc, _ximage, 0, 0, 0, 0, 00181 _full_frame_buffer->xsize, _full_frame_buffer->ysize); 00182 XFlush(_display); 00183 } 00184 00185 //////////////////////////////////////////////////////////////////// 00186 // Function: TinyXGraphicsWindow::supports_pixel_zoom 00187 // Access: Published, Virtual 00188 // Description: Returns true if a call to set_pixel_zoom() will be 00189 // respected, false if it will be ignored. If this 00190 // returns false, then get_pixel_factor() will always 00191 // return 1.0, regardless of what value you specify for 00192 // set_pixel_zoom(). 00193 // 00194 // This may return false if the underlying renderer 00195 // doesn't support pixel zooming, or if you have called 00196 // this on a DisplayRegion that doesn't have both 00197 // set_clear_color() and set_clear_depth() enabled. 00198 //////////////////////////////////////////////////////////////////// 00199 bool TinyXGraphicsWindow:: 00200 supports_pixel_zoom() const { 00201 return true; 00202 } 00203 00204 //////////////////////////////////////////////////////////////////// 00205 // Function: TinyXGraphicsWindow::process_events 00206 // Access: Public, Virtual 00207 // Description: Do whatever processing is necessary to ensure that 00208 // the window responds to user events. Also, honor any 00209 // requests recently made via request_properties() 00210 // 00211 // This function is called only within the window 00212 // thread. 00213 //////////////////////////////////////////////////////////////////// 00214 void TinyXGraphicsWindow:: 00215 process_events() { 00216 LightReMutexHolder holder(TinyXGraphicsPipe::_x_mutex); 00217 00218 GraphicsWindow::process_events(); 00219 00220 if (_xwindow == (Window)0) { 00221 return; 00222 } 00223 00224 poll_raw_mice(); 00225 00226 XEvent event; 00227 XKeyEvent keyrelease_event; 00228 bool got_keyrelease_event = false; 00229 00230 while (XCheckIfEvent(_display, &event, check_event, (char *)this)) { 00231 if (XFilterEvent(&event, None)) { 00232 continue; 00233 } 00234 00235 if (got_keyrelease_event) { 00236 // If a keyrelease event is immediately followed by a matching 00237 // keypress event, that's just key repeat and we should treat 00238 // the two events accordingly. It would be nice if X provided a 00239 // way to differentiate between keyrepeat and explicit 00240 // keypresses more generally. 00241 got_keyrelease_event = false; 00242 00243 if (event.type == KeyPress && 00244 event.xkey.keycode == keyrelease_event.keycode && 00245 (event.xkey.time - keyrelease_event.time <= 1)) { 00246 // In particular, we only generate down messages for the 00247 // repeated keys, not down-and-up messages. 00248 handle_keystroke(event.xkey); 00249 00250 // We thought about not generating the keypress event, but we 00251 // need that repeat for backspace. Rethink later. 00252 handle_keypress(event.xkey); 00253 continue; 00254 00255 } else { 00256 // This keyrelease event is not immediately followed by a 00257 // matching keypress event, so it's a genuine release. 00258 handle_keyrelease(keyrelease_event); 00259 } 00260 } 00261 00262 WindowProperties properties; 00263 ButtonHandle button; 00264 00265 switch (event.type) { 00266 case ReparentNotify: 00267 break; 00268 00269 case ConfigureNotify: 00270 _awaiting_configure = false; 00271 if (_properties.get_fixed_size()) { 00272 // If the window properties indicate a fixed size only, undo 00273 // any attempt by the user to change them. In X, there 00274 // doesn't appear to be a way to universally disallow this 00275 // directly (although we do set the min_size and max_size to 00276 // the same value, which seems to work for most window 00277 // managers.) 00278 WindowProperties current_props = get_properties(); 00279 if (event.xconfigure.width != current_props.get_x_size() || 00280 event.xconfigure.height != current_props.get_y_size()) { 00281 XWindowChanges changes; 00282 changes.width = current_props.get_x_size(); 00283 changes.height = current_props.get_y_size(); 00284 int value_mask = (CWWidth | CWHeight); 00285 XConfigureWindow(_display, _xwindow, value_mask, &changes); 00286 } 00287 00288 } else { 00289 // A normal window may be resized by the user at will. 00290 properties.set_size(event.xconfigure.width, event.xconfigure.height); 00291 system_changed_properties(properties); 00292 ZB_resize(_full_frame_buffer, NULL, _properties.get_x_size(), _properties.get_y_size()); 00293 _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3; 00294 create_reduced_frame_buffer(); 00295 create_ximage(); 00296 } 00297 break; 00298 00299 case ButtonPress: 00300 // This refers to the mouse buttons. 00301 button = get_mouse_button(event.xbutton); 00302 _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); 00303 _input_devices[0].button_down(button); 00304 break; 00305 00306 case ButtonRelease: 00307 button = get_mouse_button(event.xbutton); 00308 _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); 00309 _input_devices[0].button_up(button); 00310 break; 00311 00312 case MotionNotify: 00313 _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); 00314 break; 00315 00316 case KeyPress: 00317 handle_keystroke(event.xkey); 00318 handle_keypress(event.xkey); 00319 break; 00320 00321 case KeyRelease: 00322 // The KeyRelease can't be processed immediately, because we 00323 // have to check first if it's immediately followed by a 00324 // matching KeyPress event. 00325 keyrelease_event = event.xkey; 00326 got_keyrelease_event = true; 00327 break; 00328 00329 case EnterNotify: 00330 _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); 00331 break; 00332 00333 case LeaveNotify: 00334 _input_devices[0].set_pointer_out_of_window(); 00335 break; 00336 00337 case FocusIn: 00338 properties.set_foreground(true); 00339 system_changed_properties(properties); 00340 break; 00341 00342 case FocusOut: 00343 properties.set_foreground(false); 00344 system_changed_properties(properties); 00345 break; 00346 00347 case UnmapNotify: 00348 properties.set_minimized(true); 00349 system_changed_properties(properties); 00350 break; 00351 00352 case MapNotify: 00353 properties.set_minimized(false); 00354 system_changed_properties(properties); 00355 00356 // Auto-focus the window when it is mapped. 00357 XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime); 00358 break; 00359 00360 case ClientMessage: 00361 if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) { 00362 // This is a message from the window manager indicating that 00363 // the user has requested to close the window. 00364 string close_request_event = get_close_request_event(); 00365 if (!close_request_event.empty()) { 00366 // In this case, the app has indicated a desire to intercept 00367 // the request and process it directly. 00368 throw_event(close_request_event); 00369 00370 } else { 00371 // In this case, the default case, the app does not intend 00372 // to service the request, so we do by closing the window. 00373 00374 // TODO: don't release the gsg in the window thread. 00375 close_window(); 00376 properties.set_open(false); 00377 system_changed_properties(properties); 00378 } 00379 } 00380 break; 00381 00382 case DestroyNotify: 00383 // Apparently, we never get a DestroyNotify on a toplevel 00384 // window. Instead, we rely on hints from the window manager 00385 // (see above). 00386 tinydisplay_cat.info() 00387 << "DestroyNotify\n"; 00388 break; 00389 00390 default: 00391 tinydisplay_cat.error() 00392 << "unhandled X event type " << event.type << "\n"; 00393 } 00394 } 00395 00396 if (got_keyrelease_event) { 00397 // This keyrelease event is not immediately followed by a 00398 // matching keypress event, so it's a genuine release. 00399 handle_keyrelease(keyrelease_event); 00400 } 00401 } 00402 00403 //////////////////////////////////////////////////////////////////// 00404 // Function: TinyXGraphicsWindow::close_window 00405 // Access: Protected, Virtual 00406 // Description: Closes the window right now. Called from the window 00407 // thread. 00408 //////////////////////////////////////////////////////////////////// 00409 void TinyXGraphicsWindow:: 00410 close_window() { 00411 if (_gsg != (GraphicsStateGuardian *)NULL) { 00412 TinyGraphicsStateGuardian *tinygsg; 00413 DCAST_INTO_V(tinygsg, _gsg); 00414 tinygsg->_current_frame_buffer = NULL; 00415 _gsg.clear(); 00416 _active = false; 00417 } 00418 00419 x11GraphicsWindow::close_window(); 00420 } 00421 00422 //////////////////////////////////////////////////////////////////// 00423 // Function: TinyXGraphicsWindow::open_window 00424 // Access: Protected, Virtual 00425 // Description: Opens the window right now. Called from the window 00426 // thread. Returns true if the window is successfully 00427 // opened, or false if there was a problem. 00428 //////////////////////////////////////////////////////////////////// 00429 bool TinyXGraphicsWindow:: 00430 open_window() { 00431 TinyXGraphicsPipe *tinyx_pipe; 00432 DCAST_INTO_R(tinyx_pipe, _pipe, false); 00433 00434 // GSG Creation/Initialization 00435 TinyGraphicsStateGuardian *tinygsg; 00436 if (_gsg == 0) { 00437 // There is no old gsg. Create a new one. 00438 tinygsg = new TinyGraphicsStateGuardian(_engine, _pipe, NULL); 00439 _gsg = tinygsg; 00440 } else { 00441 DCAST_INTO_R(tinygsg, _gsg, false); 00442 } 00443 00444 XVisualInfo vinfo_template; 00445 vinfo_template.screen = _screen; 00446 vinfo_template.depth = 32; 00447 vinfo_template.c_class = TrueColor; 00448 00449 // Try to get each of these properties in turn. 00450 int try_masks[] = { 00451 VisualScreenMask | VisualDepthMask | VisualClassMask, 00452 VisualScreenMask | VisualClassMask, 00453 VisualScreenMask | VisualDepthMask, 00454 VisualScreenMask, 00455 0, 00456 }; 00457 00458 int i = 0; 00459 int num_vinfos = 0; 00460 XVisualInfo *vinfo_array; 00461 while (try_masks[i] != 0 && num_vinfos == 0) { 00462 vinfo_array = 00463 XGetVisualInfo(_display, try_masks[i], &vinfo_template, &num_vinfos); 00464 ++i; 00465 } 00466 00467 if (num_vinfos == 0) { 00468 // No suitable X visual. 00469 tinydisplay_cat.error() 00470 << "No suitable X Visual available; cannot open window.\n"; 00471 return false; 00472 } 00473 _visual_info = &vinfo_array[0]; 00474 00475 _visual = _visual_info->visual; 00476 _depth = _visual_info->depth; 00477 _bytes_per_pixel = _depth / 8; 00478 if (_bytes_per_pixel == 3) { 00479 // Seems to be a special case. 00480 _bytes_per_pixel = 4; 00481 } 00482 tinydisplay_cat.info() 00483 << "Got X Visual with depth " << _depth << " (bpp " << _bytes_per_pixel << ") and class "; 00484 switch (_visual_info->c_class) { 00485 case TrueColor: 00486 tinydisplay_cat.info(false) << "TrueColor\n"; 00487 break; 00488 00489 case DirectColor: 00490 tinydisplay_cat.info(false) << "DirectColor\n"; 00491 break; 00492 00493 case StaticColor: 00494 tinydisplay_cat.info(false) << "StaticColor\n"; 00495 break; 00496 00497 case StaticGray: 00498 tinydisplay_cat.info(false) << "StaticGray\n"; 00499 break; 00500 00501 case GrayScale: 00502 tinydisplay_cat.info(false) << "GrayScale\n"; 00503 break; 00504 00505 case PseudoColor: 00506 tinydisplay_cat.info(false) << "PseudoColor\n"; 00507 break; 00508 } 00509 00510 setup_colormap(_visual_info); 00511 00512 if (!x11GraphicsWindow::open_window()) { 00513 return false; 00514 } 00515 00516 _gc = XCreateGC(_display, _xwindow, 0, NULL); 00517 00518 create_full_frame_buffer(); 00519 if (_full_frame_buffer == NULL) { 00520 tinydisplay_cat.error() 00521 << "Could not create frame buffer.\n"; 00522 return false; 00523 } 00524 create_reduced_frame_buffer(); 00525 create_ximage(); 00526 nassertr(_ximage != NULL, false); 00527 00528 tinygsg->_current_frame_buffer = _full_frame_buffer; 00529 00530 tinygsg->reset_if_new(); 00531 if (!tinygsg->is_valid()) { 00532 close_window(); 00533 return false; 00534 } 00535 00536 XMapWindow(_display, _xwindow); 00537 00538 if (_properties.get_raw_mice()) { 00539 open_raw_mice(); 00540 } else { 00541 if (tinydisplay_cat.is_debug()) { 00542 tinydisplay_cat.debug() 00543 << "Raw mice not requested.\n"; 00544 } 00545 } 00546 00547 // Create a WindowHandle for ourselves 00548 _window_handle = NativeWindowHandle::make_x11(_xwindow); 00549 00550 // And tell our parent window that we're now its child. 00551 if (_parent_window_handle != (WindowHandle *)NULL) { 00552 _parent_window_handle->attach_child(_window_handle); 00553 } 00554 00555 return true; 00556 } 00557 00558 //////////////////////////////////////////////////////////////////// 00559 // Function: TinyXGraphicsWindow::pixel_factor_changed 00560 // Access: Protected, Virtual 00561 // Description: Called internally when the pixel factor changes. 00562 //////////////////////////////////////////////////////////////////// 00563 void TinyXGraphicsWindow:: 00564 pixel_factor_changed() { 00565 x11GraphicsWindow::pixel_factor_changed(); 00566 create_reduced_frame_buffer(); 00567 } 00568 00569 //////////////////////////////////////////////////////////////////// 00570 // Function: TinyXGraphicsWindow::create_full_frame_buffer 00571 // Access: Private 00572 // Description: Creates a suitable frame buffer for the current 00573 // window size. 00574 //////////////////////////////////////////////////////////////////// 00575 void TinyXGraphicsWindow:: 00576 create_full_frame_buffer() { 00577 if (_full_frame_buffer != NULL) { 00578 ZB_close(_full_frame_buffer); 00579 _full_frame_buffer = NULL; 00580 } 00581 00582 int mode; 00583 switch (_bytes_per_pixel) { 00584 case 1: 00585 tinydisplay_cat.error() 00586 << "Palette images are currently not supported.\n"; 00587 return; 00588 00589 case 2: 00590 mode = ZB_MODE_5R6G5B; 00591 break; 00592 case 4: 00593 mode = ZB_MODE_RGBA; 00594 break; 00595 00596 default: 00597 return; 00598 } 00599 00600 _full_frame_buffer = ZB_open(_properties.get_x_size(), _properties.get_y_size(), mode, 0, 0, 0, 0); 00601 _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3; 00602 } 00603 00604 //////////////////////////////////////////////////////////////////// 00605 // Function: TinyXGraphicsWindow::create_reduced_frame_buffer 00606 // Access: Private 00607 // Description: Creates a suitable frame buffer for the current 00608 // window size and pixel zoom. 00609 //////////////////////////////////////////////////////////////////// 00610 void TinyXGraphicsWindow:: 00611 create_reduced_frame_buffer() { 00612 if (_reduced_frame_buffer != NULL) { 00613 ZB_close(_reduced_frame_buffer); 00614 _reduced_frame_buffer = NULL; 00615 } 00616 00617 int x_size = get_fb_x_size(); 00618 int y_size = get_fb_y_size(); 00619 00620 if (x_size == _full_frame_buffer->xsize) { 00621 // No zooming is necessary. 00622 00623 } else { 00624 // The reduced size is different, so we need a separate buffer to 00625 // render into. 00626 _reduced_frame_buffer = ZB_open(x_size, y_size, _full_frame_buffer->mode, 0, 0, 0, 0); 00627 } 00628 } 00629 00630 00631 //////////////////////////////////////////////////////////////////// 00632 // Function: TinyXGraphicsWindow::create_ximage 00633 // Access: Private 00634 // Description: Creates a suitable XImage for the current 00635 // window size. 00636 //////////////////////////////////////////////////////////////////// 00637 void TinyXGraphicsWindow:: 00638 create_ximage() { 00639 if (_ximage != NULL) { 00640 PANDA_FREE_ARRAY(_ximage->data); 00641 _ximage->data = NULL; 00642 XDestroyImage(_ximage); 00643 _ximage = NULL; 00644 } 00645 00646 int image_size = _full_frame_buffer->ysize * _pitch; 00647 char *data = (char *)PANDA_MALLOC_ARRAY(image_size); 00648 00649 _ximage = XCreateImage(_display, _visual, _depth, ZPixmap, 0, data, 00650 _full_frame_buffer->xsize, _full_frame_buffer->ysize, 00651 32, 0); 00652 } 00653 00654 #endif // HAVE_X11 00655