1.1 Hello Window - neslib/DelphiLearnOpenGL GitHub Wiki

:link: Source Code

Create new application

Let's see if we can get our first app up and running. The easiest way is to use an existing application as a template by copying over the source code from the repository and renaming the project. But if you want to start from scratch, then follow these steps:

  • First, open Delphi and create a new Multi-Device Application.
  • Choose the Blank Application template.
  • Remove the automatically generated form from the project (eg. Unit1.pas). Don't save the form.
  • Open the project source code (Project | View Source).
  • Remove the uses clause and everything between begin and end. In particular, we don't want any references to FMX units.
  • Make sure you have downloaded or cloned the Tutorials/Common directory from the repository. Add this directory to the search path of your application, for all configurations. (Go to "Project | Options... | Delphi Compiler" and select the Target "All configurations - All platforms". Then add the directory to the "Search path" option).

Create application class

Create a new unit called App and create a new class derived from TApplication (from the unit Sample.App). We must override the abstract methods Initialize, Update and Shutdown. We also override the KeyDown method so we can terminate the app when the user presses the Esc key.

type
  THelloWindow = class(TApplication)
  public
    procedure Initialize; override;
    procedure Update(const ADeltaTimeSec, ATotalTimeSec: Double); override;
    procedure Shutdown; override;
    procedure KeyDown(const AKey: Integer; const AShift: TShiftState); override;
  end;

This first tutorial doesn't create any OpenGL resources, so we can leave the implementations of the Initialize and Shutdown methods empty. The key method to override is Update, which is called once every frame to update application state and render a frame:

procedure THelloWindow.Update(const ADeltaTimeSec, ATotalTimeSec: Double);
begin
  { Define the viewport dimensions }
  glViewport(0, 0, Width, Height);

  { Render by simply clearing the color buffer }
  glClearColor(0.2, 0.3, 0.3, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);
end;

This method is explained below:

Viewport

Before we can start rendering we have to tell OpenGL the size of the rendering window so OpenGL knows how we want to display the data and coordinates with respect to the window. We can set those dimensions via the glViewport function:

glViewport(0, 0, Width, Height);

The first two parameters of glViewport set the location of the lower left corner of the window. The third and fourth parameter set the width and height of the rendering window in pixels, which are properties of the TApplication class. On mobile devices, these properties are set to the dimensions of the device and automatically updated when the user rotates the device. On desktop platforms, these properties are initialized to the size of the client area of the window that is created.

We could actually set the viewport dimensions at values smaller than the window dimensions; then all the OpenGL rendering would be displayed in a smaller area and we could for example display other elements outside the OpenGL viewport.

:information_source: Behind the scenes OpenGL uses the data specified via glViewport to transform the 2D coordinates it processed to coordinates on your screen. For example, a processed point of location (-0.5,0.5) would (as its final transformation) be mapped to (200,450) in screen coordinates. Note that processed coordinates in OpenGL are between -1 and 1 so we effectively map from the range (-1 to 1) to (0, 800) and (0, 600).

Rendering

We want to place all the rendering commands in the game loop, since we want to execute all the rendering commands each iteration of the loop. So all rendering must occur inside the Update method (or other methods that are called from the Update method).

Just to test if things actually work we want to clear the screen with a color of our choice. At the start of each render iteration we always want to clear the screen otherwise we would still see the results from the previous iteration (this could be the effect you're looking for, but usually you don't). We can clear the screen's color buffer using the glClear function where we pass in buffer bits to specify which buffer we would like to clear. The possible bits we can set are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT. Right now we only care about the color values so we only clear the color buffer:

glClearColor(0.2, 0.3, 0.3, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

Note that we also set a color via glClearColor to clear the screen with. Whenever we call glClear and clear the color buffer, the entire colorbuffer will be filled with the color as configured by glClearColor. This will result in a dark green-blueish color.

:information_source: As you might recall from the OpenGL tutorial, the glClearColor function is a state-setting function and glClear is a state-using function in that it uses the current state to retrieve the clearing color from.

Image of window with glClearColor defined

Input

We also want to have some form of input control. This is accomplished by overriding one or more event methods from TApplication, like MouseDown, MouseMove, KeyDown etc. In this tutorial, we override KeyDown to terminate the app when the Esc key is pressed:

procedure THelloWindow.KeyDown(const AKey: Integer; const AShift: TShiftState);
begin
  if (AKey = vkEscape) then
    Terminate;
end;

App Source Code

To recap, the complete source code of the App unit looks like this:

unit App;

{$INCLUDE 'Sample.inc'}

interface

uses
  System.Classes,
  Sample.App;

type
  THelloWindow = class(TApplication)
  public
    procedure Initialize; override;
    procedure Update(const ADeltaTimeSec, ATotalTimeSec: Double); override;
    procedure Shutdown; override;
    procedure KeyDown(const AKey: Integer; const AShift: TShiftState); override;
  end;

implementation

uses
  {$INCLUDE 'OpenGL.inc'}
  System.UITypes;

{ THelloWindow }

procedure THelloWindow.Initialize;
begin
  { Not needed in this sample }
end;

procedure THelloWindow.KeyDown(const AKey: Integer; const AShift: TShiftState);
begin
  { Terminate app when Esc key is pressed }
  if (AKey = vkEscape) then
    Terminate;
end;

procedure THelloWindow.Shutdown;
begin
  { Not needed in this sample }
end;

procedure THelloWindow.Update(const ADeltaTimeSec, ATotalTimeSec: Double);
begin
  { Define the viewport dimensions }
  glViewport(0, 0, Width, Height);

  { Render by simply clearing the color buffer }
  glClearColor(0.2, 0.3, 0.3, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);
end;

end.

There are a few things to note here:

  • The unit includes the file Sample.inc. This include file contains some common defines that are used in all tutorials. For example, it sets the $SCOPEDENUMS option and defines the conditional MOBILE on iOS and Android platforms. You should include this file in all tutorial sources.
  • In the implementation section, you see another include file in the uses clause called OpenGL.inc. This include files adds the correct OpenGL units to the uses clause depending on platform. For example, on Windows, OpenGL APIs are declared in the units Winapi.OpenGL and Winapi.OpenGLExt and on iOS they are defined in iOSapi.OpenGLES. By putting these platform differences in an include file, it keeps your uses clause clean and simple.

So right now we got everything ready to fill the game loop with lots of rendering calls, but that's for the [next tutorial](1.2 Hello Triangle). I think we've been rambling long enough here.

:arrow_left: [Creating an OpenGL App](Creating an OpenGL App) Contents [1.2 Hello Triangle](1.2 Hello Triangle) :arrow_right: