sdf-2d/src/graphics/rendering/fps-quality-autoscaler.ts
2020-10-31 11:13:28 +01:00

82 lines
2.5 KiB
TypeScript

import { clamp } from '../../helper/clamp';
import { Renderer } from './renderer/renderer';
/**
* Set the quality of rendering based on FPS values.
*
* When using this the size of the canvas must be fixed with CSS.
*
* The `addDeltaTime` method should be called once every frame.
*
* Usage:
* ```js
* const renderer = await compile(...);
* const autoscaler = new FpsQualityAutoscaler(renderer);
* ```
*/
export class FpsQualityAutoscaler {
private readonly maxAdjusmentRateInMilliseconds = 10000;
private readonly adjusmentRateIncrease = 2;
private adjusmentRateInMilliseconds = 500;
private fps = 0;
public static fpsTarget = 30;
public fpsHysteresis = 5;
constructor(private readonly renderer: Renderer) {}
public get FPS(): number {
return this.fps;
}
private deltaTimes: Array<number> = [];
private deltaTimeSinceLastAdjustment = 0;
/**
* Autoscaling is also done by calling this function
* @param deltaTimeInMilliseconds
*/
public addDeltaTime(deltaTimeInMilliseconds: DOMHighResTimeStamp) {
this.deltaTimes.push(deltaTimeInMilliseconds);
this.deltaTimeSinceLastAdjustment += deltaTimeInMilliseconds;
if (this.deltaTimeSinceLastAdjustment > this.adjusmentRateInMilliseconds) {
this.calculateFPS();
this.adjustQuality();
this.adjusmentRateInMilliseconds = Math.min(
this.maxAdjusmentRateInMilliseconds,
this.adjusmentRateInMilliseconds * this.adjusmentRateIncrease
);
this.deltaTimeSinceLastAdjustment = 0;
}
}
private calculateFPS() {
const sampleCount = this.deltaTimes.length;
this.deltaTimes.sort((a, b) => a - b);
const ninetiethPercentile = this.deltaTimes[Math.floor(sampleCount * 0.9)];
this.deltaTimes = [];
this.fps = 1000 / ninetiethPercentile;
}
private distanceScale = 0.33;
private lightsScale = 1;
private adjustQuality() {
if (this.fps >= FpsQualityAutoscaler.fpsTarget + this.fpsHysteresis) {
this.distanceScale = this.distanceScale + 0.1;
this.lightsScale = this.lightsScale + 0.1;
} else if (this.fps <= FpsQualityAutoscaler.fpsTarget + this.fpsHysteresis) {
this.distanceScale = this.distanceScale / 1.5;
this.lightsScale = this.lightsScale / 1.5;
}
this.distanceScale = clamp(this.distanceScale, 0.1, 1);
this.lightsScale = clamp(this.lightsScale, 0.2, 1);
this.renderer.setRuntimeSettings({
distanceRenderScale: this.distanceScale,
lightsRenderScale: this.lightsScale,
});
}
}