GLS.SDLWindow.pas 17 KB

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