-
Notifications
You must be signed in to change notification settings - Fork 16
Creating an OpenGL App
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.
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 theInitialize
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.
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.
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 theCreateWindowEx
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
andSetPixelFormat
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
andwglMakeCurrent
are used to create and activate the OpenGL context. After this, all OpenGL calls will apply to the just created context and window.
- We use the
- 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
andDispatchMessage
APIs. This processes all pending messages (by callingTPlatformWindows.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.
- The message pump is processed using the
- 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.
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 theNSApplicationDelegate
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 usingNSApplication.finishLaunching
.
- This delegate gets called when application-level events occur. For example, we implement its
- We create a window of type
NSWindow
. - Define a
TWindowDelegate
class that implements theNSWindowDelegate
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
.
- This delegate gets called when window-level events occur. For example, we implement its
- 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 usingNSWindow.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 usingNSOpenGLView.setOpenGLContext
and activated usingNSOpenGLContext.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 untilnil
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.
- It calls
- When the application is terminated,
PlatformMac.Shutdown
is called to destroy the OpenGL context, window and view.
TBD
TBD
⬅️ OpenGL (ES) | Contents | [1.1 Hello Window](1.1 Hello Window) ➡️ |
---|
Learn OpenGL(ES) with Delphi
-
- 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)
-
- 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)
-
- Model Loading
- [3.1 OBJ Files](3.1 OBJ Files)
- [3.2 Mesh](3.2 Mesh)
- [3.3 Model](3.3 Model)
-
- 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)
-
- 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)
-
- PBR
-
- In Practice
- [7.1 Debugging](7.1 Debugging)
- [Text Rendering](Text Rendering)
- [2D Game](2D Game)
- Breakout
- [Setting Up](Setting Up)
- [Rendering Sprites](Rendering Sprites)
- Levels
-
Collisions
- Ball
- [Collision Detection](Collision Detection)
- [Collision Resolution](Collision Resolution)
- Particles
- Postprocessing
- Powerups
- Audio
- [Render Text](Render Text)
- [Final Thoughts](Final Thoughts)