Click here to Skip to main content
Click here to Skip to main content

TetroGL: An OpenGL Game Tutorial in C++ for Win32 Platforms - Part 1

By , 26 Aug 2008
 

Foreword

This series of articles focuses on 2D game development with C++ and OpenGL for Windows platform. The target is to provide a game like the classic block puzzle game by the end of the series. We will not only focus on OpenGL but also talk about the designs that are commonly used in game programming with a full object oriented approach. You should already be familiar with the C++ language in order to get the maximum out of this series. There is a message board at the bottom of the article that you can use if you have questions, remarks or suggestions.

The series is divided into three articles:

  • Part 1: covers the win32 message loop, the window creation and the setting-up of OpenGL. You will also learn how to draw some simple shapes.
  • Part 2 : Covers resources handling and displaying simple animations.
  • Part 3: groups everything together and talk about the game logic.

Contents

Introduction

This part of the article focuses on setting up an OpenGL window in a Windows environment. We will learn how to create a message loop to receive notifications and how to create the main window that will be used for drawing. Then, we will see how to configure OpenGL properly for a 2 dimensions game. Finally, when everything is ready to start, we will learn how to display some basic shapes in the newly created OpenGL window.

Project Settings

We will start by creating a new project and configuring the different options. The tutorial project has been created with Visual Studio 2005 but it can be easily applied for another compiler. Start by creating a new project of type "Win32 Console Application" and giving it an appropriate name, then click Ok. In the creation wizard, select the type "Windows application" (not console) and check the "Empty project" option (we don't really need code that is generated for us).

When this is done, add a new source file Main.cpp to the project (if there are no source files in the project, some options are not accessible). Now open the project options and go to the "Linker" category -> "Input". In the "Addition Dependencies" option, add opengl32.lib. This tells the linker that it has to use the OpenGL library when linking the project.

New project

Next, we will disable UNICODE because we don't need it and it makes things a bit more complicated. Go into "C/C++" -> "Preprocessor" and click on "Preprocessor Definitions". A button will appear on the right, click on it and in the dialog that pops up, uncheck the "Inherit from parent or project defaults". This will disable UNICODE which is inherited from the project default.

New project

Now that the project settings are properly configured, we are ready to look at some code. Let's first examine how a Win32 application receives and processes events (keyboard, mouse, ...).

The Message Loop

The system (Windows) creates a message queue for each application and pushes messages in this queue whenever an event occurs on a window of that specific application. Your application should then retrieve and process those messages in order to react upon them. This is what is called the message loop and it is the heart of all Win32 applications.

A typical message loop looks like this:

    MSG Message;
    Message.message = (~WM_QUIT);
    // Loop until a WM_QUIT message is received
    while (Message.message != WM_QUIT)
    {
        if (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
        {
            // If a message was waiting in the message queue, process it
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
        else
        {
            // Do processing stuff here...
        }
    }

PeekMessage retrieves a message from the queue if any; the PM_REMOVE tells PeekMessage that messages should be removed from the queue. The message will be stored in the first argument and the function returns nonzero if a message was retrieved. The second argument of the function lets you specify a window handle for which the messages have to be retrieved. If NULL is supplied, messages for all windows of the application will be retrieved. The third and fourth parameters let you specify a range for the messages that should be retrieved. If 0 is supplied for both, all messages will be retrieved.
The purpose of the TranslateMessage function is to translate virtual-keys messages (WM_KEYDOWN and WM_KEYUP) into character messages (WM_CHAR). A WM_CHAR message will be generated by a combination of WM_KEYDOWN and WM_KEYUP messages.
Finally the DispatchMessage will redirect the message to the correct window procedure. As we will see later, each window in your application has a specific function (called a window procedure) that processes those messages.
So, this snippet of code tries to extract a message from the queue. If a message was available, it will be dispatched to the correct window procedure. If no message was available, we do some processing specific to the application. Once a WM_QUIT message is retrieved, the loop is exited, which terminates the application.

If we look at the code of this first tutorial, we can see that the message loop is wrapped into a class called CApplication. Let's take a closer look at this class. First the class declaration:

// The application class, which simply wraps the message queue and process
// the command line.
class CApplication
{
public:
  CApplication(HINSTANCE hInstance);
  ~CApplication();

  // Parses the command line to see if the application
  // should be in fullscreen mode.
  void ParseCmdLine(LPSTR lpCmdLine);
  // Creates the main window and starts the message loop.
  void Run();

private:
  HINSTANCE m_hInstance;
  // Specifies if the application has to be started in fullscreen
  // mode. This option is supplied through the command line
  // ("-fullscreen" option).
  bool m_bFullScreen;
};

The ParseCmdLine function is quite straightforward: it simply checks if an argument "-fullscreen" is present in the command line. In that case, the flag m_bFullScreen is set to true.

Let's look at the Run function:

void CApplication::Run()
{
  // Create the main window first
  CMainWindow mainWindow(800,600,m_bFullScreen);

    MSG Message;
    Message.message = ~WM_QUIT;
  DWORD dwNextDeadLine = GetTickCount() + FRAME_TIME;
  DWORD dwSleep = FRAME_TIME;
  bool bUpdate = false;

  // Loop until a WM_QUIT message is received
    while (Message.message != WM_QUIT)
    {
    // Wait until a message comes in or until the timeout expires. The
    // timeout is recalculated so that this function will return at
    // least every FRAME_TIME msec.
    DWORD dwResult = MsgWaitForMultipleObjectsEx(0,NULL,dwSleep,QS_ALLEVENTS,0);
    if (dwResult != WAIT_TIMEOUT)
    {
      // If the function returned with no timeout, it means that at 
      // least one message has been received, so process all of them.
      while (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
      {
        // If a message was waiting in the message queue, process it
        TranslateMessage(&Message);
        DispatchMessage(&Message);
      }

      // If the current time is close (or past) to the 
      // deadline, the application should be processed.
      if (GetTickCount() >= dwNextDeadLine)
        bUpdate = true;
      else
        bUpdate = false;
    }
    else
      // On a timeout, the application should be processed.
      bUpdate = true;

    // Check if the application should be processed
    if (bUpdate)
    {
      DWORD dwCurrentTime = GetTickCount();
      // Update the main window
      mainWindow.Update(dwCurrentTime);
      // Draw the main window
      mainWindow.Draw();

      dwNextDeadLine = dwNextDeadLine + FRAME_TIME;
    }

    // Process the sleep time, which is the difference
    // between the current time and the next deadline.
    dwSleep =  dwNextDeadLine - GetCurrentTime();
    // If the sleep time is larger than the frame time,
    // it probably means that the processing was stopped 
    // (e.g. the window was being moved,...), so recalculate
    // the next deadline.
    if (dwSleep>FRAME_TIME)
    {
      dwSleep = FRAME_TIME;
      dwNextDeadLine = GetCurrentTime() + FRAME_TIME;
    }
  }
}

The first line of the function simply creates the main window. We will see in the next chapter what it does exactly. For now, just imagine that this creates and displays the main window with a specific width and height and in fullscreen or not. As you might see, the loop itself is a bit different than what we saw before. The reason is simple: in general for a 2D game, you don't need to refresh the screen as fast as you can. Refreshing it at a constant rate, is sufficient to display animation and do the processing stuff. In our case, we defined a constant (FRAME_TIME) that specifies the time in msec between two frames.
We could do something simpler: in the first message loop example we saw, we could replace the "// Do processing stuff here..." by a check to see if 30 msec elapsed since the last update:

        else
        {
            // Do processing stuff here...
            if(GetCurrentTime() >= dwLastUpdate+30)
            {
              dwLastUpdate = GetCurrentTime();
              // Update the main window
              mainWindow.Update(dwCurrentTime);
              // Draw the main window
              mainWindow.Draw();
            }
        }

That will work fine except for the fact that it is busy waiting: if no messages are received, we will loop continuously and eat all available CPU time. This is not really nice because the CPU is used for doing nothing.
A best approach would be to wait until a message arrives or until we reached the next refresh deadline. That's what the MsgWaitForMultipleObjectsEx function does. In brief, we can specify multiple objects on which we would like to wait, but we are only interested in messages (so, that's why we specify 0 objects in the first argument and a NULL for the second argument). This function will wait without consuming CPU cycles until either the timeout period expires (specified in the 3rd argument) or when a message has been received. You can specify a filter for messages to be received in the 4th parameter, but we are interested in all messages. When the function times out, it returns WM_TIMEOUT, which is used in the code to detect when it is time to refresh the screen and update the game logic. If the function didn't time out, it means that one or more messages are waiting in the queue, so we extract all of them using PeekMessage (the function returns FALSE when no messages are in the queue anymore). Whe then determine if the application should be processed or not. At the end of the function, we recalculate the sleep time depending on the next deadline. If this sleep time is bigger then the frame time, it means that the current time was bigger than the next deadline (negative overflow). This typically happens when the window is moved or resized: during this time, the application is not processed anymore. In that case, we simply recalculate a new deadline and sleep time based on the current time.

Great, so now we have a message loop to dispatch the messages to the correct window. But there's something missing: the window itself. So let's look at how this window is created and how the messages sent to it are processed.

The Main Window

Creating the Window

As we saw before, we only had to create an instance of the CMainWindow class in the Run() method of our application class to create the main window. So let's take a look at the constructor, that's where all the stuff is handled.

CMainWindow::CMainWindow(int iWidth, int iHeight, bool bFullScreen)
  :  m_hWindow(NULL), m_hDeviceContext(NULL), m_hGLContext(NULL),
     m_bFullScreen(bFullScreen)
{
  RegisterWindowClass();

  RECT WindowRect;
  WindowRect.top = WindowRect.left = 0;
  WindowRect.right = iWidth;
  WindowRect.bottom = iHeight;

  // Window Extended Style
  DWORD dwExStyle = 0;
  // Windows Style
  DWORD dwStyle = 0;

  if (m_bFullScreen)
  {
    DEVMODE dmScreenSettings;
    memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
    dmScreenSettings.dmSize = sizeof(dmScreenSettings);
    dmScreenSettings.dmPelsWidth  = iWidth;
    dmScreenSettings.dmPelsHeight = iHeight;
    dmScreenSettings.dmBitsPerPel = 32;
    dmScreenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

    // Change the display settings to fullscreen. On error, throw
    // an exception.
    if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)
        != DISP_CHANGE_SUCCESSFUL)
    {
      throw CException("Unable to switch to fullscreen mode");
    }

    dwExStyle = WS_EX_APPWINDOW;
    dwStyle = WS_POPUP;
    // In fullscreen mode, we hide the cursor.
    ShowCursor(FALSE);
  }
  else
  {
    dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    dwStyle = WS_OVERLAPPEDWINDOW;
  }

  // Adjust the window to the true requested size
  AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
  // Now create the main window
  m_hWindow = CreateWindowEx(dwExStyle,TEXT(WINDOW_CLASSNAME),
               TEXT("Tutorial1"),
               WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle,
               0, 0, WindowRect.right-WindowRect.left,
               WindowRect.bottom-WindowRect.top,
               NULL, NULL,
               GetModuleHandle(NULL),
               this);
  if (m_hWindow==NULL)
    throw CException("Cannot create the main window");

  CreateContext();
  InitGL();
  ShowWindow(m_hWindow,SW_SHOW);
  // Call OnSize manually because in fullscreen mode it will be
  // called only when the window is created (which is too early
  // because OpenGL is not initialized yet).
  OnSize(iWidth,iHeight);
}

It looks like a lot of code but it is not that complicated. The first thing we do is call RegisterWindowClass which will, as its name states, registers the window class for our application. So what is a window class? Basically, it is a template that is used to define a window: you can specify an icon, a background brush, a cursor and other things. Every window is an instance of such a class. Let's take a look at the implementation of this function:

void CMainWindow::RegisterWindowClass()
{
    WNDCLASS WindowClass;
    WindowClass.style         = 0;
    WindowClass.lpfnWndProc   = &CMainWindow::OnEvent;
    WindowClass.cbClsExtra    = 0;
    WindowClass.cbWndExtra    = 0;
    WindowClass.hInstance     = GetModuleHandle(NULL);
    WindowClass.hIcon         = NULL;
    WindowClass.hCursor       = 0;
    WindowClass.hbrBackground = 0;
    WindowClass.lpszMenuName  = NULL;
    WindowClass.lpszClassName = WINDOW_CLASSNAME;

    RegisterClass(&WindowClass);
}

What it does is register a new class instance (which is called Tutorial1) and the only thing we specify is the window procedure that will be called when messages are retrieved for that window. This is the OnEvent function of the class. If you look closely at the function declaration, you will notice that it is a static function. The reason for that is very simple: non-static member functions don't have the same prototype as global functions even if they have the same argument list. It is because an implicit parameter is passed to the function: the this parameter which identifies the instance of the class on which the function is called. Static member functions do not follow the same rule, because they don't belong to a specific instance (they are shared among all instances of the class). The WNDCLASS structure accepts only global or static member functions for the lpfnWndProc parameter. We will see later the consequences of that.

Now, back to the CMainWindow constructor. The next thing we do there is check if the window should be in fullscreen. If that is the case, we switch to fullscreen mode (by calling ChangeDisplaySettings). If this function call fails, we throw an exception. We will talk more in detail about exceptions and exception handling in a following chapter.

We will now create the main window but first, we need to adjust the rectangle size because the window caption and borders are eating up a bit of the size. To correct that, we simply call AdjustWindowRectEx. This function doesn't have any effect if we are in fullscreen mode. We finally call CreateWindowEx which will create the window with the required style. The second parameter of the function specifies the window class to use (which will of course be the window class we registered earlier). In the last parameter of the function, we pass the this pointer (the pointer to this CMainWindow instance). We will see later why we do so. If the window creation fails, we also throw an exception. The CreateContext and InitGL functions will initialize OpenGL properly, but we will see that in a following chapter.

The Window Procedure

We just created a new window by calling CreateWindowEx and we specified that the window should use the window class we registered earlier. This window class uses the OnEvent function as a window procedure. Let's take a look at this function:

LRESULT CMainWindow::OnEvent(HWND Handle, UINT Message, WPARAM wParam, LPARAM lParam)
{
  if (Message == WM_NCCREATE)
  {
        // Get the creation parameters.
    CREATESTRUCT* pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);

    // Set as the "user data" parameter of the window
    SetWindowLongPtr(Handle, GWLP_USERDATA,
          reinterpret_cast<long>(pCreateStruct->lpCreateParams));
  }

  // Get the CMainWindow instance corresponding to the window handle
  CMainWindow* pWindow = reinterpret_cast<CMainWindow*>
    (GetWindowLongPtr(Handle, GWLP_USERDATA));
  if (pWindow)
    pWindow->ProcessEvent(Message,wParam,lParam);

  return DefWindowProc(Handle, Message, wParam, lParam);
}

