JavaScript 如何实现 gpu 加速? 下面本篇文章给大家介绍一下. 有一定的参考价值, 有需要的朋友可以参考一下, 希望对大家有所帮助.
一, 什么是 JavaScript 实现 GPU 加速?
CPU 与 GPU 设计目标不同, 导致它们之间内部结构差异很大.
CPU 需要应对通用场景, 内部结构非常复杂.
而 GPU 往往面向数据类型统一, 且相互无依赖的计算.
所以, 我们在 web 上实现 3D 场景时, 通常使用 WebGL 利用 GPU 运算 (大量顶点).
但是, 如果只是通用的计算场景呢? 比如处理图片中大量像素信息, 我们有办法使用 GPU 资源吗? 这正是本文要讲的, GPU 通用计算, 简称 GPGPU.
二, 实例演示: 色块识别.
如下图所示, 我们识别图片中彩虹糖色块, 给糖果添加表情.
2.1, 实例地址 (打开页面后, 依次点击按钮 "使用 CPU 计算","使用 GPU 计算"):
http://tgideas.qq.com/2018/brucewan/gpgpu.html
2.2, 运行代码:
- var rgb2hsv = function(r, g, b) {
- var max = Math.max(r, g, b), min = Math.min(r, g, b),
- d = max - min,
- h,
- s = (max === 0 ? 0 : d / max),
- v = max / 255;
- switch (max) {
- case min: h = 0; break;
- case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break;
- case g: h = (b - r) + d * 2; h /= 6 * d; break;
- case b: h = (r - g) + d * 4; h /= 6 * d; break;
- }
- return {
- h: self.hueIndexs[parseInt(h*360)],
- s: s,
- v: v
- }
- };
运行次数: 262144 次
2.3, 测试结论:
实例中, 我们分别使用 GPU 和 CPU 进行色相转换 (防止光线影响识别准确度), 其余步骤均一致.
测试平台 | 测试结论 |
PC | GPU 较 CPU 优势较少 |
iOS | GPU 较 CPU 优势较少 |
Android | vivoX20(运行 10 次平均) CPU:770ms,GPU:270 GPU 较 CPU 快 2.85 倍 < br ztid="92" ow="0" oh="0"> 三星 S7(运行 10 次平均) CPU:982ms,GPU:174ms GPU 较 CPU 快 5.64 倍 |
2.4, 使用 GPGPU 意义:
GPU 与 CPU 数据传输过程, 与 GPU 实际运算耗时相当, 所以使用 GPU 运算传输成本过高, 实测在 Android 中具有较大优势.
本测试案例是从 webAR 项目中抽取, 需要实时跟踪用户摄像头处理视频流 (256*256), 使用 GPU 计算意义非常大, 否则无法实现实时跟踪.
三, 如何实现 GPU 通用计算?
3.1, 首先, 我们通过一张流程图, 演示原理:
3.2, 实现:
3.2.1, 创建顶点着色器, 只是传递了贴图坐标.
- attribute vec4 position;
- varying vec2 vCoord;
- void main() {
- vCoord = position.xy * 0.5 + 0.5;
- gl_Position = position;
- }
3.2.2, 创建片元着色器, 根据贴图坐标贴图.
- precision highp float;
- varying vec2 vCoord;
- uniform sampler2D map;
- void main(void) {
- vec4 color = texture2D(map, vCoord);
- gl_FragColor = color;
- }
3.3.3, 根据如上着色器代码, 创建程序对象, 变量 code 是我们要传入的用于计算的代码.
- // 绑定并编译着色器程序
- var vertexShaderSource = '...';
- var fragmentShaderSource = '...' + code + '...';
- var vertexShader = gl.createShader(gl.VERTEX_SHADER);
- gl.shaderSource(vertexShader, vertexShaderSource);
- gl.compileShader(vertexShader);
- var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
- gl.shaderSource(fragmentShader, fragmentShaderSource);
- gl.compileShader(fragmentShader);
- // 创建程序对象
- var program = gl.createProgram();
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.linkProgram(program);
- gl.useProgram(program);
3.3.4, 传入顶点数据, 创建一个面覆盖整个画布.
- // 顶点数据传输
- var vertices = new Float32Array([-1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0]);
- var vertexBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
- var aPosition = gl.getAttribLocation(program, 'position');
- gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(aPosition);
3.3.5, 传入原始数据, 本例中传入我要处理的图像数据, 作为贴图, 最终绘制到屏幕.
- var gl = this.gl;
- var program = this.program;
- var texture = gl.createTexture();
- var uMap = gl.getUniformLocation(program, 'map');
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.generateMipmap(gl.TEXTURE_2D);
- gl.uniform1i(uMap, 0);
- // 绘制
- gl.clearColor(0, 0, 0, 1);
- gl.clear(gl.COLOR_BUFFER_BIT);
- gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
3.3.6, 从最终绘制的画面上, 获取颜色信息作为最终处理结果数据.
- var pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
- gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA,
- gl.UNSIGNED_BYTE, pixels);
3.3.7, 完整代码:
http://tgideas.qq.com/2018/brucewan/gpu.js
其实清楚原理后, 整体实现比较简单.
但是对于不了解 WebGL 的同学来说, 理解上有一定难度, 我后续准备写一个系列的 WebGL 教程, 有兴趣的同学可以关注.
四, 有无现成类库?
大家可以看到, 我实现的 gpu.JS 中, 并没有将 JavaScript 转换成着色器语言 (类 C), 而是用户直接传入着色器代码. 但是 GitHub 上已有将 JavaScript 转换为着色器语言的库.
https://github.com/gpujs/gpu.js
为什么我没有直接使用呢?
1, 简单的使用, 2k 可以实现的代码, 不想引入 200k 的库;
2, 数据输入输出可以由自己灵活控制;
3, 着色器语言很简单, 特别只是使用基础运算逻辑的代码, 没必要由库从 JavaScript 转换.
没有 WebGL 基础的同学, 建议直接使用 https://github.com/gpujs/gpu.js, 从本文理解整体逻辑;
有一定基础的同学, 建议由 http://tgideas.qq.com/2018/brucewan/gpu.js 自己定制, 更为灵活.
另外, 这个组件我没打算深度封装, 也没打算维护...... 嗯, 就这样.
更多 Web 前端开发 https://www.html.cn/ 知识, 请查阅 HTML 中文网 !!
来源: http://www.css88.com/qa/javascript/17331.html