PDA

View Full Version : Use OpenGL from TThread with delphi2005?



noeska
04-04-2008, 05:24 PM
Delphi 2005 win32
DGLOpenGL
TThread
-------------------------------------------------------------------------------------

I am trying to use opengl from an thread. Unfortunately all i get is a black screen. All code seems to execute. Thanks for your help in advance.



unit OpenGLTemplateForm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, DglOpenGL;

type
TDGLForm = class(TForm)
procedure FormKeyPress(Sender: TObject; var Key: Char);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

TOpenGLRender = class(TThread)
public
destructor Destroy; override;
procedure Init(aHandle: cardinal);
procedure Draw;
procedure Stop;
procedure Execute; override;
end;

var
DGLForm: TDGLForm;
OpenGLRender: TOpenGLRender;
DC:HDC;
RC:HGLRC;
angle: integer;


implementation

{$R *.DFM}

function getNormal(p1,p2,p3:TGLArrayf3):TGLArrayf3;
var a,b:TGLArrayf3;
begin
a[0]:=p2[0]-p1[0]; a[1]:=p2[1]-p1[1]; a[2]:=p2[2]-p1[2];
b[0]:=p3[0]-p1[0]; b[1]:=p3[1]-p1[1]; b[2]:=p3[2]-p1[2];
result[0]:=a[1]*b[2]-a[2]*b[1];
result[1]:=a[2]*b[0]-a[0]*b[2];
result[2]:=a[0]*b[1]-a[1]*b[0];
end;

//TOpenGLRender

destructor TOpenGLRender.Destroy;
begin
inherited;
end;

procedure TOpenGLRender.Execute;
begin
while not terminated do
begin
Draw;
sleep(1);
end;
end;

procedure TOpenGLRender.Init(aHandle: cardinal);
const
light0_position:TGLArrayf4=( -8.0, 8.0, -16.0, 0.0);
ambient: TGLArrayf4=( 0.3, 0.3, 0.3, 0.3);
begin

InitOpenGL; // Initialize DglOpenGL

DC := GetDC(aHandle);
// Create RenderContext (32 Bit Pixel, 24 Bit DepthBuffer, Doublebuffering)
RC := CreateRenderingContext(DC, [opDoubleBuffered], 32, 24, 0, 0, 0, 0);
// Activate RenderContext
ActivateRenderingContext(DC, RC);

// set viewing projection
glMatrixMode(GL_PROJECTION);
glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);

// position viewer
glMatrixMode(GL_MODELVIEW);

// Active DepthBuffer
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

// Set lighting
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_POSITION, @light0_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient);
glEnable(GL_LIGHT0);

// Set clear background color
glClearColor(0,0,0,0);
end;

procedure TOpenGLRender.Draw;
const
D=1.5;
H1=D/1.732;
H2=D*1.732-H1; // D/H = tg(30) = 1/sqrt(3)
HY=3.0;
//vertexes
a1:TGLArrayf3=(-D, 0, -H1);
a2:TGLArrayf3=(D, 0, -H1);
a3:TGLArrayf3=(0, 0, H2);
a4:TGLArrayf3=(0, HY, 0);
var
n1, n2, n3, n4: TGLArrayf3; //normals
begin
angle:=angle+1;
n1 := getNormal(a1,a3,a2);
n2 := getNormal(a1,a2,a4);
n3 := getNormal(a2,a3,a4);
n4 := getNormal(a3,a1,a4);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glEnable(GL_NORMALIZE);
glShadeModel(GL_FLAT);
glCullFace(GL_BACK);
glLoadIdentity;
glTranslatef(0.0, 0.0, -12.0);
glRotatef(angle, 0.0, 1.0, 0.0);
glBegin(GL_TRIANGLES);
glNormal3fv(@n1);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a3);
glNormal3fv(@n2);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a4);
glNormal3fv(@n3);
glVertex3fv(@a2); glVertex3fv(@a3); glVertex3fv(@a4);
glNormal3fv(@n4);
glVertex3fv(@a3); glVertex3fv(@a1); glVertex3fv(@a4);
glEnd;
SwapBuffers(DC);
end;

procedure TOpenGLRender.Stop;
begin
end;

//TDGLForm

procedure TDGLForm.FormCreate(Sender: TObject);
begin
DecimalSeparator:='.'; //always use . as decimal seperator
OpenGLRender := TOpenGLRender.Create(true);
OpenGLRender.Init(Handle);
OpenGLRender.Resume;
end;

procedure TDGLForm.FormDestroy(Sender: TObject);
begin
OpenGLRender.Suspend;
OpenGLRender.Free;
DeactivateRenderingContext; // Deactivate RenderContext
wglDeleteContext(RC); //Delete RenderContext
ReleaseDC(Handle, DC);
end;

procedure TDGLForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
case Key of
#27 : Close;
end;
end;

end.

AthenaOfDelphi
04-04-2008, 06:16 PM
Hi noeska,

I'm not 100% sure of the reason why its not working as I haven't played around with multi-threaded rendering, but I think it has something to do with the creation of the rendering context.

As your code is laid out at the moment, the init routine is called within the context of the main VCL thread, not the rendering thread.

Maybe what you should do, is store the handle as a property and call Init as the first thing you do in the execute method. Then the rendering context will be created in the context of the thread thats trying to use it.

Like I say, I'm not 100% sure as I haven't played around that much with multi-threaded rendering. At the very least, its worth a shot as its not a massive change.... if it doesn't work, then I'm wrong and you've not wasted too much time.

If you do try this, please let me know how you get on.

AthenaOfDelphi
04-04-2008, 06:31 PM
In fact... I've just tried it... dglOpenGL on BDS 2006, and making the changes I suggested seems to work.

Here's the unit I ended up with....


unit unitOpenGLTemplateForm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, DglOpenGL;

type
TdglForm = class(TForm)
procedure FormKeyPress(Sender: TObject; var Key: Char);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

TOpenGLRender = class(TThread)
protected
fTargetHandle : THandle;

fDC : HDC;
fRC : HGLRC;

procedure Execute; override;

procedure init;
procedure deinit;
public
destructor Destroy; override;

procedure Draw;

property targetHandle:THandle read fTargetHandle write fTargetHandle;

end;

var
dglForm: TdglForm;

OpenGLRender: TOpenGLRender;
angle: integer;


implementation

{$R *.DFM}

function getNormal(p1,p2,p3:TGLArrayf3):TGLArrayf3;
var a,b:TGLArrayf3;
begin
a[0]:=p2[0]-p1[0]; a[1]:=p2[1]-p1[1]; a[2]:=p2[2]-p1[2];
b[0]:=p3[0]-p1[0]; b[1]:=p3[1]-p1[1]; b[2]:=p3[2]-p1[2];
result[0]:=a[1]*b[2]-a[2]*b[1];
result[1]:=a[2]*b[0]-a[0]*b[2];
result[2]:=a[0]*b[1]-a[1]*b[0];
end;

//TOpenGLRender

destructor TOpenGLRender.Destroy;
begin
inherited;
end;

procedure TOpenGLRender.Execute;
begin
init;

while not terminated do
begin
Draw;
sleep(1);
end;

deinit;
end;

procedure TOpenGLRender.deinit;
begin
DeactivateRenderingContext; // Deactivate RenderContext
wglDeleteContext(fRC); //Delete RenderContext
ReleaseDC(fTargetHandle, fDC);
end;

procedure TOpenGLRender.Init;
const
light0_position:TGLArrayf4=( -8.0, 8.0, -16.0, 0.0);
ambient: TGLArrayf4=( 0.3, 0.3, 0.3, 0.3);
begin

InitOpenGL; // Initialize DglOpenGL

fDC := GetDC(fTargetHandle);
// Create RenderContext (32 Bit Pixel, 24 Bit DepthBuffer, Doublebuffering)
fRC := CreateRenderingContext(fDC, [opDoubleBuffered], 32, 24, 0, 0, 0, 0);
// Activate RenderContext
ActivateRenderingContext(fDC, fRC);

// set viewing projection
glMatrixMode(GL_PROJECTION);
glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);

// position viewer
glMatrixMode(GL_MODELVIEW);

// Active DepthBuffer
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

// Set lighting
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_POSITION, @light0_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient);
glEnable(GL_LIGHT0);

// Set clear background color
glClearColor(0,0,0,0);
end;

procedure TOpenGLRender.Draw;
const
D=1.5;
H1=D/1.732;
H2=D*1.732-H1; // D/H = tg(30) = 1/sqrt(3)
HY=3.0;
//vertexes
a1:TGLArrayf3=(-D, 0, -H1);
a2:TGLArrayf3=(D, 0, -H1);
a3:TGLArrayf3=(0, 0, H2);
a4:TGLArrayf3=(0, HY, 0);
var
n1, n2, n3, n4: TGLArrayf3; //normals
begin
angle:=angle+1;
n1 := getNormal(a1,a3,a2);
n2 := getNormal(a1,a2,a4);
n3 := getNormal(a2,a3,a4);
n4 := getNormal(a3,a1,a4);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glEnable(GL_NORMALIZE);
glShadeModel(GL_FLAT);
glCullFace(GL_BACK);
glLoadIdentity;
glTranslatef(0.0, 0.0, -12.0);
glRotatef(angle, 0.0, 1.0, 0.0);
glBegin(GL_TRIANGLES);
glNormal3fv(@n1);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a3);
glNormal3fv(@n2);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a4);
glNormal3fv(@n3);
glVertex3fv(@a2); glVertex3fv(@a3); glVertex3fv(@a4);
glNormal3fv(@n4);
glVertex3fv(@a3); glVertex3fv(@a1); glVertex3fv(@a4);
glEnd;
SwapBuffers(fDC);
end;



procedure TdglForm.FormCreate(Sender: TObject);
begin
DecimalSeparator:='.'; //always use . as decimal seperator
OpenGLRender := TOpenGLRender.Create(true);
OpenGLRender.targetHandle:=self.handle;
OpenGLRender.Resume;
end;

procedure TdglForm.FormDestroy(Sender: TObject);
begin
OpenGLRender.Terminate;
OpenGLRender.WaitFor;
OpenGLRender.Free;
end;

procedure TdglForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
case Key of
#27 : Close;
end;
end;

end.

noeska
04-04-2008, 06:39 PM
Yes it works i just finished modifying my version the way you suggested and it works. Thanks.

noeska
04-04-2008, 06:55 PM
Should i use:


OpenGLRender.Terminate;
OpenGLRender.WaitFor;
OpenGLRender.Free;


or



OpenGLRender.Suspend;
OpenGLRender.Free;

AthenaOfDelphi
04-04-2008, 08:04 PM
There are a multitude of ways you can handle it. You can set the 'freeOnTerminate' property and then just terminate it... it will free it up itself. You can do what I've done... you can do what you've done (I think... the reason I say I think, is because I'm not sure how free handles suspended threads - whether it resumes them or not)

I think its very much a personal preference... though having said that, I use different methods for different situations, so just tinker... you'll find a solution thats right for you and the situation you are using the threads in.