Add parallel shader compiling
This commit is contained in:
parent
a6efbc02b9
commit
39a6ee9a20
17 changed files with 209 additions and 77 deletions
|
|
@ -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,19 +78,31 @@ 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);
|
||||
|
|
@ -113,10 +128,14 @@ tree.print();
|
|||
console.log(tree.findIntersecting(new BoundingBox(960, 1050, 50, 190, 'G')));
|
||||
*/
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
Random.seed = 42;
|
||||
new Game();
|
||||
await new Game().start();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(e);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
@ -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<WebGLProgram> => {
|
||||
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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -1,7 +1,29 @@
|
|||
const extensions: Map<string, any> = 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
await super.initialize();
|
||||
this.prepareScreenQuad('vertexPosition');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
export interface IProgram {
|
||||
initialize(): Promise<void>;
|
||||
setDrawingRectangle(bottomLeft: vec2, size: vec2): void;
|
||||
bindAndSetUniforms(values: { [name: string]: any }): void;
|
||||
draw(): void;
|
||||
|
|
|
|||
|
|
@ -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<WebGLShader> = [];
|
||||
|
||||
private programPromise: Promise<WebGLProgram>;
|
||||
private modelTransform = mat2d.identity(mat2d.create());
|
||||
|
||||
private readonly ndcToUv: mat2d;
|
||||
|
||||
private uniforms: Array<{
|
||||
name: Array<string>;
|
||||
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<void> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export class UniformArrayAutoScalingProgram implements IProgram {
|
|||
}
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { IDrawable } from './drawables/i-drawable';
|
|||
import { ILight } from './drawables/lights/i-light';
|
||||
|
||||
export interface IRenderer {
|
||||
initialize(): Promise<void>;
|
||||
|
||||
startFrame(deltaTime: DOMHighResTimeStamp): void;
|
||||
finishFrame(): void;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ export class RenderingPass {
|
|||
);
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
await this.program.initialize();
|
||||
}
|
||||
|
||||
public addDrawable(drawable: IDrawable) {
|
||||
this.drawables.push(drawable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
await this.initializePromise;
|
||||
}
|
||||
|
||||
public drawShape(shape: IDrawable) {
|
||||
this.distancePass.addDrawable(shape);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export class Game implements IGame {
|
|||
private infoText = new InfoText();
|
||||
private character: Character;
|
||||
private renderer: IRenderer;
|
||||
private initializeRendererPromise: Promise<void>;
|
||||
|
||||
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<void> {
|
||||
await this.initializeRendererPromise;
|
||||
requestAnimationFrame(this.gameLoop.bind(this));
|
||||
}
|
||||
|
||||
|
|
|
|||
29
frontend/src/scripts/helper/wait-for.ts
Normal file
29
frontend/src/scripts/helper/wait-for.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export const waitWhileFalse = (body: () => boolean): Promise<void> => {
|
||||
let resolveOnDone: () => void;
|
||||
let rejectOnDone: (e: Error) => void;
|
||||
|
||||
const onDone = new Promise<void>((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;
|
||||
};
|
||||
3
frontend/src/scripts/helper/wait.ts
Normal file
3
frontend/src/scripts/helper/wait.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const wait = (ms: number): Promise<void> => {
|
||||
return new Promise<void>((resolve, _) => setTimeout(resolve, ms));
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue