From bc16bdd62e0b097f742c2351fb2381183e4199fb Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Wed, 16 Sep 2020 14:45:08 +0200 Subject: [PATCH] Add more improvements --- src/drawables/lights/circle-light.ts | 3 +- src/drawables/lights/flashlight.ts | 3 +- src/drawables/shapes/circle.ts | 12 ++- src/drawables/shapes/tunnel.ts | 93 +++++++++++++++++++ .../compiling/check-program.ts | 2 +- .../compiling/check-shader.ts | 2 +- .../compiling/create-program.ts | 10 +- .../compiling/create-shader.ts | 7 +- .../frame-buffer/default-frame-buffer.ts | 4 +- .../frame-buffer/frame-buffer.ts | 7 +- .../frame-buffer/intermediate-frame-buffer.ts | 18 ++-- .../helper/enable-extension.ts | 8 +- .../graphics-library/helper/stopwatch.ts | 3 +- .../program/fragment-shader-only-program.ts | 8 +- .../graphics-library/program/program.ts | 3 +- .../uniform-array-autoscaling-program.ts | 4 +- src/graphics/rendering/insights.ts | 21 +++++ src/graphics/rendering/renderer.ts | 14 +-- src/graphics/rendering/rendering-pass.ts | 12 ++- .../{ => rendering}/shaders/distance-fs.glsl | 33 +------ .../{ => rendering}/shaders/distance-vs.glsl | 0 .../{ => rendering}/shaders/shading-fs.glsl | 12 +-- .../{ => rendering}/shaders/shading-vs.glsl | 0 .../rendering/webgl2-renderer-settings.ts | 9 ++ src/graphics/rendering/webgl2-renderer.ts | 85 ++++++++++++++--- src/graphics/settings.ts | 8 -- src/helper/array.ts | 54 ++++------- src/helper/autoscaler.ts | 3 +- src/main.ts | 10 +- 29 files changed, 288 insertions(+), 160 deletions(-) create mode 100644 src/drawables/shapes/tunnel.ts create mode 100644 src/graphics/rendering/insights.ts rename src/graphics/{ => rendering}/shaders/distance-fs.glsl (64%) rename src/graphics/{ => rendering}/shaders/distance-vs.glsl (100%) rename src/graphics/{ => rendering}/shaders/shading-fs.glsl (95%) rename src/graphics/{ => rendering}/shaders/shading-vs.glsl (100%) create mode 100644 src/graphics/rendering/webgl2-renderer-settings.ts diff --git a/src/drawables/lights/circle-light.ts b/src/drawables/lights/circle-light.ts index 1bccf56..91e1166 100644 --- a/src/drawables/lights/circle-light.ts +++ b/src/drawables/lights/circle-light.ts @@ -1,5 +1,4 @@ import { mat2d, vec2, vec3 } from 'gl-matrix'; -import { settings } from '../../graphics/settings'; import { Drawable } from '../drawable'; import { DrawableDescriptor } from '../drawable-descriptor'; @@ -8,7 +7,7 @@ export class CircleLight extends Drawable { return { uniformName: 'circleLights', uniformCountMacroName: 'CIRCLE_LIGHT_COUNT', - shaderCombinationSteps: settings.shaderCombinations.circleLightSteps, + shaderCombinationSteps: [0, 1, 2, 4], empty: new CircleLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0), }; } diff --git a/src/drawables/lights/flashlight.ts b/src/drawables/lights/flashlight.ts index 5abfdc8..cdbecda 100644 --- a/src/drawables/lights/flashlight.ts +++ b/src/drawables/lights/flashlight.ts @@ -1,5 +1,4 @@ import { mat2d, vec2, vec3 } from 'gl-matrix'; -import { settings } from '../../graphics/settings'; import { Drawable } from '../drawable'; import { DrawableDescriptor } from '../drawable-descriptor'; @@ -8,7 +7,7 @@ export class Flashlight extends Drawable { return { uniformName: 'flashlights', uniformCountMacroName: 'FLASHLIGHT_COUNT', - shaderCombinationSteps: settings.shaderCombinations.flashlightSteps, + shaderCombinationSteps: [0, 1, 2, 4], empty: new Flashlight( vec2.fromValues(0, 0), vec2.fromValues(0, 0), diff --git a/src/drawables/shapes/circle.ts b/src/drawables/shapes/circle.ts index 0f14783..82c7f75 100644 --- a/src/drawables/shapes/circle.ts +++ b/src/drawables/shapes/circle.ts @@ -13,10 +13,16 @@ export class Circle extends Drawable { }[CIRCLE_COUNT] circles; void circleMinDistance(inout float minDistance, inout float color) { + float circleMinDistance = minDistance; for (int i = 0; i < CIRCLE_COUNT; i++) { float dist = distance(circles[i].center, position) - circles[i].radius; - minDistance = min(minDistance, dist); + circleMinDistance = min(circleMinDistance, dist); } + minDistance = min(minDistance, circleMinDistance); + color = mix(2.0, color, step( + distanceNdcPixelSize + SURFACE_OFFSET, + circleMinDistance + )); } `, distanceFunctionName: 'circleMinDistance', @@ -32,8 +38,8 @@ export class Circle extends Drawable { super(); } - public distance(position: vec2): number { - return vec2.dist(this.center, position) - this.radius; + public distance(target: vec2): number { + return vec2.dist(this.center, target) - this.radius; } protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any { diff --git a/src/drawables/shapes/tunnel.ts b/src/drawables/shapes/tunnel.ts new file mode 100644 index 0000000..401ebe0 --- /dev/null +++ b/src/drawables/shapes/tunnel.ts @@ -0,0 +1,93 @@ +import { mat2d, vec2 } from 'gl-matrix'; +import { clamp01 } from '../../helper/clamp'; +import { mix } from '../../helper/mix'; +import { Drawable } from '../drawable'; +import { DrawableDescriptor } from '../drawable-descriptor'; + +export class Tunnel extends Drawable { + public readonly isInverted = false; + public readonly toFromDelta: vec2; + + public static get descriptor(): DrawableDescriptor { + return { + sdf: { + shader: ` + uniform struct Tunnel { + vec2 from; + vec2 toFromDelta; + float fromRadius; + float toRadius; + float inverted; + }[TUNNEL_COUNT] tunnels; + + void tunnelMinDistance(inout float minDistance, inout float color) { + float tunnelMinDistance = minDistance; + for (int i = 0; i < TUNNEL_COUNT; i++) { + Tunnel tunnel = tunnels[i]; + vec2 targetFromDelta = position - tunnel.from; + + float h = clamp( + dot(targetFromDelta, tunnel.toFromDelta) + / dot(tunnel.toFromDelta, tunnel.toFromDelta), + 0.0, 1.0 + ); + + float lineDistance = -mix( + tunnel.fromRadius, tunnel.toRadius, h + ) + tunnel.inverted * distance( + targetFromDelta, tunnel.toFromDelta * h + ); + + tunnelMinDistance = min(tunnelMinDistance, lineDistance); + } + + color = mix(2.0, color, step( + distanceNdcPixelSize + SURFACE_OFFSET, + -tunnelMinDistance + )); + minDistance = -tunnelMinDistance; + } + `, + distanceFunctionName: 'tunnelMinDistance', + }, + uniformName: 'tunnels', + uniformCountMacroName: 'TUNNEL_COUNT', + shaderCombinationSteps: [0, 1, 2, 8, 32], + empty: new Tunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0), + }; + } + + constructor( + public readonly from: vec2, + public readonly to: vec2, + public readonly fromRadius: number, + public readonly toRadius: number + ) { + super(); + this.toFromDelta = vec2.subtract(vec2.create(), to, from); + } + + public distance(target: vec2): number { + const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); + + const h = clamp01( + vec2.dot(targetFromDelta, this.toFromDelta) / + vec2.dot(this.toFromDelta, this.toFromDelta) + ); + + return ( + vec2.distance(targetFromDelta, vec2.scale(vec2.create(), this.toFromDelta, h)) - + mix(this.fromRadius, this.toRadius, h) + ); + } + + protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any { + return { + from: vec2.transformMat2d(vec2.create(), this.from, transform2d), + toFromDelta: vec2.scale(vec2.create(), this.toFromDelta, transform1d), + fromRadius: this.fromRadius * transform1d, + toRadius: this.toRadius * transform1d, + inverted: this.isInverted ? -1 : 1, + }; + } +} diff --git a/src/graphics/graphics-library/compiling/check-program.ts b/src/graphics/graphics-library/compiling/check-program.ts index 03491fe..b963139 100644 --- a/src/graphics/graphics-library/compiling/check-program.ts +++ b/src/graphics/graphics-library/compiling/check-program.ts @@ -3,7 +3,7 @@ import { checkShader } from './check-shader'; export const checkProgram = (gl: WebGL2RenderingContext, program: WebGLProgram) => { const success = gl.getProgramParameter(program, gl.LINK_STATUS); - if (!success) { + if (!success && !gl.isContextLost()) { gl.getAttachedShaders(program)?.forEach((s) => { checkShader(gl, s); }); diff --git a/src/graphics/graphics-library/compiling/check-shader.ts b/src/graphics/graphics-library/compiling/check-shader.ts index cc7ff32..670915b 100644 --- a/src/graphics/graphics-library/compiling/check-shader.ts +++ b/src/graphics/graphics-library/compiling/check-shader.ts @@ -1,7 +1,7 @@ export const checkShader = (gl: WebGL2RenderingContext, shader: WebGLShader) => { const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - if (!success) { + if (!success && !gl.isContextLost()) { throw new Error(gl.getShaderInfoLog(shader)!); } }; diff --git a/src/graphics/graphics-library/compiling/create-program.ts b/src/graphics/graphics-library/compiling/create-program.ts index 0416fb5..80a08cc 100644 --- a/src/graphics/graphics-library/compiling/create-program.ts +++ b/src/graphics/graphics-library/compiling/create-program.ts @@ -7,11 +7,8 @@ export const createProgram = ( fragmentShaderSource: string, substitutions: { [name: string]: string } ): WebGLProgram => { - const program = gl.createProgram(); - - if (!program) { - throw new Error('Could not create program'); - } + // can only return null on lost context + const program = gl.createProgram()!; // const extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile'); @@ -43,5 +40,8 @@ export const createProgram = ( checkProgram(gl, program); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + return program; }; diff --git a/src/graphics/graphics-library/compiling/create-shader.ts b/src/graphics/graphics-library/compiling/create-shader.ts index fab7ec8..18dd20b 100644 --- a/src/graphics/graphics-library/compiling/create-shader.ts +++ b/src/graphics/graphics-library/compiling/create-shader.ts @@ -9,11 +9,8 @@ export const createShader = ( return Number.isInteger(value) ? `${value}.0` : value; }); - const shader = gl.createShader(type); - - if (!shader) { - throw new Error('Could not create shader'); - } + // can only return null on lost context + const shader = gl.createShader(type)!; gl.shaderSource(shader, source); gl.compileShader(shader); diff --git a/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts index be495ad..de26568 100644 --- a/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/default-frame-buffer.ts @@ -1,8 +1,8 @@ import { FrameBuffer } from './frame-buffer'; export class DefaultFrameBuffer extends FrameBuffer { - constructor(gl: WebGL2RenderingContext) { - super(gl); + constructor(gl: WebGL2RenderingContext, enableHighDpiRendering: boolean) { + super(gl, enableHighDpiRendering); this.frameBuffer = null; this.setSize(); } diff --git a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts index c56f707..29a9a9e 100644 --- a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts @@ -1,16 +1,17 @@ import { vec2 } from 'gl-matrix'; -import { settings } from '../../settings'; export abstract class FrameBuffer { public renderScale = 1; - public enableHighDpiRendering = settings.enableHighDpiRendering; protected size = vec2.create(); // null means the default framebuffer protected frameBuffer: WebGLFramebuffer | null = null; - constructor(protected gl: WebGL2RenderingContext) {} + constructor( + protected gl: WebGL2RenderingContext, + private enableHighDpiRendering: boolean + ) {} public bindAndClear(colorInput?: WebGLTexture) { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); diff --git a/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts b/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts index e16ec57..716a743 100644 --- a/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts @@ -5,8 +5,8 @@ export class IntermediateFrameBuffer extends FrameBuffer { private frameTexture: WebGLTexture; private floatLinearEnabled = false; - constructor(gl: WebGL2RenderingContext) { - super(gl); + constructor(gl: WebGL2RenderingContext, enableHighDpiRendering: boolean) { + super(gl, enableHighDpiRendering); enableExtension(gl, 'EXT_color_buffer_float'); @@ -17,18 +17,12 @@ export class IntermediateFrameBuffer extends FrameBuffer { // it's okay } - const texture = this.gl.createTexture(); - if (!texture) { - throw new Error('Could not create texture'); - } - this.frameTexture = texture; + // can only return null on lost context + this.frameTexture = this.gl.createTexture()!; this.configureTexture(); - const frameBuffer = this.gl.createFramebuffer(); - if (!frameBuffer) { - throw new Error('Could not create frame buffer'); - } - this.frameBuffer = frameBuffer; + // can only return null on lost context + this.frameBuffer = this.gl.createFramebuffer()!; this.configureFrameBuffer(); this.setSize(); diff --git a/src/graphics/graphics-library/helper/enable-extension.ts b/src/graphics/graphics-library/helper/enable-extension.ts index 0ad5aa5..fd23466 100644 --- a/src/graphics/graphics-library/helper/enable-extension.ts +++ b/src/graphics/graphics-library/helper/enable-extension.ts @@ -1,11 +1,13 @@ +import { Insights } from '../../rendering/insights'; + const extensions: Map = new Map(); -const printExtensions = () => { +const logExtensions = () => { const values = {}; for (const [k, v] of extensions.entries()) { values[k] = v !== null; } - //InfoText.modifyRecord('extensions', values); + Insights.setValue('extensions', values); }; export const tryEnableExtension = ( @@ -23,7 +25,7 @@ export const tryEnableExtension = ( extensions.set(name, extension); - printExtensions(); + logExtensions(); return extension; }; diff --git a/src/graphics/graphics-library/helper/stopwatch.ts b/src/graphics/graphics-library/helper/stopwatch.ts index 4e324b4..be45f80 100644 --- a/src/graphics/graphics-library/helper/stopwatch.ts +++ b/src/graphics/graphics-library/helper/stopwatch.ts @@ -1,3 +1,4 @@ +import { Insights } from '../../rendering/insights'; import { enableExtension } from './enable-extension'; // https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query_webgl2/ @@ -37,7 +38,7 @@ export class WebGlStopwatch { this.gl.QUERY_RESULT ); - //InfoText.modifyRecord('Draw time', `${this.resultsInMilliSeconds.toFixed(2)} ms`); + Insights.setValue('GPU draw time', `${this.resultsInMilliSeconds.toFixed(2)} ms`); this.isReady = true; } diff --git a/src/graphics/graphics-library/program/fragment-shader-only-program.ts b/src/graphics/graphics-library/program/fragment-shader-only-program.ts index f92dbab..d6ad3ab 100644 --- a/src/graphics/graphics-library/program/fragment-shader-only-program.ts +++ b/src/graphics/graphics-library/program/fragment-shader-only-program.ts @@ -35,11 +35,9 @@ export class FragmentShaderOnlyProgram extends Program { new Float32Array([-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]), this.gl.STATIC_DRAW ); - const vao = this.gl.createVertexArray(); - if (!vao) { - throw new Error('Could not create vertex array object'); - } - this.vao = vao; + + // can only return null on lost context + this.vao = this.gl.createVertexArray()!; this.gl.bindVertexArray(this.vao); this.gl.enableVertexAttribArray(positionAttributeLocation); diff --git a/src/graphics/graphics-library/program/program.ts b/src/graphics/graphics-library/program/program.ts index 6e7e1b4..081dbb3 100644 --- a/src/graphics/graphics-library/program/program.ts +++ b/src/graphics/graphics-library/program/program.ts @@ -1,5 +1,4 @@ import { mat2d, vec2 } from 'gl-matrix'; -import { settings } from '../../settings'; import { createProgram } from '../compiling/create-program'; import { loadUniform } from '../helper/load-uniform'; import { IProgram } from './i-program'; @@ -20,7 +19,7 @@ export default abstract class Program implements IProgram { [vertexShaderSource, fragmentShaderSource]: [string, string], substitutions: { [name: string]: string } ) { - substitutions = { ...settings.shaderMacros, ...substitutions }; + substitutions = { ...substitutions }; this.program = createProgram( this.gl, diff --git a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts index ac22c4e..b302958 100644 --- a/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts +++ b/src/graphics/graphics-library/program/uniform-array-autoscaling-program.ts @@ -18,7 +18,8 @@ export class UniformArrayAutoScalingProgram implements IProgram { constructor( private gl: WebGL2RenderingContext, shaderSources: [string, string], - private descriptors: Array + private descriptors: Array, + private substitutions: { [name: string]: any } ) { for (const combination of getCombinations( descriptors.map((o) => o.shaderCombinationSteps) @@ -75,6 +76,7 @@ export class UniformArrayAutoScalingProgram implements IProgram { shaderSources: [string, string] ): FragmentShaderOnlyProgram { const substitutions = { + ...this.substitutions, macroDefinitions: this.getMacroDefinitions(combination, descriptors), declarations: this.getDeclarations(combination, descriptors), functionCalls: this.getFunctionCalls(combination, descriptors), diff --git a/src/graphics/rendering/insights.ts b/src/graphics/rendering/insights.ts new file mode 100644 index 0000000..ff139a4 --- /dev/null +++ b/src/graphics/rendering/insights.ts @@ -0,0 +1,21 @@ +export class Insights { + private static insights: any = {}; + + public static setValue(key: string | Array, value: any): void { + if (Array.isArray(key)) { + key.reduce((previousValue, currentKey) => { + if (!Object.prototype.hasOwnProperty.call(previousValue, currentKey)) { + previousValue[currentKey] = {}; + } + + return previousValue[currentKey]; + }, Insights.insights); + } else { + Insights.insights[key] = value; + } + } + + public static get values(): any { + return Insights.insights; + } +} diff --git a/src/graphics/rendering/renderer.ts b/src/graphics/rendering/renderer.ts index d340fa2..8e1a9cb 100644 --- a/src/graphics/rendering/renderer.ts +++ b/src/graphics/rendering/renderer.ts @@ -2,13 +2,13 @@ import { vec2 } from 'gl-matrix'; import { Drawable } from '../../drawables/drawable'; export interface Renderer { - setViewArea(topLeft: vec2, size: vec2): void; - - addDrawable(drawable: Drawable): void; - renderDrawables(): void; - - autoscaleQuality(deltaTime: DOMHighResTimeStamp): void; - readonly canvasSize: vec2; + readonly insights: any; + + setViewArea(topLeft: vec2, size: vec2): void; setCursorPosition(position: vec2): void; + addDrawable(drawable: Drawable): void; + autoscaleQuality(deltaTime: DOMHighResTimeStamp): void; + renderDrawables(): void; + destroy(): void; } diff --git a/src/graphics/rendering/rendering-pass.ts b/src/graphics/rendering/rendering-pass.ts index 9e1d708..70fb2cf 100644 --- a/src/graphics/rendering/rendering-pass.ts +++ b/src/graphics/rendering/rendering-pass.ts @@ -3,7 +3,6 @@ import { Drawable } from '../../drawables/drawable'; import { DrawableDescriptor } from '../../drawables/drawable-descriptor'; import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer'; import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program'; -import { settings } from '../settings'; export class RenderingPass { private drawables: Array = []; @@ -13,12 +12,15 @@ export class RenderingPass { gl: WebGL2RenderingContext, shaderSources: [string, string], drawableDescriptors: Array, - private frame: FrameBuffer + private frame: FrameBuffer, + substitutions: { [name: string]: any }, + private tileMultiplier: number ) { this.program = new UniformArrayAutoScalingProgram( gl, shaderSources, - drawableDescriptors + drawableDescriptors, + substitutions ); } @@ -29,7 +31,7 @@ export class RenderingPass { public render(commonUniforms: any, inputTexture?: WebGLTexture) { this.frame.bindAndClear(inputTexture); - const stepsInUV = 1 / settings.tileMultiplier; + const stepsInUV = 1 / this.tileMultiplier; const worldR = 0.5 * @@ -41,7 +43,7 @@ export class RenderingPass { for (let x = -1; x < 1; x += stepsInNDC) { for (let y = -1; y < 1; y += stepsInNDC) { - const uniforms = { ...commonUniforms, maxMinDistance: 0.0 }; + const uniforms = { ...commonUniforms, maxMinDistance: radiusInNDC }; const ndcBottomLeft = vec2.fromValues(x, y); diff --git a/src/graphics/shaders/distance-fs.glsl b/src/graphics/rendering/shaders/distance-fs.glsl similarity index 64% rename from src/graphics/shaders/distance-fs.glsl rename to src/graphics/rendering/shaders/distance-fs.glsl index da85409..8611b37 100644 --- a/src/graphics/shaders/distance-fs.glsl +++ b/src/graphics/rendering/shaders/distance-fs.glsl @@ -13,37 +13,6 @@ in vec2 position; {declarations} /* -#if LINE_COUNT > 0 - uniform struct { - vec2 from; - vec2 toFromDelta; - float fromRadius; - float toRadius; - }[LINE_COUNT] lines; - - void lineMinDistance(inout float minDistance, inout float color) { - float myMinDistance = maxMinDistance; - for (int i = 0; i < LINE_COUNT; i++) { - vec2 targetFromDelta = position - lines[i].from; - vec2 toFromDelta = lines[i].toFromDelta; - - float h = clamp( - dot(targetFromDelta, toFromDelta) / dot(toFromDelta, toFromDelta), - 0.0, 1.0 - ); - - float lineDistance = -mix( - lines[i].fromRadius, lines[i].toRadius, h - ) + distance( - targetFromDelta, toFromDelta * h - ); - - myMinDistance = min(myMinDistance, lineDistance); - } - - color = mix(0.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, -myMinDistance)); - minDistance = -myMinDistance; - } #endif #if BLOB_COUNT > 0 uniform struct { @@ -93,7 +62,7 @@ in vec2 position; out vec2 fragmentColor; void main() { - float minDistance = 10.0; //-maxMinDistance; + float minDistance = maxMinDistance; float color = 1.0; {functionCalls} diff --git a/src/graphics/shaders/distance-vs.glsl b/src/graphics/rendering/shaders/distance-vs.glsl similarity index 100% rename from src/graphics/shaders/distance-vs.glsl rename to src/graphics/rendering/shaders/distance-vs.glsl diff --git a/src/graphics/shaders/shading-fs.glsl b/src/graphics/rendering/shaders/shading-fs.glsl similarity index 95% rename from src/graphics/shaders/shading-fs.glsl rename to src/graphics/rendering/shaders/shading-fs.glsl index 2acf257..ae0f955 100644 --- a/src/graphics/shaders/shading-fs.glsl +++ b/src/graphics/rendering/shaders/shading-fs.glsl @@ -3,9 +3,11 @@ precision lowp float; #define INFINITY 1000.0 -#define LIGHT_DROP_INSIDE_RATIO 0.3 +#define LIGHT_DROP_INSIDE_RATIO 0.5 #define AMBIENT_LIGHT vec3(0.25, 0.15, 0.25) #define SHADOW_HARDNESS 150.0 +#define HARD_SHADOW_TRACE_COUNT {hardShadowTraceCount} +#define SOFT_SHADOW_TRACE_COUNT {softShadowTraceCount} {macroDefinitions} @@ -18,9 +20,7 @@ in vec2 position; in vec2 uvCoordinates; vec3[3] colors = vec3[]( - vec3(0.4, 0.35, 0.6), // cave color - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0) + {palette} ); float getDistance(in vec2 target, out vec3 color) { @@ -37,7 +37,7 @@ float softShadowTransparency(float startingDistance, float lightCenterDistance, float rayLength = startingDistance; float q = 1.0 / SHADOW_HARDNESS; - for (int j = 0; j < 128; j++) { + for (int j = 0; j < SOFT_SHADOW_TRACE_COUNT; j++) { float minDistance = getDistance(uvCoordinates + direction * rayLength); q = min(q, minDistance / rayLength); @@ -54,7 +54,7 @@ float softShadowTransparency(float startingDistance, float lightCenterDistance, float hardShadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { float rayLength = startingDistance; - for (int j = 0; j < 32; j++) { + for (int j = 0; j < HARD_SHADOW_TRACE_COUNT; j++) { rayLength += getDistance(uvCoordinates + direction * rayLength); } diff --git a/src/graphics/shaders/shading-vs.glsl b/src/graphics/rendering/shaders/shading-vs.glsl similarity index 100% rename from src/graphics/shaders/shading-vs.glsl rename to src/graphics/rendering/shaders/shading-vs.glsl diff --git a/src/graphics/rendering/webgl2-renderer-settings.ts b/src/graphics/rendering/webgl2-renderer-settings.ts new file mode 100644 index 0000000..5578edf --- /dev/null +++ b/src/graphics/rendering/webgl2-renderer-settings.ts @@ -0,0 +1,9 @@ +export interface WebGl2RendererSettings { + enableHighDpiRendering: boolean; + enableStopwatch: boolean; + tileMultiplier: number; + shaderMacros: { + softShadowTraceCount: string; + hardShadowTraceCount: string; + }; +} diff --git a/src/graphics/rendering/webgl2-renderer.ts b/src/graphics/rendering/webgl2-renderer.ts index 46513fc..8759724 100644 --- a/src/graphics/rendering/webgl2-renderer.ts +++ b/src/graphics/rendering/webgl2-renderer.ts @@ -1,19 +1,21 @@ -import { vec2 } from 'gl-matrix'; +import { vec2, vec3 } from 'gl-matrix'; import { Drawable } from '../../drawables/drawable'; import { DrawableDescriptor } from '../../drawables/drawable-descriptor'; import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer'; import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer'; import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context'; import { WebGlStopwatch } from '../graphics-library/helper/stopwatch'; -import distanceFragmentShader from '../shaders/distance-fs.glsl'; -import distanceVertexShader from '../shaders/distance-vs.glsl'; -import lightsFragmentShader from '../shaders/shading-fs.glsl'; -import lightsVertexShader from '../shaders/shading-vs.glsl'; import { FpsAutoscaler } from './fps-autoscaler'; +import { Insights } from './insights'; import { Renderer } from './renderer'; import { RenderingPass } from './rendering-pass'; import { RenderingPassName } from './rendering-pass-name'; +import distanceFragmentShader from './shaders/distance-fs.glsl'; +import distanceVertexShader from './shaders/distance-vs.glsl'; +import lightsFragmentShader from './shaders/shading-fs.glsl'; +import lightsVertexShader from './shaders/shading-vs.glsl'; import { UniformsProvider } from './uniforms-provider'; +import { WebGl2RendererSettings } from './webgl2-renderer-settings'; export class WebGl2Renderer implements Renderer { private gl: WebGL2RenderingContext; @@ -24,24 +26,54 @@ export class WebGl2Renderer implements Renderer { private passes: { [key in keyof typeof RenderingPassName]: RenderingPass }; private autoscaler: FpsAutoscaler; - constructor(private canvas: HTMLCanvasElement, descriptors: Array) { + private static defaultSettings: WebGl2RendererSettings = { + enableHighDpiRendering: true, + enableStopwatch: true, + tileMultiplier: 8, + shaderMacros: { + softShadowTraceCount: '128', + hardShadowTraceCount: '32', + }, + }; + + constructor( + private canvas: HTMLCanvasElement, + descriptors: Array, + palette: Array, + settingsOverrides: Partial + ) { + const settings = { ...WebGl2Renderer.defaultSettings, ...settingsOverrides }; + this.gl = getWebGl2Context(canvas); - this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl); - this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl); + this.distanceFieldFrameBuffer = new IntermediateFrameBuffer( + this.gl, + settings.enableHighDpiRendering + ); + this.lightingFrameBuffer = new DefaultFrameBuffer( + this.gl, + settings.enableHighDpiRendering + ); this.passes = { [RenderingPassName.distance]: new RenderingPass( this.gl, [distanceVertexShader, distanceFragmentShader], descriptors.filter(WebGl2Renderer.hasSdf), - this.distanceFieldFrameBuffer + this.distanceFieldFrameBuffer, + settings.shaderMacros, + settings.tileMultiplier ), [RenderingPassName.pixel]: new RenderingPass( this.gl, [lightsVertexShader, lightsFragmentShader], descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)), - this.lightingFrameBuffer + this.lightingFrameBuffer, + { + palette: this.generatePaletteCode(palette), + ...settings.shaderMacros, + }, + settings.tileMultiplier ), }; @@ -55,13 +87,26 @@ export class WebGl2Renderer implements Renderer { (this.uniformsProvider.softShadowsEnabled = v as boolean), }); - try { - this.stopwatch = new WebGlStopwatch(this.gl); - } catch { - // no problem + if (settings.enableStopwatch) { + try { + this.stopwatch = new WebGlStopwatch(this.gl); + } catch { + // no problem + } } } + public get insights(): any { + return Insights.values; + } + + public destroy(): void { + // todo + /*const extension = enableExtension(this.gl, 'WEBGL_lose_context'); + extension.loseContext(); + extension.restoreContext();*/ + } + private static hasSdf(descriptor: DrawableDescriptor) { return Object.prototype.hasOwnProperty.call(descriptor, 'sdf'); } @@ -78,6 +123,18 @@ export class WebGl2Renderer implements Renderer { this.autoscaler.autoscale(deltaTime); } + private generatePaletteCode(palette: Array) { + const numberToGlslFloat = (n) => (Number.isInteger(n) ? `${n}.0` : `${n}`); + return palette + .map( + (c) => + `vec3(${numberToGlslFloat(c[0])}, ${numberToGlslFloat( + c[1] + )}, ${numberToGlslFloat(c[2])})` + ) + .join(',\n'); + } + public renderDrawables() { this.stopwatch?.start(); diff --git a/src/graphics/settings.ts b/src/graphics/settings.ts index 5c7354b..e9369d4 100644 --- a/src/graphics/settings.ts +++ b/src/graphics/settings.ts @@ -1,5 +1,4 @@ export const settings = { - enableHighDpiRendering: true, qualityScaling: { targetDeltaTimeInMilliseconds: 20, deltaTimeError: 2, @@ -48,11 +47,4 @@ export const settings = { multiplicativeDecrease: 1.05, }, }, - tileMultiplier: 8, - shaderMacros: {}, - shaderCombinations: { - circleLightSteps: [0, 1], - circleSteps: [0, 1, 16], - flashlightSteps: [0, 1], - }, }; diff --git a/src/helper/array.ts b/src/helper/array.ts index bcde98b..5a0ff29 100644 --- a/src/helper/array.ts +++ b/src/helper/array.ts @@ -10,40 +10,22 @@ declare global { } } -export const applyArrayPlugins = () => { - Object.defineProperty(Array.prototype, 'x', { - get() { - return this[0]; - }, - set(value) { - this[0] = value; - }, - }); - - Object.defineProperty(Array.prototype, 'y', { - get() { - return this[1]; - }, - set(value) { - this[1] = value; - }, - }); - - Object.defineProperty(Float32Array.prototype, 'x', { - get() { - return this[0]; - }, - set(value) { - this[0] = value; - }, - }); - - Object.defineProperty(Float32Array.prototype, 'y', { - get() { - return this[1]; - }, - set(value) { - this[1] = value; - }, - }); +const setIndexAlias = (name: string, index: number, type: any) => { + if (!Object.prototype.hasOwnProperty.call(type.prototype, name)) { + Object.defineProperty(type.prototype, name, { + get() { + return this[index]; + }, + set(value) { + this[index] = value; + }, + }); + } +}; + +export const applyArrayPlugins = () => { + setIndexAlias('x', 0, Array); + setIndexAlias('y', 1, Array); + setIndexAlias('x', 0, Float32Array); + setIndexAlias('y', 1, Float32Array); }; diff --git a/src/helper/autoscaler.ts b/src/helper/autoscaler.ts index 98d0ffa..c7b7143 100644 --- a/src/helper/autoscaler.ts +++ b/src/helper/autoscaler.ts @@ -1,3 +1,4 @@ +import { Insights } from '../graphics/rendering/insights'; import { clamp } from './clamp'; import { mix } from './mix'; @@ -54,6 +55,6 @@ export class Autoscaler { this.setters[key](current); } - //InfoText.modifyRecord('quality', result); + Insights.setValue('quality', result); } } diff --git a/src/main.ts b/src/main.ts index acef3c4..2749dd4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,20 +1,24 @@ -import { glMatrix } from 'gl-matrix'; +import { glMatrix, vec3 } from 'gl-matrix'; import { DrawableDescriptor } from './drawables/drawable-descriptor'; import { Renderer } from './graphics/rendering/renderer'; import { WebGl2Renderer } from './graphics/rendering/webgl2-renderer'; +import { WebGl2RendererSettings } from './graphics/rendering/webgl2-renderer-settings'; import { applyArrayPlugins } from './helper/array'; export { Drawable } from './drawables/drawable'; export { CircleLight } from './drawables/lights/circle-light'; export { Flashlight } from './drawables/lights/flashlight'; export { Circle } from './drawables/shapes/circle'; +export { Tunnel } from './drawables/shapes/tunnel'; export { RenderingPassName } from './graphics/rendering/rendering-pass-name'; export const compile = ( canvas: HTMLCanvasElement, - descriptors: Array + descriptors: Array, + palette: Array, + settings: Partial = {} ): Renderer => { glMatrix.setMatrixArrayType(Array); applyArrayPlugins(); - return new WebGl2Renderer(canvas, descriptors); + return new WebGl2Renderer(canvas, descriptors, palette, settings); };