2007. január 8., hétfő

OpenGL I: Hello World


Problem/Question/Abstract:

Most people know Delphi as a RAD tool to create database applications, Delphi programmers know that with Delphi you can do EVERYTHING

Answer:

There's quite some people out there doing great efforts to promote OpenGL and DirectX technologies with Delphi;
I will mention this, as I got the base code (and fixed it a little bit) from here:
http://nehe.gamedev.net/

In this article I will show you the base code to create fast and small Delphi-OpenGL applications

I would like to comment that graphics programming is not easy, you will need some knowledge about math and a lot of reading, is like learning a new languaje (a hard one)

First, we will be using no forms (to make application small) and therefore obviously no components (we're going to do this as real programmers =o) )

Our application will consist of the "project source" and one unit In our unit we are just going to create a record to hold some of the application values and some simple constants that are explained in detail here's the "header" of our unit (sorry, no classes or objects):

type
  TGLWindow = record
    Active: Boolean;
      //Window Active Flag (False is minimized, so we don't draw stuff when minimized)
    ExitGame: Boolean; //The main loop is based on this variable
    Keys: array[0..255] of Bool; //Array Used For The Keyboard Routine
    Fullscreen: Boolean; //Fullscreen Flag
    MouseLButton: Integer;
    MouseRButton: Integer; //Left or right buttons pressed? (0 or 1)
    MouseX: Integer;
    MouseY: Integer;
    MouseZ: Integer;
      //Used when right button is pressed (up and down move in and out in the Z axis)
  end;

  { All User Variables Here }
var
  GS: TGLWindow;

const
  POS_X = 100; //Position of window (only when NOT in fullscren mode)
  POS_Y = 100;
  RES_X = 640; //Resolution
  RES_Y = 480;
  RES_BITS = 16; //16 bits resolution
  WIN_TITLE = 'My Game'; //Title for our window

Then from our unit we need to export this function:

function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: PChar; nCmdShow:
  Integer): integer; stdcall;

and we will also need these variables (private to our unit, so after implementation):

var
  h_RC: HGLRC; //Permanent Rendering Context
  h_DC: HDC; //Private GDI Device Context
  h_Wnd: HWND; //Holds Our Window Handle

This function basically does everything, initializes the window, draws our stuff, process messages, and when you're done destroys the window. Ok, now we need all the procedures to initialize, process messages, etc...

Here are the functions and some explanations (I'll just list the functions and later will put the actual implementation of each one)

function DrawGLScene(): Bool; { All Rendering Done Here }
procedure ReSizeGLScene(const Width: GLsizei; Height: GLsizei);
{ Resize and Initialize The GL Window }
function InitGL(const Width, Height: Glsizei): Bool;
{ All Setup For OpenGL Goes Here }

//WndProc handles all the messages coming to our window

function WndProc(hWnd: HWND; //Handle For The Window
  message: UINT; //Message For This Window
  wParam: WPARAM; //Additional Message Information
  lParam: LPARAM): //Additional Message Information
LRESULT; stdcall;

{in the CreateWindow we do:
- Register the class window
- Create the window
- Get a Device Context (DC)
- Create a Rendering Context (RC) }

function CreateGLWindow(Title: PChar; PosX, PosY: Integer; const Width,
  Height, Bits: Integer; const FullScreenFlag: Bool): Bool; stdcall;

{In the KillWindow we do (obviously the opposite of the CreateWindow and in reverse order):
- Restore the display settings (we need to do this even if something else fails)
- Delete the Rendering Context (RC)
- Release the Device Context (DC)
- Destroy the Window
- Unregister the class window }

procedure KillGLWindow; { Properly Kill the Window }

//WinMain is the actual Main Program (gets called from the actual Main.dpr)

function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: PChar;
  nCmdShow: Integer): integer; stdcall;

That's all you will need to get started, here's the implementation of these procedures/functions, take a good look at WinMain and WndProc, they show some good stuff even for not graphic applications, you could use them to create small programs that do not require windows...

