diff --git a/frontend/src/index.ts b/frontend/src/index.ts index ac666fe..048e364 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -25,14 +25,16 @@ const testSDF = () => { return -res; */ - let min = 1000; + /*let min = 1000; for (const t of objects) { min = Math.min(min, t.distance(position)); } if (min < 0) { // min = Math.min(min, vec2.distance(position, vec2.fromValues(40, 80 - 31.62)) - 31.62); } - return -min; + return -min;*/ + + return -vec2.distance(position, vec2.fromValues(40, 40)) + 30; }; const width = 80; @@ -55,11 +57,12 @@ const testSDF = () => { const position = vec2.fromValues(x, y); const redIndex = y * (width * 4) + x * 4; const dist = getPixelValue(position); - if (Math.abs(dist) < 1) { - const blueIndex = y * (width * 4) + x * 4 + 2; + if (Math.abs(dist) < 1.5) { zeroes.push(position); - imageData.data[blueIndex] = 255; } + const blueIndex = y * (width * 4) + x * 4 + 2; + //imageData.data[blueIndex] = dist * 10; + imageData.data[redIndex + 3] = 255; } } @@ -75,25 +78,37 @@ const testSDF = () => { const position = vec2.fromValues(x, y); const dist = getPixelValue(position); - const nearestDist = - zeroes.find((z) => Math.abs(vec2.distance(z, position) - dist) < 0.5)?.length > 0; - if (nearestDist) { + const realDistance = zeroes.map((z) => vec2.distance(z, position)).sort()[0]; + + if (Math.abs(realDistance - dist) < 3) { const greenIndex = y * (width * 4) + x * 4 + 1; imageData.data[greenIndex] = 255; } else if (dist >= 0) { const redIndex = y * (width * 4) + x * 4; - errors.push(dist); + errors.push(realDistance - dist); imageData.data[redIndex] = 255; } + + /*if (zeroes.find((z) => vec2.distance(z, position) < dist) !== undefined) { + const blueIndex = y * (width * 4) + x * 4 + 2; + imageData.data[blueIndex] = 255; + }*/ } } + zeroes.forEach((z) => { + const blueIndex = z.y * (width * 4) + z.x * 4 + 2; + imageData.data[blueIndex] = 255; + imageData.data[blueIndex - 1] = 255; + imageData.data[blueIndex - 2] = 255; + }); + console.log(errors); ctx.putImageData(imageData, 0, 0); }; -// testSDF(); +//testSDF(); // extract as new image (data-uri) /* @@ -113,10 +128,14 @@ tree.print(); console.log(tree.findIntersecting(new BoundingBox(960, 1050, 50, 190, 'G'))); */ -try { - Random.seed = 42; - new Game(); -} catch (e) { - console.error(e); - alert(e); -} +const main = async () => { + try { + Random.seed = 42; + await new Game().start(); + } catch (e) { + console.error(e); + alert(e); + } +}; + +main(); diff --git a/frontend/src/scripts/drawing/graphics-library/compiling/check-program.ts b/frontend/src/scripts/drawing/graphics-library/compiling/check-program.ts new file mode 100644 index 0000000..b750771 --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/compiling/check-program.ts @@ -0,0 +1,13 @@ +import { checkShader } from './check-shader'; + +export const checkProgram = (gl: WebGL2RenderingContext, program: WebGLProgram) => { + const success = gl.getProgramParameter(program, gl.LINK_STATUS); + + if (!success) { + gl.getAttachedShaders(program).forEach((s) => { + checkShader(gl, s); + }); + + throw new Error(gl.getProgramInfoLog(program)); + } +}; diff --git a/frontend/src/scripts/drawing/graphics-library/compiling/check-shader.ts b/frontend/src/scripts/drawing/graphics-library/compiling/check-shader.ts new file mode 100644 index 0000000..0608e06 --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/compiling/check-shader.ts @@ -0,0 +1,7 @@ +export const checkShader = (gl: WebGL2RenderingContext, shader: WebGLShader) => { + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + + if (!success) { + throw new Error(gl.getShaderInfoLog(shader)); + } +}; diff --git a/frontend/src/scripts/drawing/graphics-library/compiling/create-program.ts b/frontend/src/scripts/drawing/graphics-library/compiling/create-program.ts new file mode 100644 index 0000000..790178c --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/compiling/create-program.ts @@ -0,0 +1,44 @@ +import { waitWhileFalse } from '../../../helper/wait-for'; +import { tryEnableExtension } from '../helper/enable-extension'; +import { checkProgram } from './check-program'; +import { createShader } from './create-shader'; + +export const createProgram = async ( + gl: WebGL2RenderingContext, + vertexShaderSource: string, + fragmentShaderSource: string, + substitutions: { [name: string]: string } +): Promise => { + const program = gl.createProgram(); + + const extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile'); + + const vertexShader = createShader( + gl, + gl.VERTEX_SHADER, + vertexShaderSource, + substitutions + ); + gl.attachShader(program, vertexShader); + + const fragmentShader = createShader( + gl, + gl.FRAGMENT_SHADER, + fragmentShaderSource, + substitutions + ); + gl.attachShader(program, fragmentShader); + + gl.linkProgram(program); + + if (extension) { + const checkCompileStatus = () => + gl.getProgramParameter(program, extension.COMPLETION_STATUS_KHR); + + await waitWhileFalse(checkCompileStatus); + } + + checkProgram(gl, program); + + return program; +}; diff --git a/frontend/src/scripts/drawing/graphics-library/helper/create-shader.ts b/frontend/src/scripts/drawing/graphics-library/compiling/create-shader.ts similarity index 57% rename from frontend/src/scripts/drawing/graphics-library/helper/create-shader.ts rename to frontend/src/scripts/drawing/graphics-library/compiling/create-shader.ts index 0c6901f..5de03c2 100644 --- a/frontend/src/scripts/drawing/graphics-library/helper/create-shader.ts +++ b/frontend/src/scripts/drawing/graphics-library/compiling/create-shader.ts @@ -1,26 +1,17 @@ -import { settings } from '../../settings'; - export const createShader = ( gl: WebGL2RenderingContext, type: GLenum, source: string, substitutions: { [name: string]: string } ): WebGLShader => { - const allSubstitutions = { ...settings.shaderMacros, ...substitutions }; - source = source.replace(/{(.+)}/gm, (_, name: string): string => { - const value = allSubstitutions[name]; + const value = substitutions[name]; return Number.isInteger(value) ? `${value}.0` : value; }); const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); - const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - - if (!success) { - throw new Error(gl.getShaderInfoLog(shader)); - } return shader; }; diff --git a/frontend/src/scripts/drawing/graphics-library/helper/enable-extension.ts b/frontend/src/scripts/drawing/graphics-library/helper/enable-extension.ts index 6989983..23e51c9 100644 --- a/frontend/src/scripts/drawing/graphics-library/helper/enable-extension.ts +++ b/frontend/src/scripts/drawing/graphics-library/helper/enable-extension.ts @@ -1,7 +1,29 @@ +const extensions: Map = new Map(); + +export const tryEnableExtension = ( + gl: WebGL2RenderingContext, + name: string +): any | null => { + if (extensions.has(name)) { + return extensions.get(name); + } + + let extension = null; + if (gl.getSupportedExtensions().indexOf(name) != -1) { + extension = gl.getExtension(name); + } + + extensions.set(name, extension); + + return extension; +}; + export const enableExtension = (gl: WebGL2RenderingContext, name: string): any => { - if (gl.getSupportedExtensions().indexOf(name) == -1) { + const extension = tryEnableExtension(gl, name); + + if (extension === null) { throw new Error(`Unsupported extension ${name}`); } - return gl.getExtension(name); + return extension; }; diff --git a/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts b/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts index beba0b9..05701d4 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts @@ -5,10 +5,14 @@ export class FragmentShaderOnlyProgram extends Program { constructor( gl: WebGL2RenderingContext, - shaderSources: [string, string], + sources: [string, string], substitutions: { [name: string]: string } ) { - super(gl, shaderSources, substitutions); + super(gl, sources, substitutions); + } + + public async initialize(): Promise { + await super.initialize(); this.prepareScreenQuad('vertexPosition'); } diff --git a/frontend/src/scripts/drawing/graphics-library/program/i-program.ts b/frontend/src/scripts/drawing/graphics-library/program/i-program.ts index 086026a..2eec3b7 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/i-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/i-program.ts @@ -1,6 +1,7 @@ import { vec2 } from 'gl-matrix'; export interface IProgram { + initialize(): Promise; setDrawingRectangle(bottomLeft: vec2, size: vec2): void; bindAndSetUniforms(values: { [name: string]: any }): void; draw(): void; diff --git a/frontend/src/scripts/drawing/graphics-library/program/program.ts b/frontend/src/scripts/drawing/graphics-library/program/program.ts index a657b2a..ca5bd9d 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/program.ts @@ -1,17 +1,14 @@ import { mat2d, vec2 } from 'gl-matrix'; -import { createShader } from '../helper/create-shader'; +import { settings } from '../../settings'; +import { createProgram } from '../compiling/create-program'; import { loadUniform } from '../helper/load-uniform'; import { IProgram } from './i-program'; export default abstract class Program implements IProgram { protected program: WebGLProgram; - - private shaders: Array = []; - + private programPromise: Promise; private modelTransform = mat2d.identity(mat2d.create()); - private readonly ndcToUv: mat2d; - private uniforms: Array<{ name: Array; location: WebGLUniformLocation; @@ -23,13 +20,24 @@ export default abstract class Program implements IProgram { [vertexShaderSource, fragmentShaderSource]: [string, string], substitutions: { [name: string]: string } ) { - this.createProgram(vertexShaderSource, fragmentShaderSource, substitutions); - this.queryUniforms(); + substitutions = { ...settings.shaderMacros, ...substitutions }; + + this.programPromise = createProgram( + this.gl, + vertexShaderSource, + fragmentShaderSource, + substitutions + ); this.ndcToUv = mat2d.fromScaling(mat2d.create(), vec2.fromValues(0.5, 0.5)); mat2d.translate(this.ndcToUv, this.ndcToUv, vec2.fromValues(1, 1)); } + public async initialize(): Promise { + this.program = await this.programPromise; + this.queryUniforms(); + } + public bindAndSetUniforms(values: { [name: string]: any }) { this.bind(); this.setUniforms({ modelTransform: this.modelTransform, ...values }); @@ -56,7 +64,7 @@ export default abstract class Program implements IProgram { } public delete() { - this.shaders.forEach((s) => this.gl.deleteShader(s)); + this.gl.getAttachedShaders(this.program).forEach((s) => this.gl.deleteShader(s)); this.gl.deleteProgram(this.program); } @@ -91,38 +99,4 @@ export default abstract class Program implements IProgram { return u1; }); } - - private createProgram( - passthroughVertexShaderSource: string, - fragmentShaderSource: string, - substitutions: { [name: string]: string } - ) { - this.program = this.gl.createProgram(); - - const vertexShader = createShader( - this.gl, - this.gl.VERTEX_SHADER, - passthroughVertexShaderSource, - substitutions - ); - this.gl.attachShader(this.program, vertexShader); - this.shaders.push(vertexShader); - - const fragmentShader = createShader( - this.gl, - this.gl.FRAGMENT_SHADER, - fragmentShaderSource, - substitutions - ); - this.gl.attachShader(this.program, fragmentShader); - this.shaders.push(fragmentShader); - - this.gl.linkProgram(this.program); - - const success = this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS); - - if (!success) { - throw new Error(this.gl.getProgramInfoLog(this.program)); - } - } } diff --git a/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts b/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts index bbb6cb0..0e26a4c 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts @@ -28,6 +28,10 @@ export class UniformArrayAutoScalingProgram implements IProgram { } } + public async initialize(): Promise { + await Promise.all(this.programs.map((p) => p.program.initialize())); + } + public bindAndSetUniforms(uniforms: { [name: string]: any }): void { const values = this.descriptors.map((d) => uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0 diff --git a/frontend/src/scripts/drawing/i-renderer.ts b/frontend/src/scripts/drawing/i-renderer.ts index ed666c8..fa6c0d8 100644 --- a/frontend/src/scripts/drawing/i-renderer.ts +++ b/frontend/src/scripts/drawing/i-renderer.ts @@ -4,6 +4,8 @@ import { IDrawable } from './drawables/i-drawable'; import { ILight } from './drawables/lights/i-light'; export interface IRenderer { + initialize(): Promise; + startFrame(deltaTime: DOMHighResTimeStamp): void; finishFrame(): void; diff --git a/frontend/src/scripts/drawing/rendering/rendering-pass.ts b/frontend/src/scripts/drawing/rendering/rendering-pass.ts index 671059d..de03fa2 100644 --- a/frontend/src/scripts/drawing/rendering/rendering-pass.ts +++ b/frontend/src/scripts/drawing/rendering/rendering-pass.ts @@ -24,6 +24,10 @@ export class RenderingPass { ); } + public async initialize(): Promise { + await this.program.initialize(); + } + public addDrawable(drawable: IDrawable) { this.drawables.push(drawable); } diff --git a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts index ef9a3a9..feb75a9 100644 --- a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts +++ b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts @@ -36,6 +36,8 @@ export class WebGl2Renderer implements IRenderer { private lightingPass: RenderingPass; private autoscaler: FpsAutoscaler; + private initializePromise: Promise<[void, void]> = null; + private matrices: { distanceScreenToWorld?: mat2d; worldToDistanceUV?: mat2d; @@ -63,6 +65,11 @@ export class WebGl2Renderer implements IRenderer { this.lightingFrameBuffer ); + this.initializePromise = Promise.all([ + this.distancePass.initialize(), + this.lightingPass.initialize(), + ]); + this.autoscaler = new FpsAutoscaler([ this.lightingFrameBuffer, this.distanceFieldFrameBuffer, @@ -75,6 +82,10 @@ export class WebGl2Renderer implements IRenderer { } } + public async initialize(): Promise { + await this.initializePromise; + } + public drawShape(shape: IDrawable) { this.distancePass.addDrawable(shape); } diff --git a/frontend/src/scripts/drawing/settings.ts b/frontend/src/scripts/drawing/settings.ts index 9ddc82d..f5db8a7 100644 --- a/frontend/src/scripts/drawing/settings.ts +++ b/frontend/src/scripts/drawing/settings.ts @@ -8,11 +8,11 @@ export const settings = { [0.2, 0.1], [0.6, 0.1], [1, 1], - [1.25, 0.75], + /*[1.25, 0.75], [1.5, 1], [1.75, 1.25], //[1.75, 1.75], - //[2, 2], + //[2, 2],*/ ], startingTargetIndex: 2, scalingOptions: { diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 43bf3ed..d3664a7 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -29,6 +29,7 @@ export class Game implements IGame { private infoText = new InfoText(); private character: Character; private renderer: IRenderer; + private initializeRendererPromise: Promise; constructor() { const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); @@ -46,10 +47,13 @@ export class Game implements IGame { ); this.renderer = new WebGl2Renderer(canvas, overlay); - + this.initializeRendererPromise = this.renderer.initialize(); this.initializeScene(); this.physics.start(); + } + public async start(): Promise { + await this.initializeRendererPromise; requestAnimationFrame(this.gameLoop.bind(this)); } diff --git a/frontend/src/scripts/helper/wait-for.ts b/frontend/src/scripts/helper/wait-for.ts new file mode 100644 index 0000000..1e0f708 --- /dev/null +++ b/frontend/src/scripts/helper/wait-for.ts @@ -0,0 +1,29 @@ +export const waitWhileFalse = (body: () => boolean): Promise => { + let resolveOnDone: () => void; + let rejectOnDone: (e: Error) => void; + + const onDone = new Promise((resolve, reject) => { + resolveOnDone = resolve; + rejectOnDone = reject; + }); + + const waiter = () => { + let success: boolean; + + try { + success = body(); + } catch (e) { + rejectOnDone(e); + return; + } + + if (success) { + resolveOnDone(); + } else { + requestAnimationFrame(waiter); + } + }; + + waiter(); + return onDone; +}; diff --git a/frontend/src/scripts/helper/wait.ts b/frontend/src/scripts/helper/wait.ts new file mode 100644 index 0000000..73dd2c2 --- /dev/null +++ b/frontend/src/scripts/helper/wait.ts @@ -0,0 +1,3 @@ +export const wait = (ms: number): Promise => { + return new Promise((resolve, _) => setTimeout(resolve, ms)); +};