engine.occlusionQuery.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import { Nullable, int } from "../../types";
  2. import { Engine } from "../../Engines/engine";
  3. import { AbstractMesh } from "../../Meshes/abstractMesh";
  4. import { _TimeToken } from "../../Instrumentation/timeToken";
  5. /** @hidden */
  6. export class _OcclusionDataStorage {
  7. /** @hidden */
  8. public occlusionInternalRetryCounter = 0;
  9. /** @hidden */
  10. public isOcclusionQueryInProgress = false;
  11. /** @hidden */
  12. public isOccluded = false;
  13. /** @hidden */
  14. public occlusionRetryCount = -1;
  15. /** @hidden */
  16. public occlusionType = AbstractMesh.OCCLUSION_TYPE_NONE;
  17. /** @hidden */
  18. public occlusionQueryAlgorithmType = AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE;
  19. }
  20. declare module "../../Engines/engine" {
  21. export interface Engine {
  22. /**
  23. * Create a new webGL query (you must be sure that queries are supported by checking getCaps() function)
  24. * @return the new query
  25. */
  26. createQuery(): WebGLQuery;
  27. /**
  28. * Delete and release a webGL query
  29. * @param query defines the query to delete
  30. * @return the current engine
  31. */
  32. deleteQuery(query: WebGLQuery): Engine;
  33. /**
  34. * Check if a given query has resolved and got its value
  35. * @param query defines the query to check
  36. * @returns true if the query got its value
  37. */
  38. isQueryResultAvailable(query: WebGLQuery): boolean;
  39. /**
  40. * Gets the value of a given query
  41. * @param query defines the query to check
  42. * @returns the value of the query
  43. */
  44. getQueryResult(query: WebGLQuery): number;
  45. /**
  46. * Initiates an occlusion query
  47. * @param algorithmType defines the algorithm to use
  48. * @param query defines the query to use
  49. * @returns the current engine
  50. * @see https://doc.babylonjs.com/features/occlusionquery
  51. */
  52. beginOcclusionQuery(algorithmType: number, query: WebGLQuery): Engine;
  53. /**
  54. * Ends an occlusion query
  55. * @see https://doc.babylonjs.com/features/occlusionquery
  56. * @param algorithmType defines the algorithm to use
  57. * @returns the current engine
  58. */
  59. endOcclusionQuery(algorithmType: number): Engine;
  60. /**
  61. * Starts a time query (used to measure time spent by the GPU on a specific frame)
  62. * Please note that only one query can be issued at a time
  63. * @returns a time token used to track the time span
  64. */
  65. startTimeQuery(): Nullable<_TimeToken>;
  66. /**
  67. * Ends a time query
  68. * @param token defines the token used to measure the time span
  69. * @returns the time spent (in ns)
  70. */
  71. endTimeQuery(token: _TimeToken): int;
  72. /** @hidden */
  73. _currentNonTimestampToken: Nullable<_TimeToken>;
  74. /** @hidden */
  75. _createTimeQuery(): WebGLQuery;
  76. /** @hidden */
  77. _deleteTimeQuery(query: WebGLQuery): void;
  78. /** @hidden */
  79. _getGlAlgorithmType(algorithmType: number): number;
  80. /** @hidden */
  81. _getTimeQueryResult(query: WebGLQuery): any;
  82. /** @hidden */
  83. _getTimeQueryAvailability(query: WebGLQuery): any;
  84. }
  85. }
  86. Engine.prototype.createQuery = function(): WebGLQuery {
  87. return this._gl.createQuery();
  88. };
  89. Engine.prototype.deleteQuery = function(query: WebGLQuery): Engine {
  90. this._gl.deleteQuery(query);
  91. return this;
  92. };
  93. Engine.prototype.isQueryResultAvailable = function(query: WebGLQuery): boolean {
  94. return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT_AVAILABLE) as boolean;
  95. };
  96. Engine.prototype.getQueryResult = function(query: WebGLQuery): number {
  97. return this._gl.getQueryParameter(query, this._gl.QUERY_RESULT) as number;
  98. };
  99. Engine.prototype.beginOcclusionQuery = function(algorithmType: number, query: WebGLQuery): Engine {
  100. var glAlgorithm = this._getGlAlgorithmType(algorithmType);
  101. this._gl.beginQuery(glAlgorithm, query);
  102. return this;
  103. };
  104. Engine.prototype.endOcclusionQuery = function(algorithmType: number): Engine {
  105. var glAlgorithm = this._getGlAlgorithmType(algorithmType);
  106. this._gl.endQuery(glAlgorithm);
  107. return this;
  108. };
  109. Engine.prototype._createTimeQuery = function(): WebGLQuery {
  110. let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
  111. if (timerQuery.createQueryEXT) {
  112. return timerQuery.createQueryEXT();
  113. }
  114. return this.createQuery();
  115. };
  116. Engine.prototype._deleteTimeQuery = function(query: WebGLQuery): void {
  117. let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
  118. if (timerQuery.deleteQueryEXT) {
  119. timerQuery.deleteQueryEXT(query);
  120. return;
  121. }
  122. this.deleteQuery(query);
  123. };
  124. Engine.prototype._getTimeQueryResult = function(query: WebGLQuery): any {
  125. let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
  126. if (timerQuery.getQueryObjectEXT) {
  127. return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_EXT);
  128. }
  129. return this.getQueryResult(query);
  130. };
  131. Engine.prototype._getTimeQueryAvailability = function(query: WebGLQuery): any {
  132. let timerQuery = <EXT_disjoint_timer_query>this.getCaps().timerQuery;
  133. if (timerQuery.getQueryObjectEXT) {
  134. return timerQuery.getQueryObjectEXT(query, timerQuery.QUERY_RESULT_AVAILABLE_EXT);
  135. }
  136. return this.isQueryResultAvailable(query);
  137. };
  138. Engine.prototype.startTimeQuery = function(): Nullable<_TimeToken> {
  139. let caps = this.getCaps();
  140. let timerQuery = caps.timerQuery;
  141. if (!timerQuery) {
  142. return null;
  143. }
  144. let token = new _TimeToken();
  145. this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
  146. if (caps.canUseTimestampForTimerQuery) {
  147. token._startTimeQuery = this._createTimeQuery();
  148. timerQuery.queryCounterEXT(token._startTimeQuery, timerQuery.TIMESTAMP_EXT);
  149. } else {
  150. if (this._currentNonTimestampToken) {
  151. return this._currentNonTimestampToken;
  152. }
  153. token._timeElapsedQuery = this._createTimeQuery();
  154. if (timerQuery.beginQueryEXT) {
  155. timerQuery.beginQueryEXT(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
  156. } else {
  157. this._gl.beginQuery(timerQuery.TIME_ELAPSED_EXT, token._timeElapsedQuery);
  158. }
  159. this._currentNonTimestampToken = token;
  160. }
  161. return token;
  162. };
  163. Engine.prototype.endTimeQuery = function(token: _TimeToken): int {
  164. let caps = this.getCaps();
  165. let timerQuery = caps.timerQuery;
  166. if (!timerQuery || !token) {
  167. return -1;
  168. }
  169. if (caps.canUseTimestampForTimerQuery) {
  170. if (!token._startTimeQuery) {
  171. return -1;
  172. }
  173. if (!token._endTimeQuery) {
  174. token._endTimeQuery = this._createTimeQuery();
  175. timerQuery.queryCounterEXT(token._endTimeQuery, timerQuery.TIMESTAMP_EXT);
  176. }
  177. } else if (!token._timeElapsedQueryEnded) {
  178. if (!token._timeElapsedQuery) {
  179. return -1;
  180. }
  181. if (timerQuery.endQueryEXT) {
  182. timerQuery.endQueryEXT(timerQuery.TIME_ELAPSED_EXT);
  183. } else {
  184. this._gl.endQuery(timerQuery.TIME_ELAPSED_EXT);
  185. }
  186. token._timeElapsedQueryEnded = true;
  187. }
  188. let disjoint = this._gl.getParameter(timerQuery.GPU_DISJOINT_EXT);
  189. let available: boolean = false;
  190. if (token._endTimeQuery) {
  191. available = this._getTimeQueryAvailability(token._endTimeQuery);
  192. } else if (token._timeElapsedQuery) {
  193. available = this._getTimeQueryAvailability(token._timeElapsedQuery);
  194. }
  195. if (available && !disjoint) {
  196. let result = 0;
  197. if (caps.canUseTimestampForTimerQuery) {
  198. if (!token._startTimeQuery || !token._endTimeQuery) {
  199. return -1;
  200. }
  201. let timeStart = this._getTimeQueryResult(token._startTimeQuery);
  202. let timeEnd = this._getTimeQueryResult(token._endTimeQuery);
  203. result = timeEnd - timeStart;
  204. this._deleteTimeQuery(token._startTimeQuery);
  205. this._deleteTimeQuery(token._endTimeQuery);
  206. token._startTimeQuery = null;
  207. token._endTimeQuery = null;
  208. } else {
  209. if (!token._timeElapsedQuery) {
  210. return -1;
  211. }
  212. result = this._getTimeQueryResult(token._timeElapsedQuery);
  213. this._deleteTimeQuery(token._timeElapsedQuery);
  214. token._timeElapsedQuery = null;
  215. token._timeElapsedQueryEnded = false;
  216. this._currentNonTimestampToken = null;
  217. }
  218. return result;
  219. }
  220. return -1;
  221. };
  222. Engine.prototype._getGlAlgorithmType = function(algorithmType: number): number {
  223. return algorithmType === AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE ? this._gl.ANY_SAMPLES_PASSED_CONSERVATIVE : this._gl.ANY_SAMPLES_PASSED;
  224. };
  225. declare module "../../Meshes/abstractMesh" {
  226. export interface AbstractMesh {
  227. /**
  228. * Backing filed
  229. * @hidden
  230. */
  231. __occlusionDataStorage: _OcclusionDataStorage;
  232. /**
  233. * Access property
  234. * @hidden
  235. */
  236. _occlusionDataStorage: _OcclusionDataStorage;
  237. /**
  238. * This number indicates the number of allowed retries before stop the occlusion query, this is useful if the occlusion query is taking long time before to the query result is retireved, the query result indicates if the object is visible within the scene or not and based on that Babylon.Js engine decideds to show or hide the object.
  239. * The default value is -1 which means don't break the query and wait till the result
  240. * @see https://doc.babylonjs.com/features/occlusionquery
  241. */
  242. occlusionRetryCount: number;
  243. /**
  244. * This property is responsible for starting the occlusion query within the Mesh or not, this property is also used to determine what should happen when the occlusionRetryCount is reached. It has supports 3 values:
  245. * * OCCLUSION_TYPE_NONE (Default Value): this option means no occlusion query whith the Mesh.
  246. * * OCCLUSION_TYPE_OPTIMISTIC: this option is means use occlusion query and if occlusionRetryCount is reached and the query is broken show the mesh.
  247. * * OCCLUSION_TYPE_STRICT: this option is means use occlusion query and if occlusionRetryCount is reached and the query is broken restore the last state of the mesh occlusion if the mesh was visible then show the mesh if was hidden then hide don't show.
  248. * @see https://doc.babylonjs.com/features/occlusionquery
  249. */
  250. occlusionType: number;
  251. /**
  252. * This property determines the type of occlusion query algorithm to run in WebGl, you can use:
  253. * * AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE which is mapped to GL_ANY_SAMPLES_PASSED.
  254. * * AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE (Default Value) which is mapped to GL_ANY_SAMPLES_PASSED_CONSERVATIVE which is a false positive algorithm that is faster than GL_ANY_SAMPLES_PASSED but less accurate.
  255. * @see https://doc.babylonjs.com/features/occlusionquery
  256. */
  257. occlusionQueryAlgorithmType: number;
  258. /**
  259. * Gets or sets whether the mesh is occluded or not, it is used also to set the intial state of the mesh to be occluded or not
  260. * @see https://doc.babylonjs.com/features/occlusionquery
  261. */
  262. isOccluded: boolean;
  263. /**
  264. * Flag to check the progress status of the query
  265. * @see https://doc.babylonjs.com/features/occlusionquery
  266. */
  267. isOcclusionQueryInProgress: boolean;
  268. }
  269. }
  270. Object.defineProperty(AbstractMesh.prototype, "isOcclusionQueryInProgress", {
  271. get: function(this: AbstractMesh) {
  272. return this._occlusionDataStorage.isOcclusionQueryInProgress;
  273. },
  274. set: function(this: AbstractMesh, value: boolean) {
  275. this._occlusionDataStorage.isOcclusionQueryInProgress = value;
  276. },
  277. enumerable: false,
  278. configurable: true
  279. });
  280. Object.defineProperty(AbstractMesh.prototype, "_occlusionDataStorage", {
  281. get: function(this: AbstractMesh) {
  282. if (!this.__occlusionDataStorage) {
  283. this.__occlusionDataStorage = new _OcclusionDataStorage();
  284. }
  285. return this.__occlusionDataStorage;
  286. },
  287. enumerable: false,
  288. configurable: true
  289. });
  290. Object.defineProperty(AbstractMesh.prototype, "isOccluded", {
  291. get: function(this: AbstractMesh) {
  292. return this._occlusionDataStorage.isOccluded;
  293. },
  294. set: function(this: AbstractMesh, value: boolean) {
  295. this._occlusionDataStorage.isOccluded = value;
  296. },
  297. enumerable: true,
  298. configurable: true
  299. });
  300. Object.defineProperty(AbstractMesh.prototype, "occlusionQueryAlgorithmType", {
  301. get: function(this: AbstractMesh) {
  302. return this._occlusionDataStorage.occlusionQueryAlgorithmType;
  303. },
  304. set: function(this: AbstractMesh, value: number) {
  305. this._occlusionDataStorage.occlusionQueryAlgorithmType = value;
  306. },
  307. enumerable: true,
  308. configurable: true
  309. });
  310. Object.defineProperty(AbstractMesh.prototype, "occlusionType", {
  311. get: function(this: AbstractMesh) {
  312. return this._occlusionDataStorage.occlusionType;
  313. },
  314. set: function(this: AbstractMesh, value: number) {
  315. this._occlusionDataStorage.occlusionType = value;
  316. },
  317. enumerable: true,
  318. configurable: true
  319. });
  320. Object.defineProperty(AbstractMesh.prototype, "occlusionRetryCount", {
  321. get: function(this: AbstractMesh) {
  322. return this._occlusionDataStorage.occlusionRetryCount;
  323. },
  324. set: function(this: AbstractMesh, value: number) {
  325. this._occlusionDataStorage.occlusionRetryCount = value;
  326. },
  327. enumerable: true,
  328. configurable: true
  329. });
  330. // We also need to update AbstractMesh as there is a portion of the code there
  331. AbstractMesh.prototype._checkOcclusionQuery = function() {
  332. let dataStorage = this._occlusionDataStorage;
  333. if (dataStorage.occlusionType === AbstractMesh.OCCLUSION_TYPE_NONE) {
  334. dataStorage.isOccluded = false;
  335. return false;
  336. }
  337. var engine = this.getEngine();
  338. if (engine.webGLVersion < 2) {
  339. dataStorage.isOccluded = false;
  340. return false;
  341. }
  342. if (!engine.isQueryResultAvailable) { // Occlusion query where not referenced
  343. dataStorage.isOccluded = false;
  344. return false;
  345. }
  346. if (this.isOcclusionQueryInProgress && this._occlusionQuery) {
  347. var isOcclusionQueryAvailable = engine.isQueryResultAvailable(this._occlusionQuery);
  348. if (isOcclusionQueryAvailable) {
  349. var occlusionQueryResult = engine.getQueryResult(this._occlusionQuery);
  350. dataStorage.isOcclusionQueryInProgress = false;
  351. dataStorage.occlusionInternalRetryCounter = 0;
  352. dataStorage.isOccluded = occlusionQueryResult === 1 ? false : true;
  353. }
  354. else {
  355. dataStorage.occlusionInternalRetryCounter++;
  356. if (dataStorage.occlusionRetryCount !== -1 && dataStorage.occlusionInternalRetryCounter > dataStorage.occlusionRetryCount) {
  357. dataStorage.isOcclusionQueryInProgress = false;
  358. dataStorage.occlusionInternalRetryCounter = 0;
  359. // if optimistic set isOccluded to false regardless of the status of isOccluded. (Render in the current render loop)
  360. // if strict continue the last state of the object.
  361. dataStorage.isOccluded = dataStorage.occlusionType === AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC ? false : dataStorage.isOccluded;
  362. }
  363. else {
  364. return false;
  365. }
  366. }
  367. }
  368. var scene = this.getScene();
  369. if (scene.getBoundingBoxRenderer) {
  370. var occlusionBoundingBoxRenderer = scene.getBoundingBoxRenderer();
  371. if (!this._occlusionQuery) {
  372. this._occlusionQuery = engine.createQuery();
  373. }
  374. engine.beginOcclusionQuery(dataStorage.occlusionQueryAlgorithmType, this._occlusionQuery);
  375. occlusionBoundingBoxRenderer.renderOcclusionBoundingBox(this);
  376. engine.endOcclusionQuery(dataStorage.occlusionQueryAlgorithmType);
  377. this._occlusionDataStorage.isOcclusionQueryInProgress = true;
  378. }
  379. return dataStorage.isOccluded;
  380. };