Files
wxWidgets/src/msw/glcanvas.cpp

676 lines
20 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/msw/glcanvas.cpp
// Purpose: wxGLCanvas, for using OpenGL with wxWidgets under MS Windows
// Author: Julian Smart
// Modified by:
// Created: 04/01/98
// RCS-ID: $Id$
// Copyright: (c) Julian Smart
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "wx/wxprec.h"
#if defined(__BORLANDC__)
#pragma hdrstop
#endif
#if wxUSE_GLCANVAS
#ifndef WX_PRECOMP
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/app.h"
#include "wx/module.h"
#endif
#include "wx/msw/private.h"
#include "wx/glcanvas.h"
// from src/msw/window.cpp
LRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
#ifdef GL_EXT_vertex_array
#define WXUNUSED_WITHOUT_GL_EXT_vertex_array(name) name
#else
#define WXUNUSED_WITHOUT_GL_EXT_vertex_array(name) WXUNUSED(name)
#endif
// ----------------------------------------------------------------------------
// libraries
// ----------------------------------------------------------------------------
/*
The following two compiler directives are specific to the Microsoft Visual
C++ family of compilers
Fundementally what they do is instruct the linker to use these two libraries
for the resolution of symbols. In essence, this is the equivalent of adding
these two libraries to either the Makefile or project file.
This is NOT a recommended technique, and certainly is unlikely to be used
anywhere else in wxWidgets given it is so specific to not only wxMSW, but
also the VC compiler. However, in the case of opengl support, it's an
applicable technique as opengl is optional in setup.h This code (wrapped by
wxUSE_GLCANVAS), now allows opengl support to be added purely by modifying
setup.h rather than by having to modify either the project or DSP fle.
See MSDN for further information on the exact usage of these commands.
*/
#ifdef _MSC_VER
# pragma comment( lib, "opengl32" )
# pragma comment( lib, "glu32" )
#endif
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
static const wxChar *wxGLCanvasClassName = wxT("wxGLCanvasClass");
static const wxChar *wxGLCanvasClassNameNoRedraw = wxT("wxGLCanvasClassNR");
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxGLModule is responsible for unregistering wxGLCanvasClass Windows class
// ----------------------------------------------------------------------------
class wxGLModule : public wxModule
{
public:
bool OnInit() { return true; }
void OnExit() { UnregisterClasses(); }
// register the GL classes if not done yet, return true if ok, false if
// registration failed
static bool RegisterClasses();
// unregister the classes, done automatically on program termination
static void UnregisterClasses();
private:
// wxGLCanvas is only used from the main thread so this is MT-ok
static bool ms_registeredGLClasses;
DECLARE_DYNAMIC_CLASS(wxGLModule)
};
IMPLEMENT_DYNAMIC_CLASS(wxGLModule, wxModule)
bool wxGLModule::ms_registeredGLClasses = false;
/* static */
bool wxGLModule::RegisterClasses()
{
if ( ms_registeredGLClasses )
return true;
// We have to register a special window class because we need the CS_OWNDC
// style for GLCanvas: some OpenGL drivers are buggy and don't work with
// windows without this style
WNDCLASS wndclass;
// the fields which are common to all classes
wndclass.lpfnWndProc = (WNDPROC)wxWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof( DWORD ); // VZ: what is this DWORD used for?
wndclass.hInstance = wxhInstance;
wndclass.hIcon = (HICON) NULL;
wndclass.hCursor = ::LoadCursor((HINSTANCE)NULL, IDC_ARROW);
wndclass.lpszMenuName = NULL;
// Register the GLCanvas class name
wndclass.hbrBackground = (HBRUSH)NULL;
wndclass.lpszClassName = wxGLCanvasClassName;
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
if ( !::RegisterClass(&wndclass) )
{
wxLogLastError(wxT("RegisterClass(wxGLCanvasClass)"));
return false;
}
// Register the GLCanvas class name for windows which don't do full repaint
// on resize
wndclass.lpszClassName = wxGLCanvasClassNameNoRedraw;
wndclass.style &= ~(CS_HREDRAW | CS_VREDRAW);
if ( !::RegisterClass(&wndclass) )
{
wxLogLastError(wxT("RegisterClass(wxGLCanvasClassNameNoRedraw)"));
::UnregisterClass(wxGLCanvasClassName, wxhInstance);
return false;
}
ms_registeredGLClasses = true;
return true;
}
/* static */
void wxGLModule::UnregisterClasses()
{
// we need to unregister the classes in case we're in a DLL which is
// unloaded and then loaded again because if we don't, the registration is
// going to fail in wxGLCanvas::Create() the next time we're loaded
if ( ms_registeredGLClasses )
{
::UnregisterClass(wxGLCanvasClassName, wxhInstance);
::UnregisterClass(wxGLCanvasClassNameNoRedraw, wxhInstance);
ms_registeredGLClasses = false;
}
}
// ----------------------------------------------------------------------------
// wxGLContext
// ----------------------------------------------------------------------------
IMPLEMENT_CLASS(wxGLContext, wxObject)
wxGLContext::wxGLContext(wxGLCanvas *win, const wxGLContext* other)
{
m_glContext = wglCreateContext(win->GetHDC());
wxCHECK_RET( m_glContext, wxT("Couldn't create OpenGL context") );
if ( other )
{
if ( !wglShareLists(other->m_glContext, m_glContext) )
wxLogLastError(_T("wglShareLists"));
}
}
wxGLContext::~wxGLContext()
{
// note that it's ok to delete the context even if it's the current one
wglDeleteContext(m_glContext);
}
bool wxGLContext::SetCurrent(const wxGLCanvas& win) const
{
if ( !wglMakeCurrent(win.GetHDC(), m_glContext) )
{
wxLogLastError(_T("wglMakeCurrent"));
return false;
}
return true;
}
// ============================================================================
// wxGLCanvas
// ============================================================================
IMPLEMENT_CLASS(wxGLCanvas, wxWindow)
BEGIN_EVENT_TABLE(wxGLCanvas, wxWindow)
EVT_PALETTE_CHANGED(wxGLCanvas::OnPaletteChanged)
EVT_QUERY_NEW_PALETTE(wxGLCanvas::OnQueryNewPalette)
END_EVENT_TABLE()
// ----------------------------------------------------------------------------
// wxGLCanvas construction
// ----------------------------------------------------------------------------
void wxGLCanvas::Init()
{
#if WXWIN_COMPATIBILITY_2_8
m_glContext = NULL;
#endif
m_hDC = NULL;
}
wxGLCanvas::wxGLCanvas(wxWindow *parent,
wxWindowID id,
const int *attribList,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name,
const wxPalette& palette)
{
Init();
(void)Create(parent, id, pos, size, style, name, attribList, palette);
}
wxGLCanvas::~wxGLCanvas()
{
::ReleaseDC(GetHwnd(), m_hDC);
}
// Replaces wxWindow::Create functionality, since we need to use a different
// window class
bool wxGLCanvas::Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name,
const int *attribList,
const wxPalette& palette)
{
wxCHECK_MSG( parent, false, wxT("can't create wxWindow without parent") );
if ( !wxGLModule::RegisterClasses() )
{
wxLogError(_("Failed to register OpenGL window class."));
return false;
}
if ( !CreateBase(parent, id, pos, size, style, wxDefaultValidator, name) )
return false;
parent->AddChild(this);
/*
A general rule with OpenGL and Win32 is that any window that will have a
HGLRC built for it must have two flags: WS_CLIPCHILDREN & WS_CLIPSIBLINGS.
You can find references about this within the knowledge base and most OpenGL
books that contain the wgl function descriptions.
*/
WXDWORD exStyle = 0;
DWORD msflags = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
msflags |= MSWGetStyle(style, &exStyle);
if ( !MSWCreate(wxGLCanvasClassName, NULL, pos, size, msflags, exStyle) )
return false;
m_hDC = ::GetDC(GetHwnd());
if ( !m_hDC )
return false;
if ( !DoSetup(attribList) )
return false;
#if wxUSE_PALETTE
if ( !SetupPalette(palette) )
return false;
#else // !wxUSE_PALETTE
wxUnusedVar(palette);
#endif // wxUSE_PALETTE/!wxUSE_PALETTE
return true;
}
// ----------------------------------------------------------------------------
// operations
// ----------------------------------------------------------------------------
bool wxGLCanvas::SwapBuffers()
{
if ( !::SwapBuffers(m_hDC) )
{
wxLogLastError(_T("SwapBuffers"));
return false;
}
return true;
}
// ----------------------------------------------------------------------------
// pixel format stuff
// ----------------------------------------------------------------------------
static void
AdjustPFDForAttributes(PIXELFORMATDESCRIPTOR& pfd, const int *attribList)
{
if ( !attribList )
return;
// remove default attributes
pfd.dwFlags &= ~PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_COLORINDEX;
for ( int arg = 0; attribList[arg]; )
{
switch ( attribList[arg++] )
{
case WX_GL_RGBA:
pfd.iPixelType = PFD_TYPE_RGBA;
break;
case WX_GL_BUFFER_SIZE:
pfd.cColorBits = attribList[arg++];
break;
case WX_GL_LEVEL:
// this member looks like it may be obsolete
if ( attribList[arg] > 0 )
pfd.iLayerType = PFD_OVERLAY_PLANE;
else if ( attribList[arg] < 0 )
pfd.iLayerType = (BYTE)PFD_UNDERLAY_PLANE;
else
pfd.iLayerType = PFD_MAIN_PLANE;
arg++;
break;
case WX_GL_DOUBLEBUFFER:
pfd.dwFlags |= PFD_DOUBLEBUFFER;
break;
case WX_GL_STEREO:
pfd.dwFlags |= PFD_STEREO;
break;
case WX_GL_AUX_BUFFERS:
pfd.cAuxBuffers = attribList[arg++];
break;
case WX_GL_MIN_RED:
pfd.cColorBits += (pfd.cRedBits = attribList[arg++]);
break;
case WX_GL_MIN_GREEN:
pfd.cColorBits += (pfd.cGreenBits = attribList[arg++]);
break;
case WX_GL_MIN_BLUE:
pfd.cColorBits += (pfd.cBlueBits = attribList[arg++]);
break;
case WX_GL_MIN_ALPHA:
// doesn't count in cColorBits
pfd.cAlphaBits = attribList[arg++];
break;
case WX_GL_DEPTH_SIZE:
pfd.cDepthBits = attribList[arg++];
break;
case WX_GL_STENCIL_SIZE:
pfd.cStencilBits = attribList[arg++];
break;
case WX_GL_MIN_ACCUM_RED:
pfd.cAccumBits += (pfd.cAccumRedBits = attribList[arg++]);
break;
case WX_GL_MIN_ACCUM_GREEN:
pfd.cAccumBits += (pfd.cAccumGreenBits = attribList[arg++]);
break;
case WX_GL_MIN_ACCUM_BLUE:
pfd.cAccumBits += (pfd.cAccumBlueBits = attribList[arg++]);
break;
case WX_GL_MIN_ACCUM_ALPHA:
pfd.cAccumBits += (pfd.cAccumAlphaBits = attribList[arg++]);
break;
}
}
}
/* static */
int
wxGLCanvas::ChooseMatchingPixelFormat(HDC hdc,
const int *attribList,
PIXELFORMATDESCRIPTOR *ppfd)
{
// default neutral pixel format
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // size
1, // version
PFD_SUPPORT_OPENGL |
PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER, // use double-buffering by default
PFD_TYPE_RGBA, // default pixel type
0, // preferred color depth (don't care)
0, 0, 0, 0, 0, 0, // color bits and shift bits (ignored)
0, 0, // alpha bits and shift (ignored)
0, // accumulation total bits
0, 0, 0, 0, // accumulator RGBA bits (not used)
16, // depth buffer
0, // no stencil buffer
0, // no auxiliary buffers
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0, // no layer, visible, damage masks
};
if ( !ppfd )
ppfd = &pfd;
else
*ppfd = pfd;
AdjustPFDForAttributes(*ppfd, attribList);
return ::ChoosePixelFormat(hdc, ppfd);
}
/* static */
bool wxGLCanvasBase::IsDisplaySupported(const int *attribList)
{
// We need a device context to test the pixel format, so get one
// for the root window.
return wxGLCanvas::ChooseMatchingPixelFormat(ScreenHDC(), attribList) > 0;
}
bool wxGLCanvas::DoSetup(const int *attribList)
{
PIXELFORMATDESCRIPTOR pfd;
const int pixelFormat = ChooseMatchingPixelFormat(m_hDC, attribList, &pfd);
if ( !pixelFormat )
{
wxLogLastError(_T("ChoosePixelFormat"));
return false;
}
if ( !::SetPixelFormat(m_hDC, pixelFormat, &pfd) )
{
wxLogLastError(_T("SetPixelFormat"));
return false;
}
return true;
}
// ----------------------------------------------------------------------------
// palette stuff
// ----------------------------------------------------------------------------
#if wxUSE_PALETTE
bool wxGLCanvas::SetupPalette(const wxPalette& palette)
{
const int pixelFormat = ::GetPixelFormat(m_hDC);
if ( !pixelFormat )
{
wxLogLastError(_T("GetPixelFormat"));
return false;
}
PIXELFORMATDESCRIPTOR pfd;
if ( !::DescribePixelFormat(m_hDC, pixelFormat, sizeof(pfd), &pfd) )
{
wxLogLastError(_T("DescribePixelFormat"));
return false;
}
if ( !(pfd.dwFlags & PFD_NEED_PALETTE) )
return true;
m_palette = palette;
if ( !m_palette.Ok() )
{
m_palette = CreateDefaultPalette();
if ( !m_palette.Ok() )
return false;
}
if ( !::SelectPalette(m_hDC, GetHpaletteOf(m_palette), FALSE) )
{
wxLogLastError(_T("SelectPalette"));
return false;
}
if ( ::RealizePalette(m_hDC) == GDI_ERROR )
{
wxLogLastError(_T("RealizePalette"));
return false;
}
return true;
}
wxPalette wxGLCanvas::CreateDefaultPalette()
{
PIXELFORMATDESCRIPTOR pfd;
int paletteSize;
int pixelFormat = GetPixelFormat(m_hDC);
DescribePixelFormat(m_hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
paletteSize = 1 << pfd.cColorBits;
LOGPALETTE* pPal =
(LOGPALETTE*) malloc(sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY));
pPal->palVersion = 0x300;
pPal->palNumEntries = (WORD)paletteSize;
/* build a simple RGB color palette */
{
int redMask = (1 << pfd.cRedBits) - 1;
int greenMask = (1 << pfd.cGreenBits) - 1;
int blueMask = (1 << pfd.cBlueBits) - 1;
int i;
for (i=0; i<paletteSize; ++i) {
pPal->palPalEntry[i].peRed =
(BYTE)((((i >> pfd.cRedShift) & redMask) * 255) / redMask);
pPal->palPalEntry[i].peGreen =
(BYTE)((((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
pPal->palPalEntry[i].peBlue =
(BYTE)((((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
pPal->palPalEntry[i].peFlags = 0;
}
}
HPALETTE hPalette = CreatePalette(pPal);
free(pPal);
wxPalette palette;
palette.SetHPALETTE((WXHPALETTE) hPalette);
return palette;
}
void wxGLCanvas::OnQueryNewPalette(wxQueryNewPaletteEvent& event)
{
/* realize palette if this is the current window */
if ( GetPalette()->Ok() ) {
::UnrealizeObject((HPALETTE) GetPalette()->GetHPALETTE());
::SelectPalette(GetHDC(), (HPALETTE) GetPalette()->GetHPALETTE(), FALSE);
::RealizePalette(GetHDC());
Refresh();
event.SetPaletteRealized(true);
}
else
event.SetPaletteRealized(false);
}
void wxGLCanvas::OnPaletteChanged(wxPaletteChangedEvent& event)
{
/* realize palette if this is *not* the current window */
if ( GetPalette() &&
GetPalette()->Ok() && (this != event.GetChangedWindow()) )
{
::UnrealizeObject((HPALETTE) GetPalette()->GetHPALETTE());
::SelectPalette(GetHDC(), (HPALETTE) GetPalette()->GetHPALETTE(), FALSE);
::RealizePalette(GetHDC());
Refresh();
}
}
#endif // wxUSE_PALETTE
// ----------------------------------------------------------------------------
// deprecated wxGLCanvas methods using implicit wxGLContext
// ----------------------------------------------------------------------------
// deprecated constructors creating an implicit m_glContext
#if WXWIN_COMPATIBILITY_2_8
wxGLCanvas::wxGLCanvas(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name,
const int *attribList,
const wxPalette& palette)
{
Init();
if ( Create(parent, id, pos, size, style, name, attribList, palette) )
m_glContext = new wxGLContext(this);
}
wxGLCanvas::wxGLCanvas(wxWindow *parent,
const wxGLContext *shared,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name,
const int *attribList,
const wxPalette& palette)
{
Init();
if ( Create(parent, id, pos, size, style, name, attribList, palette) )
m_glContext = new wxGLContext(this, shared);
}
wxGLCanvas::wxGLCanvas(wxWindow *parent,
const wxGLCanvas *shared,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name,
const int *attribList,
const wxPalette& palette)
{
Init();
if ( Create(parent, id, pos, size, style, name, attribList, palette) )
m_glContext = new wxGLContext(this, shared ? shared->m_glContext : NULL);
}
#endif // WXWIN_COMPATIBILITY_2_8
// ----------------------------------------------------------------------------
// wxGLApp
// ----------------------------------------------------------------------------
bool wxGLApp::InitGLVisual(const int *attribList)
{
if ( !wxGLCanvas::ChooseMatchingPixelFormat(ScreenHDC(), attribList) )
{
wxLogError(_("Failed to initialize OpenGL"));
return false;
}
return true;
}
#endif // wxUSE_GLCANVAS