WebGPU基本对象及其创建摘要本文详细介绍了 WebGPU 核心对象的创建流程与作用。首先通过浏览器 Navigator.gpu 检测支持性并获取 GPUAdapter进而创建 GPUDevice 作为与 GPU 的逻辑连接。GPUDevice 用于创建各类资源缓冲区、纹理、管线等及 GPUCommandEncoder 来编码指令。指令缓冲区通过 GPUQueue 提交执行。若需图形渲染还需将 GPUDevice 与 HTML Canvas 元素关联获取 Canvas 上下文并配置纹理格式。文章最后给出了完整的初始化代码示例涵盖从检测支持到画布配置的全过程。Part 1WebGPU的因承和编程范式 目录总体流程NavigatorGPUDeviceGPUCommandEncoderGPUQueueCanvas元素Canvas Context一个完整的初始化流程总体流程设备 GPU 和运行 WebGPU API 的 Web 浏览器之间有几个抽象层。在开始学习 WebGPU 时了解这些抽象层很有用WebGPU操作的核心范式是将指令和数据打包为指令缓冲区command buffer对象提交到与GPU关联的队列queue中执行。完成这一核心流程至少需要以下六个步骤访问浏览器的Navigator对象检查其是否支持WebGPU若支持通过Navigator获取GPUAdapter适配器使用GPUAdapter获取GPUDevice设备的逻辑连接使用GPUDevice创建GPUCommandEncoder指令编码器配置该编码器将具体指令录入到指令缓冲区将指令缓冲区提交到与GPUDevice关联的GPUQueue。如果应用还需要在页面中进行图形渲染而不仅仅是通用计算则还需要将GPUDevice与页面中的canvas元素关联起来这需要额外的三个步骤在HTML文档中访问canvas元素通过该元素获取GPUCanvasContext画布上下文使用GPUDevice及所需的纹理格式对GPUCanvasContext进行配置。完成上述关联后应用便可以通过GPUDevice发出的渲染指令将图形结果绘制到画布上。下面依次介绍流程中涉及的各个核心对象。Navigator现代浏览器都提供全局的Navigator对象用于暴露浏览器自身的能力信息。例如clipboard属性可用于读取系统剪贴板connection属性用于检测网络连接状况deviceMemory属性用于查看可用内存等。如果当前浏览器支持WebGPUNavigator对象上会存在一个gpu属性类型为GPU。因此可以用以下代码来检测浏览器是否支持WebGPU// 检查浏览器是否支持WebGPUif(!navigator.gpu){thrownewError(浏览器不支持WebGPU。);}gpu属性提供了两个重要的方法方法说明requestAdapter()返回一个Promise解析为一个GPUAdapter对象若失败则解析为nullgetPreferredCanvasFormat()返回一个字符串表示当前浏览器/设备下最适合用于画布渲染的纹理格式需要特别注意浏览器支持WebGPU这一API并不代表客户端硬件一定支持WebGPU所需的渲染与计算能力。必须实际调用requestAdapter()来进行确认如// 访问GPU适配器constadapterawaitnavigator.gpu.requestAdapter();if(!adapter){thrownewError(没有找到合适的GPUAdapter。);}GPUAdapter代表了对客户端某个物理或软件模拟的GPU实现的一次抽象访问它本身并不直接执行渲染或计算而是用来查询设备能力并进一步获取GPUDevice。关于适配器信息在早期版本的规范中需要通过异步方法adapter.requestAdapterInfo()获取设备信息在当前的WebGPU标准中这一信息已改为通过只读属性adapter.info类型为GPUAdapterInfo同步获取不再需要await。GPUAdapterInfo包含以下只读字段属性类型描述vendorStringGPU的制造商标识如nvidia、amd、inteldeviceString设备名称标识architectureString设备架构标识descriptionString适配器的描述信息这些信息在应用需要针对不同厂商GPU执行不同代码路径例如规避某些驱动缺陷时非常有用。GPUAdapter还有两个额外的属性features是一个GPUSupportedFeatures对象类似Set的结构列出了该GPU所支持的可选特性limits是一个GPUSupportedLimits对象描述了GPU在各项操作参数上的最大/最小取值范围可以使用adapter.features.has(featureName)判断设备是否支持某项特性也可以直接遍历features集合获取全部已支持特性。下表列出了WebGPU中常见的可选特性特性ID描述depth-clip-control允许应用使用类似OpenGL的深度范围[-1, 1]而非WebGPU默认的[0, 1]depth32float-stencil8深度/模板缓冲区中的元素可以是32位浮点深度值加8位模板值texture-compression-bc支持使用块压缩BC算法压缩的纹理texture-compression-etc2支持使用Ericsson纹理压缩2ETC2算法压缩的纹理texture-compression-astc支持使用自适应可伸缩纹理压缩ASTC算法压缩的纹理indirect-first-instance允许间接绘制调用indirect draw中的firstInstance参数设置为大于0的值shader-f16着色器中可以访问16位半精度浮点数f16rg11b10ufloat-renderable允许将rg11b10ufloat用作可渲染的纹理格式bgra8unorm-storage允许创建像素格式为bgra8unorm的存储纹理storage texturefloat32-filterableGPU可以对32位浮点纹理进行线性过滤平滑处理更多可用功能见MDN GPUSupportedFeatures文档。当然GPUAdapter最重要的作用还是提供访问GPUDevice。GPUDevice可以通过以下代码获取GPUDevice// 访问GPUconstdeviceawaitadapter.requestDevice();if(!device){thrownewError(创建GPUDevice失败。);}GPUDevice代表了应用与客户端GPU之间的一条逻辑连接在WebGPU开发中处于绝对核心的位置。几乎所有WebGPU资源与操作对象都是通过它的方法创建的包括但不限于createBuffer()创建缓冲区buffer资源createTexture()/createSampler()创建纹理与采样器createShaderModule()创建着色器模块createBindGroup()/createBindGroupLayout()创建资源绑定组及其布局createRenderPipeline()/createComputePipeline()创建渲染管线与计算管线createCommandEncoder()创建指令编码器。此外GPUDevice还继承自EventTarget因此可以通过device.addEventListener(uncapturederror, ...)监听未被捕获的错误同时它还提供了一个device.lostPromise可用于监控设备连接意外丢失的情况。这些细节超出了本章范围后续章节会陆续展开。GPUCommandEncoderGPUCommandEncoder负责创建并临时存储将要发送给GPU的一系列指令它通过调用GPUDevice上的方法创建// 创建command encoderencoderdevice.createCommandEncoder();if(!encoder){thrownewError(创建GPUCommandEncoder失败。);}有了这个对象就可以定义要发送给设备执行的具体操作了。它的方法可以分为两大类1开启子编码器 / 完成编码最常用的三个方法方法描述beginRenderPass()开始编码一个渲染通道返回GPURenderPassEncoder用于定义图形渲染相关操作beginComputePass()开始编码一个计算通道返回GPUComputePassEncoder用于定义通用计算相关操作finish()结束该编码器全部指令的录入返回一个GPUCommandBuffer随后即可提交给设备队列执行2资源操作与调试辅助方法除以上三个核心方法外GPUCommandEncoder还提供了下列方法用于在缓冲区/纹理之间拷贝数据以及插入调试信息方法描述clearBuffer()将缓冲区内容清零copyBufferToBuffer()在两个缓冲区之间拷贝数据copyBufferToTexture()将缓冲区数据拷贝到纹理copyTextureToBuffer()将纹理数据拷贝到缓冲区copyTextureToTexture()在两个纹理之间拷贝数据pushDebugGroup()/popDebugGroup()开启/结束一个带标签的调试分组便于在调试工具中定位指令insertDebugMarker()在指令序列中插入一条调试标记resolveQuerySet()将查询集合如时间戳查询、遮挡查询的结果写入缓冲区writeTimestamp()向查询集合写入一个时间戳用于性能分析其中finish()是每个非平凡WebGPU应用都必须调用的方法它将编码器已录入的全部数据打包为一个GPUCommandBuffer。一旦这个缓冲区被提交到设备队列GPU便会按顺序开始执行其中的指令。GPUQueueGPUDevice还有一个重要的只读属性queue用于访问与该设备关联的GPUQueue。应用需要显式调用队列上的方法才能真正触发GPU执行操作。其常用方法包括writeBuffer(buffer, bufferOffset, data, ...)直接向某个GPU缓冲区写入更新数据writeTexture(destination, data, dataLayout, size)直接向某个GPU纹理写入更新数据submit(commandBuffers)将一个或多个指令缓冲区提交给GPU执行。调用submit()是整个流程中最关键的一步——GPU会依据其中的指令真正执行图形渲染或通用计算任务。提交之后应用还可以调用queue.onSubmittedWorkDone()返回一个Promise用来在队列中已提交的全部工作执行完毕后得到通知从而监控执行进度。Canvas元素canvas是用于承载图形显示的HTML元素。当WebGPU执行渲染操作时最终生成的图形会显示在与之关联的画布中。因此若要用WebGPU进行图形渲染通常需要以下三个步骤在HTML文档中定义一个canvas元素在JavaScript中获取该元素对应的HTMLCanvasElement对象从该元素获取一个渲染上下文并对其进行配置。默认情况下canvas元素显示为一块空白矩形。要设置其显示内容需要先在JavaScript中获取该元素——既可以通过标签选择document.querySelector(canvas)也可以通过ID选择document.getElementById(id)——随后调用该元素上的getContext()方法获得一个用于绘制内容的上下文对象。Canvas Context需要注意WebGPU只是设置canvas显示内容的众多方式之一此外还可以使用WebGL、2D位图绘制API或简单的2D绘图上下文2d。因此需要在调用getContext()时明确指定所需的上下文类型。对于WebGPU应用应传入字符串webgpuconstcanvasdocument.getElementById(canvas_example);constcontextcanvas.getContext(webgpu);由此得到的context对象类型为GPUCanvasContext它提供了一个关键方法——configure()——用于设置渲染上下文的具体行为。传给configure()的配置对象GPUCanvasConfiguration主要包含以下字段属性是否必需描述device是用于控制该画布渲染的GPUDeviceformat是从画布中获取的纹理所使用的像素格式决定了WebGPU应以何种像素格式向画布输出内容usage否描述从画布获取的纹理将如何被使用如GPUTextureUsage.RENDER_ATTACHMENT默认已包含渲染附件用途alphaMode否控制透明度/合成方式opaque或premultipliedcolorSpace否获取纹理的色彩空间viewFormats否允许创建的附加视图格式数组其中最核心的是device与format这两个属性device应设置为前面获取的GPUDeviceformat则决定了WebGPU向画布输出内容时采用的像素格式。由于不同硬件/操作系统所支持的最佳纹理格式并不完全相同通常不需要手动指定而是通过调用navigator.gpu.getPreferredCanvasFormat()获取当前系统的最优格式返回值为rgba8unorm或bgra8unorm之一再将其赋给format// 获取canvas对象constcanvasdocument.getElementById(mycanvas);// 获取该canvas的WebGPU渲染上下文constcontextcanvas.getContext(webgpu);// 获取当前系统下最优的纹理像素格式constcanvasFormatnavigator.gpu.getPreferredCanvasFormat();// 使用device与format配置渲染上下文context.configure({device:device,format:canvasFormat,});配置完成后此后由GPUDevice发起的渲染操作产生的图形便会显示在这个已关联的画布上。一个完整的初始化流程综合以上内容下面给出一个完整、可直接运行的WebGPU初始化流程示例仅完成对象创建与画布关联尚未涉及具体的渲染或计算指令asyncfunctioninitWebGPU(){// 检查是否支持WebGPUif(!navigator.gpu){thrownewError(浏览器不支持WebGPU。);}// 获取GPUAdapterconstadapterawaitnavigator.gpu.requestAdapter();if(!adapter){thrownewError(没有找到GPUAdapter);}// 获取GPU设备constdeviceawaitadapter.requestDevice();if(!device){thrownewError(GPUDevice创建失败);}// 创建指令编码器constencoderdevice.createCommandEncoder();if(!encoder){thrownewError(GPUCommandEncoder创建失败);}// 获取canvas对象constcanvasdocument.getElementById(canvas_example);if(!canvas){thrownewError(无法获取页面的canvas对象);}// 获取canvas的WebGPU上下文constcontextcanvas.getContext(webgpu);// 获取最佳的像素格式constcanvasFormatnavigator.gpu.getPreferredCanvasFormat();// 配置上下文context.configure({device:device,format:canvasFormat,});}initWebGPU();这就是我们在系列文章的第一篇WebGPU的因承和编程范式中定义的初始化函数。至此我们已经完成了WebGPU应用最基础的初始化环节确认浏览器与硬件支持能力、建立与GPU的逻辑连接、创建指令编码器并将渲染目标canvas与设备关联起来。在下一篇Part3: 图形渲染中我们会介绍如何在GPU显存中定义数据以及如何定义与突然渲染有关的渲染通道。