GLS.SceneViewer.pas 16 KB

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