OpenGL Windows

How do you get OpenGL to run in a Windows app WITHOUT using GLUT?

Here’s an example, commented in detail, showing how. Emphasis on “easy to understand.”

//////////////////////////////////////////
//                                      //
// OpenGL in a PROPER Windows APP       //
// ( NO GLUT !! )                       //
//                                      //
// You found this at bobobobo's weblog, //
// http://bobobobo.wordpress.com        //
//                                      //
// Creation date:  Feb 9/08             //
// Last modified:  Feb 10/08            //
//                                      //
//////////////////////////////////////////

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gl/gl.h>
#include <gl/glu.h>

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

////////////////////////
// OK, here's the deal.
//
// OpenGL is typically so hard to
// get started on Windows (what, with the
// HDC and the HGLRC and the PIXELFORMATDESCRIPTOR)
// most get turned off very quickly
// and stick with GLUT for a very long time,
// because GLUT hides away A LOT of the
// complexities of getting a window up
// and drawing into it.

// If you're still reading, you're
// probably tired of doing things the
// GLUT way, and you want to cross over
// to doing things in OpenGL using a pure
// Windows application.

// Good choice my friend!  If you want
// full control over your app, you'll
// want to do things in pure Win32.

// Once you get used to programming in Win32,
// its not ALL THAT BAD, I promise (evil grin).

// This demo shows you how to get a basic
// window up that OpenGL can draw into,
// and explains each step in detail.

// I give some additional references at the
// bottom of the page.

//////////////////////////
// OVERVIEW:
//
// The OUTPUT of OpenGL is a two dimensional
// PICTURE of a 3D scene.

// That's right.  I said 2D.

// People often say that the process OpenGL
// goes through to render a 3D scene is just like
// a CAMERA TAKING A PHOTOGRAPH OF A REAL WORLD SCENE.
//
// So, you know obviously that a PHOTOGRAPH
// is PURELY 2D after its been developed,
// and it DEPICTS the 3D scene that it
// "took a picture" of accurately.

// That's exactly what OpenGL does.  OpenGL's JOB
// is to take all your instructions, all your
// glColor3f()'s and your glVertex3f()'s, and
// to ultimately end up DRAWING A 2D PICTURE
// from those instructions that can be displayed
// on a computer screen.

// In THIS program, our MAIN GOAL is to
// CONNECT UP the OUTPUT of OpenGL (that 2D
// image OpenGL produces)
// with YOUR APPLICATION'S WINDOW.

// Does this look familiar?

/*
     HDC hdc = GetDC( hwnd );
*/

// You should already know that the way
// you as a Windows programmer draw to
// your application's window is using
// your window's HDC.

// If this doesn't sound familiar,
// then I strongly recommend you go read up
// on Win GDI before continuing!

////////////////////////////////!
//
// So if our way to draw to our application window
// is the HDC, and OpenGL produces some 2D image
// HOW IN THE WORLD DO YOU CONNECT UP the OUTPUT
// of the OpenGL program (that 2D picture)
// WITH the HDC of a WINDOW?
//
// That's the main subject we're tackling here
// in this tutorial.
//
// And its EASY.
//
// WE'RE lucky.  Microsoft created a bunch
// of functions (all beginning with "wgl"),
// that make this job of "connecting up" the output
// of OpenGL with the HDC of our window quite easy!

/////////////////////////
// BIG PICTURE:
//
// Here's the big picture of what
// we're going to be doing here:

/*

|---------|  draws to   |-------|  copied out   |---------|  shows in  |-----------|
|         | ==========> |       | ============> |         | =========> |           |
|---------|             |-------|               |---------|            |-----------|
  OPENGL                  HGLRC                     HDC                 application
 FUNCTION                                                                 window
  CALLS                                                                     

*/

