diff --git a/.npmignore b/.npmignore index 130f134..cd3ca40 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,2 @@ -.* -**/tsconfig.json -**/webpack.config.js node_modules src 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 de26568..be495ad 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, enableHighDpiRendering: boolean) { - super(gl, enableHighDpiRendering); + constructor(gl: WebGL2RenderingContext) { + super(gl); 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 29a9a9e..1ad4e78 100644 --- a/src/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ b/src/graphics/graphics-library/frame-buffer/frame-buffer.ts @@ -2,16 +2,14 @@ import { vec2 } from 'gl-matrix'; export abstract class FrameBuffer { public renderScale = 1; + public enableHighDpiRendering = false; protected size = vec2.create(); // null means the default framebuffer protected frameBuffer: WebGLFramebuffer | null = null; - constructor( - protected gl: WebGL2RenderingContext, - private enableHighDpiRendering: boolean - ) {} + constructor(protected gl: WebGL2RenderingContext) {} 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 716a743..ff99787 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, enableHighDpiRendering: boolean) { - super(gl, enableHighDpiRendering); + constructor(gl: WebGL2RenderingContext) { + super(gl); enableExtension(gl, 'EXT_color_buffer_float'); diff --git a/src/graphics/rendering/renderer.ts b/src/graphics/rendering/renderer.ts index 8e1a9cb..1fae188 100644 --- a/src/graphics/rendering/renderer.ts +++ b/src/graphics/rendering/renderer.ts @@ -1,12 +1,14 @@ import { vec2 } from 'gl-matrix'; import { Drawable } from '../../drawables/drawable'; +import { RuntimeSettings } from './runtime-settings'; export interface Renderer { readonly canvasSize: vec2; + readonly viewAreaSize: vec2; readonly insights: any; setViewArea(topLeft: vec2, size: vec2): void; - setCursorPosition(position: vec2): void; + setRuntimeSettings(overrides: Partial): void; addDrawable(drawable: Drawable): void; autoscaleQuality(deltaTime: DOMHighResTimeStamp): void; renderDrawables(): void; diff --git a/src/graphics/rendering/rendering-pass.ts b/src/graphics/rendering/rendering-pass.ts index a757024..5f24d3d 100644 --- a/src/graphics/rendering/rendering-pass.ts +++ b/src/graphics/rendering/rendering-pass.ts @@ -5,21 +5,20 @@ import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer'; import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program'; export class RenderingPass { + public tileMultiplier = 8; + public isWorldInverted = false; + private drawables: Array = []; private program: UniformArrayAutoScalingProgram; - constructor( - gl: WebGL2RenderingContext, - private frame: FrameBuffer, - private tileMultiplier: number - ) { + constructor(gl: WebGL2RenderingContext, private frame: FrameBuffer) { this.program = new UniformArrayAutoScalingProgram(gl); } public async initialize( shaderSources: [string, string], descriptors: Array, - substitutions: { [name: string]: any } + substitutions: { [name: string]: any } = {} ): Promise { await this.program.initialize(shaderSources, descriptors, substitutions); } @@ -43,7 +42,10 @@ export class RenderingPass { for (let x = -1; x < 1; x += stepsInNDC) { for (let y = -1; y < 1; y += stepsInNDC) { - const uniforms = { ...commonUniforms, maxMinDistance: radiusInNDC }; + const uniforms = { + ...commonUniforms, + maxMinDistance: radiusInNDC * (this.isWorldInverted ? -1 : 1), + }; const ndcBottomLeft = vec2.fromValues(x, y); diff --git a/src/graphics/rendering/runtime-settings.ts b/src/graphics/rendering/runtime-settings.ts new file mode 100644 index 0000000..ffe4781 --- /dev/null +++ b/src/graphics/rendering/runtime-settings.ts @@ -0,0 +1,8 @@ +import { vec3 } from 'gl-matrix'; + +export interface RuntimeSettings { + enableHighDpiRendering: boolean; + tileMultiplier: number; + isWorldInverted: boolean; + ambientLight: vec3; +} diff --git a/src/graphics/rendering/shaders/shading-fs.glsl b/src/graphics/rendering/shaders/shading-fs.glsl index cd1addc..e4123dd 100644 --- a/src/graphics/rendering/shaders/shading-fs.glsl +++ b/src/graphics/rendering/shaders/shading-fs.glsl @@ -4,7 +4,6 @@ precision lowp float; #define INFINITY 1000.0 #define INTENSITY_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} @@ -19,6 +18,8 @@ uniform sampler2D distanceTexture; in vec2 position; in vec2 uvCoordinates; +uniform vec3 ambientLight; + vec3[3] colors = vec3[]( {palette} ); @@ -127,7 +128,7 @@ float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 out vec4 fragmentColor; void main() { - vec3 lighting = AMBIENT_LIGHT; + vec3 lighting = ambientLight; vec3 colorAtPosition; float startingDistance = getDistance(uvCoordinates, colorAtPosition); diff --git a/src/graphics/rendering/startup-settings.ts b/src/graphics/rendering/startup-settings.ts new file mode 100644 index 0000000..2eb3be4 --- /dev/null +++ b/src/graphics/rendering/startup-settings.ts @@ -0,0 +1,5 @@ +export interface StartupSettings { + enableStopwatch: boolean; + softShadowTraceCount: string; + hardShadowTraceCount: string; +} diff --git a/src/graphics/rendering/uniforms-provider.ts b/src/graphics/rendering/uniforms-provider.ts index eaf7501..79615ef 100644 --- a/src/graphics/rendering/uniforms-provider.ts +++ b/src/graphics/rendering/uniforms-provider.ts @@ -1,6 +1,8 @@ -import { mat2d, vec2 } from 'gl-matrix'; +import { mat2d, vec2, vec3 } from 'gl-matrix'; export class UniformsProvider { + public ambientLight = vec3.fromValues(0.25, 0.15, 0.25); + private scaleWorldLengthToNDC = 1; private transformWorldToNDC = mat2d.create(); @@ -8,17 +10,15 @@ export class UniformsProvider { private worldAreaInView = vec2.create(); private squareToAspectRatio = vec2.create(); private uvToWorld = mat2d.create(); - private cursorPosition = vec2.create(); public softShadowsEnabled?: boolean; public constructor(private gl: WebGL2RenderingContext) {} public getUniforms(uniforms: any): any { - const cursorPosition = this.uvToWorldCoordinate(this.cursorPosition); return { ...uniforms, - cursorPosition, + ambientLight: this.ambientLight, uvToWorld: this.uvToWorld, worldAreaInView: this.worldAreaInView, squareToAspectRatio: this.squareToAspectRatio, @@ -51,6 +51,10 @@ export class UniformsProvider { ); } + public getViewArea(): vec2 { + return this.worldAreaInView; + } + public setViewArea(topLeft: vec2, size: vec2) { this.worldAreaInView = size; @@ -87,8 +91,4 @@ export class UniformsProvider { this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft); mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView); } - - public setCursorPosition(position: vec2): void { - this.cursorPosition = position; - } } diff --git a/src/graphics/rendering/webgl2-renderer-settings.ts b/src/graphics/rendering/webgl2-renderer-settings.ts deleted file mode 100644 index 7d9272e..0000000 --- a/src/graphics/rendering/webgl2-renderer-settings.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface WebGl2RendererSettings { - enableHighDpiRendering: boolean; - enableStopwatch: boolean; - tileMultiplier: number; - shaderMacros: { - softShadowTraceCount: string; - hardShadowTraceCount: string; - // ambientColor: vec3; - // defaultIsInside: boolean; - }; -} diff --git a/src/graphics/rendering/webgl2-renderer.ts b/src/graphics/rendering/webgl2-renderer.ts index 08040b3..18e92c1 100644 --- a/src/graphics/rendering/webgl2-renderer.ts +++ b/src/graphics/rendering/webgl2-renderer.ts @@ -11,12 +11,13 @@ import { Insights } from './insights'; import { Renderer } from './renderer'; import { RenderingPass } from './rendering-pass'; import { RenderingPassName } from './rendering-pass-name'; +import { RuntimeSettings } from './runtime-settings'; 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 { StartupSettings } from './startup-settings'; import { UniformsProvider } from './uniforms-provider'; -import { WebGl2RendererSettings } from './webgl2-renderer-settings'; type Passes = { [key in keyof typeof RenderingPassName]: RenderingPass }; @@ -28,38 +29,49 @@ export class WebGl2Renderer implements Renderer { private lightingFrameBuffer: DefaultFrameBuffer; private passes: Passes; private autoscaler: FpsAutoscaler; - private settings: WebGl2RendererSettings; - private static defaultSettings: WebGl2RendererSettings = { - enableHighDpiRendering: true, - enableStopwatch: true, - tileMultiplier: 8, - shaderMacros: { - softShadowTraceCount: '128', - hardShadowTraceCount: '32', + private applyRuntimeSettings: { + [key: string]: (value: any) => void; + } = { + enableHighDpiRendering: (v) => { + this.distanceFieldFrameBuffer.enableHighDpiRendering = v; + this.lightingFrameBuffer.enableHighDpiRendering = v; + }, + tileMultiplier: (v) => { + this.passes[RenderingPassName.distance].tileMultiplier = v; + this.passes[RenderingPassName.pixel].tileMultiplier = v; + }, + isWorldInverted: (v) => { + this.passes[RenderingPassName.distance].isWorldInverted = v; + this.passes[RenderingPassName.pixel].isWorldInverted = v; + }, + ambientLight: (v) => { + this.uniformsProvider.ambientLight = v; }, }; + private static defaultStartupSettings: StartupSettings = { + enableStopwatch: true, + softShadowTraceCount: '128', + hardShadowTraceCount: '32', + }; + + setRuntimeSettings(overrides: Partial): void { + Object.entries(overrides).forEach((k, v) => { + this.applyRuntimeSettings[(k as unknown) as string](v); + }); + } + constructor( private canvas: HTMLCanvasElement, - private descriptors: Array, - private palette: Array, - settingsOverrides: Partial + private descriptors: Array ) { - this.settings = { ...WebGl2Renderer.defaultSettings, ...settingsOverrides }; - this.gl = getWebGl2Context(canvas); ParallelCompiler.initialize(this.gl); - this.distanceFieldFrameBuffer = new IntermediateFrameBuffer( - this.gl, - this.settings.enableHighDpiRendering - ); - this.lightingFrameBuffer = new DefaultFrameBuffer( - this.gl, - this.settings.enableHighDpiRendering - ); + this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl); + this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl); this.passes = this.createPasses(); @@ -72,14 +84,6 @@ export class WebGl2Renderer implements Renderer { softShadowsEnabled: (v) => (this.uniformsProvider.softShadowsEnabled = v as boolean), }); - - if (this.settings.enableStopwatch) { - try { - this.stopwatch = new WebGlStopwatch(this.gl); - } catch { - // no problem - } - } } @Insights.measure('create render passes') @@ -87,26 +91,25 @@ export class WebGl2Renderer implements Renderer { return { [RenderingPassName.distance]: new RenderingPass( this.gl, - this.distanceFieldFrameBuffer, - this.settings.tileMultiplier - ), - [RenderingPassName.pixel]: new RenderingPass( - this.gl, - this.lightingFrameBuffer, - this.settings.tileMultiplier + this.distanceFieldFrameBuffer ), + [RenderingPassName.pixel]: new RenderingPass(this.gl, this.lightingFrameBuffer), }; } @Insights.measure('initialize render passes') - public async initialize(): Promise { + public async initialize( + palette: Array, + settingsOverrides: Partial + ): Promise { + const settings = { ...WebGl2Renderer.defaultStartupSettings, ...settingsOverrides }; + const promises: Array> = []; promises.push( this.passes[RenderingPassName.distance].initialize( [distanceVertexShader, distanceFragmentShader], - this.descriptors.filter(WebGl2Renderer.hasSdf), - this.settings.shaderMacros + this.descriptors.filter(WebGl2Renderer.hasSdf) ) ); @@ -115,8 +118,9 @@ export class WebGl2Renderer implements Renderer { [lightsVertexShader, lightsFragmentShader], this.descriptors.filter((d) => !WebGl2Renderer.hasSdf(d)), { - palette: this.generatePaletteCode(this.palette), - ...this.settings.shaderMacros, + palette: this.generatePaletteCode(palette), + hardShadowTraceCount: settings.hardShadowTraceCount, + softShadowTraceCount: settings.softShadowTraceCount, } ) ); @@ -124,6 +128,14 @@ export class WebGl2Renderer implements Renderer { ParallelCompiler.compilePrograms(); await Promise.all(promises); + + if (settings.enableStopwatch) { + try { + this.stopwatch = new WebGlStopwatch(this.gl); + } catch { + // no problem + } + } } public get insights(): any { @@ -191,8 +203,8 @@ export class WebGl2Renderer implements Renderer { this.uniformsProvider.setViewArea(topLeft, size); } - public setCursorPosition(position: vec2) { - this.uniformsProvider.setCursorPosition(position); + public get viewAreaSize(): vec2 { + return this.uniformsProvider.getViewArea(); } public get canvasSize(): vec2 { diff --git a/src/helper/wait-while-false.ts b/src/helper/wait-while-false.ts deleted file mode 100644 index 1e0f708..0000000 --- a/src/helper/wait-while-false.ts +++ /dev/null @@ -1,29 +0,0 @@ -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/src/main.ts b/src/main.ts index 2b7b471..9c463d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,8 @@ import { vec3 } from 'gl-matrix'; import { DrawableDescriptor } from './drawables/drawable-descriptor'; import { Renderer } from './graphics/rendering/renderer'; +import { StartupSettings } from './graphics/rendering/startup-settings'; 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'; @@ -30,11 +30,11 @@ export const compile = async ( canvas: HTMLCanvasElement, descriptors: Array, palette: Array, - settings: Partial = {} + settings: Partial = {} ): Promise => { applyArrayPlugins(); - const renderer = new WebGl2Renderer(canvas, descriptors, palette, settings); - await renderer.initialize(); + const renderer = new WebGl2Renderer(canvas, descriptors); + await renderer.initialize(palette, settings); return renderer; };