oh... and one last thing... the "hello world" program in OpenGL won't even show the words "Hello World" (WHAAAT??)
the thing is that outputing text to the screen in OpenGL is a little advanced and I will show it to you in later articles (if you are interested that is). The purpose of this article is to show you the base code and hopefully you will get to understand it and then from there we can concentrate in the OpenGL stuff, so... I will just show you how to create a simple rectangle =o (,but don't be dissapointed, as I say OpenGL is not easy and you won't be creating the next QUAKE in the next 3 months, first you need to create a rectangle, a triangle... a circle ...and theeeen... eventually you will get there (if you really persist)

with no more preambule, here's the code:

  //---------------------------------------------------------//
  //                                                         //
  //     Original Copyrights: Daniel Vivas                   //
  //                          daniel@vivas.com.br            //
  //                                                         //
  //     Main Game Unit                                      //
  //     Ported To Delhi By: Bryce TeBeest                   //
  //     Assistance Provided By: JP Krauss                   //
  //                                                         //
  //     Taken from Jeff Molofi (NEHE) WebSite               //
  //     http://nehe.gamedev.net                             //
  //                                                         //
  //     Some fixes and comments by: EberSys                 //
  //---------------------------------------------------------//
unit oglMain;

interface

uses
  Classes,
  Messages,
  Windows,
  OpenGL;

type
  TGLWindow = record
    Active: Boolean;
      //Window Active Flag (False is minimized, so we don't draw stuff when minimized)
    ExitGame: Boolean; //The main loop is based on this variable
    Keys: array[0..255] of Bool; //Array Used For The Keyboard Routine
    Fullscreen: Boolean; //Fullscreen Flag
    MouseLButton: Integer;
    MouseRButton: Integer; //Left or right buttons pressed? (0 or 1)
    MouseX: Integer;
    MouseY: Integer;
    MouseZ: Integer;
      //Used when right button is pressed (up and down move in and out in the Z axis)
  end;

  { All User Variables Here }
var
  GS: TGLWindow;

const
  POS_X = 100; //Position of window (only when NOT in fullscren mode)
  POS_Y = 100;
  RES_X = 640; //Resolution
  RES_Y = 480;
  RES_BITS = 16; //16 bits resolution
  WIN_TITLE = 'My Game'; //Title for our window

  {-----------------------------------------------------------}
  { Public Procedures: }

function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: PChar; nCmdShow:
  Integer): integer; stdcall;
{-----------------------------------------------------------}

implementation

var
  h_RC: HGLRC; //Permanent Rendering Context
  h_DC: HDC; //Private GDI Device Context
  h_Wnd: HWND; //Holds Our Window Handle
  {-----------------------------------------------------------}

function DrawGLScene(): Bool; { All Rendering Done Here }
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); //Clear Screen and Depth Buffer
  glLoadIdentity(); //Reset The View (move to 0, 0, 0)

  glTranslatef(0.0, 0.0, -6.0); // Move Right 1.5 Units And Into The Screen 6.0
  glColor3f(0.0, 0.5, 0.5);
  glBegin(GL_QUADS); // Draw A Quad
  glVertex3f(-0.5, 0.5, 0.0); // Top Left
  glVertex3f(0.5, 0.5, 0.0); // Top Right
  glVertex3f(0.5, -0.5, 0.0); // Bottom Right
  glVertex3f(-0.5, -0.5, 0.0); // Bottom Left
  glEnd();

  DrawGLScene := True
end;

procedure ReSizeGLScene(const Width: GLsizei; Height: GLsizei);
  { Resize and Initialize The GL Window }
var
  fWidth, fHeight: GLFloat;
begin
  if (Height = 0) then //Prevent Divide by Zero
    Height := 1; //Be Setting Height at One

  glViewport(0, 0, Width, Height);
    //Reset The Current Viewport and Perspective Transformation
  glMatrixMode(GL_PROJECTION); //Select The Projection Matrix
  glLoadIdentity(); //Reset The Project Matrix
  fWidth := Width;
  fHeight := Height;
  gluPerspective(45.0, fWidth / fHeight, 0.1, 100);
    //Calculate the Aspect Ratio of the Window
  glMatrixMode(GL_MODELVIEW); //Select The ModelView Matrix
