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
import { JobScheduler } from "../../Source/Cesium.js";
import { JobType } from "../../Source/Cesium.js";
 
describe("Scene/JobScheduler", function () {
  var originalGetTimestamp;
 
  beforeAll(function () {
    originalGetTimestamp = JobScheduler.getTimestamp;
 
    var time = 0.0;
    JobScheduler.getTimestamp = function () {
      return time++;
    };
  });
 
  afterAll(function () {
    JobScheduler.getTimestamp = originalGetTimestamp;
  });
 
  ///////////////////////////////////////////////////////////////////////////
 
  var MockJob = function () {
    this.executed = false;
  };
 
  MockJob.prototype.execute = function () {
    this.executed = true;
  };
 
  ///////////////////////////////////////////////////////////////////////////
 
  it("constructs with defaults", function () {
    var js = new JobScheduler();
    expect(js.totalBudget).toEqual(50.0);
 
    var budgets = js._budgets;
    expect(budgets.length).toEqual(JobType.NUMBER_OF_JOB_TYPES);
    expect(budgets[JobType.TEXTURE].total).toEqual(10.0);
    expect(budgets[JobType.PROGRAM].total).toEqual(10.0);
    expect(budgets[JobType.BUFFER].total).toEqual(30.0);
  });
 
  it("executes a job", function () {
    var js = new JobScheduler([
      2.0, // JobType.TEXTURE
      0.0, // JobType.PROGRAM
      0.0,
    ]); // JobType.BUFFER
 
    var job = new MockJob();
    var executed = js.execute(job, JobType.TEXTURE);
 
    expect(executed).toEqual(true);
    expect(job.executed).toEqual(true);
    expect(js._totalUsedThisFrame).toEqual(1.0);
    expect(js._budgets[JobType.TEXTURE].total).toEqual(2.0);
    expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
  });
 
  it("disableThisFrame does not execute a job", function () {
    var js = new JobScheduler([2.0, 0.0, 0.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
 
    js.disableThisFrame();
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
  });
 
  it("executes different job types", function () {
    var js = new JobScheduler([1.0, 1.0, 1.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
 
    expect(js._totalUsedThisFrame).toEqual(3.0);
    var budgets = js._budgets;
    expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
    expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(1.0);
    expect(budgets[JobType.BUFFER].usedThisFrame).toEqual(1.0);
  });
 
  it("executes a second job", function () {
    var js = new JobScheduler([2.0, 0.0, 0.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js._totalUsedThisFrame).toEqual(2.0);
    expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(2.0);
  });
 
  it("does not execute second job (exceeds total time)", function () {
    var js = new JobScheduler([1.0, 0.0, 0.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
    expect(js._budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true);
  });
 
  it("executes a second job (TEXTURE steals PROGRAM budget)", function () {
    var js = new JobScheduler([1.0, 1.0, 0.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js._totalUsedThisFrame).toEqual(2.0);
 
    var budgets = js._budgets;
    expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0);
    expect(budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true);
    expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(0.0);
    expect(budgets[JobType.PROGRAM].stolenFromMeThisFrame).toEqual(1.0);
    expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(false);
 
    // There are no budgets left to steal from
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Allowed once per frame
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
    expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(true);
  });
 
  it("does not steal in the same frame", function () {
    var js = new JobScheduler([1.0, 1.0, 1.0]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
 
    // Exhaust budget for all job types in the first frame
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
 
    // In this next frame, no job type can steal from another since
    // they all exhausted their budgets in the previous frame
    js.resetBudgets();
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
 
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
 
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
  });
 
  it("does not steal from starving job types over multiple frames", function () {
    var js = new JobScheduler([1.0, 1.0, 0.0]);
 
    // Exhaust in first frame
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Stolen from PROGRAM
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
 
    js.resetBudgets();
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
 
    js.resetBudgets();
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE yet
 
    js.resetBudgets();
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Can steal from TEXTURE since it wasn't exhausted last frame
  });
 
  it("Allows progress on all job types once per frame", function () {
    var js = new JobScheduler([1.0, 1.0, 1.0]);
 
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from PROGRAM
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from BUFFER
 
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false);
 
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false);
 
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false);
  });
 
  it("Long job still allows progress on other job types once per frame", function () {
    // Job duration is always 1.0 in the tests so shorten budget
    var js = new JobScheduler([0.5, 0.2, 0.2]);
    expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Goes over total budget
    expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame
    expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame
  });
 
  it("constructor throws when budgets.length is not JobType.NUMBER_OF_JOB_TYPES", function () {
    expect(function () {
      return new JobScheduler([1.0]);
    }).toThrowDeveloperError();
  });
});