Skip to content

Creating an OpenGL App

Erik van Bilsen edited this page Dec 20, 2016 · 4 revisions

The first thing we need to do to create stunning graphics is to create an OpenGL context and an application window to draw in. However, those operations are specific per operating system and OpenGL purposefully tries to abstract from these operations. This means we have to create a window, define a context and handle user input all by ourselves.

Application Framework

To facilitate this, we create a simple application framework in Delphi. This framework is very small and lightweight, and nothing as complex as FireMonkey or VCL at all. It is a good exercise in creating your own framework though, and you can easily build on top of it.

All framework classes are located in the directory \Tutorials\Common. It's main class is TApplication defined in the unit Sample.App. This is an abstract base class which each tutorial derives from:

type
  TApplication = class abstract
  public
    constructor Create(const AWidth, AHeight: Integer; const ATitle: String); virtual;

    procedure Initialize; virtual; abstract;
    procedure Update(const ADeltaTimeSec, ATotalTimeSec: Double); virtual; abstract;
    procedure Shutdown; virtual; abstract;

    procedure MouseDown(const AButton: TMouseButton; const AShift: TShiftState;
      const AX, AY: Single); virtual;
    procedure MouseMove(const AShift: TShiftState; const AX, AY: Single); virtual;
    procedure MouseUp(const AButton: TMouseButton; const AShift: TShiftState;
      const AX, AY: Single); virtual;

    ...
  end;

You must override the methods Initialize, Update and Shutdown:

  • Initialize is where you create and load you OpenGL resources like buffers, textures, shaders etc.
  • Update is called for every frame (about 60 times per second) to update the application state and render a new frame.
  • Shutdown is where you clean up the app and release any resources that you created in the Initialize method.

You can optionally override event methods like MouseDown, KeyUp etc., which are called whenever such an event is generated.

The next tutorial shows how to create the simplest OpenGL application using this class.

TPlatform Class

We encapsulate all operating system specific operations, in an abstract static base class TPlatformBase in the unit Sample.Platform. This is a static class because it only defined class methods and class properties. It defines a single virtual method DoRun that each OS-specific subclass overrides:

type
  { Static base class for platform-specific functionality. For every platform
    (Windows, MacOS, iOS and Android), there is a derived class. }
  TPlatformBase = class abstract // static
  protected
    { Must be overridden to run the application. }
    class procedure DoRun; virtual; abstract;
  public
    { Runs the application. }
    class procedure Run(const AApp: TApplication); static;

    ...
  end;
  TPlaformClass = class of TPlatformBase;

var
  { Actual platform class. For example, on Windows this will be TPlatformWindows }
  TPlatform: TPlaformClass = nil;

You actually use the TPlatform variable, which is set to the actual class type used, depending on operating system. So, to run an application, you can write something like TPlatform.Run(MyApplication). More on this in the next tutorial.

For each operating system, there is an additional unit (for example Sample.Platform.Windows) that defines the actual platform class (like TPlatformWindows).

Below I will present some summaries of what is needed to create an OpenGL context and app on each platform. I don't delve into the details to much since these tutorials are about learing OpenGL and not about learning how to create an application framework.

Feel free to skip the rest of this page and go to the next tutorial if you are not interested in the platform specifics and want to focus on OpenGL only.

If you are interested, then please keep the (documented) source code at hand since the text below refers to the relevant methods.

Windows