As you remember, this function is a static function. The function will be called when a message is received and dispatched to our main window. It accepts four parameters:

  • Handle: The handle of the window to which the message is sent to
  • Message: The message Id
  • wParam: Optional message parameter
  • lParam: Optional message parameter

Depending on the type of message, some additional information will be stored in the wParam, lParam or both (e.g. a mouse move message contains the mouse coordinates, a key down event contains the key code...).

As this function is static, we don't have access to other non-static class member, which is of course not very useful in our situation. But, don't panic, there's an easy solution for that, and it's the reason why we passed the this pointer in the last argument of CreateWindowEx. One of the first message that will be sent to your window procedure is the WM_NCCREATE message. When this message is received, the lParam argument contains a pointer to a CREATESTRUCT structure, which contains information about the window creation, which are in fact the parameters that were passed in the CreateWindowEx call. The lpCreateParams field contains the additional data, which is in our case the pointer to the CMainWindow instance. Unfortunately, this additional data is not sent with every message, so we need a way to store this pointer for later use. That's what we are doing by calling SetWindowLongPtr: this function lets you save some user data (GWLP_USERDATA) for a specific window (identified by its handle). In this case, we save the pointer to the class instance. When other messages are received, we will simply retrieve this pointer by calling (GetWindowLongPtr), and then call a non-static function on the pointer: ProcessEvent, which is in charge of processing the message. The WM_NCCREATE message is not the first one that is sent, that's why we need to check if the call to GetWindowLongPtr did return something else than NULL.

