webgl与webgpu比较

绘制流程

webgl无论是操作着色器,还是操作 VBO,亦或者是创建一些 Buffer、Texture 对象,基本上都得通过 gl 变量一条一条函数地走过程,顺序是非常讲究的。每一次调用 gl.xxx 时,都会完成 CPU 到 GPU 的信号传递,改变 GPU 的状态,是立即生效的。任意一条 gl 函数改变 GPU 状态的过程,大致要走完 CPU ~ 总线 ~ GPU 这么长一段距离。

WebGPU 虽然也有一个总管家一样的对象 —— device,类型是 GPUDevice,表示可以操作 GPU 设备的一个高层级抽象,它负责创建操作图形运算的各个对象,最后装配成一个叫 “CommandBuffer(指令缓冲,GPUCommandBuffer)”的对象并提交给队列,这才完成 CPU 这边的劳动。device.createXXX 创建过程中的对象时,并不会像 WebGL 一样立即通知 GPU 完成状态的改变,而是在 CPU 端写的代码就从逻辑、类型上确保了待会传递给 GPU 的东西是准确的,并让他们按自己的坑站好位,随时等待提交给 GPU。

webgl与webgpu比较_第1张图片

GPU计算

WebGL 没有用于(GPU)计算的特殊 API,但仍然存在一种较为“hack”的方法可以实现这一功能。即将数据转换为一张图像,图像作为一个纹理上传到 GPU,随着纹理着色器不断地进行计算,纹理同时会被渲染出来。最后,我们得到的计算结果是 元素中的一组像素,我们必须用 getPixelsData 同步地读取,然后将颜色代码转换回你的数据。这看起来效率很低。

WebGPU 为(计算着色器)提供的 API 是不同的,它很容易忽略改进的重要性,但同时,它为开发者提供了全新的功能。它的工作方式是这样的:

两种方式的差异:

  1. 数据将作为缓存(buffer)上传到 GPU,你无需再将它转换成一张图片,所以它的性能开销更小
  2. 计算是异步执行的,它不会阻塞 JS 主线程(这意味着以 60 帧进行实时后置处理与复杂的物理模拟器的时代已经到来!)
  3. 我们将再也不需要创建 canvas 元素了,因此我们可以避开它对于图像尺寸的限制
  4. 我们无需做昂贵的、同步的 getPixelsData 操作
  5. 我们无需花费时间在像素转换回值数据上

WebGPU 性能明显更高,比 WebGL 快 3.5 倍有余。

数据传入

在 WebGPU 中,一切都是按字节偏移量或索引(通常称为“location”)进行的。而webgl是通过名称连接的,所以参数的顺序并不重要。
webgl与webgpu比较_第2张图片

画布

WebGL 为您管理画布。您可以在创建 WebGL 上下文时选择抗锯齿、preserveDrawingBuffer、模板、深度和 alpha,之后由 WebGL 管理画布本身。您所要做的就是设置 canvas.width和canvas.height。

WebGPU 你必须自己做很多事情。如果您想要深度缓冲区,您可以自己创建它(带或不带模板缓冲区)。如果您想要抗锯齿,您可以创建自己的多重采样纹理并将它们解析为画布纹理。但是,正因为如此,与 WebGL 不同,您可以使用一个 WebGPU device渲染到多个画布。

MipMap

在 WebGL 中,您可以创建纹理的 0 级 mip,然后调用 gl.generateMipmap,WebGL 将生成所有其他 mip 级别。WebGPU没有这样的功能。如果您想要纹理的 mip,您必须自己生成它们。

采样器

在 WebGL1 中,采样器不存在,或者换句话说,采样器由 WebGL 内部处理。在 WebGL2 中,使用采样器是可选的。在 WebGPU 中必须需要采样器。

缓冲区和纹理的大小

在 WebGL 中,我们可以创建缓冲区或纹理,然后随时更改其大小。例如,如果我们调用gl.bufferData,缓冲区将被重新分配。如果调用gl.texImage2D,纹理将被重新分配。纹理的常见模式是创建一个 1x1 像素占位符,然后可以立即开始渲染,然后异步加载图像。当图像加载完成后,我们可以就地更新纹理。

在 WebGPU 中,纹理和缓冲区大小、用法、格式是不可变的。我们可以更改它们的内容,但无法更改有关它们的任何其他内容。这意味着,要更改的 WebGL 中的模式(如上面提到的示例)需要重构以创建新资源。

