mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
535 lines
17 KiB
C++
535 lines
17 KiB
C++
// 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 <wingdi.h>
|
|
|
|
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);
|
|
}
|
|
|
|
|