GPU画布上下文(GPUCanvasContext)用于提供画布(canvas)的 WebGPU 渲染上下文,其本身相当于 WebGPU 中的一个纹理,可以将 GPU 渲染的结果显示到画布(网页)当中。
如果想在 <Canvas />
元素上绘制 2D 内容,那么首先需要得到一个 2D 画布的一个渲染上下文,代码如下:
const canvas = document.querySelector('#mycanvas')
const ctx = canvas.getContext('2d')
在 WebGL 时代,如果我们使用 Three.js 绘制 3D 场景,对于我们使用者而言是直接将 画布元素(canvas) 作为参数传递给 WebGLRenderer 的。
在 WebRenderer 内部,他会依次尝试使用 "webgl2"、"webgl"、"experimental-webgl" 来获取 画布 WebGL 上下文。
“2d” 得到的是一个 CanvasRenderingContext2D 实例
“webgl” 得到的都是一个 WebGLRenderingContext 实例
而此刻,获取一个 GPU 画布上下文的方式和上面类似,就是把 .getContext() 的第一个参数写成 "webgpu" 。
const canvas = document.querySelector('#mycanvas')
const ctx = canvas.getContext('webgpu')
这样我们就得到一个 GPU画布上下文(GPUCanvasContext) 实例。
补充:对于 WebGL 而言,.getContext() 第 2 个参数用于设置 画布上下文的一些属性,例如:
- alpha:表明画布是否背景透明
- antialias:表明画布是否开启抗锯齿
- ...
但这些对于 WebGPU 而言,是不需要设置第 2 个参数的。
若想获得 离屏画布(OffscreenCanvas) 的 GPU 画布上下文,则代码如下:
const offscreenCanvas = new OffscreenCanvas(400,300)
const ctx = offscreenCanvas.getContext('webgpu')
注:目前 火狐 和 苹果浏览器是不支持 离屏画布 的。
我不清楚是不是因为这个原因(或许根本不是这个原因),目前在 TS (@webgpu/types) 环境下使用 OffscreenCanvas 时会提示错误。但是在原生 JS 环境下是可以正常运行的。
Error: 'OffscreenCanvas' only refers to a type, but is being used as a value here.
这段话来源于
四季留歌
的这篇文章:https://juejin.cn/post/7010673087586762766
GPUCanvasContext 与 WebGLRenderingContext 的最大不同点在于,GPUCanvasContext 仅仅作为 Canvas 与 WebGPU 沟通的桥梁,其自身相当于 WebGPU 中的一个纹理,除此之外再无别的其他作用。
但是 WebGLRenderingContext 除了上述职责外,还包括创建 WebGL 子对象、绑定、编译着色器、触发渲染等等。
这实际上也解释了为什么 .getContext("webgpu") 不需要第 2 个参数的原因,因为它(GPUCanvasContext) 根本并不负责 渲染相关操作,自然也不需要进行渲染相关的配置。
GPUCanvasContext 只有一个 .canvas 的只读属性,用于获取上下文对应的实际 canvas 元素。
readonly canvas: HTMLCanvasElement | OffscreenCanvas;
getPreferredFormat():获取当前系统首选的纹理格式
getPreferredFormat(
adapter: GPUAdapter
): GPUTextureFormat;
通常情况下,我们系统首选的纹理格式为 “bgra8unorm”。
configure():配置画布的上下文
注意,配置画布的上下文是本文的学习重点。
configure(
configuration: GPUCanvasConfiguration
): undefined;
interface GPUCanvasConfiguration {
device: GPUDevice;
format: GPUTextureFormat;
usage?: GPUTextureUsageFlags;
viewFormats?: Iterable<GPUTextureFormat>;
colorSpace?: GPUPredefinedColorSpace;
compositingAlphaMode?: GPUCanvasCompositingAlphaMode;
size?: GPUExtent3D;
}
下面逐个介绍一下各个配置项。
device:与此画布上下文关联的 GPU设备(GPUDevice)
注:我个人把 GPUDevice 翻译为 显卡设备,但实际上我们知道这个 “设备” 并不真的是系统的 GPU 硬件设备,而是 WebGPU 为了适配不同 GPU 而做出来的一个 “虚构” 的设备。
我看某个人的文章,他把 GPUDevice 翻译为 “GPU驱动”... 额,无论翻译成什么,你明白其中含义即可。
format:设定 纹理(实际)格式,一般情况下我们都会将其设置为系统首选纹理格式
const textformat = ctx.getPreferredFormat(adapter) //先得到首选纹理格式
//将首选纹理格式作为 画布上下文 的纹理配置参数值
ctx.configure({
...
textform
...
})
usage:纹理用途(usage),默认为 0x10,表明是用作颜色附件的
viewFormats:设定纹理 除了实际格式(format)以外,调用 createView() 时其他被允许的格式
colorSpace:画布的颜色空间,该值目前只支持 "srgb"
compositingAlphaMode:合成到画布上的透明选项,默认为 “opaque” 即 不透明
type GPUCanvasCompositingAlphaMode = "opaque" | "premultiplied";
"opaque" 表示不透明,会忽略 alpha 原有的值,换句话说相当于把 alpha 的值全部设置为 1
"premultiplied" 表示 颜色透明度预乘
假设原有颜色值为 src,最终颜色值为 dst,那么 预乘 需要满足的公式为:
|dst.rgb = src.rgb + dst.rgb*(1-src.a)|
注:上面颜色的 4 个分量 r g b a 他们的取值范围都是 0 ~ 1
特别补充:在较早之前 compositingAlphaMode 的默认值是 “premultiplied”,而不是现在的 "opaque"。
对于目前而言(谷歌浏览器 102 版本),如果你没有明确配置 compositingAlphaMode 的值,那么在浏览器调试时会收到一个警告信息:
The default GPUCanvasCompositingAlphaMode will change from "premultiplied" to "opaque". Please explicitly pass "premultiplied" if you would like to continue using that compositing mode.
尽管我们知道其默认值已经是 "opaque" 了,但是在配置画布上下文时,还是选择主动加上
compositingAlphaMode: 'opaque'
,就可以消除这句警告信息了。
以下内容来源于
LaoYuanPython
的一篇文章:图像处理术语解释:什么是PRGBA和Alpha预乘(Premultiplied Alpha )https://blog.csdn.net/LaoYuanPython/article/details/106772363
名词解释:alpha预乘
对于 4 通道 rgba 格式的图像(颜色)在合成时存在 2 个问题:
在图像合成时需要对 r g b 通道应用 alpha 通道 ,这会造成 合成 时处理压力较大
在图像合成时可能需要进行 插值处理,但一般的插值计算都只是在 r g b 这 3 个通道中进行的,而带有 alpha 通道的图像的 r g b 并不是最终的 r g b 颜色
为了解决这个问题,在 图像处理 过程中引入了 预乘(premultiplied) 这个概念,经过预乘处理的图片格式称为 prgba。保存的数据通常称为(ar,ag,ab,a),这样即保存了真正展现时的像素 rgb 值,又保存了 alpha 通道值。
由于 预乘 计算后保存的值是整数,会丢失小数点后的数值,因此会存在偏差,其中当 r g b 的值很小但 alpha 的值很大时,该误差会比较大。
关于 预乘 在 WebGPU 官方文档中有这样一句话:颜色值(rgb) 应该小于或等于他们的 alpha 值。例如 [1.0,0,0,0.5] 是 “超亮”,无法可靠显示。
size:画布的像素点数量
size?: GPUExtent3D;
type GPUExtent3D = Iterable<GPUIntegerCoordinate> | GPUExtent3DDict;
type GPUIntegerCoordinate = number;
interface GPUExtent3DDict {
width: GPUIntegerCoordinate;
height?: GPUIntegerCoordinate;
depthOrArrayLayers?: GPUIntegerCoordinate;
}
你可以有 2 种设置 size 的方式:
注:这里说的画布宽高是指 canvas 元素实际的宽高,而不是指通过 css 控制画布的宽高
{
/** size: [canvas.clientWidth, canvas.clientHeight] */
size: [400, 300]
}
//或者
{
size: {
width: canvas.clientWidth,
height: canvas.clientHeight
}
}
对于高清屏,那么可能需要将上述代码中 画布的宽高对应的像素数量 修改为:
- canvas.clientWidth
+ canvas.clientWidth * window.devicePixelRatio
- canvas.clientHeight
+ canvas.clientHeight * window.devicePiexRatio
你会发现上述配置项中 format、usage、size 这些都是创建 纹理(GPUTexture) 时需要的配置项。
从侧面也印证了那个结论:GPUCanvasContext 相当于 WebGPU 中的一个纹理。
当每次通过 .configure() 修改画布上下文配置后,都会立即销毁之前 配置 所产生的纹理。
再次提醒:GPUCanvasContext 仅仅相当于 WebGPU 中的一个特殊纹理。
unconfigure():销毁画布上下文的配置
unconfigure(): undefined;
执行该函数后,则会立即销毁此前 画布上下文 产生的任何纹理。
getCurrentTexture():获取当前通过 GPUCanvasContext 即将要合并到文档中的纹理
getCurrentTexture(): GPUTexture;
在 英文版官方文档中,这句的原话是:Get the GPUTexture that will be composited to the document by the GPUCanvasContext
以上就是 GPU 画布上下文(GPUCanvasContext) 的全部内容。
在 WebGPU 运行的过程中,可能会发生一些意外错误。
GPUDevice.lost:GPU丢失错误(不可用)
readonly lost: Promise<GPUDeviceLostInfo>;
interface GPUDeviceLostInfo {
readonly __brand: "GPUDeviceLostInfo";
readonly reason: GPUDeviceLostReason | undefined;
readonly message: string;
}
type GPUDeviceLostReason = "destroyed";
我们可以向 GPUDevice 实例的 .lost 添加错误侦听。
device.lost.catch((err) => {
console.log(err)
})
GPU 丢失属于致命性的错误。若系统 GPU 都突然不可用了,那还搞什么 WebGPU。
GPUOutOfMemoryError:GPU内存不足错误
interface GPUOutOfMemoryError {
readonly __brand: "GPUOutOfMemoryError";
}
GPUValidationError:GPU验证错误
interface GPUValidationError {
readonly __brand: "GPUValidationError";
readonly message: string;
}
错误范围(gpu error scope)
错误范围用于隔离一组 WebGPU 调用中发生的错误,通常用于调试或使操作更具容错性。
在讲解 GPUDevice 时提到过,GPUDevice 实例 有 3 个和错误相关的方法:
-
pushErrorScope():在结尾处 添加(push) 一个错误范围
pushErrorScope( filter: GPUErrorFilter ): undefined; type GPUErrorFilter = "out-of-memory" | "validation";
-
popErrorScope():在结尾处 删除并返回 最后一个错误范围
popErrorScope(): Promise<GPUError | null>; type GPUError = GPUOutOfMemoryError | GPUValidationError;
-
onuncapturederror:当发生错误(GPUError) 但未被任何 错误范围 观察到时
onuncapturederror: (( this: GPUDevice, ev: GPUUncapturedErrorEvent ) => any) | null; interface GPUUncapturedErrorEvent extends Event { readonly __brand: "GPUUncapturedErrorEvent"; readonly error: GPUError; }
--------- 伟大的分割线 ---------
到此刻为止,我们终于把 WebGPU 绝大多数模块和类学习一遍了,第一阶段完成了!
尽管第一阶段花费了很多时间,尽管很多类的概念、用法 我们依然不是那么清晰,但是有了这些基础,相信下一阶段会通顺起来的。
地基打得深,大楼才能盖得高。
接下来,我们将开始编写看得见的 WebGPU 示例。
Hello world 我来啦!