436 lines
12 KiB
ObjectPascal

// SPDX-License-Identifier: LGPL-3.0-linking-exception
unit BGLVirtualScreen;
{$mode objfpc}{$H+}
interface
uses
Classes, BGRAClasses, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
ExtCtrls, BGRABitmapTypes, BGRAOpenGL, OpenGLContext, BGRACanvasGL,
BGRASpriteGL;
type
TCustomBGLVirtualScreen = class;
TBGLRedrawEvent = procedure (Sender: TObject; BGLContext: TBGLContext) of object;
TBGLLoadTexturesEvent = procedure (Sender: TObject; BGLContext: TBGLContext) of object;
TBGLElapseEvent = procedure (Sender: TObject; BGLContext: TBGLContext; ElapsedMs: integer) of object;
TBGLFramesPerSecondEvent = procedure (Sender: TObject; BGLContext: TBGLContext; FramesPerSecond: integer) of object;
TBGLUseContextCallback = procedure (Sender: TObject; BGLContext: TBGLContext; Data: Pointer) of object;
{ TCustomBGLVirtualScreen }
TCustomBGLVirtualScreen = class(TCustomOpenGLControl)
private
{ Private declarations }
FOnRedraw: TBGLRedrawEvent;
FOnLoadTextures: TBGLLoadTexturesEvent;
FOnUnloadTextures: TBGLLoadTexturesEvent;
FOnElapse: TBGLElapseEvent;
FOnFramesPerSecond: TBGLFramesPerSecondEvent;
FSmoothedElapse: boolean;
FTexturesLoaded: boolean;
FBevelInner, FBevelOuter: TPanelBevel;
FBevelWidth: TBevelWidth;
FBorderWidth: TBorderWidth;
FRedrawOnIdle: boolean;
FSprites: TBGLCustomSpriteEngine;
FElapseAccumulator, FElapseCount, FStoredFPS: integer;
FSmoothedElapseAccumulator: single;
FContextPrepared: boolean;
FOldSprites: TBGLCustomSpriteEngine;
FShaderList,FOldShaderList: TStringList;
function GetCanvas: TBGLCustomCanvas;
procedure SetBevelInner(const AValue: TPanelBevel);
procedure SetBevelOuter(const AValue: TPanelBevel);
procedure SetBevelWidth(const AValue: TBevelWidth);
procedure SetBorderWidth(const AValue: TBorderWidth);
procedure SetRedrawOnIdle(AValue: Boolean);
procedure SetSmoothedElapse(AValue: boolean);
protected
class var FToRedrawOnIdle: array of TCustomBGLVirtualScreen;
{ Protected declarations }
procedure RedrawContent(ctx: TBGLContext); virtual;
procedure SetEnabled(Value: boolean); override;
procedure OnAppIdle(Sender: TObject; var Done: Boolean);
procedure LoadTextures; virtual;
function PrepareBGLContext: TBGLContext;
procedure ReleaseBGLContext(ctx: TBGLContext);
public
{ Public declarations }
procedure DoOnPaint; override;
procedure QueryLoadTextures; virtual;
procedure UnloadTextures; virtual;
procedure UseContext(ACallback: TBGLUseContextCallback; AData: Pointer = nil);
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
public
property Canvas: TBGLCustomCanvas read GetCanvas;
property Sprites: TBGLCustomSpriteEngine read FSprites;
property OnLoadTextures: TBGLLoadTexturesEvent Read FOnLoadTextures Write FOnLoadTextures;
property OnUnloadTextures: TBGLLoadTexturesEvent Read FOnUnloadTextures Write FOnUnloadTextures;
property OnRedraw: TBGLRedrawEvent Read FOnRedraw Write FOnRedraw;
property OnElapse: TBGLElapseEvent Read FOnElapse Write FOnElapse;
property OnFramesPerSecond: TBGLFramesPerSecondEvent Read FOnFramesPerSecond Write FOnFramesPerSecond;
property RedrawOnIdle: Boolean read FRedrawOnIdle write SetRedrawOnIdle default False;
property BorderWidth: TBorderWidth Read FBorderWidth Write SetBorderWidth default 0;
property BevelInner: TPanelBevel Read FBevelInner Write SetBevelInner default bvNone;
property BevelOuter: TPanelBevel Read FBevelOuter Write SetBevelOuter default bvNone;
property BevelWidth: TBevelWidth Read FBevelWidth Write SetBevelWidth default 1;
property SmoothedElapse: boolean read FSmoothedElapse write SetSmoothedElapse default False;
end;
TBGLVirtualScreen = class(TCustomBGLVirtualScreen)
published
property OnRedraw;
property Align;
property Anchors;
property AutoSize;
property BorderSpacing;
property BevelInner;
property BevelOuter;
property BevelWidth;
property BidiMode;
property BorderWidth;
property BorderStyle;
property Caption;
property ChildSizing;
property ClientHeight;
property ClientWidth;
property Color;
property Constraints;
property DockSite;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property Font;
property ParentBidiMode;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property RedrawOnIdle;
property ShowHint;
property TabOrder;
property TabStop;
property UseDockManager default True;
property Visible;
property OnClick;
property OnContextPopup;
property OnDockDrop;
property OnDockOver;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnElapse;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnFramesPerSecond;
property OnGetSiteInfo;
property OnGetDockCaption;
property OnLoadTextures;
property OnUnloadTextures;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnStartDock;
property OnStartDrag;
property OnUnDock;
property SmoothedElapse;
end;
procedure Register;
implementation
procedure Register;
begin
{$I bglvirtualscreen_icon.lrs}
RegisterComponents('OpenGL', [TBGLVirtualScreen]);
end;
{ TCustomBGLVirtualScreen }
procedure TCustomBGLVirtualScreen.SetBevelInner(const AValue: TPanelBevel);
begin
if FBevelInner = AValue then
exit;
FBevelInner := AValue;
Invalidate;
end;
function TCustomBGLVirtualScreen.GetCanvas: TBGLCustomCanvas;
begin
result := BGLCanvas;
end;
procedure TCustomBGLVirtualScreen.SetBevelOuter(const AValue: TPanelBevel);
begin
if FBevelOuter = AValue then
exit;
FBevelOuter := AValue;
Invalidate;
end;
procedure TCustomBGLVirtualScreen.SetBevelWidth(const AValue: TBevelWidth);
begin
if FBevelWidth = AValue then
exit;
FBevelWidth := AValue;
Invalidate;
end;
procedure TCustomBGLVirtualScreen.SetBorderWidth(const AValue: TBorderWidth);
begin
if FBorderWidth = AValue then
exit;
FBorderWidth := AValue;
Invalidate;
end;
procedure TCustomBGLVirtualScreen.SetRedrawOnIdle(AValue: Boolean);
var
i: Integer;
j: Integer;
begin
if FRedrawOnIdle=AValue then Exit;
FRedrawOnIdle:=AValue;
if FRedrawOnIdle then
begin
if length(FToRedrawOnIdle)= 0 then
Application.AddOnIdleHandler(@OnAppIdle);
setlength(FToRedrawOnIdle, length(FToRedrawOnIdle)+1);
FToRedrawOnIdle[high(FToRedrawOnIdle)] := self;
end
else
if length(FToRedrawOnIdle)> 0 then
begin
for i := 0 to high(FToRedrawOnIdle) do
begin
if FToRedrawOnIdle[i]=self then
begin
for j := i to high(FToRedrawOnIdle)-1 do
FToRedrawOnIdle[j] := FToRedrawOnIdle[j+1];
setlength(FToRedrawOnIdle, length(FToRedrawOnIdle)-1);
break;
end;
end;
if length(FToRedrawOnIdle) = 0 then
Application.RemoveOnIdleHandler(@OnAppIdle);
end;
end;
procedure TCustomBGLVirtualScreen.SetSmoothedElapse(AValue: boolean);
begin
if FSmoothedElapse=AValue then Exit;
FSmoothedElapse:=AValue;
end;
procedure TCustomBGLVirtualScreen.DoOnPaint;
var
ctx: TBGLContext;
knownFPS: Integer;
begin
if not FTexturesLoaded then LoadTextures;
ctx := PrepareBGLContext;
if Color = clNone then
BGLViewPort(ClientWidth,ClientHeight)
else
if Color = clDefault then
BGLViewPort(ClientWidth,ClientHeight,ColorToBGRA(clWindow))
else
BGLViewPort(ClientWidth,ClientHeight,ColorToBGRA(Color));
RedrawContent(ctx);
inherited DoOnPaint;
SwapBuffers;
inc(FElapseAccumulator, FrameDiffTimeInMSecs);
Inc(FElapseCount);
if FElapseAccumulator >= 2000 then
begin
FStoredFPS := 1000*FElapseCount div FElapseAccumulator;
if Assigned(FOnFramesPerSecond) then
FOnFramesPerSecond(self, ctx, FStoredFPS);
FElapseAccumulator := 0;
FElapseCount := 0;
end;
If Assigned(FOnElapse) then
begin
if SmoothedElapse then
begin
If FStoredFPS <> 0 then
knownFPS:= FStoredFPS
else
if FElapseAccumulator >= 500 then
knownFPS := 1000*FElapseCount div FElapseAccumulator
else
knownFPS := 0;
if knownFPS > 0 then
begin
IncF(FSmoothedElapseAccumulator, 1000/knownFPS);
end else
IncF(FSmoothedElapseAccumulator, FrameDiffTimeInMSecs);
FOnElapse(self, ctx, Trunc(FSmoothedElapseAccumulator));
DecF(FSmoothedElapseAccumulator, Trunc(FSmoothedElapseAccumulator));
end else
FOnElapse(self, ctx, FrameDiffTimeInMSecs);
end;
ReleaseBGLContext(ctx);
end;
procedure TCustomBGLVirtualScreen.QueryLoadTextures;
begin
FTexturesLoaded := false;
end;
procedure TCustomBGLVirtualScreen.LoadTextures;
var ctx: TBGLContext;
begin
if MakeCurrent then
begin
if Assigned(FOnLoadTextures) then
begin
ctx := PrepareBGLContext;
FOnLoadTextures(self, ctx);
ReleaseBGLContext(ctx);
end;
FTexturesLoaded:= true;
end;
end;
function TCustomBGLVirtualScreen.PrepareBGLContext: TBGLContext;
begin
if FContextPrepared then
raise exception.Create('Context already prepared');
FOldSprites := BGRASpriteGL.BGLSpriteEngine;
BGRASpriteGL.BGLSpriteEngine := FSprites;
FOldShaderList := BGLCanvas.Lighting.ShaderList;
BGLCanvas.Lighting.ShaderList := FShaderList;
result.Canvas := BGLCanvas;
result.Sprites := FSprites;
FContextPrepared := true;
end;
procedure TCustomBGLVirtualScreen.ReleaseBGLContext(ctx: TBGLContext);
begin
if not FContextPrepared then
raise exception.Create('Context not prepared');
ctx.Canvas.Lighting.ShaderList := FOldShaderList;
BGRASpriteGL.BGLSpriteEngine := FOldSprites;
FContextPrepared := false;
end;
procedure TCustomBGLVirtualScreen.UnloadTextures;
var ctx: TBGLContext;
begin
if MakeCurrent then
begin
ctx := PrepareBGLContext;
if Assigned(FOnUnloadTextures) then FOnUnloadTextures(self, ctx);
FSprites.Clear;
ctx.Canvas.Lighting.FreeShaders;
ReleaseBGLContext(ctx);
FTexturesLoaded := false;
end;
end;
procedure TCustomBGLVirtualScreen.UseContext(ACallback: TBGLUseContextCallback; AData: Pointer);
var
ctx: TBGLContext;
begin
if not MakeCurrent then
raise exception.Create('Unable to switch to the OpenGL context');
ctx := PrepareBGLContext;
try
ACallback(self, ctx, AData);
finally
ReleaseBGLContext(ctx);
end;
end;
procedure TCustomBGLVirtualScreen.RedrawContent(ctx: TBGLContext);
var
ARect: TRect;
w: integer;
begin
ARect := rect(0,0,ctx.Canvas.Width,ctx.Canvas.Height);
w := BevelWidth;
if w = 0 then w := 1;
// if BevelOuter is set then draw a frame with BevelWidth
if (BevelOuter <> bvNone) and (w > 0) then
ctx.Canvas.Frame3d(ARect, w, BevelOuter); // Note: Frame3D inflates ARect
ARect.Inflate(-BorderWidth, -BorderWidth);
// if BevelInner is set then skip the BorderWidth and draw a frame with BevelWidth
if (BevelInner <> bvNone) and (w > 0) then
ctx.Canvas.Frame3d(ARect, w, BevelInner); // Note: Frame3D inflates ARect
if Assigned(FOnRedraw) then
FOnRedraw(self, ctx);
end;
procedure TCustomBGLVirtualScreen.SetEnabled(Value: boolean);
begin
if Value <> Enabled then Invalidate;
inherited SetEnabled(Value);
end;
procedure TCustomBGLVirtualScreen.OnAppIdle(Sender: TObject; var Done: Boolean);
var
i: Integer;
begin
if length(FToRedrawOnIdle) > 0 then
begin
for i := 0 to high(FToRedrawOnIdle) do
if not (csDesigning in FToRedrawOnIdle[i].ComponentState) then
FToRedrawOnIdle[i].Invalidate;
Done:=false;
end;
end;
constructor TCustomBGLVirtualScreen.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
FTexturesLoaded:= False;
AutoResizeViewport := true;
FSprites := TBGLDefaultSpriteEngine.Create;
FShaderList:= TStringList.Create;
FStoredFPS := 0;
FElapseAccumulator := 0;
FElapseCount := 0;
FSmoothedElapseAccumulator := 0;
end;
destructor TCustomBGLVirtualScreen.Destroy;
var
i: Integer;
begin
for i := 0 to FShaderList.Count-1 do
FShaderList.Objects[i].Free;
FShaderList.Free;
RedrawOnIdle := false;
FSprites.Free;
inherited Destroy;
end;
end.