You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stats-panel.ts 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /*
  2. * encantar.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. * stats-panel.ts
  20. * Stats panel used for development purposes
  21. */
  22. import { SpeedySize } from 'speedy-vision/types/core/speedy-size';
  23. import { Settings, PowerPreference } from '../core/settings';
  24. import { Viewport } from '../core/viewport';
  25. import { Tracker } from '../trackers/tracker';
  26. import { Source } from '../sources/source';
  27. import { Utils, Nullable } from '../utils/utils';
  28. import AR from '../main';
  29. /** Update interval, in ms */
  30. const UPDATE_INTERVAL = 500;
  31. /** Icons for different power profiles */
  32. const POWER_ICON: { readonly [P in PowerPreference]: string } = Object.freeze({
  33. 'default': '',
  34. 'low-power': '&#x1F50B',
  35. 'high-performance': '&#x26A1'
  36. });
  37. /**
  38. * Stats panel used for development purposes
  39. */
  40. export class StatsPanel
  41. {
  42. /** The viewport associated to this panel */
  43. private readonly _viewport: Viewport;
  44. /** A container for the panel */
  45. private readonly _container: HTMLDivElement;
  46. /** Time of last update, in milliseconds */
  47. private _lastUpdate: DOMHighResTimeStamp;
  48. /**
  49. * Constructor
  50. * @param viewport Viewport
  51. */
  52. constructor(viewport: Viewport)
  53. {
  54. this._viewport = viewport;
  55. this._lastUpdate = 0;
  56. this._container = this._createContainer();
  57. viewport.hud.container.appendChild(this._container);
  58. }
  59. /**
  60. * Release the panel
  61. */
  62. release(): void
  63. {
  64. this._container.remove();
  65. }
  66. /**
  67. * A method to be called in the update loop
  68. * @param time current time in ms
  69. * @param trackers the trackers attached to the session
  70. * @param sources the sources of media linked to the session
  71. * @param gpu GPU cycles per second
  72. * @param fps frames per second
  73. */
  74. update(time: DOMHighResTimeStamp, trackers: Tracker[], sources: Source[], gpu: number, fps: number): void
  75. {
  76. if(time >= this._lastUpdate + UPDATE_INTERVAL) {
  77. this._lastUpdate = time;
  78. this._update(trackers, sources, fps, gpu);
  79. }
  80. }
  81. /**
  82. * Visibility of the panel
  83. */
  84. get visible(): boolean
  85. {
  86. return !this._container.hidden;
  87. }
  88. /**
  89. * Visibility of the panel
  90. */
  91. set visible(visible: boolean)
  92. {
  93. this._container.hidden = !visible;
  94. }
  95. /**
  96. * Update the contents of the panel
  97. * @param trackers the trackers attached to the session
  98. * @param sources the sources of media linked to the session
  99. * @param fps frames per second
  100. * @param gpu GPU cycles per second
  101. */
  102. private _update(trackers: Tracker[], sources: Source[], fps: number, gpu: number): void
  103. {
  104. // all sanitized
  105. const lfps = this._label('_ar_fps');
  106. if(lfps !== null) {
  107. lfps.style.color = this._color(fps);
  108. lfps.innerText = String(fps);
  109. }
  110. const lgpu = this._label('_ar_gpu');
  111. if(lgpu !== null) {
  112. lgpu.style.color = this._color(gpu);
  113. lgpu.innerText = String(gpu);
  114. }
  115. const lpower = this._label('_ar_power');
  116. if(lpower !== null)
  117. lpower.innerHTML = POWER_ICON[Settings.powerPreference];
  118. const lin = this._label('_ar_in');
  119. if(lin !== null) {
  120. const sourceStats = sources.map(source => source._stats).join(', ');
  121. lin.innerText = sourceStats;
  122. }
  123. const lout = this._label('_ar_out');
  124. if(lout !== null) {
  125. const trackerStats = trackers.map(tracker => tracker._stats).join(', ');
  126. lout.innerText = trackerStats;
  127. }
  128. }
  129. /**
  130. * Get a label of the panel
  131. * @param className
  132. * @returns the HTML element, or null if it doesn't exist
  133. */
  134. private _label(className: string): Nullable<HTMLElement>
  135. {
  136. return this._container.getElementsByClassName(className).item(0) as Nullable<HTMLElement>;
  137. }
  138. /**
  139. * Associate a color to a frequency number
  140. * @param f frequency given in cycles per second
  141. * @returns colorized number (HTML)
  142. */
  143. private _color(f: number): string
  144. {
  145. const GREEN = '#0f0', YELLOW = '#ff0', RED = '#f33';
  146. const color3 = f >= 50 ? GREEN : (f >= 30 ? YELLOW : RED);
  147. const color2 = f >= 30 ? GREEN : RED;
  148. const color = Settings.powerPreference != 'low-power' ? color3 : color2;
  149. return color;
  150. }
  151. /**
  152. * Create the container for the panel
  153. * @returns a container
  154. */
  155. private _createContainer(): HTMLDivElement
  156. {
  157. const container = document.createElement('div');
  158. container.style.position = 'absolute';
  159. container.style.left = container.style.top = '0px';
  160. container.style.zIndex = '1000000';
  161. container.style.padding = '0px';
  162. container.appendChild(this._createTitle());
  163. container.appendChild(this._createContent());
  164. return container;
  165. }
  166. /**
  167. * Create a title
  168. * @returns a title
  169. */
  170. private _createTitle(): HTMLElement
  171. {
  172. const title = document.createElement('div');
  173. title.style.backgroundColor = '#7e56c2';
  174. title.style.color = 'white';
  175. title.style.fontFamily = 'monospace';
  176. title.style.fontSize = '14px';
  177. title.style.fontWeight = 'bold';
  178. title.style.padding = '2px';
  179. title.innerText = 'encantar.js ' + AR.version;
  180. return title;
  181. }
  182. /**
  183. * Create a content container
  184. * @returns a content container
  185. */
  186. private _createContent(): HTMLElement
  187. {
  188. const content = document.createElement('div');
  189. const print = (html: string): void => content.insertAdjacentHTML('beforeend', html);
  190. content.style.backgroundColor = 'rgba(0,0,0,0.5)';
  191. content.style.color = 'white';
  192. content.style.fontFamily = 'monospace';
  193. content.style.fontSize = '14px';
  194. content.style.padding = '2px';
  195. content.style.whiteSpace = 'pre-line';
  196. // all sanitized
  197. print('FPS: <span class="_ar_fps"></span> | ');
  198. print('GPU: <span class="_ar_gpu"></span> ');
  199. print('<span class="_ar_power"></span>');
  200. print('<br>');
  201. print('IN: <span class="_ar_in"></span>');
  202. print('<br>');
  203. print('OUT: <span class="_ar_out"></span>');
  204. return content;
  205. }
  206. }