I am trying to create a custom control that will essentially be a OpenGL Window.
I have it all setup and working (at least it seems to be) with the help of some guides to setup the pixel format etc, however I notice when I resize the parent Form the OpenGL graphics become scaled / stretched.
To illustrate this, the following image is how it should appear:
After the Form has been resized, it now appears as below for example:
Disregard the OSD at the top as this is part of the screen recorder Software I use which also distorts.
Here I have added a Gif to better demonstrate what it happening when the Form is resized:
Here is the unit for my custom control:
unit OpenGLControl;
interface
uses
Winapi.Windows,
System.SysUtils,
System.Classes,
Vcl.Controls;
type
TOpenGLControl = class(TCustomControl)
private
FDC: HDC;
FRC: HGLRC;
FOnPaint: TNotifyEvent;
protected
procedure SetupPixelFormat;
procedure GLInit;
procedure GLRelease;
procedure CreateHandle; override;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
end;
implementation
uses
OpenGL;
{ TOpenGLControl }
constructor TOpenGLControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TOpenGLControl.Destroy;
begin
GLRelease;
inherited Destroy;
end;
procedure TOpenGLControl.CreateHandle;
begin
inherited;
GLInit;
end;
procedure TOpenGLControl.SetupPixelFormat;
var
PixelFormatDescriptor: TPixelFormatDescriptor;
pfIndex: Integer;
begin
with PixelFormatDescriptor do
begin
nSize := SizeOf(TPixelFormatDescriptor);
nVersion := 1;
dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
iPixelType := PFD_TYPE_RGBA;
cColorBits := 32;
cRedBits := 0;
cRedShift := 0;
cGreenBits := 0;
cGreenShift := 0;
cBlueBits := 0;
cBlueShift := 0;
cAlphaBits := 0;
cAlphaShift := 0;
cAccumBits := 0;
cAccumRedBits := 0;
cAccumGreenBits := 0;
cAccumBlueBits := 0;
cAccumAlphaBits := 0;
cDepthBits := 16;
cStencilBits := 0;
cAuxBuffers := 0;
iLayerType := PFD_MAIN_PLANE;
bReserved := 0;
dwLayerMask := 0;
dwVisibleMask := 0;
dwDamageMask := 0;
end;
pfIndex := ChoosePixelFormat(FDC, @PixelFormatDescriptor);
if pfIndex = 0 then Exit;
if not SetPixelFormat(FDC, pfIndex, @PixelFormatDescriptor) then
raise Exception.Create('Unable to set pixel format.');
end;
procedure TOpenGLControl.GLInit;
begin
FDC := GetDC(Handle);
if FDC = 0 then Exit;
SetupPixelFormat;
FRC := wglCreateContext(FDC);
if FRC = 0 then Exit;
if not wglMakeCurrent(FDC, FRC) then
raise Exception.Create('Unable to initialize.');
end;
procedure TOpenGLControl.GLRelease;
begin
wglMakeCurrent(FDC, 0);
wglDeleteContext(FRC);
ReleaseDC(Handle, FDC);
end;
procedure TOpenGLControl.Paint;
begin
inherited;
if Assigned(FOnPaint) then
begin
FOnPaint(Self);
end;
end;
end.
To test, create a new Application and add a TPanel
to the Form, also create the Forms OnCreate
and OnDestroy
event handlers then use the following:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, OpenGLControl;
type
TForm1 = class(TForm)
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure OpenGLControlPaint(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
FOpenGLControl: TOpenGLControl;
implementation
uses
OpenGL;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FOpenGLControl := TOpenGLControl.Create(nil);
FOpenGLControl.Parent := Panel1;
FOpenGLControl.Align := alClient;
FOpenGLControl.Visible := True;
FOpenGLControl.OnPaint := OpenGLControlPaint;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FOpenGLControl.Free;
end;
procedure TForm1.OpenGLControlPaint(Sender: TObject);
begin
glViewPort(0, 0, FOpenGLControl.Width, FOpenGLControl.Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor3f(0.60, 0.10, 0.35);
glVertex3f( 0.0, 1.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glEnd;
SwapBuffers(wglGetCurrentDC);
end;
end.
Interestingly setting the parent of FOpenGLControl
to the Form seems to work as expected, eg:
procedure TForm1.FormCreate(Sender: TObject);
begin
FOpenGLControl := TOpenGLControl.Create(nil);
FOpenGLControl.Parent := Form1;
FOpenGLControl.Align := alClient;
FOpenGLControl.Visible := True;
FOpenGLControl.OnPaint := OpenGLControlPaint;
end;
It's important to know I have limited knowledge with OpenGL and most of this is new to me, I am unsure if this is something to do with setting the view port of the window which I thought I had done but maybe the issue lies elsewhere or I did something incorrectly.
So my question is, How do I correctly render OpenGL inside a control without it stretching / distorting when the parent windows resizes?
Thank you.
Update 1
procedure TForm1.FormResize(Sender: TObject);
var
Aspect: Single;
begin
glViewPort(0, 0, FOpenGLControl.Width, FOpenGLControl.Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Aspect := Real(FOpenGLControl.Width) / Real(FOpenGLControl.Height);
glOrtho(-Aspect, Aspect, -1.0, 1.0, -1.0, 1.0);
end;
procedure TForm1.OpenGLControlPaint(Sender: TObject);
begin
glBegin(GL_TRIANGLES);
glColor3f(0.60, 0.10, 0.35);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glEnd;
SwapBuffers(wglGetCurrentDC);
end;
The above works but only when the parent is aligned to client, in this example when Panel1
is aligned to client. When the Panel is not aligned it distorts when the window is resized.