engine.occlusionQuery.ts 17 KB

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