GLCrossPlatform.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLCrossPlatform;
  5. (* Cross platform support functions and types for GLScene *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. Windows,
  10. System.Types,
  11. System.Classes,
  12. System.SysUtils,
  13. System.StrUtils,
  14. VCL.Consts,
  15. VCL.Graphics,
  16. VCL.Controls,
  17. VCL.Forms,
  18. VCL.Dialogs;
  19. type
  20. THalfFloat = type Word;
  21. PHalfFloat = ^THalfFloat;
  22. TGLMouseEvent = procedure(Sender: TObject; Button: TMouseButton;
  23. Shift: TShiftState; X, Y: Integer) of object;
  24. EGLOSError = EOSError;
  25. TGLComponent = class(TComponent);
  26. TProjectTargetNameFunc = function(): string;
  27. const
  28. FONT_CHARS_COUNT = 2024;
  29. var
  30. IsDesignTime: Boolean = False;
  31. vProjectTargetName: TProjectTargetNameFunc;
  32. function GetGLRect(const aLeft, aTop, aRight, aBottom: Integer): TRect;
  33. (* Increases or decreases the width and height of the specified rectangle.
  34. Adds dx units to the left and right ends of the rectangle and dy units to
  35. the top and bottom. *)
  36. procedure InflateGLRect(var aRect: TRect; dx, dy: Integer);
  37. procedure IntersectGLRect(var aRect: TRect; const rect2: TRect);
  38. procedure RaiseLastOSError;
  39. (* Number of pixels per logical inch along the screen width for the device.
  40. Under Win32 awaits a HDC and returns its LOGPIXELSX. *)
  41. function GetDeviceLogicalPixelsX(device: HDC): Integer;
  42. // Number of bits per pixel for the current desktop resolution.
  43. function GetCurrentColorDepth: Integer;
  44. // Returns the number of color bits associated to the given pixel format.
  45. function PixelFormatToColorBits(aPixelFormat: TPixelFormat): Integer;
  46. // Replace path delimiter to delimiter of the current platform.
  47. procedure FixPathDelimiter(var S: string);
  48. // Remove if possible part of path witch leads to project executable.
  49. function RelativePath(const S: string): string;
  50. (* Returns the current value of the highest-resolution counter.
  51. If the platform has none, should return a value derived from the highest
  52. precision time reference available, avoiding, if possible, timers that
  53. allocate specific system resources. *)
  54. procedure QueryPerformanceCounter(out val: Int64);
  55. (* Returns the frequency of the counter used by QueryPerformanceCounter.
  56. Return value is in ticks per second (Hz), returns False if no precision
  57. counter is available. *)
  58. function QueryPerformanceFrequency(out val: Int64): Boolean;
  59. (* Starts a precision timer.
  60. Returned value should just be considered as 'handle', even if it ain't so.
  61. Default platform implementation is to use QueryPerformanceCounter and
  62. QueryPerformanceFrequency, if higher precision references are available,
  63. they should be used. The timer will and must be stopped/terminated/released
  64. with StopPrecisionTimer. *)
  65. function StartPrecisionTimer: Int64;
  66. // Computes time elapsed since timer start. Return time lap in seconds.
  67. function PrecisionTimerLap(const precisionTimer: Int64): Double;
  68. // Computes time elapsed since timer start and stop timer. Return time lap in seconds.
  69. function StopPrecisionTimer(const precisionTimer: Int64): Double;
  70. // Returns time in milisecond from application start.
  71. function AppTime: Double;
  72. // Returns the number of CPU cycles since startup. Use the similarly named CPU instruction.
  73. function GLOKMessageBox(const Text, Caption: string): Integer;
  74. procedure GLLoadBitmapFromInstance(Instance: LongInt; ABitmap: TBitmap; const AName: string);
  75. procedure ShowHTMLUrl(const Url: string);
  76. procedure SetExeDirectory;
  77. // StrUtils.pas
  78. function AnsiStartsText(const ASubText, AText: string): Boolean;
  79. // Classes.pas
  80. function IsSubComponent(const AComponent: TComponent): Boolean; inline;
  81. procedure MakeSubComponent(const AComponent: TComponent; const Value: Boolean);
  82. function FindUnitName(anObject: TObject): string; overload;
  83. function FindUnitName(aClass: TClass): string; overload;
  84. function FloatToHalf(Float: Single): THalfFloat;
  85. function HalfToFloat(Half: THalfFloat): Single;
  86. function GetValueFromStringsIndex(const AStrings: TStrings; const AIndex: Integer): string;
  87. // Determine if the directory is writable.
  88. function IsDirectoryWriteable(const AName: string): Boolean;
  89. function CharToWideChar(const AChar: AnsiChar): WideChar;
  90. //-----------------------------------------------------------
  91. implementation
  92. //-----------------------------------------------------------
  93. uses
  94. ShellApi;
  95. var
  96. vInvPerformanceCounterFrequency: Double;
  97. vInvPerformanceCounterFrequencyReady: Boolean = False;
  98. vLastProjectTargetName: string;
  99. function IsSubComponent(const AComponent: TComponent): Boolean;
  100. begin
  101. Result := (csSubComponent in AComponent.ComponentStyle);
  102. end;
  103. procedure MakeSubComponent(const AComponent: TComponent; const Value: Boolean);
  104. begin
  105. AComponent.SetSubComponent(Value);
  106. end;
  107. function AnsiStartsText(const ASubText, AText: string): Boolean;
  108. begin
  109. Result := AnsiStartsText(ASubText, AText);
  110. end;
  111. function GLOKMessageBox(const Text, Caption: string): Integer;
  112. begin
  113. Result := Application.MessageBox(PChar(Text), PChar(Caption), MB_OK);
  114. end;
  115. procedure GLLoadBitmapFromInstance(Instance: LongInt; ABitmap: TBitmap; const AName: string);
  116. begin
  117. ABitmap.Handle := LoadBitmap(Instance, PChar(AName));
  118. end;
  119. procedure ShowHTMLUrl(const Url: string);
  120. begin
  121. ShellExecute(0, 'open', PChar(Url), nil, nil, SW_SHOW);
  122. end;
  123. function GetGLRect(const aLeft, aTop, aRight, aBottom: Integer): TRect;
  124. begin
  125. Result.Left := aLeft;
  126. Result.Top := aTop;
  127. Result.Right := aRight;
  128. Result.Bottom := aBottom;
  129. end;
  130. procedure InflateGLRect(var aRect: TRect; dx, dy: Integer);
  131. begin
  132. aRect.Left := aRect.Left - dx;
  133. aRect.Right := aRect.Right + dx;
  134. if aRect.Right < aRect.Left then
  135. aRect.Right := aRect.Left;
  136. aRect.Top := aRect.Top - dy;
  137. aRect.Bottom := aRect.Bottom + dy;
  138. if aRect.Bottom < aRect.Top then
  139. aRect.Bottom := aRect.Top;
  140. end;
  141. procedure IntersectGLRect(var aRect: TRect; const rect2: TRect);
  142. var
  143. a: Integer;
  144. begin
  145. if (aRect.Left > rect2.Right) or (aRect.Right < rect2.Left)
  146. or (aRect.Top > rect2.Bottom) or (aRect.Bottom < rect2.Top) then
  147. begin
  148. // no intersection
  149. a := 0;
  150. aRect.Left := a;
  151. aRect.Right := a;
  152. aRect.Top := a;
  153. aRect.Bottom := a;
  154. end
  155. else
  156. begin
  157. if aRect.Left < rect2.Left then
  158. aRect.Left := rect2.Left;
  159. if aRect.Right > rect2.Right then
  160. aRect.Right := rect2.Right;
  161. if aRect.Top < rect2.Top then
  162. aRect.Top := rect2.Top;
  163. if aRect.Bottom > rect2.Bottom then
  164. aRect.Bottom := rect2.Bottom;
  165. end;
  166. end;
  167. procedure RaiseLastOSError;
  168. var
  169. e: EGLOSError;
  170. begin
  171. e := EGLOSError.Create('OS Error : ' + SysErrorMessage(GetLastError));
  172. raise e;
  173. end;
  174. type
  175. TDeviceCapabilities = record
  176. Xdpi, Ydpi: integer; // Number of pixels per logical inch.
  177. Depth: integer; // The bit depth.
  178. NumColors: integer; // Number of entries in the device's color table.
  179. end;
  180. function GetDeviceCapabilities: TDeviceCapabilities;
  181. var
  182. Device: HDC;
  183. begin
  184. Device := GetDC(0);
  185. try
  186. result.Xdpi := GetDeviceCaps(Device, LOGPIXELSX);
  187. result.Ydpi := GetDeviceCaps(Device, LOGPIXELSY);
  188. result.Depth := GetDeviceCaps(Device, BITSPIXEL);
  189. result.NumColors := GetDeviceCaps(Device, NUMCOLORS);
  190. finally
  191. ReleaseDC(0, Device);
  192. end;
  193. end;
  194. function GetDeviceLogicalPixelsX(device: HDC): Integer;
  195. begin
  196. result := GetDeviceCapabilities().Xdpi;
  197. end;
  198. function GetCurrentColorDepth: Integer;
  199. begin
  200. result := GetDeviceCapabilities().Depth;
  201. end;
  202. function PixelFormatToColorBits(aPixelFormat: TPixelFormat): Integer;
  203. begin
  204. case aPixelFormat of
  205. pfCustom{$IFDEF WIN32}, pfDevice{$ENDIF}: // use current color depth
  206. Result := GetCurrentColorDepth;
  207. pf1bit: Result := 1;
  208. {$IFDEF WIN32}
  209. pf4bit: Result := 4;
  210. pf15bit: Result := 15;
  211. {$ENDIF}
  212. pf8bit: Result := 8;
  213. pf16bit: Result := 16;
  214. pf32bit: Result := 32;
  215. else
  216. Result := 24;
  217. end;
  218. end;
  219. procedure FixPathDelimiter(var S: string);
  220. var
  221. I: Integer;
  222. begin
  223. for I := Length(S) downto 1 do
  224. if (S[I] = '/') or (S[I] = '\') then
  225. S[I] := PathDelim;
  226. end;
  227. function RelativePath(const S: string): string;
  228. var
  229. path: string;
  230. begin
  231. Result := S;
  232. if IsDesignTime then
  233. begin
  234. if Assigned(vProjectTargetName) then
  235. begin
  236. path := vProjectTargetName();
  237. if Length(path) = 0 then
  238. path := vLastProjectTargetName
  239. else
  240. vLastProjectTargetName := path;
  241. path := IncludeTrailingPathDelimiter(ExtractFilePath(path));
  242. end
  243. else
  244. exit;
  245. end
  246. else
  247. begin
  248. path := ExtractFilePath(ParamStr(0));
  249. path := IncludeTrailingPathDelimiter(path);
  250. end;
  251. if Pos(path, S) = 1 then
  252. Delete(Result, 1, Length(path));
  253. end;
  254. procedure QueryPerformanceCounter(out val: Int64);
  255. begin
  256. Windows.QueryPerformanceCounter(val);
  257. end;
  258. function QueryPerformanceFrequency(out val: Int64): Boolean;
  259. begin
  260. Result := Boolean(Windows.QueryPerformanceFrequency(val));
  261. end;
  262. function StartPrecisionTimer: Int64;
  263. begin
  264. QueryPerformanceCounter(Result);
  265. end;
  266. function PrecisionTimerLap(const precisionTimer: Int64): Double;
  267. begin
  268. // we can do this, because we don't really stop anything
  269. Result := StopPrecisionTimer(precisionTimer);
  270. end;
  271. function StopPrecisionTimer(const precisionTimer: Int64): Double;
  272. var
  273. cur, freq: Int64;
  274. begin
  275. QueryPerformanceCounter(cur);
  276. if not vInvPerformanceCounterFrequencyReady then
  277. begin
  278. QueryPerformanceFrequency(freq);
  279. vInvPerformanceCounterFrequency := 1.0 / freq;
  280. vInvPerformanceCounterFrequencyReady := True;
  281. end;
  282. Result := (cur - precisionTimer) * vInvPerformanceCounterFrequency;
  283. end;
  284. var
  285. vGLSStartTime : TDateTime;
  286. vLastTime: TDateTime;
  287. vDeltaMilliSecond: TDateTime;
  288. function AppTime: Double;
  289. var
  290. SystemTime: TSystemTime;
  291. begin
  292. GetLocalTime(SystemTime);
  293. with SystemTime do
  294. Result := (wHour * (MinsPerHour * SecsPerMin * MSecsPerSec) +
  295. wMinute * (SecsPerMin * MSecsPerSec) +
  296. wSecond * MSecsPerSec +
  297. wMilliSeconds) - vGLSStartTime;
  298. // Hack to fix time precession
  299. if Result - vLastTime = 0 then
  300. begin
  301. Result := Result + vDeltaMilliSecond;
  302. vDeltaMilliSecond := vDeltaMilliSecond + 0.1;
  303. end
  304. else begin
  305. vLastTime := Result;
  306. vDeltaMilliSecond := 0.1;
  307. end;
  308. end;
  309. function FindUnitName(anObject: TObject): string;
  310. begin
  311. if Assigned(anObject) then
  312. Result := anObject.UnitName
  313. else
  314. Result := '';
  315. end;
  316. function FindUnitName(aClass: TClass): string;
  317. begin
  318. if Assigned(aClass) then
  319. Result := aClass.UnitName
  320. else
  321. Result := '';
  322. end;
  323. procedure SetExeDirectory;
  324. var
  325. path: string;
  326. begin
  327. if IsDesignTime then
  328. begin
  329. if Assigned(vProjectTargetName) then
  330. begin
  331. path := vProjectTargetName();
  332. if Length(path) = 0 then
  333. path := vLastProjectTargetName
  334. else
  335. vLastProjectTargetName := path;
  336. path := IncludeTrailingPathDelimiter(ExtractFilePath(path));
  337. SetCurrentDir(path);
  338. end;
  339. end
  340. else
  341. begin
  342. path := ExtractFilePath(ParamStr(0));
  343. path := IncludeTrailingPathDelimiter(path);
  344. SetCurrentDir(path);
  345. end;
  346. end;
  347. function HalfToFloat(Half: THalfFloat): Single;
  348. var
  349. Dst, Sign, Mantissa: LongWord;
  350. Exp: LongInt;
  351. begin
  352. // extract sign, exponent, and mantissa from half number
  353. Sign := Half shr 15;
  354. Exp := (Half and $7C00) shr 10;
  355. Mantissa := Half and 1023;
  356. if (Exp > 0) and (Exp < 31) then
  357. begin
  358. // common normalized number
  359. Exp := Exp + (127 - 15);
  360. Mantissa := Mantissa shl 13;
  361. Dst := (Sign shl 31) or (LongWord(Exp) shl 23) or Mantissa;
  362. // Result := Power(-1, Sign) * Power(2, Exp - 15) * (1 + Mantissa / 1024);
  363. end
  364. else if (Exp = 0) and (Mantissa = 0) then
  365. begin
  366. // zero - preserve sign
  367. Dst := Sign shl 31;
  368. end
  369. else if (Exp = 0) and (Mantissa <> 0) then
  370. begin
  371. // denormalized number - renormalize it
  372. while (Mantissa and $00000400) = 0 do
  373. begin
  374. Mantissa := Mantissa shl 1;
  375. Dec(Exp);
  376. end;
  377. Inc(Exp);
  378. Mantissa := Mantissa and not $00000400;
  379. // now assemble normalized number
  380. Exp := Exp + (127 - 15);
  381. Mantissa := Mantissa shl 13;
  382. Dst := (Sign shl 31) or (LongWord(Exp) shl 23) or Mantissa;
  383. // Result := Power(-1, Sign) * Power(2, -14) * (Mantissa / 1024);
  384. end
  385. else if (Exp = 31) and (Mantissa = 0) then
  386. begin
  387. // +/- infinity
  388. Dst := (Sign shl 31) or $7F800000;
  389. end
  390. else //if (Exp = 31) and (Mantisa <> 0) then
  391. begin
  392. // not a number - preserve sign and mantissa
  393. Dst := (Sign shl 31) or $7F800000 or (Mantissa shl 13);
  394. end;
  395. // reinterpret LongWord as Single
  396. Result := PSingle(@Dst)^;
  397. end;
  398. function FloatToHalf(Float: Single): THalfFloat;
  399. var
  400. Src: LongWord;
  401. Sign, Exp, Mantissa: LongInt;
  402. begin
  403. Src := PLongWord(@Float)^;
  404. // extract sign, exponent, and mantissa from Single number
  405. Sign := Src shr 31;
  406. Exp := LongInt((Src and $7F800000) shr 23) - 127 + 15;
  407. Mantissa := Src and $007FFFFF;
  408. if (Exp > 0) and (Exp < 30) then
  409. begin
  410. // simple case - round the significand and combine it with the sign and exponent
  411. Result := (Sign shl 15) or (Exp shl 10) or ((Mantissa + $00001000) shr 13);
  412. end
  413. else if Src = 0 then
  414. begin
  415. // input float is zero - return zero
  416. Result := 0;
  417. end
  418. else
  419. begin
  420. // difficult case - lengthy conversion
  421. if Exp <= 0 then
  422. begin
  423. if Exp < -10 then
  424. begin
  425. // input float's value is less than HalfMin, return zero
  426. Result := 0;
  427. end
  428. else
  429. begin
  430. // Float is a normalized Single whose magnitude is less than HalfNormMin.
  431. // We convert it to denormalized half.
  432. Mantissa := (Mantissa or $00800000) shr (1 - Exp);
  433. // round to nearest
  434. if (Mantissa and $00001000) > 0 then
  435. Mantissa := Mantissa + $00002000;
  436. // assemble Sign and Mantissa (Exp is zero to get denotmalized number)
  437. Result := (Sign shl 15) or (Mantissa shr 13);
  438. end;
  439. end
  440. else if Exp = 255 - 127 + 15 then
  441. begin
  442. if Mantissa = 0 then
  443. begin
  444. // input float is infinity, create infinity half with original sign
  445. Result := (Sign shl 15) or $7C00;
  446. end
  447. else
  448. begin
  449. // input float is NaN, create half NaN with original sign and mantissa
  450. Result := (Sign shl 15) or $7C00 or (Mantissa shr 13);
  451. end;
  452. end
  453. else
  454. begin
  455. // Exp is > 0 so input float is normalized Single
  456. // round to nearest
  457. if (Mantissa and $00001000) > 0 then
  458. begin
  459. Mantissa := Mantissa + $00002000;
  460. if (Mantissa and $00800000) > 0 then
  461. begin
  462. Mantissa := 0;
  463. Exp := Exp + 1;
  464. end;
  465. end;
  466. if Exp > 30 then
  467. begin
  468. // exponent overflow - return infinity half
  469. Result := (Sign shl 15) or $7C00;
  470. end
  471. else
  472. // assemble normalized half
  473. Result := (Sign shl 15) or (Exp shl 10) or (Mantissa shr 13);
  474. end;
  475. end;
  476. end;
  477. function GetValueFromStringsIndex(const AStrings: TStrings; const AIndex: Integer): string;
  478. begin
  479. Result := AStrings.ValueFromIndex[AIndex];
  480. end;
  481. function IsDirectoryWriteable(const AName: string): Boolean;
  482. var
  483. LFileName: String;
  484. LHandle: THandle;
  485. begin
  486. LFileName := IncludeTrailingPathDelimiter(AName) + 'chk.tmp';
  487. LHandle := CreateFile(PChar(LFileName), GENERIC_READ or GENERIC_WRITE, 0, nil,
  488. CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
  489. Result := LHandle <> INVALID_HANDLE_VALUE;
  490. if Result then
  491. CloseHandle(LHandle);
  492. end;
  493. function CharToWideChar(const AChar: AnsiChar): WideChar;
  494. var
  495. lResult: PWideChar;
  496. begin
  497. GetMem(lResult, 2);
  498. MultiByteToWideChar(CP_ACP, 0, @AChar, 1, lResult, 2);
  499. Result := lResult^;
  500. FreeMem(lResult, 2);
  501. end;
  502. //----------------------------------------
  503. initialization
  504. //----------------------------------------
  505. vGLSStartTime := AppTime;
  506. end.