end;

{ All Setup For OpenGL Goes Here }

function InitGL(const Width, Height: Glsizei): Bool;
var
  fWidth, fHeight: GLfloat;
begin
  glClearColor(0.0, 0.0, 0.0, 0.0); //Black Background
  glClearDepth(1.0); //Depth Buffer Setup
  glDepthFunc(GL_LESS); //Text
  glEnable(GL_DEPTH_TEST); //Enables Depth Testing
  glShadeModel(GL_SMOOTH); //Enables Smooth Color Shading
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity(); //reset the View (move to 0, 0, 0)

  fWidth := Width;
  fHeight := Height;
  gluPerspective(45.0, fWidth / fHeight, 0.1, 100);
    //Calculate Aspect Ratio Of The Window
  glMatrixMode(GL_MODELVIEW)
end;

//WndProc handles all the messages coming to our window

function WndProc(hWnd: HWND; //Handle For The Window
  message: UINT; //Message For This Window
  wParam: WPARAM; //Additional Message Information
  lParam: LPARAM): //Additional Message Information
LRESULT; stdcall;
begin
  if message = WM_SYSCOMMAND then
    case wParam of //Check System Calls
      SC_SCREENSAVE, SC_MONITORPOWER:
        //Screensaver Trying To Start, Monitor Trying To Enter Powersave
        begin
          Result := 0;
          exit
        end
    end;

  case message of //Tells Windows We Want To Check Message
    WM_ACTIVATE:
      begin
        if (Hiword(wParam) = 0) then //Check Minimization State
          GS.Active := True
        else
          GS.Active := False; //when Active is False we don't draw anything
        Result := 0;
      end;
    WM_CLOSE: //Did we get a close message?
      begin
        PostQuitMessage(0); //Send A Quit Message
        Result := 0; //Return To The Message Loop
      end;
    WM_KEYDOWN: //Is A Key Being Held Down?
      begin
        GS.Keys[wParam] := True;
        Result := 0; //Return To The Message Loop
      end;
    WM_KEYUP: //Is A Key Being Released?
      begin
        GS.Keys[wParam] := False;
        Result := 0;
      end;
    WM_SIZE: //Resize scene
      begin
        ReSizeGLScene(LOWORD(lParam), HIWORD(lParam)); //LoWord=Width, HighWord=Height
        Result := 0;
      end;
    WM_LBUTTONDOWN: //(mouse) Left button pressed
      begin
        ReleaseCapture(); //Need Them Here, Because If Mouse Moves Off
        SetCapture(h_Wnd); //Window and Returns, It Needs To Reset Status
        GS.MouseLButton := 1;
        GS.MouseX := LOWORD(lParam);
        GS.MouseY := HIWORD(lParam);
      end;
    WM_LBUTTONUP: //(mouse) Left button released
      begin
        ReleaseCapture();
        GS.MouseLButton := 0;
        GS.MouseX := 0;
        GS.MouseY := 0;
        Result := 0;
      end;
    WM_RBUTTONDOWN: //(mouse) Right button pressed
      begin
        ReleaseCapture();
        SetCapture(h_Wnd);
        GS.MouseRButton := 1;
        GS.MouseZ := HIWORD(lParam);
        Result := 0;
      end;
    WM_RBUTTONUP: //(mouse) Right button released
      begin
        ReleaseCapture();
        GS.MouseRButton := 0;
        Result := 0
      end
  else
    { Pass All Unhandled Messages TO DefWinProc }
    Result := DefWindowProc(hWnd, message, wParam, lParam)
  end //case message of
end;

{In the KillWindow we do (obviously the opposite of the CreateWindow and in reverse order):
- Restore the display settings (we need to do this even if something else fails)
- Delete the Rendering Context (RC)
- Release the Device Context (DC)
- Destroy the Window
- Unregister the class window }

procedure KillGLWindow; { Properly Kill the Window }
begin
  if (GS.FullScreen) then
  begin //Are We In FullScreen Mode?
    ChangeDisplaySettings(devmode(nil^), 0); //Switch Back To The Desktop
    ShowCursor(True); //Show The Mouse Pointer
  end;

  if (h_RC <> 0) and not (wglDeleteContext(h_RC)) then //Are We Able To Delete The Rc?
  begin
    MessageBox(0, 'Release of Rendering Context failed.', ' Shutdown Error', MB_OK or
      MB_ICONERROR);
    h_RC := 0 //Set Rendering Context To Null
  end;
  if (h_DC <> 0) and (releaseDC(h_Wnd, h_DC) = 0) then
    //Are We Able To Release The Device Context?
  begin
    MessageBox(0, 'Release of Device Context failed.', ' Shutdown Error', MB_OK or
      MB_ICONERROR);
    h_Dc := 0; //Set Dc To Null
  end;
  if (h_Wnd <> 0) and not (destroywindow(h_Wnd)) then
    //Are We Able To Destroy The Window?
  begin
    MessageBox(0, 'Could not release hWnd.', ' Shutdown Error', MB_OK or
      MB_ICONERROR);
    h_Wnd := 0; //Set hWnd To Null
  end;
  UnregisterClass('OpenGL', hInstance)
end;

{in the CreateWindow we do:
- Register the class window
- Create the window
- Get a Device Context (DC)
- Create a Rendering Context (RC) }

function CreateGLWindow(Title: PChar; PosX, PosY: Integer; const Width, Height, Bits:
  Integer; const FullScreenFlag: Bool): Bool; stdcall;
var
  PixelFormat: GLUint; //Holds The Result After Searching For A Match
  WC: TWndClass; //Windows Class Structure
  dwExStyle: DWord; //Extended Windows Style
  dwStyle: DWord; //Window Style
  PFD: PixelFormatDescriptor; //Tells Windows How We Want Things To Be
  dmScreenSettings: DevMode; //Device Mode
  h_Instance: hInst; //Holds The Instance Of The Application
