sceneOptimizer.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. import { Scene, IDisposable } from "../scene";
  2. import { EngineStore } from "../Engines/engineStore";
  3. import { AbstractMesh } from "../Meshes/abstractMesh";
  4. import { Mesh } from "../Meshes/mesh";
  5. import { Nullable } from "../types";
  6. import { Observer, Observable } from "./observable";
  7. /**
  8. * Defines the root class used to create scene optimization to use with SceneOptimizer
  9. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  10. */
  11. export class SceneOptimization {
  12. /**
  13. * Gets a string describing the action executed by the current optimization
  14. * @returns description string
  15. */
  16. public getDescription(): string {
  17. return "";
  18. }
  19. /**
  20. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  21. * @param scene defines the current scene where to apply this optimization
  22. * @param optimizer defines the current optimizer
  23. * @returns true if everything that can be done was applied
  24. */
  25. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  26. return true;
  27. }
  28. /**
  29. * Creates the SceneOptimization object
  30. * @param priority defines the priority of this optimization (0 by default which means first in the list)
  31. * @param desc defines the description associated with the optimization
  32. */
  33. constructor(
  34. /**
  35. * Defines the priority of this optimization (0 by default which means first in the list)
  36. */
  37. public priority: number = 0) {
  38. }
  39. }
  40. /**
  41. * Defines an optimization used to reduce the size of render target textures
  42. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  43. */
  44. export class TextureOptimization extends SceneOptimization {
  45. /**
  46. * Gets a string describing the action executed by the current optimization
  47. * @returns description string
  48. */
  49. public getDescription(): string {
  50. return "Reducing render target texture size to " + this.maximumSize;
  51. }
  52. /**
  53. * Creates the TextureOptimization object
  54. * @param priority defines the priority of this optimization (0 by default which means first in the list)
  55. * @param maximumSize defines the maximum sized allowed for textures (1024 is the default value). If a texture is bigger, it will be scaled down using a factor defined by the step parameter
  56. * @param step defines the factor (0.5 by default) used to scale down textures bigger than maximum sized allowed.
  57. */
  58. constructor(
  59. /**
  60. * Defines the priority of this optimization (0 by default which means first in the list)
  61. */
  62. public priority: number = 0,
  63. /**
  64. * Defines the maximum sized allowed for textures (1024 is the default value). If a texture is bigger, it will be scaled down using a factor defined by the step parameter
  65. */
  66. public maximumSize: number = 1024,
  67. /**
  68. * Defines the factor (0.5 by default) used to scale down textures bigger than maximum sized allowed.
  69. */
  70. public step = 0.5) {
  71. super(priority);
  72. }
  73. /**
  74. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  75. * @param scene defines the current scene where to apply this optimization
  76. * @param optimizer defines the current optimizer
  77. * @returns true if everything that can be done was applied
  78. */
  79. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  80. var allDone = true;
  81. for (var index = 0; index < scene.textures.length; index++) {
  82. var texture = scene.textures[index];
  83. if (!texture.canRescale || (<any>texture).getContext) {
  84. continue;
  85. }
  86. var currentSize = texture.getSize();
  87. var maxDimension = Math.max(currentSize.width, currentSize.height);
  88. if (maxDimension > this.maximumSize) {
  89. texture.scale(this.step);
  90. allDone = false;
  91. }
  92. }
  93. return allDone;
  94. }
  95. }
  96. /**
  97. * Defines an optimization used to increase or decrease the rendering resolution
  98. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  99. */
  100. export class HardwareScalingOptimization extends SceneOptimization {
  101. private _currentScale = -1;
  102. private _directionOffset = 1;
  103. /**
  104. * Gets a string describing the action executed by the current optimization
  105. * @return description string
  106. */
  107. public getDescription(): string {
  108. return "Setting hardware scaling level to " + this._currentScale;
  109. }
  110. /**
  111. * Creates the HardwareScalingOptimization object
  112. * @param priority defines the priority of this optimization (0 by default which means first in the list)
  113. * @param maximumScale defines the maximum scale to use (2 by default)
  114. * @param step defines the step to use between two passes (0.5 by default)
  115. */
  116. constructor(
  117. /**
  118. * Defines the priority of this optimization (0 by default which means first in the list)
  119. */
  120. public priority: number = 0,
  121. /**
  122. * Defines the maximum scale to use (2 by default)
  123. */
  124. public maximumScale: number = 2,
  125. /**
  126. * Defines the step to use between two passes (0.5 by default)
  127. */
  128. public step: number = 0.25) {
  129. super(priority);
  130. }
  131. /**
  132. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  133. * @param scene defines the current scene where to apply this optimization
  134. * @param optimizer defines the current optimizer
  135. * @returns true if everything that can be done was applied
  136. */
  137. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  138. if (this._currentScale === -1) {
  139. this._currentScale = scene.getEngine().getHardwareScalingLevel();
  140. if (this._currentScale > this.maximumScale) {
  141. this._directionOffset = -1;
  142. }
  143. }
  144. this._currentScale += this._directionOffset * this.step;
  145. scene.getEngine().setHardwareScalingLevel(this._currentScale);
  146. return this._directionOffset === 1 ? this._currentScale >= this.maximumScale : this._currentScale <= this.maximumScale;
  147. }
  148. }
  149. /**
  150. * Defines an optimization used to remove shadows
  151. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  152. */
  153. export class ShadowsOptimization extends SceneOptimization {
  154. /**
  155. * Gets a string describing the action executed by the current optimization
  156. * @return description string
  157. */
  158. public getDescription(): string {
  159. return "Turning shadows on/off";
  160. }
  161. /**
  162. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  163. * @param scene defines the current scene where to apply this optimization
  164. * @param optimizer defines the current optimizer
  165. * @returns true if everything that can be done was applied
  166. */
  167. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  168. scene.shadowsEnabled = optimizer.isInImprovementMode;
  169. return true;
  170. }
  171. }
  172. /**
  173. * Defines an optimization used to turn post-processes off
  174. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  175. */
  176. export class PostProcessesOptimization extends SceneOptimization {
  177. /**
  178. * Gets a string describing the action executed by the current optimization
  179. * @return description string
  180. */
  181. public getDescription(): string {
  182. return "Turning post-processes on/off";
  183. }
  184. /**
  185. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  186. * @param scene defines the current scene where to apply this optimization
  187. * @param optimizer defines the current optimizer
  188. * @returns true if everything that can be done was applied
  189. */
  190. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  191. scene.postProcessesEnabled = optimizer.isInImprovementMode;
  192. return true;
  193. }
  194. }
  195. /**
  196. * Defines an optimization used to turn lens flares off
  197. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  198. */
  199. export class LensFlaresOptimization extends SceneOptimization {
  200. /**
  201. * Gets a string describing the action executed by the current optimization
  202. * @return description string
  203. */
  204. public getDescription(): string {
  205. return "Turning lens flares on/off";
  206. }
  207. /**
  208. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  209. * @param scene defines the current scene where to apply this optimization
  210. * @param optimizer defines the current optimizer
  211. * @returns true if everything that can be done was applied
  212. */
  213. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  214. scene.lensFlaresEnabled = optimizer.isInImprovementMode;
  215. return true;
  216. }
  217. }
  218. /**
  219. * Defines an optimization based on user defined callback.
  220. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  221. */
  222. export class CustomOptimization extends SceneOptimization {
  223. /**
  224. * Callback called to apply the custom optimization.
  225. */
  226. public onApply: (scene: Scene, optimizer: SceneOptimizer) => boolean;
  227. /**
  228. * Callback called to get custom description
  229. */
  230. public onGetDescription: () => string;
  231. /**
  232. * Gets a string describing the action executed by the current optimization
  233. * @returns description string
  234. */
  235. public getDescription(): string {
  236. if (this.onGetDescription) {
  237. return this.onGetDescription();
  238. }
  239. return "Running user defined callback";
  240. }
  241. /**
  242. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  243. * @param scene defines the current scene where to apply this optimization
  244. * @param optimizer defines the current optimizer
  245. * @returns true if everything that can be done was applied
  246. */
  247. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  248. if (this.onApply) {
  249. return this.onApply(scene, optimizer);
  250. }
  251. return true;
  252. }
  253. }
  254. /**
  255. * Defines an optimization used to turn particles off
  256. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  257. */
  258. export class ParticlesOptimization extends SceneOptimization {
  259. /**
  260. * Gets a string describing the action executed by the current optimization
  261. * @return description string
  262. */
  263. public getDescription(): string {
  264. return "Turning particles on/off";
  265. }
  266. /**
  267. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  268. * @param scene defines the current scene where to apply this optimization
  269. * @param optimizer defines the current optimizer
  270. * @returns true if everything that can be done was applied
  271. */
  272. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  273. scene.particlesEnabled = optimizer.isInImprovementMode;
  274. return true;
  275. }
  276. }
  277. /**
  278. * Defines an optimization used to turn render targets off
  279. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  280. */
  281. export class RenderTargetsOptimization extends SceneOptimization {
  282. /**
  283. * Gets a string describing the action executed by the current optimization
  284. * @return description string
  285. */
  286. public getDescription(): string {
  287. return "Turning render targets off";
  288. }
  289. /**
  290. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  291. * @param scene defines the current scene where to apply this optimization
  292. * @param optimizer defines the current optimizer
  293. * @returns true if everything that can be done was applied
  294. */
  295. public apply(scene: Scene, optimizer: SceneOptimizer): boolean {
  296. scene.renderTargetsEnabled = optimizer.isInImprovementMode;
  297. return true;
  298. }
  299. }
  300. /**
  301. * Defines an optimization used to merge meshes with compatible materials
  302. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  303. */
  304. export class MergeMeshesOptimization extends SceneOptimization {
  305. private static _UpdateSelectionTree = false;
  306. /**
  307. * Gets or sets a boolean which defines if optimization octree has to be updated
  308. */
  309. public static get UpdateSelectionTree(): boolean {
  310. return MergeMeshesOptimization._UpdateSelectionTree;
  311. }
  312. /**
  313. * Gets or sets a boolean which defines if optimization octree has to be updated
  314. */
  315. public static set UpdateSelectionTree(value: boolean) {
  316. MergeMeshesOptimization._UpdateSelectionTree = value;
  317. }
  318. /**
  319. * Gets a string describing the action executed by the current optimization
  320. * @return description string
  321. */
  322. public getDescription(): string {
  323. return "Merging similar meshes together";
  324. }
  325. private _canBeMerged = (abstractMesh: AbstractMesh): boolean => {
  326. if (!(abstractMesh instanceof Mesh)) {
  327. return false;
  328. }
  329. var mesh = <Mesh>abstractMesh;
  330. if (mesh.isDisposed()) {
  331. return false;
  332. }
  333. if (!mesh.isVisible || !mesh.isEnabled()) {
  334. return false;
  335. }
  336. if (mesh.instances.length > 0) {
  337. return false;
  338. }
  339. if (mesh.skeleton || mesh.hasLODLevels) {
  340. return false;
  341. }
  342. return true;
  343. }
  344. /**
  345. * This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
  346. * @param scene defines the current scene where to apply this optimization
  347. * @param optimizer defines the current optimizer
  348. * @param updateSelectionTree defines that the selection octree has to be updated (false by default)
  349. * @returns true if everything that can be done was applied
  350. */
  351. public apply(scene: Scene, optimizer: SceneOptimizer, updateSelectionTree?: boolean): boolean {
  352. var globalPool = scene.meshes.slice(0);
  353. var globalLength = globalPool.length;
  354. for (var index = 0; index < globalLength; index++) {
  355. var currentPool = new Array<Mesh>();
  356. var current = globalPool[index];
  357. // Checks
  358. if (!this._canBeMerged(current)) {
  359. continue;
  360. }
  361. currentPool.push(<Mesh>current);
  362. // Find compatible meshes
  363. for (var subIndex = index + 1; subIndex < globalLength; subIndex++) {
  364. var otherMesh = globalPool[subIndex];
  365. if (!this._canBeMerged(otherMesh)) {
  366. continue;
  367. }
  368. if (otherMesh.material !== current.material) {
  369. continue;
  370. }
  371. if (otherMesh.checkCollisions !== current.checkCollisions) {
  372. continue;
  373. }
  374. currentPool.push(<Mesh>otherMesh);
  375. globalLength--;
  376. globalPool.splice(subIndex, 1);
  377. subIndex--;
  378. }
  379. if (currentPool.length < 2) {
  380. continue;
  381. }
  382. // Merge meshes
  383. Mesh.MergeMeshes(currentPool, undefined, true);
  384. }
  385. // Call the octree system optimization if it is defined.
  386. const sceneAsAny = scene as any;
  387. if (sceneAsAny.createOrUpdateSelectionOctree) {
  388. if (updateSelectionTree != undefined) {
  389. if (updateSelectionTree) {
  390. sceneAsAny.createOrUpdateSelectionOctree();
  391. }
  392. }
  393. else if (MergeMeshesOptimization.UpdateSelectionTree) {
  394. sceneAsAny.createOrUpdateSelectionOctree();
  395. }
  396. }
  397. return true;
  398. }
  399. }
  400. /**
  401. * Defines a list of options used by SceneOptimizer
  402. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  403. */
  404. export class SceneOptimizerOptions {
  405. /**
  406. * Gets the list of optimizations to apply
  407. */
  408. public optimizations = new Array<SceneOptimization>();
  409. /**
  410. * Creates a new list of options used by SceneOptimizer
  411. * @param targetFrameRate defines the target frame rate to reach (60 by default)
  412. * @param trackerDuration defines the interval between two checkes (2000ms by default)
  413. */
  414. constructor(
  415. /**
  416. * Defines the target frame rate to reach (60 by default)
  417. */
  418. public targetFrameRate: number = 60,
  419. /**
  420. * Defines the interval between two checkes (2000ms by default)
  421. */
  422. public trackerDuration: number = 2000) {
  423. }
  424. /**
  425. * Add a new optimization
  426. * @param optimization defines the SceneOptimization to add to the list of active optimizations
  427. * @returns the current SceneOptimizerOptions
  428. */
  429. public addOptimization(optimization: SceneOptimization): SceneOptimizerOptions {
  430. this.optimizations.push(optimization);
  431. return this;
  432. }
  433. /**
  434. * Add a new custom optimization
  435. * @param onApply defines the callback called to apply the custom optimization (true if everything that can be done was applied)
  436. * @param onGetDescription defines the callback called to get the description attached with the optimization.
  437. * @param priority defines the priority of this optimization (0 by default which means first in the list)
  438. * @returns the current SceneOptimizerOptions
  439. */
  440. public addCustomOptimization(onApply: (scene: Scene) => boolean, onGetDescription: () => string, priority: number = 0): SceneOptimizerOptions {
  441. let optimization = new CustomOptimization(priority);
  442. optimization.onApply = onApply;
  443. optimization.onGetDescription = onGetDescription;
  444. this.optimizations.push(optimization);
  445. return this;
  446. }
  447. /**
  448. * Creates a list of pre-defined optimizations aimed to reduce the visual impact on the scene
  449. * @param targetFrameRate defines the target frame rate (60 by default)
  450. * @returns a SceneOptimizerOptions object
  451. */
  452. public static LowDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions {
  453. var result = new SceneOptimizerOptions(targetFrameRate);
  454. var priority = 0;
  455. result.addOptimization(new MergeMeshesOptimization(priority));
  456. result.addOptimization(new ShadowsOptimization(priority));
  457. result.addOptimization(new LensFlaresOptimization(priority));
  458. // Next priority
  459. priority++;
  460. result.addOptimization(new PostProcessesOptimization(priority));
  461. result.addOptimization(new ParticlesOptimization(priority));
  462. // Next priority
  463. priority++;
  464. result.addOptimization(new TextureOptimization(priority, 1024));
  465. return result;
  466. }
  467. /**
  468. * Creates a list of pre-defined optimizations aimed to have a moderate impact on the scene visual
  469. * @param targetFrameRate defines the target frame rate (60 by default)
  470. * @returns a SceneOptimizerOptions object
  471. */
  472. public static ModerateDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions {
  473. var result = new SceneOptimizerOptions(targetFrameRate);
  474. var priority = 0;
  475. result.addOptimization(new MergeMeshesOptimization(priority));
  476. result.addOptimization(new ShadowsOptimization(priority));
  477. result.addOptimization(new LensFlaresOptimization(priority));
  478. // Next priority
  479. priority++;
  480. result.addOptimization(new PostProcessesOptimization(priority));
  481. result.addOptimization(new ParticlesOptimization(priority));
  482. // Next priority
  483. priority++;
  484. result.addOptimization(new TextureOptimization(priority, 512));
  485. // Next priority
  486. priority++;
  487. result.addOptimization(new RenderTargetsOptimization(priority));
  488. // Next priority
  489. priority++;
  490. result.addOptimization(new HardwareScalingOptimization(priority, 2));
  491. return result;
  492. }
  493. /**
  494. * Creates a list of pre-defined optimizations aimed to have a big impact on the scene visual
  495. * @param targetFrameRate defines the target frame rate (60 by default)
  496. * @returns a SceneOptimizerOptions object
  497. */
  498. public static HighDegradationAllowed(targetFrameRate?: number): SceneOptimizerOptions {
  499. var result = new SceneOptimizerOptions(targetFrameRate);
  500. var priority = 0;
  501. result.addOptimization(new MergeMeshesOptimization(priority));
  502. result.addOptimization(new ShadowsOptimization(priority));
  503. result.addOptimization(new LensFlaresOptimization(priority));
  504. // Next priority
  505. priority++;
  506. result.addOptimization(new PostProcessesOptimization(priority));
  507. result.addOptimization(new ParticlesOptimization(priority));
  508. // Next priority
  509. priority++;
  510. result.addOptimization(new TextureOptimization(priority, 256));
  511. // Next priority
  512. priority++;
  513. result.addOptimization(new RenderTargetsOptimization(priority));
  514. // Next priority
  515. priority++;
  516. result.addOptimization(new HardwareScalingOptimization(priority, 4));
  517. return result;
  518. }
  519. }
  520. /**
  521. * Class used to run optimizations in order to reach a target frame rate
  522. * @description More details at http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer
  523. */
  524. export class SceneOptimizer implements IDisposable {
  525. private _isRunning = false;
  526. private _options: SceneOptimizerOptions;
  527. private _scene: Scene;
  528. private _currentPriorityLevel = 0;
  529. private _targetFrameRate = 60;
  530. private _trackerDuration = 2000;
  531. private _currentFrameRate = 0;
  532. private _sceneDisposeObserver: Nullable<Observer<Scene>>;
  533. private _improvementMode = false;
  534. /**
  535. * Defines an observable called when the optimizer reaches the target frame rate
  536. */
  537. public onSuccessObservable = new Observable<SceneOptimizer>();
  538. /**
  539. * Defines an observable called when the optimizer enables an optimization
  540. */
  541. public onNewOptimizationAppliedObservable = new Observable<SceneOptimization>();
  542. /**
  543. * Defines an observable called when the optimizer is not able to reach the target frame rate
  544. */
  545. public onFailureObservable = new Observable<SceneOptimizer>();
  546. /**
  547. * Gets a boolean indicating if the optimizer is in improvement mode
  548. */
  549. public get isInImprovementMode(): boolean {
  550. return this._improvementMode;
  551. }
  552. /**
  553. * Gets the current priority level (0 at start)
  554. */
  555. public get currentPriorityLevel(): number {
  556. return this._currentPriorityLevel;
  557. }
  558. /**
  559. * Gets the current frame rate checked by the SceneOptimizer
  560. */
  561. public get currentFrameRate(): number {
  562. return this._currentFrameRate;
  563. }
  564. /**
  565. * Gets or sets the current target frame rate (60 by default)
  566. */
  567. public get targetFrameRate(): number {
  568. return this._targetFrameRate;
  569. }
  570. /**
  571. * Gets or sets the current target frame rate (60 by default)
  572. */
  573. public set targetFrameRate(value: number) {
  574. this._targetFrameRate = value;
  575. }
  576. /**
  577. * Gets or sets the current interval between two checks (every 2000ms by default)
  578. */
  579. public get trackerDuration(): number {
  580. return this._trackerDuration;
  581. }
  582. /**
  583. * Gets or sets the current interval between two checks (every 2000ms by default)
  584. */
  585. public set trackerDuration(value: number) {
  586. this._trackerDuration = value;
  587. }
  588. /**
  589. * Gets the list of active optimizations
  590. */
  591. public get optimizations(): SceneOptimization[] {
  592. return this._options.optimizations;
  593. }
  594. /**
  595. * Creates a new SceneOptimizer
  596. * @param scene defines the scene to work on
  597. * @param options defines the options to use with the SceneOptimizer
  598. * @param autoGeneratePriorities defines if priorities must be generated and not read from SceneOptimization property (true by default)
  599. * @param improvementMode defines if the scene optimizer must run the maximum optimization while staying over a target frame instead of trying to reach the target framerate (false by default)
  600. */
  601. public constructor(scene: Scene, options?: SceneOptimizerOptions, autoGeneratePriorities = true, improvementMode = false) {
  602. if (!options) {
  603. this._options = new SceneOptimizerOptions();
  604. } else {
  605. this._options = options;
  606. }
  607. if (this._options.targetFrameRate) {
  608. this._targetFrameRate = this._options.targetFrameRate;
  609. }
  610. if (this._options.trackerDuration) {
  611. this._trackerDuration = this._options.trackerDuration;
  612. }
  613. if (autoGeneratePriorities) {
  614. let priority = 0;
  615. for (var optim of this._options.optimizations) {
  616. optim.priority = priority++;
  617. }
  618. }
  619. this._improvementMode = improvementMode;
  620. this._scene = scene || EngineStore.LastCreatedScene;
  621. this._sceneDisposeObserver = this._scene.onDisposeObservable.add(() => {
  622. this._sceneDisposeObserver = null;
  623. this.dispose();
  624. });
  625. }
  626. /**
  627. * Stops the current optimizer
  628. */
  629. public stop() {
  630. this._isRunning = false;
  631. }
  632. /**
  633. * Reset the optimizer to initial step (current priority level = 0)
  634. */
  635. public reset() {
  636. this._currentPriorityLevel = 0;
  637. }
  638. /**
  639. * Start the optimizer. By default it will try to reach a specific framerate
  640. * but if the optimizer is set with improvementMode === true then it will run all optimiatiation while frame rate is above the target frame rate
  641. */
  642. public start() {
  643. if (this._isRunning) {
  644. return;
  645. }
  646. this._isRunning = true;
  647. // Let's wait for the scene to be ready before running our check
  648. this._scene.executeWhenReady(() => {
  649. setTimeout(() => {
  650. this._checkCurrentState();
  651. }, this._trackerDuration);
  652. });
  653. }
  654. private _checkCurrentState() {
  655. if (!this._isRunning) {
  656. return;
  657. }
  658. let scene = this._scene;
  659. let options = this._options;
  660. this._currentFrameRate = Math.round(scene.getEngine().getFps());
  661. if (this._improvementMode && this._currentFrameRate <= this._targetFrameRate ||
  662. !this._improvementMode && this._currentFrameRate >= this._targetFrameRate) {
  663. this._isRunning = false;
  664. this.onSuccessObservable.notifyObservers(this);
  665. return;
  666. }
  667. // Apply current level of optimizations
  668. var allDone = true;
  669. var noOptimizationApplied = true;
  670. for (var index = 0; index < options.optimizations.length; index++) {
  671. var optimization = options.optimizations[index];
  672. if (optimization.priority === this._currentPriorityLevel) {
  673. noOptimizationApplied = false;
  674. allDone = allDone && optimization.apply(scene, this);
  675. this.onNewOptimizationAppliedObservable.notifyObservers(optimization);
  676. }
  677. }
  678. // If no optimization was applied, this is a failure :(
  679. if (noOptimizationApplied) {
  680. this._isRunning = false;
  681. this.onFailureObservable.notifyObservers(this);
  682. return;
  683. }
  684. // If all optimizations were done, move to next level
  685. if (allDone) {
  686. this._currentPriorityLevel++;
  687. }
  688. // Let's the system running for a specific amount of time before checking FPS
  689. scene.executeWhenReady(() => {
  690. setTimeout(() => {
  691. this._checkCurrentState();
  692. }, this._trackerDuration);
  693. });
  694. }
  695. /**
  696. * Release all resources
  697. */
  698. public dispose(): void {
  699. this.stop();
  700. this.onSuccessObservable.clear();
  701. this.onFailureObservable.clear();
  702. this.onNewOptimizationAppliedObservable.clear();
  703. if (this._sceneDisposeObserver) {
  704. this._scene.onDisposeObservable.remove(this._sceneDisposeObserver);
  705. }
  706. }
  707. /**
  708. * Helper function to create a SceneOptimizer with one single line of code
  709. * @param scene defines the scene to work on
  710. * @param options defines the options to use with the SceneOptimizer
  711. * @param onSuccess defines a callback to call on success
  712. * @param onFailure defines a callback to call on failure
  713. * @returns the new SceneOptimizer object
  714. */
  715. public static OptimizeAsync(scene: Scene, options?: SceneOptimizerOptions, onSuccess?: () => void, onFailure?: () => void): SceneOptimizer {
  716. let optimizer = new SceneOptimizer(scene, options || SceneOptimizerOptions.ModerateDegradationAllowed(), false);
  717. if (onSuccess) {
  718. optimizer.onSuccessObservable.add(() => {
  719. onSuccess();
  720. });
  721. }
  722. if (onFailure) {
  723. optimizer.onFailureObservable.add(() => {
  724. onFailure();
  725. });
  726. }
  727. optimizer.start();
  728. return optimizer;
  729. }
  730. }