Let's look at the ProcessEvent function:

void CMainWindow::ProcessEvent(UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch (Message)
    {
    // Quit when we close the main window
    case WM_CLOSE :
      PostQuitMessage(0);
      break;
    case WM_SIZE:
      OnSize(LOWORD(lParam),HIWORD(lParam));
      break;
    case WM_KEYDOWN :
      break;
    case WM_KEYUP :
      break;
    }
}

Not too much code here, but this function will be filled in the next tutorials as we need to handle some events. The WM_CLOSE message is sent when the user clicks on the red cross of the window. At this time, we need to send a WM_QUIT message in order to exit the main loop and quit the program. A WM_SIZE message is sent whenever the window is resized, with the new size contained in the lParam (LOWORD and HIWORD are two macros that extract the first 2 bytes and the last 2 bytes from the parameter). When such message is received, we delegate the resizing handling to our OnSize member function. Some other messages will be handled later: WM_KEYDOWN when a key is pressed, WM_KEYUP when a key is released, ...

Up to now, the only thing our program does is create an empty window and display it on the screen (in fullscreen mode or not).

Exception Handling

Error management is an important point for all programs, and this is also true for games: you don't want your game to crash because a resource is missing. My preferred way to handle errors for games is to use exceptions. It is much more convenient than returning error codes from functions (and routing them where I want the error to be handled). The main reason is that I can delegate the error handling in one single place: in my main function, where all my exceptions will be caught. Let's first take a look at our exception class, which is quite basic:

class CException : public std::exception
{
public:
  const char* what() const  { return m_strMessage.c_str(); }

  CException(const std::string& strMessage="") : m_strMessage(strMessage)  { }
  virtual ~CException()  { }

  std::string m_strMessage;
};

So, nothing fancy here: our exception class inherits from std::exception (which is not mandatory but is considered good practice). We simply override the what() function which returns the error message. I kept the scenario quite simple here, but for a bigger game, you might want to specialize this exception into specific ones: out of memory, resource missing, file loading failed, ... This could prove handy because sometimes it is useful to filter the exceptions. A typical example is when the user of your game wants to load a file (containing a previous saved game) which is corrupted. In that case, the load file function will throw an exception but you don't want to exit the program because of that. Displaying a message to the user telling him that the file is corrupted is what you would like to do. You can then easily catch all 'file corrupted' exceptions at an early stage and let all the others be routed to your main exception handling function. After all, if some resources are missing when loading the file, this is probably a critical error and you might want to exit the program.

So, how does my main function look like and how do I handle the exceptions ?

int WINAPI WinMain(HINSTANCE Instance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT)
{
  try
  {
    // Create the application class,
    // parse the command line and
    // start the app.
    CApplication theApp(Instance);
    theApp.ParseCmdLine(lpCmdLine);
    theApp.Run();
  }
  catch(CException& e)
  {
    MessageBox(NULL,e.what(),"Error",MB_OK|MB_ICONEXCLAMATION);
  }

  return 0;
}

Pretty easy to understand, isn't it ? We already saw what the CApplication class is doing and for the exception handling, we simply wrap everything inside a little try/catch block. When an exception is thrown somewhere in the program, we simply display an error message with the text of the exception and we nicely exit the program. Note that as theApp is local to the function, it will be destroyed at the end of the function and its destructor will be called.

Setting up OpenGL

If you remember, in our CMainWindow constructor, we were calling two functions: CreateContext and InitGL. I didn't explain yet what those functions do, so let's correct that now. CreateContext will initialize the rendering context so that OpenGL primitives can be drawn on the window:

void CMainWindow::CreateContext()
{
  // Describes the pixel format of the drawing surface
  PIXELFORMATDESCRIPTOR pfd;
  memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
  pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
  pfd.nVersion = 1; // Version Number
  pfd.dwFlags = PFD_DRAW_TO_WINDOW |  // Draws to a window
                PFD_SUPPORT_OPENGL |  // The format must support OpenGL
                PFD_DOUBLEBUFFER;     // Support for double buffering
  pfd.iPixelType = PFD_TYPE_RGBA;     // Uses an RGBA pixel format
  pfd.cColorBits = 32;                // 32 bits colors

  if (!(m_hDeviceContext=GetDC(m_hWindow)))
    throw CException("Unable to create rendering context");

  int PixelFormat;
  // Do Windows find a matching pixel format ?
  if (!(PixelFormat=ChoosePixelFormat(m_hDeviceContext,&pfd)))
    throw CException("Unable to create rendering context");
  // Set the new pixel format
  if(!SetPixelFormat(m_hDeviceContext,PixelFormat,&pfd))
    throw CException("Unable to create rendering context");
  // Create the OpenGL rendering context
  if (!(m_hGLContext=wglCreateContext(m_hDeviceContext)))
    throw CException("Unable to create rendering context");
  // Activate the rendering context
  if(!wglMakeCurrent(m_hDeviceContext,m_hGLContext))
    throw CException("Unable to create rendering context");
}

The first part of the function fills a PIXELFORMATDESCRIPTOR with the correct information: the buffer is used to draw to a window, must support OpenGL and uses double buffering (to avoid flickering). We then call ChoosePixelFormat to see if this pixel format is supported. The function returns a pixel format index (or 0 if no matching pixel format was found). Once we have the index of the pixel format, we set the new format by calling SetPixelFormat. We then create the OpenGL rendering context by calling wglCreateContext. Finally, by calling wglMakeCurrent, we specify that all subsequent OpenGL calls made by the thread are drawn on this device context. You can also see that if an error is encountered while creating the context, an exception is thrown and will be handled in our main function.

The InitGL function is rather simple:

