| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 | import defined from "../Core/defined.js";import DeveloperError from "../Core/DeveloperError.js";import getTimestamp from "../Core/getTimestamp.js";import JobType from "./JobType.js";/** * * @private * @constructor */function JobTypeBudget(total) {  /**   * Total budget, in milliseconds, allowed for one frame   */  this._total = total;  /**   * Time, in milliseconds, used so far during this frame   */  this.usedThisFrame = 0.0;  /**   * Time, in milliseconds, that other job types stole this frame   */  this.stolenFromMeThisFrame = 0.0;  /**   * Indicates if this job type was starved this frame, i.e., a job   * tried to run but didn't have budget   */  this.starvedThisFrame = false;  /**   * Indicates if this job was starved last frame.  This prevents it   * from being stolen from this frame.   */  this.starvedLastFrame = false;}Object.defineProperties(JobTypeBudget.prototype, {  total: {    get: function () {      return this._total;    },  },});/** * Engine for time slicing jobs during a frame to amortize work over multiple frames.  This supports: * <ul> *   <li> *     Separate budgets for different job types, e.g., texture, shader program, and buffer creation.  This *     allows all job types to make progress each frame. *   </li> *   <li> *     Stealing from other jobs type budgets if they were not exhausted in the previous frame.  This allows *     using the entire budget for all job types each frame even if, for example, all the jobs are the same type. *   </li> *   <li> *     Guaranteed progress on all job types each frame, even if it means exceeding the total budget for the frame. *     This prevents, for example, several expensive texture uploads over many frames from prevent a shader compile. *   </li> * </ul> * * @private */function JobScheduler(budgets) {  //>>includeStart('debug', pragmas.debug);  if (defined(budgets) && budgets.length !== JobType.NUMBER_OF_JOB_TYPES) {    throw new DeveloperError(      "A budget must be specified for each job type; budgets.length should equal JobType.NUMBER_OF_JOB_TYPES."    );  }  //>>includeEnd('debug');  // Total for defaults is half of of one frame at 10 fps  const jobBudgets = new Array(JobType.NUMBER_OF_JOB_TYPES);  jobBudgets[JobType.TEXTURE] = new JobTypeBudget(    defined(budgets) ? budgets[JobType.TEXTURE] : 10.0  );  // On cache miss, this most likely only allows one shader compile per frame  jobBudgets[JobType.PROGRAM] = new JobTypeBudget(    defined(budgets) ? budgets[JobType.PROGRAM] : 10.0  );  jobBudgets[JobType.BUFFER] = new JobTypeBudget(    defined(budgets) ? budgets[JobType.BUFFER] : 30.0  );  const length = jobBudgets.length;  let i;  let totalBudget = 0.0;  for (i = 0; i < length; ++i) {    totalBudget += jobBudgets[i].total;  }  const executedThisFrame = new Array(length);  for (i = 0; i < length; ++i) {    executedThisFrame[i] = false;  }  this._totalBudget = totalBudget;  this._totalUsedThisFrame = 0.0;  this._budgets = jobBudgets;  this._executedThisFrame = executedThisFrame;}// For unit testingJobScheduler.getTimestamp = getTimestamp;Object.defineProperties(JobScheduler.prototype, {  totalBudget: {    get: function () {      return this._totalBudget;    },  },});JobScheduler.prototype.disableThisFrame = function () {  // Prevent jobs from running this frame  this._totalUsedThisFrame = this._totalBudget;};JobScheduler.prototype.resetBudgets = function () {  const budgets = this._budgets;  const length = budgets.length;  for (let i = 0; i < length; ++i) {    const budget = budgets[i];    budget.starvedLastFrame = budget.starvedThisFrame;    budget.starvedThisFrame = false;    budget.usedThisFrame = 0.0;    budget.stolenFromMeThisFrame = 0.0;  }  this._totalUsedThisFrame = 0.0;};JobScheduler.prototype.execute = function (job, jobType) {  const budgets = this._budgets;  const budget = budgets[jobType];  // This ensures each job type makes progress each frame by executing at least once  const progressThisFrame = this._executedThisFrame[jobType];  if (this._totalUsedThisFrame >= this._totalBudget && progressThisFrame) {    // No budget left this frame for jobs of any type    budget.starvedThisFrame = true;    return false;  }  let stolenBudget;  if (budget.usedThisFrame + budget.stolenFromMeThisFrame >= budget.total) {    // No budget remaining for jobs of this type. Try to steal from other job types.    const length = budgets.length;    let i;    for (i = 0; i < length; ++i) {      stolenBudget = budgets[i];      // Steal from this budget if it has time left and it wasn't starved last fame      if (        stolenBudget.usedThisFrame + stolenBudget.stolenFromMeThisFrame <          stolenBudget.total &&        !stolenBudget.starvedLastFrame      ) {        break;      }    }    if (i === length && progressThisFrame) {      // No other job types can give up their budget this frame, and      // this job type already progressed this frame      return false;    }    if (progressThisFrame) {      // It is considered "starved" even if it executes using stolen time so that      // next frame, no other job types can steal time from it.      budget.starvedThisFrame = true;    }  }  const startTime = JobScheduler.getTimestamp();  job.execute();  const duration = JobScheduler.getTimestamp() - startTime;  // Track both time remaining for this job type and all jobs  // so budget stealing does send us way over the total budget.  this._totalUsedThisFrame += duration;  if (stolenBudget) {    stolenBudget.stolenFromMeThisFrame += duration;  } else {    budget.usedThisFrame += duration;  }  this._executedThisFrame[jobType] = true;  return true;};export default JobScheduler;
 |