Skip to content

Commit 7cd3475

Browse files
authored
Merge pull request #5847 from spikyMalkallam/High_Refresh
Fix Frame Rate higher than target on high refresh rate displays (>200Hz)
2 parents f970dd3 + a091ed0 commit 7cd3475

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

src/core/main.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ class p5 {
284284
loadingScreen.parentNode.removeChild(loadingScreen);
285285
}
286286
if (!this._setupDone) {
287-
this._lastFrameTime = window.performance.now();
287+
this._lastTargetFrameTime = window.performance.now();
288+
this._lastRealFrameTime = window.performance.now();
288289
context._setup();
289290
context._draw();
290291
}
@@ -357,7 +358,8 @@ class p5 {
357358
}
358359
}
359360

360-
this._lastFrameTime = window.performance.now();
361+
this._lastTargetFrameTime = window.performance.now();
362+
this._lastRealFrameTime = window.performance.now();
361363
this._setupDone = true;
362364
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
363365
this._updateAccsOutput();
@@ -366,7 +368,7 @@ class p5 {
366368

367369
this._draw = () => {
368370
const now = window.performance.now();
369-
const time_since_last = now - this._lastFrameTime;
371+
const time_since_last = now - this._lastTargetFrameTime;
370372
const target_time_between_frames = 1000 / this._targetFrameRate;
371373

372374
// only draw if we really need to; don't overextend the browser.
@@ -384,10 +386,12 @@ class p5 {
384386
) {
385387
//mandatory update values(matrixes and stack)
386388
this.redraw();
387-
this._frameRate = 1000.0 / (now - this._lastFrameTime);
388-
this.deltaTime = now - this._lastFrameTime;
389+
this._frameRate = 1000.0 / (now - this._lastRealFrameTime);
390+
this.deltaTime = now - this._lastRealFrameTime;
389391
this._setProperty('deltaTime', this.deltaTime);
390-
this._lastFrameTime = now;
392+
this._lastTargetFrameTime = Math.max(this._lastTargetFrameTime
393+
+ target_time_between_frames, now);
394+
this._lastRealFrameTime = now;
391395

392396
// If the user is actually using mouse module, then update
393397
// coordinates, otherwise skip. We can test this by simply

test/unit/core/environment.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,77 @@ suite('Environment', function() {
120120
test('p5.prototype.getFrameRate', function() {
121121
assert.strictEqual(myp5.getFrameRate(), 0);
122122
});
123+
124+
suite('drawing with target frame rates', function() {
125+
let clock;
126+
let prevRequestAnimationFrame;
127+
let nextFrameCallback = () => {};
128+
let controlledP5;
129+
130+
setup(function() {
131+
clock = sinon.useFakeTimers(0);
132+
sinon.stub(window.performance, 'now', Date.now);
133+
134+
// Save the real requestAnimationFrame so we can restore it later
135+
prevRequestAnimationFrame = window.requestAnimationFrame;
136+
// Use a fake requestAnimationFrame that just stores a ref to the callback
137+
// so that we can call it manually
138+
window.requestAnimationFrame = function(cb) {
139+
nextFrameCallback = cb;
140+
};
141+
142+
return new Promise(function(resolve) {
143+
controlledP5 = new p5(function(p) {
144+
p.setup = function() {
145+
p.createCanvas(10, 10);
146+
p.frameRate(60);
147+
p.loop();
148+
resolve(p);
149+
};
150+
151+
p.draw = function() {};
152+
});
153+
});
154+
});
155+
156+
teardown(function() {
157+
clock.restore();
158+
window.performance.now.restore();
159+
window.requestAnimationFrame = prevRequestAnimationFrame;
160+
nextFrameCallback = function() {};
161+
controlledP5.remove();
162+
});
163+
164+
test('draw() is called at the correct frame rate given a faster display', function() {
165+
sinon.spy(controlledP5, 'draw');
166+
167+
clock.tick(1000 / 200); // Simulate a 200Hz refresh rate
168+
nextFrameCallback(); // trigger the next requestAnimationFrame
169+
assert(controlledP5.draw.notCalled, 'draw() should not be called before 1s/60');
170+
171+
// Advance until 5ms before the next frame should render.
172+
// This should be within p5's threshold for rendering the frame.
173+
clock.tick(1000 / 60 - 1000 / 200 - 5);
174+
nextFrameCallback(); // trigger the next requestAnimationFrame
175+
assert(controlledP5.draw.calledOnce, 'one frame should have been drawn');
176+
// deltaTime should reflect real elapsed time
177+
assert.equal(controlledP5.deltaTime, 1000 / 60 - 5);
178+
179+
// Advance enough time forward to be 1s/60 - 5ms from the last draw
180+
clock.tick(1000 / 60 - 5);
181+
nextFrameCallback(); // trigger the next requestAnimationFrame
182+
// Even though this is 1s/60 - 5ms from the last draw, the last frame came
183+
// in early, so we still shouldn't draw
184+
assert(controlledP5.draw.calledOnce, 'draw() should not be called before 1s/60 past the last target draw time');
185+
186+
// Advance enough time forward to be 1s/60 from the last draw
187+
clock.tick(5);
188+
nextFrameCallback();
189+
assert(controlledP5.draw.calledTwice); // Now it should draw again!
190+
// deltaTime should reflect real elapsed time
191+
assert.equal(controlledP5.deltaTime, 1000 / 60);
192+
});
193+
});
123194
});
124195

125196
suite('p5.prototype.getTargetFrameRate', function() {

0 commit comments

Comments
 (0)