mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-03-24 11:24:08 +08:00
The same library binary can now be used to use either GLX or EGL for wxGLCanvas implementation instead of the choice being done at build time (by either always using EGL if it is enabled or always using GLX otherwise). New wxGLCanvas::PreferGLX() function (available only if wxHAS_GLX is defined) can be used to request using GLX even if EGL is available, otherwise EGL is used by default, just as before. Additionally new "opengl.egl" system option can be used to the same effect, mostly to allow users to set "wx_opengl_egl" environment variable to 0 to use GLX if possible, i.e. with X11 and not Wayland.
555 lines
16 KiB
C++
555 lines
16 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Name: cube.cpp
|
|
// Purpose: wxGLCanvas demo program
|
|
// Author: Julian Smart
|
|
// Modified by: Vadim Zeitlin to use new wxGLCanvas API (2007-04-09)
|
|
// Created: 04/01/98
|
|
// Copyright: (c) Julian Smart
|
|
// Licence: wxWindows licence
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/wx.h"
|
|
#endif
|
|
|
|
#if !wxUSE_GLCANVAS
|
|
#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
|
|
#endif
|
|
|
|
#include "cube.h"
|
|
|
|
#ifndef wxHAS_IMAGES_IN_RESOURCES
|
|
#include "../../sample.xpm"
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// constants
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// control ids
|
|
enum
|
|
{
|
|
SpinTimer = wxID_HIGHEST
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// helper functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static void CheckGLError()
|
|
{
|
|
GLenum errLast = GL_NO_ERROR;
|
|
|
|
for ( ;; )
|
|
{
|
|
GLenum err = glGetError();
|
|
if ( err == GL_NO_ERROR )
|
|
return;
|
|
|
|
// normally the error is reset by the call to glGetError() but if
|
|
// glGetError() itself returns an error, we risk looping forever here
|
|
// so check that we get a different error than the last time
|
|
if ( err == errLast )
|
|
{
|
|
wxLogError("OpenGL error state couldn't be reset.");
|
|
return;
|
|
}
|
|
|
|
errLast = err;
|
|
|
|
wxLogError("OpenGL error %d", err);
|
|
}
|
|
}
|
|
|
|
// function to draw the texture for cube faces
|
|
static wxImage DrawDice(int size, unsigned num)
|
|
{
|
|
wxASSERT_MSG( num >= 1 && num <= 6, "invalid dice index" );
|
|
|
|
const int dot = size/16; // radius of a single dot
|
|
const int gap = 5*size/32; // gap between dots
|
|
|
|
wxBitmap bmp(size, size);
|
|
wxMemoryDC dc;
|
|
dc.SelectObject(bmp);
|
|
dc.SetBackground(*wxWHITE_BRUSH);
|
|
dc.Clear();
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
|
|
// the upper left and lower right points
|
|
if ( num != 1 )
|
|
{
|
|
dc.DrawCircle(gap + dot, gap + dot, dot);
|
|
dc.DrawCircle(size - gap - dot, size - gap - dot, dot);
|
|
}
|
|
|
|
// draw the central point for odd dices
|
|
if ( num % 2 )
|
|
{
|
|
dc.DrawCircle(size/2, size/2, dot);
|
|
}
|
|
|
|
// the upper right and lower left points
|
|
if ( num > 3 )
|
|
{
|
|
dc.DrawCircle(size - gap - dot, gap + dot, dot);
|
|
dc.DrawCircle(gap + dot, size - gap - dot, dot);
|
|
}
|
|
|
|
// finally those 2 are only for the last dice
|
|
if ( num == 6 )
|
|
{
|
|
dc.DrawCircle(gap + dot, size/2, dot);
|
|
dc.DrawCircle(size - gap - dot, size/2, dot);
|
|
}
|
|
|
|
dc.SelectObject(wxNullBitmap);
|
|
|
|
return bmp.ConvertToImage();
|
|
}
|
|
|
|
// ============================================================================
|
|
// implementation
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TestGLContext
|
|
// ----------------------------------------------------------------------------
|
|
|
|
TestGLContext::TestGLContext(wxGLCanvas *canvas)
|
|
: wxGLContext(canvas)
|
|
{
|
|
SetCurrent(*canvas);
|
|
|
|
// set up the parameters we want to use
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_LIGHT0);
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
// add slightly more light, the default lighting is rather dark
|
|
GLfloat ambient[] = { 0.5, 0.5, 0.5, 0.5 };
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
|
|
|
|
// set viewing projection
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glFrustum(-0.5, 0.5, -0.5, 0.5, 1, 3);
|
|
|
|
// create the textures to use for cube sides: they will be reused by all
|
|
// canvases (which is probably not critical in the case of simple textures
|
|
// we use here but could be really important for a real application where
|
|
// each texture could take many megabytes)
|
|
glGenTextures(WXSIZEOF(m_textures), m_textures);
|
|
|
|
for ( unsigned i = 0; i < WXSIZEOF(m_textures); i++ )
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[i]);
|
|
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
const wxImage img(DrawDice(256, i + 1));
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.GetWidth(), img.GetHeight(),
|
|
0, GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
|
|
}
|
|
|
|
CheckGLError();
|
|
}
|
|
|
|
void TestGLContext::DrawRotatedCube(float xangle, float yangle)
|
|
{
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
glTranslatef(0.0f, 0.0f, -2.0f);
|
|
glRotatef(xangle, 1.0f, 0.0f, 0.0f);
|
|
glRotatef(yangle, 0.0f, 1.0f, 0.0f);
|
|
|
|
// draw six faces of a cube of size 1 centered at (0, 0, 0)
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[0]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f( 0.0f, 0.0f, 1.0f);
|
|
glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f(-0.5f, 0.5f, 0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f(-0.5f,-0.5f, 0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f( 0.5f,-0.5f, 0.5f);
|
|
glEnd();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[1]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f( 0.0f, 0.0f,-1.0f);
|
|
glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f(-0.5f, 0.5f,-0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f( 0.5f, 0.5f,-0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f( 0.5f,-0.5f,-0.5f);
|
|
glEnd();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[2]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f( 0.0f, 1.0f, 0.0f);
|
|
glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f( 0.5f, 0.5f,-0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f(-0.5f, 0.5f,-0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f(-0.5f, 0.5f, 0.5f);
|
|
glEnd();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[3]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f( 0.0f,-1.0f, 0.0f);
|
|
glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f( 0.5f,-0.5f,-0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f( 0.5f,-0.5f, 0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f(-0.5f,-0.5f, 0.5f);
|
|
glEnd();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[4]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f( 1.0f, 0.0f, 0.0f);
|
|
glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f( 0.5f,-0.5f, 0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f( 0.5f,-0.5f,-0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f( 0.5f, 0.5f,-0.5f);
|
|
glEnd();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_textures[5]);
|
|
glBegin(GL_QUADS);
|
|
glNormal3f(-1.0f, 0.0f, 0.0f);
|
|
glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
|
|
glTexCoord2f(1, 0); glVertex3f(-0.5f,-0.5f, 0.5f);
|
|
glTexCoord2f(1, 1); glVertex3f(-0.5f, 0.5f, 0.5f);
|
|
glTexCoord2f(0, 1); glVertex3f(-0.5f, 0.5f,-0.5f);
|
|
glEnd();
|
|
|
|
glFlush();
|
|
|
|
CheckGLError();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// MyApp: the application object
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxIMPLEMENT_APP(MyApp);
|
|
|
|
bool MyApp::OnInit()
|
|
{
|
|
if ( !wxApp::OnInit() )
|
|
return false;
|
|
|
|
new MyFrame();
|
|
|
|
return true;
|
|
}
|
|
|
|
int MyApp::OnExit()
|
|
{
|
|
delete m_glContext;
|
|
delete m_glStereoContext;
|
|
|
|
return wxApp::OnExit();
|
|
}
|
|
|
|
TestGLContext& MyApp::GetContext(wxGLCanvas *canvas, bool useStereo)
|
|
{
|
|
TestGLContext *glContext;
|
|
if ( useStereo )
|
|
{
|
|
if ( !m_glStereoContext )
|
|
{
|
|
// Create the OpenGL context for the first stereo window which needs it:
|
|
// subsequently created windows will all share the same context.
|
|
m_glStereoContext = new TestGLContext(canvas);
|
|
}
|
|
glContext = m_glStereoContext;
|
|
}
|
|
else
|
|
{
|
|
if ( !m_glContext )
|
|
{
|
|
// Create the OpenGL context for the first mono window which needs it:
|
|
// subsequently created windows will all share the same context.
|
|
m_glContext = new TestGLContext(canvas);
|
|
}
|
|
glContext = m_glContext;
|
|
}
|
|
|
|
glContext->SetCurrent(*canvas);
|
|
|
|
return *glContext;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TestGLCanvas
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxBEGIN_EVENT_TABLE(TestGLCanvas, wxGLCanvas)
|
|
EVT_PAINT(TestGLCanvas::OnPaint)
|
|
EVT_KEY_DOWN(TestGLCanvas::OnKeyDown)
|
|
EVT_TIMER(SpinTimer, TestGLCanvas::OnSpinTimer)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
TestGLCanvas::TestGLCanvas(wxWindow *parent, bool useStereo)
|
|
// With perspective OpenGL graphics, the wxFULL_REPAINT_ON_RESIZE style
|
|
// flag should always be set, because even making the canvas smaller should
|
|
// be followed by a paint event that updates the entire canvas with new
|
|
// viewport settings.
|
|
: m_xangle(30.0),
|
|
m_yangle(30.0),
|
|
m_spinTimer(this,SpinTimer),
|
|
m_useStereo(useStereo),
|
|
m_stereoWarningAlreadyDisplayed(false)
|
|
{
|
|
wxGLAttributes attribs = wxGLAttributes().Defaults();
|
|
if ( useStereo )
|
|
attribs.Stereo();
|
|
attribs.EndList();
|
|
|
|
if ( !wxGLCanvas::Create(parent, attribs, wxID_ANY,
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxFULL_REPAINT_ON_RESIZE) )
|
|
{
|
|
wxLogError("Creating OpenGL window failed.");
|
|
}
|
|
}
|
|
|
|
void TestGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
|
|
{
|
|
// This is required even though dc is not used otherwise.
|
|
wxPaintDC dc(this);
|
|
|
|
// Set the OpenGL viewport according to the client size of this canvas.
|
|
// This is done here rather than in a wxSizeEvent handler because our
|
|
// OpenGL rendering context (and thus viewport setting) is used with
|
|
// multiple canvases: If we updated the viewport in the wxSizeEvent
|
|
// handler, changing the size of one canvas causes a viewport setting that
|
|
// is wrong when next another canvas is repainted.
|
|
const wxSize ClientSize = GetClientSize() * GetContentScaleFactor();
|
|
|
|
TestGLContext& canvas = wxGetApp().GetContext(this, m_useStereo);
|
|
glViewport(0, 0, ClientSize.x, ClientSize.y);
|
|
|
|
// Render the graphics and swap the buffers.
|
|
GLboolean quadStereoSupported;
|
|
glGetBooleanv( GL_STEREO, &quadStereoSupported);
|
|
if ( quadStereoSupported )
|
|
{
|
|
glDrawBuffer( GL_BACK_LEFT );
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glFrustum(-0.47, 0.53, -0.5, 0.5, 1, 3);
|
|
canvas.DrawRotatedCube(m_xangle, m_yangle);
|
|
CheckGLError();
|
|
glDrawBuffer( GL_BACK_RIGHT );
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glFrustum(-0.53, 0.47, -0.5, 0.5, 1, 3);
|
|
canvas.DrawRotatedCube(m_xangle, m_yangle);
|
|
CheckGLError();
|
|
}
|
|
else
|
|
{
|
|
canvas.DrawRotatedCube(m_xangle, m_yangle);
|
|
if ( m_useStereo && !m_stereoWarningAlreadyDisplayed )
|
|
{
|
|
m_stereoWarningAlreadyDisplayed = true;
|
|
wxLogError("Stereo not supported by the graphics card.");
|
|
}
|
|
}
|
|
SwapBuffers();
|
|
}
|
|
|
|
void TestGLCanvas::Spin(float xSpin, float ySpin)
|
|
{
|
|
m_xangle += xSpin;
|
|
m_yangle += ySpin;
|
|
|
|
Refresh(false);
|
|
}
|
|
|
|
void TestGLCanvas::OnKeyDown(wxKeyEvent& event)
|
|
{
|
|
float angle = 5.0;
|
|
|
|
switch ( event.GetKeyCode() )
|
|
{
|
|
case WXK_RIGHT:
|
|
Spin( 0.0, -angle );
|
|
break;
|
|
|
|
case WXK_LEFT:
|
|
Spin( 0.0, angle );
|
|
break;
|
|
|
|
case WXK_DOWN:
|
|
Spin( -angle, 0.0 );
|
|
break;
|
|
|
|
case WXK_UP:
|
|
Spin( angle, 0.0 );
|
|
break;
|
|
|
|
case WXK_SPACE:
|
|
if ( m_spinTimer.IsRunning() )
|
|
m_spinTimer.Stop();
|
|
else
|
|
m_spinTimer.Start( 25 );
|
|
break;
|
|
|
|
default:
|
|
event.Skip();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TestGLCanvas::OnSpinTimer(wxTimerEvent& WXUNUSED(event))
|
|
{
|
|
Spin(0.0, 4.0);
|
|
}
|
|
|
|
wxString glGetwxString(GLenum name)
|
|
{
|
|
const GLubyte *v = glGetString(name);
|
|
if ( v == nullptr )
|
|
{
|
|
// The error is not important. It is GL_INVALID_ENUM.
|
|
// We just want to clear the error stack.
|
|
glGetError();
|
|
|
|
return wxString();
|
|
}
|
|
|
|
return wxString((const char*)v);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// MyFrame: main application window
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
|
|
EVT_MENU(wxID_NEW, MyFrame::OnNewWindow)
|
|
EVT_MENU(NEW_STEREO_WINDOW, MyFrame::OnNewStereoWindow)
|
|
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
|
|
EVT_MENU(wxID_CLOSE, MyFrame::OnClose)
|
|
wxEND_EVENT_TABLE()
|
|
|
|
MyFrame::MyFrame( bool stereoWindow )
|
|
: wxFrame(nullptr, wxID_ANY, "wxWidgets OpenGL Cube Sample")
|
|
{
|
|
new TestGLCanvas(this, stereoWindow);
|
|
|
|
SetIcon(wxICON(sample));
|
|
|
|
// Make a menubar
|
|
wxMenu *menu = new wxMenu;
|
|
menu->Append(wxID_NEW);
|
|
menu->Append(NEW_STEREO_WINDOW, "New Stereo Window");
|
|
menu->AppendSeparator();
|
|
menu->Append(wxID_ABOUT, "&About...\tF1");
|
|
menu->AppendSeparator();
|
|
menu->Append(wxID_CLOSE);
|
|
wxMenuBar *menuBar = new wxMenuBar;
|
|
menuBar->Append(menu, "&Cube");
|
|
|
|
SetMenuBar(menuBar);
|
|
|
|
CreateStatusBar();
|
|
|
|
SetClientSize(400, 400);
|
|
Show();
|
|
|
|
// test IsDisplaySupported() function:
|
|
wxGLAttributes attribs;
|
|
attribs.RGBA().DoubleBuffer().EndList();
|
|
wxLogStatus("Double-buffered display %s supported",
|
|
wxGLCanvas::IsDisplaySupported(attribs) ? "is" : "not");
|
|
|
|
if ( stereoWindow )
|
|
{
|
|
const wxString vendor = glGetwxString(GL_VENDOR).Lower();
|
|
const wxString renderer = glGetwxString(GL_RENDERER).Lower();
|
|
if ( vendor.find("nvidia") != wxString::npos &&
|
|
renderer.find("quadro") == wxString::npos )
|
|
ShowFullScreen(true);
|
|
}
|
|
}
|
|
|
|
void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
wxString info = "This is the wxWidgets OpenGL Cube sample.\n\n";
|
|
|
|
#ifdef wxHAS_GLX
|
|
const int glxVersion = wxGLCanvasUnix::GetGLXVersion();
|
|
if ( glxVersion == 0 )
|
|
{
|
|
info += "Using EGL.\n\n";
|
|
}
|
|
else
|
|
{
|
|
info += wxString::Format("Using GLX %d.%d.\n\n",
|
|
glxVersion / 10, glxVersion % 10);
|
|
}
|
|
#endif // wxHAS_GLX
|
|
|
|
auto const getString = [](GLenum name) -> wxString
|
|
{
|
|
const GLubyte* const str = glGetString(name);
|
|
return wxString::FromUTF8(reinterpret_cast<const char*>(str));
|
|
};
|
|
|
|
info += wxString::Format("OpenGL version: %s\n", getString(GL_VERSION));
|
|
info += wxString::Format("OpenGL vendor: %s\n", getString(GL_VENDOR));
|
|
info += wxString::Format("OpenGL renderer: %s", getString(GL_RENDERER));
|
|
|
|
wxMessageBox(info,
|
|
"About wxWidgets OpenGL cube sample",
|
|
wxOK | wxICON_INFORMATION,
|
|
this);
|
|
}
|
|
|
|
void MyFrame::OnClose(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
// true is to force the frame to close
|
|
Close(true);
|
|
}
|
|
|
|
void MyFrame::OnNewWindow( wxCommandEvent& WXUNUSED(event) )
|
|
{
|
|
new MyFrame();
|
|
}
|
|
|
|
void MyFrame::OnNewStereoWindow( wxCommandEvent& WXUNUSED(event) )
|
|
{
|
|
wxGLAttributes attribs;
|
|
attribs.RGBA().DoubleBuffer().Stereo().EndList();
|
|
if ( wxGLCanvas::IsDisplaySupported(attribs) )
|
|
{
|
|
new MyFrame(true);
|
|
}
|
|
else
|
|
{
|
|
wxLogError("Stereo not supported by OpenGL on this system, sorry.");
|
|
}
|
|
}
|