2
0

GXS.SceneViewer.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. //
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.SceneViewer;
  5. (* Windows SceneViewer *)
  6. interface
  7. uses
  8. Winapi.OpenGL,
  9. Winapi.OpenGLext,
  10. Winapi.Windows,
  11. WinApi.Messages,
  12. System.Classes,
  13. System.SysUtils,
  14. System.Types,
  15. FMX.Graphics,
  16. FMX.Forms,
  17. FMX.Controls,
  18. FMX.Dialogs.Win,
  19. FMX.Viewport3D,
  20. Stage.OpenGL4, // WGL_EXT_swap_control
  21. GXS.Scene,
  22. GXS.Context,
  23. GXS.WinContext;
  24. type
  25. TgxCreateParams = record
  26. Caption: PChar;
  27. Style: DWORD;
  28. ExStyle: DWORD;
  29. X, Y: Integer;
  30. Width, Height: Integer;
  31. //WndParent: HWnd; - not fmx
  32. Param: Pointer;
  33. //WindowClass: TWndClass; - not fmx
  34. WinClassName: array[0..63] of Char;
  35. end;
  36. TgxTouchEvent = procedure(X, Y, TouchWidth, TouchHeight : integer;
  37. TouchID : Cardinal; MultiTouch : boolean) of object;
  38. (* Component where the GLScene objects get rendered.
  39. This component delimits the area where OpenGL renders the scene,
  40. it represents the 3D scene viewed from a camera (specified in the
  41. camera property). This component can also render to a file or to a bitmap.
  42. It is primarily a windowed component, but it can handle full-screen
  43. operations : simply make this component fit the whole screen (use a
  44. borderless form).
  45. This viewer also allows to define rendering options such a fog, face culling,
  46. depth testing, etc. and can take care of framerate calculation. *)
  47. TgxSceneViewer = class(TViewPort3D)
  48. private
  49. FBuffer: TgxSceneBuffer;
  50. FVSync: TgxSyncMode;
  51. FOwnDC: HDC;
  52. FOnMouseEnter, FOnMouseLeave: TNotifyEvent;
  53. FMouseInControl: Boolean;
  54. FLastScreenPos: TPoint;
  55. FPenAsTouch: boolean;
  56. FOnTouchMove: TgxTouchEvent;
  57. FOnTouchUp: TgxTouchEvent;
  58. FOnTouchDown: TgxTouchEvent;
  59. procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  60. procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  61. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  62. procedure WMGetDglCode(var Message: TMessage); message WM_GETDLGCODE;
  63. procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
  64. procedure WMTouch(var Message: TMessage); message WM_TOUCH;
  65. { TODO -oPW -cMessages : Convert message CM_MOUSEENTER to FMX }
  66. procedure CMMouseEnter(var msg: TMessage); ///message CM_MOUSEENTER;
  67. { TODO -oPW -cMessages : Convert message CM_MOUSELEAVE; to FMX }
  68. procedure CMMouseLeave(var msg: TMessage); ///message CM_MOUSELEAVE;
  69. function GetFieldOfView: single;
  70. procedure SetFieldOfView(const Value: single);
  71. function GetIsRenderingContextAvailable: Boolean;
  72. protected
  73. procedure SetBeforeRender(const val: TNotifyEvent);
  74. function GetBeforeRender: TNotifyEvent;
  75. procedure SetPostRender(const val: TNotifyEvent);
  76. function GetPostRender: TNotifyEvent;
  77. procedure SetAfterRender(const val: TNotifyEvent);
  78. function GetAfterRender: TNotifyEvent;
  79. procedure SetCamera(const val: TgxCamera);
  80. function GetCamera: TgxCamera;
  81. procedure SetBuffer(const val: TgxSceneBuffer);
  82. procedure CreateParams(var Params: TgxCreateParams); /// Vcl - override;
  83. procedure CreateWnd; /// Vcl - override;
  84. procedure DestroyWnd; /// Vcl - override;
  85. procedure Loaded; override;
  86. procedure DoBeforeRender(Sender: TObject); virtual;
  87. procedure DoBufferChange(Sender: TObject); virtual;
  88. procedure DoBufferStructuralChange(Sender: TObject); virtual;
  89. procedure MouseMove(Shift: TShiftState; X, Y: Integer); /// Vcl - override;
  90. public
  91. constructor Create(AOwner: TComponent); override;
  92. destructor Destroy; override;
  93. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  94. (* Makes TWinControl's RecreateWnd public.
  95. This procedure allows to work around limitations in some OpenGL
  96. drivers (like MS Software OpenGL) that are not able to share lists
  97. between RCs that already have display lists. *)
  98. procedure RecreateWnd;
  99. property IsRenderingContextAvailable: Boolean read GetIsRenderingContextAvailable;
  100. function LastFrameTime: Single;
  101. function FramesPerSecond: Single;
  102. function FramesPerSecondText(decimals: Integer = 1): string;
  103. procedure ResetPerformanceMonitor;
  104. function CreateSnapShotBitmap: TBitmap;
  105. procedure RegisterTouch;
  106. procedure UnregisterTouch;
  107. property RenderDC: HDC read FOwnDC;
  108. property MouseInControl: Boolean read FMouseInControl;
  109. published
  110. // Camera from which the scene is rendered.
  111. property Camera: TgxCamera read GetCamera write SetCamera;
  112. (* Specifies if the refresh should be synchronized with the VSync signal.
  113. If the underlying OpenGL ICD does not support the WGL_EXT_swap_control
  114. extension, this property is ignored. *)
  115. property VSync: TgxSyncMode read FVSync write FVSync default vsmNoSync;
  116. (* Triggered before the scene's objects get rendered.
  117. You may use this event to execute your own OpenGL rendering. *)
  118. property BeforeRender: TNotifyEvent read GetBeforeRender write SetBeforeRender;
  119. { Triggered just after all the scene's objects have been rendered.
  120. The OpenGL context is still active in this event, and you may use it
  121. to execute your own OpenGL rendering. }
  122. property PostRender: TNotifyEvent read GetPostRender write SetPostRender;
  123. (* Called after rendering.
  124. You cannot issue OpenGL calls in this event, if you want to do your own
  125. OpenGL stuff, use the PostRender event. *)
  126. property AfterRender: TNotifyEvent read GetAfterRender write SetAfterRender;
  127. // Access to buffer properties.
  128. property Buffer: TgxSceneBuffer read FBuffer write SetBuffer;
  129. (* Returns or sets the field of view for the viewer, in degrees.
  130. This value depends on the camera and the width and height of the scene.
  131. The value isn't persisted, if the width/height or camera.focallength is
  132. changed, FieldOfView is changed also. *)
  133. property FieldOfView: single read GetFieldOfView write SetFieldOfView;
  134. (* Since Windows 10 Fall Creators Update in 2017, pen also triggers
  135. touch input. You can set PenAsTouch = false to filter out such input. *)
  136. property PenAsTouch: boolean read FPenAsTouch write FPenAsTouch;
  137. property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
  138. property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
  139. property OnTouchMove: TgxTouchEvent read FOnTouchMove write FOnTouchMove;
  140. property OnTouchUp: TgxTouchEvent read FOnTouchUp write FOnTouchUp;
  141. property OnTouchDown: TgxTouchEvent read FOnTouchDown write FOnTouchDown;
  142. property Align;
  143. property Anchors;
  144. /// property DragCursor;
  145. property DragMode;
  146. property Enabled;
  147. /// property HelpContext; - Vcl
  148. property Hint;
  149. property PopupMenu;
  150. property Visible;
  151. property OnClick;
  152. property OnDblClick;
  153. property OnDragDrop;
  154. property OnDragOver;
  155. /// property OnStartDrag; - Vcl
  156. /// property OnEndDrag; - Vcl
  157. property OnMouseDown;
  158. property OnMouseMove;
  159. property OnMouseUp;
  160. property OnMouseWheel;
  161. /// property OnMouseWheelDown; - Vcl
  162. /// property OnMouseWheelUp; - Vcl
  163. property OnKeyDown;
  164. property OnKeyUp;
  165. /// property OnContextPopup; - Vcl
  166. property TabStop;
  167. property TabOrder;
  168. property OnEnter;
  169. property OnExit;
  170. property OnGesture;
  171. property Touch;
  172. end;
  173. procedure SetupVSync(const AVSyncMode : TgxSyncMode);
  174. var
  175. Handle: HWND;
  176. implementation // -----------------------------------------------------------
  177. // ------------------
  178. // ------------------ TgxSceneViewerFMX ------------------
  179. // ------------------
  180. procedure SetupVSync(const AVSyncMode : TgxSyncMode);
  181. var
  182. I: Integer;
  183. begin
  184. if WGL_EXT_swap_control then
  185. begin
  186. {! TODO
  187. I := wglGetSwapIntervalEXT;
  188. case AVSyncMode of
  189. vsmSync : if I <> 1 then wglSwapIntervalEXT(1);
  190. vsmNoSync: if I <> 0 then wglSwapIntervalEXT(0);
  191. else
  192. Assert(False);
  193. end;
  194. }
  195. end;
  196. end;
  197. constructor TgxSceneViewer.Create(AOwner: TComponent);
  198. begin
  199. inherited Create(AOwner);
  200. { TODO -oPW -cStyles : ContrilStyle in FMX }
  201. (* - todo Vcl styles convert to FMX styles
  202. ControlStyle := [csClickEvents, csDoubleClicks, csOpaque, csCaptureMouse];
  203. if csDesigning in ComponentState then
  204. ControlStyle := ControlStyle + [csFramed];
  205. *)
  206. Width := 100;
  207. Height := 100;
  208. FVSync := vsmNoSync;
  209. FBuffer := TgxSceneBuffer.Create(Self);
  210. FBuffer.ViewerBeforeRender := DoBeforeRender;
  211. FBuffer.OnChange := DoBufferChange;
  212. FBuffer.OnStructuralChange := DoBufferStructuralChange;
  213. end;
  214. destructor TgxSceneViewer.Destroy;
  215. begin
  216. FBuffer.Free;
  217. FBuffer := nil;
  218. inherited Destroy;
  219. end;
  220. procedure TgxSceneViewer.Notification(AComponent: TComponent; Operation: TOperation);
  221. begin
  222. if (Operation = opRemove) and (FBuffer <> nil) then
  223. begin
  224. if (AComponent = FBuffer.Camera) then
  225. FBuffer.Camera := nil;
  226. end;
  227. inherited;
  228. end;
  229. procedure TgxSceneViewer.RecreateWnd;
  230. begin
  231. inherited;
  232. end;
  233. procedure TgxSceneViewer.RegisterTouch;
  234. begin
  235. /// RegisterTouchWindow(Handle, 0); - Vcl Handle
  236. end;
  237. procedure TgxSceneViewer.SetBeforeRender(const val: TNotifyEvent);
  238. begin
  239. FBuffer.BeforeRender := val;
  240. end;
  241. function TgxSceneViewer.GetBeforeRender: TNotifyEvent;
  242. begin
  243. Result := FBuffer.BeforeRender;
  244. end;
  245. procedure TgxSceneViewer.SetPostRender(const val: TNotifyEvent);
  246. begin
  247. FBuffer.PostRender := val;
  248. end;
  249. procedure TgxSceneViewer.UnregisterTouch;
  250. begin
  251. /// UnregisterTouchWindow(Handle); - Vcl Handle
  252. end;
  253. function TgxSceneViewer.GetPostRender: TNotifyEvent;
  254. begin
  255. Result := FBuffer.PostRender;
  256. end;
  257. procedure TgxSceneViewer.SetAfterRender(const val: TNotifyEvent);
  258. begin
  259. FBuffer.AfterRender := val;
  260. end;
  261. function TgxSceneViewer.GetAfterRender: TNotifyEvent;
  262. begin
  263. Result := FBuffer.AfterRender;
  264. end;
  265. procedure TgxSceneViewer.SetCamera(const val: TgxCamera);
  266. begin
  267. FBuffer.Camera := val;
  268. end;
  269. function TgxSceneViewer.GetCamera: TgxCamera;
  270. begin
  271. Result := FBuffer.Camera;
  272. end;
  273. procedure TgxSceneViewer.SetBuffer(const val: TgxSceneBuffer);
  274. begin
  275. FBuffer.Assign(val);
  276. end;
  277. procedure TgxSceneViewer.CreateParams(var Params: TgxCreateParams);
  278. begin
  279. inherited; /// Vcl - inherited CreateParams(Params);
  280. with Params do
  281. begin
  282. Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS;
  283. { TODO : E2003 Undeclared identifier: 'WindowClass' in FMX.Forms.TCreateParams}
  284. (*WindowClass.Style := WindowClass.Style or CS_OWNDC;*)
  285. end;
  286. end;
  287. procedure TgxSceneViewer.CreateWnd;
  288. begin
  289. inherited; /// Vcl - inherited CreateWnd;
  290. // initialize and activate the OpenGL rendering context
  291. // need to do this only once per window creation as we have a private DC
  292. FBuffer.Resize(0, 0, Round(Self.Width), Round(Self.Height));
  293. FOwnDC := GetDC(Handle);
  294. FBuffer.CreateRC(FOwnDC, False);
  295. end;
  296. procedure TgxSceneViewer.DestroyWnd;
  297. begin
  298. FBuffer.DestroyRC;
  299. if FOwnDC <> 0 then
  300. begin
  301. ReleaseDC(Handle, FOwnDC);
  302. FOwnDC := 0;
  303. end;
  304. inherited;
  305. end;
  306. procedure TgxSceneViewer.WMEraseBkgnd(var Message: TWMEraseBkgnd);
  307. begin
  308. if IsRenderingContextAvailable then
  309. Message.Result := 1
  310. else
  311. inherited;
  312. end;
  313. procedure TgxSceneViewer.WMSize(var Message: TWMSize);
  314. begin
  315. inherited;
  316. FBuffer.Resize(0, 0, Message.Width, Message.Height);
  317. end;
  318. procedure TgxSceneViewer.WMTouch(var Message: TMessage);
  319. function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
  320. begin
  321. Result := Point(TOUCH_COORD_TO_PIXEL(TouchPoint.X), TOUCH_COORD_TO_PIXEL(TouchPoint.Y));
  322. PhysicalToLogicalPoint(Handle, Result);
  323. { TODO -oPW -cIncompatibility : Incompatible Handle and Result in FMX }
  324. ///Result:=ScreenToClient(Result);
  325. end;
  326. var
  327. TouchInputs: array of TTouchInput;
  328. TouchInput: TTouchInput;
  329. Handled: Boolean;
  330. Point: TPoint;
  331. Multitouch : boolean;
  332. begin
  333. Handled := False;
  334. SetLength(TouchInputs, Message.WParam);
  335. Multitouch := Message.WParam > 1;
  336. GetTouchInputInfo(Message.LParam, Message.WParam, @TouchInputs[0],
  337. SizeOf(TTouchInput));
  338. try
  339. for TouchInput in TouchInputs do
  340. begin
  341. Point := TouchPointToPoint(TouchInput);
  342. if (TouchInput.dwFlags AND TOUCHEVENTF_MOVE) > 0 then
  343. if Assigned(OnTouchMove) then
  344. begin
  345. OnTouchMove(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  346. end;
  347. if (TouchInput.dwFlags AND TOUCHEVENTF_DOWN) > 0 then
  348. if Assigned(OnTouchDown) then
  349. begin
  350. OnTouchDown(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  351. end;
  352. if (TouchInput.dwFlags AND TOUCHEVENTF_UP) > 0 then
  353. if Assigned(OnTouchUp) then
  354. begin
  355. OnTouchUp(Point.X, Point.Y, TouchInput.cxContact, TouchInput.cyContact, TouchInput.dwID, Multitouch);
  356. end;
  357. end;
  358. Handled := True;
  359. finally
  360. if Handled then
  361. CloseTouchInputHandle(Message.LParam)
  362. else
  363. inherited;
  364. end;
  365. end;
  366. procedure TgxSceneViewer.WMPaint(var Message: TWMPaint);
  367. var
  368. PS: TPaintStruct;
  369. p: TPoint;
  370. begin
  371. { TODO -oPW -cUnworkable : FMX.Forms.IFMXWindowService.ClientToScreen in FMX }
  372. /// p := ClientToScreen(Point(0, 0));
  373. if (FLastScreenPos.X <> p.X) or (FLastScreenPos.Y <> p.Y) then
  374. begin
  375. // Workaround for MS OpenGL "black borders" bug
  376. if FBuffer.RCInstantiated then
  377. { TODO -oPW -cUnworkable : Not applicable in FMX }
  378. /// PostMessage(Handle, WM_SIZE, SIZE_RESTORED, Width + (Height shl 16));
  379. FLastScreenPos := p;
  380. end;
  381. BeginPaint(Handle, PS);
  382. try
  383. if IsRenderingContextAvailable and (Width > 0) and (Height > 0) then
  384. FBuffer.Render;
  385. finally
  386. EndPaint(Handle, PS);
  387. Message.Result := 0;
  388. end;
  389. end;
  390. procedure TgxSceneViewer.WMGetDglCode(var Message: TMessage);
  391. begin
  392. Message.Result := Message.Result or DLGC_WANTARROWS;
  393. end;
  394. procedure TgxSceneViewer.WMDestroy(var Message: TWMDestroy);
  395. begin
  396. if Assigned(FBuffer) then
  397. begin
  398. FBuffer.DestroyRC;
  399. if FOwnDC <> 0 then
  400. begin
  401. ReleaseDC(Handle, FOwnDC);
  402. FOwnDC := 0;
  403. end;
  404. end;
  405. inherited;
  406. end;
  407. procedure TgxSceneViewer.CMMouseEnter(var msg: TMessage);
  408. begin
  409. inherited;
  410. FMouseInControl := True;
  411. if Assigned(FOnMouseEnter) then
  412. FOnMouseEnter(Self);
  413. end;
  414. procedure TgxSceneViewer.CMMouseLeave(var msg: TMessage);
  415. begin
  416. inherited;
  417. FMouseInControl := False;
  418. if Assigned(FOnMouseLeave) then
  419. FOnMouseLeave(Self);
  420. end;
  421. procedure TgxSceneViewer.Loaded;
  422. begin
  423. inherited Loaded;
  424. // initiate window creation
  425. { TODO -oPW -cUnworkable : HandleNeeded not found in FMX.Controls }
  426. ///HandleNeeded;
  427. end;
  428. procedure TgxSceneViewer.DoBeforeRender(Sender: TObject);
  429. begin
  430. SetupVSync(VSync);
  431. end;
  432. procedure TgxSceneViewer.DoBufferChange(Sender: TObject);
  433. begin
  434. if (not Buffer.Rendering) and (not Buffer.Freezed) then
  435. { TODO -oPW -cUnworkable : Invalidate not found in FMX.Controls }
  436. /// Invalidate;
  437. end;
  438. procedure TgxSceneViewer.DoBufferStructuralChange(Sender: TObject);
  439. begin
  440. RecreateWnd;
  441. end;
  442. procedure TgxSceneViewer.MouseMove(Shift: TShiftState; X, Y: Integer);
  443. begin
  444. { TODO -oPW -cIncompatibility : not found in FMX.Controls }
  445. /// inherited;
  446. /// if csDesignInteractive in ControlStyle then FBuffer.NotifyMouseMove(Shift, X, Y);
  447. end;
  448. function TgxSceneViewer.LastFrameTime: Single;
  449. begin
  450. Result := FBuffer.LastFrameTime;
  451. end;
  452. function TgxSceneViewer.FramesPerSecond: Single;
  453. begin
  454. Result := FBuffer.FramesPerSecond;
  455. end;
  456. function TgxSceneViewer.FramesPerSecondText(decimals: Integer = 1): string;
  457. begin
  458. Result := Format('%.*f FPS', [decimals, FBuffer.FramesPerSecond]);
  459. end;
  460. procedure TgxSceneViewer.ResetPerformanceMonitor;
  461. begin
  462. FBuffer.ResetPerformanceMonitor;
  463. end;
  464. function TgxSceneViewer.CreateSnapShotBitmap: TBitmap;
  465. begin
  466. Result := TBitmap.Create;
  467. { TODO -oPW -cIncompatibility : Find analog of pf24bit in FMX }
  468. /// Result.PixelFormat := pf24bit;
  469. Result.Width := Round(Width);
  470. Result.Height := Round(Height);
  471. { TODO -oPW -cUnworkable : Handle not found in FMX }
  472. /// BitBlt(Result.Canvas.Handle, 0, 0, Width, Height, RenderDC, 0, 0, SRCCOPY);
  473. end;
  474. function TgxSceneViewer.GetFieldOfView: single;
  475. begin
  476. if not Assigned(Camera) then
  477. result := 0
  478. else if Width < Height then
  479. result := Camera.GetFieldOfView(Width)
  480. else
  481. result := Camera.GetFieldOfView(Height);
  482. end;
  483. function TgxSceneViewer.GetIsRenderingContextAvailable: Boolean;
  484. begin
  485. Result := FBuffer.RCInstantiated and FBuffer.RenderingContext.IsValid;
  486. end;
  487. procedure TgxSceneViewer.SetFieldOfView(const Value: single);
  488. begin
  489. if Assigned(Camera) then
  490. begin
  491. if Width < Height then
  492. Camera.SetFieldOfView(Value, Width)
  493. else
  494. Camera.SetFieldOfView(Value, Height);
  495. end;
  496. end;
  497. initialization // ------------------------------------------------------------
  498. RegisterClass(TgxSceneViewer);
  499. end.