Irrlicht + wxWidgets + Linux (updated 04 October 2010)
Posted: Thu Aug 26, 2010 8:22 pm
Update 04 October 2010: new sources
Irrlicht plays well with wxWidgets on windows, as one can just get a handle to the window you want to render in, and pass that pointer to the createDeviceEx() function. On linux, it's a little harder. So there's two ways to do this. The easy way, and the right way.
Disclaimer
The information presented here is my understanding based on information I received from the internet. Therefore it is absolutely and 100% true. If you believe there to be an error, take it up with the internet (or just post a reply).
Introduction
First of all, it's helpful to understand how Irrlicht and WxWidgets are different. In the beginning, there was hardware (graphics hardware to be precise). Then there's the X Windows System, which is a pretty thin abstraction of the hardware. X manages a tree in memory of "windows" which are logical rectangles that can be drawn on, and that it can render to the physical screen. X does not manage gui controls and, in fact, doesn't really deal with the details of the appearance at all. The window manager (Gnome, or Compiz in standard ubuntu) actually handles how to draw the windows. GTK+ is a gui widget/control/thingamajig/library that is implemented by the window manager and sits on top of X and provides all of the details for drawing pretty gui controls. WxWidgets is a gui widget/control/thingamajig/library that sits on top of GTK+ (at least in the case of linux) and provides a nice object oriented, c++ API for creating gui's.
Now, 3d drawing (as far as irrlicht is concerned) is done through opengl. OpenGL is a library for drawing stuff (typically implemented in a hardware specific way). OpenGL plays nice with the other kids on your Linux screen real-estate through X. Irrlicht sits on top of openGL and provides a nice c++ API for creating scenes.
ASCII-Graphically, we have this:
So you see how it might be hard to do what we want. As it turns out Irrlicht draws to the screen using X, so in order to get wxWidgets and Irrlicht to play together, we have to dig through a few of the APIs to get what we need.
The easy way (see sources a few posts below):
The constructor for a CIrrDeviceLinux will accept as a SIrrlichtCreationParameters::WindowId the numeric id of an XWindow. It will assume that the window is on the local X server (device 0), and it will attempt to create a glx window from that one. Therefore, all we need to do, is get the X window id. So what we do is we derive a class from wxGLCanvas, and put one of these in a wxWidgets window. At some point after that object has been displayed, we use GetHandle() on our object. The result, on wxGTK is a GTKWidget* pointer. The GTKWidget struct contains a member called "window" which is a pointer to something else. We can then use the GDK_WINDOW_XDISPLAY() macro to get the window id of the X Window of the GTKWidget of the wxWidget (*whew*). If you're curious where the "GDK" came from, GTK is actually built on top of another library called GDK, but I was already tired of making the ASCII art diagram so I left it out until now.
In any case, once we have that ID we can stuff it into a SIrrlichtCreationParameters object and pass it into the createDeviceEx() function to create our irrlicht device. Then, the onPaint handler for our widget does the following:
1. Set it's GL context to the active one
2. Tell irrlicht to render the scene
3. Swap it's buffers to make the new image visible
Now there's a little bit of a thing here. As it turns out, the wxGLCanvas actually makes it's own glxContext object, so things go a little screwy in the CIrrLinuxDevice constructor. As a result, you'll see a little bit of gibberish spit out on your console:
Nevertheless, it seems to work, so I guess Irrlicht manages to get it figured out later on. I haven't dug further into the source to see why. In any case, here's the source for the wxWiget along with some test code (edit: removed, see post below)
The Right Way (updated 04 October 2010)
The reason it works with a wxGLCanvas is that the Irrlicht OpenGL driver class queries XLib and GLX libraries to get the "current" Drawable (window) and GL Context. Therefore, even though the Irrlicht Device constructor thinks it failed to create these things, since the wxGLCanvas already made them current, the OpenGL driver can still find them when it is initializing.
The truth is that we don't actually need a wxGLCanvas to attach irrlicht to a wxWidgets window though. If we try to pass in a normal wxWindow's XWindow id, however, we get errors and nothing is displayed. This is because wxWidgets creates an XWindow whose attributes do not match those that are implied by the parameters of SIrrlichtCreationParameters. So, we have two options.
Irrlicht plays well with wxWidgets on windows, as one can just get a handle to the window you want to render in, and pass that pointer to the createDeviceEx() function. On linux, it's a little harder. So there's two ways to do this. The easy way, and the right way.
Disclaimer
The information presented here is my understanding based on information I received from the internet. Therefore it is absolutely and 100% true. If you believe there to be an error, take it up with the internet (or just post a reply).
Introduction
First of all, it's helpful to understand how Irrlicht and WxWidgets are different. In the beginning, there was hardware (graphics hardware to be precise). Then there's the X Windows System, which is a pretty thin abstraction of the hardware. X manages a tree in memory of "windows" which are logical rectangles that can be drawn on, and that it can render to the physical screen. X does not manage gui controls and, in fact, doesn't really deal with the details of the appearance at all. The window manager (Gnome, or Compiz in standard ubuntu) actually handles how to draw the windows. GTK+ is a gui widget/control/thingamajig/library that is implemented by the window manager and sits on top of X and provides all of the details for drawing pretty gui controls. WxWidgets is a gui widget/control/thingamajig/library that sits on top of GTK+ (at least in the case of linux) and provides a nice object oriented, c++ API for creating gui's.
Now, 3d drawing (as far as irrlicht is concerned) is done through opengl. OpenGL is a library for drawing stuff (typically implemented in a hardware specific way). OpenGL plays nice with the other kids on your Linux screen real-estate through X. Irrlicht sits on top of openGL and provides a nice c++ API for creating scenes.
ASCII-Graphically, we have this:
Code: Select all
___________________
| wxwidgets | _______________
| _______________ | | irrlicht |
| | gtk | | | ___________ |
| | ___________ | | | | opengl | |
| | | gnome | | | | | _______ | |
| | | _______ | | | | | | | | |
| | | | | | | | | | | nativ | | |
| | | | X | | | | | | |_______| | |
| | | |_______| | | | | |_____|_____| |
| | |_____|_____| | | |_______|_______|
| |_______|_______| | |
|_________|_________| |
| __________ |
| | | |
------>| hardware |<----
|__________|
The easy way (see sources a few posts below):
The constructor for a CIrrDeviceLinux will accept as a SIrrlichtCreationParameters::WindowId the numeric id of an XWindow. It will assume that the window is on the local X server (device 0), and it will attempt to create a glx window from that one. Therefore, all we need to do, is get the X window id. So what we do is we derive a class from wxGLCanvas, and put one of these in a wxWidgets window. At some point after that object has been displayed, we use GetHandle() on our object. The result, on wxGTK is a GTKWidget* pointer. The GTKWidget struct contains a member called "window" which is a pointer to something else. We can then use the GDK_WINDOW_XDISPLAY() macro to get the window id of the X Window of the GTKWidget of the wxWidget (*whew*). If you're curious where the "GDK" came from, GTK is actually built on top of another library called GDK, but I was already tired of making the ASCII art diagram so I left it out until now.
In any case, once we have that ID we can stuff it into a SIrrlichtCreationParameters object and pass it into the createDeviceEx() function to create our irrlicht device. Then, the onPaint handler for our widget does the following:
1. Set it's GL context to the active one
2. Tell irrlicht to render the scene
3. Swap it's buffers to make the new image visible
Now there's a little bit of a thing here. As it turns out, the wxGLCanvas actually makes it's own glxContext object, so things go a little screwy in the CIrrLinuxDevice constructor. As a result, you'll see a little bit of gibberish spit out on your console:
Code: Select all
X Error: BadMatch (invalid parameter attributes)
From call : unknown
X Error: GLXBadDrawable
From call : unknown
Could not make context current.
The Right Way (updated 04 October 2010)
The reason it works with a wxGLCanvas is that the Irrlicht OpenGL driver class queries XLib and GLX libraries to get the "current" Drawable (window) and GL Context. Therefore, even though the Irrlicht Device constructor thinks it failed to create these things, since the wxGLCanvas already made them current, the OpenGL driver can still find them when it is initializing.
The truth is that we don't actually need a wxGLCanvas to attach irrlicht to a wxWidgets window though. If we try to pass in a normal wxWindow's XWindow id, however, we get errors and nothing is displayed. This is because wxWidgets creates an XWindow whose attributes do not match those that are implied by the parameters of SIrrlichtCreationParameters. So, we have two options.
- Figure out the attributes of the XWindow that wxWidgets has created, map those to the appropriate values of the elements in SIrrlichtCreationParameters, and pass those in when we create the irrlicht device
- When Irrlicht creates a new device, just create a new XWindow as a child of the one that is passed in.