Skip to content

Commit

Permalink
Add support for WebGL1
Browse files Browse the repository at this point in the history
Should work on Safari now
  • Loading branch information
huww98 committed Aug 4, 2020
1 parent d73cd35 commit 30028a1
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 53 deletions.
2 changes: 2 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ function main() {
const dataSin = [];
const dataCos = [];
const chart = new TimeChart(el, {
// debugWebGL: true,
// forceWebGL1: true,
baseTime: Date.now() - performance.now(),
series: [
{
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ <h2>1000 points / second adding to chart.</h2>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-axis.v1.min.js"></script>

<!-- <script src="../dist/timechart.umd.js"></script> -->
<script src="../dist/timechart.min.js"></script>
<script src="./demo.js"></script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const config = {
format: 'umd',
sourcemap: true
},
{ file: pkg.module, format: 'es', sourcemap: true },
{ file: 'dist/timechart.module.js', format: 'es', sourcemap: true },
],
external: (id) => id.startsWith('d3-'),
watch: {
Expand Down
26 changes: 18 additions & 8 deletions src/canvasLayer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { resolveColorRGBA, ResolvedRenderOptions } from './options';
import { RenderModel } from './renderModel';

function getContext(canvas: HTMLCanvasElement, forceWebGL1: boolean) {
if (!forceWebGL1) {
const ctx = canvas.getContext('webgl2');
if (ctx) {
return ctx;
}
}
const ctx = canvas.getContext('webgl');
if (ctx) {
return ctx;
}
throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.');
}

export class CanvasLayer {
canvas: HTMLCanvasElement
gl: WebGL2RenderingContext;
gl: WebGL2RenderingContext | WebGLRenderingContext;

constructor(el: HTMLElement, private options: ResolvedRenderOptions, model: RenderModel) {
el.style.position = 'relative';
Expand All @@ -13,14 +27,10 @@ export class CanvasLayer {
canvas.style.position = 'absolute';
el.appendChild(canvas);

const ctx = canvas.getContext('webgl2');
if (!ctx) {
throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.');
}
this.gl = ctx;
this.gl = getContext(canvas, options.forceWebGL1);

const bgColor = resolveColorRGBA(options.backgroundColor);
ctx.clearColor(...bgColor);
this.gl.clearColor(...bgColor);

this.canvas = canvas;

Expand All @@ -30,7 +40,7 @@ export class CanvasLayer {
el.removeChild(canvas);
canvas.width = 0;
canvas.height = 0;
const lossContext = ctx.getExtension('WEBGL_lose_context');
const lossContext = this.gl.getExtension('WEBGL_lose_context');
if (lossContext) {
lossContext.loseContext();
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const defaultOptions = {
realTime: false,
baseTime: 0,
debugWebGL: false,
forceWebGL1: false,
} as const;

const defaultSeriesOptions = {
Expand Down
165 changes: 125 additions & 40 deletions src/lineChartRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ const enum VertexAttribLocations {
DIR = 1,
}

const vsSource = `#version 300 es
layout (location = ${VertexAttribLocations.DATA_POINT}) in vec2 aDataPoint;
layout (location = ${VertexAttribLocations.DIR}) in vec2 aDir;
function vsSource(gl: WebGL2RenderingContext | WebGLRenderingContext) {
const body = `
uniform vec2 uModelScale;
uniform vec2 uModelTranslation;
uniform vec2 uProjectionScale;
Expand All @@ -25,20 +23,41 @@ void main() {
dir = normalize(dir);
vec2 pos2d = uProjectionScale * (cssPose + vec2(-dir.y, dir.x) * uLineWidth);
gl_Position = vec4(pos2d, 0, 1);
}`;

if (gl instanceof WebGL2RenderingContext) {
return `#version 300 es
layout (location = ${VertexAttribLocations.DATA_POINT}) in vec2 aDataPoint;
layout (location = ${VertexAttribLocations.DIR}) in vec2 aDir;
${body}`;
} else {
return `
attribute vec2 aDataPoint;
attribute vec2 aDir;
${body}`;
}
}
`;

const fsSource = `#version 300 es
function fsSource(gl: WebGL2RenderingContext | WebGLRenderingContext) {
const body = `
`;
if (gl instanceof WebGL2RenderingContext) {
return `#version 300 es
precision lowp float;
uniform vec4 uColor;
out vec4 outColor;
void main() {
outColor = uColor;
}`;
} else {
return `
precision lowp float;
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}`;
}
}
`;

class LineChartWebGLProgram extends LinkedWebGLProgram {
locations: {
Expand All @@ -48,14 +67,24 @@ class LineChartWebGLProgram extends LinkedWebGLProgram {
uLineWidth: WebGLUniformLocation;
uColor: WebGLUniformLocation;
};
constructor(gl: WebGLRenderingContext, debug: boolean) {
super(gl, vsSource, fsSource, debug);
constructor(gl: WebGL2RenderingContext | WebGLRenderingContext, debug: boolean) {
super(gl, vsSource(gl), fsSource(gl), debug);

if (gl instanceof WebGLRenderingContext) {
gl.bindAttribLocation(this.program, VertexAttribLocations.DATA_POINT, 'aDataPoint');
gl.bindAttribLocation(this.program, VertexAttribLocations.DIR, 'aDir');
}

this.link();

const getLoc = (name: string) => throwIfFalsy(gl.getUniformLocation(this.program, name));

this.locations = {
uModelScale: throwIfFalsy(gl.getUniformLocation(this.program, 'uModelScale')),
uModelTranslation: throwIfFalsy(gl.getUniformLocation(this.program, 'uModelTranslation')),
uProjectionScale: throwIfFalsy(gl.getUniformLocation(this.program, 'uProjectionScale')),
uLineWidth: throwIfFalsy(gl.getUniformLocation(this.program, 'uLineWidth')),
uColor: throwIfFalsy(gl.getUniformLocation(this.program, 'uColor')),
uModelScale: getLoc('uModelScale'),
uModelTranslation: getLoc('uModelTranslation'),
uProjectionScale: getLoc('uProjectionScale'),
uLineWidth: getLoc('uLineWidth'),
uColor: getLoc('uColor'),
}
}
}
Expand All @@ -67,35 +96,85 @@ const BYTES_PER_POINT = INDEX_PER_POINT * Float32Array.BYTES_PER_ELEMENT;
const BUFFER_DATA_POINT_CAPACITY = 128 * 1024;
const BUFFER_CAPACITY = BUFFER_DATA_POINT_CAPACITY * INDEX_PER_DATAPOINT + 2 * POINT_PER_DATAPOINT;

class VertexArray {
vao: WebGLVertexArrayObject;
interface IVAO {
bind(): void;
clear(): void;
}

class WebGL2VAO implements IVAO {
private vao: WebGLVertexArrayObject;
constructor(private gl: WebGL2RenderingContext) {
this.vao = throwIfFalsy(gl.createVertexArray());
this.bind();
}
bind() {
this.gl.bindVertexArray(this.vao);
}
clear() {
this.gl.deleteVertexArray(this.vao);
}
}

class OESVAO implements IVAO {
vao: WebGLVertexArrayObjectOES;
constructor(private vaoExt: OES_vertex_array_object) {
this.vao = throwIfFalsy(vaoExt.createVertexArrayOES());
this.bind();
}
bind() {
this.vaoExt.bindVertexArrayOES(this.vao);
}
clear() {
this.vaoExt.deleteVertexArrayOES(this.vao);
}
}

class WebGL1BufferInfo implements IVAO {
constructor(private bindFunc: () => void) {
}
bind() {
this.bindFunc();
}
clear() {
}
}

class SeriesSegmentVertexArray {
vao: IVAO;
dataBuffer: WebGLBuffer;
length = 0;

/**
* @param firstDataPointIndex At least 1, since datapoint 0 has no path to draw.
*/
constructor(
private gl: WebGL2RenderingContext,
private gl: WebGL2RenderingContext | WebGLRenderingContext,
private dataPoints: ArrayLike<DataPoint>,
public readonly firstDataPointIndex: number,
) {
this.vao = throwIfFalsy(gl.createVertexArray());
this.bind();

this.dataBuffer = throwIfFalsy(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.dataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, BUFFER_CAPACITY * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
const bindFunc = () => {
gl.bindBuffer(gl.ARRAY_BUFFER, this.dataBuffer);

gl.enableVertexAttribArray(VertexAttribLocations.DATA_POINT);
gl.vertexAttribPointer(VertexAttribLocations.DATA_POINT, 2, gl.FLOAT, false, BYTES_PER_POINT, 0);
gl.enableVertexAttribArray(VertexAttribLocations.DATA_POINT);
gl.vertexAttribPointer(VertexAttribLocations.DATA_POINT, 2, gl.FLOAT, false, BYTES_PER_POINT, 0);

gl.enableVertexAttribArray(VertexAttribLocations.DIR);
gl.vertexAttribPointer(VertexAttribLocations.DIR, 2, gl.FLOAT, false, BYTES_PER_POINT, 2 * Float32Array.BYTES_PER_ELEMENT);
}
gl.enableVertexAttribArray(VertexAttribLocations.DIR);
gl.vertexAttribPointer(VertexAttribLocations.DIR, 2, gl.FLOAT, false, BYTES_PER_POINT, 2 * Float32Array.BYTES_PER_ELEMENT);
}
if (gl instanceof WebGL2RenderingContext) {
this.vao = new WebGL2VAO(gl);
} else {
// const vaoExt = gl.getExtension('OES_vertex_array_object');
// if (vaoExt) {
// this.vao = new OESVAO(vaoExt);
// } else {
this.vao = new WebGL1BufferInfo(bindFunc);
// }
}
bindFunc();

bind() {
this.gl.bindVertexArray(this.vao);
gl.bufferData(gl.ARRAY_BUFFER, BUFFER_CAPACITY * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
}

clear() {
Expand All @@ -105,7 +184,7 @@ class VertexArray {
delete() {
this.clear();
this.gl.deleteBuffer(this.dataBuffer);
this.gl.deleteVertexArray(this.vao);
this.vao.clear();
}

/**
Expand Down Expand Up @@ -183,31 +262,31 @@ class VertexArray {
const count = last - first;

const gl = this.gl;
this.bind();
this.vao.bind();
gl.drawArrays(gl.TRIANGLE_STRIP, first * POINT_PER_DATAPOINT, count * POINT_PER_DATAPOINT);
}
}

/**
* An array of `VertexArray` to represent a series
* An array of `SeriesSegmentVertexArray` to represent a series
*
* `series.data` index: 0 [1 ... C] [C+1 ... 2C] ... (C = `BUFFER_DATA_POINT_CAPACITY`)
* `vertexArrays` index: 0 1 ...
*/
class SeriesVertexArray {
private vertexArrays = [] as VertexArray[];
private vertexArrays = [] as SeriesSegmentVertexArray[];
constructor(
private gl: WebGL2RenderingContext,
private gl: WebGL2RenderingContext | WebGLRenderingContext,
private series: TimeChartSeriesOptions,
) {
}

syncBuffer() {
let activeArray: VertexArray;
let activeArray: SeriesSegmentVertexArray;
let bufferedDataPointNum = 1;

const newArray = () => {
activeArray = new VertexArray(this.gl, this.series.data , bufferedDataPointNum);
activeArray = new SeriesSegmentVertexArray(this.gl, this.series.data, bufferedDataPointNum);
this.vertexArrays.push(activeArray);
}

Expand Down Expand Up @@ -265,7 +344,7 @@ export class LineChartRenderer {

constructor(
private model: RenderModel,
private gl: WebGL2RenderingContext,
private gl: WebGL2RenderingContext | WebGLRenderingContext,
private options: ResolvedRenderOptions,
) {
this.program.use();
Expand Down Expand Up @@ -316,6 +395,12 @@ export class LineChartRenderer {
};
arr.draw(renderDomain);
}
if (this.options.debugWebGL) {
const err = gl.getError();
if (err != gl.NO_ERROR) {
throw new Error(`WebGL error ${err}`);
}
}
}

private ySvgToView(v: number) {
Expand Down
1 change: 1 addition & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface TimeChartRenderOptions {
baseTime: number;

debugWebGL: boolean;
forceWebGL1: boolean;
}

interface TimeChartOptionsBase extends TimeChartRenderOptions {
Expand Down
16 changes: 12 additions & 4 deletions src/webGLUtils.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
export class LinkedWebGLProgram {
program: WebGLProgram;

constructor(private gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string, debug: boolean) {
constructor(
private gl: WebGLRenderingContext,
vertexSource: string, fragmentSource: string,
public readonly debug: boolean
) {
const program = throwIfFalsy(gl.createProgram());
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, debug)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, debug)));
this.program = program
}

link() {
const gl = this.gl;
const program = this.program;
gl.linkProgram(program);
if (debug) {
if (this.debug) {
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) {
const message = gl.getProgramInfoLog(program) ?? 'Unknown Error.';
gl.deleteProgram(program);
throw new Error(message);
}
}

this.program = program
}

public use() {
Expand Down

0 comments on commit 30028a1

Please sign in to comment.