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: * * * @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 testing JobScheduler.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;