【问题标题】:Why WebGL is faster than Canvas?为什么 WebGL 比 Canvas 快?
【发布时间】:2015-05-06 04:47:03
【问题描述】:

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

我的意思是,我想知道为什么在底层,从代码到处理器的链。

会发生什么? Canvas/WebGL 直接与驱动程序通信,然后与显卡通信?

【问题讨论】:

  • WebGL 是 OpenGL 的子集,与 DirectX 无关。另外我不知道如何回答这个问题,因为 WebGL 通过 <canvas> 元素工作,所以我不知道你想将它与什么进行比较......
  • 我认为您应该删除 DirectX 部分,因为我们会根据 DX 版本 + gpu 得到不同的结果。除了在 Linux 中没有 DX,但我们仍然有 canvas 和 gl。所以宁愿它是WEB - 浏览器 - 驱动程序 - gpu。如果浏览器使用 DX,则需要安装它(我没有将其视为要求)并且怀疑 MS 是否允许嵌入(甚至可能吗?) DX
  • @DaGhostmanDimitrov 在 Windows 机器上,WebGL 通过 DirectX 执行(自 XP SP2 起包含在 Windows 中)使用 ANGLE project
  • @jPlatte 看我上面的评论
  • @LJ_1102 我看起来是不是很笨...谢谢我不知道 :)

标签: html performance canvas webgl gpu


【解决方案1】:

Canvas 速度较慢,因为它是通用的,因此很难优化到可以优化 WebGL 的水平。举个简单的例子,用arc画一个实心圆。

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

ctx.beginPath():
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.fill();

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

  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 has its shaders as well)。我们还必须创建一个缓冲区并用三角形填充一个圆圈,(注意我们已经节省了时间,因为我们跳过了点的中间缓冲区)。然后我们可以调用gl.drawArraysgl.drawElements 来绘制我们的圆圈。如果我们想画第二个圆圈?我们只需更新制服并再次调用gl.drawArrays 即可跳过所有其他步骤。

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]);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

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

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();
&lt;canvas&gt;&lt;/canvas&gt;

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

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

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

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

【讨论】:

    【解决方案2】:

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

    唉,我们正处于听到现代替代 OpenGL 的公告的风口浪尖上:Vulkan,他的职责包括以比 OpenCL/CUDA 更普通的方式公开通用计算以及使用多核进行烘焙处理器可能只是将 Canvas 之类的处理转移到硬件上

    【讨论】:

    • Canvas 处理已经在硬件中包含在包括 Safari 和 Chrome 在内的许多实现中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-16
    • 2013-07-12
    • 2011-09-28
    • 2019-11-02
    • 1970-01-01
    • 2015-07-24
    相关资源
    最近更新 更多