1 |
|
2 | import createShader from "gl-shader";
|
3 | import createTexture from "gl-texture2d";
|
4 | import now from "performance-now";
|
5 | import ndarray from "ndarray";
|
6 |
|
7 | type Color = [number, number, number, number];
|
8 |
|
9 | function colorMatches(actual: Color, expected: Color): boolean {
|
10 | const dr = actual[0] - expected[0];
|
11 | const dg = actual[1] - expected[1];
|
12 | const db = actual[2] - expected[2];
|
13 | const da = actual[3] - expected[3];
|
14 |
|
15 | return dr * dr + dg * dg + db * db + da * da < 20;
|
16 | }
|
17 |
|
18 | type CompilerResult = {
|
19 | data: {
|
20 | compileTime: number,
|
21 | drawTime: number,
|
22 | },
|
23 | errors: Array<{
|
24 | type: string,
|
25 | message: string,
|
26 | code: string,
|
27 | line?: number,
|
28 | }>,
|
29 | };
|
30 |
|
31 | const VERTEX_SHADER = `attribute vec2 _p;
|
32 | varying vec2 uv;
|
33 | void main() {
|
34 | gl_Position = vec4(_p,0.0,1.0);
|
35 | uv = vec2(0.5, 0.5) * (_p+vec2(1.0, 1.0));
|
36 | }`;
|
37 |
|
38 | export default (gl: WebGLRenderingContext) => {
|
39 | const { drawingBufferWidth: w, drawingBufferHeight: h } = gl;
|
40 | const pixels = new Uint8Array(w * h * 4);
|
41 |
|
42 | function colorAt(x, y) {
|
43 | const i = (x + y * w) * 4;
|
44 | const r = pixels[i + 0];
|
45 | const g = pixels[i + 1];
|
46 | const b = pixels[i + 2];
|
47 | const a = pixels[i + 3];
|
48 | return [r, g, b, a];
|
49 | }
|
50 |
|
51 | const buffer = gl.createBuffer();
|
52 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
53 | gl.bufferData(
|
54 | gl.ARRAY_BUFFER,
|
55 | new Float32Array([-1, -1, -1, 4, 4, -1]),
|
56 | gl.STATIC_DRAW
|
57 | );
|
58 | gl.viewport(0, 0, w, h);
|
59 |
|
60 | const genericTexture = createTexture(
|
61 | gl,
|
62 | ndarray(
|
63 |
|
64 | [ 0.1, 0.1, 0.1, 1.0,
|
65 | 0.5, 0.5, 0.5, 1.0,
|
66 | 0.5, 0.5, 0.5, 1.0,
|
67 | 0.9, 0.9, 0.9, 1.0 ],
|
68 | [2, 2, 4]
|
69 | )
|
70 | );
|
71 | genericTexture.minFilter = gl.LINEAR;
|
72 | genericTexture.magFilter = gl.LINEAR;
|
73 |
|
74 | return (glsl: string): CompilerResult => {
|
75 | let data = {
|
76 | compileTime: 0,
|
77 | drawTime: 0,
|
78 | };
|
79 | const errors = [];
|
80 | let shader;
|
81 | try {
|
82 | const beforeCompilation = now();
|
83 | shader = createShader(
|
84 | gl,
|
85 | VERTEX_SHADER,
|
86 | `precision highp float;varying vec2 uv;uniform float progress, ratio;vec4 getFromColor (vec2 uv) { return vec4(uv, 0.0, 1.0); } vec4 getToColor (vec2 uv) { return vec4(uv, 1.0, 1.0); } ${glsl}
|
87 | void main () {
|
88 | gl_FragColor = transition(uv);
|
89 | }`
|
90 | );
|
91 | gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
92 | const afterCompilation = now();
|
93 | data.compileTime = afterCompilation - beforeCompilation;
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | shader.bind();
|
99 | shader.attributes._p.pointer();
|
100 |
|
101 | Object.keys(shader.types.uniforms).forEach(key => {
|
102 | if (shader.types.uniforms[key] === "sampler2D") {
|
103 | shader.uniforms[key] = genericTexture.bind();
|
104 | }
|
105 | });
|
106 |
|
107 | shader.uniforms.ratio = w / h;
|
108 |
|
109 | gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
110 | const beforeDraw1 = now();
|
111 | shader.uniforms.progress = 0;
|
112 | gl.drawArrays(gl.TRIANGLES, 0, 3);
|
113 | gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
114 | const afterDraw1 = now();
|
115 | const draw1matches = [
|
116 | colorMatches(colorAt(0, 0), [0, 0, 0, 255]),
|
117 | colorMatches(colorAt(w - 1, 0), [255, 0, 0, 255]),
|
118 | colorMatches(colorAt(0, h - 1), [0, 255, 0, 255]),
|
119 | colorMatches(colorAt(w - 1, h - 1), [255, 255, 0, 255]),
|
120 | ];
|
121 |
|
122 | const beforeDraw2 = now();
|
123 | shader.uniforms.progress = 1;
|
124 | gl.drawArrays(gl.TRIANGLES, 0, 3);
|
125 | gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
126 | const afterDraw2 = now();
|
127 | const draw2matches = [
|
128 | colorMatches(colorAt(0, 0), [0, 0, 255, 255]),
|
129 | colorMatches(colorAt(w - 1, 0), [255, 0, 255, 255]),
|
130 | colorMatches(colorAt(0, h - 1), [0, 255, 255, 255]),
|
131 | colorMatches(colorAt(w - 1, h - 1), [255, 255, 255, 255]),
|
132 | ];
|
133 |
|
134 | const drawErrorMessages = [];
|
135 | if (draw1matches.some(t => !t)) {
|
136 | drawErrorMessages.push("render getFromColor(uv) when progress=0.0");
|
137 | }
|
138 | if (draw2matches.some(t => !t)) {
|
139 | drawErrorMessages.push("render getToColor(uv) when progress=1.0");
|
140 | }
|
141 | if (drawErrorMessages.length > 0) {
|
142 | errors.push({
|
143 | code: "Transition_draw_invalid",
|
144 | type: "error",
|
145 | message: "invalid transition, it must: " +
|
146 | drawErrorMessages.join(", "),
|
147 | });
|
148 | }
|
149 |
|
150 | const drawTime1 = afterDraw1 - beforeDraw1;
|
151 | const drawTime2 = afterDraw2 - beforeDraw2;
|
152 |
|
153 | data.drawTime = (drawTime1 + drawTime2) / 2;
|
154 | } catch (e) {
|
155 | let error;
|
156 | const lines = e.message.split("\n");
|
157 | for (let _i = 0, _len = lines.length; _i < _len; _i++) {
|
158 | const i = lines[_i];
|
159 | if (i.substr(0, 5) === "ERROR") {
|
160 | error = i;
|
161 | }
|
162 | }
|
163 | if (!error) {
|
164 | errors.push({
|
165 | line: 0,
|
166 | code: "WebGL_unknown_error",
|
167 | type: "error",
|
168 | message: "Unknown error: " + e.message,
|
169 | });
|
170 | } else {
|
171 | const details = error.split(":");
|
172 | if (details.length < 4) {
|
173 | errors.push({
|
174 | line: 0,
|
175 | code: "WebGL_error",
|
176 | type: "error",
|
177 | message: error,
|
178 | });
|
179 | } else {
|
180 | const lineStr = details[2];
|
181 | let line = parseInt(lineStr, 10);
|
182 | if (isNaN(line)) line = 0;
|
183 | const message = details.splice(3).join(":");
|
184 | errors.push({
|
185 | line,
|
186 | code: "WebGL_error",
|
187 | type: "error",
|
188 | message,
|
189 | });
|
190 | }
|
191 | }
|
192 | } finally {
|
193 | if (shader) shader.dispose();
|
194 | }
|
195 | return {
|
196 | data,
|
197 | errors,
|
198 | };
|
199 | };
|
200 | };
|