Refactor rendering
This commit is contained in:
parent
9b47d56d8f
commit
c892ca2d01
38 changed files with 511 additions and 429 deletions
|
|
@ -1,47 +0,0 @@
|
|||
import { mix } from '../../helper/mix';
|
||||
import { clamp } from '../../helper/clamp';
|
||||
|
||||
export class Autoscaler {
|
||||
// can have fractions
|
||||
private index: number;
|
||||
|
||||
constructor(
|
||||
private setters: Array<(value: number) => void>,
|
||||
private targets: Array<Array<number>>,
|
||||
startingIndex: number,
|
||||
private scalingOptions: {
|
||||
additiveIncrease: number;
|
||||
multiplicativeDecrease: number;
|
||||
}
|
||||
) {
|
||||
this.index = startingIndex;
|
||||
this.applyScaling();
|
||||
}
|
||||
|
||||
public increase() {
|
||||
this.index += this.scalingOptions.additiveIncrease;
|
||||
this.applyScaling();
|
||||
}
|
||||
|
||||
public decrease() {
|
||||
this.index /= this.scalingOptions.multiplicativeDecrease;
|
||||
this.applyScaling();
|
||||
}
|
||||
|
||||
private applyScaling() {
|
||||
this.index = clamp(this.index, 0, this.targets.length - 1);
|
||||
|
||||
const floor = Math.floor(this.index);
|
||||
const fract = this.index - floor;
|
||||
|
||||
const previousTarget = this.targets[floor];
|
||||
const nextTarget =
|
||||
floor + 1 == this.targets.length
|
||||
? previousTarget
|
||||
: this.targets[floor + 1];
|
||||
|
||||
this.setters.forEach((setter, i) =>
|
||||
setter(mix(previousTarget[i], nextTarget[i], fract))
|
||||
);
|
||||
}
|
||||
}
|
||||
54
frontend/src/scripts/drawing/rendering/fps-autoscaler.ts
Normal file
54
frontend/src/scripts/drawing/rendering/fps-autoscaler.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { Autoscaler } from '../../helper/autoscaler';
|
||||
import { settings } from '../settings';
|
||||
import { exponentialDecay } from '../../helper/exponential-decay';
|
||||
import { InfoText } from '../../objects/types/info-text';
|
||||
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer';
|
||||
import { toPercent } from '../../helper/to-percent';
|
||||
|
||||
export class FpsAutoscaler extends Autoscaler {
|
||||
private timeSinceLastAdjusment = 0;
|
||||
private exponentialDecayedDeltaTime = 0.0;
|
||||
|
||||
constructor(private frameBuffers: Array<FrameBuffer>) {
|
||||
super(
|
||||
frameBuffers.map((f) => (v) => (f.renderScale = v)),
|
||||
settings.qualityScaling.scaleTargets,
|
||||
settings.qualityScaling.startingTargetIndex,
|
||||
settings.qualityScaling.scalingOptions
|
||||
);
|
||||
}
|
||||
|
||||
public autoscale(lastDeltaTime: DOMHighResTimeStamp) {
|
||||
this.timeSinceLastAdjusment += lastDeltaTime;
|
||||
if (
|
||||
this.timeSinceLastAdjusment >=
|
||||
settings.qualityScaling.adjusmentRateInMilliseconds
|
||||
) {
|
||||
this.timeSinceLastAdjusment = 0;
|
||||
this.exponentialDecayedDeltaTime = exponentialDecay(
|
||||
this.exponentialDecayedDeltaTime,
|
||||
lastDeltaTime,
|
||||
settings.qualityScaling.deltaTimeResponsiveness
|
||||
);
|
||||
|
||||
if (
|
||||
this.exponentialDecayedDeltaTime <=
|
||||
settings.qualityScaling.targetDeltaTimeInMilliseconds -
|
||||
settings.qualityScaling.deltaTimeError
|
||||
) {
|
||||
this.increase();
|
||||
} else if (
|
||||
this.exponentialDecayedDeltaTime >
|
||||
settings.qualityScaling.targetDeltaTimeInMilliseconds +
|
||||
settings.qualityScaling.deltaTimeError
|
||||
) {
|
||||
this.decrease();
|
||||
}
|
||||
}
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'quality',
|
||||
this.frameBuffers.map((f) => toPercent(f.renderScale)).join(', ')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { IPrimitive } from '../primitives/i-primitive';
|
||||
import { ILight } from '../lights/i-light';
|
||||
|
||||
export interface IRenderer {
|
||||
startFrame(deltaTime: DOMHighResTimeStamp): void;
|
||||
finishFrame(): void;
|
||||
|
||||
drawPrimitive(primitive: IPrimitive): void;
|
||||
drawLight(light: ILight): void;
|
||||
drawInfoText(text: string): void;
|
||||
|
||||
setCameraPosition(position: vec2): void;
|
||||
setCursorPosition(position: vec2): void;
|
||||
setInViewArea(size: number): vec2;
|
||||
}
|
||||
|
|
@ -1,8 +1,105 @@
|
|||
import { IProgram } from '../graphics-library/program/i-program';
|
||||
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer';
|
||||
import { IDrawable } from '../drawables/i-drawable';
|
||||
import { settings } from '../settings';
|
||||
import { vec2 } from 'gl-matrix';
|
||||
import { Circle } from '../drawables/primitives/circle';
|
||||
import { InfoText } from '../../objects/types/info-text';
|
||||
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
|
||||
import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor';
|
||||
|
||||
export class RenderingPass {
|
||||
constructor(private program: IProgram, private frame: FrameBuffer) {}
|
||||
private drawables: Array<IDrawable> = [];
|
||||
private program: UniformArrayAutoScalingProgram;
|
||||
|
||||
public render(uniforms: any, inputTexture?: WebGLTexture) {}
|
||||
constructor(
|
||||
gl: WebGL2RenderingContext,
|
||||
shaderSources: [string, string],
|
||||
private drawableDescriptors: Array<IDrawableDescriptor>,
|
||||
private frame: FrameBuffer
|
||||
) {
|
||||
this.program = new UniformArrayAutoScalingProgram(
|
||||
gl,
|
||||
shaderSources,
|
||||
drawableDescriptors
|
||||
);
|
||||
}
|
||||
|
||||
public addDrawable(drawable: IDrawable) {
|
||||
this.drawables.push(drawable);
|
||||
}
|
||||
|
||||
public render(
|
||||
commonUniforms: any,
|
||||
viewCircle: Circle,
|
||||
inputTexture?: WebGLTexture
|
||||
) {
|
||||
this.frame.bindAndClear(inputTexture);
|
||||
const q = 1 / settings.tileMultiplier;
|
||||
const tileUvSize = vec2.fromValues(q, q);
|
||||
|
||||
const possiblyOnScreenDrawables = this.drawables.filter(
|
||||
(p) => p.minimumDistance(viewCircle.center) < viewCircle.radius
|
||||
);
|
||||
|
||||
const origin = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.fromValues(0, 0),
|
||||
commonUniforms.uvToWorld
|
||||
);
|
||||
|
||||
const firstCenter = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.fromValues(q, q),
|
||||
commonUniforms.uvToWorld
|
||||
);
|
||||
|
||||
vec2.subtract(firstCenter, firstCenter, origin);
|
||||
|
||||
const worldR = vec2.length(firstCenter);
|
||||
|
||||
let sumLineCount = 0;
|
||||
|
||||
for (let x = 0; x < 1; x += q) {
|
||||
for (let y = 0; y < 1; y += q) {
|
||||
const uniforms = { ...commonUniforms };
|
||||
uniforms.maxMinDistance = 2 * worldR;
|
||||
|
||||
const uvBottomLeft = vec2.fromValues(x, y);
|
||||
this.program.setDrawingRectangle(uvBottomLeft, tileUvSize);
|
||||
|
||||
const tileCenterWorldCoordinates = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.add(vec2.create(), uvBottomLeft, vec2.fromValues(q, q)),
|
||||
uniforms.uvToWorld
|
||||
);
|
||||
|
||||
const primitivesNearTile = possiblyOnScreenDrawables.filter(
|
||||
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
|
||||
);
|
||||
|
||||
sumLineCount += primitivesNearTile.length;
|
||||
|
||||
primitivesNearTile.forEach((p) => p.serializeToUniforms(uniforms));
|
||||
|
||||
this.program.bindAndSetUniforms(uniforms);
|
||||
this.program.draw();
|
||||
}
|
||||
}
|
||||
|
||||
this.drawables = [];
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'nearby ' + this.drawableDescriptors[0].countMacroName,
|
||||
possiblyOnScreenDrawables.length.toFixed(2)
|
||||
);
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'drawn ' + this.drawableDescriptors[0].countMacroName,
|
||||
(
|
||||
sumLineCount /
|
||||
settings.tileMultiplier /
|
||||
settings.tileMultiplier
|
||||
).toFixed(2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
export const settings = {
|
||||
qualityScaling: {
|
||||
targetDeltaTimeInMilliseconds: 30,
|
||||
deltaTimeError: 2,
|
||||
deltaTimeResponsiveness: 1 / 16,
|
||||
adjusmentRateInMilliseconds: 300,
|
||||
scaleTargets: [
|
||||
[0.2, 0.1],
|
||||
[0.6, 0.1],
|
||||
[1, 0.3],
|
||||
[1.25, 0.75],
|
||||
[1.5, 1],
|
||||
[1.75, 1.25],
|
||||
[1.75, 1.75],
|
||||
],
|
||||
startingTargetIndex: 2,
|
||||
scalingOptions: {
|
||||
additiveIncrease: 0.2,
|
||||
multiplicativeDecrease: 1.15,
|
||||
},
|
||||
},
|
||||
tileMultiplier: 5,
|
||||
shaderUniforms: {
|
||||
distanceScale: 64,
|
||||
edgeSmoothing: 10,
|
||||
},
|
||||
};
|
||||
|
|
@ -1,24 +1,28 @@
|
|||
import { mat2d, vec2 } from 'gl-matrix';
|
||||
import { InfoText } from '../../objects/types/info-text';
|
||||
import caveFragmentShader from '../shaders/cave-distance-fs.glsl';
|
||||
import lightsFragmentShader from '../shaders/lights-shading-fs.glsl';
|
||||
import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
|
||||
import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl';
|
||||
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
|
||||
import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer';
|
||||
import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer';
|
||||
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
|
||||
import { IProgram } from '../graphics-library/program/i-program';
|
||||
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
|
||||
import { IRenderer } from './i-renderer';
|
||||
import { Circle } from '../primitives/circle';
|
||||
import { IPrimitive } from '../primitives/i-primitive';
|
||||
import { Rectangle } from '../primitives/rectangle';
|
||||
import { TunnelShape } from '../primitives/tunnel-shape';
|
||||
import { Autoscaler } from './autoscaler';
|
||||
import { ILight } from '../lights/i-light';
|
||||
import { toPercent } from '../../helper/to-percent';
|
||||
import { exponentialDecay } from '../../helper/exponential-decay';
|
||||
import { settings } from './settings';
|
||||
import { IRenderer } from '../i-renderer';
|
||||
import { Circle } from '../drawables/primitives/circle';
|
||||
import { IPrimitive } from '../drawables/primitives/i-primitive';
|
||||
import { Rectangle } from '../drawables/primitives/rectangle';
|
||||
import { ILight } from '../drawables/lights/i-light';
|
||||
import { settings } from '../settings';
|
||||
import { FpsAutoscaler } from './fps-autoscaler';
|
||||
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
|
||||
import { RenderingPass } from './rendering-pass';
|
||||
import { TunnelShape } from '../drawables/primitives/tunnel-shape';
|
||||
import { CircleLight } from '../drawables/lights/circle-light';
|
||||
|
||||
export class WebGl2Renderer implements IRenderer {
|
||||
private gl: WebGL2RenderingContext;
|
||||
private qualityScaler: Autoscaler;
|
||||
private stopwatch?: WebGlStopwatch;
|
||||
|
||||
private viewBox: Rectangle = new Rectangle();
|
||||
|
|
@ -28,131 +32,51 @@ export class WebGl2Renderer implements IRenderer {
|
|||
|
||||
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
|
||||
private lightingFrameBuffer: DefaultFrameBuffer;
|
||||
private distanceProgram: IProgram;
|
||||
private lightingProgram: IProgram;
|
||||
private distancePass: RenderingPass;
|
||||
private lightingPass: RenderingPass;
|
||||
|
||||
private primitives: Array<IPrimitive>;
|
||||
private lights: Array<ILight>;
|
||||
private autoscaler: FpsAutoscaler;
|
||||
|
||||
constructor(
|
||||
private canvas: HTMLCanvasElement,
|
||||
private overlay: HTMLElement,
|
||||
shaderSources: Array<[string, string]>
|
||||
) {
|
||||
this.getContext();
|
||||
this.createPipeline(shaderSources);
|
||||
this.setupAutoscaling();
|
||||
constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) {
|
||||
this.gl = getWebGl2Context(canvas);
|
||||
|
||||
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl);
|
||||
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl);
|
||||
|
||||
this.distancePass = new RenderingPass(
|
||||
this.gl,
|
||||
[caveVertexShader, caveFragmentShader],
|
||||
[TunnelShape.descriptor],
|
||||
this.distanceFieldFrameBuffer
|
||||
);
|
||||
|
||||
this.lightingPass = new RenderingPass(
|
||||
this.gl,
|
||||
[lightsVertexShader, lightsFragmentShader],
|
||||
[CircleLight.descriptor],
|
||||
this.lightingFrameBuffer
|
||||
);
|
||||
|
||||
this.autoscaler = new FpsAutoscaler([
|
||||
this.lightingFrameBuffer,
|
||||
this.distanceFieldFrameBuffer,
|
||||
]);
|
||||
|
||||
try {
|
||||
this.stopwatch = new WebGlStopwatch(this.gl);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
private getContext() {
|
||||
this.gl = this.canvas.getContext('webgl2');
|
||||
if (!this.gl) {
|
||||
throw new Error('WebGl2 is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
private createPipeline(shaderSources: Array<[string, string]>) {
|
||||
const distanceScale = settings.shaderUniforms.distanceScale;
|
||||
|
||||
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl);
|
||||
this.distanceProgram = new UniformArrayAutoScalingProgram(
|
||||
this.gl,
|
||||
shaderSources[0][0],
|
||||
shaderSources[0][1],
|
||||
{ ...settings.shaderUniforms },
|
||||
{
|
||||
getValueFromUniforms: (v) => (v.lines ? v.lines.length / 2 : 0),
|
||||
uniformArraySizeName: 'lineCount',
|
||||
startingValue: 0,
|
||||
enablingMacro: 'linesEnabled',
|
||||
steps: 1,
|
||||
maximumValue: 15,
|
||||
}
|
||||
);
|
||||
|
||||
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl);
|
||||
this.lightingProgram = new UniformArrayAutoScalingProgram(
|
||||
this.gl,
|
||||
shaderSources[1][0],
|
||||
shaderSources[1][1],
|
||||
{ ...settings.shaderUniforms },
|
||||
{
|
||||
getValueFromUniforms: (v) => (v.lights ? v.lights.length : 0),
|
||||
uniformArraySizeName: 'lightCount',
|
||||
startingValue: 1,
|
||||
enablingMacro: null,
|
||||
steps: 1,
|
||||
maximumValue: 8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private setupAutoscaling() {
|
||||
this.qualityScaler = new Autoscaler(
|
||||
[
|
||||
(v) => (this.lightingFrameBuffer.renderScale = v),
|
||||
(v) => (this.distanceFieldFrameBuffer.renderScale = v),
|
||||
],
|
||||
settings.qualityScaling.scaleTargets,
|
||||
settings.qualityScaling.startingTargetIndex,
|
||||
settings.qualityScaling.scalingOptions
|
||||
);
|
||||
}
|
||||
|
||||
private timeSinceLastAdjusment = 0;
|
||||
private exponentialDecayedDeltaTime = 0.0;
|
||||
private configureQuality(deltaTime: DOMHighResTimeStamp) {
|
||||
this.timeSinceLastAdjusment += deltaTime;
|
||||
if (
|
||||
this.timeSinceLastAdjusment >=
|
||||
settings.qualityScaling.adjusmentRateInMilliseconds
|
||||
) {
|
||||
this.timeSinceLastAdjusment = 0;
|
||||
this.exponentialDecayedDeltaTime = exponentialDecay(
|
||||
this.exponentialDecayedDeltaTime,
|
||||
deltaTime,
|
||||
settings.qualityScaling.deltaTimeResponsiveness
|
||||
);
|
||||
|
||||
if (
|
||||
this.exponentialDecayedDeltaTime <=
|
||||
settings.qualityScaling.targetDeltaTimeInMilliseconds -
|
||||
settings.qualityScaling.deltaTimeError
|
||||
) {
|
||||
this.qualityScaler.increase();
|
||||
} else if (
|
||||
this.exponentialDecayedDeltaTime >
|
||||
settings.qualityScaling.targetDeltaTimeInMilliseconds +
|
||||
settings.qualityScaling.deltaTimeError
|
||||
) {
|
||||
this.qualityScaler.decrease();
|
||||
}
|
||||
}
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'quality',
|
||||
`${toPercent(this.distanceFieldFrameBuffer.renderScale)}, ${toPercent(
|
||||
this.lightingFrameBuffer.renderScale
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
public drawPrimitive(primitive: IPrimitive) {
|
||||
this.primitives.push(primitive);
|
||||
this.distancePass.addDrawable(primitive);
|
||||
}
|
||||
|
||||
public drawLight(light: ILight) {
|
||||
this.lights.push(light);
|
||||
this.lightingPass.addDrawable(light);
|
||||
}
|
||||
|
||||
public startFrame(deltaTime: DOMHighResTimeStamp): void {
|
||||
this.configureQuality(deltaTime);
|
||||
this.primitives = [];
|
||||
this.lights = [];
|
||||
this.autoscaler.autoscale(deltaTime);
|
||||
|
||||
this.stopwatch?.start();
|
||||
this.distanceFieldFrameBuffer.setSize();
|
||||
|
|
@ -160,84 +84,13 @@ export class WebGl2Renderer implements IRenderer {
|
|||
}
|
||||
|
||||
public finishFrame() {
|
||||
const uniforms: any = this.calculateOwnUniforms();
|
||||
|
||||
this.lights.forEach((l) => l.serializeToUniforms(uniforms));
|
||||
|
||||
this.distanceFieldFrameBuffer.bindAndClear();
|
||||
const q = 1 / settings.tileMultiplier;
|
||||
const uvSize = vec2.fromValues(q, q);
|
||||
|
||||
const possiblyOnScreenPrimitives = this.primitives.filter(
|
||||
(p) => p.minimumDistance(this.viewCircle.center) < this.viewCircle.radius
|
||||
) as Array<TunnelShape>;
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'nearby lines',
|
||||
possiblyOnScreenPrimitives.length.toString()
|
||||
);
|
||||
|
||||
const origin = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.fromValues(0, 0),
|
||||
uniforms.uvToWorld
|
||||
);
|
||||
|
||||
const firstCenter = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.fromValues(q, q),
|
||||
uniforms.uvToWorld
|
||||
);
|
||||
|
||||
vec2.subtract(firstCenter, firstCenter, origin);
|
||||
|
||||
const worldR = vec2.length(firstCenter);
|
||||
uniforms.maxMinDistance = 2 * worldR;
|
||||
|
||||
let sumLineCount = 0;
|
||||
|
||||
for (let x = 0; x < 1; x += q) {
|
||||
for (let y = 0; y < 1; y += q) {
|
||||
const uvBottomLeft = vec2.fromValues(x, y);
|
||||
this.distanceProgram.setDrawingRectangle(uvBottomLeft, uvSize);
|
||||
|
||||
const tileCenterWorldCoordinates = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.add(vec2.create(), uvBottomLeft, vec2.fromValues(q, q)),
|
||||
uniforms.uvToWorld
|
||||
);
|
||||
|
||||
const primitivesNearTile = possiblyOnScreenPrimitives.filter(
|
||||
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
|
||||
);
|
||||
|
||||
sumLineCount += primitivesNearTile.length;
|
||||
|
||||
uniforms.lines = [];
|
||||
uniforms.radii = [];
|
||||
|
||||
primitivesNearTile.forEach((p) => p.serializeToUniforms(uniforms));
|
||||
|
||||
this.distanceProgram.bindAndSetUniforms(uniforms);
|
||||
this.distanceProgram.draw();
|
||||
}
|
||||
}
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'lines',
|
||||
(
|
||||
sumLineCount /
|
||||
settings.tileMultiplier /
|
||||
settings.tileMultiplier
|
||||
).toFixed(2)
|
||||
);
|
||||
|
||||
this.lightingFrameBuffer.bindAndClear(
|
||||
const uniforms = this.calculateOwnUniforms();
|
||||
this.distancePass.render(uniforms, this.viewCircle);
|
||||
this.lightingPass.render(
|
||||
uniforms,
|
||||
this.viewCircle,
|
||||
this.distanceFieldFrameBuffer.colorTexture
|
||||
);
|
||||
this.lightingProgram.bindAndSetUniforms(uniforms);
|
||||
this.lightingProgram.draw();
|
||||
|
||||
this.stopwatch?.stop();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue