Add better insights and FPS autoscaling
This commit is contained in:
parent
30296409ac
commit
e5423212c3
11 changed files with 132 additions and 220 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
import { Insights } from '../../rendering/insights';
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const tryEnableExtension = (
|
export const tryEnableExtension = (
|
||||||
gl: WebGL2RenderingContext | WebGLRenderingContext,
|
gl: UniversalRenderingContext,
|
||||||
name: string
|
name: string
|
||||||
): any | null => {
|
): any | null => {
|
||||||
let extension = null;
|
let extension = null;
|
||||||
|
|
@ -10,16 +10,13 @@ export const tryEnableExtension = (
|
||||||
extension = gl.getExtension(name);
|
extension = gl.getExtension(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Insights.setValue(['extensions', name], extension !== null);
|
gl.insights.extensions[name] = extension !== null;
|
||||||
|
|
||||||
return extension;
|
return extension;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const enableExtension = (
|
export const enableExtension = (gl: UniversalRenderingContext, name: string): any => {
|
||||||
gl: WebGL2RenderingContext | WebGLRenderingContext,
|
|
||||||
name: string
|
|
||||||
): any => {
|
|
||||||
const extension = tryEnableExtension(gl, name);
|
const extension = tryEnableExtension(gl, name);
|
||||||
|
|
||||||
if (extension === null) {
|
if (extension === null) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { numberToGlslFloat } from '../../helper/number-to-glsl-float';
|
import { numberToGlslFloat } from '../../helper/number-to-glsl-float';
|
||||||
import { wait } from '../../helper/wait';
|
import { wait } from '../../helper/wait';
|
||||||
import { Insights } from '../rendering/insights';
|
|
||||||
import { tryEnableExtension } from './helper/enable-extension';
|
import { tryEnableExtension } from './helper/enable-extension';
|
||||||
import { UniversalRenderingContext } from './universal-rendering-context';
|
import { UniversalRenderingContext } from './universal-rendering-context';
|
||||||
|
|
||||||
|
|
@ -60,11 +59,10 @@ export class ParallelCompiler {
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Insights.measure('compile programs')
|
|
||||||
public async compilePrograms(): Promise<void> {
|
public async compilePrograms(): Promise<void> {
|
||||||
this.programs.forEach((p) => this.gl.linkProgram(p.program));
|
this.programs.forEach((p) => this.gl.linkProgram(p.program));
|
||||||
|
|
||||||
Insights.setValue('program count', this.programs.length);
|
this.gl.insights.programCount = this.programs.length;
|
||||||
|
|
||||||
while (this.programs.length > 0) {
|
while (this.programs.length > 0) {
|
||||||
this.resolveFinishedPrograms();
|
this.resolveFinishedPrograms();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Insights } from '../rendering/insights';
|
import { RendererInfo } from '../rendering/renderer/renderer-info';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export type UniversalRenderingContext =
|
export type UniversalRenderingContext = (
|
||||||
| (WebGL2RenderingContext & { isWebGL2: true })
|
| (WebGL2RenderingContext & { isWebGL2: true })
|
||||||
| (WebGLRenderingContext & { isWebGL2: false });
|
| (WebGLRenderingContext & { isWebGL2: false })
|
||||||
|
) & { insights: RendererInfo };
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export const getUniversalRenderingContext = (
|
export const getUniversalRenderingContext = (
|
||||||
|
|
@ -31,7 +32,14 @@ export const getUniversalRenderingContext = (
|
||||||
result.isWebGL2 = false;
|
result.isWebGL2 = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Insights.setValue('using WebGL2', result.isWebGL2);
|
result.insights = {
|
||||||
|
isWebGL2: result.isWebGL2,
|
||||||
|
extensions: {},
|
||||||
|
renderPasses: {
|
||||||
|
distance: {},
|
||||||
|
lights: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let isDestroyed = false;
|
let isDestroyed = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import { clamp } from '../../helper/clamp';
|
|
||||||
import { exponentialDecay } from '../../helper/exponential-decay';
|
|
||||||
import { mix } from '../../helper/mix';
|
|
||||||
import { Insights } from './insights';
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
const settings = {
|
|
||||||
targetDeltaTimeInMilliseconds: 20,
|
|
||||||
deltaTimeErrorInMilliseconds: 3,
|
|
||||||
deltaTimeResponsiveness: 1 / 32,
|
|
||||||
adjusmentRateInMilliseconds: 1000,
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
distanceRenderScale: 0.1,
|
|
||||||
finalRenderScale: 0.2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
distanceRenderScale: 0.1,
|
|
||||||
finalRenderScale: 0.6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
distanceRenderScale: 0.5,
|
|
||||||
finalRenderScale: 1.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
distanceRenderScale: 0.5,
|
|
||||||
finalRenderScale: 1.0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
qualityStepIncrease: 0.01,
|
|
||||||
qualityStepDecrese: 0.2,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export class FpsAutoscaler {
|
|
||||||
private timeSinceLastAdjusment = 0;
|
|
||||||
private exponentialDecayedDeltaTime = settings.targetDeltaTimeInMilliseconds;
|
|
||||||
|
|
||||||
// can have fractions
|
|
||||||
private index = 3;
|
|
||||||
|
|
||||||
constructor(private setters: { [key: string]: (value: number | boolean) => void }) {
|
|
||||||
this.applyScaling();
|
|
||||||
}
|
|
||||||
|
|
||||||
public increase() {
|
|
||||||
this.index += settings.qualityStepIncrease;
|
|
||||||
this.applyScaling();
|
|
||||||
}
|
|
||||||
|
|
||||||
public decrease() {
|
|
||||||
this.index -= settings.qualityStepDecrese;
|
|
||||||
this.applyScaling();
|
|
||||||
}
|
|
||||||
|
|
||||||
private applyScaling() {
|
|
||||||
this.index = clamp(this.index, 0, settings.targets.length - 1);
|
|
||||||
|
|
||||||
const floor = Math.floor(this.index);
|
|
||||||
const fract = this.index - floor;
|
|
||||||
|
|
||||||
const previousTarget: any = settings.targets[floor];
|
|
||||||
const nextTarget =
|
|
||||||
floor + 1 == settings.targets.length ? previousTarget : settings.targets[floor + 1];
|
|
||||||
|
|
||||||
const result: any = {};
|
|
||||||
for (const key in this.setters) {
|
|
||||||
const previous = previousTarget[key];
|
|
||||||
const next = nextTarget[key];
|
|
||||||
let current: number | boolean;
|
|
||||||
if (typeof previous == 'number') {
|
|
||||||
current = mix(previous, next as number, fract);
|
|
||||||
} else {
|
|
||||||
current = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[key] = current;
|
|
||||||
this.setters[key](current);
|
|
||||||
}
|
|
||||||
|
|
||||||
Insights.setValue('quality', result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public autoscale(lastDeltaTime: DOMHighResTimeStamp) {
|
|
||||||
this.exponentialDecayedDeltaTime = exponentialDecay(
|
|
||||||
this.exponentialDecayedDeltaTime,
|
|
||||||
lastDeltaTime,
|
|
||||||
settings.deltaTimeResponsiveness
|
|
||||||
);
|
|
||||||
Insights.setValue('FPS', 1000 / this.exponentialDecayedDeltaTime);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(this.timeSinceLastAdjusment += lastDeltaTime) >=
|
|
||||||
settings.adjusmentRateInMilliseconds
|
|
||||||
) {
|
|
||||||
this.timeSinceLastAdjusment = 0;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.exponentialDecayedDeltaTime + settings.deltaTimeErrorInMilliseconds <=
|
|
||||||
settings.targetDeltaTimeInMilliseconds
|
|
||||||
) {
|
|
||||||
this.increase();
|
|
||||||
} else if (
|
|
||||||
this.exponentialDecayedDeltaTime > settings.targetDeltaTimeInMilliseconds
|
|
||||||
) {
|
|
||||||
this.decrease();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
78
src/graphics/rendering/fps-quality-autoscaler.ts
Normal file
78
src/graphics/rendering/fps-quality-autoscaler.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
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 = 1.3;
|
||||||
|
private adjusmentRateInMilliseconds = 500;
|
||||||
|
private fps = 0;
|
||||||
|
|
||||||
|
public fpsTarget = 50;
|
||||||
|
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.5;
|
||||||
|
private lightsScale = 1;
|
||||||
|
|
||||||
|
private adjustQuality() {
|
||||||
|
console.log(this.distanceScale, this.lightsScale);
|
||||||
|
if (this.fps >= this.fpsTarget + this.fpsHysteresis) {
|
||||||
|
this.renderer.setRuntimeSettings({
|
||||||
|
distanceRenderScale: this.distanceScale += 0.1,
|
||||||
|
lightsRenderScale: this.lightsScale += 0.1,
|
||||||
|
});
|
||||||
|
} else if (this.fps <= this.fpsTarget + this.fpsHysteresis) {
|
||||||
|
this.renderer.setRuntimeSettings({
|
||||||
|
distanceRenderScale: this.distanceScale *= 0.7,
|
||||||
|
lightsRenderScale: this.lightsScale *= 0.7,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { last } from '../../helper/last';
|
|
||||||
import { msToString } from '../../helper/ms-to-string';
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export class Insights {
|
|
||||||
private static insights: any = {};
|
|
||||||
|
|
||||||
public static setValue(key: string | Array<string>, value: any): void {
|
|
||||||
if (Array.isArray(key)) {
|
|
||||||
if (key.length == 0) {
|
|
||||||
throw new Error('Key length must be greater than 0');
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastObject = key.slice(0, -1).reduce((previousValue, currentKey) => {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(previousValue, currentKey)) {
|
|
||||||
previousValue[currentKey] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return previousValue[currentKey];
|
|
||||||
}, Insights.insights);
|
|
||||||
|
|
||||||
lastObject[last(key)!] = value;
|
|
||||||
} else {
|
|
||||||
Insights.insights[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static measure(key: string | Array<string>) {
|
|
||||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
||||||
const targetFunction = descriptor.value;
|
|
||||||
|
|
||||||
descriptor.value = function (...values: Array<any>) {
|
|
||||||
return Insights.measureFunction(key, () => targetFunction.apply(this, values));
|
|
||||||
};
|
|
||||||
|
|
||||||
return descriptor;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static measureFunction(
|
|
||||||
key: string | Array<string>,
|
|
||||||
targetFunction: () => any
|
|
||||||
): any {
|
|
||||||
const start = performance.now();
|
|
||||||
const result = targetFunction();
|
|
||||||
const end = performance.now();
|
|
||||||
|
|
||||||
const newKey = Array.isArray(key) ? ['measurements', ...key] : ['measurements', key];
|
|
||||||
Insights.setValue(newKey, msToString(end - start));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get values(): any {
|
|
||||||
return Insights.insights;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Drawable } from '../../../drawables/drawable';
|
import { Drawable } from '../../../drawables/drawable';
|
||||||
import { Texture } from '../../graphics-library/texture/texture';
|
import { Texture } from '../../graphics-library/texture/texture';
|
||||||
import { Insights } from '../insights';
|
|
||||||
import { RenderPass } from './render-pass';
|
import { RenderPass } from './render-pass';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
@ -74,18 +73,10 @@ export class DistanceRenderPass extends RenderPass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Insights.setValue(
|
this.gl.insights.renderPasses.distance.drawableCount = this.drawables.length;
|
||||||
['render pass', 'distance', 'all drawables'],
|
this.gl.insights.renderPasses.distance.drawnDrawableCount =
|
||||||
this.drawables.length
|
drawnDrawablesCount / this.tileMultiplier ** 2;
|
||||||
);
|
this.gl.insights.renderPasses.distance.tileCount = this.tileMultiplier ** 2;
|
||||||
Insights.setValue(
|
|
||||||
['render pass', 'distance', 'tile count'],
|
|
||||||
this.tileMultiplier ** 2
|
|
||||||
);
|
|
||||||
Insights.setValue(
|
|
||||||
['render pass', 'distance', 'rendered drawables'],
|
|
||||||
drawnDrawablesCount / this.tileMultiplier ** 2
|
|
||||||
);
|
|
||||||
|
|
||||||
this.drawables = [];
|
this.drawables = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { vec2 } from 'gl-matrix';
|
||||||
import { LightDrawable } from '../../../drawables/lights/light-drawable';
|
import { LightDrawable } from '../../../drawables/lights/light-drawable';
|
||||||
import { clamp01 } from '../../../helper/clamp';
|
import { clamp01 } from '../../../helper/clamp';
|
||||||
import { Texture } from '../../graphics-library/texture/texture';
|
import { Texture } from '../../graphics-library/texture/texture';
|
||||||
import { Insights } from '../insights';
|
|
||||||
import { RenderPass } from './render-pass';
|
import { RenderPass } from './render-pass';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
@ -50,10 +49,7 @@ export class LightsRenderPass extends RenderPass {
|
||||||
|
|
||||||
this.program.draw(commonUniforms);
|
this.program.draw(commonUniforms);
|
||||||
|
|
||||||
Insights.setValue(
|
this.gl.insights.renderPasses.lights.drawnDrawableCount = drawablesNearTile.length;
|
||||||
['render pass', 'lights', 'rendered drawables'],
|
|
||||||
drawablesNearTile.length
|
|
||||||
);
|
|
||||||
|
|
||||||
this.drawables = [];
|
this.drawables = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { RuntimeSettings } from '../settings/runtime-settings';
|
||||||
import { StartupSettings } from '../settings/startup-settings';
|
import { StartupSettings } from '../settings/startup-settings';
|
||||||
import { Renderer } from './renderer';
|
import { Renderer } from './renderer';
|
||||||
import { RendererImplementation } from './renderer-implementation';
|
import { RendererImplementation } from './renderer-implementation';
|
||||||
|
import { RendererInfo } from './renderer-info';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export class ContextAwareRenderer implements Renderer {
|
export class ContextAwareRenderer implements Renderer {
|
||||||
|
|
@ -109,8 +110,8 @@ export class ContextAwareRenderer implements Renderer {
|
||||||
return this.handle(() => this.renderer.viewAreaSize, vec2.create());
|
return this.handle(() => this.renderer.viewAreaSize, vec2.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
public get insights(): any {
|
public get insights(): RendererInfo | null {
|
||||||
return this.handle(() => this.renderer.insights, {});
|
return this.handle(() => this.renderer.insights, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setViewArea(topLeft: vec2, size: vec2): void {
|
public setViewArea(topLeft: vec2, size: vec2): void {
|
||||||
|
|
@ -132,10 +133,6 @@ export class ContextAwareRenderer implements Renderer {
|
||||||
return this.handle(() => this.renderer.addDrawable(drawable), undefined);
|
return this.handle(() => this.renderer.addDrawable(drawable), undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public autoscaleQuality(deltaTime: number): void {
|
|
||||||
return this.handle(() => this.renderer.autoscaleQuality(deltaTime), undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
public displayToWorldCoordinates(displayCoordinates: vec2): vec2 {
|
public displayToWorldCoordinates(displayCoordinates: vec2): vec2 {
|
||||||
return this.handle(
|
return this.handle(
|
||||||
() => this.renderer.displayToWorldCoordinates(displayCoordinates),
|
() => this.renderer.displayToWorldCoordinates(displayCoordinates),
|
||||||
|
|
|
||||||
22
src/graphics/rendering/renderer/renderer-info.ts
Normal file
22
src/graphics/rendering/renderer/renderer-info.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export interface RendererInfo {
|
||||||
|
isWebGL2: boolean;
|
||||||
|
vendor?: string;
|
||||||
|
renderer?: string;
|
||||||
|
extensions: { [name: string]: boolean };
|
||||||
|
floatInterpolationEnabled?: boolean;
|
||||||
|
programCount?: number;
|
||||||
|
renderPasses: {
|
||||||
|
distance: {
|
||||||
|
renderScale?: number;
|
||||||
|
drawableCount?: number;
|
||||||
|
drawnDrawableCount?: number;
|
||||||
|
tileCount?: number;
|
||||||
|
};
|
||||||
|
lights: {
|
||||||
|
renderScale?: number;
|
||||||
|
drawnDrawableCount?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
startupTimeInMilliseconds?: number;
|
||||||
|
[k: string]: any;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Drawable } from '../../../drawables/drawable';
|
import { Drawable } from '../../../drawables/drawable';
|
||||||
import { RuntimeSettings } from '../settings/runtime-settings';
|
import { RuntimeSettings } from '../settings/runtime-settings';
|
||||||
|
import { RendererInfo } from './renderer-info';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main interface through which rendering can be achieved.
|
* The main interface through which rendering can be achieved.
|
||||||
|
|
@ -84,21 +85,12 @@ export interface Renderer {
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* Get useful information about the hardware and the SDF2D renderer.
|
||||||
|
*
|
||||||
|
* Its sheme is subject to change.
|
||||||
|
*
|
||||||
|
* During context lost it might be null.
|
||||||
*
|
*
|
||||||
* Debug information updated on each `renderDrawables` call.
|
|
||||||
* Its scheme is not yet defined. The main purpose of this is
|
|
||||||
* human debugging.
|
|
||||||
*/
|
*/
|
||||||
readonly insights: any;
|
readonly insights: RendererInfo | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @experimental
|
|
||||||
*
|
|
||||||
* Scale the render scale for both the canvas and the SDF memoization based
|
|
||||||
* on the current and historical FPS values.
|
|
||||||
*
|
|
||||||
* @param deltaTime since the last frame, in milliseconds.
|
|
||||||
*/
|
|
||||||
autoscaleQuality(deltaTime: DOMHighResTimeStamp): void;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue