SDLx.Window.pas 17 KB

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