//////////////////////
// In code:  this is the steps
// we'll follow.
//
// 1.  CREATE WINDOW AS USUAL.
//
// 2.  GET DC OF WINDOW.
//     Get the HDC of our window using a line like:
//          hdc = GetDC( hwnd );
//
// 3.  SET PIXEL FORMAT OF HDC.
//     You do 3 things here:
//          Create a PFD ('pixel format descriptor')
//          ChoosePixelFormat()
//          SetPixelFormat()
//
// 4.  CREATE RENDERING CONTEXT (HGLRC).
//          wglCreateContext()
//     Create the surface to which OpenGL
//     shall draw.  It is created such that
//     it shall be completely compatible
//     with the DC of our window, (it will
//     use the same pixel format!)
//
// 5.  CONNECT THE RENDER CONTEXT (HGLRC)
//     WITH THE DEVICE CONTEXT (HDC) OF WINDOW.
//          wglMakeCurrent()
//
// 6.  DRAW USING OPENGL.
//          glVertex3d(); glColor3d(); // ETC!
//     You call OpenGL functions to perform
//     your drawing!  OpenGL will spit out
//     its result picture to the HGLRC, which is
//     connected to the backbuffer of your HDC.
//
// 7.  SWAP BUFFERS.
//          SwapBuffers( hdc );
//     Assuming you've picked a DOUBLE BUFFERED
//     pixel format all the way back in step 2,
//     you'll need to SWAP the buffers so that
//     the image you've created using OpenGL on
//     the backbuffer of your hdc is shown
//     in your application window.

// And that's all!

// Ready for the code??? Let's go!

/////////////////////
// GLOBALS
//
/// Define a structure to hold all
/// of the global variables of this app.
struct Globals
{
    HINSTANCE hInstance;    // window app instance

    HWND hwnd;      // handle for the window

    HDC   hdc;      // handle to device context

    HGLRC hglrc;    // handle to OpenGL rendering context

    int width, height;      // the desired width and
    // height of the CLIENT AREA
    // (DRAWABLE REGION in Window)
};

///////////////////////////
// GLOBALS
// declare one struct Globals called g;
Globals g;
//
///////////////////////////

// Function prototypes.
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam );
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow );
void draw();            // drawing function containing OpenGL function calls