void CMainWindow::InitGL()
{
  // Enable 2D texturing
  glEnable(GL_TEXTURE_2D);
  // Choose a smooth shading model
    glShadeModel(GL_SMOOTH);
  // Set the clear color to black
  glClearColor(0.0, 0.0, 0.0, 0.0);

  // Enable the alpha test. This is needed 
  // to be able to have images with transparent 
  // parts.
  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_GREATER, 0.0f);
}

We first enable the 2D texturing. Without this call, we won't be able to apply textures to shapes on the screen. Those textures will be loaded from file and used to display the different game elements. We then choose a smooth shading model. This is not really important in our case, but it simply tells OpenGL if the points of a primitive (a basic shape, like a triangle or a rectangle) have different colors, they will be interpolated. We'll see later what it does on a concrete example.We then specify a clear color. This color is used to clear the color buffer before drawing anything to it. Finally, we enable the alpha testing. This is needed if we want to render some parts of a texture transparent. Suppose for example that you want to draw a ship on the screen and that this ship is loaded from a file. The ship doesn't have a rectangular shape so, you would like to make the texture around the ship transparent so that you don't have a white rectangle in which you have your ship. This is done by using an alpha channel that specifies the opacity of a pixel (this will be covered more in details in the second article). Once the alpha testing has been enabled, we need also to select which function will be used to discard pixels depending on their alpha channel. This is done through the glAlphaFunc: we specify that all pixels with an alpha channel greater (GL_GREATER) than the specified threshold (0) will be discarded (not drawn). Other alpha functions also exist (GL_LESS, GL_EQUAL, ...).

Let's now take a look at the OnSize function. If you remember, this function is called whenever the window is resized (and at least once, at the window creation):

void CMainWindow::OnSize(GLsizei width, GLsizei height)
{
  // Sets the size of the OpenGL viewport
    glViewport(0,0,width,height);

  // Select the projection stack and apply
  // an orthographic projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0,width,height,0.0,-1.0,1.0);
  glMatrixMode(GL_MODELVIEW);
}

It receives as parameter the new size of the window. The first thing we do here is call glViewport. This function specifies which section of the window will be used by OpenGL for drawing. You can for example limit the drawing to a portion of the full window. In our case, we will use the full window as the viewport. By default, OpenGL will use the full window size so this call is not necessary (only for educational purposes).

Now we'll glMatrixMode. In order to understand what it does, let me first explain that OpenGL uses three matrix stacks at different stages of the process. These stacks are:

  • GL_MODELVIEW: This matrix stack affects the objects in your scene. In case of our tutorial, these objects will simply be textured rectangles. By manipulating this matrix stack, you will be able to translate, rotate and scale the objects in your scene.
  • GL_PROJECTION: This matrix stack affects how the objects in your scene will be projected on the viewport. By manipulating this stack, you can specify which kind of projection should be applied to your objects.
  • GL_TEXTURE: This matrix stack defines how the textures will be manipulated before being applied to objects. We won't manipulate this stack in this tutorial.

OpenGL will always work with the matrix that is currently on top of each stack, but using a stack might be useful because you can then push the current matrix down the stack to be used later. We will see at the end of this tutorial a more concrete example of that.

After this little explanation, we are back to our code: what glMatrixMode does is that it simply tells OpenGL which matrix stack will be affected by the next operations. In your code, we select the projection stack. We then load the identity matrix in the stack (which simply resets the current matrix to the identity matrix) and then we specify that we would like an orthographic projection of the objects on the viewport. We finally switch back to the default matrix, which is the model view matrix.

You might wonder what is this orthographic projection? Let's take a deeper look at what it does. You can have two different projections in OpenGL: the perspective or the orthographic projection. Instead of going into a detailed explanation, I'll put two pictures showing the two projections.

Orthographic projection

Orthographic projection.

Perspective projection

Perspective projection.

As you can see, a perspective projection is the way to go if you develop a 3D game: it will be similar as what your eyes can see as objects that are far from the camera will look small. An orthographic projection on the other hand won't distort objects: a cube at a distance will look the same size as a cube just in front of the camera (given they are the same size). For a 2D game, I prefer to use an orthographic projection because then I don't have to take the z position into account: I can give whatever value and the object won't be smaller or bigger depending of this value.

The arguments you pass to glOrtho are the coordinates of the viewing volume (left, right, bottom, top, nearVal and farVal). The values you choose here will in fact define the 'units' you will be working with: OpenGL doesn't define any units on its own. For example, I've chosen the window width as the width of my viewing volume. It means that if I move an object 1 unit to the left, it will move 1 pixel. You will also often see values from 0.0 to 1.0 for left/bottom and right/top. In that case, one unit is the width of window in the horizontal direction and is the height of the window in the vertical direction. In 2D games, I prefer to use the first option because if I want to draw two textures next to each other, I know exactly how much I have to move my second texture: it is the width of the first texture (e.g. if my textures are 24 pixels width, my second texture will be moved 24 units to the right). On the other hand, if I want to position something in the middle of my window, I have to take into consideration the width of the window. For the other option, 0.5 units is the middle of the window. That's just a matter of choice but as I am familiar with MFC and GDI, I tend to use the first option to have the same feeling. You might also have noticed another point: I gave a value of height for the bottom and of 0 for the top. It means that my top and bottom are inverted. Here also, it is just a matter of choice: the Y axis in OpenGL goes from the bottom to the top, which is the opposite as what I'm used to do (window coordinates start at the top of the window to the bottom of the window).

Drawing Simple Shapes

Now that everything is set-up correctly, we will finally be able to draw some basic shapes on our window. We are using double buffering to avoid flickering, this means that everything will be written to an off-screen buffer and once the image is composed, the buffers will be swapped, bringing the off-screen buffer to the screen and vice-versa. This avoids having to draw directly on the buffer that is displayed on the screen. Let's look at our CMainWindow::Draw() function where the drawing code should be:

void CMainWindow::Draw()
{
  // Clear the buffer
  glClear(GL_COLOR_BUFFER_BIT);

  SwapBuffers(m_hDeviceContext);
}

The first line of code simply clears the buffer using the clear color that was specified earlier in our InitGL function (black). At the end of the function, we swap the buffers by calling SwapBuffers. Our drawing code will be placed between these calls.

OpenGL allows you to draw some simple shapes, called primitives which can be points, lines and polygons (most of the times, triangles and rectangles). These primitives are described by their vertices, the coordinates of the points themselves, the endpoints of the line segments or the corners of the polygons. For 2D games, we will probably limit ourselves to rectangles: when textured, they allow you to display bitmaps which is almost all we need for a 2D game. For more complex games (like 3D games), complex shapes can be created by assembling triangles together to form a mesh. Let's draw a rectangle and a triangle on the screen: we will put this code between the two function calls in our drawing function.

  glBegin(GL_QUADS);
    glVertex3i(50,200,0);
    glVertex3i(250,200,0);
    glVertex3i(250,350,0);
    glVertex3i(50,350,0);
  glEnd();

  glBegin(GL_TRIANGLES);
    glVertex3i(400,350,0);
    glVertex3i(500,200,0);
    glVertex3i(600,350,0);
  glEnd();

Specifying vertices (calls to glVertex3i) should always be wrapped inside a glBegin/glEnd pair. The argument supplied to glBegin defines the type of shape we are drawing. You can draw multiple shapes within the same glBegin/glEnd pair, you simply have to provide enough vertices: e.g. if you want to draw two rectangles, you have to provide 8 vertices. The arguments you provide to glVertex3i are the coordinates of the vertex, which depend on how the projection was defined (remember what we did in the CMainWindow::OnSize() method). I've chosen to stick to window coordinates for this example. The '3i' at the end of the function specifies the number and type of arguments to the function. Several versions of this function exist: from two to four arguments which can be integers, floats, doubles, signed, unsigned, arrays, ... Simply select the one that is the most suited to your needs.

