-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWebGL.html
359 lines (318 loc) · 13.4 KB
/
WebGL.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebGL</title>
<script src="https://cdn.jsdelivr.net/npm/@assemblyscript/loader/umd/index.js"></script>
</head>
<body>
<script type="module">
(async function main() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
document.body.append(canvas);
const t0 = performance.now();
let success;
const VS_SHADER = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
`;
const FS_SHADER = `
precision mediump float;
void main() {
gl_FragColor = vec4(0, 0.5, 1, 1);
}
`;
// 先回忆一下怎么绘制三角形的。。。这东西不使用就会忘记的了
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, VS_SHADER);
gl.compileShader(vs);
success = gl.getShaderParameter(vs, gl.COMPILE_STATUS);
if (!success)
return console.log(
'compile vs shader fail',
gl.getShaderInfoLog(vs),
);
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, FS_SHADER);
gl.compileShader(fs);
success = gl.getShaderParameter(fs, gl.COMPILE_STATUS);
if (!success)
return console.log('compile fs shader fail', gl.getShaderInfoLog(fs));
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success)
return console.log(
'link program fail',
gl.getProgramInfoLog(program),
);
const positionLocation = gl.getAttribLocation(program, 'a_position');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([0, 0.5, -0.5, 0, 0.5, 0]),
gl.STATIC_DRAW,
);
gl.useProgram(program);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
const t1 = performance.now();
for (let i = 0; i < 1_000_000; i++) {
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
const t2 = performance.now();
console.log('t1 - t0', t1 - t0);
console.log('t2 - t1', t2 - t1);
console.log('t2 - t0', t2 - t0);
// 这是最基本的webgl使用方式,其实就是一堆修改webgl状态机的API
})();
</script>
<script type="module">
import * as AsBind from './node_modules/as-bind/dist/as-bind.esm.js';
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
document.body.append(canvas);
const memory = new WebAssembly.Memory({ initial: 64 });
let wasmExports;
const FN_LIST = [
'createShader',
'shaderSource',
'compileShader',
'getShaderParameter',
'getShaderInfoLog',
'createProgram',
'attachShader',
'linkProgram',
'getProgramParameter',
'getProgramInfoLog',
'getAttribLocation',
'createBuffer',
'bindBuffer',
'bufferData',
'useProgram',
'enableVertexAttribArray',
'drawArrays',
'vertexAttribPointer',
];
const FN_CUSTOM_PARSER = {
shaderSource: {
parseArgs(args) {
args[0] = parseGLCallArg(args[0]);
args[1] = wasmExports.__getString(args[1]);
return args;
},
parseReturn: parseGLCallReturn,
},
getAttribLocation: {
parseArgs(args) {
args[0] = parseGLCallArg(args[0]);
args[1] = wasmExports.__getString(args[1]);
return args;
},
parseReturn: parseGLCallReturn,
},
getShaderInfoLog: {
parseArgs(args) {
args[0] = parseGLCallArg(args[0]);
return args;
},
parseReturn: parseString,
},
getProgramInfoLog: {
parseArgs(args) {
args[0] = parseGLCallArg(args[0]);
return args;
},
parseReturn: parseString,
},
bufferData: {
parseArgs(args) {
args[1] = parseArrayView(args[1]);
return args;
},
parseReturn: parseGLCallReturn,
},
};
// 无法传递的数据需要新建mapping
// 所以叫做binding,数据状态的实际存储均是js层,暴露给wasm是等效的操作,但是同时是native也是这样暴露操作方式给js也是一个binding
// 这个binding应该是批量实现的,要么就是分类,一个是默认转发的另一个是固定转发,但是需要很多类型的切换
// 那么如果是一样的话,也是一对一的转发了,不能实现成批量转发看看是否支持overload, 貌似不支持函数的overload,即便支持了,也会有非常多的类型,一样不好实现
// webgl的类型能做映射,但是字符串就尴尬了,返回的是一个offset地址,都是number类型
// 感觉as还需要生成js类型转换部分逻辑,现在这部分需要手动判断处理,非常不利于批量的转换机制
// 需要反射类,知道gl api的参数类型,返回类型,这样也是可以实现通用的数据格式转换机制, 不过涉及字符串的API比较少,所以还可以接受
// 需要定制格式化的类型有 StaticArray(很多) 和 string(6) DOMString
// 虽然性能上面会比手写一一对应的慢一些,但是机制化,代码量少,貌似还是慢不少的感觉。。。
// 还是需要使用as-bind比较好,额外的开销并不大
const WebGLClassInstanceMap = {};
const WebGLClassArray = [
'WebGLActiveInfo',
'WebGLBuffer',
'WebGLContextEvent',
'WebGLFramebuffer',
'WebGLProgram',
'WebGLQuery',
'WebGLRenderbuffer',
'WebGLRenderingContext',
'WebGLSampler',
'WebGLShader',
'WebGLShaderPrecisionFormat',
'WebGLSync',
'WebGLTexture',
'WebGLTransformFeedback',
'WebGLUniformLocation',
'WebGLVertexArrayObject',
];
let WebGLClassArrayMap = {};
WebGLClassArray.forEach((v, k) => {
WebGLClassArrayMap[v] = k;
WebGLClassInstanceMap[v] ??= [];
});
const MAGIC_HEAD = 0b111 << 29;
function parseString(strPtr) {
return wasmExports.__getString(strPtr);
}
function parseArrayView(ptr) {
return wasmExports.__getArrayView(ptr);
}
let parseGLCallArgCost = 0;
function parseGLCallArg(arg) {
// const t = performance.now();
if (
// typeof arg === 'number' &&
Number.isInteger(arg) &&
arg & 0b11100000000000000000000000000000
) {
const classIndex = (arg & 0b00011111000000000000000000000000) >> 24;
const instanceIndex = arg & 0b00000000111111111111111111111111;
if (classIndex < WebGLClassArray.length) {
return WebGLClassInstanceMap[WebGLClassArray[classIndex]][
instanceIndex
];
}
}
// parseGLCallArgCost += performance.now() - t;
return arg;
}
let parseGLCallReturnCost = 0;
let WebGLClassArrayFindCost = 0;
let WebGLClassInstanceNameCost = 0;
function parseGLCallReturn(ret) {
// const t = performance.now();
// find太耗时了
// const WebGLClassInstanceName = WebGLClassArray.find(
// i => ret instanceof window[i],
// );
if (
// ret instanceof Object &&
typeof ret === 'object' &&
ret.constructor.name.indexOf('WebGL') === 0
// ret.constructor.name.startsWith('WebGL')
) {
const WebGLClassInstanceName = ret.constructor.name;
// WebGLClassArrayFindCost += Date.now() - t;
if (WebGLClassInstanceName) {
// const t = Date.now();
// const classIndex = WebGLClassArray.indexOf(WebGLClassInstanceName);
const classIndex = WebGLClassArrayMap[WebGLClassInstanceName];
const instanceIndex =
WebGLClassInstanceMap[WebGLClassInstanceName].push(ret) - 1;
// 3位 5位 24位
// 标识符 instanceIndex classIndex
// 使用位移运算出来结果
// prettier-ignore
// const binaryStr = `111${classIndex.toString(2).padStart(5, '0')}${instanceIndex.toString(2).padStart(24, '0')}`;
// ret = parseInt(binaryStr, 2);
ret = 0b11100000000000000000000000000000 | (classIndex << 24) | instanceIndex;
// WebGLClassInstanceNameCost += Date.now() - t;
}
}
// parseGLCallReturnCost += performance.now() - t;
return ret;
}
loader
// AsBind
.instantiate(
fetch('./build/webgl.wasm').then(i => i.arrayBuffer()),
{
env: { memory },
webgl: {
'console.log'(strPtr) {
console.log(parseString(strPtr));
},
...FN_LIST.reduce((acc, fnName) => {
const glFnName = 'gl.' + fnName;
if (FN_CUSTOM_PARSER[fnName]) {
acc[glFnName] = function () {
const argsParsed =
FN_CUSTOM_PARSER[fnName].parseArgs(arguments);
const ret = FN_CUSTOM_PARSER[fnName].parseReturn(
gl[fnName](...argsParsed),
// gl[fnName].apply(gl, argsParsed),
);
// console.log('gl', fnName, argsParsed, ret);
// const error = gl.getError();
// if (error !== gl.NO_ERROR) console.error(error);
return ret;
};
} else {
acc[glFnName] = function () {
const argsParsed = new Array(arguments.length);
for (let i = 0; i < arguments.length; i++) {
argsParsed[i] = parseGLCallArg(arguments[i]);
}
// const argsParsed = args.map(parseGLCallArg);
const ret = parseGLCallReturn(gl[fnName](...argsParsed));
// const ret = parseGLCallReturn(gl[fnName].apply(gl, argsParsed));
// console.log('gl', fnName, argsParsed, ret);
// const error = gl.getError();
// if (error !== gl.NO_ERROR) console.error(error);
return ret;
};
}
if (fnName === 'bufferData') {
acc[glFnName + '<f32>'] = acc[glFnName];
acc[glFnName + '<f64>'] = acc[glFnName];
acc[glFnName + '<i32>'] = acc[glFnName];
}
return acc;
}, {}),
},
},
)
.then(({ exports }) => {
wasmExports = exports;
const t0 = performance.now();
exports.main();
const t2 = performance.now();
console.log('t2 - t0', t2 - t0);
console.log('parseGLCallArgCost', parseGLCallArgCost);
console.log('parseGLCallReturnCost', parseGLCallReturnCost); // 24 目前的实现太耗时了
console.log('WebGLClassInstanceNameCost', WebGLClassInstanceNameCost);
console.log('WebGLClassArrayFindCost', WebGLClassArrayFindCost); // 12 主要因为用find + instanceof 替换掉就好了,能直接获取
// 加上细分性能打点会非常耗时,取消性能打点之后耗时大幅下降, 是值得留意一个现象
// 只保留 最开始结尾 和 wasm内,耗时只有4.5 最低
// 只保留 最开始结尾,耗时只有3.5 最低
// 3.5 / 1000 也就是平均每个gl call 的耗时增加是很小的,但是循环多起来,累加多起来就会比较明显
// 如果想要避免wasm2js的耗时,估计就是减少这类的调用,或者batch的方式
// 后面需要尝试command buffer的方式,或者wasm是结构运算部分
// https://zhuanlan.zhihu.com/p/102692865 里也有人提到用command buffer来优化了
// 新发现!把这3行移出循环外,WASM耗时稳定低于JS,wasm稳定470 ~ 490 JS最低490,但是优势600+ 700+ 800+
// gl.useProgram(program);
// gl.enableVertexAttribArray(positionLocation);
// gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, 0, 0, 0);
// 循环里的JS CALL合并成一个可能会更好??说到底还是batch的方式
// 本来以为wasm 体积上面会有优势,但是发现并没有,可能目前gl api的也占用了不少体积吧
// 可以使用as-bind实现基础数据的互通string statictype array
// 先尝试提个PR 给as-bind吧 done 没想到这么简单就支持了
});
</script>
</body>
</html>