为什么WebGL比Canvas快?

Aca*_*uza 5 performance html5 gpu canvas webgl

如果两者都使用硬件加速(GPU)执行代码,为什么WebGL比Canvas最快?

我的意思是,我想知道为什么从代码到处理器的链处于较低水平。

怎么了?Canvas / WebGL直接与驱动程序进行通信,然后与视频卡进行通信?

gma*_*man 9

Canvas 较慢,因为它是通用的,因此很难优化到与优化 WebGL 相同的水平。让我们举一个简单的例子,用 绘制一个实心圆arc

Canvas 实际上也运行在 GPU 之上,并使用与 WebGL 相同的 API。那么,画圆的时候canvas有什么作用呢?使用 canvas 2d 在 JavaScript 中绘制圆的最小代码是

ctx.beginPath():
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.fill();
Run Code Online (Sandbox Code Playgroud)

你可以在内部想象最简单的实现是

  1. beginPath创建缓冲区 ( gl.bufferData)
  2. arc生成构成圆形的三角形的点并上传gl.bufferData
  3. fill打电话gl.drawArraysgl.drawElements

但是等一下......知道我们对 GL 工作原理的了解画布无法在第 2 步生成点,因为如果我们调用stroke而不是fill基于我们对 GL 工作原理的了解,我们需要一组不同的点实心圆(填充)与圆的轮廓(笔画)。所以,真正发生的事情更像是

  1. beginPath 创建或重置一些内部缓冲区
  2. arc 生成使圆进入内部缓冲区的点
  3. fill获取该内部缓冲区中的点,为该内部缓冲区中的点生成正确的三角形集到 GL 缓冲区中,使用gl.bufferData、调用gl.drawArraysgl.drawElements

如果我们想画 2 个圆圈会发生什么?可能会重复相同的步骤。

让我们将其与我们在 WebGL 中所做的进行比较。当然,在 WebGL 中,我们必须编写自己的着色器(Canvas 也有它的着色器)。我们还必须创建一个缓冲区并用三角形填充它作为圆,(注意我们已经节省了时间,因为我们跳过了中间的点缓冲区)。然后我们可以调用gl.drawArraysgl.drawElements绘制我们的圆圈。如果我们想画第二个圆圈?我们只是更新制服并gl.drawArrays再次调用跳过所有其他步骤。

ctx.beginPath():
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.fill();
Run Code Online (Sandbox Code Playgroud)
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;

void main() {
  gl_Position = u_matrix * position;
}
`;

const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
  gl_FragColor = u_color;
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getUniformLocation(program, 'u_color');
const matrixLoc = gl.getUniformLocation(program, 'u_matrix');

const positions = [];
const radius = 50;
const numEdgePoints = 64;
for (let i = 0; i < numEdgePoints; ++i) {
  const angle0 = (i    ) * Math.PI * 2 / numEdgePoints;
  const angle1 = (i + 1) * Math.PI * 2 / numEdgePoints;
  // make a triangle
  positions.push(
    0, 0,
    Math.cos(angle0) * radius,
    Math.sin(angle0) * radius,
    Math.cos(angle1) * radius,
    Math.sin(angle1) * radius,
  );
}

const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
                 
gl.useProgram(program);
                 
const projection = m4.ortho(0, gl.canvas.width, 0, gl.canvas.height, -1, 1);

function drawCircle(x, y, color) {
  const mat = m4.translate(projection, [x, y, 0]);
  gl.uniform4fv(colorLoc, color);
  gl.uniformMatrix4fv(matrixLoc, false, mat);

  gl.drawArrays(gl.TRIANGLES, 0, numEdgePoints * 3);
}

drawCircle( 50, 75, [1, 0, 0, 1]);
drawCircle(150, 75, [0, 1, 0, 1]);
drawCircle(250, 75, [0, 0, 1, 1]);
Run Code Online (Sandbox Code Playgroud)

一些开发人员可能会看到并认为 Canvas 缓存缓冲区,因此它可以在第二次绘制调用中重用这些点。这可能是真的,但我有点怀疑。为什么?由于画布 api 的通用性。fill,完成所有实际工作的函数不知道点的内部缓冲区中有什么。你可以打电话arc,然后moveTolineTo,然后arc再次,然后调用fill。当我们到达 时,所有这些点都将在点的内部缓冲区中fill

<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)
const ctx = document.querySelector('canvas').getContext('2d');
ctx.beginPath();
ctx.moveTo(50, 30);
ctx.lineTo(100, 150);
ctx.arc(150, 75, 30, 0, Math.PI * 2);
ctx.fill();
Run Code Online (Sandbox Code Playgroud)

换句话说,填充需要始终查看所有点。另一件事,我怀疑 arc 试图优化大小。如果你arc用半径 2调用它可能比你用半径 2000 调用它生成的点少。画布可能缓存点,但考虑到命中率可能很小,这似乎不太可能。

在任何情况下,关键是 WebGL 让您在较低级别进行控制,允许您跳过画布无法跳过的步骤。它还允许您重用画布无法重用的数据。

事实上,如果我们知道我们想要绘制 10000 个动画圆,我们甚至在 WebGL 中还有其他选择。我们可以生成 10000 个圆的点,这是一个有效的选项。我们也可以使用实例化。这两种技术都比画布快得多,因为在画布中我们必须调用arc10000 次,而且以一种或另一种方式,它必须在每一帧中为 10000 个圆生成点,而不是在开始时只生成一次,它必须调用gl.drawXXX10000 次而不是一次。

当然相反的是canvas很容易。绘制圆圈需要 3 行代码。在 WebGL 中,因为您需要设置和编写着色器,所以它可能至少需要 60 行代码。事实上,上面的例子大约有 60 行,不包括编译和链接着色器的代码(约 10 行)。除此之外,画布还支持变换、图案、渐变、蒙版等。我们必须在 WebGL 中添加更多行代码来添加所有选项。因此,canvas 基本上是通过 WebGL 来换取易用性以换取速度。


Sco*_*and 4

Canvas 不会像 OpenGL/WebGL 一样执行处理层的管道,将顶点集和索引转换为三角形,然后在硬件中为三角形提供纹理和光照...这是这种速度差异的根本原因...Canvas此类公式的对应部分均在 CPU 上完成,仅将最终渲染发送到图形硬件...当尝试在 Canvas 与 WebGL 上合成/动画大量此类顶点时,速度差异尤其明显...

唉,我们即将听到 OpenGL 的现代替代品的公开宣布: Vulkan的职责包括以比 OpenCL/CUDA 更行人的方式公开通用计算,以及使用多核处理器进行烘焙,这可能会发生变化像画布一样在硬件上进行处理