////////////////////////////
// In a C++ Windows app, the starting point is WinMain().
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow )
{
    //////////////////
    // First we'll start by saving a copy of
    // the hInstance parameter inside our
    // "glob" of globals "g":
    g.hInstance = hInstance;

    #pragma region part 1 - create a window
    // The next few lines you should already
    // be used to:  create a WNDCLASS
    // that describes the properties of
    // the window we're going to soon create.
    // A.  Create the WNDCLASS
    WNDCLASS wc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = TEXT("Philip");
    wc.lpszMenuName = 0;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

    // Register that class with the Windows O/S..
	RegisterClass(&wc);

    /////////////////
    // Ok, AT THIS POINT, we'd normally
    // just go ahead and call CreateWindow().
    // And we WILL call CreateWindow(), but
    // there is something I must explain to
    // you first.  That thing is the RECT structure.

    /////////////////
    // RECT:
    //
    // A RECT is just a C struct meant to represent
    // a rectangle.
    //
    // The RECT structure WILL DESCRIBE EXACTLY WHERE
    // AND HOW WE WANT OUR WINDOW TO APPEAR WHEN WE
    // CREATE IT.
    //
    //         TOP
    //       --------
    //       |      |
    // LEFT  |      | RIGHT
    //       --------
    //        BOTTOM
    //
    // So, what we do is, we create the RECT
    // struct for our window as follows:
    RECT rect;
    SetRect( &rect, 50,  // left
                    50,  // top
                    850, // right
                    650 ); // bottom

    // Save width and height off.
    g.width = rect.right - rect.left;
    g.height = rect.bottom - rect.top;

    // Adjust it.
    AdjustWindowRect( &rect, WS_OVERLAPPEDWINDOW, false );

    // AdjustWindowRect() expands the RECT
    // so that the CLIENT AREA (drawable region)
    // has EXACTLY the dimensions we specify
    // in the incoming RECT.

    // If you didn't just understand that, understand
    // this:  "you have to call AdjustWindowRect()",
    // and move on.  Its not THAT important, but its
    // good for the performance of your app.

    ///////////////////
    // NOW we call CreateWindow, using
    // that adjusted RECT structure to
    // specify the width and height of the window.
    g.hwnd = CreateWindow(TEXT("Philip"),
                          TEXT("GL WINDOW!"),
                          WS_OVERLAPPEDWINDOW,
                          rect.left, rect.top,  // adjusted x, y positions
                          rect.right - rect.left, rect.bottom - rect.top,  // adjusted width and height
                          NULL, NULL,
                          hInstance, NULL);

    // check to see that the window
    // was created successfully!
    if( g.hwnd == NULL )
    {
        FatalAppExit( NULL, TEXT("CreateWindow() failed!") );
    }

    // and show.
    ShowWindow( g.hwnd, iCmdShow );
    #pragma endregion

    #pragma region part 2 - Get DC of window we just made
    //2.  GET DC OF WINDOW, and keep it in our global
    //    variable g.  We will NOT release this DC
    //    until our app is about to exit.
    g.hdc = GetDC( g.hwnd );

    // If this keeping-DC-for-life-of-program-thing
    // disturbs you as much as it disturbed me,
    // GOOD LUCK in finding MS-based documentation to validate
    // this practice!  All examples I've seen and
    // Astle's OpenGL gaming book do it this way,
    // so. . . I suppose its ok.

    // One of the things I make sure to do is to specify
    // CS_OWNDC when I create the window,
    // so that Windows maintains a separate device context
    // for my application's window.

    // I haven't run into problems with this.  I don't
    // think you should either.
    #pragma endregion

    #pragma region part 3 - SET PIXEL FORMAT OF HDC
    //3.  SET PIXEL FORMAT OF HDC.
    //
    // We now have to set up the PIXELFORMAT
    // of our HDC.

    // A PIXEL FORMAT just describes the
    // "qualities" that each pixel in the
    // window will have.  Do you want your
    // OpenGL app to use 24 bit color
    // ("true color" -- really high
    // quality!)?  Or 16 bit color (can look
    // a bit washed out)?

    // There are 3 substeps here:
    //    A> create the PFD and set it up to describe
    //       the pixel format we DESIRE (dream of having!)
    //
    //    B> call ChoosePixelFormat() to make windows
    //       choose us the ID of the appropriate pixel format that
    //       is CLOSEST to our dream pixel format.
    //
    //    C> Call SetPixelFormat() using the integer ID number
    //       that ChoosePixelFormat() returned to us in step B>

    // So let's do that:

    ////////////////////
    // A> CREATE PFD:
    PIXELFORMATDESCRIPTOR pfd = { 0 };  // create the pfd,
    // and start it out with ALL ZEROs in ALL
    // of its fields.

    // A good description of the PIXELFORMATDESCRIPTOR
    // struct is under the documentation
    // for the ChoosePixelFormat() function:
    // http://msdn2.microsoft.com/en-us/library/ms537556(VS.85).aspx

    // If you look at the docs, MANY of the fields
    // are "NOT USED" and REMAIN 0.

    // That should be something of a relief to you!
    // Look at the number of fields in this beast!

    // So we set only the fields of the pfd we care about:
    pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR );    // just its size
    pfd.nVersion = 1;   // always 1

    pfd.dwFlags = PFD_SUPPORT_OPENGL |  // OpenGL support - not DirectDraw
                  PFD_DOUBLEBUFFER   |  // double buffering support
                  PFD_DRAW_TO_WINDOW;   // draw to the app window, not to a bitmap image

    pfd.iPixelType = PFD_TYPE_RGBA ;    // red, green, blue, alpha for each pixel
    pfd.cColorBits = 24;                // 24 bit == 8 bits for red, 8 for green, 8 for blue.
                                        // This count of color bits EXCLUDES alpha.

    pfd.cDepthBits = 32;                // 32 bits to measure pixel depth.  That's accurate!

    ///////////////////
    // B> Alright!  We've filled out the pfd
    // and it describes the way we want
    // our pixels to appear on the screen.
    //
    // Now this next step is a little bit weird.
    // The thing is, there are only a couple of
    // dozen ACCEPTABLE pixel formats in existence.
    //
    // In other words, the system MIGHT NOT
    // be able to use a pixel format the likes
    // of which you have described in your
    // PIXELFORMATDESCRIPTOR.

    // What to do?? It would be awful annoying
    // to have to keep TRYING different
    // PIXELFORMATDESCRIPTORS until we found
    // one that actually WORKED on this system.

    // So MSFT has a better solution.

    // We'll make a call to a function called
    // ChoosePixelFormat().  ChoosePixelFormat()
    // will examine the PIXELFORMATDESCRIPTOR
    // structure that you send it, then it will
    // give you back an ID for the pixel format
    // that MOST CLOSELY MATCHES the pixel format you
    // SAID you wanted.

    int chosenPixelFormat = ChoosePixelFormat( g.hdc, &pfd );
    // what comes back from ChoosePixelFormat() is
    // an integer identifier for a specific pixel
    // format that Windows ALREADY knows about.
    // If you got 0 back, then there was an error.
    if( chosenPixelFormat == 0 )
    {
        FatalAppExit( NULL, TEXT("ChoosePixelFormat() failed!") );
    }

    char b[100];
    sprintf(b, "You got ID# %d as your pixelformat!\n", chosenPixelFormat);
    MessageBoxA( NULL, b, "Your pixelformat", MB_OK );

    /////////////////
    // C> So finally, we call SetPixelFormat() using the integer ID number
    // that ChoosePixelFormat() returned to us previously.
    int result = SetPixelFormat( g.hdc, chosenPixelFormat, &pfd );

    if (result == NULL)
    {
        FatalAppExit( NULL, TEXT("SetPixelFormat() failed!") );
    }
    // and that's all there is to setting
    // the pixel format!
    //////////
    #pragma endregion

    #pragma region part 4 - CREATE THE RENDERING CONTEXT
    //4.  CREATE RENDERING CONTEXT (HGLRC).

    // What's a rendering context?
    // Its the "surface" that OpenGL
    // will DRAW to.

    // The HGLRC will be created
    // such that it is COMPATIBLE
    // with the hdc.

    g.hglrc = wglCreateContext( g.hdc );
    // Created the rendering context
    // and saved handle to it in global 'g'.
    //
    // Wasn't that awfully easy to create
    // such a complicated sounding thing?
    ///////////////
    #pragma endregion

    #pragma region part 5 - CONNECT THE RENDERING CONTEXT WITH THE DEVICE CONTEXT OF THE WINDOW
    //5.  CONNECT THE RENDER CONTEXT (HGLRC)
    //    WITH THE DEVICE CONTEXT (HDC) OF WINDOW.
    wglMakeCurrent( g.hdc, g.hglrc );

    //
    // OPEN GL INIT COMPLETED!!
    ////////////////////////////
    #pragma endregion

    #pragma region message loop
    MSG msg;

    while( 1 )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            if( msg.message == WM_QUIT )
            {
                break;
            }

            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }

        //6.  DRAW USING OPENGL.
        // This region right here is the
        // heart of our application.  THE MOST
        // execution time is spent just repeating
        // this draw() function.
        draw();

    }
    #pragma endregion

    //////////////
    // clean up
    #pragma region clean up
    // UNmake your rendering context (make it 'uncurrent')
    wglMakeCurrent( NULL, NULL );

    // Delete the rendering context, we no longer need it.
    wglDeleteContext( g.hglrc );

    // release your window's DC
    ReleaseDC( g.hwnd, g.hdc );
    #pragma endregion

    // and a cheesy fade exit
    AnimateWindow( g.hwnd, 200, AW_HIDE | AW_BLEND );

    return msg.wParam;
}