The DoRun method creates a window, OpenGL context and runs the Windows message pump:

  • TPlatformWindows.RegisterWindowClass is called to register a window class. The most important thing here is to set the window procedure to a method that called to handle window messages (TPlatformWindows.WndProc).
  • TPlatformWindows.SetupWindow first creates a top-level window (using the CreateWindowEx API) and shows it centered on screen.
  • TPlatformWindows.SetupOpenGLContext is called to create an OpenGL context for the window just created.
    • We use the ChoosePixelFormat and SetPixelFormat APIs to configure the rendering surface. In particular, we create an RGBA surface using 24-bits per pixel and a depth buffer of 16 bits. We enable OpenGL drawing and double buffering on the surface. (If some of these terms are unfamiliar to you: don't worry, they will be explained in later tutorials).
    • Finally, wglCreateContext and wglMakeCurrent are used to create and activate the OpenGL context. After this, all OpenGL calls will apply to the just created context and window.
  • The run loop (TPlatformWindows.RunLoop) runs the message pump and updates the app continuously until the app is terminated.
    • The message pump is processed using the PeekMessage, TranslateMessage and DispatchMessage APIs. This processes all pending messages (by calling TPlatformWindows.WndProc until the message queue is empty).
    • Then the application is updated and a frame is rendered by calling the virtual TApplication.Update method (which you override in your own apps).
    • Finally, SwapBuffers is called to swap the front and back buffer and display it. By default, this happens on the Vertical Sync (VSync) interval of your display to minimize tearing effects. This means that the screen will be updated at most 60 (usually) times per second. SwapBuffers will wait until the next VSync interval occurs.
  • When the application is terminated, PlatformWindows.Shutdown is called to destroy the OpenGL context and window.

As a side note, note that your application may be waiting a lot as a result of the SwapBuffers call. You cannot use that time to do other things (like running application logic, physics emulation etc). So in a real application, you may want to improve this by either:

  • Run all application logic in a separate thread.
  • Or perform all rendering in a separate thread. In that case, your OpenGL context and all OpenGL resources must be created and bound in that thread.

macOS

On macOS you need to do a lot more. You need to create an application delegate, window, window delegate, view and OpenGL context. Many of these are either Objective-C objects that you use (like the window and view), or Objective-C protocols that you implement (like the application and window delegates).

  • Define a TApplicationDelegate class that implements the NSApplicationDelegate protocol.
    • This delegate gets called when application-level events occur. For example, we implement its applicationShouldTerminate method to break out of the run loop.
    • We attach the delegate to the shared application (NSApplication.sharedApplication.setDelegate) and start the application using NSApplication.finishLaunching.
  • We create a window of type NSWindow.
  • Define a TWindowDelegate class that implements the NSWindowDelegate protocol.
    • This delegate gets called when window-level events occur. For example, we implement its windowShouldClose method the terminate the app when the window is closed.
    • We attach the delegate to the window we just created using NSWindow.setDelegate.
  • We create a view of type NSOpenGLView. This view type is optimized for OpenGL rendering. We client-align it to the window and add it to the window using NSWindow.contentView.addSubview.
  • We create an OpenGL context of type NSOpenGLContext, and configure it for double buffering and a 16-bit depth buffer. The context is attached to the view using NSOpenGLView.setOpenGLContext and activated using NSOpenGLContext.makeCurrentContext. After this, all OpenGL calls will apply to the just created context and view.
  • The run loop (TPlatformMac.RunLoop) handles all pending messages:
    • It calls NSApplication.nextEventMatchingMask to continuously remove events from the event queue until nil is returned.
    • Each event is handled in TPlatformMac.DispatchEvent, which converts the event to a cross-platform event that is passed to the application.
    • Then the application is updated and a frame is rendered by calling the virtual TApplication.Update method (which you override in your own apps).
    • Finally, NSOpenGLContext.flushBuffer is called to swap the front and back buffer and display it. Again, this happens on the VSync interval.
  • When the application is terminated, PlatformMac.Shutdown is called to destroy the OpenGL context, window and view.

iOS

TBD

Android

TBD

⬅️ OpenGL (ES) Contents [1.1 Hello Window](1.1 Hello Window) ➡️

Learn OpenGL (ES) with Delphi

    1. Getting Started
    • OpenGL (ES)
    • [Creating an OpenGL App](Creating an OpenGL App)
    • [1.1 Hello Window](1.1 Hello Window)
    • [1.2 Hello Triangle](1.2 Hello Triangle)
    • [1.3 Shaders](1.3 Shaders)
    • [1.4 Textures](1.4 Textures)
    • [1.5 Transformations](1.5 Transformations)
    • [1.6 Coordinate Systems](1.6 Coordinate Systems)
    • [1.7 Camera](1.7 Camera)
    • [Review](Getting Started Review)
    1. Lighting
    • [2.1 Colors](2.1 Colors)
    • [2.2 Basic Lighting](2.2 Basic Lighting)
    • [2.3 Materials](2.3 Materials)
    • [2.4 Lighting Maps](2.4 Lighting Maps)
    • [2.5 Light Casters](2.5 Light Casters)
    • [2.6 Multiple Lights](2.6 Multiple Lights)
    • [Review](Lighting Review)
    1. Model Loading
    • [3.1 OBJ Files](3.1 OBJ Files)
    • [3.2 Mesh](3.2 Mesh)
    • [3.3 Model](3.3 Model)
    1. Advanced OpenGL
    • [4.1 Depth Testing](4.1 Depth Testing)
    • [4.2 Stencil Testing](4.2 Stencil Testing)
    • [4.3 Blending](4.3 Blending)
    • [4.4 Face Culling](4.4 Face Culling)
    • [4.5 Framebuffers](4.5 Framebuffers)
    • [4.6 Cubemaps](4.6 Cubemaps)
    • [4.7 Advanced Data](4.7 Advanced Data)
    • [4.8 Advanced GLSL](4.8 Advanced GLSL)
    • [4.9 Geometry Shader](4.9 Geometry Shader)
    • 4.10Instancing
    • [4.11 Anti Aliasing](4.11 Anti Aliasing)
    1. Advanced Lighting
    • [5.1 Advanced Lighting](5.1 Advanced Lighting)
    • [5.2 Gamma Correction](5.2 Gamma Correction)
    • [5.3 Shadows](5.3 Shadows)
      • [5.3.1 Shadow Mapping](5.3.1 Shadow Mapping)
      • [5.3.2 Point Shadows](5.3.2 Point Shadows)
      • [5.3.3 CSM](5.3.3 CSM)
    • [5.4 Normal Mapping](5.4 Normal Mapping)
    • [5.5 Parallax Mapping](5.5 Parallax Mapping)
    • [5.6 HDR](5.6 HDR)
    • [5.7 Bloom](5.7 Bloom)
    • [5.8 Deferred Shading](5.8 Deferred Shading)
    • [5.9 SSAO](5.9 SSAO)
    1. PBR
    • Theory
    • [6.1 Lighting](6.1 Lighting)
    • IBL
      • [Diffuse Irradiance](Diffuse Irradiance)
      • [Specular IBL](Specular IBL)
    1. In Practice
    • [7.1 Debugging](7.1 Debugging)
    • [Text Rendering](Text Rendering)
    • [2D Game](2D Game)
Clone this wiki locally