begin
  h_Instance := GetModuleHandle(nil); //Grab An Instance For Our Window
  GS.Fullscreen := FullScreenFlag; //Set The Global FullScreen Flag

  with WC do //can't use parentesis on "with" when using packed records
  begin
    Style := CS_HREDRAW or CS_VREDRAW or CS_OWNDC;
      //ReDraw On Size -- Own DC For Window
    lpfnWndProc := @WndProc; //WndProc Handles The Messages
    cbClsExtra := 0; //No Extra Window Data
    cbWndExtra := 0; //No Extra Window Data
    hInstance := h_Instance; //Set The Instance
    hIcon := LoadIcon(0, IDI_WINLOGO); //Load The Default Icon
    hCursor := LoadCursor(0, IDC_ARROW); //Load The Arrow Pointer
    hbrBackground := 0; //No BackGround Required For OpenGL
    lpszMenuName := nil; //We Don't Want A Menu
    lpszClassname := 'OpenGL'; //Set The Class Name
  end;

  if (RegisterClass(WC) = 0) then //Attempt To Register The Class Window
  begin
    MessageBox(0, 'Failed To Register The Window Class.', 'Error', MB_OK or
      MB_ICONERROR);
    CreateGLWindow := False;
    exit
  end;

  if (GS.FullScreen) then
  begin
    ZeroMemory(@dmScreenSettings, SizeOf(dmScreenSettings));
      //Make Sure Memory's Availiable

    with dmScreenSettings do //don't use parentesis on "with" when using packed records
    begin
      dmSize := SizeOf(dmScreenSettings); //Size Of The DevMode Structure
      dmPelsWidth := Width; //Selected Screen Width
      dmPelsHeight := Height; //Selected Screen Height
      dmBitsPerPel := Bits; //Selected Bits Per Pixel
      dmFields := DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT;
        //Try to Set Selected Mode
    end;

    if (ChangeDisplaySettings(dmScreenSettings, CDS_FULLSCREEN) <>
      DISP_CHANGE_SUCCESSFUL) then
      if (MessageBox(0,
                                'This Fullscreen Mode Is Not Supported.  Use Windowed Mode Instead?',
                                WIN_TITLE,
        MB_YESNO or MB_ICONEXCLAMATION) = IDYES) then
        GS.Fullscreen := False //Select Windowed Mode
      else
      begin
        { Show Message Box To Let User Know Program Is Ending }
        MessageBox(0, 'Program Will Now Close.', 'Error', MB_OK or MB_ICONERROR);
        CreateGLWindow := False; //Return False
        Exit
      end
  end;

  if (GS.Fullscreen) then //If Still In FullScreen Mode
  begin
    dwExStyle := WS_EX_APPWINDOW; //Entended Window Style
    dwStyle := WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN; //Window Style
    ShowCursor(False);
    PosX := 0; //reset these to zero
    PosY := 0
  end
  else
  begin
    dwExStyle := WS_EX_APPWINDOW or WS_EX_WINDOWEDGE; //Extended Window Style
    dwStyle := WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN;
      //Windows Style
  end;

  h_Wnd := CreateWindowEx(dwExStyle, //Extends Style For The Window
    'OpenGL', //Class Name
    Title, //Window Title
    dwStyle, //Window Style
    PosX, PosY, //Window Position
    Width, Height, //Selected Width and Height
    0, //No Parent Window
    0, //No Menu
    hInstance, //Instance
    nil); //Don't Pass Anything To WM_CREATE
  if (h_Wnd = 0) then
  begin //If The Window Creation Failed
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Window Creation Error.', 'Error', MB_OK or MB_ICONEXCLAMATION);
    CreateGLWindow := False;
    exit;
  end;

  with PFD do //don't use parentesis on "with" when using packed records
  begin //Tells Window How We Want Things To Be
    nSize := SizeOf(PIXELFORMATDESCRIPTOR); //Size Of This Pixel Format Descriptor
    nVersion := 1; //Version Number (?)
    dwFlags := PFD_DRAW_TO_WINDOW //Format Must Support Window
    or PFD_SUPPORT_OPENGL //Format Must Support OpenGL
    or PFD_DOUBLEBUFFER; //Must Support Double Buffering
    iPixelType := PFD_TYPE_RGBA; //Request An RGBA Format
    cColorBits := Bits; //Select Our Color Depth
    cRedBits := 0; //Color Bits Ignored
    cRedShift := 0;
    cGreenBits := 0;
    cGreenShift := 0;
    cBlueBits := 0;
    cBlueShift := 0;
    cAlphaBits := 0; //No Alpha Buffer
    cAlphaShift := 0; //Shift Bit Ignored
    cAccumBits := 0; //No Accumulation Buffer
    cAccumRedBits := 0; //Accumulation Bits Ignored
    cAccumGreenBits := 0;
    cAccumBlueBits := 0;
    cAccumAlphaBits := 0;
    cDepthBits := 16; //16 Bit Z-Buffer (Depth Buffer)
    cStencilBits := 0; //No Stencil Buffer
    cAuxBuffers := 0; //No Auxilary Buffer
    iLayerType := PFD_MAIN_PLANE; //Main Drawing Layer
    bReserved := 0; //Reserved
    dwLayerMask := 0; //Layer Masks Ignored
    dwVisibleMask := 0;
    dwDamageMask := 0;
  end;

  h_DC := GetDC(h_Wnd); //Try Getting a Device Context
  if (h_DC = 0) then // Did We Get Device Context For The Window?
  begin
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Cant''t create a GL device context.', 'Error', MB_OK or
      MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;
  PixelFormat := ChoosePixelFormat(h_Dc, @pfd);
    // Finds The Closest Match To The Pixel Format We Set Above
  if (PixelFormat = 0) then //Did We Find A Matching Pixelformat?
  begin
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Cant''t Find A Suitable PixelFormat.', 'Error', MB_OK or
      MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;
  if not (SetPixelFormat(h_Dc, PixelFormat, @pfd)) then
  begin //Are We Able To Set The Pixelformat?
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Cant''t set PixelFormat.', 'Error', MB_OK or MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;

  h_RC := wglCreateContext(h_DC); //Are We Able To create a Rendering Context?
  if (h_RC = 0) then
  begin
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Cant''t create a GL rendering context.', 'Error', MB_OK or
      MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;

  if not (wglMakeCurrent(h_DC, h_RC)) then
    //Are We Able To Activate The Rendering Context?
  begin
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Cant''t activate the GL rendering context.', 'Error', MB_OK or
      MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;

  ShowWindow(h_Wnd, SW_SHOW); //Show The Window
  SetForegroundWindow(h_Wnd); //Slightly Higher Priority
  SetFocus(h_Wnd); //Set Keyboard Focus To The Window
  ReSizeGLScene(Width, Height); //Set Up Our Perspective Gl Screen
  if not (InitGl(Width, Height)) then
    //Do all the initialization here (load textures, etc)
  begin
    KillGLWindow(); //Reset The Display
    MessageBox(0, 'Initialization Failed.', 'Error', MB_OK or MB_ICONEXCLAMATION);
    CreateGLWindow := False; //Return False
    exit;
  end;
  CreateGLWindow := True //Succes