// webgpu模式伪代码
let tex = createTexture(size: [1, 1]);
fillTextureWith1x1PixelPlaceholder(tex)
imageLoad(url).then(img => {
  tex.destroy();  // 删除旧纹理
  tex = createTexture(size: [img.width, img.height]);
  copyImageToTexture(tex, image));
});

在窗口画布大小改变时,webgl会自动采样处理,而webgpu需要我们手动销毁旧纹理(颜色和深度)并创建新纹理,属性sampleCount实际上是WebGL上下文创建属性的抗锯齿属性的模拟。在创建 WebGL 上下文时,sampleCount: 4 相当于 WebGL 的 antialias: true (默认值)或者说是MSAA,而sampleCount: 1 相当于 antialias: false

const newRenderTarget = device.createTexture({
        size: [canvas.width, canvas.height],
        format: presentationFormat,
        sampleCount,
        usage: GPUTextureUsage.RENDER_ATTACHMENT,
      });
      canvasInfo.renderTarget = newRenderTarget;

WebGL 会尽量不耗尽内存,这意味着如果请求 16000x16000 画布,WebGL 可能会返回 4096x4096 画布。WebGL 这样做的原因是 (1) 在多个显示器上拉伸画布可能会使尺寸大于 GPU 可以处理的大小 (2) 系统内存可能不足,WebGL 会返回较小的绘图缓冲区,而不仅仅是崩溃。
在 WebGPU 中,我们必须自己检查内存是否不足,并且与 WebGPU 中的其他所有内容一样,这样做是异步的。

device.pushErrorScope('out-of-memory');
context.configure({...});
if (sampleCount > 1) {
  const newRenderTarget = device.createTexture({...});
  ...
}
 
const newDepthTexture = device.createTexture({...});
...
device.popErrorScope().then(error => {
  if (error) {
    // 内存不足,尝试较小的大小
  }
});

Shader

代码风格

wgsl偏rust风格,glsl偏c风格。WGSL 的概念是,如果不指定变量的类型,它将根据右侧表达式的类型推导出来,而 GLSL 要求始终指定类型。

入口函数

WebGL 和 WebGPU 之间的另一个区别是,在 WebGPU 中,我们可以将多个着色器放在同一个代码块中。在 WebGL 中,始终会调用着色器的入口main函数,但在 WebGPU 中,当使用着色器时,可以指定要调用的函数。

编译

webgpu中可以一次性编译多个shader。

const shaderModule = device.createShaderModule({code: shaderSrc});

在 WebGL 中,如果着色器未编译,则需要通过gl.getShaderParameter检查_ COMPILE_STATUS_,如果编译失败,需要通过调用 gl.getShaderInfoLog 来提取错误消息。如果不这样做,则不会显示任何错误。

在 WebGPU 中,大多数实现都会将错误打印到 JavaScript 控制台。当然,仍然可以自己检查错误,但即使什么都不做,仍然可以获得一些有用的信息。

Program与Pipeline

WebGL 中发生的几件事将合并为 WebGPU 中的一件事情。例如,链接着色器、设置属性参数、选择绘制模式(点、线、三角形)、设置如何使用深度缓冲区。
createProgram => createRenderPipeline

Uniform

在 WebGL 情况下,我们计算一个值并将其传递到gl.uniform???适当的位置。

在 WebGPU 的情况下,我们将值写入类型化数组,然后将这些类型化数组的内容复制到相应的 GPU 缓冲区。

坐标

在 WebGL NDC空间中 Z 为 -1 到 +1;在 WebGPU 中,它是 0 到 1。
https://gpuweb.github.io/gpuweb/#coordinate-systems
纹理坐标(0, 0, 1, 1)在 WebGL 中引用左下角,但在 WebGPU 中引用左上角。

WGSL

gl_FragCoord是@builtin(position),中0,0 是左上角,而 WebGL 中 0,0 是左下角
gl_VertexID 是 @builtin(vertex_index) myVarOrField: u32
gl_InstanceID 是 @builtin(instance_index) myVarOrField: u32
gl_Position 是 @builtin(position) vec4f ,它可以是顶点着色器的返回值或顶点着色器返回的结构体中的字段
没有 gl_PointCoord 等价物,因为点在 WebGPU 中只有 1 个像素
没有#ifdef宏定义,需要用常量覆盖。

参考资料

  1. WebGPU from WebGL

你可能感兴趣的:(webgpu,webgl,webgl,webgpu)