You can also specify a color for each of the vertices of your shape, so let's try some nice things here:

  glBegin(GL_QUADS);
    glColor3f(1.0,0.0,0.0);   glVertex3i(50,200,0);
    glColor3f(0.0,1.0,0.0);   glVertex3i(250,200,0);
    glColor3f(0.0,0.0,1.0);   glVertex3i(250,350,0);
    glColor3f(1.0,1.0,1.0);   glVertex3i(50,350,0);
  glEnd();

  glBegin(GL_TRIANGLES);
    glColor3f(1.0,0.0,0.0);  glVertex3i(400,350,0);
    glColor3f(0.0,1.0,0.0);  glVertex3i(500,200,0);
    glColor3f(0.0,0.0,1.0);  glVertex3i(600,350,0);
  glEnd();

Specifying the current color is done by calling glColor3f, here also, several versions of the function exist. For the floating point version, the full intensity corresponds to 1.0, and no intensity corresponds to 0.0. If you run the code, you will see that the colors of each vertex blend nicely together (it is the image that is on top of this article). That is because we've chosen the GL_SMOOTH shading model when calling glShadeModel in our CMainWindow::InitGL() function. If you change it into GL_FLAT, you'll see that the shapes have only one color, which is the last supplied one.

Modeling Transformations

I will finish this tutorial by showing you what can be done by manipulating the model view matrix stack. This won't be used in next tutorials (or even in the final game) but it is nice to understand these concepts. That's the reason why I'll be quite brief on this subject.

