You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
5.7 KiB
JavaScript
200 lines
5.7 KiB
JavaScript
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
|
|
var 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
|
|
);
|
|
|
|
var length = jobBudgets.length;
|
|
var i;
|
|
|
|
var totalBudget = 0.0;
|
|
for (i = 0; i < length; ++i) {
|
|
totalBudget += jobBudgets[i].total;
|
|
}
|
|
|
|
var 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 () {
|
|
var budgets = this._budgets;
|
|
var length = budgets.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
var 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) {
|
|
var budgets = this._budgets;
|
|
var budget = budgets[jobType];
|
|
|
|
// This ensures each job type makes progress each frame by executing at least once
|
|
var 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;
|
|
}
|
|
|
|
var stolenBudget;
|
|
|
|
if (budget.usedThisFrame + budget.stolenFromMeThisFrame >= budget.total) {
|
|
// No budget remaining for jobs of this type. Try to steal from other job types.
|
|
var length = budgets.length;
|
|
var 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;
|
|
}
|
|
}
|
|
|
|
var startTime = JobScheduler.getTimestamp();
|
|
job.execute();
|
|
var 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;
|