GLS.SDL.Window.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.SDL.Window;
  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. uses
  15. System.Classes,
  16. System.SysUtils,
  17. System.SyncObjs,
  18. Stage.OpenGLTokens,
  19. Stage.VectorGeometry,
  20. GLS.OpenGLAdapter,
  21. Stage.VectorTypes,
  22. GLS.State,
  23. GLS.Context,
  24. SDL.Import;
  25. type
  26. (* Pixel Depth options.
  27. vpd16bits: 16bpp graphics (565) (and 16 bits depth buffer for OpenGL)
  28. vpd24bits: 24bpp graphics (565) (and 24 bits depth buffer for OpenGL) *)
  29. TSDLWindowPixelDepth = (vpd16bits, vpd24bits);
  30. (* Specifies optional settings for the SDL window.
  31. Those options are a simplified subset of the SDL options:
  32. voDoubleBuffer: create a double-buffered window
  33. voOpenGL: requires OpenGL capability for the window
  34. voResizable: window should be resizable
  35. voFullScreen: requires a full screen "window" (screen resolution may be changed)
  36. voStencilBuffer: requires a stencil buffer (8bits, use along voOpenGL) *)
  37. TSDLWindowOption = (voDoubleBuffer, voHardwareAccel, voOpenGL, voResizable,
  38. voFullScreen, voStencilBuffer);
  39. TSDLWindowOptions = set of TSDLWindowOption;
  40. TSDLEvent = procedure(sender: TObject; const event: TSDL_Event) of object;
  41. const
  42. cDefaultSDLWindowOptions = [voDoubleBuffer, voHardwareAccel, voOpenGL,
  43. 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;
  189. external 'MSVCRT.DLL';
  190. function SDL_putenv(const variable: PAnsiChar): Integer;
  191. begin
  192. Result := _putenv(variable);
  193. end;
  194. function getenv(const name: PAnsiChar): PAnsiChar; cdecl; external 'MSVCRT.DLL';
  195. function SDL_getenv(const name: PAnsiChar): PAnsiChar;
  196. begin
  197. Result := getenv(name);
  198. end;
  199. // ------------------
  200. // ------------------ TSDLEventThread ------------------
  201. // ------------------
  202. procedure TSDLEventThread.Execute;
  203. begin
  204. try
  205. while not Terminated do
  206. begin
  207. vSDLCS.Enter;
  208. try
  209. SDL_Delay(Owner.ThreadSleepLength);
  210. finally
  211. vSDLCS.Leave;
  212. end;
  213. Synchronize(DoPollEvents);
  214. end;
  215. except
  216. // bail out asap, problem wasn't here anyway
  217. end;
  218. vSDLCS.Enter;
  219. try
  220. if Assigned(Owner) then
  221. Owner.FThread := nil;
  222. finally
  223. vSDLCS.Leave;
  224. end;
  225. end;
  226. procedure TSDLEventThread.DoPollEvents;
  227. begin
  228. // no need for a CS here, we're in the main thread
  229. if Assigned(Owner) then
  230. Owner.PollEvents;
  231. end;
  232. // ------------------
  233. // ------------------ TSDLWindow ------------------
  234. // ------------------
  235. constructor TSDLWindow.Create(AOwner: TComponent);
  236. begin
  237. inherited Create(AOwner);
  238. FWidth := 640;
  239. FHeight := 480;
  240. FPixelDepth := vpd24bits;
  241. FThreadedEventPolling := True;
  242. FThreadSleepLength := 1;
  243. FThreadPriority := tpLower;
  244. FOptions := cDefaultSDLWindowOptions;
  245. end;
  246. destructor TSDLWindow.Destroy;
  247. begin
  248. Close;
  249. inherited Destroy;
  250. end;
  251. procedure TSDLWindow.SetWidth(const val: Integer);
  252. begin
  253. if FWidth <> val then
  254. if val > 0 then
  255. FWidth := val;
  256. end;
  257. procedure TSDLWindow.SetHeight(const val: Integer);
  258. begin
  259. if FHeight <> val then
  260. if val > 0 then
  261. FHeight := val;
  262. end;
  263. procedure TSDLWindow.SetPixelDepth(const val: TSDLWindowPixelDepth);
  264. begin
  265. FPixelDepth := val;
  266. end;
  267. procedure TSDLWindow.SetOptions(const val: TSDLWindowOptions);
  268. begin
  269. FOptions := val;
  270. end;
  271. function TSDLWindow.BuildSDLVideoFlags: Cardinal;
  272. var
  273. videoInfo: PSDL_RendererInfo;
  274. begin
  275. SDL_GetRendererInfo(Self, videoInfo);
  276. if not Assigned(videoInfo) then
  277. raise ESDLError.Create('Video query failed.');
  278. Result := 0;
  279. if voOpenGL in Options then
  280. Result := Result + SDL_WINDOW_OPENGL;
  281. if voDoubleBuffer in Options then
  282. Result := Result + SDL_GL_DOUBLEBUFFER;
  283. if voResizable in Options then
  284. Result := Result + SDL_WINDOW_RESIZABLE;
  285. if voFullScreen in Options then
  286. Result := Result + SDL_WINDOW_FULLSCREEN;
  287. if voStencilBuffer in Options then
  288. Result := Result + SDL_SWSURFACE; // for compatibility with SDL 1.2 only!
  289. end;
  290. procedure TSDLWindow.SetSDLGLAttributes;
  291. begin
  292. case PixelDepth of
  293. vpd16bits:
  294. begin
  295. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
  296. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
  297. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
  298. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
  299. end;
  300. vpd24bits:
  301. begin
  302. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  303. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  304. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  305. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  306. end;
  307. else
  308. Assert(False);
  309. end;
  310. if voStencilBuffer in Options then
  311. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8)
  312. else
  313. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
  314. if voDoubleBuffer in Options then
  315. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
  316. else
  317. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0)
  318. end;
  319. procedure TSDLWindow.CreateOrRecreateSDLSurface;
  320. const
  321. cPixelDepthToBpp: array [Low(TSDLWindowPixelDepth)
  322. .. High(TSDLWindowPixelDepth)] of Integer = (16, 24);
  323. var
  324. videoFlags: Integer;
  325. begin
  326. videoFlags := BuildSDLVideoFlags;
  327. if voOpenGL in Options then
  328. SetSDLGLAttributes;
  329. (*
  330. SDL_WM_SetCaption(PAnsiChar(AnsiString(FCaption)), nil);
  331. FSDLSurface := SDL_SetVideoMode(Width, Height, cPixelDepthToBpp[PixelDepth], videoFlags);
  332. *)
  333. FSDLWindow := SDL_CreateWindow(PChar(AnsiString(FCaption)),
  334. SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height,
  335. videoFlags);
  336. if not Assigned(FSDLSurface) then
  337. RaiseSDLError('Unable to create surface.');
  338. if voOpenGL in Options then
  339. ResizeGLWindow;
  340. end;
  341. procedure TSDLWindow.SetupSDLEnvironmentValues;
  342. var
  343. envVal: String;
  344. begin
  345. if FWindowHandle <> 0 then
  346. begin
  347. envVal := '';
  348. SDL_putenv('SDL_VIDEODRIVER=windib');
  349. envVal := 'SDL_WINDOWID=' + IntToStr(Integer(FWindowHandle));
  350. SDL_putenv(PAnsiChar(AnsiString(envVal)));
  351. end;
  352. end;
  353. procedure TSDLWindow.Open;
  354. begin
  355. if Active then
  356. Exit;
  357. if vSDLActive then
  358. raise ESDLError.Create('Only one SDL window can be opened at a time...')
  359. else
  360. vSDLActive := True;
  361. if SDL_Init(SDL_INIT_VIDEO) < 0 then
  362. raise ESDLError.Create('Could not initialize SDL.');
  363. if voOpenGL in Options then
  364. InitOpenGL;
  365. SetupSDLEnvironmentValues;
  366. CreateOrRecreateSDLSurface;
  367. FActive := True;
  368. if Assigned(FOnOpen) then
  369. FOnOpen(Self);
  370. if Assigned(FOnResize) then
  371. FOnResize(Self);
  372. if ThreadedEventPolling then
  373. StartThread;
  374. end;
  375. procedure TSDLWindow.Close;
  376. begin
  377. if not Active then
  378. Exit;
  379. if Assigned(FOnClose) then
  380. FOnClose(Self);
  381. FActive := False;
  382. StopThread;
  383. SDL_Quit; // SubSystem(SDL_INIT_VIDEO);
  384. FSDLSurface := nil;
  385. vSDLActive := False;
  386. end;
  387. procedure TSDLWindow.UpdateWindow;
  388. begin
  389. if Active then
  390. CreateOrRecreateSDLSurface;
  391. end;
  392. procedure TSDLWindow.SwapBuffers;
  393. begin
  394. if Active then
  395. if voOpenGL in Options then
  396. SDL_GL_SwapWindow(SDLWindow)
  397. else
  398. SDL_RenderPresent(SDLWindow);
  399. end;
  400. procedure TSDLWindow.ResizeGLWindow;
  401. var
  402. RC: TGLContext;
  403. begin
  404. RC := CurrentGLContext;
  405. if Assigned(RC) then
  406. RC.GLStates.ViewPort := Vector4iMake(0, 0, Width, Height);
  407. end;
  408. procedure TSDLWindow.SetActive(const val: Boolean);
  409. begin
  410. if val <> FActive then
  411. if val then
  412. Open
  413. else
  414. Close;
  415. end;
  416. procedure TSDLWindow.SetCaption(const val: String);
  417. begin
  418. if FCaption <> val then
  419. begin
  420. FCaption := val;
  421. if Active then
  422. SDL_SetWindowTitle(nil, PChar(AnsiString(FCaption)));
  423. end;
  424. end;
  425. procedure TSDLWindow.SetThreadSleepLength(const val: Integer);
  426. begin
  427. if val >= 0 then
  428. FThreadSleepLength := val;
  429. end;
  430. procedure TSDLWindow.SetThreadPriority(const val: TThreadPriority);
  431. begin
  432. FThreadPriority := val;
  433. if Assigned(FThread) then
  434. FThread.Priority := val;
  435. end;
  436. procedure TSDLWindow.SetThreadedEventPolling(const val: Boolean);
  437. begin
  438. if FThreadedEventPolling <> val then
  439. begin
  440. FThreadedEventPolling := val;
  441. if ThreadedEventPolling then
  442. begin
  443. if Active and (not Assigned(FThread)) then
  444. StartThread;
  445. end
  446. else if Assigned(FThread) then
  447. StopThread;
  448. end;
  449. end;
  450. procedure TSDLWindow.StartThread;
  451. begin
  452. if Active and ThreadedEventPolling and (not Assigned(FThread)) then
  453. begin
  454. FThread := TSDLEventThread.Create(True);
  455. TSDLEventThread(FThread).Owner := Self;
  456. FThread.Priority := ThreadPriority;
  457. FThread.FreeOnTerminate := True;
  458. FThread.Resume;
  459. end;
  460. end;
  461. procedure TSDLWindow.StopThread;
  462. begin
  463. if Assigned(FThread) then
  464. begin
  465. vSDLCS.Enter;
  466. try
  467. TSDLEventThread(FThread).Owner := nil;
  468. FThread.Terminate;
  469. finally
  470. vSDLCS.Leave;
  471. end;
  472. end;
  473. end;
  474. procedure TSDLWindow.PollEvents;
  475. var
  476. event: TSDL_Event;
  477. begin
  478. if Active then
  479. begin
  480. while SDL_PollEvent(@event) > 0 do
  481. begin
  482. case event.type_ of
  483. SDL_QUITEV:
  484. begin
  485. Close;
  486. Break;
  487. end;
  488. SDL_WINDOWEVENT_RESIZED:
  489. begin
  490. FWidth := event.window.data1; // resize.w
  491. FHeight := event.window.data2; // resize.h
  492. if voOpenGL in Options then
  493. ResizeGLWindow
  494. else
  495. begin
  496. CreateOrRecreateSDLSurface;
  497. if not Assigned(FSDLSurface) then
  498. RaiseSDLError('Could not get a surface after resize.');
  499. end;
  500. if Assigned(FOnResize) then
  501. FOnResize(Self);
  502. end;
  503. else
  504. if Assigned(FOnSDLEvent) then
  505. FOnSDLEvent(Self, event);
  506. end;
  507. end;
  508. if Active then
  509. if Assigned(FOnEventPollDone) then
  510. FOnEventPollDone(Self);
  511. end;
  512. end;
  513. procedure Register;
  514. begin
  515. RegisterComponents('GLScene Utils', [TSDLWindow]);
  516. end;
  517. initialization // -----------------------------------------------------------
  518. // We DON'T free this stuff manually, automatic release will take care of this
  519. vSDLCS := TCriticalSection.Create;
  520. end.