123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.SceneViewer;
- (* Windows SceneViewer *)
- interface
- uses
- Winapi.OpenGL,
- Winapi.OpenGLext,
- Winapi.Windows,
- WinApi.Messages,
- System.Classes,
- System.SysUtils,
- System.Types,
- FMX.Graphics,
- FMX.Forms,
- FMX.Controls,
- FMX.Dialogs.Win,
- FMX.Viewport3D,
- Stage.OpenGL4, // WGL_EXT_swap_control
- GXS.Scene,
- GXS.Context,
- GXS.WinContext;
- type
- TgxCreateParams = record
- Caption: PChar;
- Style: DWORD;
- ExStyle: DWORD;
- X, Y: Integer;
- Width, Height: Integer;
- //WndParent: HWnd; - not fmx
- Param: Pointer;
- //WindowClass: TWndClass; - not fmx
- WinClassName: array[0..63] of Char;
- end;
- TgxTouchEvent = procedure(X, Y, TouchWidth, TouchHeight : integer;
- TouchID : Cardinal; MultiTouch : boolean) of object;
- (* Component where the GLScene objects get rendered.
- This component delimits the area where OpenGL renders the scene,
- it represents the 3D scene viewed from a camera (specified in the
- camera property). This component can also render to a file or to a bitmap.
- It is primarily a windowed component, but it can handle full-screen
- operations : simply make this component fit the whole screen (use a
- borderless form).
- This viewer also allows to define rendering options such a fog, face culling,
- depth testing, etc. and can take care of framerate calculation. *)
- TgxSceneViewer = class(TViewPort3D)
- private
- FBuffer: TgxSceneBuffer;
- FVSync: TgxSyncMode;
- FOwnDC: HDC;
- FOnMouseEnter, FOnMouseLeave: TNotifyEvent;
- FMouseInControl: Boolean;
- FLastScreenPos: TPoint;
- FPenAsTouch: boolean;
- FOnTouchMove: TgxTouchEvent;
- FOnTouchUp: TgxTouchEvent;
- FOnTouchDown: TgxTouchEvent;
- procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
- procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
- procedure WMSize(var Message: TWMSize); message WM_SIZE;
- procedure WMGetDglCode(var Message: TMessage); message WM_GETDLGCODE;
- procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
- procedure WMTouch(var Message: TMessage); message WM_TOUCH;
- { TODO -oPW -cMessages : Convert message CM_MOUSEENTER to FMX }
- procedure CMMouseEnter(var msg: TMessage); ///message CM_MOUSEENTER;
- { TODO -oPW -cMessages : Convert message CM_MOUSELEAVE; to FMX }
- procedure CMMouseLeave(var msg: TMessage); ///message CM_MOUSELEAVE;
- function GetFieldOfView: single;
- procedure SetFieldOfView(const Value: single);
- function GetIsRenderingContextAvailable: Boolean;
- protected
- procedure SetBeforeRender(const val: TNotifyEvent);
- function GetBeforeRender: TNotifyEvent;
- procedure SetPostRender(const val: TNotifyEvent);
- function GetPostRender: TNotifyEvent;
- procedure SetAfterRender(const val: TNotifyEvent);
- function GetAfterRender: TNotifyEvent;
- procedure SetCamera(const val: TgxCamera);
- function GetCamera: TgxCamera;
- procedure SetBuffer(const val: TgxSceneBuffer);
- procedure CreateParams(var Params: TgxCreateParams); /// Vcl - override;
- procedure CreateWnd; /// Vcl - override;
- procedure DestroyWnd; /// Vcl - override;
- procedure Loaded; override;
- procedure DoBeforeRender(Sender: TObject); virtual;
- procedure DoBufferChange(Sender: TObject); virtual;
- procedure DoBufferStructuralChange(Sender: TObject); virtual;
- procedure MouseMove(Shift: TShiftState; X, Y: Integer); /// Vcl - override;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Notification(AComponent: TComponent; Operation: TOperation); override;
- (* Makes TWinControl's RecreateWnd public.
- This procedure allows to work around limitations in some OpenGL
- drivers (like MS Software OpenGL) that are not able to share lists
- between RCs that already have display lists. *)
- procedure RecreateWnd;
- property IsRenderingContextAvailable: Boolean read GetIsRenderingContextAvailable;
- function LastFrameTime: Single;
- function FramesPerSecond: Single;
- function FramesPerSecondText(decimals: Integer = 1): string;
- procedure ResetPerformanceMonitor;
- function CreateSnapShotBitmap: TBitmap;
- procedure RegisterTouch;
- procedure UnregisterTouch;
- property RenderDC: HDC read FOwnDC;
- property MouseInControl: Boolean read FMouseInControl;
- published
- // Camera from which the scene is rendered.
- property Camera: TgxCamera read GetCamera write SetCamera;
- (* Specifies if the refresh should be synchronized with the VSync signal.
- If the underlying OpenGL ICD does not support the WGL_EXT_swap_control
- extension, this property is ignored. *)
- property VSync: TgxSyncMode read FVSync write FVSync default vsmNoSync;
- (* Triggered before the scene's objects get rendered.
- You may use this event to execute your own OpenGL rendering. *)
- property BeforeRender: TNotifyEvent read GetBeforeRender write SetBeforeRender;
- { Triggered just after all the scene's objects have been rendered.
- The OpenGL context is still active in this event, and you may use it
- to execute your own OpenGL rendering. }
- property PostRender: TNotifyEvent read GetPostRender write SetPostRender;
- (* Called after rendering.
- You cannot issue OpenGL calls in this event, if you want to do your own
- OpenGL stuff, use the PostRender event. *)
- property AfterRender: TNotifyEvent read GetAfterRender write SetAfterRender;
- // Access to buffer properties.
- property Buffer: TgxSceneBuffer read FBuffer write SetBuffer;
- (* Returns or sets the field of view for the viewer, in degrees.
- This value depends on the camera and the width and height of the scene.
- The value isn't persisted, if the width/height or camera.focallength is
- changed, FieldOfView is changed also. *)
- property FieldOfView: single read GetFieldOfView write SetFieldOfView;
- (* Since Windows 10 Fall Creators Update in 2017, pen also triggers
- touch input. You can set PenAsTouch = false to filter out such input. *)
- property PenAsTouch: boolean read FPenAsTouch write FPenAsTouch;
- property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
- property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
- property OnTouchMove: TgxTouchEvent read FOnTouchMove write FOnTouchMove;
- property OnTouchUp: TgxTouchEvent read FOnTouchUp write FOnTouchUp;
- property OnTouchDown: TgxTouchEvent read FOnTouchDown write FOnTouchDown;
- property Align;
- property Anchors;
- /// property DragCursor;
- property DragMode;
- property Enabled;
- /// property HelpContext; - Vcl
- property Hint;
- property PopupMenu;
- property Visible;
- property OnClick;
- property OnDblClick;
- property OnDragDrop;
- property OnDragOver;
- /// property OnStartDrag; - Vcl
- /// property OnEndDrag; - Vcl
- property OnMouseDown;
- property OnMouseMove;
- property OnMouseUp;
- property OnMouseWheel;
- /// property OnMouseWheelDown; - Vcl
- /// property OnMouseWheelUp; - Vcl
- property OnKeyDown;
- property OnKeyUp;
- /// property OnContextPopup; - Vcl
- property TabStop;
- property TabOrder;
- property OnEnter;
- property OnExit;
- property OnGesture;
- property Touch;
- end;
- procedure SetupVSync(const AVSyncMode : TgxSyncMode);
- var
- Handle: HWND;
- implementation // -----------------------------------------------------------
- // ------------------
- // ------------------ TgxSceneViewerFMX ------------------
- // ------------------
- procedure SetupVSync(const AVSyncMode : TgxSyncMode);
- var
- I: Integer;
- begin
- if WGL_EXT_swap_control then
- begin
- {! TODO
- I := wglGetSwapIntervalEXT;
- case AVSyncMode of
- vsmSync : if I <> 1 then wglSwapIntervalEXT(1);
- vsmNoSync: if I <> 0 then wglSwapIntervalEXT(0);
- else
- Assert(False);
- end;
- }
- end;
- end;
- constructor TgxSceneViewer.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- { TODO -oPW -cStyles : ContrilStyle in FMX }
- (* - todo Vcl styles convert to FMX styles
- ControlStyle := [csClickEvents, csDoubleClicks, csOpaque, csCaptureMouse];
- if csDesigning in ComponentState then
- ControlStyle := ControlStyle + [csFramed];
- *)
- Width := 100;
- Height := 100;
- FVSync := vsmNoSync;
- FBuffer := TgxSceneBuffer.Create(Self);
- FBuffer.ViewerBeforeRender := DoBeforeRender;
- FBuffer.OnChange := DoBufferChange;
- FBuffer.OnStructuralChange := DoBufferStructuralChange;
- end;
- destructor TgxSceneViewer.Destroy;
- begin
- FBuffer.Free;
- FBuffer := nil;
- inherited Destroy;
- end;
- procedure TgxSceneViewer.Notification(AComponent: TComponent; Operation: TOperation);
- begin
- if (Operation = opRemove) and (FBuffer <> nil) then
- begin
- if (AComponent = FBuffer.Camera) then
- FBuffer.Camera := nil;
- end;
- inherited;
- end;
- procedure TgxSceneViewer.RecreateWnd;
- begin
- inherited;
- end;
- procedure TgxSceneViewer.RegisterTouch;
- begin
- /// RegisterTouchWindow(Handle, 0); - Vcl Handle
- end;
- procedure TgxSceneViewer.SetBeforeRender(const val: TNotifyEvent);
- begin
- FBuffer.BeforeRender := val;
- end;
- function TgxSceneViewer.GetBeforeRender: TNotifyEvent;
- begin
- Result := FBuffer.BeforeRender;
- end;
- procedure TgxSceneViewer.SetPostRender(const val: TNotifyEvent);
- begin
- FBuffer.PostRender := val;
- end;
- procedure TgxSceneViewer.UnregisterTouch;
- begin
- /// UnregisterTouchWindow(Handle); - Vcl Handle
- end;
- function TgxSceneViewer.GetPostRender: TNotifyEvent;
- begin
- Result := FBuffer.PostRender;
- end;
- procedure TgxSceneViewer.SetAfterRender(const val: TNotifyEvent);
- begin
- FBuffer.AfterRender := val;
- end;
- function TgxSceneViewer.GetAfterRender: TNotifyEvent;
- begin
- Result := FBuffer.AfterRender;
- end;
- procedure TgxSceneViewer.SetCamera(const val: TgxCamera);
- begin
- FBuffer.Camera := val;
- end;
- function TgxSceneViewer.GetCamera: TgxCamera;
- begin
- Result := FBuffer.Camera;
- end;
- procedure TgxSceneViewer.SetBuffer(const val: TgxSceneBuffer);
- begin
- FBuffer.Assign(val);
- end;
- procedure TgxSceneViewer.CreateParams(var Params: TgxCreateParams);
- begin
- inherited; /// Vcl - inherited CreateParams(Params);
- with Params do
- begin
- Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS;
- { TODO : E2003 Undeclared identifier: 'WindowClass' in FMX.Forms.TCreateParams}
- (*WindowClass.Style := WindowClass.Style or CS_OWNDC;*)
- end;
- end;
- procedure TgxSceneViewer.CreateWnd;
- begin
- inherited; /// Vcl - inherited CreateWnd;
- // initialize and activate the OpenGL rendering context
- // need to do this only once per window creation as we have a private DC
- FBuffer.Resize(0, 0, Round(Self.Width), Round(Self.Height));
- FOwnDC := GetDC(Handle);
- FBuffer.CreateRC(FOwnDC, False);
- end;
- procedure TgxSceneViewer.DestroyWnd;
- begin
- FBuffer.DestroyRC;
- if FOwnDC <> 0 then
- begin
- ReleaseDC(Handle, FOwnDC);
- FOwnDC := 0;
- end;
- inherited;
- end;
- procedure TgxSceneViewer.WMEraseBkgnd(var Message: TWMEraseBkgnd);
- begin
- if IsRenderingContextAvailable then
- Message.Result := 1
- else
- inherited;
- end;
- procedure TgxSceneViewer.WMSize(var Message: TWMSize);
- begin
- inherited;
- FBuffer.Resize(0, 0, Message.Width, Message.Height);
- end;
- procedure TgxSceneViewer.WMTouch(var Message: TMessage);
- function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
- begin
- Result := Point(TOUCH_COORD_TO_PIXEL(TouchPoint.X), TOUCH_COORD_TO_PIXEL(TouchPoint.Y));
- PhysicalToLogicalPoint(Handle, Result);
- { TODO -oPW -cIncompatibility : Incompatible Handle and Result in FMX }
- ///Result:=ScreenToClient(Result);
- end;
- var
- TouchInputs: array of TTouchInput;
- TouchInput: TTouchInput;
- Handled: Boolean;
- Point: TPoint;
- Multitouch : boolean;
- begin
- Handled := False;
- SetLength(TouchInputs, Message.WParam);
- Multitouch := Message.WParam > 1;
- GetTouchInputInfo(Message.LParam, Message.WParam, @TouchInputs[0],
- SizeOf(TTouchInput));
- try
- for TouchInput in TouchInputs do
- begin
- Point := TouchPointToPoint(TouchInput);
- if (TouchInput.dwFlags AND TOUCHEVENTF_MOVE) > 0 then
- if Assigned(OnTouchMove) then
- begin
- OnTouchMove(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
- end;
- if (TouchInput.dwFlags AND TOUCHEVENTF_DOWN) > 0 then
- if Assigned(OnTouchDown) then
- begin
- OnTouchDown(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
- end;
- if (TouchInput.dwFlags AND TOUCHEVENTF_UP) > 0 then
- if Assigned(OnTouchUp) then
- begin
- OnTouchUp(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
- end;
- end;
- Handled := True;
- finally
- if Handled then
- CloseTouchInputHandle(Message.LParam)
- else
- inherited;
- end;
- end;
- procedure TgxSceneViewer.WMPaint(var Message: TWMPaint);
- var
- PS: TPaintStruct;
- p: TPoint;
- begin
- { TODO -oPW -cUnworkable : FMX.Forms.IFMXWindowService.ClientToScreen in FMX }
- /// p := ClientToScreen(Point(0, 0));
- if (FLastScreenPos.X <> p.X) or (FLastScreenPos.Y <> p.Y) then
- begin
- // Workaround for MS OpenGL "black borders" bug
- if FBuffer.RCInstantiated then
- { TODO -oPW -cUnworkable : Not applicable in FMX }
- /// PostMessage(Handle, WM_SIZE, SIZE_RESTORED, Width + (Height shl 16));
- FLastScreenPos := p;
- end;
- BeginPaint(Handle, PS);
- try
- if IsRenderingContextAvailable and (Width > 0) and (Height > 0) then
- FBuffer.Render;
- finally
- EndPaint(Handle, PS);
- Message.Result := 0;
- end;
- end;
- procedure TgxSceneViewer.WMGetDglCode(var Message: TMessage);
- begin
- Message.Result := Message.Result or DLGC_WANTARROWS;
- end;
- procedure TgxSceneViewer.WMDestroy(var Message: TWMDestroy);
- begin
- if Assigned(FBuffer) then
- begin
- FBuffer.DestroyRC;
- if FOwnDC <> 0 then
- begin
- ReleaseDC(Handle, FOwnDC);
- FOwnDC := 0;
- end;
- end;
- inherited;
- end;
- procedure TgxSceneViewer.CMMouseEnter(var msg: TMessage);
- begin
- inherited;
- FMouseInControl := True;
- if Assigned(FOnMouseEnter) then
- FOnMouseEnter(Self);
- end;
- procedure TgxSceneViewer.CMMouseLeave(var msg: TMessage);
- begin
- inherited;
- FMouseInControl := False;
- if Assigned(FOnMouseLeave) then
- FOnMouseLeave(Self);
- end;
- procedure TgxSceneViewer.Loaded;
- begin
- inherited Loaded;
- // initiate window creation
- { TODO -oPW -cUnworkable : HandleNeeded not found in FMX.Controls }
- ///HandleNeeded;
- end;
- procedure TgxSceneViewer.DoBeforeRender(Sender: TObject);
- begin
- SetupVSync(VSync);
- end;
- procedure TgxSceneViewer.DoBufferChange(Sender: TObject);
- begin
- if (not Buffer.Rendering) and (not Buffer.Freezed) then
- { TODO -oPW -cUnworkable : Invalidate not found in FMX.Controls }
- /// Invalidate;
- end;
- procedure TgxSceneViewer.DoBufferStructuralChange(Sender: TObject);
- begin
- RecreateWnd;
- end;
- procedure TgxSceneViewer.MouseMove(Shift: TShiftState; X, Y: Integer);
- begin
- { TODO -oPW -cIncompatibility : not found in FMX.Controls }
- /// inherited;
- /// if csDesignInteractive in ControlStyle then FBuffer.NotifyMouseMove(Shift, X, Y);
- end;
- function TgxSceneViewer.LastFrameTime: Single;
- begin
- Result := FBuffer.LastFrameTime;
- end;
- function TgxSceneViewer.FramesPerSecond: Single;
- begin
- Result := FBuffer.FramesPerSecond;
- end;
- function TgxSceneViewer.FramesPerSecondText(decimals: Integer = 1): string;
- begin
- Result := Format('%.*f FPS', [decimals, FBuffer.FramesPerSecond]);
- end;
- procedure TgxSceneViewer.ResetPerformanceMonitor;
- begin
- FBuffer.ResetPerformanceMonitor;
- end;
- function TgxSceneViewer.CreateSnapShotBitmap: TBitmap;
- begin
- Result := TBitmap.Create;
- { TODO -oPW -cIncompatibility : Find analog of pf24bit in FMX }
- /// Result.PixelFormat := pf24bit;
- Result.Width := Round(Width);
- Result.Height := Round(Height);
- { TODO -oPW -cUnworkable : Handle not found in FMX }
- /// BitBlt(Result.Canvas.Handle, 0, 0, Width, Height, RenderDC, 0, 0, SRCCOPY);
- end;
- function TgxSceneViewer.GetFieldOfView: single;
- begin
- if not Assigned(Camera) then
- result := 0
- else if Width < Height then
- result := Camera.GetFieldOfView(Width)
- else
- result := Camera.GetFieldOfView(Height);
- end;
- function TgxSceneViewer.GetIsRenderingContextAvailable: Boolean;
- begin
- Result := FBuffer.RCInstantiated and FBuffer.RenderingContext.IsValid;
- end;
- procedure TgxSceneViewer.SetFieldOfView(const Value: single);
- begin
- if Assigned(Camera) then
- begin
- if Width < Height then
- Camera.SetFieldOfView(Value, Width)
- else
- Camera.SetFieldOfView(Value, Height);
- end;
- end;
- initialization // ------------------------------------------------------------
- RegisterClass(TgxSceneViewer);
- end.
|