// Filename: wglGraphicsBuffer.cxx // Created by: drose (08Feb04) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "wglGraphicsBuffer.h" #include "config_wgldisplay.h" #include "glgsg.h" #include "pStatTimer.h" #include TypeHandle wglGraphicsBuffer::_type_handle; const char * const wglGraphicsBuffer::_window_class_name = "wglGraphicsBuffer"; bool wglGraphicsBuffer::_window_class_registered = false; //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// wglGraphicsBuffer:: wglGraphicsBuffer(GraphicsPipe *pipe, GraphicsStateGuardian *gsg, const string &name, int x_size, int y_size, bool want_texture) : GraphicsBuffer(pipe, gsg, name, x_size, y_size, want_texture) { _window = (HWND)0; _window_dc = (HDC)0; _pbuffer = (HPBUFFERARB)0; _pbuffer_dc = (HDC)0; // Since the pbuffer (or window, if we use a hidden window) never // gets flipped, we get screenshots from the same buffer we draw // into. // Actually, because of an apparent driver bug in nVidia drivers, we // must flip the pbuffer after all. // _screenshot_buffer_type = _draw_buffer_type; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::Destructor // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// wglGraphicsBuffer:: ~wglGraphicsBuffer() { } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::begin_frame // Access: Public, Virtual // Description: This function will be called within the draw thread // before beginning rendering for a given frame. It // should do whatever setup is required, and return true // if the frame should be rendered, or false if it // should be skipped. //////////////////////////////////////////////////////////////////// bool wglGraphicsBuffer:: begin_frame() { if (_gsg == (GraphicsStateGuardian *)NULL) { return false; } wglGraphicsStateGuardian *wglgsg; DCAST_INTO_R(wglgsg, _gsg, false); if (_pbuffer_dc) { int flag = 0; wglgsg->_wglQueryPbufferARB(_pbuffer, WGL_PBUFFER_LOST_ARB, &flag); if (flag != 0) { // The pbuffer was lost, due to a mode change or something // silly like that. We must therefore recreate the pbuffer. close_buffer(); if (!open_buffer()) { return false; } } } return GraphicsBuffer::begin_frame(); } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::end_frame // Access: Public, Virtual // Description: This function will be called within the draw thread // after rendering is completed for a given frame. It // should do whatever finalization is required. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: end_frame() { nassertv(_gsg != (GraphicsStateGuardian *)NULL); _gsg->end_frame(); if (_copy_texture) { wglGraphicsStateGuardian *wglgsg; DCAST_INTO_V(wglgsg, _gsg); // If we've lost the pbuffer image (due to a mode-switch, for // instance), don't attempt to copy it to the texture, since the // frame is invalid. In fact, now we need to recreate the // pbuffer. if (_pbuffer_dc) { int flag = 0; wglgsg->_wglQueryPbufferARB(_pbuffer, WGL_PBUFFER_LOST_ARB, &flag); if (flag != 0) { wgldisplay_cat.info() << "Pbuffer contents lost.\n"; return; } } // For now, we copy the framebuffer to the texture every frame. // Eventually we can take advantage of the render_texture // extension, if it is available, to render directly into a // texture in the first place (but I don't have a card that // supports that right now). nassertv(has_texture()); DisplayRegion dr(this, _x_size, _y_size); RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type()); get_texture()->copy(_gsg, &dr, buffer); // It appears that the nVidia graphics driver 5.3.0.3, dated // 11/17/2003 will get confused with the above copy operation and // copy the texture from the wrong window, unless we immediately // swap buffers in the pbuffer, even though there's no reason to // swap buffers otherwise. This is unfortunate, because it means // we can't use single-buffered pbuffers (for which SwapBuffers is // a no-op). if (_pbuffer_dc) { SwapBuffers(_pbuffer_dc); } else if (_window_dc) { SwapBuffers(_window_dc); } } } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::make_current // Access: Public, Virtual // Description: This function will be called within the draw thread // during begin_frame() to ensure the graphics context // is ready for drawing. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: make_current() { PStatTimer timer(_make_current_pcollector); wglGraphicsStateGuardian *wglgsg; DCAST_INTO_V(wglgsg, _gsg); // Use the pbuffer if we got it, otherwise fall back to the window. if (_pbuffer_dc) { wglMakeCurrent(_pbuffer_dc, wglgsg->get_context(_pbuffer_dc)); } else { wglMakeCurrent(_window_dc, wglgsg->get_context(_window_dc)); } } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::release_gsg // Access: Public, Virtual // Description: Releases the current GSG pointer, if it is currently // held, and resets the GSG to NULL. The window will be // permanently unable to render; this is normally called // only just before destroying the window. This should // only be called from within the draw thread. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: release_gsg() { GraphicsBuffer::release_gsg(); } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::process_events // Access: Public, Virtual // Description: Do whatever processing is necessary to ensure that // the window responds to user events. Also, honor any // requests recently made via request_properties() // // This function is called only within the window // thread. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: process_events() { GraphicsBuffer::process_events(); MSG msg; // Handle all the messages on the queue in a row. Some of these // might be for another window, but they will get dispatched // appropriately. while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { process_1_event(); } } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::close_buffer // Access: Protected, Virtual // Description: Closes the buffer right now. Called from the window // thread. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: close_buffer() { if (_window_dc) { ReleaseDC(_window, _window_dc); _window_dc = 0; } if (_window) { DestroyWindow(_window); _window = 0; } if (_gsg != (GraphicsStateGuardian *)NULL) { wglGraphicsStateGuardian *wglgsg; DCAST_INTO_V(wglgsg, _gsg); if (_pbuffer_dc) { wglgsg->_wglReleasePbufferDCARB(_pbuffer, _pbuffer_dc); } if (_pbuffer) { wglgsg->_wglDestroyPbufferARB(_pbuffer); } } _pbuffer_dc = 0; _pbuffer = 0; _is_valid = false; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::open_buffer // Access: Protected, Virtual // Description: Opens the window right now. Called from the window // thread. Returns true if the window is successfully // opened, or false if there was a problem. //////////////////////////////////////////////////////////////////// bool wglGraphicsBuffer:: open_buffer() { if (!make_window()) { // If we couldn't make a window, we can't get a GL context. return false; } wglGraphicsStateGuardian *wglgsg; DCAST_INTO_R(wglgsg, _gsg, false); wglMakeCurrent(_window_dc, wglgsg->get_context(_window_dc)); wglgsg->reset_if_new(); // Now that we have fully made a window and used that window to // create a rendering context, we can attempt to create a pbuffer. // This might fail if the pbuffer extensions are not supported; in // that case, we'll just keep the window and hope it works even if // it is not shown. if (make_pbuffer()) { _pbuffer_dc = wglgsg->_wglGetPbufferDCARB(_pbuffer); wgldisplay_cat.info() << "Created PBuffer " << _pbuffer << ", DC " << _pbuffer_dc << "\n"; wglMakeCurrent(_pbuffer_dc, wglgsg->get_context(_pbuffer_dc)); wglgsg->report_my_gl_errors(); // Now that the pbuffer is created, we don't need the window any // more. if (_window_dc) { ReleaseDC(_window, _window_dc); _window_dc = 0; } if (_window) { DestroyWindow(_window); _window = 0; } SwapBuffers(_pbuffer_dc); } _is_valid = true; return true; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::make_window // Access: Private // Description: Creates an invisible window to associate with the GL // context, even if we are not going to use it. This is // necessary because in the Windows OpenGL API, we have // to create window before we can create a GL // context--even before we can ask about what GL // extensions are available! //////////////////////////////////////////////////////////////////// bool wglGraphicsBuffer:: make_window() { DWORD window_style = WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW; RECT win_rect; SetRect(&win_rect, 0, 0, _x_size, _y_size); // compute window size based on desired client area size if (!AdjustWindowRect(&win_rect, window_style, FALSE)) { wgldisplay_cat.error() << "AdjustWindowRect failed!" << endl; return false; } register_window_class(); HINSTANCE hinstance = GetModuleHandle(NULL); _window = CreateWindow(_window_class_name, "buffer", window_style, win_rect.left, win_rect.top, win_rect.right - win_rect.left, win_rect.bottom - win_rect.top, NULL, NULL, hinstance, 0); if (!_window) { wgldisplay_cat.error() << "CreateWindow() failed!" << endl; return false; } ShowWindow(_window, SW_HIDE); _window_dc = GetDC(_window); wglGraphicsStateGuardian *wglgsg; DCAST_INTO_R(wglgsg, _gsg, false); int pfnum = wglgsg->get_pfnum(); PIXELFORMATDESCRIPTOR pixelformat; if (!SetPixelFormat(_window_dc, pfnum, &pixelformat)) { wgldisplay_cat.error() << "SetPixelFormat(" << pfnum << ") failed after window create\n"; return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::make_pbuffer // Access: Private // Description: Once the GL context has been fully realized, attempts // to create an offscreen pbuffer if the graphics API // supports it. Returns true if successful, false on // failure. //////////////////////////////////////////////////////////////////// bool wglGraphicsBuffer:: make_pbuffer() { wglGraphicsStateGuardian *wglgsg; DCAST_INTO_R(wglgsg, _gsg, false); if (!wglgsg->_supports_pbuffer) { return false; } int pbformat = wglgsg->get_pfnum(); if (wglgsg->_supports_pixel_format) { // Select a suitable pixel format that matches the GSG's existing // format, and also is appropriate for a pixel buffer. PIXELFORMATDESCRIPTOR pfd; ZeroMemory(&pfd,sizeof(PIXELFORMATDESCRIPTOR)); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; DescribePixelFormat(_window_dc, wglgsg->get_pfnum(), sizeof(PIXELFORMATDESCRIPTOR), &pfd); static const int max_attrib_list = 32; int iattrib_list[max_attrib_list]; float fattrib_list[max_attrib_list]; int ni = 0; int nf = 0; // Since we are trying to create a pbuffer, the pixel format we // request (and subsequently use) must be "pbuffer capable". iattrib_list[ni++] = WGL_DRAW_TO_PBUFFER_ARB; iattrib_list[ni++] = true; iattrib_list[ni++] = WGL_SUPPORT_OPENGL_ARB; iattrib_list[ni++] = true; // Match up the framebuffer bits. iattrib_list[ni++] = WGL_RED_BITS_ARB; iattrib_list[ni++] = pfd.cRedBits; iattrib_list[ni++] = WGL_GREEN_BITS_ARB; iattrib_list[ni++] = pfd.cGreenBits; iattrib_list[ni++] = WGL_BLUE_BITS_ARB; iattrib_list[ni++] = pfd.cBlueBits; iattrib_list[ni++] = WGL_ALPHA_BITS_ARB; iattrib_list[ni++] = pfd.cAlphaBits; iattrib_list[ni++] = WGL_DEPTH_BITS_ARB; iattrib_list[ni++] = pfd.cDepthBits; iattrib_list[ni++] = WGL_STENCIL_BITS_ARB; iattrib_list[ni++] = pfd.cStencilBits; // Match up properties. iattrib_list[ni++] = WGL_DOUBLE_BUFFER_ARB; iattrib_list[ni++] = ((pfd.dwFlags & PFD_DOUBLEBUFFER) != 0); iattrib_list[ni++] = WGL_STEREO_ARB; iattrib_list[ni++] = ((pfd.dwFlags & PFD_STEREO) != 0); // Terminate the lists. nassertr(ni < max_attrib_list && nf < max_attrib_list, NULL); iattrib_list[ni] = 0; fattrib_list[nf] = 0; // Now obtain a list of pixel formats that meet these minimum // requirements. static const int max_pformats = 32; int pformat[max_pformats]; memset(pformat, 0, sizeof(pformat)); unsigned int nformats = 0; if (!wglgsg->_wglChoosePixelFormatARB(_window_dc, iattrib_list, fattrib_list, max_pformats, pformat, &nformats)) { wgldisplay_cat.info() << "Couldn't find a suitable pixel format for creating a pbuffer.\n"; return false; } if (wgldisplay_cat.is_debug()) { wgldisplay_cat.debug() << "Found " << nformats << " pbuffer formats: ["; for (unsigned int i = 0; i < nformats; i++) { wgldisplay_cat.debug(false) << " " << pformat[i]; } wgldisplay_cat.debug(false) << " ]\n"; } pbformat = pformat[0]; } int attrib_list[] = { 0, }; _pbuffer = wglgsg->_wglCreatePbufferARB(_window_dc, pbformat, _x_size, _y_size, attrib_list); if (_pbuffer == 0) { wgldisplay_cat.info() << "Attempt to create pbuffer failed.\n"; return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::process_1_event // Access: Private, Static // Description: Handles one event from the message queue. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: process_1_event() { MSG msg; if (!GetMessage(&msg, NULL, 0, 0)) { // WM_QUIT received. We need a cleaner way to deal with this. // DestroyAllWindows(false); exit(msg.wParam); // this will invoke AtExitFn } // Translate virtual key messages TranslateMessage(&msg); // Call window_proc DispatchMessage(&msg); } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::register_window_class // Access: Private, Static // Description: Registers a Window class for all wglGraphicsBuffers. // This only needs to be done once per session. //////////////////////////////////////////////////////////////////// void wglGraphicsBuffer:: register_window_class() { if (_window_class_registered) { return; } WNDCLASS wc; HINSTANCE instance = GetModuleHandle(NULL); // Clear before filling in window structure! ZeroMemory(&wc, sizeof(WNDCLASS)); wc.style = CS_OWNDC; wc.lpfnWndProc = static_window_proc; wc.hInstance = instance; wc.lpszClassName = _window_class_name; if (!RegisterClass(&wc)) { wgldisplay_cat.error() << "could not register window class!" << endl; return; } _window_class_registered = true; } //////////////////////////////////////////////////////////////////// // Function: wglGraphicsBuffer::static_window_proc // Access: Private, Static // Description: This is attached to the window class for all // wglGraphicsBuffer windows; it is called to handle // window events. //////////////////////////////////////////////////////////////////// LONG WINAPI wglGraphicsBuffer:: static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { return DefWindowProc(hwnd, msg, wparam, lparam); }