mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 09:43:42 +02:00
465 lines
14 KiB
ObjectPascal
465 lines
14 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
* *
|
|
* See the file COPYING.modifiedLGPL, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
|
|
Abstract:
|
|
TOpenGLControl is a LCL control with an opengl context.
|
|
It works under the following platforms:
|
|
- gtk with glx : full
|
|
- gtk2 with glx : full
|
|
- carbon with agl : full
|
|
- windows with wgl: full
|
|
}
|
|
unit OpenGLContext;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{$IFDEF LCLGTK}
|
|
{$DEFINE UseGtkGLX}
|
|
{$DEFINE OpenGLTargetDefined}
|
|
{$ENDIF}
|
|
{$IFDEF LCLGTK2}
|
|
{$DEFINE UseGtk2GLX}
|
|
{$DEFINE OpenGLTargetDefined}
|
|
{$ENDIF}
|
|
{$IFDEF LCLCarbon}
|
|
{$DEFINE UseCarbonAGL}
|
|
{$DEFINE OpenGLTargetDefined}
|
|
{$ENDIF}
|
|
{$IFDEF LCLWin32}
|
|
{$DEFINE UseWin32WGL}
|
|
{$DEFINE OpenGLTargetDefined}
|
|
{$ENDIF}
|
|
{$IFNDEF OpenGLTargetDefined}
|
|
{$ERROR this target is not yet supported}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc, Forms, Controls, LCLType, LCLIntf, LResources,
|
|
Graphics, LMessages, WSLCLClasses, WSControls,
|
|
{$IFDEF UseGtkGLX}
|
|
GLGtkGlxContext;
|
|
{$ENDIF}
|
|
{$IFDEF UseGtk2GLX}
|
|
GLGtkGlxContext;
|
|
{$ENDIF}
|
|
{$IFDEF UseCarbonAGL}
|
|
GLCarbonAGLContext;
|
|
{$ENDIF}
|
|
{$IFDEF UseWin32WGL}
|
|
GLWin32WGLContext;
|
|
{$ENDIF}
|
|
|
|
|
|
type
|
|
TOpenGlCtrlMakeCurrentEvent = procedure(Sender: TObject;
|
|
var Allow: boolean) of object;
|
|
|
|
{ TCustomOpenGLControl }
|
|
{ Sharing:
|
|
You can share opengl contexts. For example:
|
|
Assume OpenGLControl2 and OpenGLControl3 should share the same as
|
|
OpenGLControl1. Then set
|
|
|
|
OpenGLControl2.SharedControl:=OpenGLControl1;
|
|
OpenGLControl3.SharedControl:=OpenGLControl1;
|
|
|
|
After this OpenGLControl1.SharingControlCount will be two and
|
|
OpenGLControl1.SharingControls will contain OpenGLControl2 and
|
|
OpenGLControl3.
|
|
}
|
|
|
|
TCustomOpenGLControl = class(TWinControl)
|
|
private
|
|
FAutoResizeViewport: boolean;
|
|
FCanvas: TCanvas; // only valid at designtime
|
|
FDoubleBuffered: boolean;
|
|
FFrameDiffTime: integer;
|
|
FOnMakeCurrent: TOpenGlCtrlMakeCurrentEvent;
|
|
FOnPaint: TNotifyEvent;
|
|
FCurrentFrameTime: integer; // in msec
|
|
FLastFrameTime: integer; // in msec
|
|
FRGBA: boolean;
|
|
FSharedOpenGLControl: TCustomOpenGLControl;
|
|
FSharingOpenGlControls: TList;
|
|
function GetSharingControls(Index: integer): TCustomOpenGLControl;
|
|
procedure SetAutoResizeViewport(const AValue: boolean);
|
|
procedure SetDoubleBuffered(const AValue: boolean);
|
|
procedure SetRGBA(const AValue: boolean);
|
|
procedure SetSharedControl(const AValue: TCustomOpenGLControl);
|
|
protected
|
|
procedure WMPaint(var Message: TLMPaint); message LM_PAINT;
|
|
procedure WMSize(var Message: TLMSize); message LM_SIZE;
|
|
procedure UpdateFrameTimeDiff;
|
|
procedure OpenGLAttributesChanged;
|
|
procedure EraseBackground(DC: HDC); override;
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
Procedure Paint; virtual;
|
|
procedure RealizeBounds; override;
|
|
procedure DoOnPaint; virtual;
|
|
procedure SwapBuffers; virtual;
|
|
function MakeCurrent(SaveOldToStack: boolean = false): boolean; virtual;
|
|
function RestoreOldOpenGLControl: boolean;
|
|
function SharingControlCount: integer;
|
|
property SharingControls[Index: integer]: TCustomOpenGLControl read GetSharingControls;
|
|
procedure Invalidate; override;
|
|
public
|
|
property FrameDiffTimeInMSecs: integer read FFrameDiffTime;
|
|
property OnMakeCurrent: TOpenGlCtrlMakeCurrentEvent read FOnMakeCurrent
|
|
write FOnMakeCurrent;
|
|
property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
|
|
property SharedControl: TCustomOpenGLControl read FSharedOpenGLControl
|
|
write SetSharedControl;
|
|
property AutoResizeViewport: boolean read FAutoResizeViewport
|
|
write SetAutoResizeViewport default false;
|
|
property DoubleBuffered: boolean read FDoubleBuffered write SetDoubleBuffered default true;
|
|
property RGBA: boolean read FRGBA write SetRGBA default true;
|
|
end;
|
|
|
|
{ TOpenGLControl }
|
|
|
|
TOpenGLControl = class(TCustomOpenGLControl)
|
|
published
|
|
property Align;
|
|
property Anchors;
|
|
property AutoResizeViewport;
|
|
property BorderSpacing;
|
|
property Enabled;
|
|
property OnChangeBounds;
|
|
property OnClick;
|
|
property OnConstrainedResize;
|
|
property OnDblClick;
|
|
property OnEnter;
|
|
property OnExit;
|
|
property OnKeyDown;
|
|
property OnKeyPress;
|
|
property OnKeyUp;
|
|
property OnMouseDown;
|
|
property OnMouseEnter;
|
|
property OnMouseLeave;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
property OnMouseWheel;
|
|
property OnMouseWheelDown;
|
|
property OnMouseWheelUp;
|
|
property OnPaint;
|
|
property OnResize;
|
|
property OnShowHint;
|
|
property PopupMenu;
|
|
property ShowHint;
|
|
property Visible;
|
|
end;
|
|
|
|
{ TWSOpenGLControl }
|
|
|
|
TWSOpenGLControl = class(TWidgetSetWSWinControl)
|
|
public
|
|
class function CreateHandle(const AWinControl: TWinControl;
|
|
const AParams: TCreateParams): HWND; override;
|
|
class procedure DestroyHandle(const AWinControl: TWinControl); override;
|
|
end;
|
|
|
|
|
|
|
|
procedure Register;
|
|
|
|
|
|
implementation
|
|
|
|
var
|
|
OpenGLControlStack: TList = nil;
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('OpenGL',[TOpenGLControl]);
|
|
end;
|
|
|
|
{ TCustomOpenGLControl }
|
|
|
|
function TCustomOpenGLControl.GetSharingControls(Index: integer
|
|
): TCustomOpenGLControl;
|
|
begin
|
|
Result:=TCustomOpenGLControl(FSharingOpenGlControls[Index]);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.SetAutoResizeViewport(const AValue: boolean);
|
|
begin
|
|
if FAutoResizeViewport=AValue then exit;
|
|
FAutoResizeViewport:=AValue;
|
|
if AutoResizeViewport
|
|
and ([csLoading,csDestroying]*ComponentState=[])
|
|
and IsVisible and HandleAllocated
|
|
and MakeCurrent then
|
|
LOpenGLViewport(0,0,Width,Height);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.SetDoubleBuffered(const AValue: boolean);
|
|
begin
|
|
if FDoubleBuffered=AValue then exit;
|
|
FDoubleBuffered:=AValue;
|
|
OpenGLAttributesChanged;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.SetRGBA(const AValue: boolean);
|
|
begin
|
|
if FRGBA=AValue then exit;
|
|
FRGBA:=AValue;
|
|
OpenGLAttributesChanged;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.SetSharedControl(
|
|
const AValue: TCustomOpenGLControl);
|
|
begin
|
|
if FSharedOpenGLControl=AValue then exit;
|
|
if AValue=Self then
|
|
Raise Exception.Create('A control can not be shared by itself.');
|
|
// unshare old
|
|
if (AValue<>nil) and (AValue.SharedControl<>nil) then
|
|
Raise Exception.Create('Target control is sharing too. A sharing control can not be shared.');
|
|
if FSharedOpenGLControl<>nil then
|
|
FSharedOpenGLControl.FSharingOpenGlControls.Remove(Self);
|
|
// share new
|
|
if (AValue<>nil) and (csDestroying in AValue.ComponentState) then
|
|
FSharedOpenGLControl:=nil
|
|
else begin
|
|
FSharedOpenGLControl:=AValue;
|
|
if (FSharedOpenGLControl<>nil) then begin
|
|
if FSharedOpenGLControl.FSharingOpenGlControls=nil then
|
|
FSharedOpenGLControl.FSharingOpenGlControls:=TList.Create;
|
|
FSharedOpenGLControl.FSharingOpenGlControls.Add(Self);
|
|
end;
|
|
end;
|
|
// recreate handle if needed
|
|
if HandleAllocated and (not (csDesigning in ComponentState)) then
|
|
ReCreateWnd(Self);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.WMPaint(var Message: TLMPaint);
|
|
begin
|
|
Include(FControlState, csCustomPaint);
|
|
inherited WMPaint(Message);
|
|
//debugln('TCustomGTKGLAreaControl.WMPaint A ',dbgsName(Self),' ',dbgsName(FCanvas));
|
|
if (csDesigning in ComponentState) and (FCanvas<>nil) then begin
|
|
with FCanvas do begin
|
|
if Message.DC <> 0 then
|
|
Handle := Message.DC;
|
|
Brush.Color:=clLtGray;
|
|
Pen.Color:=clRed;
|
|
Rectangle(0,0,Self.Width-1,Self.Height-1);
|
|
MoveTo(0,0);
|
|
LineTo(Self.Width,Self.Height);
|
|
MoveTo(0,Self.Height);
|
|
LineTo(Self.Width,0);
|
|
if Message.DC <> 0 then
|
|
Handle := 0;
|
|
end;
|
|
end else begin
|
|
Paint;
|
|
end;
|
|
Exclude(FControlState, csCustomPaint);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.WMSize(var Message: TLMSize);
|
|
begin
|
|
if (Message.SizeType and Size_SourceIsInterface)>0 then
|
|
DoOnResize;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.UpdateFrameTimeDiff;
|
|
begin
|
|
FCurrentFrameTime:=integer(GetTickCount);
|
|
if FLastFrameTime=0 then
|
|
FLastFrameTime:=FCurrentFrameTime;
|
|
// calculate time since last call:
|
|
FFrameDiffTime:=FCurrentFrameTime-FLastFrameTime;
|
|
// if the counter is reset restart:
|
|
if (FFrameDiffTime<0) then FFrameDiffTime:=1;
|
|
FLastFrameTime:=FCurrentFrameTime;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.OpenGLAttributesChanged;
|
|
begin
|
|
if HandleAllocated
|
|
and ([csLoading,csDesigning,csDestroying]*ComponentState=[]) then
|
|
RecreateWnd(Self);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.EraseBackground(DC: HDC);
|
|
begin
|
|
if DC=0 then ;
|
|
// everything is painted, so erasing the background is not needed
|
|
end;
|
|
|
|
constructor TCustomOpenGLControl.Create(TheOwner: TComponent);
|
|
begin
|
|
inherited Create(TheOwner);
|
|
FDoubleBuffered:=true;
|
|
FRGBA:=true;
|
|
ControlStyle:=ControlStyle-[csSetCaption];
|
|
if (csDesigning in ComponentState) then begin
|
|
FCanvas := TControlCanvas.Create;
|
|
TControlCanvas(FCanvas).Control := Self;
|
|
end else
|
|
FCompStyle:=csNonLCL;
|
|
SetInitialBounds(0, 0, 160, 90);
|
|
end;
|
|
|
|
destructor TCustomOpenGLControl.Destroy;
|
|
begin
|
|
if FSharingOpenGlControls<>nil then begin
|
|
while SharingControlCount>0 do
|
|
SharingControls[SharingControlCount-1].SharedControl:=nil;
|
|
FreeAndNil(FSharingOpenGlControls);
|
|
end;
|
|
SharedControl:=nil;
|
|
if OpenGLControlStack<>nil then begin
|
|
OpenGLControlStack.Remove(Self);
|
|
if OpenGLControlStack.Count=0 then
|
|
FreeAndNil(OpenGLControlStack);
|
|
end;
|
|
FCanvas.Free;
|
|
FCanvas:=nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.Paint;
|
|
begin
|
|
if IsVisible and HandleAllocated then begin
|
|
UpdateFrameTimeDiff;
|
|
if ([csDesigning,csDestroying]*ComponentState=[]) then begin
|
|
if not MakeCurrent then exit;
|
|
if AutoResizeViewport then
|
|
LOpenGLViewport(0,0,Width,Height);
|
|
end;
|
|
DoOnPaint;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.RealizeBounds;
|
|
begin
|
|
if IsVisible and HandleAllocated
|
|
and ([csDesigning,csDestroying]*ComponentState=[]) and MakeCurrent then begin
|
|
if AutoResizeViewport then
|
|
LOpenGLViewport(0,0,Width,Height);
|
|
end;
|
|
inherited RealizeBounds;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.DoOnPaint;
|
|
begin
|
|
if Assigned(OnPaint) then OnPaint(Self);
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.SwapBuffers;
|
|
begin
|
|
LOpenGLSwapBuffers(Handle);
|
|
end;
|
|
|
|
function TCustomOpenGLControl.MakeCurrent(SaveOldToStack: boolean): boolean;
|
|
var
|
|
Allowed: Boolean;
|
|
begin
|
|
if csDesigning in ComponentState then exit(false);
|
|
if Assigned(FOnMakeCurrent) then begin
|
|
Allowed:=true;
|
|
OnMakeCurrent(Self,Allowed);
|
|
if not Allowed then begin
|
|
Result:=False;
|
|
exit;
|
|
end;
|
|
end;
|
|
// make current
|
|
Result:=LOpenGLMakeCurrent(Handle);
|
|
if Result and SaveOldToStack then begin
|
|
// on success push on stack
|
|
if OpenGLControlStack=nil then
|
|
OpenGLControlStack:=TList.Create;
|
|
OpenGLControlStack.Add(Self);
|
|
end;
|
|
end;
|
|
|
|
function TCustomOpenGLControl.RestoreOldOpenGLControl: boolean;
|
|
var
|
|
RestoredControl: TCustomOpenGLControl;
|
|
begin
|
|
Result:=false;
|
|
// check if the current context is on stack
|
|
if (OpenGLControlStack=nil) or (OpenGLControlStack.Count=0) then exit;
|
|
// pop
|
|
OpenGLControlStack.Delete(OpenGLControlStack.Count-1);
|
|
// make old control the current control
|
|
if OpenGLControlStack.Count>0 then begin
|
|
RestoredControl:=
|
|
TCustomOpenGLControl(OpenGLControlStack[OpenGLControlStack.Count-1]);
|
|
if (not LOpenGLMakeCurrent(RestoredControl.Handle)) then
|
|
exit;
|
|
end else begin
|
|
FreeAndNil(OpenGLControlStack);
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TCustomOpenGLControl.SharingControlCount: integer;
|
|
begin
|
|
if FSharingOpenGlControls=nil then
|
|
Result:=0
|
|
else
|
|
Result:=FSharingOpenGlControls.Count;
|
|
end;
|
|
|
|
procedure TCustomOpenGLControl.Invalidate;
|
|
begin
|
|
if csCustomPaint in FControlState then exit;
|
|
inherited Invalidate;
|
|
end;
|
|
|
|
{ TWSOpenGLControl }
|
|
|
|
class function TWSOpenGLControl.CreateHandle(const AWinControl: TWinControl;
|
|
const AParams: TCreateParams): HWND;
|
|
var
|
|
OpenGlControl: TCustomOpenGLControl;
|
|
AttrControl: TCustomOpenGLControl;
|
|
begin
|
|
if csDesigning in AWinControl.ComponentState then
|
|
Result:=inherited CreateHandle(AWinControl,AParams)
|
|
else begin
|
|
OpenGlControl:=AWinControl as TCustomOpenGLControl;
|
|
if OpenGlControl.SharedControl<>nil then
|
|
AttrControl:=OpenGlControl.SharedControl
|
|
else
|
|
AttrControl:=OpenGlControl;
|
|
Result:=LOpenGLCreateContext(OpenGlControl,WSPrivate,
|
|
OpenGlControl.SharedControl,
|
|
AttrControl.DoubleBuffered,AttrControl.RGBA,
|
|
AParams);
|
|
end;
|
|
end;
|
|
|
|
class procedure TWSOpenGLControl.DestroyHandle(const AWinControl: TWinControl);
|
|
begin
|
|
LOpenGLDestroyContextInfo(AWinControl);
|
|
inherited DestroyHandle(AWinControl);
|
|
end;
|
|
|
|
initialization
|
|
RegisterWSComponent(TCustomOpenGLControl,TWSOpenGLControl);
|
|
{$I openglcontext.lrs}
|
|
|
|
end.
|
|
|