Add WebGL compatibility
This commit is contained in:
parent
3725f56c78
commit
1d4980ae28
29 changed files with 567 additions and 212 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import { Drawable } from './drawable';
|
import { Drawable } from './drawable';
|
||||||
|
|
||||||
export interface DrawableDescriptor {
|
export interface DrawableDescriptor {
|
||||||
uniformName: string;
|
propertyUniformMapping: { [property: string]: string };
|
||||||
uniformCountMacroName: string;
|
uniformCountMacroName: string;
|
||||||
sdf?: {
|
sdf?: {
|
||||||
shader: string;
|
shader: string;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ export abstract class Drawable {
|
||||||
public static readonly descriptor: DrawableDescriptor;
|
public static readonly descriptor: DrawableDescriptor;
|
||||||
|
|
||||||
public abstract minDistance(target: vec2): number;
|
public abstract minDistance(target: vec2): number;
|
||||||
|
|
||||||
protected abstract getObjectToSerialize(transform2d: mat2d, transform1d: number): any;
|
protected abstract getObjectToSerialize(transform2d: mat2d, transform1d: number): any;
|
||||||
|
|
||||||
public serializeToUniforms(
|
public serializeToUniforms(
|
||||||
|
|
@ -14,12 +13,15 @@ export abstract class Drawable {
|
||||||
transform2d: mat2d,
|
transform2d: mat2d,
|
||||||
transform1d: number
|
transform1d: number
|
||||||
): void {
|
): void {
|
||||||
const { uniformName } = (this.constructor as typeof Drawable).descriptor;
|
const { propertyUniformMapping } = (this.constructor as typeof Drawable).descriptor;
|
||||||
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) {
|
const serialized = this.getObjectToSerialize(transform2d, transform1d);
|
||||||
uniforms[uniformName] = [];
|
Object.entries(propertyUniformMapping).forEach(([k, v]) => {
|
||||||
}
|
if (!Object.prototype.hasOwnProperty.call(uniforms, v)) {
|
||||||
|
uniforms[v] = [];
|
||||||
|
}
|
||||||
|
|
||||||
uniforms[uniformName].push(this.getObjectToSerialize(transform2d, transform1d));
|
uniforms[v].push(serialized[k]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@ import { LightDrawable } from './light-drawable';
|
||||||
|
|
||||||
export class CircleLight extends LightDrawable {
|
export class CircleLight extends LightDrawable {
|
||||||
public static readonly descriptor: DrawableDescriptor = {
|
public static readonly descriptor: DrawableDescriptor = {
|
||||||
uniformName: 'circleLights',
|
propertyUniformMapping: {
|
||||||
|
center: 'circleLightCenters',
|
||||||
|
color: 'circleLightColors',
|
||||||
|
intensity: 'circleLightIntensities',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'CIRCLE_LIGHT_COUNT',
|
uniformCountMacroName: 'CIRCLE_LIGHT_COUNT',
|
||||||
shaderCombinationSteps: [0, 1, 2, 4, 8],
|
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
|
||||||
empty: new CircleLight(vec2.fromValues(0, 0), vec3.fromValues(0, 0, 0), 0),
|
empty: new CircleLight(vec2.fromValues(0, 0), vec3.fromValues(0, 0, 0), 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,12 @@ import { LightDrawable } from './light-drawable';
|
||||||
|
|
||||||
export class Flashlight extends LightDrawable {
|
export class Flashlight extends LightDrawable {
|
||||||
public static readonly descriptor: DrawableDescriptor = {
|
public static readonly descriptor: DrawableDescriptor = {
|
||||||
uniformName: 'flashlights',
|
propertyUniformMapping: {
|
||||||
|
center: 'flashlightCenters',
|
||||||
|
color: 'flashlightColors',
|
||||||
|
intensity: 'flashlightIntensities',
|
||||||
|
direction: 'flashlightDirections',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'FLASHLIGHT_COUNT',
|
uniformCountMacroName: 'FLASHLIGHT_COUNT',
|
||||||
shaderCombinationSteps: [0, 1, 2, 4],
|
shaderCombinationSteps: [0, 1, 2, 4],
|
||||||
empty: new Flashlight(
|
empty: new Flashlight(
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@ export class Circle extends Drawable {
|
||||||
public static descriptor: DrawableDescriptor = {
|
public static descriptor: DrawableDescriptor = {
|
||||||
sdf: {
|
sdf: {
|
||||||
shader: `
|
shader: `
|
||||||
uniform struct {
|
uniform vec2 circleCenters[CIRCLE_COUNT];
|
||||||
vec2 center;
|
uniform vec2 circleRadii[CIRCLE_COUNT];
|
||||||
float radius;
|
|
||||||
}[CIRCLE_COUNT] circles;
|
|
||||||
|
|
||||||
void circleMinDistance(inout float minDistance, inout float color) {
|
void circleMinDistance(inout float minDistance, inout float color) {
|
||||||
float myMinDistance = maxMinDistance;
|
float myMinDistance = maxMinDistance;
|
||||||
for (int i = 0; i < CIRCLE_COUNT; i++) {
|
for (int i = 0; i < CIRCLE_COUNT; i++) {
|
||||||
float dist = distance(circles[i].center, position) - circles[i].radius;
|
float dist = distance(circleCenters[i], position) - circleRadii[i];
|
||||||
myMinDistance = min(myMinDistance, dist);
|
myMinDistance = min(myMinDistance, dist);
|
||||||
}
|
}
|
||||||
minDistance = min(minDistance, myMinDistance);
|
minDistance = min(minDistance, myMinDistance);
|
||||||
|
|
@ -26,9 +24,12 @@ export class Circle extends Drawable {
|
||||||
`,
|
`,
|
||||||
distanceFunctionName: 'circleMinDistance',
|
distanceFunctionName: 'circleMinDistance',
|
||||||
},
|
},
|
||||||
uniformName: 'circles',
|
propertyUniformMapping: {
|
||||||
|
center: 'circleCenters',
|
||||||
|
radius: 'circleRadii',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'CIRCLE_COUNT',
|
uniformCountMacroName: 'CIRCLE_COUNT',
|
||||||
shaderCombinationSteps: [0, 1, 2, 3, 16, 32],
|
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
|
||||||
empty: new Circle(vec2.fromValues(0, 0), 0),
|
empty: new Circle(vec2.fromValues(0, 0), 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,44 +8,46 @@ export class InvertedTunnel extends Drawable {
|
||||||
public static descriptor: DrawableDescriptor = {
|
public static descriptor: DrawableDescriptor = {
|
||||||
sdf: {
|
sdf: {
|
||||||
shader: `
|
shader: `
|
||||||
uniform struct InvertedTunnel {
|
uniform vec2 froms[INVERTED_TUNNEL_COUNT];
|
||||||
vec2 from;
|
uniform vec2 toFromDeltas[INVERTED_TUNNEL_COUNT];
|
||||||
vec2 toFromDelta;
|
uniform float fromRadii[INVERTED_TUNNEL_COUNT];
|
||||||
float fromRadius;
|
uniform float toRadii[INVERTED_TUNNEL_COUNT];
|
||||||
float toRadius;
|
|
||||||
}[INVERTED_TUNNEL_COUNT] invertedTunnels;
|
|
||||||
|
|
||||||
void invertedTunnelMinDistance(inout float minDistance, inout float color) {
|
void invertedTunnelMinDistance(inout float minDistance, inout float color) {
|
||||||
float myMinDistance = -minDistance;
|
float myMinDistance = -minDistance;
|
||||||
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
|
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
|
||||||
InvertedTunnel tunnel = invertedTunnels[i];
|
vec2 targetFromDelta = position - froms[i];
|
||||||
vec2 targetFromDelta = position - tunnel.from;
|
|
||||||
|
float h = clamp(
|
||||||
float h = clamp(
|
dot(targetFromDelta, toFromDeltas[i])
|
||||||
dot(targetFromDelta, tunnel.toFromDelta)
|
/ dot(toFromDeltas[i], toFromDeltas[i]),
|
||||||
/ dot(tunnel.toFromDelta, tunnel.toFromDelta),
|
0.0, 1.0
|
||||||
0.0, 1.0
|
);
|
||||||
);
|
|
||||||
|
float currentDistance = -mix(
|
||||||
float currentDistance = -mix(
|
fromRadii[i], toRadii[i], h
|
||||||
tunnel.fromRadius, tunnel.toRadius, h
|
) + distance(
|
||||||
) + distance(
|
targetFromDelta, toFromDeltas[i] * h
|
||||||
targetFromDelta, tunnel.toFromDelta * h
|
);
|
||||||
);
|
|
||||||
|
myMinDistance = min(myMinDistance, currentDistance);
|
||||||
myMinDistance = min(myMinDistance, currentDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
color = mix(0.0, color, step(
|
|
||||||
distanceNdcPixelSize + SURFACE_OFFSET,
|
|
||||||
-myMinDistance
|
|
||||||
));
|
|
||||||
minDistance = -myMinDistance;
|
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
|
color = mix(0.0, color, step(
|
||||||
|
distanceNdcPixelSize + SURFACE_OFFSET,
|
||||||
|
-myMinDistance
|
||||||
|
));
|
||||||
|
minDistance = -myMinDistance;
|
||||||
|
}
|
||||||
|
`,
|
||||||
distanceFunctionName: 'invertedTunnelMinDistance',
|
distanceFunctionName: 'invertedTunnelMinDistance',
|
||||||
},
|
},
|
||||||
uniformName: 'invertedTunnels',
|
propertyUniformMapping: {
|
||||||
|
from: 'froms',
|
||||||
|
toFromDelta: 'toFromDeltas',
|
||||||
|
fromRadius: 'fromRadii',
|
||||||
|
toRadius: 'toRadii',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
|
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
|
||||||
shaderCombinationSteps: [0, 1, 4, 16, 32],
|
shaderCombinationSteps: [0, 1, 4, 16, 32],
|
||||||
empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0),
|
empty: new InvertedTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0),
|
||||||
|
|
|
||||||
|
|
@ -8,44 +8,46 @@ export class Tunnel extends Drawable {
|
||||||
public static descriptor: DrawableDescriptor = {
|
public static descriptor: DrawableDescriptor = {
|
||||||
sdf: {
|
sdf: {
|
||||||
shader: `
|
shader: `
|
||||||
uniform struct Tunnel {
|
uniform vec2 froms[TUNNEL_COUNT];
|
||||||
vec2 from;
|
uniform vec2 toFromDeltas[TUNNEL_COUNT];
|
||||||
vec2 toFromDelta;
|
uniform float fromRadii[TUNNEL_COUNT];
|
||||||
float fromRadius;
|
uniform float toRadii[TUNNEL_COUNT];
|
||||||
float toRadius;
|
|
||||||
}[TUNNEL_COUNT] tunnels;
|
|
||||||
|
|
||||||
void tunnelMinDistance(inout float minDistance, inout float color) {
|
void tunnelMinDistance(inout float minDistance, inout float color) {
|
||||||
float myMinDistance = minDistance;
|
float myMinDistance = minDistance;
|
||||||
for (int i = 0; i < TUNNEL_COUNT; i++) {
|
for (int i = 0; i < TUNNEL_COUNT; i++) {
|
||||||
Tunnel tunnel = tunnels[i];
|
vec2 targetFromDelta = position - froms[i];
|
||||||
vec2 targetFromDelta = position - tunnel.from;
|
|
||||||
|
float h = clamp(
|
||||||
float h = clamp(
|
dot(targetFromDelta, toFromDeltas[i])
|
||||||
dot(targetFromDelta, tunnel.toFromDelta)
|
/ dot(toFromDeltas[i], toFromDeltas[i]),
|
||||||
/ dot(tunnel.toFromDelta, tunnel.toFromDelta),
|
0.0, 1.0
|
||||||
0.0, 1.0
|
);
|
||||||
);
|
|
||||||
|
float currentDistance = -mix(
|
||||||
float currentDistance = -mix(
|
fromRadii[i], toRadii[i], h
|
||||||
tunnel.fromRadius, tunnel.toRadius, h
|
) + distance(
|
||||||
) + distance(
|
targetFromDelta, toFromDeltas[i] * h
|
||||||
targetFromDelta, tunnel.toFromDelta * h
|
);
|
||||||
);
|
|
||||||
|
|
||||||
myMinDistance = min(myMinDistance, currentDistance);
|
myMinDistance = min(myMinDistance, currentDistance);
|
||||||
}
|
|
||||||
|
|
||||||
color = mix(2.0, color, step(
|
|
||||||
distanceNdcPixelSize + SURFACE_OFFSET,
|
|
||||||
myMinDistance
|
|
||||||
));
|
|
||||||
minDistance = min(minDistance, myMinDistance);
|
|
||||||
}
|
}
|
||||||
`,
|
|
||||||
|
color = mix(2.0, color, step(
|
||||||
|
distanceNdcPixelSize + SURFACE_OFFSET,
|
||||||
|
myMinDistance
|
||||||
|
));
|
||||||
|
minDistance = min(minDistance, myMinDistance);
|
||||||
|
}
|
||||||
|
`,
|
||||||
distanceFunctionName: 'tunnelMinDistance',
|
distanceFunctionName: 'tunnelMinDistance',
|
||||||
},
|
},
|
||||||
uniformName: 'tunnels',
|
propertyUniformMapping: {
|
||||||
|
from: 'froms',
|
||||||
|
toFromDelta: 'toFromDeltas',
|
||||||
|
fromRadius: 'fromRadii',
|
||||||
|
toRadius: 'toRadii',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'TUNNEL_COUNT',
|
uniformCountMacroName: 'TUNNEL_COUNT',
|
||||||
shaderCombinationSteps: [0, 1, 4, 16, 32],
|
shaderCombinationSteps: [0, 1, 4, 16, 32],
|
||||||
empty: new Tunnel(
|
empty: new Tunnel(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import { FrameBuffer } from './frame-buffer';
|
import { FrameBuffer } from './frame-buffer';
|
||||||
|
|
||||||
export class DefaultFrameBuffer extends FrameBuffer {
|
export class DefaultFrameBuffer extends FrameBuffer {
|
||||||
constructor(gl: WebGL2RenderingContext) {
|
constructor(gl: UniversalRenderingContext) {
|
||||||
super(gl);
|
super(gl);
|
||||||
this.frameBuffer = null;
|
this.frameBuffer = null;
|
||||||
this.setSize();
|
this.setSize();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
|
|
||||||
export abstract class FrameBuffer {
|
export abstract class FrameBuffer {
|
||||||
public renderScale = 1;
|
public renderScale = 1;
|
||||||
|
|
@ -9,7 +10,7 @@ export abstract class FrameBuffer {
|
||||||
// null means the default framebuffer
|
// null means the default framebuffer
|
||||||
protected frameBuffer: WebGLFramebuffer | null = null;
|
protected frameBuffer: WebGLFramebuffer | null = null;
|
||||||
|
|
||||||
constructor(protected gl: WebGL2RenderingContext) {}
|
constructor(protected gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public bindAndClear(colorInput?: WebGLTexture) {
|
public bindAndClear(colorInput?: WebGLTexture) {
|
||||||
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
|
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { enableExtension } from '../helper/enable-extension';
|
import { enableExtension } from '../helper/enable-extension';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import { FrameBuffer } from './frame-buffer';
|
import { FrameBuffer } from './frame-buffer';
|
||||||
|
|
||||||
export class IntermediateFrameBuffer extends FrameBuffer {
|
export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
private frameTexture: WebGLTexture;
|
private frameTexture: WebGLTexture;
|
||||||
private floatLinearEnabled = false;
|
private floatLinearEnabled = false;
|
||||||
|
|
||||||
constructor(gl: WebGL2RenderingContext) {
|
constructor(gl: UniversalRenderingContext) {
|
||||||
super(gl);
|
super(gl);
|
||||||
|
|
||||||
enableExtension(gl, 'EXT_color_buffer_float');
|
if (gl.isWebGL2) {
|
||||||
|
enableExtension(gl, 'EXT_color_buffer_float');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
enableExtension(gl, 'OES_texture_float_linear');
|
enableExtension(gl, 'OES_texture_float_linear');
|
||||||
|
|
@ -45,12 +48,12 @@ export class IntermediateFrameBuffer extends FrameBuffer {
|
||||||
this.gl.texImage2D(
|
this.gl.texImage2D(
|
||||||
this.gl.TEXTURE_2D,
|
this.gl.TEXTURE_2D,
|
||||||
0,
|
0,
|
||||||
this.gl.RG16F,
|
this.gl.isWebGL2 ? this.gl.RG16F : this.gl.RGBA,
|
||||||
this.size.x,
|
this.size.x,
|
||||||
this.size.y,
|
this.size.y,
|
||||||
0,
|
0,
|
||||||
this.gl.RG,
|
this.gl.isWebGL2 ? this.gl.RG : this.gl.RGBA,
|
||||||
this.gl.FLOAT,
|
this.gl.isWebGL2 ? this.gl.FLOAT : this.gl.UNSIGNED_BYTE,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Insights } from '../../rendering/insights';
|
import { Insights } from '../../rendering/insights';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
|
|
||||||
const extensions: Map<string, any> = new Map();
|
const extensions: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
|
@ -11,7 +12,7 @@ const logExtensions = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tryEnableExtension = (
|
export const tryEnableExtension = (
|
||||||
gl: WebGL2RenderingContext,
|
gl: UniversalRenderingContext,
|
||||||
name: string
|
name: string
|
||||||
): any | null => {
|
): any | null => {
|
||||||
if (extensions.has(name)) {
|
if (extensions.has(name)) {
|
||||||
|
|
@ -30,7 +31,7 @@ export const tryEnableExtension = (
|
||||||
return extension;
|
return extension;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enableExtension = (gl: WebGL2RenderingContext, name: string): any => {
|
export const enableExtension = (gl: UniversalRenderingContext, name: string): any => {
|
||||||
const extension = tryEnableExtension(gl, name);
|
const extension = tryEnableExtension(gl, name);
|
||||||
|
|
||||||
if (extension === null) {
|
if (extension === null) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
export const getWebGl2Context = (canvas: HTMLCanvasElement): WebGL2RenderingContext => {
|
|
||||||
const gl = canvas.getContext('webgl2');
|
|
||||||
|
|
||||||
if (!gl) {
|
|
||||||
throw new Error('WebGl2 is not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
return gl;
|
|
||||||
};
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
|
import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
|
|
||||||
const loaderMat3 = mat3.create();
|
const loaderMat3 = mat3.create();
|
||||||
|
|
||||||
export const loadUniform = (
|
export const loadUniform = (
|
||||||
gl: WebGL2RenderingContext,
|
gl: UniversalRenderingContext,
|
||||||
value: any,
|
value: any,
|
||||||
type: GLenum,
|
type: GLenum,
|
||||||
location: WebGLUniformLocation
|
location: WebGLUniformLocation
|
||||||
): any => {
|
): any => {
|
||||||
const converters: Map<
|
const converters: Map<
|
||||||
GLenum,
|
GLenum,
|
||||||
(gl: WebGL2RenderingContext, value: any, location: WebGLUniformLocation) => void
|
(gl: UniversalRenderingContext, value: any, location: WebGLUniformLocation) => void
|
||||||
> = new Map();
|
> = new Map();
|
||||||
{
|
{
|
||||||
converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => {
|
converters.set(WebGLRenderingContext.FLOAT, (gl, v, l) => {
|
||||||
if (v instanceof Array) {
|
if (v instanceof Array || v[0] instanceof Float32Array) {
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -25,19 +26,18 @@ export const loadUniform = (
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
converters.set(WebGL2RenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array<vec2>, l) => {
|
converters.set(WebGLRenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array<vec2>, l) => {
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v[0] instanceof Array) {
|
if (v[0] instanceof Array || v[0] instanceof Float32Array) {
|
||||||
const result = new Float32Array(v.length * 2);
|
const result = new Float32Array(v.length * 2);
|
||||||
|
|
||||||
for (let i = 0; i < v.length; i++) {
|
for (let i = 0; i < v.length; i++) {
|
||||||
result[2 * i] = (v[i] as Array<number>).x;
|
result[2 * i] = (v[i] as Array<number>).x;
|
||||||
result[2 * i + 1] = (v[i] as Array<number>).y;
|
result[2 * i + 1] = (v[i] as Array<number>).y;
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.uniform2fv(l, result);
|
gl.uniform2fv(l, result);
|
||||||
} else {
|
} else {
|
||||||
gl.uniform2fv(l, v as vec2);
|
gl.uniform2fv(l, v as vec2);
|
||||||
|
|
@ -45,13 +45,13 @@ export const loadUniform = (
|
||||||
});
|
});
|
||||||
|
|
||||||
converters.set(
|
converters.set(
|
||||||
WebGL2RenderingContext.FLOAT_VEC3,
|
WebGLRenderingContext.FLOAT_VEC3,
|
||||||
(gl, v: ReadonlyVec3 | Array<vec3>, l) => {
|
(gl, v: ReadonlyVec3 | Array<vec3>, l) => {
|
||||||
if (v.length == 0) {
|
if (v.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v[0] instanceof Array) {
|
if (v[0] instanceof Array || v[0] instanceof Float32Array) {
|
||||||
const result = new Float32Array(v.length * 3);
|
const result = new Float32Array(v.length * 3);
|
||||||
|
|
||||||
for (let i = 0; i < v.length; i++) {
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
|
@ -67,11 +67,19 @@ export const loadUniform = (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
converters.set(WebGL2RenderingContext.FLOAT_MAT3, (gl, v, l) => {
|
converters.set(WebGLRenderingContext.FLOAT_MAT3, (gl, v, l) => {
|
||||||
gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v));
|
if (gl.isWebGL2) {
|
||||||
|
gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v));
|
||||||
|
} else {
|
||||||
|
gl.uniformMatrix3fv(
|
||||||
|
l,
|
||||||
|
false,
|
||||||
|
mat3.transpose(mat3.create(), mat3.fromMat2d(loaderMat3, v))
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
converters.set(WebGL2RenderingContext.BOOL, (gl, v, l) => gl.uniform1i(l, v));
|
converters.set(WebGLRenderingContext.BOOL, (gl, v, l) => gl.uniform1i(l, v));
|
||||||
|
|
||||||
if (!converters.has(type)) {
|
if (!converters.has(type)) {
|
||||||
throw new Error(`Unimplemented webgl type: ${type}`);
|
throw new Error(`Unimplemented webgl type: ${type}`);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import { enableExtension } from './enable-extension';
|
import { enableExtension } from './enable-extension';
|
||||||
|
|
||||||
enum StopwatchState {
|
enum StopwatchState {
|
||||||
|
|
@ -13,7 +14,7 @@ export class WebGlStopwatch {
|
||||||
private timerExtension: any;
|
private timerExtension: any;
|
||||||
private timerQuery?: WebGLQuery;
|
private timerQuery?: WebGLQuery;
|
||||||
|
|
||||||
constructor(private gl: WebGL2RenderingContext) {
|
constructor(private gl: UniversalRenderingContext) {
|
||||||
this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2');
|
this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
import { wait } from '../../helper/wait';
|
import { wait } from '../../helper/wait';
|
||||||
import { Insights } from '../rendering/insights';
|
import { Insights } from '../rendering/insights';
|
||||||
import { tryEnableExtension } from './helper/enable-extension';
|
import { tryEnableExtension } from './helper/enable-extension';
|
||||||
|
import { UniversalRenderingContext } from './universal-rendering-context';
|
||||||
|
|
||||||
type CompilingProgram = {
|
type CompilingProgram = {
|
||||||
program: WebGLProgram;
|
program: WebGLProgram;
|
||||||
resolvePromise: ((program: WebGLProgram) => void) | null;
|
resolvePromise: ((program: WebGLProgram) => void) | null;
|
||||||
|
vertexShader: ShaderWithSource;
|
||||||
|
fragmentShader: ShaderWithSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ShaderWithSource = WebGLShader & { source: string };
|
||||||
|
|
||||||
export abstract class ParallelCompiler {
|
export abstract class ParallelCompiler {
|
||||||
private static extension?: any;
|
private static extension?: any;
|
||||||
private static gl: WebGL2RenderingContext;
|
private static gl: UniversalRenderingContext;
|
||||||
private static programs: Array<CompilingProgram> = [];
|
private static programs: Array<CompilingProgram> = [];
|
||||||
|
|
||||||
public static initialize(gl: WebGL2RenderingContext) {
|
public static initialize(gl: UniversalRenderingContext) {
|
||||||
ParallelCompiler.gl = gl;
|
ParallelCompiler.gl = gl;
|
||||||
ParallelCompiler.extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile');
|
ParallelCompiler.extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile');
|
||||||
}
|
}
|
||||||
|
|
@ -28,14 +33,14 @@ export abstract class ParallelCompiler {
|
||||||
// can only return null on lost context
|
// can only return null on lost context
|
||||||
const program = ParallelCompiler.gl.createProgram()!;
|
const program = ParallelCompiler.gl.createProgram()!;
|
||||||
|
|
||||||
ParallelCompiler.compileShader(
|
const vertexShader = ParallelCompiler.compileShader(
|
||||||
vertexShaderSource,
|
vertexShaderSource,
|
||||||
ParallelCompiler.gl.VERTEX_SHADER,
|
ParallelCompiler.gl.VERTEX_SHADER,
|
||||||
program,
|
program,
|
||||||
substitutions
|
substitutions
|
||||||
);
|
);
|
||||||
|
|
||||||
ParallelCompiler.compileShader(
|
const fragmentShader = ParallelCompiler.compileShader(
|
||||||
fragmentShaderSource,
|
fragmentShaderSource,
|
||||||
ParallelCompiler.gl.FRAGMENT_SHADER,
|
ParallelCompiler.gl.FRAGMENT_SHADER,
|
||||||
program,
|
program,
|
||||||
|
|
@ -45,6 +50,8 @@ export abstract class ParallelCompiler {
|
||||||
ParallelCompiler.programs.push({
|
ParallelCompiler.programs.push({
|
||||||
program,
|
program,
|
||||||
resolvePromise,
|
resolvePromise,
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
|
|
@ -67,7 +74,7 @@ export abstract class ParallelCompiler {
|
||||||
type: GLenum,
|
type: GLenum,
|
||||||
program: WebGLProgram,
|
program: WebGLProgram,
|
||||||
substitutions: { [name: string]: string }
|
substitutions: { [name: string]: string }
|
||||||
) {
|
): ShaderWithSource {
|
||||||
const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => {
|
const processedSource = source.replace(/{(.+)}/gm, (_, name: string): string => {
|
||||||
const value = substitutions[name];
|
const value = substitutions[name];
|
||||||
return Number.isInteger(value) ? `${value}.0` : value;
|
return Number.isInteger(value) ? `${value}.0` : value;
|
||||||
|
|
@ -79,7 +86,11 @@ export abstract class ParallelCompiler {
|
||||||
this.gl.shaderSource(shader, processedSource);
|
this.gl.shaderSource(shader, processedSource);
|
||||||
this.gl.compileShader(shader);
|
this.gl.compileShader(shader);
|
||||||
this.gl.attachShader(program, shader);
|
this.gl.attachShader(program, shader);
|
||||||
this.gl.deleteShader(shader);
|
|
||||||
|
const result = shader as ShaderWithSource;
|
||||||
|
result.source = processedSource;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static resolveFinishedPrograms() {
|
private static resolveFinishedPrograms() {
|
||||||
|
|
@ -93,7 +104,7 @@ export abstract class ParallelCompiler {
|
||||||
ParallelCompiler.extension.COMPLETION_STATUS_KHR
|
ParallelCompiler.extension.COMPLETION_STATUS_KHR
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
ParallelCompiler.checkProgram(p.program);
|
ParallelCompiler.checkProgram(p);
|
||||||
done.push(p);
|
done.push(p);
|
||||||
p.resolvePromise!(p.program);
|
p.resolvePromise!(p.program);
|
||||||
}
|
}
|
||||||
|
|
@ -104,18 +115,39 @@ export abstract class ParallelCompiler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static checkProgram(program: WebGLProgram) {
|
private static checkProgram(program: CompilingProgram) {
|
||||||
const success = ParallelCompiler.gl.getProgramParameter(
|
const success = ParallelCompiler.gl.getProgramParameter(
|
||||||
program,
|
program.program,
|
||||||
ParallelCompiler.gl.LINK_STATUS
|
ParallelCompiler.gl.LINK_STATUS
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success && !ParallelCompiler.gl.isContextLost()) {
|
if (!success && !ParallelCompiler.gl.isContextLost()) {
|
||||||
ParallelCompiler.gl.getAttachedShaders(program)?.forEach((s) => {
|
ParallelCompiler.prettyPrintErrorsIfThereAreAny(program.vertexShader);
|
||||||
ParallelCompiler.checkShader(s);
|
ParallelCompiler.prettyPrintErrorsIfThereAreAny(program.fragmentShader);
|
||||||
});
|
|
||||||
|
|
||||||
throw new Error(ParallelCompiler.gl.getProgramInfoLog(program)!);
|
throw new Error(ParallelCompiler.gl.getProgramInfoLog(program.program)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gl.deleteShader(program.vertexShader);
|
||||||
|
this.gl.deleteShader(program.fragmentShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static prettyPrintErrorsIfThereAreAny(shader: ShaderWithSource) {
|
||||||
|
try {
|
||||||
|
ParallelCompiler.checkShader(shader);
|
||||||
|
} catch (e) {
|
||||||
|
for (const match of e
|
||||||
|
.toString()
|
||||||
|
.matchAll(/ERROR: 0:(?<line>\d+): (?<error>.*)$/gm)) {
|
||||||
|
const line = Number.parseInt(match.groups.line);
|
||||||
|
const error = match.groups.error;
|
||||||
|
console.error(
|
||||||
|
`Error: ${error}\nSource (line ${line}):\n${
|
||||||
|
shader.source.split('\n')[line - 1]
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error('Error while compiling shader');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
|
import { enableExtension } from '../helper/enable-extension';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import Program from './program';
|
import Program from './program';
|
||||||
|
|
||||||
export class FragmentShaderOnlyProgram extends Program {
|
export class FragmentShaderOnlyProgram extends Program {
|
||||||
private vao?: WebGLVertexArrayObject;
|
private vao?: WebGLVertexArrayObject;
|
||||||
|
private vertexArrayExtension: any;
|
||||||
constructor(gl: WebGL2RenderingContext) {
|
constructor(gl: UniversalRenderingContext) {
|
||||||
super(gl);
|
super(gl);
|
||||||
|
this.vertexArrayExtension = enableExtension(this.gl, 'OES_vertex_array_object');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(
|
||||||
|
|
@ -17,7 +20,11 @@ export class FragmentShaderOnlyProgram extends Program {
|
||||||
|
|
||||||
public bind() {
|
public bind() {
|
||||||
super.bind();
|
super.bind();
|
||||||
this.gl.bindVertexArray(this.vao!);
|
if (this.gl.isWebGL2) {
|
||||||
|
this.gl.bindVertexArray(this.vao!);
|
||||||
|
} else {
|
||||||
|
this.vertexArrayExtension.createVertexArrayOES();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(uniforms: { [name: string]: any }) {
|
public draw(uniforms: { [name: string]: any }) {
|
||||||
|
|
@ -26,7 +33,12 @@ export class FragmentShaderOnlyProgram extends Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this.gl.deleteVertexArray(this.vao!);
|
if (this.gl.isWebGL2) {
|
||||||
|
this.gl.deleteVertexArray(this.vao!);
|
||||||
|
} else {
|
||||||
|
this.vertexArrayExtension.deleteVertexArrayOES(this.vao!);
|
||||||
|
}
|
||||||
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,10 +57,15 @@ export class FragmentShaderOnlyProgram extends Program {
|
||||||
this.gl.STATIC_DRAW
|
this.gl.STATIC_DRAW
|
||||||
);
|
);
|
||||||
|
|
||||||
// can only return null on lost context
|
if (this.gl.isWebGL2) {
|
||||||
this.vao = this.gl.createVertexArray()!;
|
// can only return null on lost context
|
||||||
|
this.vao = this.gl.createVertexArray()!;
|
||||||
|
this.gl.bindVertexArray(this.vao!);
|
||||||
|
} else {
|
||||||
|
this.vao = this.vertexArrayExtension.createVertexArrayOES();
|
||||||
|
this.vertexArrayExtension.bindVertexArrayOES(this.vao!);
|
||||||
|
}
|
||||||
|
|
||||||
this.gl.bindVertexArray(this.vao);
|
|
||||||
this.gl.enableVertexAttribArray(positionAttributeLocation);
|
this.gl.enableVertexAttribArray(positionAttributeLocation);
|
||||||
this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
|
this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { mat2d, vec2 } from 'gl-matrix';
|
import { mat2d, vec2 } from 'gl-matrix';
|
||||||
import { loadUniform } from '../helper/load-uniform';
|
import { loadUniform } from '../helper/load-uniform';
|
||||||
import { ParallelCompiler } from '../parallel-compiler';
|
import { ParallelCompiler } from '../parallel-compiler';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import { IProgram } from './i-program';
|
import { IProgram } from './i-program';
|
||||||
|
|
||||||
export default abstract class Program implements IProgram {
|
export default abstract class Program implements IProgram {
|
||||||
|
|
@ -14,7 +15,7 @@ export default abstract class Program implements IProgram {
|
||||||
type: GLenum;
|
type: GLenum;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
constructor(protected gl: WebGL2RenderingContext) {}
|
constructor(protected gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(
|
||||||
[vertexShaderSource, fragmentShaderSource]: [string, string],
|
[vertexShaderSource, fragmentShaderSource]: [string, string],
|
||||||
|
|
@ -67,7 +68,7 @@ export default abstract class Program implements IProgram {
|
||||||
private queryUniforms() {
|
private queryUniforms() {
|
||||||
const uniformCount = this.gl.getProgramParameter(
|
const uniformCount = this.gl.getProgramParameter(
|
||||||
this.program!,
|
this.program!,
|
||||||
WebGL2RenderingContext.ACTIVE_UNIFORMS
|
WebGLRenderingContext.ACTIVE_UNIFORMS
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < uniformCount; i++) {
|
for (let i = 0; i < uniformCount; i++) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { mat2d, vec2 } from 'gl-matrix';
|
||||||
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
|
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
|
||||||
import { getCombinations } from '../../../helper/get-combinations';
|
import { getCombinations } from '../../../helper/get-combinations';
|
||||||
import { last } from '../../../helper/last';
|
import { last } from '../../../helper/last';
|
||||||
|
import { UniversalRenderingContext } from '../universal-rendering-context';
|
||||||
import { FragmentShaderOnlyProgram } from './fragment-shader-only-program';
|
import { FragmentShaderOnlyProgram } from './fragment-shader-only-program';
|
||||||
import { IProgram } from './i-program';
|
import { IProgram } from './i-program';
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ export class UniformArrayAutoScalingProgram implements IProgram {
|
||||||
private drawingRectangleBottomLeft = vec2.fromValues(0, 0);
|
private drawingRectangleBottomLeft = vec2.fromValues(0, 0);
|
||||||
private drawingRectangleSize = vec2.fromValues(1, 1);
|
private drawingRectangleSize = vec2.fromValues(1, 1);
|
||||||
|
|
||||||
constructor(private gl: WebGL2RenderingContext) {}
|
constructor(private gl: UniversalRenderingContext) {}
|
||||||
|
|
||||||
public async initialize(
|
public async initialize(
|
||||||
shaderSources: [string, string],
|
shaderSources: [string, string],
|
||||||
|
|
@ -44,9 +45,14 @@ export class UniformArrayAutoScalingProgram implements IProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(uniforms: { [name: string]: any }): void {
|
public draw(uniforms: { [name: string]: any }): void {
|
||||||
const values = this.descriptors!.map((d) =>
|
const values = this.descriptors!.map((d) => {
|
||||||
uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0
|
const uniformNames = last(Object.entries(d.propertyUniformMapping));
|
||||||
);
|
if (!uniformNames) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const uniformName = uniformNames[1];
|
||||||
|
return uniforms[uniformName] ? uniforms[uniformName].length : 0;
|
||||||
|
});
|
||||||
|
|
||||||
const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i]));
|
const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i]));
|
||||||
|
|
||||||
|
|
|
||||||
34
src/graphics/graphics-library/universal-rendering-context.ts
Normal file
34
src/graphics/graphics-library/universal-rendering-context.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Insights } from '../rendering/insights';
|
||||||
|
|
||||||
|
export type UniversalRenderingContext = WebGL2RenderingContext &
|
||||||
|
WebGLRenderingContext & { isWebGL2: boolean };
|
||||||
|
|
||||||
|
export const getUniversalRenderingContext = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
ignoreWebGL2 = false
|
||||||
|
): UniversalRenderingContext => {
|
||||||
|
let context: WebGL2RenderingContext | WebGLRenderingContext | null = ignoreWebGL2
|
||||||
|
? null
|
||||||
|
: canvas.getContext('webgl2');
|
||||||
|
|
||||||
|
let webgl2Support = true;
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
context = canvas.getContext('webgl');
|
||||||
|
webgl2Support = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
context = canvas.getContext('experimental-webgl') as WebGLRenderingContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Neither WebGL or WebGL2 is supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
Insights.setValue('using WebGL2', webgl2Support);
|
||||||
|
|
||||||
|
const result = context as UniversalRenderingContext;
|
||||||
|
result.isWebGL2 = webgl2Support;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
|
import { DrawableDescriptor } from '../../../drawables/drawable-descriptor';
|
||||||
import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer';
|
import { FrameBuffer } from '../../graphics-library/frame-buffer/frame-buffer';
|
||||||
import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program';
|
import { UniformArrayAutoScalingProgram } from '../../graphics-library/program/uniform-array-autoscaling-program';
|
||||||
|
import { UniversalRenderingContext } from '../../graphics-library/universal-rendering-context';
|
||||||
|
|
||||||
export abstract class RenderPass {
|
export abstract class RenderPass {
|
||||||
protected program: UniformArrayAutoScalingProgram;
|
protected program: UniformArrayAutoScalingProgram;
|
||||||
|
|
||||||
constructor(gl: WebGL2RenderingContext, protected frame: FrameBuffer) {
|
constructor(gl: UniversalRenderingContext, protected frame: FrameBuffer) {
|
||||||
this.program = new UniformArrayAutoScalingProgram(gl);
|
this.program = new UniformArrayAutoScalingProgram(gl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue