GLS.SDLWindow.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLS.SDLWindow;
  5. (*
  6. Non visual wrapper around basic SDL window features.
  7. Notes:
  8. Unit must ultimately *NOT* make use of any platform specific stuff,
  9. *EVEN* through the use of conditionals.
  10. SDL-specifics should also be avoided in the "interface" section.
  11. This component uses a header conversion for SDL from http://libsdl.org
  12. *)
  13. interface
  14. {$I GLScene.inc}
  15. uses
  16. System.Classes,
  17. System.SysUtils,
  18. System.SyncObjs,
  19. OpenGLTokens,
  20. OpenGLAdapter,
  21. GLVectorTypes,
  22. GLState,
  23. GLContext,
  24. GLVectorGeometry,
  25. Import.SDL2;
  26. type
  27. (* Pixel Depth options.
  28. vpd16bits: 16bpp graphics (565) (and 16 bits depth buffer for OpenGL)
  29. vpd24bits: 24bpp graphics (565) (and 24 bits depth buffer for OpenGL) *)
  30. TSDLWindowPixelDepth = (vpd16bits, vpd24bits);
  31. (* Specifies optional settings for the SDL window.
  32. Those options are a simplified subset of the SDL options:
  33. voDoubleBuffer: create a double-buffered window
  34. voOpenGL: requires OpenGL capability for the window
  35. voResizable: window should be resizable
  36. voFullScreen: requires a full screen "window" (screen resolution may be changed)
  37. voStencilBuffer: requires a stencil buffer (8bits, use along voOpenGL) *)
  38. TSDLWindowOption = (voDoubleBuffer, voHardwareAccel, voOpenGL, voResizable,
  39. voFullScreen, voStencilBuffer);
  40. TSDLWindowOptions = set of TSDLWindowOption;
  41. TSDLEvent = procedure(sender: TObject; const event: TSDL_Event) of object;
  42. const
  43. cDefaultSDLWindowOptions = [voDoubleBuffer, voHardwareAccel, voOpenGL, voResizable];
  44. type
  45. (* A basic SDL-based window (non-visual component).
  46. Only a limited subset of SDL's features are available, and this window
  47. is heavily oriented toward using it for OpenGL rendering.
  48. Be aware SDL is currently limited to a single window at any time...
  49. so you may have multiple components, but only one can be used *)
  50. TSDLWindow = class(TComponent)
  51. private
  52. FWidth: Integer;
  53. FHeight: Integer;
  54. FPixelDepth: TSDLWindowPixelDepth;
  55. FOptions: TSDLWindowOptions;
  56. FActive: Boolean;
  57. FOnOpen: TNotifyEvent;
  58. FOnClose: TNotifyEvent;
  59. FOnResize: TNotifyEvent;
  60. FOnSDLEvent: TSDLEvent;
  61. FOnEventPollDone: TNotifyEvent;
  62. FCaption: String;
  63. FThreadSleepLength: Integer;
  64. FThreadPriority: TThreadPriority;
  65. FThreadedEventPolling: Boolean;
  66. FThread: TThread;
  67. FSDLSurface: PSDL_Surface;
  68. FWindowHandle: Longword;
  69. FSDLWindow: PSDL_Window;
  70. protected
  71. procedure SetWidth(const val: Integer);
  72. procedure SetHeight(const val: Integer);
  73. procedure SetPixelDepth(const val: TSDLWindowPixelDepth);
  74. procedure SetOptions(const val: TSDLWindowOptions);
  75. procedure SetActive(const val: Boolean);
  76. procedure SetCaption(const val: String);
  77. procedure SetThreadSleepLength(const val: Integer);
  78. procedure SetThreadPriority(const val: TThreadPriority);
  79. procedure SetThreadedEventPolling(const val: Boolean);
  80. function BuildSDLVideoFlags: Cardinal;
  81. procedure SetSDLGLAttributes;
  82. procedure CreateOrRecreateSDLSurface;
  83. procedure ResizeGLWindow;
  84. procedure SetupSDLEnvironmentValues;
  85. procedure StartThread;
  86. procedure StopThread;
  87. public
  88. constructor Create(AOwner: TComponent); override;
  89. destructor Destroy; override;
  90. // Initializes and Opens an SDL window
  91. procedure Open;
  92. (* Closes an already opened SDL Window.
  93. NOTE: will also kill the app due to an SDL limitation... *)
  94. procedure Close;
  95. // Applies changes (size, pixeldepth...) to the opened window
  96. procedure UpdateWindow;
  97. // Swap front and back buffer
  98. procedure SwapBuffers;
  99. (* Polls SDL events.
  100. SDL events can be either polled "manually", through a call to this
  101. method, or automatically via ThreadEventPolling *)
  102. procedure PollEvents;
  103. (* Is the SDL window active (opened)?
  104. Adjusting this value as the same effect as invoking Open/Close *)
  105. property Active: Boolean read FActive write SetActive;
  106. (* Presents the SDL surface of the window.
  107. If Active is False, this value is undefined *)
  108. property SDLSurface: PSDL_Surface read FSDLSurface;
  109. // Experimental: ask SDL to reuse and existing WindowHandle
  110. property WindowHandle: Cardinal read FWindowHandle write FWindowHandle;
  111. // Presents the SDL window. If Active is False, this value is undefined
  112. property SDLWindow: PSDL_Window read FSDLWindow;
  113. published
  114. // Width of the SDL window. To apply changes to an active window, call UpdateWindow
  115. property Width: Integer read FWidth write SetWidth default 640;
  116. // Height of the SDL window. To apply changes to an active window, call UpdateWindow
  117. property Height: Integer read FHeight write SetHeight default 480;
  118. // PixelDepth of the SDL window. To apply changes to an active window, call UpdateWindow
  119. property PixelDepth: TSDLWindowPixelDepth read FPixelDepth
  120. write SetPixelDepth default vpd24bits;
  121. // Options for the SDL window. To apply changes to an active window, call UpdateWindow
  122. property Options: TSDLWindowOptions read FOptions write SetOptions
  123. default cDefaultSDLWindowOptions;
  124. // Caption of the SDL window
  125. property Caption: String read FCaption write SetCaption;
  126. // Controls automatic threaded event polling
  127. property ThreadedEventPolling: Boolean read FThreadedEventPolling
  128. write SetThreadedEventPolling default True;
  129. // Sleep length between pollings in the polling thread
  130. property ThreadSleepLength: Integer read FThreadSleepLength
  131. write SetThreadSleepLength default 1;
  132. // Priority of the event polling thread
  133. property ThreadPriority: TThreadPriority read FThreadPriority
  134. write SetThreadPriority default tpLower;
  135. // Fired whenever Open succeeds. The SDL surface is defined and usable when the event happens
  136. property OnOpen: TNotifyEvent read FOnOpen write FOnOpen;
  137. // Fired whenever closing the window. The SDL surface is still defined and usable when the event happens
  138. property OnClose: TNotifyEvent read FOnClose write FOnClose;
  139. // Fired whenever the window is resized. Note: glViewPort call is handled automatically for OpenGL windows
  140. property OnResize: TNotifyEvent read FOnResize write FOnResize;
  141. (* Fired whenever an SDL Event is polled.
  142. SDL_QUITEV and SDL_VIDEORESIZE are not passed to this event handler,
  143. they are passed via OnClose and OnResize respectively *)
  144. property OnSDLEvent: TSDLEvent read FOnSDLEvent write FOnSDLEvent;
  145. // Fired whenever an event polling completes with no events left to poll
  146. property OnEventPollDone: TNotifyEvent read FOnEventPollDone
  147. write FOnEventPollDone;
  148. end;
  149. // Generic SDL or SDLWindow exception
  150. ESDLError = class(Exception);
  151. // -----------------------------------------------------------------------------
  152. // Get Environment Routines
  153. //------------------------------------------------------------------------------
  154. function _putenv(const variable: PAnsiChar): Integer; cdecl;
  155. (* Put a variable of the form "name=value" into the environment *)
  156. /// function SDL_putenv(const variable: PAnsiChar): integer; cdecl; external LibName;
  157. function SDL_putenv(const variable: PAnsiChar): Integer;
  158. (* The following function has been commented out to encourage developers to use
  159. SDL_putenv as it more portable *)
  160. /// function putenv(const variable: PAnsiChar): integer;
  161. function getenv(const name: PAnsiChar): PAnsiChar; cdecl;
  162. { * Retrieve a variable named "name" from the environment }
  163. /// function SDL_getenv(const name: PAnsiChar): PAnsiChar; cdecl; external LibName;
  164. function SDL_getenv(const name: PAnsiChar): PAnsiChar;
  165. (* The following function has been commented out to encourage developers to use
  166. SDL_getenv as it it more portable *)
  167. /// function getenv(const name: PAnsiChar): PAnsiChar;
  168. procedure Register;
  169. // ---------------------------------------------------------------------
  170. implementation
  171. // ---------------------------------------------------------------------
  172. var
  173. vSDLCS: TCriticalSection;
  174. vSDLActive: Boolean; // will be removed once SDL supports multiple windows
  175. type
  176. TSDLEventThread = class(TThread)
  177. Owner: TSDLWindow;
  178. procedure Execute; override;
  179. procedure DoPollEvents;
  180. end;
  181. procedure RaiseSDLError(const msg: String = '');
  182. begin
  183. if msg <> '' then
  184. raise ESDLError.Create(msg + #13#10 + SDL_GetError)
  185. else
  186. raise ESDLError.Create(SDL_GetError);
  187. end;
  188. function _putenv(const variable: PAnsiChar): Integer; cdecl; external 'MSVCRT.DLL';
  189. function SDL_putenv(const variable: PAnsiChar): Integer;
  190. begin
  191. Result := _putenv(variable);
  192. end;
  193. function getenv(const name: PAnsiChar): PAnsiChar; cdecl; external 'MSVCRT.DLL';
  194. function SDL_getenv(const name: PAnsiChar): PAnsiChar;
  195. begin
  196. Result := getenv(name);
  197. end;
  198. // ------------------
  199. // ------------------ TSDLEventThread ------------------
  200. // ------------------
  201. procedure TSDLEventThread.Execute;
  202. begin
  203. try
  204. while not Terminated do
  205. begin
  206. vSDLCS.Enter;
  207. try
  208. SDL_Delay(Owner.ThreadSleepLength);
  209. finally
  210. vSDLCS.Leave;
  211. end;
  212. Synchronize(DoPollEvents);
  213. end;
  214. except
  215. // bail out asap, problem wasn't here anyway
  216. end;
  217. vSDLCS.Enter;
  218. try
  219. if Assigned(Owner) then
  220. Owner.FThread := nil;
  221. finally
  222. vSDLCS.Leave;
  223. end;
  224. end;
  225. procedure TSDLEventThread.DoPollEvents;
  226. begin
  227. // no need for a CS here, we're in the main thread
  228. if Assigned(Owner) then
  229. Owner.PollEvents;
  230. end;
  231. // ------------------
  232. // ------------------ TSDLWindow ------------------
  233. // ------------------
  234. constructor TSDLWindow.Create(AOwner: TComponent);
  235. begin
  236. inherited Create(AOwner);
  237. FWidth := 640;
  238. FHeight := 480;
  239. FPixelDepth := vpd24bits;
  240. FThreadedEventPolling := True;
  241. FThreadSleepLength := 1;
  242. FThreadPriority := tpLower;
  243. FOptions := cDefaultSDLWindowOptions;
  244. end;
  245. destructor TSDLWindow.Destroy;
  246. begin
  247. Close;
  248. inherited Destroy;
  249. end;
  250. procedure TSDLWindow.SetWidth(const val: Integer);
  251. begin
  252. if FWidth <> val then
  253. if val > 0 then
  254. FWidth := val;
  255. end;
  256. procedure TSDLWindow.SetHeight(const val: Integer);
  257. begin
  258. if FHeight <> val then
  259. if val > 0 then
  260. FHeight := val;
  261. end;
  262. procedure TSDLWindow.SetPixelDepth(const val: TSDLWindowPixelDepth);
  263. begin
  264. FPixelDepth := val;
  265. end;
  266. procedure TSDLWindow.SetOptions(const val: TSDLWindowOptions);
  267. begin
  268. FOptions := val;
  269. end;
  270. function TSDLWindow.BuildSDLVideoFlags: Cardinal;
  271. var
  272. videoInfo: PSDL_RendererInfo;
  273. begin
  274. SDL_GetRendererInfo(Self, videoInfo);
  275. if not Assigned(videoInfo) then
  276. raise ESDLError.Create('Video query failed.');
  277. Result := 0;
  278. if voOpenGL in Options then
  279. Result := Result + SDL_WINDOW_OPENGL;
  280. if voDoubleBuffer in Options then
  281. Result := Result + SDL_GL_DOUBLEBUFFER;
  282. if voResizable in Options then
  283. Result := Result + SDL_WINDOW_RESIZABLE;
  284. if voFullScreen in Options then
  285. Result := Result + SDL_WINDOW_FULLSCREEN;
  286. if voStencilBuffer in Options then
  287. Result := Result + SDL_SWSURFACE; // for compatibility with SDL 1.2 only!
  288. end;
  289. procedure TSDLWindow.SetSDLGLAttributes;
  290. begin
  291. case PixelDepth of
  292. vpd16bits:
  293. begin
  294. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
  295. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
  296. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
  297. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
  298. end;
  299. vpd24bits:
  300. begin
  301. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  302. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  303. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  304. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  305. end;
  306. else
  307. Assert(False);
  308. end;
  309. if voStencilBuffer in Options then
  310. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8)
  311. else
  312. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
  313. if voDoubleBuffer in Options then
  314. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
  315. else
  316. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0)
  317. end;
  318. procedure TSDLWindow.CreateOrRecreateSDLSurface;
  319. const
  320. cPixelDepthToBpp: array [Low(TSDLWindowPixelDepth)
  321. .. High(TSDLWindowPixelDepth)] of Integer = (16, 24);
  322. var
  323. videoFlags: Integer;
  324. begin
  325. videoFlags := BuildSDLVideoFlags;
  326. if voOpenGL in Options then
  327. SetSDLGLAttributes;
  328. {
  329. SDL_WM_SetCaption(PAnsiChar(AnsiString(FCaption)), nil);
  330. FSDLSurface := SDL_SetVideoMode(Width, Height, cPixelDepthToBpp[PixelDepth], videoFlags);
  331. }
  332. FSDLWindow := SDL_CreateWindow(PChar(AnsiString(FCaption)),
  333. SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height,
  334. videoFlags);
  335. if not Assigned(FSDLSurface) then
  336. RaiseSDLError('Unable to create surface.');
  337. if voOpenGL in Options then
  338. ResizeGLWindow;
  339. end;
  340. procedure TSDLWindow.SetupSDLEnvironmentValues;
  341. var
  342. envVal: String;
  343. begin
  344. if FWindowHandle <> 0 then
  345. begin
  346. envVal := '';
  347. SDL_putenv('SDL_VIDEODRIVER=windib');
  348. envVal := 'SDL_WINDOWID=' + IntToStr(Integer(FWindowHandle));
  349. SDL_putenv(PAnsiChar(AnsiString(envVal)));
  350. end;
  351. end;
  352. procedure TSDLWindow.Open;
  353. begin
  354. if Active then
  355. Exit;
  356. if vSDLActive then
  357. raise ESDLError.Create('Only one SDL window can be opened at a time...')
  358. else
  359. vSDLActive := True;
  360. if SDL_Init(SDL_INIT_VIDEO) < 0 then
  361. raise ESDLError.Create('Could not initialize SDL.');
  362. if voOpenGL in Options then
  363. InitOpenGL;
  364. SetupSDLEnvironmentValues;
  365. CreateOrRecreateSDLSurface;
  366. FActive := True;
  367. if Assigned(FOnOpen) then
  368. FOnOpen(Self);
  369. if Assigned(FOnResize) then
  370. FOnResize(Self);
  371. if ThreadedEventPolling then
  372. StartThread;
  373. end;
  374. procedure TSDLWindow.Close;
  375. begin
  376. if not Active then
  377. Exit;
  378. if Assigned(FOnClose) then
  379. FOnClose(Self);
  380. FActive := False;
  381. StopThread;
  382. SDL_Quit; // SubSystem(SDL_INIT_VIDEO);
  383. FSDLSurface := nil;
  384. vSDLActive := False;
  385. end;
  386. procedure TSDLWindow.UpdateWindow;
  387. begin
  388. if Active then
  389. CreateOrRecreateSDLSurface;
  390. end;
  391. procedure TSDLWindow.SwapBuffers;
  392. begin
  393. if Active then
  394. if voOpenGL in Options then
  395. SDL_GL_SwapWindow(SDLWindow)
  396. else
  397. SDL_RenderPresent(SDLWindow);
  398. end;
  399. procedure TSDLWindow.ResizeGLWindow;
  400. var
  401. RC: TGLContext;
  402. begin
  403. RC := CurrentGLContext;
  404. if Assigned(RC) then
  405. RC.GLStates.ViewPort := Vector4iMake(0, 0, Width, Height);
  406. end;
  407. procedure TSDLWindow.SetActive(const val: Boolean);
  408. begin
  409. if val <> FActive then
  410. if val then
  411. Open
  412. else
  413. Close;
  414. end;
  415. procedure TSDLWindow.SetCaption(const val: String);
  416. begin
  417. if FCaption <> val then
  418. begin
  419. FCaption := val;
  420. if Active then
  421. SDL_SetWindowTitle(nil, PChar(AnsiString(FCaption)));
  422. end;
  423. end;
  424. procedure TSDLWindow.SetThreadSleepLength(const val: Integer);
  425. begin
  426. if val >= 0 then
  427. FThreadSleepLength := val;
  428. end;
  429. procedure TSDLWindow.SetThreadPriority(const val: TThreadPriority);
  430. begin
  431. FThreadPriority := val;
  432. if Assigned(FThread) then
  433. FThread.Priority := val;
  434. end;
  435. procedure TSDLWindow.SetThreadedEventPolling(const val: Boolean);
  436. begin
  437. if FThreadedEventPolling <> val then
  438. begin
  439. FThreadedEventPolling := val;
  440. if ThreadedEventPolling then
  441. begin
  442. if Active and (not Assigned(FThread)) then
  443. StartThread;
  444. end
  445. else if Assigned(FThread) then
  446. StopThread;
  447. end;
  448. end;
  449. procedure TSDLWindow.StartThread;
  450. begin
  451. if Active and ThreadedEventPolling and (not Assigned(FThread)) then
  452. begin
  453. FThread := TSDLEventThread.Create(True);
  454. TSDLEventThread(FThread).Owner := Self;
  455. FThread.Priority := ThreadPriority;
  456. FThread.FreeOnTerminate := True;
  457. FThread.Resume;
  458. end;
  459. end;
  460. procedure TSDLWindow.StopThread;
  461. begin
  462. if Assigned(FThread) then
  463. begin
  464. vSDLCS.Enter;
  465. try
  466. TSDLEventThread(FThread).Owner := nil;
  467. FThread.Terminate;
  468. finally
  469. vSDLCS.Leave;
  470. end;
  471. end;
  472. end;
  473. procedure TSDLWindow.PollEvents;
  474. var
  475. event: TSDL_Event;
  476. begin
  477. if Active then
  478. begin
  479. while SDL_PollEvent(@event) > 0 do
  480. begin
  481. case event.type_ of
  482. SDL_QUITEV:
  483. begin
  484. Close;
  485. Break;
  486. end;
  487. SDL_WINDOWEVENT_RESIZED:
  488. begin
  489. FWidth := event.window.data1; // resize.w
  490. FHeight := event.window.data2; // resize.h
  491. if voOpenGL in Options then
  492. ResizeGLWindow
  493. else
  494. begin
  495. CreateOrRecreateSDLSurface;
  496. if not Assigned(FSDLSurface) then
  497. RaiseSDLError('Could not get a surface after resize.');
  498. end;
  499. if Assigned(FOnResize) then
  500. FOnResize(Self);
  501. end;
  502. else
  503. if Assigned(FOnSDLEvent) then
  504. FOnSDLEvent(Self, event);
  505. end;
  506. end;
  507. if Active then
  508. if Assigned(FOnEventPollDone) then
  509. FOnEventPollDone(Self);
  510. end;
  511. end;
  512. procedure Register;
  513. begin
  514. RegisterComponents('GLScene Utils', [TSDLWindow]);
  515. end;
  516. // ---------------------------------------------------------------------
  517. initialization
  518. // ---------------------------------------------------------------------
  519. // We DON'T free this stuff manually, automatic release will take care of this
  520. vSDLCS := TCriticalSection.Create;
  521. end.