Add parallel shader compiling

This commit is contained in:
schmelczerandras 2020-09-10 11:32:05 +02:00
parent a6efbc02b9
commit 39a6ee9a20
17 changed files with 209 additions and 77 deletions

View file

@ -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();

View file

@ -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));
}
};

View file

@ -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));
}
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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');
}

View file

@ -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;

View file

@ -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));
}
}
}

View file

@ -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

View file

@ -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;

View file

@ -24,6 +24,10 @@ export class RenderingPass {
);
}
public async initialize(): Promise<void> {
await this.program.initialize();
}
public addDrawable(drawable: IDrawable) {
this.drawables.push(drawable);
}

View file

@ -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);
}

View file

@ -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: {

View file

@ -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));
}

View 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;
};

View file

@ -0,0 +1,3 @@
export const wait = (ms: number): Promise<void> => {
return new Promise<void>((resolve, _) => setTimeout(resolve, ms));
};