15832144755
2022-01-06 7b4c8991dca9cf2a809a95e239d144697d3afb56
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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
  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;