I already talked a bit about the model view matrix stack and said that you can apply transformations to this matrix which will affect the objects in your scene. I also explained that using a stack instead of a single matrix can be useful when you want to save the current matrix for later use. By calling glPushMatrix, you push the top matrix down the current selected stack (which is the model view stack by default) and create a duplicate of this matrix on the top of the stack. Once you have manipulated the model view matrix to affect certain objects in your scene, you can pop back to the previous pushed matrix by calling glPopMatrix. This is particularly useful when you have to draw elements that have children elements: the position and rotation of the children depends on the position and rotation of the parent (e.g. a finger on a robot hand depends on the position of the hand, which in turn depends on the position of the robot's arm). In that case you apply the transformation for the parent element, push the matrix down the stack, apply the transformations for the first child and draw it, then pop the first matrix to reset to the position and rotation of the parent element. You can then draw the second child by using the same method. Of course, those child elements can themselves have child elements in which case you apply the same technique.

Applying transformations to the objects in your scene is done by loading a specific matrix in the model view matrix stack. You can compound this matrix by hand but I guess that's something you would like to avoid. That's why OpenGL provides three routines that can be used for modeling transformations: glTranslate,glRotate and glScale. One thing you have to take into consideration is that each call to such functions is equivalent to creating the corresponding translation, rotation or scaling matrix and then multiply the current model view matrix with this matrix (and storing the result in the model view matrix). It means that you can 'chain' these calls to produce the transformation you like. You might also know (or remember from your math lessons) that matrix multiplication is not commutative. It means that the order in which you call your functions is important. In fact, the last transformation command called in your program is the first which is applied. You can look at it by imagining that you have to call the transformations in the reverse order in which you would like them to be applied. Suppose that you want to position an object at location (100,100) (we don't take z into account here) and have it rotated 180� around the z axis (but still centered at the same location), then you would need to apply the translation first and then rotate the object. If you do the opposite, the translation would be applied first and then the rotation would be applied, which means your object will be moved at location (100,100) and then rotated 180� around (0,0). Which means it will end up in position (-100,-100).

I don't want to go into too much detail here because matrix manipulation and modeling transformations are worth a full article on their own. I simply wanted to show you that manipulating the model view matrix could prove quite powerful, for example if you want to add some simple special effects (like rotation and scaling).

Conclusion

In this article, I've provided a basic framework that can be reused for writing 2D games. It creates the main window and set-up OpenGL accordingly. We will see in the next article how to texture the shapes with images that are loaded from files and how to efficiently manage those resources.

References

Acknowledgements

I would like to thanks Andrew Vos for the nice projection images.
Thanks to the reviewers: Vunic, Andrew.
Thanks also to Jeremy Falcon, El Corazon and Nemanja Trifunovic for their advice and help.

History

  • 23rd June, 2008:
    • Initial version

  • 23rd August, 2008:
    • Added link to the second article in the Foreword section.
    • The Run method of the CApplication class has been adapted.
    • The OnEvent method of the CMainWindow class has been adapted.
    • Added support for blending in the InitGL method of the CMainWindow class.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Cedric Moonen
Engineer
Belgium Belgium
Member
I am a 29 years old guy and I live with my girlfriend in Hoegaarden, little city from Belgium well known for its white beer Smile | :) .
I studied as an industrial engineer in electronics but I oriented myself more towards software development when I started to work.
Currently I am working in a research centre in mechatronica. I mainly develop in C++ but I also do a bit of Java.
When I have so spare time, I like to read (mainly fantasy) and play electric guitar.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberSachin Gorade29 Jul '12 - 8:59 
Awesome article, easy to understand
GeneralMy vote of 3memberzuizuimei26 Dec '10 - 19:18 
I got a lot
GeneralRe: My vote of 3memberCedric Moonen12 Jan '11 - 4:38 
Confused | :confused: You got a lot of what ?
Cédric Moonen
Software developer

Charting control [v3.0]
OpenGL game tutorial in C++

Generalblack window after ctrl + alt + delmemberKrasnapolsky5 Sep '10 - 13:21 
Hi,
 
i really liked this tutorial. helped me to finally get started with opengl. I just have one little problem i can't get rid of:
 
I'm using win7 (64-bit) and whenever i run the game and ctrl + alt + del i get that screen where i can select the task manager and after i did and return to the desktop, the window interior (of my game window) is black and it only redraws one single frame whenever i activate another window and then activate the game window again (although the draw function is still called every few milliseconds). did something mess up with the glcontext?? I read somewhere that the glcontext cannot be lost by definition... so what happened? and how do i manage to get it to redraw again?
 
thx,
 
K
GeneralRe: black window after ctrl + alt + delmemberCedric Moonen5 Sep '10 - 20:27 
I'm sorry but I never encountered such a problem. Did you try with the code I supplied (without changing anything) ?
Cédric Moonen
Software developer

Charting control [v3.0]
OpenGL game tutorial in C++

GeneralRe: black window after ctrl + alt + del [modified]memberKrasnapolsky5 Sep '10 - 22:29 
yeah i did. i just made a few adjustments (added a const to the operator< in the HighScoreData, etc) to make it compile.
maybe its just a problem with my graphics card/driver (intel GMA -.-). I'll try on another system and report back whether the problem still persists.
 
thx for the quick answer.
 
EDIT: okay, i just checked it and on two other systems (one with a nvidia one with a radeon) seem to work fine.
 
seems to be the gma then...

modified on Monday, September 6, 2010 4:35 AM

Generalhimemberbensalah31 May '10 - 7:10 
hi,
i need to implement "cut,copy,past,undo,redo":for shapes (java2d) not for Swing Components
thanks
GeneralTiming IssuesmemberWiiseguy9 Aug '08 - 11:23 
I tried to add some animation to the scene, by moving one of the triangles to the right. I did this by incrementing a 'x' value in the Update() method of the MainWindow.
 
Anyway, what I noticed was that there were some irregularities in the movement of the triangle when I moved the mouse around (and thus adding messages to the message queue) the triangle sped up.
 
The CApplication::Run() method seems fine to me, but the fact of the matter is that Update() is called more often than it should be. Fraps[^] also showed an increase in frame rate, so it's not some kind of optical illusion Wink | ;)
 
The main reason I'm interested in this is that I've started making an OpenGL Game Engine and implemented frame-rate independent movement, so stuff moves at the same speed no matter the frame-rate. Objects will often be obviously mis-aligned with frame-rates below 60, when using timed movement. So this set me out for a quest of finding other ways to have synchronized movement. You seem like you're thinking in the right way, so I'm really curious if this will be addressed in Part 2!
 
Really good tutorial so far, though!
GeneralRe: Timing IssuesmvpCedric Moonen11 Aug '08 - 9:17 
Hi !
 
When I was writing the code for the second article I realized that there was some problems with the main loop. The problem you describe is quite easy to fix: in fact, if a message is received, the bUpdate flag is never set to false, which means that the frame will be redrawn each time a message is retrieved. Another small problem that I fixed is that only one message will be retrieve from the queue at a time even if several are pending.
 
I post here the corrected code (it was adapted for an image every 75 msec):
    while (Message.message != WM_QUIT)
    {
		// Wait until a message comes in or until the timeout expires. The
		// timeout is recalculated so that this function will return at
		// least every 30 msec.
		DWORD dwResult = MsgWaitForMultipleObjectsEx(0,NULL,dwSleep,QS_ALLEVENTS,0);
		if (dwResult != WAIT_TIMEOUT)
		{
			// If the function returned with no timeout, it means that a 
			// message has been received, so process it.
			while (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
			{
				// If a message was waiting in the message queue, process it
				TranslateMessage(&Message);
				DispatchMessage(&Message);
			}
 
			// If the current time is close (or past) to the 
			// deadline, the application should be processed.
			if (GetTickCount() >= dwNextDeadLine)
				bUpdate = true;
			else
				bUpdate = false;
		}
		else
			// On a timeout, the application should be processed.
			bUpdate = true;
 
		// Check if the application should be processed
		if (bUpdate)
		{
			DWORD dwCurrentTime = GetTickCount();
			// Update the main window
			mainWindow.Update(dwCurrentTime);
			// Draw the main window
			mainWindow.Draw();
 
			dwNextDeadLine = dwNextDeadLine + 75;
		}
		dwSleep =  dwNextDeadLine - GetCurrentTime();
		if (dwSleep<0)
			dwSleep = 75;
	}
 
I was planning to correct this article once I finish the second one.
 

Wiiseguy wrote:
The main reason I'm interested in this is that I've started making an OpenGL Game Engine and implemented frame-rate independent movement, so stuff moves at the same speed no matter the frame-rate. Objects will often be obviously mis-aligned with frame-rates below 60, when using timed movement. So this set me out for a quest of finding other ways to have synchronized movement.

 
I don't fully understand what you are trying to do because if you want an frame-rate independant movement, the thing to do would be to have an easy message loop (as described in the article) and you pass the current time to the Draw function. Then each animation should know when it is time to switch to the next frame of the animation.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Timing IssuesmemberWiiseguy15 Aug '08 - 9:43 
Hello again!
 
I just tried out your new message loop and I've come across a few issues:
 
- When the window is moved or resized, Update/Draw are apparently no longer called, as the animation (moving triangle) just stops.
- You can blow some life into the animation again by moving the mouse around. Though it's a bit unpredictable: While moving the mouse slowly, the triangle moves. However, as soon as you stop, the triangle stops as well. After moving the mouse again a few times, the message loops continues as usual, making the triangle move as it did before.
 
By debugging it a bit, I found that the value of dwSleep is spectacularly high (a ten digit number) whenever the the observed stop occurs. The cause of this was GetCurrentTime() yielded a higher number than the value of dwNextDeadline.
 
I guess that's why you added this check:
 
if (dwSleep<0)
    dwSleep = 75;
 
Which would work if dwSleep was signed, but since DWORD is just an unsigned long, this won't work. Changing dwSleep's type to long actually fixed the problem!
 
The only (minor) issue is that there's still a slight, brief increase of fps after moving/resizing and moving the mouse subsequently. I can't really explain that one, though.
 

Cedric Moonen wrote:
I don't fully understand what you are trying to do because if you want an frame-rate independent movement, the thing to do would be to have an easy message loop (as described in the article) and you pass the current time to the Draw function. Then each animation should know when it is time to switch to the next frame of the animation.

 
I actually made a small Space Shooter game with it, objects were able to subscribe to a Timer (which measured how many GameTime units had passed) and triggered an OnAlarm event on the object itself. The problem with frame-rate independency is that you have a TimeFactor that can can change immensely during game play (unless you use vertical sync or the message loop wait-system you're using).
 
This was most noticeable with a group of enemies that moved in a pattern (à la Gradius) after a certain amount of time a string of enemies would change direction (kind of a snake effect). After a few changes of direction, some enemies would be misaligned from the others. The cause of this was that some timers would be overdue and thus not return in time. The solution was to also return the amount of time that the Timer was overdue. After implementing this, I found this not to be ideal.
 
Anyway, I better keep this short because it's kind of off-topic.
 
I wish you luck with the rest of your tutorials, I'm looking forward to learning more!
GeneralRe: Timing IssuesmvpCedric Moonen16 Aug '08 - 1:03 
Hi,
 
Yes, I also realized that problem and what I did was this:
		// If the sleep time is larger than the frame time,
		// it probably means that the processing was stopped 
		// (e.g. the window was being moved,...), so recalculate
		// the next deadline.
		if (dwSleep>FRAME_TIME)
		{
			dwSleep = FRAME_TIME;
			dwNextDeadLine = GetCurrentTime() + FRAME_TIME;
		}
 
instead of checking if dwSleep is <0. It works rather well.
 

Wiiseguy wrote:
When the window is moved or resized, Update/Draw are apparently no longer called, as the animation (moving triangle) just stops.

 
I think it has to do with how Windowshandle those messages: when you are moving the window or resizing it, your application is not processed anymore.
 

Wiiseguy wrote:
I wish you luck with the rest of your tutorials, I'm looking forward to learning more!

 
Part 2 is now available here[^]. Enjoy Smile | :)
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [Part 2 online]

Generalwait for Part 2!!!memberphinecos7 Aug '08 - 21:08 
I have been waiting so long for your next paper,please write it,oh,so greate series!!
GeneralRe: wait for Part 2!!! [modified]mvpCedric Moonen7 Aug '08 - 21:53 
Yeah I know it's a bit long but I've been very busy this month (and I went in holidays). Anyway, it's almost finished and I hope I can finish it for next week (I'm away this week-end).
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++
modified on Friday, August 8, 2008 4:07 AM

GeneralRe: wait for Part 2!!!mvpCedric Moonen15 Aug '08 - 21:40 
It is finally available here[^] Smile | :)
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralExecutable not closing properly - still in memorymemberZatrael2 Aug '08 - 23:59 
First, thanks for the great tutorial, I'm anxiously awaiting Part 2.
 