end;

//WinMain is Main Program (gets called from the actual Main.dpr)

function WinMain(hInstance: HINST; hPrevInstance: HINST; lpCmdLine: PChar; nCmdShow:
  Integer): integer; stdcall;
var
  msg: TMsg;
begin
  if MessageBox(0, 'Would You Like To Run In FullScreen Mode?', 'Start FullScreen',
    MB_YESNO or MB_ICONQUESTION) = idNo then
    GS.Fullscreen := False
  else
    GS.Fullscreen := True;

  if not (CreateGLWindow(WIN_TITLE, POS_X, POS_Y, RES_X, RES_Y, RES_BITS,
    GS.Fullscreen)) then
  begin //Could We Create The OpenGL Window?
    Result := 0;
    exit
  end;

  while not (GS.ExitGame) do //Main Game Loop
  begin
    if (PeekMessage(msg, 0, 0, 0, PM_REMOVE)) then //Is There A Message?
    begin
      if (msg.message = WM_QUIT) then //Have We Received A Quit Message?
        GS.ExitGame := True
      else
      begin
        TranslateMessage(msg); //Translate Message
        DispatchMessage(msg); //Dispatch the Message
      end
    end
    else
      {//No messages, so keep rendering our game} if (GS.Active) and not (DrawGLScene())
      then //here's where all the fun happens
        GS.ExitGame := True
      else
        SwapBuffers(h_DC); //Not Time To Quit Yet

    //Check for keyboard input here
    if (GS.Keys[VK_ESCAPE]) then //Time To Quit
    begin
      GS.Keys[VK_ESCAPE] := False;
      GS.ExitGame := True;
    end
    else if (GS.Keys[VK_F1]) then //Toggle FullScreen Mode
    begin
      GS.Keys[VK_F1] := False;
      KillGLWindow(); //Kill Our Current Window
      GS.Fullscreen := not GS.Fullscreen; //Toggle Our Fullscreen Flag
      //Recreate Our Window
      if not CreateGLWindow(WIN_TITLE, POS_X, POS_Y, RES_X, RES_Y, RES_BITS,
        GS.Fullscreen) then
        Result := 0;
    end
  end; //While not GS.ExitGame

  { End of the Game }
  KillGLWindow(); //Shutdown
  Result := msg.wParam
end;

end.

{and here's the code for the "project source"}

program prjShell;

uses
  oglMain in 'oglMain.pas';

begin
  GS.Active := True;
  WinMain(hInstance, hPrevInst, CmdLine, CmdShow);
end.

Nincsenek megjegyzések:

Megjegyzés küldése