JobScheduler.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import defined from '../Core/defined.js';
  2. import defineProperties from '../Core/defineProperties.js';
  3. import DeveloperError from '../Core/DeveloperError.js';
  4. import getTimestamp from '../Core/getTimestamp.js';
  5. import JobType from './JobType.js';
  6. function JobTypeBudget(total) {
  7. /**
  8. * Total budget, in milliseconds, allowed for one frame
  9. */
  10. this._total = total;
  11. /**
  12. * Time, in milliseconds, used so far during this frame
  13. */
  14. this.usedThisFrame = 0.0;
  15. /**
  16. * Time, in milliseconds, that other job types stole this frame
  17. */
  18. this.stolenFromMeThisFrame = 0.0;
  19. /**
  20. * Indicates if this job type was starved this frame, i.e., a job
  21. * tried to run but didn't have budget
  22. */
  23. this.starvedThisFrame = false;
  24. /**
  25. * Indicates if this job was starved last frame. This prevents it
  26. * from being stolen from this frame.
  27. */
  28. this.starvedLastFrame = false;
  29. }
  30. defineProperties(JobTypeBudget.prototype, {
  31. total : {
  32. get : function() {
  33. return this._total;
  34. }
  35. }
  36. });
  37. /**
  38. * Engine for time slicing jobs during a frame to amortize work over multiple frames. This supports:
  39. * <ul>
  40. * <li>
  41. * Separate budgets for different job types, e.g., texture, shader program, and buffer creation. This
  42. * allows all job types to make progress each frame.
  43. * </li>
  44. * <li>
  45. * Stealing from other jobs type budgets if they were not exhausted in the previous frame. This allows
  46. * using the entire budget for all job types each frame even if, for example, all the jobs are the same type.
  47. * </li>
  48. * <li>
  49. * Guaranteed progress on all job types each frame, even if it means exceeding the total budget for the frame.
  50. * This prevents, for example, several expensive texture uploads over many frames from prevent a shader compile.
  51. * </li>
  52. * </ul>
  53. *
  54. * @private
  55. */
  56. function JobScheduler(budgets) {
  57. //>>includeStart('debug', pragmas.debug);
  58. if (defined(budgets) && (budgets.length !== JobType.NUMBER_OF_JOB_TYPES)) {
  59. throw new DeveloperError('A budget must be specified for each job type; budgets.length should equal JobType.NUMBER_OF_JOB_TYPES.');
  60. }
  61. //>>includeEnd('debug');
  62. // Total for defaults is half of of one frame at 10 fps
  63. var jobBudgets = new Array(JobType.NUMBER_OF_JOB_TYPES);
  64. jobBudgets[JobType.TEXTURE] = new JobTypeBudget(defined(budgets) ? budgets[JobType.TEXTURE] : 10.0);
  65. // On cache miss, this most likely only allows one shader compile per frame
  66. jobBudgets[JobType.PROGRAM] = new JobTypeBudget(defined(budgets) ? budgets[JobType.PROGRAM] : 10.0);
  67. jobBudgets[JobType.BUFFER] = new JobTypeBudget(defined(budgets) ? budgets[JobType.BUFFER] : 30.0);
  68. var length = jobBudgets.length;
  69. var i;
  70. var totalBudget = 0.0;
  71. for (i = 0; i < length; ++i) {
  72. totalBudget += jobBudgets[i].total;
  73. }
  74. var executedThisFrame = new Array(length);
  75. for (i = 0; i < length; ++i) {
  76. executedThisFrame[i] = false;
  77. }
  78. this._totalBudget = totalBudget;
  79. this._totalUsedThisFrame = 0.0;
  80. this._budgets = jobBudgets;
  81. this._executedThisFrame = executedThisFrame;
  82. }
  83. // For unit testing
  84. JobScheduler.getTimestamp = getTimestamp;
  85. defineProperties(JobScheduler.prototype, {
  86. totalBudget : {
  87. get : function() {
  88. return this._totalBudget;
  89. }
  90. }
  91. });
  92. JobScheduler.prototype.disableThisFrame = function() {
  93. // Prevent jobs from running this frame
  94. this._totalUsedThisFrame = this._totalBudget;
  95. };
  96. JobScheduler.prototype.resetBudgets = function() {
  97. var budgets = this._budgets;
  98. var length = budgets.length;
  99. for (var i = 0; i < length; ++i) {
  100. var budget = budgets[i];
  101. budget.starvedLastFrame = budget.starvedThisFrame;
  102. budget.starvedThisFrame = false;
  103. budget.usedThisFrame = 0.0;
  104. budget.stolenFromMeThisFrame = 0.0;
  105. }
  106. this._totalUsedThisFrame = 0.0;
  107. };
  108. JobScheduler.prototype.execute = function(job, jobType) {
  109. var budgets = this._budgets;
  110. var budget = budgets[jobType];
  111. // This ensures each job type makes progress each frame by executing at least once
  112. var progressThisFrame = this._executedThisFrame[jobType];
  113. if ((this._totalUsedThisFrame >= this._totalBudget) && progressThisFrame) {
  114. // No budget left this frame for jobs of any type
  115. budget.starvedThisFrame = true;
  116. return false;
  117. }
  118. var stolenBudget;
  119. if ((budget.usedThisFrame + budget.stolenFromMeThisFrame >= budget.total)) {
  120. // No budget remaining for jobs of this type. Try to steal from other job types.
  121. var length = budgets.length;
  122. var i;
  123. for (i = 0; i < length; ++i) {
  124. stolenBudget = budgets[i];
  125. // Steal from this budget if it has time left and it wasn't starved last fame
  126. if ((stolenBudget.usedThisFrame + stolenBudget.stolenFromMeThisFrame < stolenBudget.total) &&
  127. (!stolenBudget.starvedLastFrame)) {
  128. break;
  129. }
  130. }
  131. if (i === length && progressThisFrame) {
  132. // No other job types can give up their budget this frame, and
  133. // this job type already progressed this frame
  134. return false;
  135. }
  136. if (progressThisFrame) {
  137. // It is considered "starved" even if it executes using stolen time so that
  138. // next frame, no other job types can steal time from it.
  139. budget.starvedThisFrame = true;
  140. }
  141. }
  142. var startTime = JobScheduler.getTimestamp();
  143. job.execute();
  144. var duration = JobScheduler.getTimestamp() - startTime;
  145. // Track both time remaining for this job type and all jobs
  146. // so budget stealing does send us way over the total budget.
  147. this._totalUsedThisFrame += duration;
  148. if (stolenBudget) {
  149. stolenBudget.stolenFromMeThisFrame += duration;
  150. } else {
  151. budget.usedThisFrame += duration;
  152. }
  153. this._executedThisFrame[jobType] = true;
  154. return true;
  155. };
  156. export default JobScheduler;