////////////////////////
// DRAWING FUNCTION
void draw()
{
    // 1. set up the viewport
    glViewport(0, 0, g.width, g.height); // set viewport
    // to be the whole width and height
    // of the CLIENT AREA (drawable region) of the window,
    // (the CLIENT AREA excludes the titlebar and the
    // maximize/minimize buttons).

    // 2. projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0,(float)g.width/(float)g.height, 1, 1000);

    // 3. viewing transformation
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(  0, 0, 10,
                0, 0, 0,
                0, 1, 0);

    // 4. modelling transformation and drawing
    glClearColor( 0.5, 0, 0, 0 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    static float i = 0.01f;
    // Notice that 'i' is a STATIC variable.
    // That's very important. (imagine me saying
    // that like Conchords in "Business Time")
    // http://youtube.com/watch?v=WGOohBytKTU

    // A 'static' variable is created ONCE
    // when the function in which it sits first runs.

    // The static variable will "LIVE ON"
    // between seperate calls to the function
    // in which it lives UNTIL THE PROGRAM ENDS.

    i+= 0.001f;     // increase i by 0.001 from its
    // it had on the LAST FUNCTION CALL to the draw() function

    float c = cos( i );
    float s = sin( i );

    glBegin (GL_TRIANGLES);
        glColor3f(  c, 0, 0 );      // red
        glVertex3f( 1+c, 0+s, 0 );

        glColor3f(  c, s, 0 );      // yellow
        glVertex3f( 0+c, 1+s, 0 );

        glColor3f(  s, 0.1f, s );   // magenta
        glVertex3f(-1+c, 0+s, 0 );
    glEnd();

    //7.  SWAP BUFFERS.
    SwapBuffers(g.hdc);
    // Its important to realize that the backbuffer
    // is intelligently managed by the HDC ON ITS OWN,
    // so all's you gots to do is call SwapBuffers
    // on the HDC of your window.
}

////////////////////////
// WNDPROC
// Notice that WndProc is very very neglected.
// We hardly do anything with it!  That's because
// we do all of our processing in the draw()
// function.
LRESULT CALLBACK WndProc(   HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
    switch( message )
    {
    case WM_CREATE:
        Beep( 50, 10 );
        return 0;
        break;

    case WM_PAINT:
        {
            HDC hdc;
            PAINTSTRUCT ps;
            hdc = BeginPaint( hwnd, &ps );
                // don't draw here.  would be waaay too slow.
                // draw in the draw() function instead.
            EndPaint( hwnd, &ps );
        }
        return 0;
        break;

    case WM_KEYDOWN:
        switch( wparam )
        {
        case VK_ESCAPE:
            PostQuitMessage( 0 );
            break;
        default:
            break;
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage( 0 ) ;
        return 0;
        break;
    }

    return DefWindowProc( hwnd, message, wparam, lparam );
}

////////////////
// END NOTES:
//
// Some good references:
// WGL function ref on MSDN:
// http://msdn2.microsoft.com/en-us/library/ms673957%28VS.85%29.aspx

// MSDN example from 1994 (but still good!)
// "OpenGL I: Quick Start"
// http://msdn2.microsoft.com/en-us/library/ms970745.aspx

//////////////////
// QUICK Q&A (make sure you know what's going on):
//
// QUESTION:  What's the means by which we can draw
//            to our window itself?
//
// ANSWER:  The HDC (HANDLE TO DEVICE CONTEXT).

// QUESTION:  What's the means by which OpenGL can
//            draw to the window?
//
// ANSWER:  USING that SAME HDC WE woulda used
//          to draw to it!! (more to come on this now).

/////////////////////
// It IS possible to access the bits
// of the output of OpenGL in 2 ways:
//      1)  Use glReadPixels() to obtain
//          the arrayful of pixels on the
//          screen.  You can then save this
//          to a .TGA or .BMP file easily.

//      2)  Render to a BITMAP, then
//          blit that bitmap to your HDC.
//          There's a 1995 msdn article on this:
//          "OpenGL VI: Rendering on DIBs with PFD_DRAW_TO_BITMAP"
//          http://msdn2.microsoft.com/en-us/library/ms970768.aspx

/*
     ____   __   __      __   __  ___
    / _  \ /  / /  /    /  /  \ \/  /
   / _/ / /  / /  /    /  /    \   /
  / _/ \ /  / /  /__  /  /__   /  /
 /_____//__/ /______//______/ /__/

*/

Download the Visual Studio 2005 project files hosted by esnips! (thanks esnips!)

7 Comments

  1. Holyyyyyy GOOOOODDDDDDD

    Thank you so much for this (glut-free way to use OpenGL). Everywhere I’ve looked all I could find was glut.

    And glut was pissing me off in that it required glut32.dll to run (which is really inconvenient when trying to distribute software!!!)

    Thank you for this, I will study it very closely and good use of this approach!!

  2. Very symbolic too, 2/9 is my birthday…

    • ajcaruana
    • Posted October 8, 2008 at 6:56 am
    • Permalink

    Hi, is it possible to use the HDC to draw to a part of the window using GDI, while drawing to another part of the window using OpenGL ?

    I need to do this in an application, and I wish to avoid creating a child window to draw into using OpenGL.

  3. you should probably use glui:
    http://www.cs.unc.edu/~rademach/glui/

    • chris
    • Posted October 30, 2008 at 7:28 am
    • Permalink

    Very nice tut, 10/10 for great use of comments, is there a way to do 2d graphics in opengl… such as loading a bitmap and a mask for transparency and using double buffering to display it on screen same as bitblt does…just wondering for the speed improvemnts using opengl, even tho i know its used for 3d, any help be appreciated..

    • Anonymous
    • Posted November 23, 2008 at 3:50 am
    • Permalink

    Awsome!

    • Anonymous
    • Posted January 27, 2009 at 6:57 pm
    • Permalink

    Fantastically detailed! Thank you so much for writing this!


Post a Comment