Add existing to tracked
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
||||
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 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;
|
||||
Reference in New Issue
Block a user