// 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.