Search This Blog

Labels

Sunday, December 26, 2010

The Generic Handy Operating System Toolkit

Introduction

The Generic Handy Operating System Toolkit (GHOST) is a windowing wrapper which was originally written at NaN Technologies BV to support their program Blender. GHOST was written when the implementation of GLUT on Mac OSX was shown to be inadequate, and since the GLUT sources were not available at that time, NaN chose to write their own GLUT replacement, thus GHOST was born.
With the open sourcing of Blender, the decision has been made to split the GHOST library out into its own project. This being done for maintenance purposes, and to allow others in the community to work with the GHOST library without having to bother with the Blender sources.

Because GHOST is a replacement for GLUT, it is able to handle all of the GLUT requirements which arise in Blender. These include:

  • Timer management.
  • Display / window management (only on the main display).
  • Event management.
  • Cursor shape management (currently no custom cursors).
  • Access to mouse buttons, mouse wheel, and keyboard information.

This article is going to show how to create a GHOST program, a simple rotating cube, which will accept keyboard and mouse wheel input.
This tutorial is going to assume that you have C coding experience and have used OpenGL before, as it will be used for the drawing. Even though this article uses C, GHOST is actually written in C++, but contains a C wrapper API. As I prefer C to C++ this article will be using that API. The concepts and functions will be similar if you wish to use C++, but you'll need to figure out the exact functions and objects to use.
Also, this program has only been complied on Linux. GHOST has support for Unix, Windows and Mac OS X systems built into it, but there is no guarentee that this example program will execute on any system other than Linux without modification.

Getting and installing GHOST

GHOST is currently only available through CVS. The repository can be accessed fromprojects.blender.org/projects/ghost. All the latest information on GHOST can be found on this site. If any bugs are found while developing they can be filed here under the tracker. The checkout directions are available under the CVS tab.
In order to compile GHOST successfully you will need a fairly recent version of autoconf/automake. When I compiled I needed to use:

  • autoconf 2.57
  • automake 1.6

Older versions may work, but then they may not. If you receive errors while doing a ./bootstrap then try upgrading your autoconf/automake.
To compile GHOST use the following commands (change /usr/local to where you wish the library to install to):

      ./bootstrap
./configure --prefix=/usr/local/
make
su
make install

If everything goes correctly you will have libGHOST.a installed under /usr/local/lib and will have the GHOST headers installed in /usr/local/include/GHOST.


The code

The example program listed here is a simple one that I used to learn the basics of GHOST. The program is pretty simple; it creates the necessary GHOST objects to show a window, draws an OpenGL box inside the window, then adds a timer when the 't' key is pressed to rotate the box. The mouse wheel is used to rotate a small amount in the X-direction whenever it is rolled.
The code listings in the article will only give the GHOST relevant portions of the code. The full code listing is availablehere.



  #include <stdlib.h>
#include <stdio.h>
#include <GHOST_C-api.h>
#include <GL/gl.h>

We will be using the C API as mentioned above, and most all GHOST applications will require the GL/gl.h library to do the OpenGL work.



  static GHOST_SystemHandle ghost_system;
static GHOST_EventConsumerHandle consumer;
static GHOST_WindowHandle win;
static GHOST_WindowHandle full_screen_window;
static GHOST_TimerTaskHandle timer;

Next we create all of our global variables to store GHOST information. The first line creates the system. All GHOST apps are required to have one (and only one) GHOST_SystemHandle. This handle will be passed to many of the GHOST functions used throughout the program.
The second line is for the GHOST_EventConsumerHandle, this handle will have a function pointer attached to it on creation which will be called whenever events arrive through the system.
Third and fourth lines are the windows which will be used in the application, the first is the main window, and the second is used when we move into full screen mode.
The fifth line is the declaration of a GHOST timer which will be used when we want to rotate our box.



  void invalidate_window(void) {
if (GHOST_GetFullScreen(ghost_system))
GHOST_InvalidateWindow(full_screen_window);
else {
if (GHOST_ValidWindow(ghost_system, win))
GHOST_InvalidateWindow(win);
}
}

The invalidate_window routine will be used when we need to tell GHOST that we have modified the window contents. This will cause GHOST to generate a GHOST_kWindowUpdate event which we will be handled later.



  void timer_proc(GHOST_TimerTaskHandle task, GHOST_TUns64 time) {
update_window();
invalidate_window();
}

This is the procedure which we will attach to the timer when it is created. It will be called whenever the timer event goes off. The parameters to the function are: a handle to the timer which caused the function call, and the time at which the timer went off.



  void setup_window(GHOST_WindowHandle win) {
GHOST_RectangleHandle rect = NULL;
GLfloat w, h, aspect;

GHOST_ActivateWindowDrawingContext(win);
rect = GHOST_GetClientBounds(win);

w = (GLfloat)GHOST_GetWidthRectangle(rect);
h = (GLfloat)GHOST_GetHeightRectangle(rect);
GHOST_DisposeRectangle(rect);
...
}

The setup_window routine is doing the initial setup of the GHOST window, through the call to GHOST_ActivateWindowDrawingContext and then receives the size of the window through the GHOST_GetClientBounds. We can then get the width and height of the window to be used in the OpenGL initialization routines.



  int process_event(GHOST_EventHandle event, GHOST_TUserDataPtr data) {

The process_event procedure will be attached to the event consumer when it is created. When an event happens the routines attached as consumers will be called and passed the event which caused the call, and any data which has been attached to the call-back.




int handled = 0;
GHOST_TEventKeyData *key_data =
(GHOST_TEventKeyData *)GHOST_GetEventData(event);

The GHOST_GetEventData call will return any keyboard data attached to this event.




switch (GHOST_GetEventType(event)) {
case GHOST_kEventUnknown:
case GHOST_kEventCursorMove:
case GHOST_kEventButtonDown:
case GHOST_kEventButtonUp:
case GHOST_kEventKeyUp:
case GHOST_kEventWindowActivate:
case GHOST_kEventWindowDeactivate:
case GHOST_kEventWindowSize:
case GHOST_kNumEventTypes:
break;

case GHOST_kEventWheel:
{
GHOST_TEventWheelData *wheel =
(GHOST_TEventWheelData *)GHOST_GetEventData(event);

if (wheel->z > 0)
xrot += 2;
else
xrot -= 2;

update_window();
invalidate_window();
}
break;

If you have a mouse wheel, GHOST is able to handle the movement of the wheel. Movement in one direction returns a wheel->z of -1 and in the other direction of 1. So, action can be taken as appropriate for the wheel motion.




case GHOST_kEventWindowUpdate:
{
GHOST_WindowHandle win_handle =
GHOST_GetEventWindow(event);
if (!GHOST_ValidWindow(ghost_system, win_handle))
break;

setup_window(win_handle);
update_window();
GHOST_SwapWindowBuffers(win_handle);
}
break;

A GHOST_kEventWindowUpdate event will be received whenever GHOST needs the window to be updated. So we initialize the OpenGL context with the setup_window call, update our content and tell GHOST to swap the drawing buffers with GHOST_SwapWindowBuffers. The GHOST_SwapWindowBuffers function requires the window to be passed. The window we are currently using can retrieved from the event data with the GHOST_GetEventWindow function call.




case GHOST_kEventQuit:
case GHOST_kEventWindowClose:
exit_requested = 1;
handled = 1;
break;

case GHOST_kEventKeyDown:
if (key_data) {
if (key_data->key == GHOST_kKeyQ) {
exit_requested = 1;
handled = 1;
} else if (key_data->key == GHOST_kKeyR) {
xrot = 0;
yrot = 0;
zrot = 0;

invalidate_window();

The GHOST_kEventKeyDown is generated whenever a key on the key board is pressed. From the key_data gathered above we can then determine the key that has been pressed and act accordingly.




} else if (key_data->key == GHOST_kKeyT) {
if (!timer)
timer =
GHOST_InstallTimer(ghost_system, 0, 10,
timer_proc, NULL);
else {
GHOST_RemoveTimer(ghost_system, timer);
timer = NULL;
}


Timers can be added and removed from the system through the GHOST_InstallTimer and GHOST_RemoveTimer function calls. The GHOST_InstallTimer call is passed the system, the delay before starting the timer, the interval between calls to the timer, the function to call when the timer expires, and any data to pass to the timer function.




} else if (key_data->key == GHOST_kKeyF) {
if (GHOST_GetFullScreen(ghost_system)) {
GHOST_EndFullScreen(ghost_system);
full_screen_window = NULL;
} else {
GHOST_DisplaySetting setting;
setting.bpp = 24;
setting.frequency = 85;
setting.xPixels = 640;
setting.yPixels = 480;

full_screen_window =
GHOST_BeginFullScreen(ghost_system,
&setting, 0);
}


GHOST is able to handle full screen as well as regular window mode. It is possible to determine if the system is running in full screen or not through the GHOST_GetFullScreen function call. This will return true if the system is running full screen.
If the system is in full screen mode, we can switch back to normal mode with the GHOST_EndFullScreen function call. This will remove the full screen window and you will be back to the size you were at before entering full screen mode.
To enter into full screen mode you need to create a GHOST_DisplaySetting object which is then passed to the GHOST_BeginFullScreen function call. The last parameter to GHOST_BeginFullScreen is a boolean stating whether to set the system into stereo vision mode. The DisplaySettings is given the parameters needed to setup the system to execute in full screen mode. They are:




  • bpp - Number of bits per pixel.


  • frequency - The refresh rate (in Hertz).


  • xPixels - The number of pixels on a line.


  • yPixels - The number of lines.

The full screen mode produced by GHOST actually produces another window which runs at full screen. If you minimize the full screen window you will be able to see your original window still running in the background.



  int main(int argc, char ** argv) {
ghost_system = GHOST_CreateSystem();
if (!ghost_system) {
printf("Coun't not create the system, dying\n");
exit(-1);
}

The first thing we do is to create the system which is to be used throughout the program to call GHOST functions. If we can't get it then there is no reason to continue. The system needs to be created before any other GHOST functions can be called.




consumer = GHOST_CreateEventConsumer(process_event, NULL);
if (!consumer) {
printf("Failed to create consumer\n");
GHOST_DisposeSystem(ghost_system);
exit(-1);
}
GHOST_AddEventConsumer(ghost_system, consumer);

Once we have the system, we can create an event consumer. The GHOST_CreateEventConsumer takes the function pointer of the function to call when an event happens and any user data to pass to the given function. If we can't get the consumer, again, there is no real reason to continue. Once the consumer is created we attach it to the system as an event consumer.




win = GHOST_CreateWindow(ghost_system, "cube-or", 10, 64, 320, 200,
GHOST_kWindowStateNormal, GHOST_kDrawingContextTypeOpenGL, 0);
if (!win) {
printf("Couldn't not create window\n");
GHOST_DisposeSystem(ghost_system);
GHOST_DisposeEventConsumer(consumer);
exit(-1);
}

We can now continue on and create the application window. This is done through a call to GHOST_CreateWindow. The parameters are as follows:




  • The system.


  • The title.


  • The position of the left edge of the window.


  • The position of the top of the window.


  • The width of the window.


  • The height of the window.


  • The state of the window.


  • The drawing context the window will be in.


  • If the window is in stereo visual mode.

The state of the window is of type GHOST_TWindowState and can be one of: GHOST_kWindowStateNormal, GHOST_kWindowStateMinimized, GHOST_kWindowStateMaximized, and GHOST_kWindowStateFullScreen. All of which should be pretty self explanatory.
The drawing context is of type GHOST_TDrawingContextType which is either: GHOST_kDrawingContextTypeNone, or GHOST_kDrawingContextTypeOpenGL. So you may as well set it to the OpenGL context if you want anything useful.




while(!exit_requested) {
GHOST_ProcessEvents(ghost_system, 0);
GHOST_DispatchEvents(ghost_system);
GHOST_SwapWindowBuffers(win);
}

This is the main loop of the program. It adds events to the event queue in the GHOST_ProcessEvents call, and then dispatches those events to the event consumers in the GHOST_DispatchEvents call. The GHOST_ProcessEvents function receives the system and a boolean which determines if the system blocks until the next event is received. The GHOST_ProcessEvents call will actually return a variable relating the success it had waiting for events (which could be caught to have the program sleep if there are no events to process.)




if (GHOST_GetFullScreen(ghost_system))
GHOST_EndFullScreen(ghost_system);

if (timer)
GHOST_RemoveTimer(ghost_system, timer);

if (GHOST_ValidWindow(ghost_system, win))
GHOST_DisposeWindow(ghost_system, win);

GHOST_DisposeSystem(ghost_system);
GHOST_DisposeEventConsumer(consumer);
return 0;
}

When the main loop is finished we need to clean up after ourselves. This includes moving out of full screen mode, deleting the timer if it exists, deleting the window if it is still valid, cleaning up the system and cleaning up any consumers.
And that's it. Those are the guts of a GHOST application (missing a few unimportant bits). The only step left is to compile the code. There is a ghost-config script under development but it doesn't quite work correctly for libraries at the time of this articles writing, so I'll just give you the compile command I use for the above program.



gcc -I/usr/local/include -I/usr/local/include/GHOST -g \
-O2 -Wall -funsigned-char -L /usr/X11R6/lib -o cube-or \
main.c -L /usr/local/lib/libGHOST.a -lGLU \
-lGL -lXxf86dga -lX11 -lXext -lutil -lm -lpthread \
-ldl -lstdc++ -lz -lGHOST

Of course any paths will need to be adjusted for your local machine. As a note, GHOST will spit out debug information on mouse movements and key presses while it is running, this is normal and to be expected.
That should be enough information for you to get some GHOST development off the ground. There are lots of routines left in the library that were not touched upon by this article and are waiting for someone to discover them.


Contributing to GHOST

If after all of this your interested in contributing to the GHOST project feel free to check out the projects site listed above and get in contact with the developers. There is currently a need for more example / test programs and for people to write API documentation. If you find bugs, have feature requests or patches, you can submit them on the project site given above.

References

The introduction section on the history of GHOST is from the GHOST documentation.
The example given here draws heavily on the monkey example in the current CVS. Without which there would be no example.


About

This article was written by dan sinclair (dj2 on #blendersauce). If you wish to contact me about this article send an email to zero at everburning dot com.

No comments:

Post a Comment