I've encountered a minor issue with the tutorial. Whenever I close the running executable it hangs around in memory (according to Task Manager). I'm using Visual Studio 2005 on Vista x64 with a Win32 build config (default). This is one of my first experiences in Win32/C++ programming, so I'm not entirely sure what the problem is, but I'm assuming that not all of the objects are destroyed properly when the WM_CLOSE message is handled in CMainWindow::ProcessEvent.
 
If anybody can confirm this happening, or point to a solution, that would be most appreciated.
 
Cheers
GeneralRe: Executable not closing properly - still in memorymvpCedric Moonen3 Aug '08 - 20:43 
Zatrael wrote:
First, thanks for the great tutorial, I'm anxiously awaiting Part 2.

 
Thank you Smile | :) . I was very busy recently so that's why the part 2 is still not available yet, but it is almost finished now. I hope to make it public in two or three weeks.
 

Zatrael wrote:
I've encountered a minor issue with the tutorial. Whenever I close the running executable it hangs around in memory (according to Task Manager).

 
Do you encounter this problem with my sources or did you start to write your own ? This typically happens when you don't handle the WM_CLOSE message correctly: this message is sent when the user clicks on the cross to close the window, and it only closes the window, not exit the main message loop. In order to quit the message loop, you should send a WM_QUIT message, which can be done by calling PostQuitMessage (see how I handle the WM_CLOSE message in my sources).
 
Hope that helps.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Executable not closing properly - still in memorymemberZatrael6 Aug '08 - 2:08 
I used the source files included in the zip file. From my very limited experience with Win32 message loops in another tutorial, I thought it might have related to the WM_CLOSE message, and played around with that to no avail. If the code is working fine for you/others then my guess would be that I've missed something in setting up the project, or it's a quirk with my environment. I'll try and look into it further this weekend if I get time.
 
Thanks again for all your hard work
GeneralRe: Executable not closing properly - still in memorymvpCedric Moonen6 Aug '08 - 3:14 
That's really strange because I don't have any problems on my computer (I tested it on two different computers).
 

Zatrael wrote:
I thought it might have related to the WM_CLOSE message

 
As I explained in my previous answer, the WM_CLOSE message do not exit your program. What you have to understand is that you have an 'infinite' loop at the heart of your program (the message loop). Until this loop is running, your application is still running (even if you don't see the main window anymore). The only way to exit it is to send a WM_QUIT message to it (if you look at the code, the loop breaks when such message is received). If you want to exit your app when you close your main window, you have to send a WM_QUIT message by calling PostQuitMessage (which also done in my code if you look).
 
Are you sure you didn't play a bit with the sources and modified some stuff there ? Could you download the sources again, recompile them and try it again ? If it still doesn't work, could you tell which IDE you are using and on which platform you are running ? Also, it could be usefull to debug to see if you receive this message or not in the main loop.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Executable not closing properly - still in memorymemberZatrael7 Aug '08 - 3:33 
I checked my message loop against your code, and I hadn't changed anything. Despite that, I created a new project, downloaded a new copy of your code, and the problem still occurs. I stepped through the debugger, and it appears that after the WM_CLOSE message is handled by ProcessEvent, control is returned to OnEvent. OnEvent then calls DefWindowProc, returns the return value from DefWindowProc, at which point the application remains in memory after the window has been closed. If I try to debug past this point it's an assembly which I obviously don't understand.
 
After none of this worked, I added an if condition to test for the WM_QUIT message after the while loop in CApplication::Run(), but the code never gets this far.
 
I'm still at a loss as to what's going wrong. I think I need to look through the code a bit more to get a solid understanding of exactly where/how messages are being processed before I can proceed with debugging. I need to go to bed (work in the morning), but I'll hopefully get a chance to look over this tomorrow night.
GeneralRe: Executable not closing properly - still in memorymvpCedric Moonen7 Aug '08 - 3:38 
I just have a doubt: which IDE are you using ? I remember that I once tried Dev-Cpp (with mingw compiler) and I experienced sometimes similar issues. In general rebuilding the project sometimes fixed the problem. Could you try that: clean your project and rebuild everything.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Executable not closing properly - still in memorymemberZatrael7 Aug '08 - 21:15 
Sorry, I forgot to mention in my previous post I'm using Visual Studio 2005. After your last post I downloaded a new copy of the tutorial code and started a new project with the new copy of the code. Is that what you mean by cleaning the project and rebuilding? In any event it still had the same issue.
 
As I said in my last post I'll hopefully get a chance to look over the code in more detail in the next day or two, and if I come up with anything I'll be sure to post it.
GeneralRe: Executable not closing properly - still in memory [modified]memberZatrael9 Aug '08 - 16:30 
I have an update to my problem. I was playing around with the code just now, and the application quits properly with the following code added to CApplication::Run()
 
...
while (Message.message != WM_QUIT)
    {
        if( mainWindow.bHaveQuit ) {
            MessageBox(NULL, "You have quit", "Quit", MB_OK);
        }
...
 
I've added a public variable in MainWindow (mainWindow.bHaveQuit above) that gets set to true when the MainWindow class receives the WM_CLOSE message. The end result is that after the user has closed the window, the application loop will try to launch a MessageBox (which oddly never appears, I just hear the system "ding" noise). For some reason that is currently beyond me, when this code is added the WM_QUIT message is received by the application loop. Without the MessageBox the application loop never receives the WM_QUIT message.
 
I'm now quite confused...
 
*EDIT*
I've been doing some more debugging and have noticed that the destructor for CMainWindow (~CMainWindow()) is never called unless the application attempts to raise a MessageBox as explained above. I guess this makes sense, because it is here the window is explicitly destroyed.
 
modified on Saturday, August 9, 2008 11:36 PM

GeneralFIXED: Executable not closing properly - still in memorymemberZatrael12 Aug '08 - 12:23 
Just wanted to let you know that with the changes you've put in the above post, the code now works as it should. I remember reading in my travels that a program won't receive a WM_QUIT message until all of the messages in the queue have been cleared. From memory the article was suggesting that PostQuitMessage(0) simply sets a flag on the queue, which tells it to send a WM_QUIT message when there's nothing left to send. My guess would be that the while loop in the revised code above clears the message queue which means that once PostQuitMessage(0) is called, the application receives it in the next loop.
 
Anyways, thanks again for your efforts, and I'm really looking forward to part 2. No pressure Poke tongue | ;-P
GeneralRe: FIXED: Executable not closing properly - still in memorymvpCedric Moonen12 Aug '08 - 21:02 
Yes, I wanted to suggest to try those modifications too. I suppose this has something to do with the queue that is never empty, as you suggested. I'm happy to see that the problem is fixed Smile | :) .
 

Zatrael wrote:
Anyways, thanks again for your efforts, and I'm really looking forward to part 2. No pressure Poke tongue | ;-P

 
Should be available soon. I finished writing the text and the code, I need to revise it and test the code. So, I hope to send it for reviewing to the CP editors end of this week.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

QuestionHow can I implementation shortest path problem in c++ using:memberengawsan20 Jul '08 - 0:11 
How can I implementation shortest path problem in c++ using:
1)dynamic programming.
2)Divided and Conqure.
AnswerRe: How can I implementation shortest path problem in c++ using:mvpCedric Moonen20 Jul '08 - 3:17 
What does this have to do with OpenGL ?
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

Generalshortest path problemmemberengawsan20 Jul '08 - 0:05 
How can I implementation shortest path problem in c++ using:
1)dynamic programming.
2)Divided and Conqure.
QuestionDev-C++memberxskltn8 Jul '08 - 19:04 
Can't get this to compile in Dev-C++ anyone know how?
It works fine in Visual Studio.
AnswerRe: Dev-C++ [modified]mvpCedric Moonen8 Jul '08 - 20:23 
Yes, I think it is possible. You have to look a bit into the project settings and specify the same things I specified. You don't haveto take UNICODE in consideration I think, so the only thing you need to specify is to link to opengl32.lib (and this is definitively possible with dev-cpp).
 
On a side note, I used Dev-Cpp before and I have to say that I was very disapointed: the UI is not really convenient, the debugger didn't work properly (some breakpoints that should have been reached were never reached), the were quite some bugs with the GUI, ... Visual Studio 2005 is free for the express edition (which doesn't have support for MFC, but Dev-Cpp doesn't have it neither), so I suggest you take a look at it if you are interested.
 
EDIT: it seems you have edited your message before I posted mine. So, if you can't compile it with Dev-Cpp, what is the problem ? Any error message ? Please, be as clear as possible.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]
modified on Wednesday, July 9, 2008 2:44 AM

GeneralRe: Dev-C++memberxskltn8 Jul '08 - 23:55 
I get an error message with the Exception.h if i remove that and it's catches i get a window but with the exception code it doesnt work.
 
The error i get is
"2 C:\Dev-Cpp\Main.cpp In file included from Main.cpp "
 
and it throws this at the #include "Exception.h" part.
GeneralRe: Dev-C++mvpCedric Moonen9 Jul '08 - 0:00 
Yeah but what is the error message from within this exception.h file ? What you posted is only telling me that there is an error in the Exception.h file. Please paste the FULL output of the compiler.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Dev-C++memberxskltn9 Jul '08 - 0:12 
Compiler: Default compiler
Building Makefile: "C:\Dev-Cpp\Makefile.win"
Executing  make...
make.exe -f "C:\Dev-Cpp\Makefile.win" all
g++.exe -c Main.cpp -o Main.o -I"C:/Dev-Cpp/lib/gcc/mingw32/3.4.2/include"  -I"C:/Dev-Cpp/include/c++/3.4.2/backward"  -I"C:/Dev-Cpp/include/c++/3.4.2/mingw32"  -I"C:/Dev-Cpp/include/c++/3.4.2"  -I"C:/Dev-Cpp/include"   
 
In file included from Main.cpp:2:
Exception.h:10: error: looser throw specifier for `virtual const char* CException::what() const'
C:/Dev-Cpp/include/c++/3.4.2/exception:59: error:   overriding `virtual const char* std::exception::what() const throw ()'
Exception.h:13: error: looser throw specifier for `virtual CException::~CException()'
C:/Dev-Cpp/include/c++/3.4.2/exception:56: error:   overriding `virtual std::exception::~exception() throw ()'
 
make.exe: *** [Main.o] Error 1
Execution terminated

GeneralRe: Dev-C++mvpCedric Moonen9 Jul '08 - 0:19 
Try adding a throw() after the what method and the destructor:
 
class CException : public std::exception
{
public:
	const char* what() const  throw() { return m_strMessage.c_str(); }
 
	CException(const std::string& strMessage="") : m_strMessage(strMessage)  { }
	virtual ~CException()  throw() { }
 
	std::string m_strMessage;
};
 
Tell me if that fixes the problem.
Don't forget to link to the opengl32.lib also.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++

GeneralRe: Dev-C++memberxskltn9 Jul '08 - 0:37 
Yeah that fixed the problem thanks Smile | :)
GeneralSetWindowLongPtr at WM_CREATEmemberNemanja Trifunovic26 Jun '08 - 10:11 
First of all, this is a great article - thanks for writing it!
 
One minor remark, though - setting the object pointer with SetWindowLongPtr when WM_CREATE is being processed may be dangerous, since there are messages that are sent to a window before WM_CREATE. There is a good article on this topic: Windows Procedures as Class Members[^]
 

GeneralRe: SetWindowLongPtr at WM_CREATEmvpCedric Moonen26 Jun '08 - 22:04 
Thanks Nemanja Smile | :)
 
I'll take a look at your article when I'm coming back from holiday, thanks for the link !
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]

Questionwhy not using singleton?memberphinecos25 Jun '08 - 22:59 
a nice article,and the code seems like MFC,but " CApplication theApp(Instance);",isn't it better to be a global object?
AnswerRe: why not using singleton?mvpCedric Moonen25 Jun '08 - 23:13 
phinecos wrote:
isn't it better to be a global object?

 
In this case, it really depends: the application class is there to wrap the message loop and to create the main window. It doesn't do anything else, so there is no need to be able to access it from anywhere in the code. I could also put in this class some managers that I will use later, then it makes sense to implement it as a singleton. But you'll see in the next article that I'll do it differently.
 
The only advantage of implementing it as a singleton (I suppose that you meant the singleton design pattern) is to avoid that it gets created twice by mistake. But as for being 'better', it's just a matter of taste. I don't like to have too much singletons everywhere (this pattern is sometimes a bit overabused).
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]

GeneralRe: why not using singleton?memberphinecos26 Jun '08 - 0:03 
thanks for your patient,another question,the application don't response well to the WM_SIZE event,when I zoom in/out the window,the content disapear!why not redraw the window as its size changes? Smile | :)
GeneralRe: why not using singleton?mvpCedric Moonen26 Jun '08 - 1:11 
phinecos wrote:
the application don't response well to the WM_SIZE event,when I zoom in/out the window,the content disapear!why not redraw the window as its size changes

 
It is not necessary because everything will be redrawn in the next cycle (the main window constantly redraws its content). So, if everything went black in the next drawing call, putting it in the WM_SIZE handler won't change anything.
Another problem is that you are duplicating your code: you will have twice your drawing code which is a bit harder to maintain. Of course you could put all this code in a specific funcion and call it from both location. But there is another problem: the animations. The code is done in a way that the window is redrawn every 30 msec (if I remember correctly), thus if you redraw your window more often because you are resizing it, then the animations will go wrong (too fast).
 
I wanted to ask also: do you have this problem (the content disappearing) with my code ? Or did you write something on your own ? If it is my code, then it means there is a problem in my code and I need to fix it Smile | :) . Anyway, I'll check also this evening if I have the same problem.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]

GeneralRe: why not using singleton?mvpCedric Moonen26 Jun '08 - 9:27 
I checked for the resizing and it's working fine for me. You did maybe something wrong in your code ? Maybe forgot to redraw the view in a loop ?
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]

GeneralRe: why not using singleton?memberphinecos26 Jun '08 - 23:36 
thx for reply,yeah ,I modified your code a little ,and make some logic mistake,now I totally understand your code,thanks again for this nice article, Smile | :)
GeneralGreat!memberNetDefender25 Jun '08 - 5:31 
Great tutorial. Well written, and easy read. Please i love more.
GeneralRe: Great!mvpCedric Moonen25 Jun '08 - 7:32 
Thank you Smile | :)
 
You'll have to be a bit patient for the part 2 of the tutorial: I'm going in holidays next week and I didn't start to write the article yet (although I know what I will put in there). The code is already finished for the second part and I hope to submit it within a month.
 
Cédric Moonen
Software developer

Charting control [v1.4]
OpenGL game tutorial in C++ [New]

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 26 Aug 2008
Article Copyright 2008 by Cedric Moonen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid